// --- constructors --- /// <summary>Creates a new instance of this system.</summary> /// <param name="train">The train.</param> internal steam(Train train) { this.Train = train; this.cutofftimer = 0.0; this.maintimer = 0.0; //Init component classes this.LiveSteamInjector = new Injector(train, InjectorType.LiveSteam); this.ExhaustSteamInjector = new Injector(train, InjectorType.ExhaustSteam); this.CylinderCocks = new CylinderCocks(train); this.Blowers = new Blowers(train); this.OverheatAlarm = new OverheatAlarm(train); }
/// <summary>Is called every frame.</summary> /// <param name="data">The data.</param> /// <param name="blocking">Whether the device is blocked or will block subsequent devices.</param> internal override void Elapse(ElapseData data, ref bool blocking) { //Check we've got a maximum temperature and a heating part if (overheat != 0 && heatingpart != 0) { this.heatingtimer += data.ElapsedTime.Milliseconds; if (heatingpart == 0 || overheat == 0) { //No heating part or overheat temperature not set this.temperature = 0.0; this.heatingtimer = 0.0; } else if (heatingpart == 1) { //Heats based upon power notch if (this.heatingtimer > 1000) { this.heatingtimer = 0.0; if (Train.Handles.PowerNotch == 0) { currentheat = heatingarray[0]; } else if (Train.Handles.PowerNotch < heatingarray.Length) { currentheat = heatingarray[Train.Handles.PowerNotch]; } else { currentheat = heatingarray[heatingarray.Length - 1]; } temperature += currentheat; } } else { //Heats based upon RPM- Not on a steam loco! this.temperature = 0.0; this.heatingtimer = 0.0; } //Keep temperature below max & above zero if (temperature > overheat) { temperature = overheat; if (overheatresult == 1) { Train.TractionManager.DemandPowerCutoff("Traction power cutoff was demanded due to the steam engine overheating"); Train.TractionManager.EngineOverheated = true; } } else if (temperature < overheat && temperature > 0) { Train.TractionManager.ResetPowerCutoff(); Train.TractionManager.EngineOverheated = false; } else if (temperature < 0) { temperature = 0; } } //First try to set automatic cutoff without calculating if (this.Train.TractionManager.AutomaticAdvancedFunctions == true && Train.CurrentSpeed == 0) { if (Train.Handles.Reverser == 0) { //If reverser is in neutral, reset cutoff to 30 cutoff = 30; } else if (Train.Handles.Reverser == 1 && cutoff >= cutoffineffective) { cutoff = cutoffmax; } else if (Train.Handles.Reverser == -1 && cutoff >= cutoffineffective) { cutoff = cutoffmin; } } else { double setcutoff = cutoff; if (cutoffstate == 1) { //Set Cutoff Up if (cutoff < cutoffmax) { this.cutofftimer += data.ElapsedTime.Milliseconds; if (this.cutofftimer > cutoffchangespeed) { setcutoff = setcutoff + 1; cutofftimer = 0.0; } } } else if (cutoffstate == -1) { //Set Cutoff Down if (cutoff > cutoffmin) { this.cutofftimer += data.ElapsedTime.Milliseconds; if (this.cutofftimer > cutoffchangespeed) { setcutoff = setcutoff - 1; cutofftimer = 0.0; } } } else { //twiddle our thumbs } cutoff = setcutoff; } //Manual { //This section of code operates the reverser & cutoff if (cutoff > cutoffineffective && (cutoffdeviation != 0)) { new_reverser = 1; //Called to workout the optimum cutoff if we're over the optimum max cutoff speed FW if (data.Vehicle.Speed.KilometersPerHour > cutoffratiobase) { speed = data.Vehicle.Speed.KilometersPerHour - cutoffratiobase; optimalcutoff = cutoffmax - speed * cutoffratio / 10; new_power = Math.Max((int)(this.Train.Specs.PowerNotches - ((optimalcutoff - cutoff) < 0 ? -(optimalcutoff - cutoff) : (optimalcutoff - cutoff)) / (int)cutoffdeviation), 0); //Automagically set cutofff if (this.Train.TractionManager.AutomaticAdvancedFunctions == true) { cutoff = (int)Math.Max(optimalcutoff, cutoffineffective + 1); } } else { new_power = Math.Max((int)(this.Train.Specs.PowerNotches - (((int)cutoffmax - cutoff) < 0 ? -((int)cutoffmax - cutoff) : ((int)cutoffmax - cutoff)) / cutoffdeviation), 0); } } else if (cutoff < cutoffineffective && cutoffdeviation != 0) { new_reverser = -1; //Called to workout the optimum cutoff if we're over the optimum max cutoff speed RV if (Math.Abs(data.Vehicle.Speed.KilometersPerHour) > cutoffratiobase) { speed = Math.Abs(data.Vehicle.Speed.KilometersPerHour) - cutoffratiobase; optimalcutoff = cutoffmin + speed * cutoffratio / 10; new_power = Math.Max((int)(this.Train.Specs.PowerNotches - ((optimalcutoff - cutoff) < 0 ? -(optimalcutoff - cutoff) : (optimalcutoff - cutoff)) / (int)cutoffdeviation), 0); //Automagically set cutoff if (this.Train.TractionManager.AutomaticAdvancedFunctions == true) { cutoff = (int)Math.Min(optimalcutoff, -cutoffineffective - 1); } } else { new_power = Math.Max((int)(this.Train.Specs.PowerNotches - ((Math.Abs(cutoffmin) - Math.Abs(cutoff)) < 0 ? -((int)Math.Abs(cutoffmin) - Math.Abs(cutoff)) : ((int)Math.Abs(cutoffmin) - Math.Abs(cutoff))) / cutoffdeviation), 0); } } else { new_reverser = 0; new_power = 0; } } //CALL NEW FUNCTION TO SET THE REVERSER STATE if (new_reverser != stm_reverser) { stm_reverser = new_reverser; data.Handles.Reverser = stm_reverser; } else { data.Handles.Reverser = stm_reverser; } { //This section of code operates the pressure power drop double bp = Math.Max(stm_boilerpressure - boilerminpressure, 0); int bp_range = (int)boilermaxpressure - (int)boilerminpressure; int pwr_limit = Math.Min(new_power, (int)((bp / (float)(bp_range) + (1.0 / this.Train.Specs.PowerNotches - 0.01)) * this.Train.Specs.PowerNotches)); new_power = Math.Max(Train.Handles.PowerNotch - this.Train.Specs.PowerNotches + pwr_limit, 0); //CALL NEW FUNCTION TO CHANGE POWER } if (Train.drastate != true) { stm_power = new_power; LastPower = new_power; Train.TractionManager.SetMaxPowerNotch(stm_power, false); } { //This section of code generates pressure and operates the blowoff //First elapse the main timer function this.maintimer += data.ElapsedTime.Seconds; //This section of code handles the fire simulator if (advancedfiring == false) { //Advanced firing is not enabled, use the standard boiler water to steam rate finalsteamrate = calculatedsteamrate; } else { //Advanced firing if (this.maintimer > 1) { //Check whether we can shovel coal //Firemass must be below maximum and shovelling true [Non automatic] //If automatic firing is on, only shovel coal if we are below 50% of max fire mass- Change??? //Use automatic behaviour if no shovelling key is set as obviously we can't shovel coal manually with no key if (shovelling == true && firemass < maximumfiremass || this.Train.TractionManager.AutomaticAdvancedFunctions == true && firemass < (firemass / 2) && firemass < maximumfiremass || Train.CurrentKeyConfiguration.ShovelFuel == null && firemass < (firemass / 2) && firemass < maximumfiremass) { //Add the amount of coal shovelled per second to the fire mass & decrease it from the fire temperature firemass += (int)shovellingrate; firetemp -= (int)shovellingrate; } int fire_tempchange; if (firemass != 0) { if (Blowers.Active) { fire_tempchange = (int)Math.Ceiling((double)(((firemass * 0.5) - 10) / (firemass * 0.05)) * Blowers.FireTempIncreaseFactor); } else { fire_tempchange = (int)Math.Ceiling((double)(((firemass * 0.5) - 10) / (firemass * 0.05))); } } else { //Temperature change must be zero if our fire mass is zero //Otherwise causes a division by zero error.... fire_tempchange = 0; } firemass = (int)((double)firemass * 0.9875); if (firetemp < 1000) { //Add calculated temperature increase to the fire temperature firetemp += fire_tempchange; } else { //Otherwise set to max firetemp = 1000; } if (Blowers.Active == true) { finalsteamrate = (int)((((double)calculatedsteamrate / 1000) * firetemp) * Blowers.PressureIncreaseFactor); } else { finalsteamrate = (int)(((double)calculatedsteamrate / 1000) * firetemp); } } } if (this.maintimer > 1) { if (Blowers.Active == true) { pressureup = (int)(((boilerwatertosteamrate / 60) * maintimer) * Blowers.PressureIncreaseFactor); } else { pressureup = (int)((boilerwatertosteamrate / 60) * maintimer); } stm_boilerpressure = stm_boilerpressure + pressureup; stm_boilerwater = stm_boilerwater - pressureup; //Newer standard blowoff handling if (stm_boilerpressure > boilermaxpressure) { switch (Blowoff.BlowoffState) { case Blowoff.BlowoffStates.None: //Switch to the over maximum pressure state, as we are over the max pressure Blowoff.BlowoffState = Blowoff.BlowoffStates.OverMaxPressure; Blowoff.Played = false; break; case Blowoff.BlowoffStates.OverMaxPressure: if (stm_boilerpressure > Blowoff.TriggerPressure) { //If our boiler pressure is over the blowoff pressure, then switch to blowoff Blowoff.BlowoffState = Blowoff.BlowoffStates.Blowoff; break; } //Otherwise, reduce the pressure by 4 //This is an OS_ATS quirk, remove??? stm_boilerpressure = stm_boilerpressure - 4; break; case Blowoff.BlowoffStates.Blowoff: //Trigger the sound- Play only once if (!SoundManager.IsPlaying(Blowoff.SoundIndex) && Blowoff.Played == false) { SoundManager.Play(Blowoff.SoundIndex, 1.0, 1.0, false); } Blowoff.Timer += maintimer; if (Blowoff.BlowoffRate != 0) { //If a blowoff time has been set, then reduce the pressure by the calculated blowoff rate stm_boilerpressure -= (int)Blowoff.BlowoffRate; } else { //Otherwise, just run a simple 10-second timer if (Blowoff.Timer > 10) { //Now reduce the boiler pressure to max, and drop the state back to none stm_boilerpressure = (int)boilermaxpressure; Blowoff.BlowoffState = Blowoff.BlowoffStates.None; } } break; } } else { Blowoff.BlowoffState = Blowoff.BlowoffStates.None; } } } if (this.Train.TractionManager.AutomaticAdvancedFunctions == true) { Blowers.Timer += data.ElapsedTime.Milliseconds; //This section of code operates the automatic injectors if (stm_boilerwater > boilermaxwaterlevel / 2 && stm_boilerpressure > boilermaxpressure / 4) { if (LiveSteamInjector.Active == true && Blowers.Timer > 10000) { LiveSteamInjector.Active = false; Train.DebugLogger.LogMessage("The automatic fireman de-activated the injectors"); Blowers.Timer = 0.0; } if (pressureuse > ((boilerwatertosteamrate / 60) * maintimer) * 1.5) { //Turn on the blowers if we're using 50% more pressure than we're generating if (Blowers.Active == false && Blowers.Timer > 10000) { Train.DebugLogger.LogMessage("The automatic fireman activated the blowers"); Blowers.Active = true; Blowers.Timer = 0.0; } } else { if (Blowers.Active == true && Blowers.Timer > 10000) { Train.DebugLogger.LogMessage("The automatic fireman de-activated the blowers"); Blowers.Active = false; Blowers.Timer = 0.0; } } } else { //Blowers shouldn't be on at the same time as the injectors if (LiveSteamInjector.Active == false && Blowers.Timer > 10000) { Train.DebugLogger.LogMessage("The automatic fireman activated the injectors"); LiveSteamInjector.Active = true; Blowers.Timer = 0.0; } Blowers.Active = false; } } LiveSteamInjector.Update(data.ElapsedTime.Seconds, ref stm_boilerwater, ref stm_boilerpressure, ref fuel); //This section of code governs pressure usage if (stm_reverser != 0) { double regpruse = ((double)Train.Handles.PowerNotch / (double)this.Train.Specs.PowerNotches) * regulatorpressureuse; //32 float cutprboost = Math.Abs((float)cutoff) / Math.Abs((float)cutoffmax); //1 float spdpruse = 1 + Math.Abs((float)data.Vehicle.Speed.KilometersPerHour) / 25; if (maintimer > 1) { pressureuse = (int)(regpruse * cutprboost * spdpruse); stm_boilerpressure = stm_boilerpressure - pressureuse; } } //This section of code governs the pressure used by the horn if (klaxonpressureuse != -1 && (Train.TractionManager.primaryklaxonplaying || Train.TractionManager.secondaryklaxonplaying || Train.TractionManager.musicklaxonplaying)) { if (this.maintimer > 1) { stm_boilerpressure = stm_boilerpressure - (int)klaxonpressureuse; } } //This section of code defines the pressure used by the train's steam heating system if (steamheatpressureuse != -1 && steamheatlevel != 0) { if (maintimer > 1) { stm_boilerpressure -= (int)(steamheatlevel * steamheatpressureuse); } } //This section of code fills our tanks from a water tower if (fuelling == true) { if (maintimer > 1) { fuel += (int)fuelfillspeed; } if (fuel > fuelcapacity) { fuel = (int)fuelcapacity; } } //Pass data to the debug window if (AdvancedDriving.CheckInst != null) { //Calculate total pressure usage figure int debugpressureuse = pressureuse; if (Train.TractionManager.primaryklaxonplaying || Train.TractionManager.secondaryklaxonplaying || Train.TractionManager.musicklaxonplaying) { debugpressureuse += (int)klaxonpressureuse; } if (steamheatpressureuse != -1 && steamheatlevel != 0) { debugpressureuse += (int)(steamheatlevel * steamheatpressureuse); } if (CylinderCocks.Active == true) { debugpressureuse += (int)(cylindercocks_basepressureuse + (cylindercocks_notchpressureuse * Train.Handles.PowerNotch)); } this.Train.TractionManager.DebugWindowData.SteamEngine.BoilerPressure = (int)stm_boilerpressure; this.Train.TractionManager.DebugWindowData.SteamEngine.PressureGenerationRate = pressureup; this.Train.TractionManager.DebugWindowData.SteamEngine.PressureUsageRate = debugpressureuse; this.Train.TractionManager.DebugWindowData.SteamEngine.CurrentCutoff = (int)cutoff; this.Train.TractionManager.DebugWindowData.SteamEngine.OptimalCutoff = (int)optimalcutoff; this.Train.TractionManager.DebugWindowData.SteamEngine.FireMass = firemass; this.Train.TractionManager.DebugWindowData.SteamEngine.FireTemperature = firetemp; this.Train.TractionManager.DebugWindowData.SteamEngine.Injectors = LiveSteamInjector.Active; this.Train.TractionManager.DebugWindowData.SteamEngine.Blowers = Blowers.Active; this.Train.TractionManager.DebugWindowData.SteamEngine.BoilerWaterLevel = Convert.ToString(stm_boilerwater) + " of " + Convert.ToString(boilermaxwaterlevel, CultureInfo.InvariantCulture); this.Train.TractionManager.DebugWindowData.SteamEngine.TanksWaterLevel = Convert.ToString(fuel) + " of " + Convert.ToString(fuelcapacity, CultureInfo.InvariantCulture); this.Train.TractionManager.DebugWindowData.SteamEngine.AutoCutoff = this.Train.TractionManager.AutomaticAdvancedFunctions; if (cylindercocks == true) { this.Train.TractionManager.DebugWindowData.SteamEngine.CylinderCocks = "Open"; } else { this.Train.TractionManager.DebugWindowData.SteamEngine.CylinderCocks = "Closed"; } } { //Set Panel Indicators if (cutoffindicator != -1) { this.Train.Panel[(cutoffindicator)] = (int)cutoff; } if (boilerpressureindicator != -1) { this.Train.Panel[boilerpressureindicator] = (int)stm_boilerpressure; } if (boilerwaterlevelindicator != -1) { this.Train.Panel[boilerwaterlevelindicator] = (int)stm_boilerwater; } if (fuelindicator != -1) { this.Train.Panel[fuelindicator] = (int)fuel; } if (automaticindicator != -1) { if (this.Train.TractionManager.AutomaticAdvancedFunctions == false) { this.Train.Panel[automaticindicator] = 0; } else { this.Train.Panel[automaticindicator] = 1; } } if (thermometer != -1) { this.Train.Panel[thermometer] = (int)temperature; } if (overheatindicator != -1) { if (temperature > overheatwarn) { this.Train.Panel[overheatindicator] = 1; } else { this.Train.Panel[overheatindicator] = 0; } } if (fuelfillindicator != -1) { if (fuelling == true) { this.Train.Panel[fuelfillindicator] = 1; } } if (Blowoff.PanelIndex != -1) { if (Blowoff.BlowoffState == Blowoff.BlowoffStates.Blowoff) { this.Train.Panel[Blowoff.PanelIndex] = 1; } else { this.Train.Panel[Blowoff.PanelIndex] = 0; } } if (steamheatindicator != -1) { this.Train.Panel[steamheatindicator] = steamheatlevel; } } //Reset the main timer if it's over 1 second if (this.maintimer > 1) { this.maintimer = 0.0; } }