// return proportion of radiation blocked by shielding public static double Shielding(Vessel v) { return(Cache.VesselInfo(v).shielding); }
void ToEVA(GameEvents.FromToAction <Part, Part> data) { Cache.PurgeObjects(data.from.vessel); Cache.PurgeObjects(data.to.vessel); // get total crew in the origin vessel double tot_crew = Lib.CrewCount(data.from.vessel) + 1.0; // get vessel resources handler Vessel_resources resources = ResourceCache.Get(data.from.vessel); // setup supply resources capacity in the eva kerbal Profile.SetupEva(data.to); String prop_name = Lib.EvaPropellantName(); // for each resource in the kerbal for (int i = 0; i < data.to.Resources.Count; ++i) { // get the resource PartResource res = data.to.Resources[i]; // eva prop is handled differently if (res.resourceName == prop_name) { continue; } double quantity = Math.Min(resources.Info(data.from.vessel, res.resourceName).amount / tot_crew, res.maxAmount); // remove resource from vessel quantity = data.from.RequestResource(res.resourceName, quantity); // add resource to eva kerbal data.to.RequestResource(res.resourceName, -quantity); } // take as much of the propellant as possible. just imagine: there are 1.3 units left, and 12 occupants // in the ship. you want to send out an engineer to fix the chemical plant that produces monoprop, // and have to get from one end of the station to the other with just 0.1 units in the tank... // nope. double evaPropQuantity = data.from.RequestResource(prop_name, Lib.EvaPropellantCapacity()); // We can't just add the monoprop here, because that doesn't always work. It might be related // to the fact that stock KSP wants to add 5 units of monoprop to new EVAs. Instead of fighting KSP here, // we just let it do it's thing and set our amount later in EVA.cs - which seems to work just fine. // don't put that into Cache.VesselInfo because that can be deleted before we get there Cache.SetVesselObjectsCache(data.to.vessel, "eva_prop", evaPropQuantity); // Airlock loss resources.Consume(data.from.vessel, "Nitrogen", PreferencesLifeSupport.Instance.evaAtmoLoss, "airlock"); // show warning if there is little or no EVA propellant in the suit if (evaPropQuantity <= 0.05 && !Lib.Landed(data.from.vessel)) { Message.Post(Severity.danger, Lib.BuildString("There isn't any <b>", prop_name, "</b> in the EVA suit"), "Don't let the ladder go!"); } // turn off headlamp light, to avoid stock bug that show them for a split second when going on eva KerbalEVA kerbal = data.to.FindModuleImplementing <KerbalEVA>(); EVA.HeadLamps(kerbal, false); // execute script DB.Vessel(data.from.vessel).computer.Execute(data.from.vessel, ScriptType.eva_out); }
public static void BackgroundUpdate(Vessel vessel, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, KerbalismScansat kerbalismScansat, Part part_prefab, VesselData vd, ResourceInfo ec, double elapsed_s) { List <ProtoPartModuleSnapshot> scanners = Cache.VesselObjectsCache <List <ProtoPartModuleSnapshot> >(vessel, "scansat_" + p.flightID); if (scanners == null) { scanners = Lib.FindModules(p, "SCANsat"); if (scanners.Count == 0) { scanners = Lib.FindModules(p, "ModuleSCANresourceScanner"); } Cache.SetVesselObjectsCache(vessel, "scansat_" + p.flightID, scanners); } if (scanners.Count == 0) { return; } var scanner = scanners[0]; bool is_scanning = Lib.Proto.GetBool(scanner, "scanning"); if (is_scanning && kerbalismScansat.ec_rate > double.Epsilon) { ec.Consume(kerbalismScansat.ec_rate * elapsed_s, "scanner"); } if (!Features.Science) { if (is_scanning && ec.Amount < double.Epsilon) { SCANsat.StopScanner(vessel, scanner, part_prefab); is_scanning = false; // remember disabled scanner vd.scansat_id.Add(p.flightID); // give the user some feedback if (vd.cfg_ec) { Message.Post(Lib.BuildString("SCANsat sensor was disabled on <b>", vessel.vesselName, "</b>")); } } else if (vd.scansat_id.Contains(p.flightID)) { // if there is enough ec // note: comparing against amount in previous simulation step // re-enable at 25% EC if (ec.Level > 0.25) { // re-enable the scanner SCANsat.ResumeScanner(vessel, m, part_prefab); is_scanning = true; // give the user some feedback if (vd.cfg_ec) { Message.Post(Lib.BuildString("SCANsat sensor resumed operations on <b>", vessel.vesselName, "</b>")); } } } // forget active scanners if (is_scanning) { vd.scansat_id.Remove(p.flightID); } return; } // if(!Feature.Science) string body_name = Lib.Proto.GetString(m, "body_name"); int sensorType = (int)Lib.Proto.GetUInt(m, "sensorType"); double body_coverage = Lib.Proto.GetDouble(m, "body_coverage"); double warp_buffer = Lib.Proto.GetDouble(m, "warp_buffer"); double new_coverage = SCANsat.Coverage(sensorType, vessel.mainBody); if (body_name == vessel.mainBody.name && new_coverage < body_coverage) { // SCANsat sometimes reports a coverage of 0, which is wrong new_coverage = body_coverage; } if (vessel.mainBody.name != body_name) { body_name = vessel.mainBody.name; body_coverage = new_coverage; } else { double coverage_delta = new_coverage - body_coverage; body_coverage = new_coverage; if (is_scanning) { ExperimentInfo expInfo = ScienceDB.GetExperimentInfo(kerbalismScansat.experimentType); SubjectData subject = ScienceDB.GetSubjectData(expInfo, vd.VesselSituations.GetExperimentSituation(expInfo)); if (subject == null) { return; } double size = expInfo.DataSize * coverage_delta / 100.0; // coverage is 0-100% size += warp_buffer; if (size > double.Epsilon) { // store what we can foreach (var d in Drive.GetDrives(vd)) { var available = d.FileCapacityAvailable(); var chunk = Math.Min(size, available); if (!d.Record_file(subject, chunk, true)) { break; } size -= chunk; if (size < double.Epsilon) { break; } } } if (size > double.Epsilon) { // we filled all drives up to the brim but were unable to store everything if (warp_buffer < double.Epsilon) { // warp buffer is empty, so lets store the rest there warp_buffer = size; size = 0; } else { // warp buffer not empty. that's ok if we didn't get new data if (coverage_delta < double.Epsilon) { size = 0; } // else we're scanning too fast. stop. } } // we filled all drives up to the brim but were unable to store everything // cancel scanning and annoy the user if (size > double.Epsilon || ec.Amount < double.Epsilon) { warp_buffer = 0; SCANsat.StopScanner(vessel, scanner, part_prefab); vd.scansat_id.Add(p.flightID); if (vd.cfg_ec) { Message.Post(Lib.BuildString("SCANsat sensor was disabled on <b>", vessel.vesselName, "</b>")); } } } else if (vd.scansat_id.Contains(p.flightID)) { if (ec.Level >= 0.25 && (vd.DrivesFreeSpace / vd.DrivesCapacity > 0.9)) { SCANsat.ResumeScanner(vessel, scanner, part_prefab); vd.scansat_id.Remove(p.flightID); if (vd.cfg_ec) { Message.Post(Lib.BuildString("SCANsat sensor resumed operations on <b>", vessel.vesselName, "</b>")); } } } } Lib.Proto.Set(m, "warp_buffer", warp_buffer); Lib.Proto.Set(m, "body_coverage", body_coverage); Lib.Proto.Set(m, "body_name", body_name); }
void FixedUpdate() { // remove control locks in any case Misc.ClearLocks(); // do nothing if paused if (Lib.IsPaused()) { return; } // maintain elapsed_s, converting to double only once // and detect warp blending double fixedDeltaTime = TimeWarp.fixedDeltaTime; if (Math.Abs(fixedDeltaTime - elapsed_s) > double.Epsilon) { warp_blending = 0; } else { ++warp_blending; } elapsed_s = fixedDeltaTime; // evict oldest entry from vessel cache Cache.Update(); // store info for oldest unloaded vessel double last_time = 0.0; Vessel last_v = null; Vessel_info last_vi = null; VesselData last_vd = null; Vessel_resources last_resources = null; // for each vessel foreach (Vessel v in FlightGlobals.Vessels) { // get vessel info from the cache Vessel_info vi = Cache.VesselInfo(v); // set locks for active vessel if (v.isActiveVessel) { Misc.SetLocks(v, vi); } // maintain eva dead animation and helmet state if (v.loaded && v.isEVA) { EVA.Update(v); } // keep track of rescue mission kerbals, and gift resources to their vessels on discovery if (v.loaded && vi.is_vessel) { // manage rescue mission mechanics Misc.ManageRescueMission(v); } // do nothing else for invalid vessels if (!vi.is_valid) { continue; } // get vessel data from db VesselData vd = DB.Vessel(v); // get resource cache Vessel_resources resources = ResourceCache.Get(v); // if loaded if (v.loaded) { // get most used resource Resource_info ec = resources.Info(v, "ElectricCharge"); // show belt warnings Radiation.BeltWarnings(v, vi, vd); // update storm data Storm.Update(v, vi, vd, elapsed_s); Communications.Update(v, vi, vd, ec, elapsed_s); // Habitat equalization ResourceBalance.Equalizer(v); // transmit science data Science.Update(v, vi, vd, resources, elapsed_s); // apply rules Profile.Execute(v, vi, vd, resources, elapsed_s); // apply deferred requests resources.Sync(v, elapsed_s); // call automation scripts vd.computer.Automate(v, vi, resources); // remove from unloaded data container unloaded.Remove(vi.id); } // if unloaded else { // get unloaded data, or create an empty one Unloaded_data ud; if (!unloaded.TryGetValue(vi.id, out ud)) { ud = new Unloaded_data(); unloaded.Add(vi.id, ud); } // accumulate time ud.time += elapsed_s; // maintain oldest entry if (ud.time > last_time) { last_time = ud.time; last_v = v; last_vi = vi; last_vd = vd; last_resources = resources; } } } // at most one vessel gets background processing per physics tick // if there is a vessel that is not the currently loaded vessel, then // we will update the vessel whose most recent background update is the oldest if (last_v != null) { // get most used resource Resource_info last_ec = last_resources.Info(last_v, "ElectricCharge"); // show belt warnings Radiation.BeltWarnings(last_v, last_vi, last_vd); // update storm data Storm.Update(last_v, last_vi, last_vd, last_time); Communications.Update(last_v, last_vi, last_vd, last_ec, last_time); // apply rules Profile.Execute(last_v, last_vi, last_vd, last_resources, last_time); // simulate modules in background Background.Update(last_v, last_vi, last_vd, last_resources, last_time); // transmit science data Science.Update(last_v, last_vi, last_vd, last_resources, last_time); // apply deferred requests last_resources.Sync(last_v, last_time); // call automation scripts last_vd.computer.Automate(last_v, last_vi, last_resources); // remove from unloaded data container unloaded.Remove(last_vi.id); } // update storm data for one body per-step if (storm_bodies.Count > 0) { storm_bodies.ForEach(k => k.time += elapsed_s); Storm_data sd = storm_bodies[storm_index]; Storm.Update(sd.body, sd.time); sd.time = 0.0; storm_index = (storm_index + 1) % storm_bodies.Count; } }
public override void OnLoad(ConfigNode node) { // deserialize data DB.Load(node); Communications.NetworkInitialized = false; Communications.NetworkInitializing = false; // initialize everything just once if (!initialized) { // add supply resources to pods Profile.SetupPods(); // initialize subsystems Cache.Init(); ResourceCache.Init(); Radiation.Init(); Science.Init(); LineRenderer.Init(); ParticleRenderer.Init(); Highlighter.Init(); UI.Init(); #if !KSP170 && !KSP16 && !KSP15 && !KSP14 Serenity.Init(); #endif // prepare storm data foreach (CelestialBody body in FlightGlobals.Bodies) { if (Storm.Skip_body(body)) { continue; } Storm_data sd = new Storm_data { body = body }; storm_bodies.Add(sd); } // various tweaks to the part icons in the editor Misc.TweakPartIcons(); // setup callbacks callbacks = new Callbacks(); // everything was initialized initialized = true; } // detect if this is a different savegame if (DB.uid != savegame_uid) { // clear caches Cache.Clear(); ResourceCache.Clear(); Message.all_logs.Clear(); // sync main window pos from db UI.Sync(); // remember savegame id savegame_uid = DB.uid; } Science.ClearDeferred(); }
// --- ENVIRONMENT ---------------------------------------------------------- // return true if the vessel specified is in sunlight public static bool InSunlight(Vessel v) { return(Cache.VesselInfo(v).sunlight > double.Epsilon); }
public void FixedUpdate() { // do nothing in the editor if (Lib.IsEditor()) { return; } // do nothing if vessel is invalid if (!Cache.VesselInfo(vessel).is_valid) { return; } // get ec handler Resource_info ec = ResourceCache.Info(vessel, "ElectricCharge"); // if experiment is active if (recording) { // detect conditions // - comparing against amount in previous step bool has_ec = ec.amount > double.Epsilon; bool has_operator = operator_cs.Check(vessel); string sit = Science.Situation(vessel, situations); // deduce issues issue = string.Empty; if (sit.Length == 0) { issue = "invalid situation"; } else if (!has_operator) { issue = "no operator"; } else if (!has_ec) { issue = "missing <b>EC</b>"; } // if there are no issues if (issue.Length == 0) { // generate subject id string subject_id = Science.Generate_subject(experiment, vessel.mainBody, sit, Science.Biome(vessel, sit), Science.Multiplier(vessel, sit)); // record in drive if (transmissible) { DB.Vessel(vessel).drive.Record_file(subject_id, data_rate * Kerbalism.elapsed_s); } else { DB.Vessel(vessel).drive.Record_sample(subject_id, data_rate * Kerbalism.elapsed_s); } // consume ec ec.Consume(ec_rate * Kerbalism.elapsed_s); } } }
public static void NetMan(this Panel p, Vessel v) { // avoid corner-case when this is called in a lambda after scene changes v = FlightGlobals.FindVessel(v.id); // if vessel doesn't exist anymore, leave the panel empty if (v == null) { return; } // get info from the cache Vessel_Info vi = Cache.VesselInfo(v); // if not a valid vessel, leave the panel empty if (!vi.is_valid) { return; } // set metadata p.Title(Lib.BuildString(Lib.Ellipsis(v.vesselName, 20), " <color=#cccccc>NETWORK INFO</color>")); // time-out simulation #if !DEBUG if (p.Timeout(vi)) { return; } #endif p.SetSection("ADAPTORS"); p.Set_IsFreqSelector(true); // store all devices var devices = new Dictionary <uint, NetDevice>(); // store device being added NetDevice adap; // loaded vessel if (v.loaded) { foreach (NetworkAdaptor m in Lib.FindModules <NetworkAdaptor>(v)) { adap = new NetAdaptorDevice(m); // add the device // - multiple same-type components in the same part will have the same id, and are ignored if (!devices.ContainsKey(adap.Id())) { devices.Add(adap.Id(), adap); } } } else { // store data required to support multiple modules of same type in a part var PD = new Dictionary <string, Lib.module_prefab_data>(); // for each part foreach (ProtoPartSnapshot proto in v.protoVessel.protoPartSnapshots) { // get part prefab (required for module properties) Part part_prefab = PartLoader.getPartInfoByName(proto.partName).partPrefab; // get all module prefabs var module_prefabs = part_prefab.FindModulesImplementing <PartModule>(); // clear module indexes PD.Clear(); // for each module foreach (ProtoPartModuleSnapshot m in proto.modules) { // get the module prefab // if the prefab doesn't contain this module, skip it PartModule module_prefab = Lib.ModulePrefab(module_prefabs, m.moduleName, PD); if (!module_prefab) { continue; } // if the module is disabled, skip it // note: this must be done after ModulePrefab is called, so that indexes are right if (!Lib.Proto.GetBool(m, "isEnabled")) { continue; } if (m.moduleName == "NetworkAdaptor") { adap = new ProtoNetAdaptorDevice(m, proto.flightID, v); // add the device // - multiple same-type components in the same part will have the same id, and are ignored if (!devices.ContainsKey(adap.Id())) { devices.Add(adap.Id(), adap); } } } } } // dict order by device name // for each device foreach (var pair in devices.OrderBy(x => x.Value.Name())) { // render device entry NetDevice dev = pair.Value; // Get how many antennas share the same freq AntennasByFrequency x = null; if (vi.antenna.antennasByFreq.ContainsKey(dev.InfoFreq())) { x = vi.antenna.antennasByFreq[dev.InfoFreq()]; } p.SetContent(dev.Name(), dev.InfoRate(), string.Empty, null, () => Highlighter.Set(dev.Part(), Color.cyan), dev.InfoFreq()); p.SetIcon(Icons.left_freq, "Decrease", () => { if (dev.InfoFreq() > 0) // && x != null { //if (x.antCount == 1 && x.countConnections > 0) //{ // Lib.Popup( // "Warning!", // Lib.BuildString("This is the last antenna on '", dev.InfoFreq().ToString(), // "' frequency.\nYou will lost connection in this frequency.\nDo you really want to remove this frequency from this vessel?"), // new DialogGUIButton("Remove", () => dev.ChangeFreq(-1)), // new DialogGUIButton("Keep it", () => { })); //} //else dev.ChangeFreq(-1); } }); p.SetIcon(Icons.right_freq, "Increase", () => { if (dev.InfoFreq() < 99) // && x != null { //if (x.antCount == 1 && x.countConnections > 0) //{ // Lib.Popup( // "Warning!", // Lib.BuildString("This is the last antenna on '", dev.InfoFreq().ToString(), // "' frequency.\nYou will lost connection in this frequency.\nDo you really want to remove this frequency from this vessel?"), // new DialogGUIButton("Remove", () => dev.ChangeFreq(+1)), // new DialogGUIButton("Keep it", () => { })); //} //else dev.ChangeFreq(+1); } }); } p.SetSection("FREQUENCY(S) DETAIL"); foreach (short key in vi.antenna.antennasByFreq.Keys) { double range = vi.antenna.antennasByFreq[key].antennaPower; double rate = vi.antenna.antennasByFreq[key].antennaRate; Render_ConnectionDetail(p, range, rate, key); } }
public static void ProtoBreak(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m) { // get reliability module prefab string type = Lib.Proto.GetString(m, "type", string.Empty); Reliability reliability = p.partPrefab.FindModulesImplementing <Reliability>().Find(k => k.type == type); if (reliability == null) { return; } // if manned, or if safemode didn't trigger if (Cache.VesselInfo(v).crew_capacity > 0 || Lib.RandomDouble() > Settings.SafeModeChance) { // flag as broken Lib.Proto.Set(m, "broken", true); // determine if this is a critical failure bool critical = Lib.RandomDouble() < Settings.CriticalChance; Lib.Proto.Set(m, "critical", critical); // for each associated module foreach (var proto_module in p.modules.FindAll(k => k.moduleName == reliability.type)) { // disable the module Lib.Proto.Set(proto_module, "isEnabled", false); } // type-specific hacks switch (reliability.type) { case "ProcessController": foreach (ProcessController pc in p.partPrefab.FindModulesImplementing <ProcessController>()) { ProtoPartResourceSnapshot res = p.resources.Find(k => k.resourceName == pc.resource); if (res != null) { res.flowState = false; } } break; } // show message broken_msg(v, reliability.title, critical); } // safe mode else { // reset age Lib.Proto.Set(m, "last", 0.0); Lib.Proto.Set(m, "next", 0.0); // notify user safemode_msg(v, reliability.title); } // in any case, incentive redundancy if (Settings.IncentiveRedundancy) { incentive_redundancy(v, reliability.redundancy); } }
public static void Update(Vessel v, Vessel_info vi, VesselData vd, Resource_info ec, double elapsed_s) { if (!Lib.IsVessel(v)) { return; } // consume ec for transmitters ec.Consume(vi.connection.ec * elapsed_s, "comms"); Cache.WarpCache(v).dataCapacity = vi.connection.rate * elapsed_s; // do nothing if network is not ready if (!NetworkInitialized) { return; } // maintain and send messages // - do not send messages during/after solar storms // - do not send messages for EVA kerbals if (!v.isEVA && v.situation != Vessel.Situations.PRELAUNCH) { if (!vd.msg_signal && !vi.connection.linked) { vd.msg_signal = true; if (vd.cfg_signal) { string subtext = Localizer.Format("#KERBALISM_UI_transmissiondisabled"); switch (vi.connection.status) { case LinkStatus.plasma: subtext = Localizer.Format("#KERBALISM_UI_Plasmablackout"); break; case LinkStatus.storm: subtext = Localizer.Format("#KERBALISM_UI_Stormblackout"); break; default: if (vi.crew_count == 0) { switch (Settings.UnlinkedControl) { case UnlinkedCtrl.none: subtext = Localizer.Format("#KERBALISM_UI_noctrl"); break; case UnlinkedCtrl.limited: subtext = Localizer.Format("#KERBALISM_UI_limitedcontrol"); break; } } break; } Message.Post(Severity.warning, Lib.BuildString(Localizer.Format("#KERBALISM_UI_signallost"), " <b>", v.vesselName, "</b>"), subtext); } } else if (vd.msg_signal && vi.connection.linked) { vd.msg_signal = false; if (vd.cfg_signal) { Message.Post(Severity.relax, Lib.BuildString("<b>", v.vesselName, "</b> ", Localizer.Format("#KERBALISM_UI_signalback")), vi.connection.status == LinkStatus.direct_link ? Localizer.Format("#KERBALISM_UI_directlink") : Lib.BuildString(Localizer.Format("#KERBALISM_UI_relayby"), " <b>", vi.connection.target_name, "</b>")); } } } }
public void FixedUpdate() { // if part is manned (even in the editor), force enabled if (Lib.IsCrewed(part) && state != State.enabled) { Set_flow(true); state = State.pressurizing; // Equalize run only in Flight mode needEqualize = Lib.IsFlight(); } perctDeployed = Lib.Level(part, "Atmosphere", true); // Only handle crewTransferred & Toggle, this way has less calls in FixedUpdate // CrewTransferred Event occur after FixedUpdate, this must be check in crewtransferred if (FixIVA) { if (Get_inflate_string().Length == 0) // it is not inflatable (We always going to show and cross those habitats) { SetPassable(true); UpdateIVA(true); } else { // Inflatable modules shows IVA and are passable only in 99.9999% deployed SetPassable(Lib.IsCrewed(part) || Math.Truncate(Math.Abs((perctDeployed + ResourceBalance.precision) - 1.0) * 100000) / 100000 <= ResourceBalance.precision); UpdateIVA(Math.Truncate(Math.Abs((perctDeployed + ResourceBalance.precision) - 1.0) * 100000) / 100000 <= ResourceBalance.precision); } FixIVA = false; } if (max_pressure < Settings.PressureThreshold && Lib.IsFlight()) { var vi = Cache.VesselInfo(vessel); vi.max_pressure = Math.Min(vi.max_pressure, max_pressure); } // state machine switch (state) { case State.enabled: // In case it is losting pressure if (perctDeployed < Settings.PressureThreshold) { if (Get_inflate_string().Length != 0) // it is inflatable { SetPassable(false || Lib.IsCrewed(part)); // Prevent to not lock a Kerbal into a the part UpdateIVA(false); } needEqualize = true; state = State.pressurizing; } break; case State.disabled: break; case State.pressurizing: state = Pressurizing(); break; case State.depressurizing: // Just do Venting when has no gravityRing or when the gravity ring is not spinning. if (hasGravityRing && !gravityRing.Is_rotating()) { state = Depressurizing(); } else if (!hasGravityRing) { state = Depressurizing(); } break; } }
// called every simulation step void FixedUpdate() { // do nothing if paused if (Lib.IsPaused()) return; // do nothing in the editors and the menus if (!Lib.SceneIsGame()) return; // do nothing if db isn't ready if (!DB.Ready()) return; // get elapsed time double elapsed_s = Kerbalism.elapsed_s; // evict oldest entry from vessel cache cache.update(); // store info for oldest unloaded vessel double last_time = 0.0; Vessel last_v = null; vessel_info last_vi = null; vessel_data last_vd = null; vessel_resources last_resources = null; // for each vessel foreach(Vessel v in FlightGlobals.Vessels) { // get vessel info from the cache vessel_info vi = Cache.VesselInfo(v); // skip invalid vessels if (!vi.is_valid) continue; // get vessel data from db vessel_data vd = DB.VesselData(v.id); // get resource cache vessel_resources resources = ResourceCache.Get(v); // if loaded if (v.loaded) { // show belt warnings Radiation.beltWarnings(v, vi, vd); // update storm data storm.update(v, vi, vd, elapsed_s); // consume relay EC and show signal warnings signal.update(v, vi, vd, resources, elapsed_s * vi.time_dilation); // apply rules Rule.applyRules(v, vi, vd, resources, elapsed_s * vi.time_dilation); // apply deferred requests resources.Sync(v, elapsed_s); // update computer vd.computer.update(v, elapsed_s); // remove from unloaded data container unloaded.Remove(vi.id); } // if unloaded else { // get unloaded data, or create an empty one unloaded_data ud; if (!unloaded.TryGetValue(vi.id, out ud)) { ud = new unloaded_data(); unloaded.Add(vi.id, ud); } // accumulate time ud.time += elapsed_s; // maintain oldest entry if (ud.time > last_time) { last_time = ud.time; last_v = v; last_vi = vi; last_vd = vd; last_resources = resources; } } } // if the oldest unloaded vessel was selected if (last_v != null) { // show belt warnings Radiation.beltWarnings(last_v, last_vi, last_vd); // decay unloaded vessels inside atmosphere Kerbalism.atmosphereDecay(last_v, last_vi, last_time); // update storm data storm.update(last_v, last_vi, last_vd, last_time); // consume relay EC and show signal warnings signal.update(last_v, last_vi, last_vd, last_resources, last_time * last_vi.time_dilation); // apply rules Rule.applyRules(last_v, last_vi, last_vd, last_resources, last_time * last_vi.time_dilation); // simulate modules in background Background.update(last_v, last_vi, last_vd, last_resources, last_time * last_vi.time_dilation); // apply deferred requests last_resources.Sync(last_v, last_time); // update computer last_vd.computer.update(last_v, last_time); // remove from unloaded data container unloaded.Remove(last_vi.id); } // update storm data for one body per-step storm_bodies.ForEach(k => k.time += elapsed_s); storm_data sd = storm_bodies[storm_index]; storm.update(sd.body, sd.time); sd.time = 0.0; storm_index = (storm_index + 1) % storm_bodies.Count; }
public override void OnLoad(ConfigNode node) { // everything in there will be called only one time : the first time a game is loaded from the main menu if (!IsCoreGameInitDone) { try { // core game systems Sim.Init(); // find suns (Kopernicus support) Radiation.Init(); // create the radiation fields ScienceDB.Init(); // build the science database (needs Sim.Init() and Radiation.Init() first) Science.Init(); // register the science hijacker // static graphic components LineRenderer.Init(); ParticleRenderer.Init(); Highlighter.Init(); // UI Textures.Init(); // set up the icon textures UI.Init(); // message system, main gui, launcher KsmGui.KsmGuiMasterController.Init(); // setup the new gui framework // part prefabs hacks Profile.SetupPods(); // add supply resources to pods Misc.PartPrefabsTweaks(); // part prefabs tweaks, must be called after ScienceDB.Init() // Create KsmGui windows new ScienceArchiveWindow(); // GameEvents callbacks Callbacks = new Callbacks(); } catch (Exception e) { string fatalError = "FATAL ERROR : Kerbalism core init has failed :" + "\n" + e.ToString(); Lib.Log(fatalError, Lib.LogLevel.Error); LoadFailedPopup(fatalError); } IsCoreGameInitDone = true; } // everything in there will be called every time a savegame (or a new game) is loaded from the main menu if (!IsSaveGameInitDone) { try { Cache.Init(); ResourceCache.Init(); // prepare storm data foreach (CelestialBody body in FlightGlobals.Bodies) { if (Storm.Skip_body(body)) { continue; } Storm_data sd = new Storm_data { body = body }; storm_bodies.Add(sd); } } catch (Exception e) { string fatalError = "FATAL ERROR : Kerbalism save game init has failed :" + "\n" + e.ToString(); Lib.Log(fatalError, Lib.LogLevel.Error); LoadFailedPopup(fatalError); } IsSaveGameInitDone = true; } // eveything else will be called on every OnLoad() call : // - save/load // - every scene change // - in various semi-random situations (thanks KSP) // Fix for background IMGUI textures being dropped on scene changes since KSP 1.8 Styles.ReloadBackgroundStyles(); // always clear the caches Cache.Clear(); ResourceCache.Clear(); // deserialize our database try { UnityEngine.Profiling.Profiler.BeginSample("Kerbalism.DB.Load"); DB.Load(node); UnityEngine.Profiling.Profiler.EndSample(); } catch (Exception e) { string fatalError = "FATAL ERROR : Kerbalism save game load has failed :" + "\n" + e.ToString(); Lib.Log(fatalError, Lib.LogLevel.Error); LoadFailedPopup(fatalError); } // I'm smelling the hacky mess in here. Communications.NetworkInitialized = false; Communications.NetworkInitializing = false; // detect if this is a different savegame if (DB.uid != savegame_uid) { // clear caches Message.all_logs.Clear(); // sync main window pos from db UI.Sync(); // remember savegame id savegame_uid = DB.uid; } Kerbalism.gameLoadTime = Time.time; }
// ctor public Vessel_Info(Vessel v, uint vessel_id, UInt64 inc) { // NOTE: anything used here can't in turn use cache, unless you know what you are doing // NOTE: you can't cache vessel position // at any point in time all vessel/body positions are relative to a different frame of reference // so comparing the current position of a vessel, with the cached one of another make no sense // associate with an unique incremental id this.inc = inc; // determine if this is a valid vessel is_vessel = Lib.IsVessel(v); if (!is_vessel) { return; } // determine if this is a rescue mission vessel is_rescue = Misc.IsRescueMission(v); if (is_rescue) { return; } // dead EVA are not valid vessels if (EVA.IsDead(v)) { return; } // shortcut for common tests is_valid = true; // generate id once id = vessel_id; // calculate crew info for the vessel crew_count = Lib.CrewCount(v); crew_capacity = Lib.CrewCapacity(v); // get vessel position Vector3d position = Lib.VesselPosition(v); // this should never happen again if (Vector3d.Distance(position, v.mainBody.position) < 1.0) { throw new Exception("Shit hit the fan for vessel " + v.vesselName); } // determine if in sunlight, calculate sun direction and distance sunlight = Sim.RaytraceBody(v, position, FlightGlobals.Bodies[0], out sun_dir, out sun_dist) ? 1.0 : 0.0; // at the two highest timewarp speed, the number of sun visibility samples drop to the point that // the quantization error first became noticeable, and then exceed 100% // to solve this, we switch to an analytical estimation of the portion of orbit that was in sunlight // - we check against timewarp rate, instead of index, to avoid issues during timewarp blending if (v.mainBody.flightGlobalsIndex != 0 && TimeWarp.CurrentRate > 1000.0f) { sunlight = 1.0 - Sim.ShadowPeriod(v) / Sim.OrbitalPeriod(v); } // environment stuff atmo_factor = Sim.AtmosphereFactor(v.mainBody, position, sun_dir); gamma_transparency = Sim.GammaTransparency(v.mainBody, v.altitude); underwater = Sim.Underwater(v); breathable = Sim.Breathable(v, underwater); landed = Lib.Landed(v); // temperature at vessel position temperature = Sim.Temperature(v, position, sunlight, atmo_factor, out solar_flux, out albedo_flux, out body_flux, out total_flux); temp_diff = Sim.TempDiff(temperature, v.mainBody, landed); // radiation radiation = Radiation.Compute(v, position, gamma_transparency, sunlight, out blackout, out magnetosphere, out inner_belt, out outer_belt, out interstellar); // extended atmosphere thermosphere = Sim.InsideThermosphere(v); exosphere = Sim.InsideExosphere(v); // malfunction stuff malfunction = Reliability.HasMalfunction(v); critical = Reliability.HasCriticalFailure(v); // signal info if (Features.KCommNet) { antenna = Cache.AntennaInfo(v); avoid_inf_recursion.Add(v.id); } else { kAntenna = new KAntennaInfo(v); avoid_inf_recursion.Add(v.id); } // TODO: Need to create a Signal integrated with CommNet connection = Signal.Connection(v, position, kAntenna, blackout, avoid_inf_recursion); transmitting = Science.Transmitting(v, connection.linked); relaying = Signal.Relaying(v, avoid_inf_recursion); avoid_inf_recursion.Remove(v.id); // habitat data volume = Habitat.Total_Volume(v); surface = Habitat.Total_Surface(v); pressure = Habitat.Pressure(v); poisoning = Habitat.Poisoning(v); shielding = Habitat.Shielding(v); living_space = Habitat.Living_Space(v); comforts = new Comforts(v, landed, crew_count > 1, true); // TODO: replace 'true' for connection.linked // data about greenhouses greenhouses = Greenhouse.Greenhouses(v); // other stuff gravioli = Sim.Graviolis(v); }
// return living space factor public static double LivingSpace(Vessel v) { return(Cache.VesselInfo(v).living_space); }
public static void Devman(this Panel p, Vessel v) { // avoid corner-case when this is called in a lambda after scene changes v = FlightGlobals.FindVessel(v.id); // if vessel doesn't exist anymore, leave the panel empty if (v == null) { return; } // get info from the cache Vessel_info vi = Cache.VesselInfo(v); // if not a valid vessel, leave the panel empty if (!vi.is_valid) { return; } // set metadata p.Title(Lib.BuildString(Lib.Ellipsis(v.vesselName, Styles.ScaleStringLength(20)), " <color=#cccccc>" + Localizer.Format("#KERBALISM_UI_devman") + "</color>")); p.Width(Styles.ScaleWidthFloat(355.0f)); p.paneltype = Panel.PanelType.scripts; // time-out simulation if (p.Timeout(vi)) { return; } // get devices Dictionary <uint, Device> devices = Computer.Boot(v); int deviceCount = 0; // direct control if (script_index == 0) { // draw section title and desc p.AddSection ( Localizer.Format("#KERBALISM_UI_devices"), Description(), () => p.Prev(ref script_index, (int)ScriptType.last), () => p.Next(ref script_index, (int)ScriptType.last), true ); // for each device foreach (var pair in devices) { // render device entry Device dev = pair.Value; if (!dev.IsVisible()) { continue; } p.AddContent(dev.Name(), dev.Info(), string.Empty, dev.Toggle, () => Highlighter.Set(dev.Part(), Color.cyan)); deviceCount++; } } // script editor else { // get script ScriptType script_type = (ScriptType)script_index; string script_name = script_type.ToString().Replace('_', ' ').ToUpper(); Script script = DB.Vessel(v).computer.Get(script_type); // draw section title and desc p.AddSection ( script_name, Description(), () => p.Prev(ref script_index, (int)ScriptType.last), () => p.Next(ref script_index, (int)ScriptType.last), true ); // for each device foreach (var pair in devices) { Device dev = pair.Value; if (!dev.IsVisible()) { continue; } // determine tribool state int state = !script.states.ContainsKey(pair.Key) ? -1 : !script.states[pair.Key] ? 0 : 1; // render device entry p.AddContent ( dev.Name(), state == -1 ? "<color=#999999>" + Localizer.Format("#KERBALISM_UI_dontcare") + " </color>" : state == 0 ? "<color=red>" + Localizer.Format("#KERBALISM_Generic_OFF") + "</color>" : "<color=cyan>" + Localizer.Format("#KERBALISM_Generic_ON") + "</color>", string.Empty, () => { switch (state) { case -1: script.Set(dev, true); break; case 0: script.Set(dev, null); break; case 1: script.Set(dev, false); break; } }, () => Highlighter.Set(dev.Part(), Color.cyan) ); deviceCount++; } } // no devices case if (deviceCount == 0) { p.AddContent("<i>no devices</i>"); } }
// return comfort factor public static double Comfort(Vessel v) { return(Cache.VesselInfo(v).comforts.factor); }
State equalize() { // in flight if (Lib.IsFlight()) { // shortcuts resource_info vessel_atmo = ResourceCache.Info(vessel, "Atmosphere"); PartResource hab_atmo = part.Resources["Atmosphere"]; // get level of atmosphere in vessel and part double vessel_level = vessel_atmo.level; double hab_level = Lib.Level(part, "Atmosphere", true); // equalization succeeded if the levels are the same // note: this behave correctly in the case the hab is the only enabled one or not if (Math.Abs(vessel_level - hab_level) < 0.01) { return(State.enabled); } // in case vessel pressure is dropping during equalization, it mean that pressure // control is not enough so we just enable the hab while not fully equalized if (vessel_atmo.rate < 0.0) { return(State.enabled); } // determine equalization speed // we deal with the case where a big hab is sucking all atmosphere from the rest of the vessel double amount = Math.Min(Cache.VesselInfo(vessel).volume, volume) * equalize_speed * Kerbalism.elapsed_s; // vessel pressure is higher if (vessel_level > hab_level) { // clamp amount to what's available in the vessel and what can fit in the part amount = Math.Min(amount, vessel_atmo.amount); amount = Math.Min(amount, hab_atmo.maxAmount - hab_atmo.amount); // consume from all enabled habs in the vessel vessel_atmo.Consume(amount); // produce in the part hab_atmo.amount += amount; } // vessel pressure is lower else { // consume from the part, clamp amount to what's available in the part amount = Math.Min(amount, hab_atmo.amount); hab_atmo.amount -= amount; // produce in all enabled habs in the vessel // (attempt recovery, but dump overboard if there is no capacity left) vessel_atmo.Produce(amount); } // equalization still in progress return(State.equalizing); } // in the editors else { // set amount to max capacity PartResource hab_atmo = part.Resources["Atmosphere"]; hab_atmo.amount = hab_atmo.maxAmount; // return new state return(State.enabled); } }
// return true if the vessel specified is inside a breathable atmosphere public static bool Breathable(Vessel v) { return(Cache.VesselInfo(v).breathable); }
// implement life support mechanics public void FixedUpdate() { // avoid case when DB isn't ready for whatever reason if (!DB.Ready()) return; // do nothing in the editors and the menus if (!Lib.SceneIsGame()) return; // do nothing if paused if (Lib.IsPaused()) return; // get time elapsed from last update double elapsed_s = TimeWarp.fixedDeltaTime; // for each vessel foreach(Vessel v in FlightGlobals.Vessels) { // skip invalid vessels if (!Lib.IsVessel(v)) continue; // skip dead eva kerbals if (EVA.IsDead(v)) continue; // get crew List<ProtoCrewMember> crew = v.loaded ? v.GetVesselCrew() : v.protoVessel.GetVesselCrew(); // get vessel info from the cache vessel_info info = Cache.VesselInfo(v); // get temperature difference // note: for gameplay reasons, climatization doesn't consume anything landed at home bool landed_home = Lib.Landed(v) && v.mainBody == FlightGlobals.GetHomeBody(); double temp_diff = landed_home ? 0.0 : Math.Abs(info.temperature - Settings.SurvivalTemperature); double temp_sign = landed_home ? 1.0 : info.temperature > Settings.SurvivalTemperature ? 1.0 : -1.0; // determine if inside breathable atmosphere bool breathable = BreathableAtmosphere(v); // for each crew foreach(ProtoCrewMember c in crew) { // get kerbal data kerbal_data kd = DB.KerbalData(c.name); // skip resque kerbals if (kd.resque == 1) continue; // skip disabled kerbals if (kd.disabled == 1) continue; // consume ec for climate control double ec_required = temp_diff * Settings.ElectricChargePerSecond * elapsed_s; double ec_consumed = Lib.RequestResource(v, "ElectricCharge", ec_required); double ec_perc = ec_required > 0.0 ? ec_consumed / ec_required : 0.0; // reset kerbal temperature, if necessary if (ec_required <= double.Epsilon || ec_perc >= 1.0 - double.Epsilon) { kd.temperature = 0.0; } else { // degenerate kerbal temperature kd.temperature += Settings.TemperatureDegradationRate * elapsed_s * (1.0 - ec_perc) * temp_diff * temp_sign; // kill kerbal if necessary if (kd.temperature <= -Settings.TemperatureFatalThreshold) { Message.Post(Severity.fatality, KerbalEvent.climate_low, v, c); Kerbalism.Kill(v, c); } else if (kd.temperature >= Settings.TemperatureFatalThreshold) { Message.Post(Severity.fatality, KerbalEvent.climate_high, v, c); Kerbalism.Kill(v, c); } // show warnings else if (kd.temperature <= -Settings.TemperatureDangerThreshold && kd.msg_freezing < 2) { Message.Post(Severity.danger, KerbalEvent.climate_low, v, c); kd.msg_freezing = 2; } else if (kd.temperature <= -Settings.TemperatureWarningThreshold && kd.msg_freezing < 1) { Message.Post(Severity.warning, KerbalEvent.climate_low, v, c); kd.msg_freezing = 1; } else if (kd.temperature > -Settings.TemperatureWarningThreshold && kd.msg_freezing > 0) { Message.Post(Severity.relax, KerbalEvent.climate_low, v, c); kd.msg_freezing = 0; } else if (kd.temperature >= Settings.TemperatureDangerThreshold && kd.msg_burning < 2) { Message.Post(Severity.danger, KerbalEvent.climate_high, v, c); kd.msg_burning = 2; } else if (kd.temperature >= Settings.TemperatureWarningThreshold && kd.msg_burning < 1) { Message.Post(Severity.warning, KerbalEvent.climate_high, v, c); kd.msg_burning = 1; } else if (kd.temperature < Settings.TemperatureWarningThreshold && kd.msg_burning > 0) { Message.Post(Severity.relax, KerbalEvent.climate_high, v, c); kd.msg_burning = 0; } } // if its meal time for this kerbal kd.time_since_food += elapsed_s; if (kd.time_since_food >= Settings.MealFrequency) { // consume food const double food_required = Settings.FoodPerMeal; double food_consumed = Lib.RequestResource(v, "Food", food_required); double food_perc = food_consumed / food_required; // reset kerbal starvation, if necessary if (food_perc >= 1.0 - double.Epsilon) { kd.starved = 0.0; kd.time_since_food = 0.0; } else { // assure piecewise consumption Lib.RequestResource(v, "Food", -food_consumed); food_consumed = 0.0; // degenerate kerbal starvation kd.starved += Settings.StarvedDegradationRate * elapsed_s; // kill kerbal if necessary if (kd.starved >= Settings.StarvedFatalThreshold) { Message.Post(Severity.fatality, KerbalEvent.food, v, c); Kerbalism.Kill(v, c); } // show warnings else if (kd.starved >= Settings.StarvedDangerThreshold && kd.msg_starved < 2) { Message.Post(Severity.danger, KerbalEvent.food, v, c); kd.msg_starved = 2; } else if (kd.starved >= Settings.StarvedWarningThreshold && kd.msg_starved < 1) { Message.Post(Severity.warning, KerbalEvent.food, v, c); kd.msg_starved = 1; } else if (kd.starved < Settings.StarvedWarningThreshold && kd.msg_starved > 0) { Message.Post(Severity.relax, KerbalEvent.food, v, c); kd.msg_starved = 0; } } // produce waste Lib.RequestResource(v, "Waste", -food_consumed); } // if not inside a breathable atmosphere if (!breathable) { // consume oxygen double oxygen_required = Settings.OxygenPerSecond * elapsed_s; double oxygen_consumed = Lib.RequestResource(v, "Oxygen", oxygen_required); double oxygen_perc = oxygen_consumed / oxygen_required; // reset kerbal deprivation, if necessary if (oxygen_perc >= 1.0 - double.Epsilon) { kd.deprived = 0.0; } else { // degenerate kerbal deprivation kd.deprived += Settings.DeprivedDegradationRate * elapsed_s * (1.0 - oxygen_perc); // kill kerbal if necessary if (kd.deprived >= Settings.DeprivedFatalThreshold) { Message.Post(Severity.fatality, KerbalEvent.oxygen, v, c); Kerbalism.Kill(v, c); } // show warnings else if (kd.deprived >= Settings.DeprivedDangerThreshold && kd.msg_deprived < 2) { Message.Post(Severity.danger, KerbalEvent.oxygen, v, c); kd.msg_deprived = 2; } else if (kd.deprived >= Settings.DeprivedWarningThreshold && kd.msg_deprived < 1) { Message.Post(Severity.warning, KerbalEvent.oxygen, v, c); kd.msg_deprived = 1; } else if (kd.deprived < Settings.DeprivedWarningThreshold && kd.msg_deprived > 0) { Message.Post(Severity.relax, KerbalEvent.oxygen, v, c); kd.msg_deprived = 0; } } // produce CO2 Lib.RequestResource(v, "CO2", -oxygen_consumed); } } } }