// return max malfunction count among all parts of a vessel public static uint MaxMalfunction(Vessel v) { uint max_malfunction = 0; if (v.loaded) { foreach(Malfunction m in v.FindPartModulesImplementing<Malfunction>()) { max_malfunction = Math.Max(max_malfunction, m.malfunctions); } } else { foreach(ProtoPartSnapshot p in v.protoVessel.protoPartSnapshots) { foreach(ProtoPartModuleSnapshot m in p.modules) { if (m.moduleName == "Malfunction") { max_malfunction = Math.Max(max_malfunction, Lib.GetProtoValue<uint>(m, "malfunctions")); } } } } return max_malfunction; }
// return average component quality public static double AverageQuality(Vessel v) { double quality_sum = 0.0; double quality_count = 0.0; if (v.loaded) { foreach(Malfunction m in v.FindPartModulesImplementing<Malfunction>()) { quality_sum += m.quality; quality_count += 1.0; } } else { foreach(ProtoPartSnapshot p in v.protoVessel.protoPartSnapshots) { foreach(ProtoPartModuleSnapshot m in p.modules) { if (m.moduleName == "Malfunction") { quality_sum += Lib.GetProtoValue<double>(m, "quality"); quality_count += 1.0; } } } } return quality_count > 0.0 ? quality_sum / quality_count : 0.0; }
// return entertainment on a vessel public static double Entertainment(Vessel v) { // deduce entertainment bonus, multiplying all entertainment factors double entertainment = 1.0; if (v.loaded) { foreach(Entertainment m in v.FindPartModulesImplementing<Entertainment>()) { entertainment *= m.rate; } foreach(GravityRing m in v.FindPartModulesImplementing<GravityRing>()) { entertainment *= m.rate; } } else { foreach(ProtoPartSnapshot part in v.protoVessel.protoPartSnapshots) { foreach(ProtoPartModuleSnapshot m in part.modules) { if (m.moduleName == "Entertainment") entertainment *= Lib.GetProtoValue<double>(m, "rate"); else if (m.moduleName == "GravityRing") entertainment *= Lib.GetProtoValue<double>(m, "rate"); } } } return entertainment; }
// return quality-of-life bonus public static double Bonus(Vessel v) { // deduce crew count and capacity int crew_count = Lib.CrewCount(v); int crew_capacity = Lib.CrewCapacity(v); // deduce entertainment bonus, multiplying all entertainment factors double entertainment = 1.0; if (v.loaded) { foreach(Entertainment m in v.FindPartModulesImplementing<Entertainment>()) { entertainment *= m.rate; } } else { foreach(ProtoPartSnapshot part in v.protoVessel.protoPartSnapshots) { foreach(ProtoPartModuleSnapshot m in part.modules) { if (m.moduleName == "Entertainment") entertainment *= Lib.GetProtoValue<double>(m, "rate"); } } } // calculate quality of life bonus return Bonus((uint)crew_count, (uint)crew_capacity, entertainment, Lib.Landed(v), Signal.Link(v).linked); }
// return read-only list of recyclers in a vessel public static List<Recycler> GetRecyclers(Vessel v, string resource_name="") { if (v.loaded) { var ret = v.FindPartModulesImplementing<Recycler>(); if (resource_name.Length > 0) ret = ret.FindAll(k => k.resource_name == resource_name); return ret == null ? new List<Recycler>() : ret; } else { List<Recycler> ret = new List<Recycler>(); foreach(ProtoPartSnapshot part in v.protoVessel.protoPartSnapshots) { foreach(ProtoPartModuleSnapshot module in part.modules) { if (module.moduleName == "Recycler") { Recycler recycler = new Recycler(); recycler.is_enabled = Lib.GetProtoValue<bool>(module, "is_enabled"); recycler.resource_name = Lib.GetProtoValue<string>(module, "resource_name"); recycler.waste_name = Lib.GetProtoValue<string>(module, "waste_name"); recycler.ec_rate = Lib.GetProtoValue<double>(module, "ec_rate"); recycler.waste_rate = Lib.GetProtoValue<double>(module, "waste_rate"); recycler.waste_ratio = Lib.GetProtoValue<double>(module, "waste_ratio"); recycler.display_name = Lib.GetProtoValue<string>(module, "display_name"); recycler.filter_name = Lib.GetProtoValue<string>(module, "filter_name"); recycler.filter_rate = Lib.GetProtoValue<double>(module, "filter_rate"); if (resource_name.Length == 0 || recycler.resource_name == resource_name) ret.Add(recycler); } } } return ret; } }
void vesselRecovered(ProtoVessel vessel, bool b) { // note: this is called multiple times when a vessel is recovered, but its safe // find out if this was an EVA kerbal and if it was dead bool is_eva_dead = false; foreach (ProtoPartSnapshot p in vessel.protoPartSnapshots) { foreach (ProtoPartModuleSnapshot m in p.modules) { is_eva_dead |= (m.moduleName == "EVA" && Lib.GetProtoValue <bool>(m, "is_dead")); } } // set roster status of eva dead kerbals if (is_eva_dead) { vessel.GetVesselCrew()[0].rosterStatus = ProtoCrewMember.RosterStatus.Dead; } // forget kerbal data of recovered kerbals else { foreach (ProtoCrewMember c in vessel.GetVesselCrew()) { DB.ForgetKerbal(c.name); } } // forget vessel data DB.ForgetVessel(vessel.vesselID); }
// return max malfunction count among all parts of a vessel, or all parts containing the specified module public static uint MaxMalfunction(Vessel v, string module_name = "") { uint max_malfunction = 0; if (v.loaded) { foreach(Malfunction m in v.FindPartModulesImplementing<Malfunction>()) { if (module_name.Length == 0 || m.part.Modules.Contains(module_name)) { max_malfunction = Math.Max(max_malfunction, m.malfunctions); } } } else { foreach(ProtoPartSnapshot p in v.protoVessel.protoPartSnapshots) { foreach(ProtoPartModuleSnapshot m in p.modules) { if (m.moduleName == "Malfunction") { if (module_name.Length == 0 || p.modules.Find(k => k.moduleName == module_name) != null) { max_malfunction = Math.Max(max_malfunction, Lib.GetProtoValue<uint>(m, "malfunctions")); } } } } } return max_malfunction; }
// return read-only list of scrubbers in a vessel public static List<Scrubber> GetScrubbers(Vessel v, string resource_name="") { if (v.loaded) { var ret = v.FindPartModulesImplementing<Scrubber>(); if (resource_name.Length > 0) ret = ret.FindAll(k => k.resource_name == resource_name); return ret == null ? new List<Scrubber>() : ret; } else { List<Scrubber> ret = new List<Scrubber>(); foreach(ProtoPartSnapshot part in v.protoVessel.protoPartSnapshots) { foreach(ProtoPartModuleSnapshot module in part.modules) { if (module.moduleName == "Scrubber") { Scrubber scrubber = new Scrubber(); scrubber.is_enabled = Lib.GetProtoValue<bool>(module, "is_enabled"); scrubber.ec_rate = Lib.GetProtoValue<double>(module, "ec_rate"); scrubber.co2_rate = Lib.GetProtoValue<double>(module, "co2_rate"); scrubber.efficiency = Lib.GetProtoValue<double>(module, "efficiency"); scrubber.intake_rate = Lib.GetProtoValue(module, "intake_rate", 1.0); //< support versions before 0.9.9.5 scrubber.resource_name = Lib.GetProtoValue(module, "resource_name", "Oxygen"); //< support versions before 0.9.9.5 scrubber.waste_name = Lib.GetProtoValue(module, "waste_name", "CO2"); //< support versions before 0.9.9.5 if (resource_name.Length == 0 || scrubber.resource_name == resource_name) ret.Add(scrubber); } } } return ret; } }
// implement malfunction mechanics for unloaded vessels public static void BackgroundUpdate(Vessel vessel, uint flight_id) { // get data ProtoPartModuleSnapshot m = Lib.GetProtoModule(vessel, flight_id, "Malfunction"); uint malfunctions = Lib.GetProtoValue<uint>(m, "malfunctions"); double min_lifetime = Lib.GetProtoValue<double>(m, "min_lifetime"); double max_lifetime = Lib.GetProtoValue<double>(m, "max_lifetime"); double lifetime = Lib.GetProtoValue<double>(m, "lifetime"); double age = Lib.GetProtoValue<double>(m, "age"); double quality = Lib.GetProtoValue<double>(m, "quality"); string malfunction_msg = m.moduleValues.GetValue("malfunction_msg"); // if for some reason quality wasn't set, default to 1.0 (but don't save it) // note: for example, resque vessels failure get background update without prelaunch if (quality <= double.Epsilon) quality = 1.0; // 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; // save data // note: done before checking for malfunction because proto Break change data again Lib.SetProtoValue<double>(m, "lifetime", lifetime); Lib.SetProtoValue<double>(m, "age", age); // check age and malfunction if needed if (age > lifetime) Break(vessel, m); }
// 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); }
// implement gravity ring mechanics for unloaded vessels public static void BackgroundUpdate(Vessel vessel, uint flight_id) { // get time elapsed from last update double elapsed_s = TimeWarp.fixedDeltaTime; // get data ProtoPartModuleSnapshot m = Lib.GetProtoModule(vessel, flight_id, "GravityRing"); double entertainment_rate = Lib.GetProtoValue<double>(m, "entertainment_rate"); double ec_rate = Lib.GetProtoValue<double>(m, "ec_rate"); float speed = Lib.GetProtoValue<float>(m, "speed"); // consume ec double ec_light_perc = 0.0; if (speed > float.Epsilon) { double ec_light_required = ec_rate * elapsed_s * speed; double ec_light = Lib.RequestResource(vessel, "ElectricCharge", ec_light_required); ec_light_perc = ec_light / ec_light_required; // if there isn't enough ec if (ec_light <= double.Epsilon) { // reset speed speed = 0.0f; } } // set entertainment double rate = 1.0 + (entertainment_rate - 1.0) * speed; // write back data Lib.SetProtoValue(m, "speed", speed); Lib.SetProtoValue(m, "rate", rate); }
// implement scrubber mechanics for unloaded vessels public static void BackgroundUpdate(Vessel vessel, uint flight_id) { // get data ProtoPartModuleSnapshot m = Lib.GetProtoModule(vessel, flight_id, "Scrubber"); bool is_enabled = Lib.GetProtoValue<bool>(m, "is_enabled"); double ec_rate = Lib.GetProtoValue<double>(m, "ec_rate"); double co2_rate = Lib.GetProtoValue<double>(m, "co2_rate"); double efficiency = Lib.GetProtoValue<double>(m, "efficiency"); // if for some reason efficiency wasn't set, default to 50% // note: for example, resque vessels scrubbers get background update without prelaunch if (efficiency <= double.Epsilon) efficiency = 0.5; // get time elapsed from last update double elapsed_s = TimeWarp.fixedDeltaTime; // if inside breathable atmosphere if (LifeSupport.BreathableAtmosphere(vessel)) { // produce oxygen from the intake Lib.RequestResource(vessel, "Oxygen", -Settings.IntakeOxygenRate * elapsed_s); } // if outside breathable atmosphere and enabled else if (is_enabled) { // recycle CO2+EC into oxygen double co2_required = co2_rate * elapsed_s; double co2 = Lib.RequestResource(vessel, "CO2", co2_required); double ec_required = ec_rate * elapsed_s * (co2 / co2_required); double ec = Lib.RequestResource(vessel, "ElectricCharge", ec_required); Lib.RequestResource(vessel, "Oxygen", -co2 * efficiency); } }
// return read-only list of greenhouses in a vessel public static List<Greenhouse> GetGreenhouses(Vessel v) { if (v.loaded) return v.FindPartModulesImplementing<Greenhouse>(); else { List<Greenhouse> ret = new List<Greenhouse>(); foreach(ProtoPartSnapshot part in v.protoVessel.protoPartSnapshots) { foreach(ProtoPartModuleSnapshot module in part.modules) { if (module.moduleName == "Greenhouse") { Greenhouse greenhouse = new Greenhouse(); greenhouse.ec_rate = Lib.GetProtoValue<double>(module, "ec_rate"); greenhouse.waste_rate = Lib.GetProtoValue<double>(module, "waste_rate"); greenhouse.harvest_size = Lib.GetProtoValue<double>(module, "harvest_size"); greenhouse.growth_rate = Lib.GetProtoValue<double>(module, "growth_rate"); greenhouse.door_opened = Lib.GetProtoValue<bool>(module, "door_opened"); greenhouse.growth = Lib.GetProtoValue<double>(module, "growth"); greenhouse.lamps = Lib.GetProtoValue<float>(module, "lamps"); greenhouse.lighting = Lib.GetProtoValue<double>(module, "lighting"); ret.Add(greenhouse); } } } return ret; } }
// fill antennas data void BuildAntennas() { // get error-correcting code factor double ecc = ECC(); // forget previous antennas antennas.Clear(); // for each vessel foreach(Vessel v in FlightGlobals.Vessels) { // skip invalid vessels if (!Lib.IsVessel(v)) continue; // store best antenna values double best_range = 0.0; double best_relay_range = 0.0; double best_relay_cost = 0.0; // get ec available double ec_amount = Lib.GetResourceAmount(v, "ElectricCharge"); // if the vessel is loaded if (v.loaded) { // choose the best antenna foreach(Antenna a in v.FindPartModulesImplementing<Antenna>()) { double range = Range(a.scope, a.penalty, ecc); best_range = Math.Max(best_range, range); if (a.relay && range > best_relay_range && ec_amount >= a.relay_cost * TimeWarp.deltaTime) { best_relay_range = range; best_relay_cost = a.relay_cost; } } } // if the vessel isn't loaded else { // choose the best antenna foreach(ProtoPartSnapshot p in v.protoVessel.protoPartSnapshots) { foreach(ProtoPartModuleSnapshot m in p.modules) { if (m.moduleName == "Antenna") { double range = Range(m.moduleValues.GetValue("scope"), Malfunction.Penalty(p), ecc); double relay_cost = Lib.GetProtoValue<double>(m, "relay_cost"); bool relay = Lib.GetProtoValue<bool>(m, "relay"); best_range = Math.Max(best_range, range); if (relay && range > best_relay_range && ec_amount >= relay_cost * TimeWarp.deltaTime) { best_relay_range = range; best_relay_cost = relay_cost; } } } } } // add antenna data antennas.Add(v.id, new antenna_data(Lib.VesselPosition(v), best_range, best_relay_range, best_relay_cost)); } }
// return malfunction penalty of a part public static double Penalty(ProtoPartSnapshot p, double scale = 0.5) { // get the module // note: if the part has no malfunction, default to no penality ProtoPartModuleSnapshot m = p.modules.Find(k => k.moduleName == "Malfunction"); if (m == null) return 1.0; // return penalty; uint malfunctions = Lib.GetProtoValue<uint>(m, "malfunctions"); return Math.Pow(scale, (double)malfunctions); }
// return true if a vessel is a dead EVA kerbal public static bool IsDead(Vessel vessel) { if (!vessel.isEVA) return false; if (vessel.loaded) return vessel.FindPartModulesImplementing<EVA>()[0].is_dead; foreach(ProtoPartSnapshot part in vessel.protoVessel.protoPartSnapshots) { foreach(ProtoPartModuleSnapshot module in part.modules) { if (module.moduleName == "EVA") return Lib.GetProtoValue<bool>(module, "is_dead"); } } return false; }
// return true if a vessel is a dead EVA kerbal public static bool IsDead(Vessel vessel) { if (!vessel.isEVA) return false; if (vessel.loaded) { var eva = vessel.FindPartModulesImplementing<EVA>(); if (eva == null || eva.Count == 0) return false; //< assume alive for resque EVA, that don't have our module return eva[0].is_dead; } foreach(ProtoPartSnapshot part in vessel.protoVessel.protoPartSnapshots) { foreach(ProtoPartModuleSnapshot module in part.modules) { if (module.moduleName == "EVA") return Lib.GetProtoValue<bool>(module, "is_dead"); } } return false; }
// return read-only list of greenhouses in a vessel public static List<Greenhouse> GetGreenhouses(Vessel v, string resource_name="") { if (v.loaded) { var ret = v.FindPartModulesImplementing<Greenhouse>(); if (resource_name.Length > 0) ret = ret.FindAll(k => k.resource_name == resource_name); return ret == null ? new List<Greenhouse>() : ret; } else { List<Greenhouse> ret = new List<Greenhouse>(); foreach(ProtoPartSnapshot part in v.protoVessel.protoPartSnapshots) { foreach(ProtoPartModuleSnapshot module in part.modules) { if (module.moduleName == "Greenhouse") { Greenhouse greenhouse = new Greenhouse(); greenhouse.resource_name = Lib.GetProtoValue(module, "resource_name", "Food"); //< support versions before 0.9.9.5 greenhouse.waste_name = Lib.GetProtoValue(module, "waste_name", "Crap"); //< support versions before 0.9.9.5 greenhouse.input_name = Lib.GetProtoValue(module, "input_name", ""); //< from version 0.9.9.8 greenhouse.ec_rate = Lib.GetProtoValue<double>(module, "ec_rate"); greenhouse.waste_rate = Lib.GetProtoValue<double>(module, "waste_rate"); greenhouse.input_rate = Lib.GetProtoValue<double>(module, "input_rate", 0.0); greenhouse.harvest_size = Lib.GetProtoValue<double>(module, "harvest_size"); greenhouse.growth_rate = Lib.GetProtoValue<double>(module, "growth_rate"); greenhouse.waste_bonus = Lib.GetProtoValue(module, "waste_bonus", 0.2); //< support versions before 0.9.9.5 greenhouse.soil_bonus = Lib.GetProtoValue(module, "soil_bonus", 0.5); //< support versions before 0.9.9.5 greenhouse.door_opened = Lib.GetProtoValue<bool>(module, "door_opened"); greenhouse.growth = Lib.GetProtoValue<double>(module, "growth"); greenhouse.lamps = Lib.GetProtoValue<float>(module, "lamps"); greenhouse.lighting = Lib.GetProtoValue<double>(module, "lighting"); greenhouse.growing = Lib.GetProtoValue<double>(module, "growing"); if (resource_name.Length == 0 || greenhouse.resource_name == resource_name) ret.Add(greenhouse); } } } return ret; } }
// implement recycler mechanics for unloaded vessels public static void BackgroundUpdate(Vessel vessel, uint flight_id) { // get data ProtoPartModuleSnapshot m = Lib.GetProtoModule(vessel, flight_id, "Recycler"); bool is_enabled = Lib.GetProtoValue<bool>(m, "is_enabled"); string resource_name = Lib.GetProtoValue<string>(m, "resource_name"); string waste_name = Lib.GetProtoValue<string>(m, "waste_name"); double ec_rate = Lib.GetProtoValue<double>(m, "ec_rate"); double waste_rate = Lib.GetProtoValue<double>(m, "waste_rate"); double waste_ratio = Lib.GetProtoValue<double>(m, "waste_ratio"); string filter_name = Lib.GetProtoValue<string>(m, "filter_name"); double filter_rate = Lib.GetProtoValue<double>(m, "filter_rate"); if (is_enabled) { // calculate worst required resource percentual double worst_input = 1.0; double waste_required = waste_rate * TimeWarp.fixedDeltaTime; double waste_amount = Lib.GetResourceAmount(vessel, waste_name); worst_input = Math.Min(worst_input, waste_amount / waste_required); double ec_required = ec_rate * TimeWarp.fixedDeltaTime; double ec_amount = Lib.GetResourceAmount(vessel, "ElectricCharge"); worst_input = Math.Min(worst_input, ec_amount / ec_required); double filter_required = filter_rate * TimeWarp.fixedDeltaTime; if (filter_name.Length > 0 && filter_rate > 0.0) { double filter_amount = Lib.GetResourceAmount(vessel, filter_name); worst_input = Math.Min(worst_input, filter_amount / filter_required); } // recycle EC+waste+filter into resource Lib.RequestResource(vessel, waste_name, waste_required * worst_input); Lib.RequestResource(vessel, "ElectricCharge", ec_required * worst_input); Lib.RequestResource(vessel, filter_name, filter_required * worst_input); Lib.RequestResource(vessel, resource_name, -waste_required * worst_input * waste_ratio); } }
// return read-only list of scrubbers in a vessel public static List<Scrubber> GetScrubbers(Vessel v) { if (v.loaded) return v.FindPartModulesImplementing<Scrubber>(); else { List<Scrubber> ret = new List<Scrubber>(); foreach(ProtoPartSnapshot part in v.protoVessel.protoPartSnapshots) { foreach(ProtoPartModuleSnapshot module in part.modules) { if (module.moduleName == "Scrubber") { Scrubber scrubber = new Scrubber(); scrubber.is_enabled = Lib.GetProtoValue<bool>(module, "is_enabled"); scrubber.ec_rate = Lib.GetProtoValue<double>(module, "ec_rate"); scrubber.co2_rate = Lib.GetProtoValue<double>(module, "co2_rate"); scrubber.efficiency = Lib.GetProtoValue<double>(module, "efficiency"); ret.Add(scrubber); } } } return ret; } }
// return true if it make sense to trigger a malfunction on the vessel public static bool CanMalfunction(Vessel v) { if (v.loaded) { foreach(Malfunction m in v.FindPartModulesImplementing<Malfunction>()) { if (m.malfunctions < 2u) return true; } } else { foreach(ProtoPartSnapshot p in v.protoVessel.protoPartSnapshots) { foreach(ProtoPartModuleSnapshot m in p.modules) { if (m.moduleName == "Malfunction") { if (Lib.GetProtoValue<uint>(m, "malfunctions") < 2u) return true; } } } } return false; }
// called at every simulation step public void FixedUpdate() { // do nothing if paused if (Lib.IsPaused()) return; // do nothing if DB isn't ready if (!DB.Ready()) return; // for each vessel foreach(Vessel vessel in FlightGlobals.Vessels) { // skip invalid vessels if (!Lib.IsVessel(vessel)) continue; // skip loaded vessels if (vessel.loaded) continue; // get vessel data from the db vessel_data vd = DB.VesselData(vessel.id); // get vessel info from the cache vessel_info info = Cache.VesselInfo(vessel); // calculate atmospheric factor (proportion of flux not blocked by atmosphere) double atmo_factor = Sim.AtmosphereFactor(vessel.mainBody, info.position, info.sun_dir); // for each part foreach(ProtoPartSnapshot part in vessel.protoVessel.protoPartSnapshots) { // get part prefab (required for module properties) Part part_prefab = PartLoader.getPartInfoByName(part.partName).partPrefab; // store index of ModuleResourceConverter to process // rationale: a part can contain multiple resource converters int converter_index = 0; // for each module foreach(ProtoPartModuleSnapshot module in part.modules) { // something weird is going on, skip this if (!part_prefab.Modules.Contains(module.moduleName)) continue; // command module if (module.moduleName == "ModuleCommand") { // get module from prefab ModuleCommand command = part_prefab.Modules.GetModules<ModuleCommand>()[0]; // do not consume if this is a MCM with no crew // rationale: for consistency, the game doesn't consume resources for MCM without crew in loaded vessels // this make some sense: you left a vessel with some battery and nobody on board, you expect it to not consume EC if (command.minimumCrew == 0 || part.protoModuleCrew.Count > 0) { // for each input resource foreach(ModuleResource ir in command.inputResources) { // consume the resource Lib.RequestResource(vessel, ir.name, ir.rate * TimeWarp.fixedDeltaTime); } } } // solar panel else if (module.moduleName == "ModuleDeployableSolarPanel") { // determine if extended bool extended = module.moduleValues.GetValue("stateString") == ModuleDeployableSolarPanel.panelStates.EXTENDED.ToString(); // if in sunlight and extended if (info.sunlight && extended) { // get module from prefab ModuleDeployableSolarPanel panel = part_prefab.Modules.GetModules<ModuleDeployableSolarPanel>()[0]; // produce electric charge Lib.RequestResource(vessel, "ElectricCharge", -PanelOutput(vessel, part, panel, info.sun_dir, info.sun_dist, atmo_factor) * TimeWarp.fixedDeltaTime * Malfunction.Penalty(part)); } } // generator // note: assume generators require all input else if (module.moduleName == "ModuleGenerator") { // determine if active bool activated = Convert.ToBoolean(module.moduleValues.GetValue("generatorIsActive")); // if active if (activated) { // get module from prefab ModuleGenerator generator = part_prefab.Modules.GetModules<ModuleGenerator>()[0]; // determine if vessel is full of all output resources bool full = true; foreach(var or in generator.outputList) { double amount = Lib.GetResourceAmount(vessel, or.name); double capacity = Lib.GetResourceCapacity(vessel, or.name); double perc = capacity > 0.0 ? amount / capacity : 0.0; full &= (perc >= 1.0 - double.Epsilon); } // if not full if (!full) { // calculate worst required resource percentual double worst_input = 1.0; foreach(var ir in generator.inputList) { double required = ir.rate * TimeWarp.fixedDeltaTime; double amount = Lib.GetResourceAmount(vessel, ir.name); worst_input = Math.Min(worst_input, amount / required); } // for each input resource foreach(var ir in generator.inputList) { // consume the resource Lib.RequestResource(vessel, ir.name, ir.rate * worst_input * TimeWarp.fixedDeltaTime); } // for each output resource foreach(var or in generator.outputList) { // produce the resource Lib.RequestResource(vessel, or.name, -or.rate * worst_input * TimeWarp.fixedDeltaTime * Malfunction.Penalty(part)); } } } } // converter // note: support multiple resource converters // note: ignore stock temperature mechanic of converters // note: ignore autoshutdown // note: ignore crew experience bonus (seem that stock ignore it too) // note: 'undo' stock behaviour by forcing lastUpdateTime to now (to minimize overlapping calculations from this and stock post-facto simulation) // note: support PlanetaryBaseSystem converters // note: support NearFuture reactors else if (module.moduleName == "ModuleResourceConverter" || module.moduleName == "ModuleKPBSConverter" || module.moduleName == "FissionReactor") { // get module from prefab ModuleResourceConverter converter = part_prefab.Modules.GetModules<ModuleResourceConverter>()[converter_index++]; // determine if active bool activated = Convert.ToBoolean(module.moduleValues.GetValue("IsActivated")); // if active if (activated) { // determine if vessel is full of all output resources bool full = true; foreach(var or in converter.outputList) { double amount = Lib.GetResourceAmount(vessel, or.ResourceName); double capacity = Lib.GetResourceCapacity(vessel, or.ResourceName); double perc = capacity > 0.0 ? amount / capacity : 0.0; full &= (perc >= converter.FillAmount - double.Epsilon); } // if not full if (!full) { // calculate worst required resource percentual double worst_input = 1.0; foreach(var ir in converter.inputList) { double required = ir.Ratio * TimeWarp.fixedDeltaTime; double amount = Lib.GetResourceAmount(vessel, ir.ResourceName); worst_input = Math.Min(worst_input, amount / required); } // for each input resource foreach(var ir in converter.inputList) { // consume the resource Lib.RequestResource(vessel, ir.ResourceName, ir.Ratio * worst_input * TimeWarp.fixedDeltaTime); } // for each output resource foreach(var or in converter.outputList) { // produce the resource Lib.RequestResource(vessel, or.ResourceName, -or.Ratio * worst_input * TimeWarp.fixedDeltaTime * Malfunction.Penalty(part)); } } // undo stock behaviour by forcing last_update_time to now module.moduleValues.SetValue("lastUpdateTime", Planetarium.GetUniversalTime().ToString()); } } // drill // note: ignore stock temperature mechanic of harvesters // note: ignore autoshutdown // note: ignore depletion (stock seem to do the same) // note: 'undo' stock behaviour by forcing lastUpdateTime to now (to minimize overlapping calculations from this and stock post-facto simulation) else if (module.moduleName == "ModuleResourceHarvester") { // determine if active bool activated = Convert.ToBoolean(module.moduleValues.GetValue("IsActivated")); // if active if (activated) { // get module from prefab ModuleResourceHarvester harvester = part_prefab.Modules.GetModules<ModuleResourceHarvester>()[0]; // [disabled] reason: not working // deduce crew bonus /*double experience_bonus = 0.0; if (harvester.UseSpecialistBonus) { foreach(ProtoCrewMember c in vessel.protoVessel.GetVesselCrew()) { experience_bonus = Math.Max(experience_bonus, (c.trait == harvester.Specialty) ? (double)c.experienceLevel : 0.0); } }*/ const double crew_bonus = 1.0; //harvester.SpecialistBonusBase + (experience_bonus + 1.0) * harvester.SpecialistEfficiencyFactor; // detect amount of ore in the ground AbundanceRequest request = new AbundanceRequest { Altitude = vessel.altitude, BodyId = vessel.mainBody.flightGlobalsIndex, CheckForLock = false, Latitude = vessel.latitude, Longitude = vessel.longitude, ResourceType = (HarvestTypes)harvester.HarvesterType, ResourceName = harvester.ResourceName }; double abundance = ResourceMap.Instance.GetAbundance(request); // if there is actually something (should be if active when unloaded) if (abundance > harvester.HarvestThreshold) { // calculate worst required resource percentual double worst_input = 1.0; foreach(var ir in harvester.inputList) { double required = ir.Ratio * TimeWarp.fixedDeltaTime; double amount = Lib.GetResourceAmount(vessel, ir.ResourceName); worst_input = Math.Min(worst_input, amount / required); } // for each input resource foreach(var ir in harvester.inputList) { // consume the resource Lib.RequestResource(vessel, ir.ResourceName, ir.Ratio * worst_input * TimeWarp.fixedDeltaTime); } // determine resource produced double res = abundance * harvester.Efficiency * crew_bonus * worst_input * Malfunction.Penalty(part); // accumulate ore Lib.RequestResource(vessel, harvester.ResourceName, -res * TimeWarp.fixedDeltaTime); } // undo stock behaviour by forcing last_update_time to now module.moduleValues.SetValue("lastUpdateTime", Planetarium.GetUniversalTime().ToString()); } } // asteroid drill // note: untested // note: ignore stock temperature mechanic of asteroid drills // note: ignore autoshutdown // note: 'undo' stock behaviour by forcing lastUpdateTime to now (to minimize overlapping calculations from this and stock post-facto simulation) else if (module.moduleName == "ModuleAsteroidDrill") { // determine if active bool activated = Convert.ToBoolean(module.moduleValues.GetValue("IsActivated")); // if active if (activated) { // get module from prefab ModuleAsteroidDrill asteroid_drill = part_prefab.Modules.GetModules<ModuleAsteroidDrill>()[0]; // [disabled] reason: not working // deduce crew bonus /*double experience_bonus = 0.0; if (asteroid_drill.UseSpecialistBonus) { foreach(ProtoCrewMember c in vessel.protoVessel.GetVesselCrew()) { experience_bonus = Math.Max(experience_bonus, (c.trait == asteroid_drill.Specialty) ? (double)c.experienceLevel : 0.0); } }*/ const double crew_bonus = 1.0; //asteroid_drill.SpecialistBonusBase + (experience_bonus + 1.0) * asteroid_drill.SpecialistEfficiencyFactor; // get asteroid data ProtoPartModuleSnapshot asteroid_info = null; ProtoPartModuleSnapshot asteroid_resource = null; foreach(ProtoPartSnapshot p in vessel.protoVessel.protoPartSnapshots) { if (asteroid_info == null) asteroid_info = p.modules.Find(k => k.moduleName == "ModuleAsteroidInfo"); if (asteroid_resource == null) asteroid_resource = p.modules.Find(k => k.moduleName == "ModuleAsteroidResource"); } // if there is actually an asteroid attached to this active asteroid drill (it should) if (asteroid_info != null && asteroid_resource != null) { // get some data double mass_threshold = Convert.ToDouble(asteroid_info.moduleValues.GetValue("massThresholdVal")); double mass = Convert.ToDouble(asteroid_info.moduleValues.GetValue("currentMassVal")); double abundance = Convert.ToDouble(asteroid_resource.moduleValues.GetValue("abundance")); string res_name = asteroid_resource.moduleValues.GetValue("resourceName"); double res_density = PartResourceLibrary.Instance.GetDefinition(res_name).density; // if asteroid isn't depleted if (mass > mass_threshold && abundance > double.Epsilon) { // consume EC double ec_required = asteroid_drill.PowerConsumption * TimeWarp.fixedDeltaTime; double ec_consumed = Lib.RequestResource(vessel, "ElectricCharge", ec_required); double ec_ratio = ec_consumed / ec_required; // determine resource extracted double res_amount = abundance * asteroid_drill.Efficiency * crew_bonus * ec_ratio * TimeWarp.fixedDeltaTime; // produce mined resource Lib.RequestResource(vessel, res_name, -res_amount); // consume asteroid mass asteroid_info.moduleValues.SetValue("currentMassVal", (mass - res_density * res_amount).ToString()); } } // undo stock behaviour by forcing last_update_time to now module.moduleValues.SetValue("lastUpdateTime", Planetarium.GetUniversalTime().ToString()); } } // science lab // note: we are only simulating the EC consumption // note: there is no easy way to 'stop' the lab when there isn't enough EC else if (module.moduleName == "ModuleScienceConverter") { // get module from prefab ModuleScienceConverter lab = part_prefab.Modules.GetModules<ModuleScienceConverter>()[0]; // determine if active bool activated = Convert.ToBoolean(module.moduleValues.GetValue("IsActivated")); // if active if (activated) { Lib.RequestResource(vessel, "ElectricCharge", lab.powerRequirement * TimeWarp.fixedDeltaTime); } } // SCANSAT support else if (module.moduleName == "SCANsat" || module.moduleName == "ModuleSCANresourceScanner") { // get ec consumption rate PartModule scansat = part_prefab.Modules[module.moduleName]; double power = Lib.ReflectionValue<float>(scansat, "power"); double ec_required = power * TimeWarp.fixedDeltaTime; bool is_scanning = Lib.GetProtoValue<bool>(module, "scanning"); bool was_disabled = vd.scansat_id.Contains(part.flightID); // if its scanning if (Lib.GetProtoValue<bool>(module, "scanning")) { // consume ec double ec_consumed = Lib.RequestResource(vessel, "ElectricCharge", ec_required); // if there isn't enough ec if (ec_consumed < ec_required * 0.99 && ec_required > double.Epsilon) { // unregister scanner SCANsat.stopScanner(vessel, module, part_prefab); // remember disabled scanner vd.scansat_id.Add(part.flightID); // give the user some feedback if (DB.VesselData(vessel.id).cfg_ec == 1) Message.Post("SCANsat sensor was disabled on <b>" + vessel.vesselName + "</b>"); } } // if it was disabled else if (vd.scansat_id.Contains(part.flightID)) { // if there is enough ec double ec_amount = Lib.GetResourceAmount(vessel, "ElectricCharge"); double ec_capacity = Lib.GetResourceCapacity(vessel, "ElectricCharge"); if (ec_capacity > double.Epsilon && ec_amount / ec_capacity > 0.25) //< re-enable at 25% EC { // re-enable the scanner SCANsat.resumeScanner(vessel, module, part_prefab); // give the user some feedback if (DB.VesselData(vessel.id).cfg_ec == 1) Message.Post("SCANsat sensor resumed operations on <b>" + vessel.vesselName + "</b>"); } } // forget active scanners if (Lib.GetProtoValue<bool>(module, "scanning")) vd.scansat_id.Remove(part.flightID); } // NearFutureSolar support // note: we assume deployed, this is a current limitation else if (module.moduleName == "ModuleCurvedSolarPanel") { // if in sunlight if (info.sunlight) { PartModule curved_panel = part_prefab.Modules[module.moduleName]; double output = CurvedPanelOutput(vessel, part, part_prefab, curved_panel, info.sun_dir, info.sun_dist, atmo_factor) * Malfunction.Penalty(part); Lib.RequestResource(vessel, "ElectricCharge", -output * TimeWarp.fixedDeltaTime); } } // NearFutureElectrical support // note: fission generator ignore heat // note: radioisotope generator doesn't support easy mode else if (module.moduleName == "FissionGenerator") { PartModule generator = part_prefab.Modules[module.moduleName]; double power = Lib.ReflectionValue<float>(generator, "PowerGeneration"); // get fission reactor tweakable, will default to 1.0 for other modules var reactor = part.modules.Find(k => k.moduleName == "FissionReactor"); double tweakable = reactor == null ? 1.0 : Lib.ConfigValue(reactor.moduleValues, "CurrentPowerPercent", 100.0) * 0.01; Lib.RequestResource(vessel, "ElectricCharge", -power * tweakable * TimeWarp.fixedDeltaTime); } else if (module.moduleName == "ModuleRadioisotopeGenerator") { double mission_time = vessel.missionTime / (3600.0 * Lib.HoursInDay() * Lib.DaysInYear()); PartModule generator = part_prefab.Modules[module.moduleName]; double half_life = Lib.ReflectionValue<float>(generator, "HalfLife"); double remaining = Math.Pow(2.0, (-mission_time) / half_life); double power = Lib.ReflectionValue<float>(generator, "BasePower"); Lib.RequestResource(vessel, "ElectricCharge", -power * remaining * TimeWarp.fixedDeltaTime); } // KERBALISM modules else if (module.moduleName == "Scrubber") { Scrubber.BackgroundUpdate(vessel, part.flightID); } else if (module.moduleName == "Greenhouse") { Greenhouse.BackgroundUpdate(vessel, part.flightID); } else if (module.moduleName == "GravityRing") { GravityRing.BackgroundUpdate(vessel, part.flightID); } else if (module.moduleName == "Malfunction") { Malfunction.BackgroundUpdate(vessel, part.flightID); } } } } }
// implement greenhouse mechanics for unloaded vessels public static void BackgroundUpdate(Vessel vessel, uint flight_id) { // get data ProtoPartModuleSnapshot m = Lib.GetProtoModule(vessel, flight_id, "Greenhouse"); double ec_rate = Lib.GetProtoValue<double>(m, "ec_rate"); double waste_rate = Lib.GetProtoValue<double>(m, "waste_rate"); double harvest_size = Lib.GetProtoValue<double>(m, "harvest_size"); double growth_rate = Lib.GetProtoValue<double>(m, "growth_rate"); bool door_opened = Lib.GetProtoValue<bool>(m, "door_opened"); double growth = Lib.GetProtoValue<double>(m, "growth"); float lamps = Lib.GetProtoValue<float>(m, "lamps"); double lighting = Lib.GetProtoValue<double>(m, "lighting"); // get time elapsed from last update double elapsed_s = TimeWarp.fixedDeltaTime; // consume ec for lighting double ec_light_perc = 0.0; if (lamps > float.Epsilon) { double ec_light_required = ec_rate * elapsed_s * lamps; double ec_light = Lib.RequestResource(vessel, "ElectricCharge", ec_light_required); ec_light_perc = ec_light / ec_light_required; // if there isn't enough ec for lighting if (ec_light <= double.Epsilon) { // shut down the light lamps = 0.0f; } } // get vessel info from the cache vessel_info info = Cache.VesselInfo(vessel); // determine lighting conditions // note: we ignore sun direction for gameplay reasons: else the user must reorient the greenhouse as the planets dance over time // - natural light depend on: distance from sun, direct sunlight, door status // - artificial light depend on: lamps tweakable and ec available, door status lighting = NaturalLighting(info.sun_dist) * (info.sunlight ? 1.0 : 0.0) * (door_opened ? 1.0 : 0.0) + lamps * ec_light_perc * (door_opened ? 1.0 : 1.0 + Settings.GreenhouseDoorBonus); // consume waste double waste_required = waste_rate * elapsed_s; double waste = Lib.RequestResource(vessel, "Waste", waste_required); double waste_perc = waste / waste_required; // determine growth bonus double growth_bonus = 0.0; growth_bonus += Settings.GreenhouseSoilBonus * (Lib.Landed(vessel) ? 1.0 : 0.0); growth_bonus += Settings.GreenhouseWasteBonus * waste_perc; // grow the crop growth += elapsed_s * (growth_rate * (1.0 + growth_bonus)) * lighting; // if it is harvest time if (growth >= 1.0) { // reset growth growth = 0.0; // produce food Lib.RequestResource(vessel, "Food", -harvest_size); // show a message to the user Message.Post("On <color=FFFFFF>" + vessel.vesselName + "</color> the crop harvest produced <color=FFFFFF>" + harvest_size.ToString("F0") + " Food</color>"); } // store data Lib.SetProtoValue(m, "growth", growth); Lib.SetProtoValue(m, "lighting", lighting); Lib.SetProtoValue(m, "lamps", lamps); }