diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fd5204b --- /dev/null +++ b/.gitignore @@ -0,0 +1,183 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ + +# Roslyn cache directories +*.ide/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +#NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding addin-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# If using the old MSBuild-Integrated Package Restore, uncomment this: +#!**/packages/repositories.config + +# Windows Azure Build Output +csx/ +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e010614 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +0.1.0 +----- + - First release \ No newline at end of file diff --git a/GameData/zKerbalismFFT/FFTKerbalismSupport.cfg b/GameData/zKerbalismFFT/FFTKerbalismSupport.cfg new file mode 100644 index 0000000..4679be5 --- /dev/null +++ b/GameData/zKerbalismFFT/FFTKerbalismSupport.cfg @@ -0,0 +1,33 @@ +// ============================================================================ +// Add resources and processes to the KerbalismSupport profile +// ============================================================================ +Profile +{ + name = KerbalismSupport + modname = Far Future Technologies + moddir = FarFutureTechnologies + + Supply + { + resource = Antimatter + low_message = #Kerbalism_FFT_low_Antimatter + empty_message = #Kerbalism_FFT_empty_Antimatter + refill_message = #Kerbalism_FFT_refill_Antimatter + } + + Supply + { + resource = LqdDeuterium + low_message = #Kerbalism_FFT_low_LqdDeuterium + empty_message = #Kerbalism_FFT_empty_LqdDeuterium + refill_message = #Kerbalism_FFT_refill_LqdDeuterium + } + + Supply + { + resource = LqdHe3 + low_message = #Kerbalism_FFT_low_LqdHe3 + empty_message = #Kerbalism_FFT_empty_LqdHe3 + refill_message = #Kerbalism_FFT_refill_LqdHe3 + } +} diff --git a/GameData/zKerbalismFFT/Localization/en-us.cfg b/GameData/zKerbalismFFT/Localization/en-us.cfg new file mode 100644 index 0000000..f8cbf43 --- /dev/null +++ b/GameData/zKerbalismFFT/Localization/en-us.cfg @@ -0,0 +1,19 @@ +Localization +{ + en-us + { + #Kerbalism_FFT_low_Antimatter = Antimatter tanks are almost empty on $VESSEL + #Kerbalism_FFT_empty_Antimatter = There is no more antimatter on $VESSEL tanks + #Kerbalism_FFT_refill_Antimatter = $VESSEL antimatter tanks refilled + #Kerbalism_FFT_low_LqdDeuterium = Lqd. deuterium tanks are almost empty on $VESSEL + #Kerbalism_FFT_empty_LqdDeuterium = There is no more lqd. deuterium on $VESSEL tanks + #Kerbalism_FFT_refill_LqdDeuterium = $VESSEL lqd. deuterium tanks refilled + #Kerbalism_FFT_low_LqdHe3 = Lqd. helium-3 tanks are almost empty on $VESSEL + #Kerbalism_FFT_empty_LqdHe3 = There is no more lqd. helium-3 on $VESSEL tanks + #Kerbalism_FFT_refill_LqdHe3 = $VESSEL lqd. helium-3 tanks refilled + #LOC_KerbalismFFT_Brokers_FusionReactor = fusion reactor + #LOC_KerbalismFFT_Brokers_FusionEngine = fusion engine + #LOC_KerbalismFFT_Brokers_AntimatterTank = antimatter tank + #LOC_KerbalismFFT_AntimatterTank_Detonation_Msg = No more Electric charge on vessel <<1>>. Antimatter tanks lost power and antimatter detonation is occuring! + } +} diff --git a/GameData/zKerbalismFFT/Localization/ru.cfg b/GameData/zKerbalismFFT/Localization/ru.cfg new file mode 100644 index 0000000..a4ce78e --- /dev/null +++ b/GameData/zKerbalismFFT/Localization/ru.cfg @@ -0,0 +1,19 @@ +Localization +{ + ru + { + #Kerbalism_FFT_low_Antimatter = $VESSEL: Запас антиматерии почти иссяк + #Kerbalism_FFT_empty_Antimatter = $VESSEL: Антиматерия закончилась + #Kerbalism_FFT_refill_Antimatter = $VESSEL: Запас антиматерии пополняется + #Kerbalism_FFT_low_LqdDeuterium = $VESSEL: Баки с жидким дейтерием почти пусты + #Kerbalism_FFT_empty_LqdDeuterium = $VESSEL: Жидкий дейтерий закончился + #Kerbalism_FFT_refill_LqdDeuterium = $VESSEL: Баки с жидким дейтерием наполняются + #Kerbalism_FFT_low_LqdHe3 = $VESSEL: Баки с жидким гелием-3 почти пусты + #Kerbalism_FFT_empty_LqdHe3 = $VESSEL: Жидкий гелий-3 закончился + #Kerbalism_FFT_refill_LqdHe3 = $VESSEL: Баки с жидким гелием-3 наполняются + #LOC_KerbalismFFT_Brokers_FusionReactor = термоядерный реактор + #LOC_KerbalismFFT_Brokers_FusionEngine = термоядерный двигатель + #LOC_KerbalismFFT_Brokers_AntimatterTank = бак с антиматерией + #LOC_KerbalismFFT_AntimatterTank_Detonation_Msg = На корабле <<1>> иссяк запас электричества. Баки с антиматерией потеряли питание и началась аннигиляция антиматерии! + } +} diff --git a/GameData/zKerbalismFFT/Patches/FFTAntimatterTanks.cfg b/GameData/zKerbalismFFT/Patches/FFTAntimatterTanks.cfg new file mode 100644 index 0000000..0d4083d --- /dev/null +++ b/GameData/zKerbalismFFT/Patches/FFTAntimatterTanks.cfg @@ -0,0 +1,14 @@ +@PART[*]:HAS[@MODULE[ModuleAntimatterTank]]:NEEDS[Kerbalism]:AFTER[FarFutureTechnologies] +{ + @MODULE[ModuleAntimatterTank] + { + @name = FFTModuleAntimatterTankKerbalism + } + + MODULE + { + name = PlannerController + title = #LOC_KerbalismFFT_Brokers_AntimatterTank + considered = true + } +} diff --git a/GameData/zKerbalismFFT/Patches/FFTEngines.cfg b/GameData/zKerbalismFFT/Patches/FFTEngines.cfg new file mode 100644 index 0000000..da5bb31 --- /dev/null +++ b/GameData/zKerbalismFFT/Patches/FFTEngines.cfg @@ -0,0 +1,380 @@ +// +// Engines radioactivity +// + +@PART[fft-antimatter-beam-1]:NEEDS[FeatureRadiation]:AFTER[FarFutureTechnologies] +{ + MODULE + { + name = Emitter + radiation = 300 // rad/s + } + MODULE + { + name = FFTRadioactiveEngine + engineID1 = AntimatterBeam + // FloatCurve with values "engine thrust (in % fo MaxThrust) / Emitter radiation (in % of max radiation)" + EmissionPercentEngine1 + { + key = 0 0 0 0 + key = 1 20 0 0 + key = 100 100 0 0 + } + // Minimum possible Emitter radiation (in % of max radiation) after full radiation decay + MinEmissionPercent = 0.0001 + // Time to lower Emitter radiation by 1%, in seconds, after engine has been stopped + EmissionDecayRate = 0.1 + } +} +@PART[fft-antimatter-microfission-1]:NEEDS[FeatureRadiation]:AFTER[FarFutureTechnologies] +{ + MODULE + { + name = Emitter + radiation = 0.6 // rad/s + } + MODULE + { + name = FFTRadioactiveEngine + engineID1 = ReactionProducts + // FloatCurve with values "engine thrust (in % fo MaxThrust) / Emitter radiation (in % of max radiation)" + EmissionPercentEngine1 + { + key = 0 0 0 0 + key = 1 20 0 0 + key = 100 100 0 0 + } + // Minimum possible Emitter radiation (in % of max radiation) after full radiation decay + MinEmissionPercent = 0.001 + // Time to lower Emitter radiation by 1%, in seconds, after engine has been stopped + EmissionDecayRate = 0.5 + } +} +@PART[fft-antimatter-microfusion-1]:NEEDS[FeatureRadiation]:AFTER[FarFutureTechnologies] +{ + MODULE + { + name = Emitter + radiation = 0.6 // rad/s + } + MODULE + { + name = FFTRadioactiveEngine + engineID1 = AIMReactor + // FloatCurve with values "engine thrust (in % fo MaxThrust) / Emitter radiation (in % of max radiation)" + EmissionPercentEngine1 + { + key = 0 0 0 0 + key = 1 20 0 0 + key = 100 100 0 0 + } + // Minimum possible Emitter radiation (in % of max radiation) after full radiation decay + MinEmissionPercent = 0.001 + // Time to lower Emitter radiation by 1%, in seconds, after engine has been stopped + EmissionDecayRate = 0.5 + } +} +@PART[fft-ffre-plasma-1]:NEEDS[FeatureRadiation]:AFTER[FarFutureTechnologies] +{ + MODULE + { + name = Emitter + radiation = 0.2 // rad/s + } + MODULE + { + name = FFTRadioactiveEngine + engineID1 = FissionFragment + engineID2 = Afterburning + // FloatCurve with values "engine thrust (in % fo MaxThrust) / Emitter radiation (in % of max radiation)" + EmissionPercentEngine1 + { + key = 0 0 0 0 + key = 1 10 0 0 + key = 100 50 0 0 + } + // FloatCurve with values "engine thrust (in % fo MaxThrust) / Emitter radiation (in % of max radiation)" + EmissionPercentEngine2 + { + key = 0 0 0 0 + key = 1 20 0 0 + key = 100 100 0 0 + } + // Minimum possible Emitter radiation (in % of max radiation) after full radiation decay + MinEmissionPercent = 0.001 + // Time to lower Emitter radiation by 1%, in seconds, after engine has been stopped + EmissionDecayRate = 0.5 + } +} +@PART[fft-ffre-solid-1]:NEEDS[FeatureRadiation]:AFTER[FarFutureTechnologies] +{ + MODULE + { + name = Emitter + radiation = 0.1 // rad/s + } + MODULE + { + name = FFTRadioactiveEngine + engineID1 = FissionFragment + // FloatCurve with values "engine thrust (in % fo MaxThrust) / Emitter radiation (in % of max radiation)" + EmissionPercentEngine1 + { + key = 0 0 0 0 + key = 1 20 0 0 + key = 100 100 0 0 + } + // Minimum possible Emitter radiation (in % of max radiation) after full radiation decay + MinEmissionPercent = 0.001 + // Time to lower Emitter radiation by 1%, in seconds, after engine has been stopped + EmissionDecayRate = 2 + } +} +@PART[fft-fission-zpinch-1]:NEEDS[FeatureRadiation]:AFTER[FarFutureTechnologies] +{ + MODULE + { + name = Emitter + radiation = 6 // rad/s + } + MODULE + { + name = FFTRadioactiveEngine + engineID1 = FissionPulse + // FloatCurve with values "engine thrust (in % fo MaxThrust) / Emitter radiation (in % of max radiation)" + EmissionPercentEngine1 + { + key = 0 0 0 0 + key = 1 20 0 0 + key = 100 100 0 0 + } + // Minimum possible Emitter radiation (in % of max radiation) after full radiation decay + MinEmissionPercent = 0.001 + // Time to lower Emitter radiation by 1%, in seconds, after engine has been stopped + EmissionDecayRate = 0.5 + } +} +@PART[fft-nswr-1]:NEEDS[FeatureRadiation]:AFTER[FarFutureTechnologies] +{ + MODULE + { + name = Emitter + radiation = 40 // rad/s + } + MODULE + { + name = FFTRadioactiveEngine + engineID1 = nswr + // FloatCurve with values "engine thrust (in % fo MaxThrust) / Emitter radiation (in % of max radiation)" + EmissionPercentEngine1 + { + key = 0 0 0 0 + key = 1 20 0 0 + key = 100 100 0 0 + } + // Minimum possible Emitter radiation (in % of max radiation) after full radiation decay + MinEmissionPercent = 0.001 + // Time to lower Emitter radiation by 1%, in seconds, after engine has been stopped + EmissionDecayRate = 10 + } +} +@PART[fft-fusion-axial-zpinch-1]:NEEDS[FeatureRadiation]:AFTER[FarFutureTechnologies] +{ + MODULE + { + name = Emitter + radiation = 100 // rad/s + } + MODULE + { + name = FFTRadioactiveEngine + engineID1 = ReactionProducts + engineID2 = Afterburner + // FloatCurve with values "engine thrust (in % fo MaxThrust) / Emitter radiation (in % of max radiation)" + EmissionPercentEngine1 + { + key = 0 0 0 0 + key = 1 10 0 0 + key = 100 50 0 0 + } + // FloatCurve with values "engine thrust (in % fo MaxThrust) / Emitter radiation (in % of max radiation)" + EmissionPercentEngine2 + { + key = 0 0 0 0 + key = 1 20 0 0 + key = 100 100 0 0 + } + // Minimum possible Emitter radiation (in % of max radiation) after full radiation decay + MinEmissionPercent = 0.001 + // Time to lower Emitter radiation by 1%, in seconds, after engine has been stopped + EmissionDecayRate = 0.5 + } +} +@PART[fft-fusion-inertial-laser-1]:NEEDS[FeatureRadiation]:AFTER[FarFutureTechnologies] +{ + MODULE + { + name = Emitter + radiation = 0.2 // rad/s + } + MODULE + { + name = FFTRadioactiveEngine + engineID1 = ReactionProducts + engineID2 = LowDensity + // FloatCurve with values "engine thrust (in % fo MaxThrust) / Emitter radiation (in % of max radiation)" + EmissionPercentEngine1 + { + key = 0 0 0 0 + key = 1 16 0 0 + key = 100 80 0 0 + } + // FloatCurve with values "engine thrust (in % fo MaxThrust) / Emitter radiation (in % of max radiation)" + EmissionPercentEngine2 + { + key = 0 0 0 0 + key = 1 20 0 0 + key = 100 100 0 0 + } + // Minimum possible Emitter radiation (in % of max radiation) after full radiation decay + MinEmissionPercent = 0.001 + // Time to lower Emitter radiation by 1%, in seconds, after engine has been stopped + EmissionDecayRate = 0.5 + } +} +@PART[fft-fusion-inertial-magnetic-1]:NEEDS[FeatureRadiation]:AFTER[FarFutureTechnologies] +{ + MODULE + { + name = Emitter + radiation = 0.5 // rad/s + } + MODULE + { + name = FFTRadioactiveEngine + engineID1 = Deuterium + engineID2 = D-He3 + // FloatCurve with values "engine thrust (in % fo MaxThrust) / Emitter radiation (in % of max radiation)" + EmissionPercentEngine1 + { + key = 0 0 0 0 + key = 1 20 0 0 + key = 100 100 0 0 + } + // FloatCurve with values "engine thrust (in % fo MaxThrust) / Emitter radiation (in % of max radiation)" + EmissionPercentEngine2 + { + key = 0 0 0 0 + key = 1 4 0 0 + key = 100 20 0 0 + } + // Minimum possible Emitter radiation (in % of max radiation) after full radiation decay + MinEmissionPercent = 0.001 + // Time to lower Emitter radiation by 1%, in seconds, after engine has been stopped + EmissionDecayRate = 0.5 + } +} +@PART[fft-fusion-magnetic-mirror-1]:NEEDS[FeatureRadiation]:AFTER[FarFutureTechnologies] +{ + MODULE + { + name = Emitter + radiation = 20 // rad/s + } + MODULE + { + name = FFTRadioactiveEngine + engineID1 = ReactionProducts + engineID2 = Afterburner + // FloatCurve with values "engine thrust (in % fo MaxThrust) / Emitter radiation (in % of max radiation)" + EmissionPercentEngine1 + { + key = 0 0 0 0 + key = 1 18 0 0 + key = 100 90 0 0 + } + // FloatCurve with values "engine thrust (in % fo MaxThrust) / Emitter radiation (in % of max radiation)" + EmissionPercentEngine2 + { + key = 0 0 0 0 + key = 1 20 0 0 + key = 100 100 0 0 + } + // Minimum possible Emitter radiation (in % of max radiation) after full radiation decay + MinEmissionPercent = 0.001 + // Time to lower Emitter radiation by 1%, in seconds, after engine has been stopped + EmissionDecayRate = 0.5 + } +} +@PART[fft-fusion-magnetic-tokamak-1]:NEEDS[FeatureRadiation]:AFTER[FarFutureTechnologies] +{ + MODULE + { + name = Emitter + radiation = 0.1 // rad/s + } + MODULE + { + name = FFTRadioactiveEngine + engineID1 = LowPower + engineID2 = HighPower + // FloatCurve with values "engine thrust (in % fo MaxThrust) / Emitter radiation (in % of max radiation)" + EmissionPercentEngine1 + { + key = 0 0 0 0 + key = 1 16 0 0 + key = 100 80 0 0 + } + // FloatCurve with values "engine thrust (in % fo MaxThrust) / Emitter radiation (in % of max radiation)" + EmissionPercentEngine2 + { + key = 0 0 0 0 + key = 1 20 0 0 + key = 100 100 0 0 + } + // Minimum possible Emitter radiation (in % of max radiation) after full radiation decay + MinEmissionPercent = 0.001 + // Time to lower Emitter radiation by 1%, in seconds, after engine has been stopped + EmissionDecayRate = 1 + } +} +@PART[fft-fusion-magnetic-tokamak-aerospike-1]:NEEDS[FeatureRadiation]:AFTER[FarFutureTechnologies] +{ + MODULE + { + name = Emitter + radiation = 0.1 // rad/s + } + MODULE + { + name = FFTRadioactiveEngine + engineID1 = D-He3 + // FloatCurve with values "engine thrust (in % fo MaxThrust) / Emitter radiation (in % of max radiation)" + EmissionPercentEngine1 + { + key = 0 0 0 0 + key = 1 20 0 0 + key = 100 100 0 0 + } + // Minimum possible Emitter radiation (in % of max radiation) after full radiation decay + MinEmissionPercent = 0.001 + // Time to lower Emitter radiation by 1%, in seconds, after engine has been stopped + EmissionDecayRate = 1 + } +} + +// +// Engines reliability +// +@PART[fft-*]:HAS[@MODULE[ModuleEngines*]]:NEEDS[FeatureReliability]:AFTER[KerbalismDefault] +{ + @MODULE[Reliability]:HAS[#type[ModuleEngines*]] + { + @rated_operation_duration = 0 + @rated_ignitions = 0 + @turnon_failure_probability = 0.001 + @extra_cost = 1.25 + @extra_mass = 0.05 + @repair = Engineer@5 + } +} diff --git a/GameData/zKerbalismFFT/Patches/FFTFusionEngines.cfg b/GameData/zKerbalismFFT/Patches/FFTFusionEngines.cfg new file mode 100644 index 0000000..555ba31 --- /dev/null +++ b/GameData/zKerbalismFFT/Patches/FFTFusionEngines.cfg @@ -0,0 +1,15 @@ +@PART[*]:HAS[@MODULE[ModuleFusionEngine]]:NEEDS[Kerbalism]:AFTER[FarFutureTechnologies] +{ + MODULE + { + name = FFTFusionEngineKerbalismUpdater + reactorModuleID = fusionreactor + } + + MODULE + { + name = PlannerController + title = #LOC_KerbalismFFT_Brokers_FusionEngine + considered = true + } +} diff --git a/GameData/zKerbalismFFT/Patches/FFTFusionReactors.cfg b/GameData/zKerbalismFFT/Patches/FFTFusionReactors.cfg new file mode 100644 index 0000000..4295769 --- /dev/null +++ b/GameData/zKerbalismFFT/Patches/FFTFusionReactors.cfg @@ -0,0 +1,32 @@ +@PART[*]:HAS[@MODULE[FusionReactor]]:NEEDS[Kerbalism]:AFTER[FarFutureTechnologies] +{ + MODULE + { + name = FFTFusionReactorKerbalismUpdater + reactorModuleID = fusionreactor + + // Minimum possible Emitter radiation (in % of max radiation) after full radiation decay + MinEmissionPercent = 10 + // Time to lower Emitter radiation by 1%, in seconds, after reactor has been stopped + EmissionDecayRate = 360 + } + + MODULE + { + name = PlannerController + title = #LOC_KerbalismFFT_Brokers_FusionReactor + considered = true + } + + MODULE:NEEDS[FeatureReliability] + { + name = Reliability + type = FusionReactor + title = #LOC_KerbalismFFT_Brokers_FusionReactor + redundancy = Power Generation + repair = Engineer + mtbf = 36288000 + extra_cost = 2.5 + extra_mass = 1.0 + } +} diff --git a/GameData/zKerbalismFFT/Patches/FFTScience.cfg b/GameData/zKerbalismFFT/Patches/FFTScience.cfg new file mode 100644 index 0000000..719e3ff --- /dev/null +++ b/GameData/zKerbalismFFT/Patches/FFTScience.cfg @@ -0,0 +1,40 @@ +@KERBALISM_EXPERIMENT_VALUES:NEEDS[FeatureScience,FarFutureTechnologies] +{ + FFT + { + particleDetector + { + size = 72000 + value = 50 + duration = 18406656 // 2 Kerbin years + } + } +} + +@EXPERIMENT_DEFINITION:HAS[#id[fftParticleDetector]]:NEEDS[FeatureScience,FarFutureTechnologies]:FOR[zzzKerbalismDefault] +{ + @baseValue = #$@KERBALISM_EXPERIMENT_VALUES/FFT/particleDetector/value$ + @dataScale = #$@KERBALISM_EXPERIMENT_VALUES/FFT/particleDetector/size$ + @dataScale /= #$baseValue$ + + KERBALISM_EXPERIMENT + { + Situation = Surface@Biomes + Situation = InSpaceLow + Situation = InSpaceHigh + } +} + +@PART[*]:HAS[@MODULE[ModuleScienceExperiment]:HAS[#experimentID[fftParticleDetector]]]:NEEDS[FeatureScience,FarFutureTechnologies]:FOR[zzzKerbalismDefault] +{ + !MODULE[ModuleScienceExperiment]:HAS[#experimentID[fftParticleDetector]] {} + MODULE + { + name = Experiment + experiment_id = fftParticleDetector + data_rate = #$@KERBALISM_EXPERIMENT_VALUES/FFT/particleDetector/size$ + @data_rate /= #$@KERBALISM_EXPERIMENT_VALUES/FFT/particleDetector/duration$ + ec_rate = 0.5 + allow_shrouded = False + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..30b88a3 --- /dev/null +++ b/README.md @@ -0,0 +1,62 @@ +# Kerbalism FarFutureTechnologies + +Experimental Kerbalism support for Far Future Technologies. + +## What parts and features of Far Future Technologies mod are supported and how? + +### Antimatter tanks + +Antimatter tanks now have full Kerbalism resource system support: +* Planner in VAB/Hangar: information about EC consumption of antimatter containment. +* EC consumption for active vessel: works exactly as in FFT, but use Kerbalism EC consumption/production system. +* EC consumption for unloaded vessels. If vessel runs out of electric charge, antimatter containment will be shut down and all antimatter will annihilate. Resulting thermal energy will be added to antimatter tank on vessel load, which could result in tank explosion. Do not leave your antimatter tanks without power! + +### Fusion reactors (this includes some FFT engines with fusion reactors) +* Planner in VAB/Hangar: information about EC production and De (or De/He3) consumption. +* Support for unloaded vessels. Reactor will automatically adjust it's throttle (respecting min and max throttle values) in order to satisfy vessel electricity consumption. + +### Particle detector science experiment + +Converted to Kerbalism science experiment. Experiment duration set to 2 Kerbin years. + +### Engines reliability + +Universal Kerbalism reliability patch makes not really good work for FFT engines. All FFT engines are given more suitable reliability parameters, like unlimited ignitions count. + +### Fusion reactors radioactivity + +Like reactors from NFElectrical, FFT fusion reactors now emit some radiation. +However, reactors will start emitting radiation only then they have been started, and after they have been shutdown, emission will slowly decay to some minimum value. + +### FFT engines radioactivity + +All FFT engines are radioactive. Some of them are **extremly** radioactive. +This mod implements new mechanics for FFT engines: they start emitting radiation then started, and emission is tied to engine throttle. After engine has been shutdown, emission will rapidly decay to some minimum value. + + +## Dependencies + +* [Kerbalism (3.14)](https://github.com/Kerbalism/Kerbalism) +* [FarFutureTechnologies (1.1.4)](https://github.com/post-kerbin-mining-corporation/FarFutureTechnologies) +* [KerbalismSystemHeat (0.4)](https://github.com/judicator/KerbalismSystemHeat) +* [Module manager (last version preferred)](https://github.com/sarbian/ModuleManager) + + +## Installation + +Please remove mod folder (zKerbalismFFT) from GameData folder inside your Kerbal Space Program folder before installation. + +Then place the GameData folder from downloaded archive inside your Kerbal Space Program folder. + + +## Licensing + +The MIT License (MIT) + +Copyright (c) 2021 Alexander Rogov + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/DetectKerbalism.cs b/src/DetectKerbalism.cs new file mode 100644 index 0000000..b7f6917 --- /dev/null +++ b/src/DetectKerbalism.cs @@ -0,0 +1,52 @@ +using System.Reflection; +using UnityEngine; +using KSP.Localization; + +namespace KerbalismFFT +{ + [KSPAddon(KSPAddon.Startup.MainMenu, true)] + public class DetectKerbalism : MonoBehaviour + { + public void Start() + { + bool kerbalismFound = false; + foreach (var a in AssemblyLoader.loadedAssemblies) + { + // Kerbalism comes with more than one assembly. There is Kerbalism for debug builds, KerbalismBootLoader, + // then there are Kerbalism18 or Kerbalism16_17 depending on the KSP version, and there might be ohter + // assemblies like KerbalismContracts etc. + // So look at the assembly name object instead of the assembly name (which is the file name and could be renamed). + + AssemblyName nameObject = new AssemblyName(a.assembly.FullName); + string realName = nameObject.Name; // Will always return "Kerbalism" as defined in the AssemblyName property of the csproj + + if (realName.Equals("Kerbalism")) + { + kerbalismFound = true; + break; + } + } + if (!kerbalismFound) + { + PopupDialog dialog; + dialog = PopupDialog.SpawnPopupDialog(new Vector2(0.5f, 0.5f), + new Vector2(0.5f, 0.5f), + new MultiOptionDialog( + "KerbalismSystemWarning", + Localizer.Format("#LOC_KerbalismFFT_KerbalismNotFound_Msg"), + Localizer.Format("#LOC_KerbalismFFT_KerbalismNotFound_Title"), + HighLogic.UISkin, + new Rect(0.5f, 0.5f, 500f, 60f), + new DialogGUIFlexibleSpace(), + new DialogGUIHorizontalLayout( + new DialogGUIFlexibleSpace(), + new DialogGUIButton(Localizer.Format("#LOC_KerbalismFFT_Quit"), Application.Quit, 140.0f, 30.0f, true), + new DialogGUIFlexibleSpace() + ) + ), + true, + HighLogic.UISkin); + } + } + } +} diff --git a/src/Modules/AntimatterTankKerbalism.cs b/src/Modules/AntimatterTankKerbalism.cs new file mode 100644 index 0000000..382c4e4 --- /dev/null +++ b/src/Modules/AntimatterTankKerbalism.cs @@ -0,0 +1,131 @@ +using KSP.Localization; +using System.Collections.Generic; +using FarFutureTechnologies; +using KERBALISM; + +namespace KerbalismFFT +{ + public class FFTModuleAntimatterTankKerbalism: ModuleAntimatterTank + { + public static string brokerName = "FFTAntimatterTank"; + public static string brokerTitle = Localizer.Format("#LOC_KerbalismFFT_Brokers_AntimatterTank"); + + [KSPField(isPersistant = true)] + public float ThermalFluxToAddOnLoad = 0f; + + public override void OnAwake() + { + base.OnAwake(); + if (Lib.IsFlight()) + { + GameEvents.onPartUnpack.Add(new EventData.OnEvent(GoOffRails)); + } + } + + void OnDestroy() + { + // Clean up events when the item is destroyed + GameEvents.OnVesselRollout.Remove(OnVesselRollout); + GameEvents.onPartUnpack.Remove(GoOffRails); + } + + public virtual void GoOffRails(Part p) + { + if (ThermalFluxToAddOnLoad > 0) + { + KFFTUtils.Log("Antimatter containment for tank " + part.partInfo.title + " on vessel " + vessel.GetDisplayName() + " was turned off due to EC loss. " + ThermalFluxToAddOnLoad.ToString() + " KW of heat was added to part as a resut of antimatter detonation."); + part.AddThermalFlux(ThermalFluxToAddOnLoad); + ThermalFluxToAddOnLoad = 0f; + } + } + + // Estimate resources production/consumption for Kerbalism planner + // This will be called by Kerbalism in the editor (VAB/SPH), possibly several times after a change to the vessel + public string PlannerUpdate(List> resourceChangeRequest, CelestialBody body, Dictionary environment) + { + if (GetResourceAmount(FuelName) > 0.0 && ContainmentEnabled && ContainmentCost > 0f) + { + resourceChangeRequest.Add(new KeyValuePair("ElectricCharge", -ContainmentCost)); + } + return brokerTitle; + } + + // Simulate resources production/consumption for unloaded vessel + public static string BackgroundUpdate(Vessel v, ProtoPartSnapshot part_snapshot, ProtoPartModuleSnapshot module_snapshot, PartModule proto_part_module, Part proto_part, Dictionary availableResources, List> resourceChangeRequest, double elapsed_s) + { + // If containment enabled + if (Lib.Proto.GetBool(module_snapshot, "ContainmentEnabled")) + { + float ContainmentCost = (proto_part_module as FFTModuleAntimatterTankKerbalism).ContainmentCost; + if (ContainmentCost > 0) + { + double EC = KERBALISM.ResourceCache.Get(v).GetResource(v, "ElectricCharge").Amount; + resourceChangeRequest.Add(new KeyValuePair("ElectricCharge", -ContainmentCost)); + if (EC < ContainmentCost) + { + Lib.Proto.Set(module_snapshot, "ContainmentEnabled", false); + Message.Post( + Severity.danger, + Localizer.Format( + "#LOC_KerbalismFFT_AntimatterTank_Detonation_Msg", + v.GetDisplayName()) + ); + } + } + } + else + { + float DetonationKJPerUnit = (proto_part_module as FFTModuleAntimatterTankKerbalism).DetonationKJPerUnit; + float DetonationRate = (proto_part_module as FFTModuleAntimatterTankKerbalism).DetonationRate; + string FuelName = (proto_part_module as FFTModuleAntimatterTankKerbalism).FuelName; + + ResourceInfo antimatter = KERBALISM.ResourceCache.GetResource(v, FuelName); + double detonatedAmount = elapsed_s * DetonationRate; + if (antimatter.Amount < detonatedAmount) + { + detonatedAmount = antimatter.Amount; + } + antimatter.Consume(detonatedAmount, KERBALISM.ResourceBroker.GetOrCreate(brokerName, KERBALISM.ResourceBroker.BrokerCategory.VesselSystem, brokerTitle)); + float ThermalFluxToAddOnLoad = Lib.Proto.GetFloat(module_snapshot, "ThermalFluxToAddOnLoad"); + ThermalFluxToAddOnLoad += (float) detonatedAmount * DetonationKJPerUnit; + Lib.Proto.Set(module_snapshot, "ThermalFluxToAddOnLoad", ThermalFluxToAddOnLoad); + } + return brokerTitle; + } + + // Calculate resources production/consumption for active vessel + public string ResourceUpdate(Dictionary availableResources, List> resourceChangeRequest) + { + if (ContainmentEnabled && ContainmentCost > 0f) + { + ResourceInfo ec = KERBALISM.ResourceCache.GetResource(vessel, "ElectricCharge"); + double chargeRequest = ContainmentCost * TimeWarp.fixedDeltaTime; + ec.Consume(chargeRequest, KERBALISM.ResourceBroker.GetOrCreate(brokerName, KERBALISM.ResourceBroker.BrokerCategory.VesselSystem, brokerTitle)); + } + return brokerTitle; + } + + public new void DoCatchup() + { + // Do nothing + } + + protected new void ConsumeCharge() + { + if (ContainmentEnabled && ContainmentCost > 0f) + { + ResourceInfo ec = KERBALISM.ResourceCache.GetResource(vessel, "ElectricCharge"); + double chargeRequest = ContainmentCost * TimeWarp.fixedDeltaTime; +// ec.Consume(chargeRequest, KERBALISM.ResourceBroker.GetOrCreate(brokerName, KERBALISM.ResourceBroker.BrokerCategory.VesselSystem, brokerTitle)); + if (ec.Amount < chargeRequest) + { + SetPoweredState(false); + } + else + { + SetPoweredState(true); + } + } + } + } +} diff --git a/src/Modules/FusionEngineKerbalismUpdater.cs b/src/Modules/FusionEngineKerbalismUpdater.cs new file mode 100644 index 0000000..6e4962a --- /dev/null +++ b/src/Modules/FusionEngineKerbalismUpdater.cs @@ -0,0 +1,218 @@ +using KSP.Localization; +using System.Collections.Generic; +using System.Linq; +using FarFutureTechnologies; +using KERBALISM; +using SystemHeat; + +namespace KerbalismFFT +{ + class FFTFusionEngineKerbalismUpdater : PartModule + { + public static string brokerName = "FFTFusionEngine"; + public static string brokerTitle = Localizer.Format("#LOC_KerbalismFFT_Brokers_FusionEngine"); + + [KSPField(isPersistant = true)] + public bool FirstLoad = true; + + // This should correspond to the related ModuleFusionEngine + [KSPField(isPersistant = true)] + public string engineModuleID; + + [KSPField(isPersistant = true)] + public int lastReactorModeIndex = 0; + [KSPField(isPersistant = true)] + public float MaxECGeneration = 0f; + [KSPField(isPersistant = true)] + public float MinThrottle = 0.1f; + + protected static string engineModuleName = "ModuleFusionEngine"; + protected ModuleFusionEngine engineModule; + + protected bool modesListParsed = false; + protected List modes; + + public virtual void Start() + { + if (Lib.IsFlight() || Lib.IsEditor()) + { + if (engineModule == null) + { + engineModule = FindEngineModule(part, engineModuleID); + } + if (FirstLoad) + { + if (engineModule != null) + { + MinThrottle = engineModule.MinimumReactorPower; + ParseModesList(part); + MaxECGeneration = modes[lastReactorModeIndex].powerGeneration; + } + FirstLoad = false; + } + } + } + + public override void OnLoad(ConfigNode node) + { + base.OnLoad(node); + ParseModesList(part); + } + + // Fetch modes list from fusion reactor ConfigNode + protected void ParseModesList(Part part) + { + if (!modesListParsed) + { + ConfigNode node = ModuleUtils.GetModuleConfigNode(part, engineModuleName); + if (node != null) + { + ConfigNode[] varNodes = node.GetNodes("FUSIONMODE"); + modes = new List(); + for (int i = 0; i < varNodes.Length; i++) + { + modes.Add(new FusionReactorMode(varNodes[i])); + } + } + modesListParsed = true; + } + } + + public virtual void FixedUpdate() + { + if (engineModule != null) + { + if (lastReactorModeIndex != engineModule.currentModeIndex) + { + lastReactorModeIndex = engineModule.currentModeIndex; + if (Lib.IsEditor()) + { + KFFTUtils.UpdateKerbalismPlannerUINow(); + } + if (!modesListParsed) + { + ParseModesList(part); + } + MaxECGeneration = modes[lastReactorModeIndex].powerGeneration; + } + } + } + + // Estimate resources production/consumption for Kerbalism planner + // This will be called by Kerbalism in the editor (VAB/SPH), possibly several times after a change to the vessel + public string PlannerUpdate(List> resourceChangeRequest, CelestialBody body, Dictionary environment) + { + if (engineModule != null) + { + if (MaxECGeneration > 0) + { + resourceChangeRequest.Add(new KeyValuePair("ElectricCharge", MaxECGeneration)); + } + foreach (ResourceRatio ratio in modes[lastReactorModeIndex].inputs) + { + resourceChangeRequest.Add(new KeyValuePair(ratio.ResourceName, -ratio.Ratio)); + } + return brokerTitle; + } + return "ERR: no engine"; + } + + // Simulate resources production/consumption for unloaded vessel + public static string BackgroundUpdate(Vessel v, ProtoPartSnapshot part_snapshot, ProtoPartModuleSnapshot module_snapshot, PartModule proto_part_module, Part proto_part, Dictionary availableResources, List> resourceChangeRequest, double elapsed_s) + { + ProtoPartModuleSnapshot reactor = KFFTUtils.FindPartModuleSnapshot(part_snapshot, engineModuleName); + if (reactor != null) + { + if (Lib.Proto.GetBool(reactor, "Enabled")) + { + float maxECGeneration = Lib.Proto.GetFloat(module_snapshot, "MaxECGeneration"); + float minThrottle = Lib.Proto.GetFloat(module_snapshot, "MinThrottle"); + int modeIndex = Lib.Proto.GetInt(module_snapshot, "lastReactorModeIndex"); + bool needToStopReactor = false; + float curThrottle = 1.0f; + + if (maxECGeneration > 0) + { + VesselResources resources = KERBALISM.ResourceCache.Get(v); + if (!(proto_part_module as FFTFusionEngineKerbalismUpdater).modesListParsed) + { + (proto_part_module as FFTFusionEngineKerbalismUpdater).ParseModesList(proto_part); + } + + // Mininum reactor throttle + // Some input/output resources will always be consumed/produced as long as minThrottle > 0 + if (minThrottle > 0) + { + ResourceRecipe recipe = new ResourceRecipe(KERBALISM.ResourceBroker.GetOrCreate( + brokerName, + KERBALISM.ResourceBroker.BrokerCategory.Converter, + brokerTitle)); + foreach (ResourceRatio ir in (proto_part_module as FFTFusionEngineKerbalismUpdater).modes[modeIndex].inputs) + { + recipe.AddInput(ir.ResourceName, ir.Ratio * minThrottle * elapsed_s); + if (resources.GetResource(v, ir.ResourceName).Amount < double.Epsilon) + { + // Input resource amount is zero - stop reactor + needToStopReactor = true; + } + } + recipe.AddOutput("ElectricCharge", minThrottle * maxECGeneration * elapsed_s, dump: true); + resources.AddRecipe(recipe); + } + + if (!needToStopReactor) + { + curThrottle -= minThrottle; + if (curThrottle > 0) + { + ResourceRecipe recipe = new ResourceRecipe(KERBALISM.ResourceBroker.GetOrCreate( + brokerName, + KERBALISM.ResourceBroker.BrokerCategory.Converter, + brokerTitle)); + foreach (ResourceRatio ir in (proto_part_module as FFTFusionEngineKerbalismUpdater).modes[modeIndex].inputs) + { + recipe.AddInput(ir.ResourceName, ir.Ratio * curThrottle * elapsed_s); + if (resources.GetResource(v, ir.ResourceName).Amount < double.Epsilon) + { + // Input resource amount is zero - stop reactor + needToStopReactor = true; + } + } + recipe.AddOutput("ElectricCharge", curThrottle * maxECGeneration * elapsed_s, dump: false); + resources.AddRecipe(recipe); + } + } + } + + // Disable reactor + if (needToStopReactor) + { + Lib.Proto.Set(reactor, "Enabled", false); + Lib.Proto.Set(reactor, "CurrentCharge", 0f); + Lib.Proto.Set(reactor, "Charged", false); + } + } + return brokerTitle; + } + return "ERR: no engine"; + } + + // Find associated Engine module + public ModuleFusionEngine FindEngineModule(Part part, string moduleName) + { + ModuleFusionEngine engine = part.GetComponents().ToList().Find(x => x.ModuleID == moduleName); + + if (engine == null) + { + KFFTUtils.LogError($"[{part}] No ModuleFusionEngine named {moduleName} was found, using first instance."); + engineModule = part.GetComponents().ToList().First(); + } + if (engine == null) + { + KFFTUtils.LogError($"[{part}] No ModuleFusionEngine was found."); + } + return engine; + } + } +} + diff --git a/src/Modules/FusionReactorKerbalismUpdater.cs b/src/Modules/FusionReactorKerbalismUpdater.cs new file mode 100644 index 0000000..e704ff8 --- /dev/null +++ b/src/Modules/FusionReactorKerbalismUpdater.cs @@ -0,0 +1,357 @@ +using KSP.Localization; +using System.Collections.Generic; +using System.Linq; +using FarFutureTechnologies; +using KERBALISM; +using SystemHeat; + +namespace KerbalismFFT +{ + class FFTFusionReactorKerbalismUpdater : PartModule + { + public static string brokerName = "FFTFusionReactor"; + public static string brokerTitle = Localizer.Format("#LOC_KerbalismFFT_Brokers_FusionReactor"); + + [KSPField(isPersistant = true)] + public bool FirstLoad = true; + + // This should correspond to the related FusionReactor module + [KSPField(isPersistant = true)] + public string reactorModuleID; + + [KSPField(isPersistant = true)] + public int lastReactorModeIndex = 0; + [KSPField(isPersistant = true)] + public float MaxECGeneration = 0f; + [KSPField(isPersistant = true)] + public float MinThrottle = 0.1f; + + [KSPField(isPersistant = true)] + public bool ReactorHasStarted = false; + [KSPField(isPersistant = true)] + public bool EmitterRunning = true; + [KSPField(isPersistant = true)] + public double EmitterMaxRadiation = 0d; + [KSPField(isPersistant = true)] + public bool LastReactorState = false; + [KSPField(isPersistant = true)] + public double ReactorStoppedTimestamp = 0d; + [KSPField(isPersistant = true)] + public double MinEmissionPercent = 0d; + [KSPField(isPersistant = true)] + public double EmissionDecayRate = 1d; + + protected static string reactorModuleName = "FusionReactor"; + protected FusionReactor reactorModule; + + protected bool modesListParsed = false; + protected List modes; + + // Radiation source on part + protected Emitter emitter; + + public virtual void Start() + { + if (Lib.IsFlight() || Lib.IsEditor()) + { + if (reactorModule == null) + { + reactorModule = FindReactorModule(part, reactorModuleID); + } + if (Features.Radiation && emitter == null) + { + emitter = FindEmitterModule(part); + } + if (FirstLoad) + { + if (emitter != null) + { + EmitterMaxRadiation = emitter.radiation; + if (EmitterMaxRadiation < 0) + { + EmitterMaxRadiation = 0d; + } + } + if (reactorModule != null) + { + MinThrottle = reactorModule.MinimumReactorPower; + ParseModesList(part); + MaxECGeneration = modes[lastReactorModeIndex].powerGeneration; + } + FirstLoad = false; + } + else + { + EmitterRunning = true; + } + } + } + + public override void OnLoad(ConfigNode node) + { + base.OnLoad(node); + ParseModesList(part); + } + + // Fetch modes list from fusion reactor ConfigNode + protected void ParseModesList(Part part) + { + if (!modesListParsed) + { + ConfigNode node = ModuleUtils.GetModuleConfigNode(part, reactorModuleName); + if (node != null) + { + ConfigNode[] varNodes = node.GetNodes("FUSIONMODE"); + modes = new List(); + for (int i = 0; i < varNodes.Length; i++) + { + modes.Add(new FusionReactorMode(varNodes[i])); + } + } + modesListParsed = true; + } + } + + public virtual void FixedUpdate() + { + if (reactorModule != null) + { + if (lastReactorModeIndex != reactorModule.currentModeIndex) + { + lastReactorModeIndex = reactorModule.currentModeIndex; + if (Lib.IsEditor()) + { + KFFTUtils.UpdateKerbalismPlannerUINow(); + } + if (!modesListParsed) + { + ParseModesList(part); + } + MaxECGeneration = modes[lastReactorModeIndex].powerGeneration; + } + if (Lib.IsFlight()) + { + if (Features.Radiation && emitter != null) + { + if (!ReactorHasStarted && !reactorModule.Enabled && EmitterRunning) + { + // Disable radiation source, because reactor has not started yet + emitter.running = false; + EmitterRunning = false; + } + if (!ReactorHasStarted && reactorModule.Enabled) + { + // Reactor has started - enable radiation source + ReactorHasStarted = true; + emitter.running = true; + emitter.radiation = EmitterMaxRadiation; + } + if (LastReactorState != reactorModule.Enabled) + { + LastReactorState = reactorModule.Enabled; + if (reactorModule.Enabled) + { + // Reactor has started again - set radiation source emission to maximum + emitter.radiation = EmitterMaxRadiation; + ReactorStoppedTimestamp = 0d; + } + else + { + // Reactor has stopped - save timestamp, when it happened + ReactorStoppedTimestamp = Planetarium.GetUniversalTime(); + } + } + if (!reactorModule.Enabled && ReactorHasStarted && ReactorStoppedTimestamp > 0 && MinEmissionPercent < 100) + { + // Radiation decay + double MinRadiation = EmitterMaxRadiation * MinEmissionPercent / 100; + if (EmissionDecayRate <= 0) + { + emitter.radiation = MinRadiation; + ReactorStoppedTimestamp = 0d; + } + else + { + double secondsPassed = Planetarium.GetUniversalTime() - ReactorStoppedTimestamp; + if (secondsPassed > 0) + { + double NewRadiation = EmitterMaxRadiation * (100 - secondsPassed / EmissionDecayRate) / 100; + if (NewRadiation <= MinRadiation) + { + NewRadiation = MinRadiation; + ReactorStoppedTimestamp = 0d; + } + emitter.radiation = NewRadiation; + } + } + } + } + } + } + } + + // Estimate resources production/consumption for Kerbalism planner + // This will be called by Kerbalism in the editor (VAB/SPH), possibly several times after a change to the vessel + public string PlannerUpdate(List> resourceChangeRequest, CelestialBody body, Dictionary environment) + { + if (reactorModule != null) + { + if (MaxECGeneration > 0) + { + resourceChangeRequest.Add(new KeyValuePair("ElectricCharge", MaxECGeneration)); + } + foreach (ResourceRatio ratio in modes[lastReactorModeIndex].inputs) + { + resourceChangeRequest.Add(new KeyValuePair(ratio.ResourceName, -ratio.Ratio)); + } + return brokerTitle; + } + return "ERR: no reactor"; + } + + // Simulate resources production/consumption for unloaded vessel + public static string BackgroundUpdate(Vessel v, ProtoPartSnapshot part_snapshot, ProtoPartModuleSnapshot module_snapshot, PartModule proto_part_module, Part proto_part, Dictionary availableResources, List> resourceChangeRequest, double elapsed_s) + { + ProtoPartModuleSnapshot reactor = KFFTUtils.FindPartModuleSnapshot(part_snapshot, reactorModuleName); + if (reactor != null) + { + if (Lib.Proto.GetBool(reactor, "Enabled")) + { + float maxECGeneration = Lib.Proto.GetFloat(module_snapshot, "MaxECGeneration"); + float minThrottle = Lib.Proto.GetFloat(module_snapshot, "MinThrottle"); + int modeIndex = Lib.Proto.GetInt(module_snapshot, "lastReactorModeIndex"); + bool needToStopReactor = false; + float curThrottle = 1.0f; + + if (maxECGeneration > 0) + { + VesselResources resources = KERBALISM.ResourceCache.Get(v); + if (!(proto_part_module as FFTFusionReactorKerbalismUpdater).modesListParsed) + { + (proto_part_module as FFTFusionReactorKerbalismUpdater).ParseModesList(proto_part); + } + + // Mininum reactor throttle + // Some input/output resources will always be consumed/produced as long as minThrottle > 0 + if (minThrottle > 0) + { + ResourceRecipe recipe = new ResourceRecipe(KERBALISM.ResourceBroker.GetOrCreate( + brokerName, + KERBALISM.ResourceBroker.BrokerCategory.Converter, + brokerTitle)); + foreach (ResourceRatio ir in (proto_part_module as FFTFusionReactorKerbalismUpdater).modes[modeIndex].inputs) + { + recipe.AddInput(ir.ResourceName, ir.Ratio * minThrottle * elapsed_s); + if (resources.GetResource(v, ir.ResourceName).Amount < double.Epsilon) + { + // Input resource amount is zero - stop reactor + needToStopReactor = true; + } + } + recipe.AddOutput("ElectricCharge", minThrottle * maxECGeneration * elapsed_s, dump: true); + resources.AddRecipe(recipe); + } + + if (!needToStopReactor) + { + curThrottle -= minThrottle; + if (curThrottle > 0) + { + ResourceRecipe recipe = new ResourceRecipe(KERBALISM.ResourceBroker.GetOrCreate( + brokerName, + KERBALISM.ResourceBroker.BrokerCategory.Converter, + brokerTitle)); + foreach (ResourceRatio ir in (proto_part_module as FFTFusionReactorKerbalismUpdater).modes[modeIndex].inputs) + { + recipe.AddInput(ir.ResourceName, ir.Ratio * curThrottle * elapsed_s); + if (resources.GetResource(v, ir.ResourceName).Amount < double.Epsilon) + { + // Input resource amount is zero - stop reactor + needToStopReactor = true; + } + } + recipe.AddOutput("ElectricCharge", curThrottle * maxECGeneration * elapsed_s, dump: false); + resources.AddRecipe(recipe); + } + } + // Disable reactor + if (needToStopReactor) + { + Lib.Proto.Set(reactor, "Enabled", false); + Lib.Proto.Set(reactor, "CurrentCharge", 0f); + Lib.Proto.Set(reactor, "Charged", false); + } + } + else + { + // Reactor disabled - radiation decay mechanics + if (Features.Radiation && + Lib.Proto.GetBool(module_snapshot, "ReactorHasStarted") && + Lib.Proto.GetDouble(module_snapshot, "ReactorStoppedTimestamp") > 0 && + Lib.Proto.GetDouble(module_snapshot, "MinEmissionPercent") < 100) + { + ProtoPartModuleSnapshot emitter = KFFTUtils.FindPartModuleSnapshot(part_snapshot, "Emitter"); + if (emitter != null) + { + double EmitterMaxRadiation = Lib.Proto.GetDouble(module_snapshot, "EmitterMaxRadiation"); + double MinEmissionPercent = Lib.Proto.GetDouble(module_snapshot, "MinEmissionPercent"); + double EmissionDecayRate = Lib.Proto.GetDouble(module_snapshot, "EmissionDecayRate"); + double MinRadiation = EmitterMaxRadiation * MinEmissionPercent / 100; + if (EmissionDecayRate <= 0) + { + Lib.Proto.Set(emitter, "radiation", MinRadiation); + Lib.Proto.Set(module_snapshot, "ReactorStoppedTimestamp", 0d); + } + else + { + double secondsPassed = Planetarium.GetUniversalTime() - Lib.Proto.GetDouble(module_snapshot, "ReactorStoppedTimestamp"); + if (secondsPassed > 0) + { + double NewRadiation = EmitterMaxRadiation * (100 - secondsPassed / EmissionDecayRate) / 100; + if (NewRadiation <= MinRadiation) + { + NewRadiation = MinRadiation; + Lib.Proto.Set(module_snapshot, "ReactorStoppedTimestamp", 0d); + } + Lib.Proto.Set(emitter, "radiation", NewRadiation); + } + } + } + } + } + } + return brokerTitle; + } + return "ERR: no reactor"; + } + + // Find associated Reactor module + public FusionReactor FindReactorModule(Part part, string moduleName) + { + FusionReactor reactor = part.GetComponents().ToList().Find(x => x.ModuleID == moduleName); + + if (reactor == null) + { + KFFTUtils.LogError($"[{part}] No FusionReactor named {moduleName} was found, using first instance."); + reactorModule = part.GetComponents().ToList().First(); + } + if (reactor == null) + { + KFFTUtils.LogError($"[{part}] No FusionReactor was found."); + } + return reactor; + } + + // Find Emitter module on part (Kerbalism radiation source) + public Emitter FindEmitterModule(Part part) + { + Emitter emitter = part.GetComponents().ToList().First(); + if (emitter == null) + { + KFFTUtils.LogWarning($"[{part}] No radiation Emitter was found."); + } + return emitter; + } + } +} + diff --git a/src/Modules/RadioactiveEngine.cs b/src/Modules/RadioactiveEngine.cs new file mode 100644 index 0000000..49e492c --- /dev/null +++ b/src/Modules/RadioactiveEngine.cs @@ -0,0 +1,220 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using KERBALISM; + +namespace KerbalismFFT +{ + class FFTRadioactiveEngine : PartModule + { + [KSPField(isPersistant = true)] + public bool FirstLoad = true; + + [KSPField(isPersistant = true)] + public string engineID1; + [KSPField(isPersistant = true)] + public string engineID2; + [KSPField(isPersistant = false)] + public FloatCurve EmissionPercentEngine1 = new FloatCurve(); + [KSPField(isPersistant = false)] + public FloatCurve EmissionPercentEngine2 = new FloatCurve(); + + [KSPField(isPersistant = true)] + public bool EngineHasStarted = false; + [KSPField(isPersistant = true)] + public bool EmitterRunning = true; + [KSPField(isPersistant = true)] + public double GoalEmission = 0d; + [KSPField(isPersistant = true)] + public double EmitterMaxRadiation = 0d; + [KSPField(isPersistant = true)] + public double EmitterRadiationBeforeEngineShutdown = 0d; + [KSPField(isPersistant = true)] + public bool LastEngineState = false; + [KSPField(isPersistant = true)] + public double LastUpdateTime = 0d; + [KSPField(isPersistant = true)] + public double MinEmissionPercent = 0d; + [KSPField(isPersistant = true)] + public double EmissionDecayRate = 1d; + + protected ModuleEnginesFX engineModule1; + protected ModuleEnginesFX engineModule2; + + protected Emitter emitter; + + public virtual void Start() + { + if (Features.Radiation && (Lib.IsFlight() || Lib.IsEditor())) + { + if (engineID1 != null && engineModule1 == null) + { + engineModule1 = FindEngineModule(part, engineID1); + } + if (engineID2 != null && engineModule2 == null) + { + engineModule2 = FindEngineModule(part, engineID2); + } + if (emitter == null) + { + emitter = FindEmitterModule(part); + } + if (FirstLoad) + { + if (emitter != null) + { + EmitterMaxRadiation = emitter.radiation; + if (EmitterMaxRadiation < 0) + { + EmitterMaxRadiation = 0d; + } + } + FirstLoad = false; + } + else + { + EmitterRunning = true; + } + } + } + + public virtual void FixedUpdate() + { + if (Lib.IsFlight() && Features.Radiation && emitter != null) + { + bool EngineIgnited = false; + double MinEmission = MinEmissionPercent * EmitterMaxRadiation / 100; + if (engineModule1 != null && engineModule1.EngineIgnited) + { + EngineIgnited = true; + } + if (engineModule2 != null && engineModule2.EngineIgnited) + { + EngineIgnited = true; + } + if (!EngineHasStarted && !EngineIgnited && EmitterRunning) + { + // Disable radiation source, because engine has not started yet + emitter.running = false; + EmitterRunning = false; + } + if (!EngineHasStarted && EngineIgnited) + { + // Engine has started - enable radiation source + EngineHasStarted = true; + emitter.radiation = 0d; + emitter.running = true; + } + if (EngineHasStarted && EngineIgnited) + { + // Update radiation emission value according to engine throttle + double emission1 = 0d; + double emission2 = 0d; + if (engineModule1 != null && engineModule1.EngineIgnited) + { + emission1 = EmitterMaxRadiation * EmissionPercentEngine1.Evaluate(engineModule1.currentThrottle * 100f) / 100d; + } + if (engineModule2 != null && engineModule2.EngineIgnited) + { + emission2 = EmitterMaxRadiation * EmissionPercentEngine2.Evaluate(engineModule2.currentThrottle * 100f) / 100d; + } + GoalEmission = Math.Max(emission1, emission2); + if (GoalEmission < MinEmission) + { + GoalEmission = MinEmission; + } + if (GoalEmission > emitter.radiation) + { + emitter.radiation = GoalEmission; + } + } + if (EngineHasStarted && !EngineIgnited) + { + // Engine was shut down + GoalEmission = MinEmission; + } + if (EngineHasStarted && emitter.radiation > GoalEmission) + { + // Radiation decay + if (EmissionDecayRate <= 0) + { + emitter.radiation = GoalEmission; + } + else + { + double secondsPassed = Planetarium.GetUniversalTime() - LastUpdateTime; + if (secondsPassed > 0) + { + double newEmission = emitter.radiation; + newEmission -= EmitterMaxRadiation * (secondsPassed / EmissionDecayRate) / 100; + if (newEmission < GoalEmission) + { + newEmission = GoalEmission; + } + emitter.radiation = newEmission; + } + } + } + LastUpdateTime = Planetarium.GetUniversalTime(); + } + } + + // Simulate resources production/consumption for unloaded vessel + public static string BackgroundUpdate(Vessel v, ProtoPartSnapshot part_snapshot, ProtoPartModuleSnapshot module_snapshot, PartModule proto_part_module, Part proto_part, Dictionary availableResources, List> resourceChangeRequest, double elapsed_s) + { + if (Features.Radiation && Lib.Proto.GetBool(module_snapshot, "EngineHasStarted")) + { + ProtoPartModuleSnapshot emitter = KFFTUtils.FindPartModuleSnapshot(part_snapshot, "Emitter"); + if (emitter != null) + { + double MinEmissionPercent = Lib.Proto.GetDouble(module_snapshot, "EmitterMaxRadiation"); + double EmitterMaxRadiation = Lib.Proto.GetDouble(module_snapshot, "EmitterMaxRadiation"); + double EmissionDecayRate = Lib.Proto.GetDouble(module_snapshot, "EmissionDecayRate"); + double GoalEmission = MinEmissionPercent * EmitterMaxRadiation / 100; + if (EmissionDecayRate <= 0) + { + Lib.Proto.Set(emitter, "radiation", GoalEmission); + } + else + { + double secondsPassed = Planetarium.GetUniversalTime() - Lib.Proto.GetDouble(module_snapshot, "LastUpdateTime"); + if (secondsPassed > 0) + { + double newEmission = Lib.Proto.GetDouble(emitter, "radiation"); + newEmission -= EmitterMaxRadiation * (secondsPassed / EmissionDecayRate) / 100; + if (newEmission < GoalEmission) + { + newEmission = GoalEmission; + } + Lib.Proto.Set(emitter, "radiation", newEmission); + } + } + } + } + Lib.Proto.Set(module_snapshot, "LastUpdateTime", Planetarium.GetUniversalTime()); + return "radioactive engine"; + } + + // Find ModuleEnginesFX module + public ModuleEnginesFX FindEngineModule(Part part, string moduleName) + { + ModuleEnginesFX engine = part.GetComponents().ToList().Find(x => x.engineID == moduleName); + if (engine == null) + { + KFFTUtils.LogError($"[FFTRadioactiveEngine][{part}] No ModuleEnginesFX named {moduleName} was found."); + } + return engine; + } + + // Find Emitter module on part (Kerbalism radiation source) + public Emitter FindEmitterModule(Part part) + { + Emitter emitter = part.GetComponents().ToList().First(); + if (emitter == null) + { + KFFTUtils.LogError($"[FFTRadioactiveEngine][{part}] No radiation Emitter was found."); + } + return emitter; + } + } +} diff --git a/src/Properties/AssemblyInfo.cs b/src/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..4baf214 --- /dev/null +++ b/src/Properties/AssemblyInfo.cs @@ -0,0 +1,34 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("KerbalismFFT.Properties")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("KerbalismFFT.Properties")] +[assembly: AssemblyCopyright("Copyright © Alexander Rogov")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("38a9fad4-9801-4a53-861c-dca6c2452096")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("0.1.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: KSPAssemblyDependency ("FarFutureTechnologies", 0, 0)] diff --git a/src/Utils.cs b/src/Utils.cs new file mode 100644 index 0000000..e2a9e9e --- /dev/null +++ b/src/Utils.cs @@ -0,0 +1,69 @@ +using System; +using System.Reflection; +using UnityEngine; +using KERBALISM.Planner; + +namespace KerbalismFFT +{ + public static class KFFTUtils + { + public static void Log(string msg) + { + Debug.Log("[KerbalismFFT] " + msg); + } + + public static void LogWarning(string msg) + { + Debug.LogWarning("[KerbalismFFT] " + msg); + } + + public static void LogError(string msg) + { + Debug.LogError("[KerbalismFFT] " + msg); + } + + public static void UpdateKerbalismPlannerUINow() + { + // Dirty hack - calling internal method from another assembly via reflection + // Wish I could find another way to update Planner UI... + // And no, onEditorShipModified event will not do the job, as it will also restart heat simulation process + string className = typeof(Planner).AssemblyQualifiedName; + ReflectionStaticCall(className, "RefreshPlanner"); + } + + public static void ReflectionStaticCall(string ClassName, string MethodName) + { + var staticClass = Type.GetType(ClassName); + if (staticClass != null) + { + try + { + staticClass.GetMethod(MethodName, BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, null); + } + catch (Exception ex) + { + LogError("Static class method " + ClassName + "." + MethodName + " reflection call failed. Exception: " + ex.Message + "\n" + ex.ToString()); + } + } + } + + // Find PartModule snapshot (used for unloaded vessels as they only have Modules snapshots) + public static ProtoPartModuleSnapshot FindPartModuleSnapshot(ProtoPartSnapshot p, string PartModuleName) + { + ProtoPartModuleSnapshot m = null; + for (int i = 0; i < p.modules.Count; i++) + { + if (p.modules[i].moduleName == PartModuleName) + { + m = p.modules[i]; + break; + } + } + if (m == null) + { + LogError($" Part [{p.partInfo.title}] No {PartModuleName} was found in part snapshot."); + } + return m; + } + } +} diff --git a/src/zKerbalismFFT.csproj b/src/zKerbalismFFT.csproj new file mode 100644 index 0000000..468ac66 --- /dev/null +++ b/src/zKerbalismFFT.csproj @@ -0,0 +1,116 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {1C84F8C1-7C2D-4220-98B1-7B3E0EE8416C} + Library + Properties + KerbalismFFT + zKerbalismFFT + v4.5.2 + 512 + + + + true + full + false + bin\Release\ + DEBUG;TRACE + prompt + 4 + Off + false + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + false + + + + ..\..\..\..\..\..\KSP 1.8.1 - test\KSP_x64_Data\Managed\Assembly-CSharp.dll + + + ..\..\..\..\..\..\KSP 1.8.1 - test\KSP_x64_Data\Managed\Assembly-CSharp-firstpass.dll + + + F:\KSP tests\1.11.2\GameData\FarFutureTechnologies\Plugins\FarFutureTechnologies.dll + + + ..\..\..\..\..\..\ksp_mods\Kerbalism-master\BuildSystem\BinariesDebug\Kerbalism.dll + + + ..\..\..\..\..\..\ksp_mods\Kerbalism-master\BuildSystem\BinariesDebug\KerbalismBootstrap.dll + + + ..\..\..\..\..\..\KSP 1.8.1 - test\KSP_x64_Data\Managed\KSPAssets.dll + + + + + + + + + F:\KSP tests\1.11.2\GameData\SystemHeat\Plugin\SystemHeat.dll + + + ..\..\..\..\..\..\KSP 1.8.1 - test\KSP_x64_Data\Managed\UnityEngine.dll + + + ..\..\..\..\..\..\KSP 1.8.1 - test\KSP_x64_Data\Managed\UnityEngine.AnimationModule.dll + + + False + ..\..\..\..\..\..\KSP 1.8.1 - test\KSP_x64_Data\Managed\UnityEngine.AssetBundleModule.dll + + + ..\..\..\..\..\..\KSP 1.8.1 - test\KSP_x64_Data\Managed\UnityEngine.CoreModule.dll + + + ..\..\..\..\..\..\KSP 1.8.1 - test\KSP_x64_Data\Managed\UnityEngine.IMGUIModule.dll + + + ..\..\..\..\..\..\KSP 1.8.1 - test\KSP_x64_Data\Managed\UnityEngine.TextRenderingModule.dll + + + ..\..\..\..\..\..\KSP 1.8.1 - test\KSP_x64_Data\Managed\UnityEngine.UI.dll + + + False + ..\..\..\..\..\..\KSP 1.8.1 - test\KSP_x64_Data\Managed\UnityEngine.UIModule.dll + + + + + + + + + + + + + + + + + + copy /Y "$(TargetDir)$(ProjectName).dll" "F:\KSP tests\1.11.2\GameData\zKerbalismFFT\Plugins\zKerbalismFFT.dll" + + + \ No newline at end of file diff --git a/src/zKerbalismFFT.sln b/src/zKerbalismFFT.sln new file mode 100644 index 0000000..916b935 --- /dev/null +++ b/src/zKerbalismFFT.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30320.27 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "zKerbalismFFT", "zKerbalismFFT.csproj", "{1C84F8C1-7C2D-4220-98B1-7B3E0EE8416C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1C84F8C1-7C2D-4220-98B1-7B3E0EE8416C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1C84F8C1-7C2D-4220-98B1-7B3E0EE8416C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1C84F8C1-7C2D-4220-98B1-7B3E0EE8416C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1C84F8C1-7C2D-4220-98B1-7B3E0EE8416C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B7CDE3D8-1B00-4805-BB1E-180B37A85DFB} + EndGlobalSection +EndGlobal