// trigger malfunction for unloaded module public static void Break(Vessel v, ProtoPartModuleSnapshot m) { // get data uint malfunctions = Lib.GetProtoValue<uint>(m, "malfunctions"); double lifetime = Lib.GetProtoValue<double>(m, "lifetime"); double age = Lib.GetProtoValue<double>(m, "age"); string malfunction_msg = m.moduleValues.GetValue("malfunction_msg"); // limit number of malfunctions per-component if (malfunctions >= 2u) return; // increase malfunction ++malfunctions; // reset age and lifetime age = 0.0; lifetime = 0.0; // show message if (DB.Ready() && DB.VesselData(v.id).cfg_malfunction == 1) { Message.Post(Severity.warning, PrepareMsg(malfunction_msg, v, malfunctions)); } // record first malfunction if (DB.Ready()) DB.NotificationData().first_malfunction = 1; // save data Lib.SetProtoValue<uint>(m, "malfunctions", malfunctions); Lib.SetProtoValue<double>(m, "lifetime", lifetime); Lib.SetProtoValue<double>(m, "age", age); }
// return true if body is relevant to the player // - body: reference body of the planetary system bool body_is_relevant(CelestialBody body) { // special case: home system is always relevant // note: we deal with the case of a planet mod setting homebody as a moon if (body == Lib.PlanetarySystem(FlightGlobals.GetHomeBody())) return true; // for each vessel foreach(Vessel v in FlightGlobals.Vessels) { // if inside the system if (Lib.PlanetarySystem(v.mainBody) == body) { // skip invalid vessels if (!Lib.IsVessel(v)) continue; // skip resque missions if (Lib.IsResqueMission(v)) continue; // skip dead eva kerbal if (EVA.IsDead(v)) continue; // obey message config if (DB.VesselData(v.id).cfg_storm == 0) continue; // body is relevant return true; } } return false; }
// draw the window void render(int id) { // get vessel data vessel_data vd = DB.VesselData(vessel_id); // get vessel name string vessel_name = FlightGlobals.Vessels.Find(k => k.id == vessel_id).vesselName; // draw pseudo-title GUILayout.BeginHorizontal(); GUILayout.Label(vessel_name, top_style); GUILayout.EndHorizontal(); // draw top spacing GUILayout.Space(spacing); // draw text area scroll_pos = GUILayout.BeginScrollView(scroll_pos, HighLogic.Skin.horizontalScrollbar, HighLogic.Skin.verticalScrollbar); vd.notes = GUILayout.TextArea(vd.notes, txt_style, txt_options); GUILayout.EndScrollView(); // draw bottom spacing GUILayout.Space(spacing); // draw footer GUILayout.BeginHorizontal(); GUILayout.Label("close", bot_style); if (Lib.IsClicked()) Close(); GUILayout.EndHorizontal(); // enable dragging GUI.DragWindow(drag_rect); }
// called every frame public void on_gui() { // forget vessel doesn't exist anymore if (vessel_id != Guid.Empty && FlightGlobals.Vessels.Find(k => k.id == vessel_id) == null) { vessel_id = Guid.Empty; } // forget file if it doesn't exist anymore if (vessel_id != Guid.Empty && !DB.VesselData(vessel_id).computer.files.ContainsKey(filename)) { filename = string.Empty; } // do nothing if there isn't a vessel or a filename specified if (vessel_id == Guid.Empty || filename.Length == 0) return; // clamp the window to the screen, so it can't be dragged outside float offset_x = Math.Max(0.0f, -win_rect.xMin) + Math.Min(0.0f, Screen.width - win_rect.xMax); float offset_y = Math.Max(0.0f, -win_rect.yMin) + Math.Min(0.0f, Screen.height - win_rect.yMax); win_rect.xMin += offset_x; win_rect.xMax += offset_x; win_rect.yMin += offset_y; win_rect.yMax += offset_y; // draw the window win_rect = GUILayout.Window(win_id, win_rect, render, "", win_style); // disable camera mouse scrolling on mouse over if (win_rect.Contains(Event.current.mousePosition)) { GameSettings.AXIS_MOUSEWHEEL.primary.scale = 0.0f; } }
public static void ShowMessage(Vessel v, string type) { if (DB.Ready() && DB.VesselData(v.id).cfg_malfunction == 1) { Message.Post(Severity.warning, Lib.BuildString("<b>", type, "</b> malfunctioned on <b>", v.vesselName, "</b>")); } }
// return true if body is relevant to the player // - body: reference body of the planetary system static bool body_is_relevant(CelestialBody body) { // [disabled] // special case: home system is always relevant // note: we deal with the case of a planet mod setting homebody as a moon //if (body == Lib.PlanetarySystem(FlightGlobals.GetHomeBody())) return true; // for each vessel foreach(Vessel v in FlightGlobals.Vessels) { // if inside the system if (Lib.PlanetarySystem(v.mainBody) == body) { // get info from the cache vessel_info vi = Cache.VesselInfo(v); // skip invalid vessels if (!vi.is_valid) continue; // obey message config if (DB.VesselData(v.id).cfg_storm == 0) continue; // body is relevant return true; } } return false; }
void exec() { // get the computer Computer cpu = DB.VesselData(vessel_id).computer; // get the environment Vessel environment = FlightGlobals.Vessels.Find(k => k.id == vessel_id); // execute current prompt as a remote command cpu.execute(prompt, environment, true); // write the command string on the buffer buffer.Add(Lib.BuildString(">> ", prompt)); // special outputs if (cpu.output.IndexOf('!') == 0) { var split = cpu.output.Substring(1).Split(' '); switch(split[0]) { case "EDIT": Editor.Open(environment, split[1]); break; case "SWITCH": Open(FlightGlobals.Vessels.Find(k => k.id == new Guid(split[1]))); break; case "CLEAR": buffer.Clear(); break; case "EXIT": buffer.Clear(); Close(); break; } } // normal output else { // write the output on the buffer // note: we deal with multi-line success outputs var split = cpu.output.Split('\n'); foreach(string s in split) { if (cpu.status) buffer.Add(s); else buffer.Add(Lib.BuildString("<color=red>", s, "</color>")); } if (cpu.output.Length > 0) buffer.Add(string.Empty); } // reset prompt prompt = string.Empty; // move the scroll bar at the bottom scroll_pos.y = 9999.0f; }
public float height() { // note: this function is abused to determine if the filter must be shown // guess vessel count uint count = 0; show_filter = false; foreach(Vessel v in FlightGlobals.Vessels) { // skip invalid vessels if (!Lib.IsVessel(v)) continue; // skip resque missions if (Lib.IsResqueMission(v)) continue; // skip dead eva kerbals if (EVA.IsDead(v)) continue; // avoid problems if the DB isn't ready if (DB.Ready()) { // get vessel data vessel_data vd = DB.VesselData(v.id); // determine if filter must be shown show_filter |= vd.group.Length > 0 && vd.group != "NONE"; // if the panel is filtered, skip filtered vessels if (filtered() && vd.group != filter) continue; } // the vessel will be rendered ++count; } // deal with no vessels case count = Math.Max(1u, count); // calculate height float vessels_height = 10.0f + (float)count * (16.0f + 10.0f); uint config_entries = 0; if (configured_id != Guid.Empty) { config_entries = 3u; // group, info & notes if (Kerbalism.ec_rule != null) ++config_entries; if (Kerbalism.supply_rules.Count > 0) ++config_entries; if (Kerbalism.features.signal) config_entries += 2u; if (Kerbalism.features.malfunction) config_entries += 2u; if (Settings.StormDuration > double.Epsilon) ++config_entries; if (filtered()) ++config_entries; } float config_height = (float)config_entries * 16.0f; float filter_height = show_filter ? 16.0f + 10.0f : 0.0f; return Math.Min(vessels_height + config_height + filter_height, Screen.height * 0.5f); }
public void BuildLinks() { // clear links container links.Clear(); // clear active relays container active_relays.Clear(); // iterate over all vessels foreach (Vessel v in FlightGlobals.Vessels) { // skip invalid vessels if (!Lib.IsVessel(v)) { continue; } // generate and store link status link_data ld = ComputeLink(v, new HashSet <Guid>()); links.Add(v.id, ld); // maintain and send messages // - do nothing if db isn't ready // - do not send messages for vessels without an antenna if (DB.Ready() && ld.status != link_status.no_antenna) { vessel_data vd = DB.VesselData(v.id); if (vd.msg_signal < 1 && !ld.linked) { vd.msg_signal = 1; if (DB.Ready()) { DB.NotificationData().first_signal_loss = 1; //< record first signal loss } if (vd.cfg_signal == 1 && !Blackout(v)) //< do not send message during storms { bool is_probe = (v.loaded ? v.GetCrewCount() == 0 : v.protoVessel.GetVesselCrew().Count == 0); Message.Post(Severity.warning, "Signal lost with <b>" + v.vesselName + "</b>", is_probe ? "Remote control disabled" : "Data transmission disabled"); } } else if (vd.msg_signal > 0 && ld.linked) { vd.msg_signal = 0; if (vd.cfg_signal == 1 && !Storm.JustEnded(v.mainBody, TimeWarp.deltaTime)) //< do not send messages after a storm { Message.Post(Severity.relax, "<b>" + v.vesselName + "</b> signal is back", ld.path.Count == 0 ? "We got a direct link with the space center" : "Relayed by <b>" + ld.path[ld.path.Count - 1] + "</b>"); } } } } }
void fromEVA(GameEvents.FromToAction <Part, Part> data) { // use Hydrazine instead of MonoPropellant if RealFuel is installed string monoprop_name = detected_mods.RealFuels ? "Hydrazine" : "MonoPropellant"; // get any leftover EVA Fuel from EVA vessel double monoprop = data.from.Resources.list[0].amount; // add the leftover monoprop back to the pod data.to.RequestResource(monoprop_name, -monoprop); // manage resources in rules foreach (Rule r in rules) { if (r.resource_name.Length == 0 || r.on_eva <= double.Epsilon) { continue; } double leftover = ResourceCache.Info(data.from.vessel, r.resource_name).amount; data.to.RequestResource(r.resource_name, -leftover); } // merge EVA computer files into vessel Computer a = DB.VesselData(data.from.vessel.id).computer; Computer b = DB.VesselData(data.to.vessel.id).computer; b.merge(a); // forget vessel data DB.ForgetVessel(data.from.vessel.id); // purge vessel from resource cache ResourceCache.Purge(data.from.vessel.id); // execute script on vessel computer if (DB.Ready()) { DB.VesselData(data.to.vessel.id).computer.execute("run", "auto/eva_in", string.Empty, data.to.vessel); } // mute messages for a couple seconds to avoid warning messages from the vessel resource amounts Message.MuteInternal(); base.StartCoroutine(CallbackUtil.DelayedCallback(2.0f, Message.UnmuteInternal)); // if vessel info is open, switch to the vessel if (Info.IsOpen()) { Info.Open(data.to.vessel); } }
// show the console public static void Open(Vessel v, string filename) { // setting file show the window instance.vessel_id = v.id; instance.filename = filename; // create file if it doesn't exist var files = DB.VesselData(v.id).computer.files; File file; if (!files.TryGetValue(filename, out file)) { file = new File(); files.Add(filename, file); } }
// return true if a storm just ended // used to avoid sending 'signal is back' messages en-masse after the storm is over // - delta_time: time between calls to this function public static bool JustEnded(Vessel v, double delta_time) { if (!DB.Ready()) return false; // if in interplanetary space if (v.mainBody.flightGlobalsIndex == 0) { return DB.VesselData(v.id).storm_age < delta_time * 2.0; } // if inside a planetary system else { return DB.BodyData(Lib.PlanetarySystem(v.mainBody).name).storm_age < delta_time * 2.0; } }
// return true if a storm is in progress public static bool InProgress(Vessel v) { if (!DB.Ready()) return false; // if in interplanetary space if (v.mainBody.flightGlobalsIndex == 0) { return DB.VesselData(v.id).storm_state == 2; } // if inside a planetary system else { return DB.BodyData(Lib.PlanetarySystem(v.mainBody).name).storm_state == 2; } }
public float height() { // note: this function is abused to determine if the filter must be shown // guess vessel count uint count = 0; show_filter = false; foreach(Vessel v in FlightGlobals.Vessels) { // get info from the cache vessel_info vi = Cache.VesselInfo(v); // skip invalid vessels if (!vi.is_valid) continue; // get vessel data vessel_data vd = DB.VesselData(v.id); // determine if filter must be shown show_filter |= vd.group.Length > 0 && vd.group != "NONE"; // if the panel is filtered, skip filtered vessels if (filtered() && vd.group != filter) continue; // the vessel will be rendered ++count; } // deal with no vessels case count = Math.Max(1u, count); // calculate height float vessels_height = 10.0f + (float)count * (16.0f + 10.0f); uint config_entries = 0; if (configured_id != Guid.Empty) { config_entries = 4u; // group, info, console, notes if (Kerbalism.ec_rule != null) ++config_entries; if (Kerbalism.supply_rules.Count > 0) ++config_entries; if (Kerbalism.features.signal) config_entries += 2u; if (Kerbalism.features.reliability) config_entries += 2u; if (Settings.StormDuration > double.Epsilon) ++config_entries; if (filtered()) ++config_entries; } float config_height = (float)config_entries * 16.0f; float filter_height = show_filter ? 16.0f + 10.0f : 0.0f; return Math.Min(vessels_height + config_height + filter_height, Screen.height * 0.5f); }
// return time left until CME is over public static double TimeLeftCME(Vessel v) { if (!DB.Ready()) return 0.0; // if in interplanetary space if (v.mainBody.flightGlobalsIndex == 0) { vessel_data vd = DB.VesselData(v.id); return TimeLeftCME(vd.storm_time, vd.storm_age); } // if inside a planetary system else { body_data bd = DB.BodyData(Lib.PlanetarySystem(v.mainBody).name); return TimeLeftCME(bd.storm_time, bd.storm_age); } }
void vesselDock(GameEvents.FromToAction <Part, Part> e) { // this need to happen when db is in a valid state if (DB.Ready()) { // merge computer data from vessel A to vessel B on docking Computer a = DB.VesselData(e.from.vessel.id).computer; Computer b = DB.VesselData(e.to.vessel.id).computer; b.merge(a); // forget vessel being docked DB.ForgetVessel(e.from.vessel.id); } // purge vessel from resource cache ResourceCache.Purge(e.from.vessel.id); }
public float height() { // note: this function is abused to determine if the filter must be shown // guess vessel count uint count = 0; show_filter = false; foreach(Vessel v in FlightGlobals.Vessels) { // skip invalid vessels if (!Lib.IsVessel(v)) continue; // skip resque missions if (Lib.IsResqueMission(v)) continue; // skip dead eva kerbals if (EVA.IsDead(v)) continue; // avoid problems if the DB isn't ready if (DB.Ready()) { // get vessel data vessel_data vd = DB.VesselData(v.id); // determine if filter must be shown show_filter |= vd.group.Length > 0 && vd.group != "NONE"; // if the panel is filtered, skip filtered vessels if (filtered() && vd.group != filter) continue; } // the vessel will be rendered ++count; } // deal with no vessels case count = Math.Max(1u, count); // calculate height float vessels_height = 10.0f + (float)count * (16.0f + 10.0f); float config_height = configured_id == Guid.Empty ? 0.0f : filtered() ? 80.0f : 96.0f; float filter_height = show_filter ? 16.0f + 10.0f : 0.0f; return Math.Min(vessels_height + config_height + filter_height, Screen.height * 0.5f); }
// set highlighting public static void SetHighlight(Part p) { // get max malfunction among all reliability components in the part uint max_malfunctions = 0; foreach (Reliability m in p.FindModulesImplementing <Reliability>()) { max_malfunctions = Math.Max(max_malfunctions, m.malfunctions); } // note: when solar panels break (the stock mechanic), this throw exceptions inside KSP try { if (DB.Ready() && DB.VesselData(p.vessel.id).cfg_highlights > 0) { switch (max_malfunctions) { case 0: p.SetHighlightDefault(); break; case 1: p.SetHighlightType(Part.HighlightType.AlwaysOn); p.SetHighlightColor(Color.yellow); p.SetHighlight(true, false); break; default: p.SetHighlightType(Part.HighlightType.AlwaysOn); p.SetHighlightColor(Color.red); p.SetHighlight(true, false); break; } } else { p.SetHighlightDefault(); } } catch {} }
// trigger malfunction public void Break() { // reset age and lifetime age = 0.0; lifetime = 0.0; // apply malfunction penalty immediately Apply(0.5); // increase malfunction ++malfunctions; // show message if (DB.Ready() && DB.VesselData(vessel.id).cfg_malfunction == 1) { Message.Post(Severity.warning, PrepareMsg(malfunction_msg, vessel, malfunctions)); } // record first malfunction if (DB.Ready()) DB.NotificationData().first_malfunction = 1; }
// draw vessel config void render_config(Vessel v) { // do nothing if db isn' ready if (!DB.Ready()) return; // get vessel data vessel_data vd = DB.VesselData(v.id); // draw the config GUILayout.BeginHorizontal(row_style); GUILayout.Label(new GUIContent(" EC MESSAGES", icon_toggle[vd.cfg_ec]), config_style); if (Lib.IsClicked()) vd.cfg_ec = (vd.cfg_ec == 0 ? 1u : 0); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(row_style); GUILayout.Label(new GUIContent(" SUPPLY MESSAGES", icon_toggle[vd.cfg_supply]), config_style); if (Lib.IsClicked()) vd.cfg_supply = (vd.cfg_supply == 0 ? 1u : 0); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(row_style); GUILayout.Label(new GUIContent(" MALFUNCTIONS MESSAGES", icon_toggle[vd.cfg_malfunction]), config_style); if (Lib.IsClicked()) vd.cfg_malfunction = (vd.cfg_malfunction == 0 ? 1u : 0); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(row_style); GUILayout.Label(new GUIContent(" SIGNAL MESSAGES", icon_toggle[vd.cfg_signal]), config_style); if (Lib.IsClicked()) vd.cfg_signal = (vd.cfg_signal == 0 ? 1u : 0); GUILayout.EndHorizontal(); if (!filtered()) { GUILayout.BeginHorizontal(row_style); GUILayout.Label(new GUIContent(" GROUP: ", icon_group), config_style, new[]{GUILayout.Width(48.0f)}); vd.group = Lib.TextFieldPlaceholder("Kerbalism_group", vd.group, "NONE", group_style).ToUpper(); GUILayout.EndHorizontal(); } GUILayout.BeginHorizontal(row_style); GUILayout.Label(new GUIContent(" NOTES", icon_notes), config_style); if (Lib.IsClicked()) Notepad.Toggle(v); GUILayout.EndHorizontal(); }
// draw the window void render(int _) { // note: the id and the vessel are valid at this point, checked in on_gui() // get vessel data vessel_data vd = DB.VesselData(vessel_id); // get vessel Vessel v = FlightGlobals.Vessels.Find(k => k.id == vessel_id); // get info from the cache vessel_info vi = Cache.VesselInfo(v); // get crew var crew = v.loaded ? v.GetVesselCrew() : v.protoVessel.GetVesselCrew(); // draw pseudo-title GUILayout.BeginHorizontal(); GUILayout.Label(v.vesselName, top_style); GUILayout.EndHorizontal(); // draw the content render_environment(v, vi); render_supplies(v, vi); render_internal_space(v, vi, crew); render_crew(v, vi, crew); render_greenhouse(vi); // draw footer GUILayout.BeginHorizontal(); GUILayout.Label("close", bot_style); if (Lib.IsClicked()) Close(); GUILayout.EndHorizontal(); // enable dragging GUI.DragWindow(drag_rect); }
// draw the window void render(int id) { // draw pseudo-title GUILayout.BeginHorizontal(); GUILayout.Label(close_left, icon_left_style); GUILayout.Label("EDITOR", top_style); GUILayout.Label(close_right, icon_right_style); if (Lib.IsClicked()) Close(); GUILayout.EndHorizontal(); // draw the text area // note: when user hit close, vessel/filename is not valid // note: file is guaranteed to exist at this point, if vessel/filename is valid if (vessel_id != Guid.Empty && filename.Length > 0) { File file = DB.VesselData(vessel_id).computer.files[filename]; scroll_pos = GUILayout.BeginScrollView(scroll_pos, HighLogic.Skin.horizontalScrollbar, HighLogic.Skin.verticalScrollbar); file.content = GUILayout.TextArea(file.content, txt_style); GUILayout.EndScrollView(); } // enable dragging GUI.DragWindow(drag_rect); }
// draw vessel config void render_config(Vessel v) { // do nothing if db isn' ready if (!DB.Ready()) return; // get vessel data vessel_data vd = DB.VesselData(v.id); // draw the config if (Kerbalism.ec_rule != null) { GUILayout.BeginHorizontal(row_style); GUILayout.Label(new GUIContent(" EC MESSAGES", icon_toggle[vd.cfg_ec]), config_style); if (Lib.IsClicked()) vd.cfg_ec = (vd.cfg_ec == 0 ? 1u : 0); GUILayout.EndHorizontal(); } if (Kerbalism.supply_rules.Count > 0) { GUILayout.BeginHorizontal(row_style); GUILayout.Label(new GUIContent(" SUPPLY MESSAGES", icon_toggle[vd.cfg_supply]), config_style); if (Lib.IsClicked()) vd.cfg_supply = (vd.cfg_supply == 0 ? 1u : 0); GUILayout.EndHorizontal(); } if (Kerbalism.features.signal) { GUILayout.BeginHorizontal(row_style); GUILayout.Label(new GUIContent(" SIGNAL MESSAGES", icon_toggle[vd.cfg_signal]), config_style); if (Lib.IsClicked()) vd.cfg_signal = (vd.cfg_signal == 0 ? 1u : 0); GUILayout.EndHorizontal(); } if (Settings.StormDuration > double.Epsilon) { GUILayout.BeginHorizontal(row_style); GUILayout.Label(new GUIContent(" STORM MESSAGES", icon_toggle[vd.cfg_storm]), config_style); if (Lib.IsClicked()) vd.cfg_storm = (vd.cfg_storm == 0 ? 1u : 0); GUILayout.EndHorizontal(); } if (Kerbalism.features.malfunction) { GUILayout.BeginHorizontal(row_style); GUILayout.Label(new GUIContent(" RELIABILITY MESSAGES", icon_toggle[vd.cfg_malfunction]), config_style); if (Lib.IsClicked()) vd.cfg_malfunction = (vd.cfg_malfunction == 0 ? 1u : 0); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(row_style); GUILayout.Label(new GUIContent(" HIGHLIGHT MALFUNCTIONS", icon_toggle[vd.cfg_highlights]), config_style); if (Lib.IsClicked()) vd.cfg_highlights = (vd.cfg_highlights == 0 ? 1u : 0); GUILayout.EndHorizontal(); } if (Kerbalism.features.signal) { GUILayout.BeginHorizontal(row_style); GUILayout.Label(new GUIContent(" SHOW LINK", icon_toggle[vd.cfg_showlink]), config_style); if (Lib.IsClicked()) vd.cfg_showlink = (vd.cfg_showlink == 0 ? 1u : 0); GUILayout.EndHorizontal(); } if (!filtered()) { GUILayout.BeginHorizontal(row_style); GUILayout.Label(new GUIContent(" GROUP: ", icon_group), config_style, new[]{GUILayout.Width(48.0f)}); vd.group = Lib.TextFieldPlaceholder("Kerbalism_group", vd.group, "NONE", group_style).ToUpper(); GUILayout.EndHorizontal(); } GUILayout.BeginHorizontal(row_style); GUILayout.Label(new GUIContent(" NOTES", icon_notes), config_style); if (Lib.IsClicked()) Notepad.Toggle(v); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(row_style); GUILayout.Label(new GUIContent(" DETAILS", icon_info), config_style); if (Lib.IsClicked()) Info.Toggle(v); GUILayout.EndHorizontal(); }
void toEVA(GameEvents.FromToAction <Part, Part> data) { // use Hydrazine instead of MonoPropellant if RealFuel is installed string monoprop_name = detected_mods.RealFuels ? "Hydrazine" : "MonoPropellant"; // determine if inside breathable atmosphere // note: the user can force the helmet + oxygen by pressing shift when going on eva bool breathable = Sim.Breathable(data.from.vessel) && !(Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)); // get total crew in the origin vessel double tot_crew = (double)data.from.vessel.GetVesselCrew().Count + 1.0; // EVA vessels start with 5 units of eva fuel, remove them data.to.RequestResource("EVA Propellant", 5.0); // determine how much MonoPropellant to get // note: never more that the 'share' of this kerbal double monoprop = Math.Min(ResourceCache.Info(data.from.vessel, monoprop_name).amount / tot_crew, Settings.MonoPropellantOnEVA); // get monoprop from the vessel monoprop = data.from.RequestResource(monoprop_name, monoprop); // transfer monoprop to the EVA kerbal data.to.RequestResource("EVA Propellant", -monoprop); // show warning if there isn't monoprop in the eva suit if (monoprop <= double.Epsilon && !Lib.Landed(data.from.vessel)) { Message.Post(Severity.danger, Lib.BuildString("There isn't any <b>", monoprop_name, "</b> in the EVA suit", "Don't let the ladder go!")); } // manage resources from rules foreach (Rule r in rules) { if (r.resource_name.Length == 0 || r.on_eva <= double.Epsilon) { continue; } // determine amount to take, never more that his own share double amount = Math.Min(ResourceCache.Info(data.from.vessel, r.resource_name).amount / tot_crew, r.on_eva); // deal with breathable modifier if (breathable && r.modifier.Contains("breathable")) { continue; } // remove resource from the vessel amount = data.from.RequestResource(r.resource_name, amount); // create new resource in the eva kerbal Lib.SetupResource(data.to, r.resource_name, amount, r.on_eva); } // get KerbalEVA KerbalEVA kerbal = data.to.FindModuleImplementing <KerbalEVA>(); // turn off headlamp light, to avoid stock bug that show the light for a split second when going on eva EVA.SetHeadlamp(kerbal, false); EVA.SetFlares(kerbal, false); // remove the helmet if inside breathable atmosphere // note: done in EVA::FixedUpdate(), but also done here avoid 'popping' of the helmet when going on eva EVA.SetHelmet(kerbal, !breathable); // remember if the kerbal has an helmet EVA.KerbalData(data.to.vessel).has_helmet = !breathable; // execute script on vessel computer if (DB.Ready()) { DB.VesselData(data.from.vessel.id).computer.execute("run", "auto/eva_out", string.Empty, data.from.vessel); } // mute messages for a couple seconds to avoid warning messages from the vessel resource amounts Message.MuteInternal(); base.StartCoroutine(CallbackUtil.DelayedCallback(2.0f, Message.UnmuteInternal)); // if vessel info is open, switch to the eva kerbal // note: for a single tick, the EVA vessel is not valid (sun_dist is zero) // this make IsVessel() return false, that in turn close the vessel info instantly // for this reason, we wait a small amount of time before switching the info window if (Info.IsOpen()) { Info.Open(data.to.vessel); } }
// draw a vessel in the monitor // - return: 1 if vessel wasn't skipped uint render_vessel(Vessel v) { // avoid case when DB isn't ready for whatever reason if (!DB.Ready()) return 0; // skip invalid vessels if (!Lib.IsVessel(v)) return 0; // skip resque missions if (Lib.IsResqueMission(v)) return 0; // skip dead eva kerbals if (EVA.IsDead(v)) return 0; // get vessel info from cache vessel_info vi = Cache.VesselInfo(v); // get vessel data from the db vessel_data vd = DB.VesselData(v.id); // skip filtered vessels if (filtered() && vd.group != filter) return 0; // get vessel crew List<ProtoCrewMember> crew = v.loaded ? v.GetVesselCrew() : v.protoVessel.GetVesselCrew(); // get vessel name string vessel_name = v.isEVA ? crew[0].name : v.vesselName; // get body name string body_name = v.mainBody.name.ToUpper(); // get list of scrubbers List<Scrubber> scrubbers = Scrubber.GetScrubbers(v); // get list of greenhouses List<Greenhouse> greenhouses = Greenhouse.GetGreenhouses(v); // store problems icons & tooltips List<Texture> problem_icons = new List<Texture>(); List<string> problem_tooltips = new List<string>(); // detect problems problem_sunlight(vi, ref problem_icons, ref problem_tooltips); problem_storm(v, ref problem_icons, ref problem_tooltips); if (crew.Count > 0) { problem_kerbals(crew, ref problem_icons, ref problem_tooltips); problem_radiation(vi, ref problem_icons, ref problem_tooltips); problem_scrubbers(v, scrubbers, ref problem_icons, ref problem_tooltips); } problem_greenhouses(v, greenhouses, ref problem_icons, ref problem_tooltips); // choose problem icon const UInt64 problem_icon_time = 3; Texture problem_icon = icon_empty; if (problem_icons.Count > 0) { UInt64 problem_index = (Convert.ToUInt64(Time.realtimeSinceStartup) / problem_icon_time) % (UInt64)(problem_icons.Count); problem_icon = problem_icons[(int)problem_index]; } // generate problem tooltips string problem_tooltip = String.Join("\n", problem_tooltips.ToArray()); // render vessel name & icons GUILayout.BeginHorizontal(row_style); GUILayout.Label(new GUIContent("<b>" + Lib.Epsilon(vessel_name, 20) + "</b>", vessel_name.Length > 20 ? vessel_name : ""), name_style); GUILayout.Label(new GUIContent(Lib.Epsilon(body_name, 8), body_name.Length > 8 ? body_name : ""), body_style); GUILayout.Label(new GUIContent(problem_icon, problem_tooltip), icon_style); GUILayout.Label(indicator_ec(v), icon_style); GUILayout.Label(indicator_supplies(v, scrubbers, greenhouses), icon_style); GUILayout.Label(indicator_reliability(v), icon_style); GUILayout.Label(indicator_signal(v), icon_style); GUILayout.EndHorizontal(); // remember last vessel clicked if (Lib.IsClicked()) last_clicked_id = v.id; // render vessel config if (configured_id == v.id) render_config(v); // spacing between vessels GUILayout.Space(10.0f); // signal that the vessel wasn't skipped for whatever reason return 1; }
void resourceWarnings() { // for each vessel foreach (Vessel v in FlightGlobals.Vessels) { // skip invalid vessels if (!Lib.IsVessel(v)) { continue; } // skip resque missions if (Lib.IsResqueMission(v)) { continue; } // skip dead eva kerbal if (EVA.IsDead(v)) { continue; } // get vessel data vessel_data vd = DB.VesselData(v.id); // get EC amount and capacity double ec_amount = Lib.GetResourceAmount(v, "ElectricCharge"); double ec_capacity = Lib.GetResourceCapacity(v, "ElectricCharge"); double ec_perc = ec_capacity > 0.0 ? ec_amount / ec_capacity : 0.0; // if it has EC capacity if (ec_capacity > 0.0) { // check EC thresholds and show messages if (ec_perc <= Settings.ResourceDangerThreshold && vd.msg_ec < 2) { if (vd.cfg_ec == 1) { Message.Post(Severity.danger, VesselEvent.ec, v); } vd.msg_ec = 2; } else if (ec_perc <= Settings.ResourceWarningThreshold && vd.msg_ec < 1) { if (vd.cfg_ec == 1) { Message.Post(Severity.warning, VesselEvent.ec, v); } vd.msg_ec = 1; } else if (ec_perc > Settings.ResourceWarningThreshold && vd.msg_ec > 0) { if (vd.cfg_ec == 1) { Message.Post(Severity.relax, VesselEvent.ec, v); } vd.msg_ec = 0; } } // get food amount and capacity double food_amount = Lib.GetResourceAmount(v, "Food"); double food_capacity = Lib.GetResourceCapacity(v, "Food"); double food_perc = food_capacity > 0.0 ? food_amount / food_capacity : 0.0; // if it has food capacity if (food_capacity > 0.0) { // check food thresholds and show messages // note: no warnings at prelaunch if (food_perc <= Settings.ResourceDangerThreshold && vd.msg_food < 2) { if (vd.cfg_supply == 1 && v.situation != Vessel.Situations.PRELAUNCH) { Message.Post(Severity.danger, VesselEvent.food, v); } vd.msg_food = 2; } else if (food_perc <= Settings.ResourceWarningThreshold && vd.msg_food < 1) { if (vd.cfg_supply == 1 && v.situation != Vessel.Situations.PRELAUNCH) { Message.Post(Severity.warning, VesselEvent.food, v); } vd.msg_food = 1; } else if (food_perc > Settings.ResourceWarningThreshold && vd.msg_food > 0) { if (vd.cfg_supply == 1 && v.situation != Vessel.Situations.PRELAUNCH) { Message.Post(Severity.relax, VesselEvent.food, v); } vd.msg_food = 0; } } // get oxygen amount and capacity double oxygen_amount = Lib.GetResourceAmount(v, "Oxygen"); double oxygen_capacity = Lib.GetResourceCapacity(v, "Oxygen"); double oxygen_perc = oxygen_capacity > 0.0 ? oxygen_amount / oxygen_capacity : 0.0; // if it has oxygen capacity if (oxygen_capacity > 0.0) { // check oxygen thresholds and show messages // note: no warnings at prelaunch if (oxygen_perc <= Settings.ResourceDangerThreshold && vd.msg_oxygen < 2) { if (vd.cfg_supply == 1 && v.situation != Vessel.Situations.PRELAUNCH) { Message.Post(Severity.danger, VesselEvent.oxygen, v); } vd.msg_oxygen = 2; } else if (oxygen_perc <= Settings.ResourceWarningThreshold && vd.msg_oxygen < 1) { if (vd.cfg_supply == 1 && v.situation != Vessel.Situations.PRELAUNCH) { Message.Post(Severity.warning, VesselEvent.oxygen, v); } vd.msg_oxygen = 1; } else if (oxygen_perc > Settings.ResourceWarningThreshold && vd.msg_oxygen > 0) { if (vd.cfg_supply == 1 && v.situation != Vessel.Situations.PRELAUNCH) { Message.Post(Severity.relax, VesselEvent.oxygen, v); } vd.msg_oxygen = 0; } } } }
public static void render() { // get home body position Vector3 home = ScaledSpace.LocalToScaledSpace(FlightGlobals.GetHomeBody().position); // for each vessel foreach (Vessel v in FlightGlobals.Vessels) { // get info from the cache vessel_info vi = Cache.VesselInfo(v); // skip invalid vessels if (!vi.is_valid) { continue; } // get data from db vessel_data vd = DB.VesselData(v.id); // skip vessels with showlink disabled if (vd.cfg_showlink == 0) { continue; } // get link data link_data link = vi.link; // skip vessels with no antenna if (link.status == link_status.no_antenna) { continue; } // start of the line Vector3 a = ScaledSpace.LocalToScaledSpace(v.GetWorldPos3D()); // determine end of line and color Vector3 b; Color color; if (link.status == link_status.no_link) { b = home; color = Color.red; } else if (link.status == link_status.direct_link) { b = home; color = Color.green; } else //< indirect link { // get link path var path = link.path; // use relay position b = ScaledSpace.LocalToScaledSpace(path[path.Count - 1].GetWorldPos3D()); color = Color.yellow; } // commit the line LineRenderer.commit(a, b, color); } }
// called at every frame public void Update() { // FIXME: can't double-click on vessel to switch active one, if line is rendered // hide all lines foreach (var l in lines) { l.Value.Show(false); } // do nothing if db isn't ready if (!DB.Ready()) { return; } // do nothing if signal is disabled if (!Kerbalism.features.signal) { return; } // do nothing if not in map view or tracking station if (!MapView.MapIsEnabled) { return; } // get homebody position Vector3d home = FlightGlobals.GetHomeBody().position; // iterate all vessels foreach (Vessel v in FlightGlobals.Vessels) { // skip invalid vessels if (!Lib.IsVessel(v)) { continue; } // skip resque missions if (Lib.IsResqueMission(v)) { continue; } // skip EVA kerbals if (v.isEVA) { continue; } // get vessel data vessel_data vd = DB.VesselData(v.id); // do nothing if showlink is disabled if (vd.cfg_showlink == 0) { continue; } // get link status link_data ld = Signal.Link(v); // if there is an antenna if (ld.status != link_status.no_antenna) { // get line renderer from the cache Line line = getLine(v.id); // start of the line Vector3d a = Lib.VesselPosition(v); // determine end of line and color Vector3d b; Color clr; if (ld.status == link_status.direct_link) { b = home; clr = Color.green; } else if (ld.status == link_status.indirect_link) { Vessel relay = FlightGlobals.Vessels.Find(k => k.id == ld.path_id[ld.path.Count - 1]); if (relay == null) { line.Show(false); continue; } //< skip if it doesn't exist anymore b = Lib.VesselPosition(relay); clr = Color.yellow; } else // no link { b = home; clr = Color.red; } // setup the line and show it line.UpdatePoints(a, b, clr); line.Show(true); } } }
// implement malfunction mechanics public void FixedUpdate() { // do nothing in the editor if (HighLogic.LoadedSceneIsEditor) return; // deduce quality from technological level if necessary // note: done at prelaunch to avoid problems with start()/load() and the tech tree being not consistent if (vessel.situation == Vessel.Situations.PRELAUNCH) quality = DeduceQuality(); // if for some reason quality wasn't set, default to 1.0 // note: for example, resque vessels never get to prelaunch if (quality <= double.Epsilon) quality = 1.0; // update rmb ui var av = FlightGlobals.ActiveVessel; Events["Repair"].active = malfunctions > 0 && av != null && av.isEVA; Events["Inspect"].active = malfunctions == 0 && av != null && av.isEVA; Fields["Status"].guiActive = malfunctions > 0; Status = malfunctions == 0 ? "" : PrepareMsg(status_msg, vessel, malfunctions); // generate lifetime if necessary if (lifetime <= double.Epsilon) { lifetime = min_lifetime + (max_lifetime - min_lifetime) * Lib.RandomDouble(); } // accumulate age age += TimeWarp.fixedDeltaTime * AgingCurve(age, min_lifetime, max_lifetime) / quality; // check age and malfunction if needed if (age > lifetime) Break(); // set highlighting if (DB.Ready() && DB.VesselData(this.vessel.id).cfg_highlights > 0) { switch(malfunctions) { case 0: part.SetHighlightDefault(); break; case 1: part.SetHighlightType(Part.HighlightType.AlwaysOn); part.SetHighlightColor(Color.yellow); part.SetHighlight(true, false); break; default: part.SetHighlightType(Part.HighlightType.AlwaysOn); part.SetHighlightColor(Color.red); part.SetHighlight(true, false); break; } } else { part.SetHighlightDefault(); } }
// implement radiation 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 crew count int crew_count = Lib.CrewCount(v); // get vessel info from the cache vessel_info info = Cache.VesselInfo(v); // get vessel data vessel_data vd = DB.VesselData(v.id); // belt warnings // note: we only show it for manned vesssels, but the first time we also show it for probes if (crew_count > 0 || DB.NotificationData().first_belt_crossing == 0) { if (InsideBelt(v) && vd.msg_belt < 1) { Message.Post("<b>" + v.vesselName + "</b> is crossing <i>" + v.mainBody.bodyName + " radiation belt</i>", "Exposed to extreme radiation"); vd.msg_belt = 1; DB.NotificationData().first_belt_crossing = 1; //< record first belt crossing } else if (!InsideBelt(v) && vd.msg_belt > 0) { // no message after crossing the belt vd.msg_belt = 0; } } // 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; } // accumulate radiation kd.radiation += info.radiation * elapsed_s; // kill kerbal if necessary if (kd.radiation >= Settings.RadiationFatalThreshold) { Message.Post(Severity.fatality, KerbalEvent.radiation, v, c); Kerbalism.Kill(v, c); } // show warnings else if (kd.radiation >= Settings.RadiationDangerThreshold && kd.msg_radiation < 2) { Message.Post(Severity.danger, KerbalEvent.radiation, v, c); kd.msg_radiation = 2; } else if (kd.radiation >= Settings.RadiationWarningThreshold && kd.msg_radiation < 1) { Message.Post(Severity.danger, KerbalEvent.radiation, v, c); kd.msg_radiation = 1; } // note: no recovery from radiations } } }