diff --git a/src/Format.cpp b/src/Format.cpp
index 78372bc8f..286acf51e 100644
--- a/src/Format.cpp
+++ b/src/Format.cpp
@@ -178,3 +178,57 @@ ByteString format::URLDecode(ByteString source)
 	}
 	return result;
 }
+
+void format::RenderTemperature(StringBuilder &sb, float temp, int scale)
+{
+	switch (scale)
+	{
+	case 1:
+		sb << (temp - 273.15f) << " °C";
+		break;
+	case 2:
+		sb << (temp - 273.15f) * 1.8f + 32.0f << " °F";
+		break;
+	default:
+		sb << temp << " K";
+		break;
+	}
+}
+
+float format::StringToTemperature(String str, int defaultScale)
+{
+	auto scale = defaultScale;
+	if (str.size())
+	{
+		if (str.EndsWith("K"))
+		{
+			scale = 0;
+			str = str.SubstrFromEnd(1);
+		}
+		else if (str.EndsWith("C"))
+		{
+			scale = 1;
+			str = str.SubstrFromEnd(1);
+		}
+		else if (str.EndsWith("F"))
+		{
+			scale = 2;
+			str = str.SubstrFromEnd(1);
+		}
+	}
+	if (!str.size())
+	{
+		throw std::out_of_range("empty string");
+	}
+	auto out = str.ToNumber<float>();
+	switch (scale)
+	{
+	case 1:
+		out = out + 273.15;
+		break;
+	case 2:
+		out = (out - 32.0f) / 1.8f + 273.15f;
+		break;
+	}
+	return out;
+}
diff --git a/src/Format.h b/src/Format.h
index 2c28da979..e395a673c 100644
--- a/src/Format.h
+++ b/src/Format.h
@@ -14,4 +14,6 @@ namespace format
 	ByteString UnixtimeToDateMini(time_t unixtime);
 	String CleanString(String dirtyString, bool ascii, bool color, bool newlines, bool numeric = false);
 	std::vector<char> VideoBufferToPPM(const VideoBuffer & vidBuf);
+	void RenderTemperature(StringBuilder &sb, float temp, int scale);
+	float StringToTemperature(String str, int defaultScale);
 }
diff --git a/src/gui/game/GameController.cpp b/src/gui/game/GameController.cpp
index fd942b1e8..b9d53ddb7 100644
--- a/src/gui/game/GameController.cpp
+++ b/src/gui/game/GameController.cpp
@@ -1035,6 +1035,16 @@ bool GameController::GetDebugHUD()
 	return gameView->GetDebugHUD();
 }
 
+void GameController::SetTemperatureScale(int temperatureScale)
+{
+	gameModel->SetTemperatureScale(temperatureScale);
+}
+
+int GameController::GetTemperatureScale()
+{
+	return gameModel->GetTemperatureScale();
+}
+
 void GameController::SetActiveColourPreset(int preset)
 {
 	gameModel->SetActiveColourPreset(preset);
diff --git a/src/gui/game/GameController.h b/src/gui/game/GameController.h
index ea61cfd4f..a3364f32e 100644
--- a/src/gui/game/GameController.h
+++ b/src/gui/game/GameController.h
@@ -119,6 +119,8 @@ public:
 	bool GetBrushEnable();
 	void SetDebugHUD(bool hudState);
 	bool GetDebugHUD();
+	void SetTemperatureScale(int temperatureScale);
+	int GetTemperatureScale();
 	void SetDebugFlags(unsigned int flags) { debugFlags = flags; }
 	void SetActiveMenu(int menuID);
 	std::vector<Menu*> GetMenuList();
diff --git a/src/gui/game/GameModel.cpp b/src/gui/game/GameModel.cpp
index 0fc6cfc8b..b5d112c91 100644
--- a/src/gui/game/GameModel.cpp
+++ b/src/gui/game/GameModel.cpp
@@ -154,6 +154,7 @@ GameModel::GameModel():
 
 	mouseClickRequired = Client::Ref().GetPrefBool("MouseClickRequired", false);
 	includePressure = Client::Ref().GetPrefBool("Simulation.IncludePressure", true);
+	temperatureScale = Client::Ref().GetPrefInteger("Renderer.TemperatureScale", 1);
 
 	ClearSimulation();
 }
@@ -534,6 +535,11 @@ int GameModel::GetEdgeMode()
 	return this->edgeMode;
 }
 
+void GameModel::SetTemperatureScale(int temperatureScale)
+{
+	this->temperatureScale = temperatureScale;
+}
+
 void GameModel::SetAmbientAirTemperature(float ambientAirTemp)
 {
 	this->ambientAirTemp = ambientAirTemp;
diff --git a/src/gui/game/GameModel.h b/src/gui/game/GameModel.h
index 142e54a15..d3e00e6ba 100644
--- a/src/gui/game/GameModel.h
+++ b/src/gui/game/GameModel.h
@@ -81,6 +81,7 @@ private:
 	bool mouseClickRequired;
 	bool includePressure;
 	bool perfectCircle = true;
+	int temperatureScale;
 
 	size_t activeColourPreset;
 	std::vector<ui::Colour> colourPresets;
@@ -123,6 +124,11 @@ public:
 
 	void SetEdgeMode(int edgeMode);
 	int GetEdgeMode();
+	void SetTemperatureScale(int temperatureScale);
+	inline int GetTemperatureScale() const
+	{
+		return temperatureScale;
+	}
 	void SetAmbientAirTemperature(float ambientAirTemp);
 	float GetAmbientAirTemperature();
 	void SetDecoSpace(int decoSpace);
diff --git a/src/gui/game/GameView.cpp b/src/gui/game/GameView.cpp
index 0808050e5..bd1bbc4f0 100644
--- a/src/gui/game/GameView.cpp
+++ b/src/gui/game/GameView.cpp
@@ -2285,7 +2285,8 @@ void GameView::OnDraw()
 					else if (ctype)
 						sampleInfo << " (" << ctype << ")";
 				}
-				sampleInfo << ", Temp: " << (sample.particle.temp - 273.15f) << " C";
+				sampleInfo << ", Temp: ";
+				format::RenderTemperature(sampleInfo, sample.particle.temp, c->GetTemperatureScale());
 				sampleInfo << ", Life: " << sample.particle.life;
 				if (sample.particle.type != PT_RFRG && sample.particle.type != PT_RFGL && sample.particle.type != PT_LIFE)
 				{
@@ -2315,7 +2316,8 @@ void GameView::OnDraw()
 			else
 			{
 				sampleInfo << c->BasicParticleInfo(sample.particle);
-				sampleInfo << ", Temp: " << sample.particle.temp - 273.15f << " C";
+				sampleInfo << ", Temp: ";
+				format::RenderTemperature(sampleInfo, sample.particle.temp, c->GetTemperatureScale());
 				sampleInfo << ", Pressure: " << sample.AirPressure;
 			}
 		}
@@ -2386,7 +2388,10 @@ void GameView::OnDraw()
 				sampleInfo << ", GX: " << sample.GravityVelocityX << " GY: " << sample.GravityVelocityY;
 
 			if (c->GetAHeatEnable())
-				sampleInfo << ", AHeat: " << sample.AirTemperature - 273.15f << " C";
+			{
+				sampleInfo << ", AHeat: ";
+				format::RenderTemperature(sampleInfo, sample.AirTemperature, c->GetTemperatureScale());
+			}
 
 			textWidth = Graphics::textwidth(sampleInfo.Build());
 			g->fillrect(XRES-20-textWidth, 27, textWidth+8, 14, 0, 0, 0, int(alpha*0.5f));
diff --git a/src/gui/game/PropertyTool.cpp b/src/gui/game/PropertyTool.cpp
index 5c56e3e4a..937360b3c 100644
--- a/src/gui/game/PropertyTool.cpp
+++ b/src/gui/game/PropertyTool.cpp
@@ -2,6 +2,7 @@
 
 #include "client/Client.h"
 #include "Menu.h"
+#include "Format.h"
 
 #include "gui/game/GameModel.h"
 #include "gui/Style.h"
@@ -23,31 +24,6 @@
 
 #include <iostream>
 
-void ParseFloatProperty(String value, float &out)
-{
-	if (!value.size())
-	{
-		throw std::out_of_range("empty string");
-	}
-	if (value.EndsWith("C"))
-	{
-		float v = value.SubstrFromEnd(1).ToNumber<float>();
-		out = v + 273.15;
-	}
-	else if(value.EndsWith("F"))
-	{
-		float v = value.SubstrFromEnd(1).ToNumber<float>();
-		out = (v-32.0f)*5/9+273.15f;
-	}
-	else
-	{
-		out = value.ToNumber<float>();
-	}
-#ifdef DEBUG
-	std::cout << "Got float value " << out << std::endl;
-#endif
-}
-
 class PropertyWindow: public ui::Window
 {
 public:
@@ -219,7 +195,7 @@ void PropertyWindow::SetProperty(bool warn)
 				}
 				case StructProperty::Float:
 				{
-					ParseFloatProperty(value, tool->propValue.Float);
+					tool->propValue.Float = format::StringToTemperature(value, tool->gameModel->GetTemperatureScale());
 				}
 					break;
 				default:
diff --git a/src/gui/options/OptionsController.cpp b/src/gui/options/OptionsController.cpp
index cb7cd23b8..15393233a 100644
--- a/src/gui/options/OptionsController.cpp
+++ b/src/gui/options/OptionsController.cpp
@@ -67,6 +67,11 @@ void OptionsController::SetEdgeMode(int edgeMode)
 	model->SetEdgeMode(edgeMode);
 }
 
+void OptionsController::SetTemperatureScale(int temperatureScale)
+{
+	model->SetTemperatureScale(temperatureScale);
+}
+
 void OptionsController::SetFullscreen(bool fullscreen)
 {
 	model->SetFullscreen(fullscreen);
diff --git a/src/gui/options/OptionsController.h b/src/gui/options/OptionsController.h
index b50a9fbb6..b7f98dd3d 100644
--- a/src/gui/options/OptionsController.h
+++ b/src/gui/options/OptionsController.h
@@ -26,6 +26,7 @@ public:
 	void SetAirMode(int airMode);
 	void SetAmbientAirTemperature(float ambientAirTemp);
 	void SetEdgeMode(int edgeMode);
+	void SetTemperatureScale(int temperatureScale);
 	void SetFullscreen(bool fullscreen);
 	void SetAltFullscreen(bool altFullscreen);
 	void SetForceIntegerScaling(bool forceIntegerScaling);
diff --git a/src/gui/options/OptionsModel.cpp b/src/gui/options/OptionsModel.cpp
index d3b7242fc..5005873f3 100644
--- a/src/gui/options/OptionsModel.cpp
+++ b/src/gui/options/OptionsModel.cpp
@@ -90,6 +90,17 @@ void OptionsModel::SetEdgeMode(int edgeMode)
 	notifySettingsChanged();
 }
 
+int OptionsModel::GetTemperatureScale()
+{
+	return gModel->GetTemperatureScale();
+}
+void OptionsModel::SetTemperatureScale(int temperatureScale)
+{
+	Client::Ref().SetPref("Renderer.TemperatureScale", temperatureScale);
+	gModel->SetTemperatureScale(temperatureScale);
+	notifySettingsChanged();
+}
+
 float OptionsModel::GetAmbientAirTemperature()
 {
 	return gModel->GetSimulation()->air->ambientAirTemp;
diff --git a/src/gui/options/OptionsModel.h b/src/gui/options/OptionsModel.h
index 2ac084487..105f03640 100644
--- a/src/gui/options/OptionsModel.h
+++ b/src/gui/options/OptionsModel.h
@@ -32,6 +32,8 @@ public:
 	void SetAmbientAirTemperature(float ambientAirTemp);
 	int GetEdgeMode();
 	void SetEdgeMode(int edgeMode);
+	int GetTemperatureScale();
+	void SetTemperatureScale(int temperatureScale);
 	int GetGravityMode();
 	void SetGravityMode(int gravityMode);
 	float GetCustomGravityX();
diff --git a/src/gui/options/OptionsView.cpp b/src/gui/options/OptionsView.cpp
index b75837ed1..bb7608978 100644
--- a/src/gui/options/OptionsView.cpp
+++ b/src/gui/options/OptionsView.cpp
@@ -4,6 +4,7 @@
 #include <cstring>
 #include <cmath>
 #include "SDLCompat.h"
+#include "Format.h"
 
 #include "OptionsController.h"
 #include "OptionsModel.h"
@@ -235,6 +236,19 @@ OptionsView::OptionsView():
 	tempLabel->Appearance.VerticalAlign = ui::Appearance::AlignMiddle;
 	scrollPanel->AddChild(tempLabel);
 
+	currentY+=20;
+	temperatureScale = new ui::DropDown(ui::Point(Size.X-95, currentY), ui::Point(80, 16));
+	scrollPanel->AddChild(temperatureScale);
+	temperatureScale->AddOption(std::pair<String, int>("Kelvin", 0));
+	temperatureScale->AddOption(std::pair<String, int>("Celsius", 1));
+	temperatureScale->AddOption(std::pair<String, int>("Fahrenheit", 2));
+	temperatureScale->SetActionCallback({ [this] { c->SetTemperatureScale(temperatureScale->GetOption().second); } });
+
+	tempLabel = new ui::Label(ui::Point(8, currentY), ui::Point(Size.X-96, 16), "Temperature Scale");
+	tempLabel->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
+	tempLabel->Appearance.VerticalAlign = ui::Appearance::AlignMiddle;
+	scrollPanel->AddChild(tempLabel);
+
 	currentY+=20;
 	tmpSeparator = new Separator(ui::Point(0, currentY), ui::Point(Size.X, 1));
 	scrollPanel->AddChild(tmpSeparator);
@@ -437,6 +451,25 @@ void OptionsView::UpdateAmbientAirTempPreview(float airTemp, bool isValid)
 	ambientAirTempPreview->Appearance.BackgroundHover = ambientAirTempPreview->Appearance.BackgroundInactive;
 }
 
+void OptionsView::AmbientAirTempToTextBox(float airTemp)
+{
+	StringBuilder sb;
+	sb << Format::Precision(2);
+	switch (temperatureScale->GetOption().second)
+	{
+	case 1:
+		sb << (airTemp - 273.15f) << "C";
+		break;
+	case 2:
+		sb << (airTemp - 273.15f) * 1.8f + 32.0f << "F";
+		break;
+	default:
+		sb << airTemp;
+		break;
+	}
+	ambientAirTemp->SetText(sb.Build());
+}
+
 void OptionsView::UpdateAirTemp(String temp, bool isDefocus)
 {
 	// Parse air temp and determine validity
@@ -444,8 +477,7 @@ void OptionsView::UpdateAirTemp(String temp, bool isDefocus)
 	bool isValid;
 	try
 	{
-		void ParseFloatProperty(String value, float &out);
-		ParseFloatProperty(temp, airTemp);
+		airTemp = format::StringToTemperature(temp, temperatureScale->GetOption().second);
 		isValid = true;
 	}
 	catch (const std::exception &ex)
@@ -470,10 +502,7 @@ void OptionsView::UpdateAirTemp(String temp, bool isDefocus)
 		else
 			return;
 
-		// Update textbox with the new value
-		StringBuilder sb;
-		sb << Format::Precision(2) << airTemp;
-		ambientAirTemp->SetText(sb.Build());
+		AmbientAirTempToTextBox(airTemp);
 	}
 	// Out of range temperatures are invalid, preview should go away
 	else if (isValid && (airTemp < MIN_TEMP || airTemp > MAX_TEMP))
@@ -488,20 +517,18 @@ void OptionsView::UpdateAirTemp(String temp, bool isDefocus)
 
 void OptionsView::NotifySettingsChanged(OptionsModel * sender)
 {
+	temperatureScale->SetOption(sender->GetTemperatureScale()); // has to happen before AmbientAirTempToTextBox is called
 	heatSimulation->SetChecked(sender->GetHeatSimulation());
 	ambientHeatSimulation->SetChecked(sender->GetAmbientHeatSimulation());
 	newtonianGravity->SetChecked(sender->GetNewtonianGravity());
 	waterEqualisation->SetChecked(sender->GetWaterEqualisation());
 	airMode->SetOption(sender->GetAirMode());
 	// Initialize air temp and preview only when the options menu is opened, and not when user is actively editing the textbox
-	if (!initializedAirTempPreview)
+	if (!ambientAirTemp->IsFocused())
 	{
-		initializedAirTempPreview = true;
 		float airTemp = sender->GetAmbientAirTemperature();
 		UpdateAmbientAirTempPreview(airTemp, true);
-		StringBuilder sb;
-		sb << Format::Precision(2) << airTemp;
-		ambientAirTemp->SetText(sb.Build());
+		AmbientAirTempToTextBox(airTemp);
 	}
 	gravityMode->SetOption(sender->GetGravityMode());
 	customGravityX = sender->GetCustomGravityX();
diff --git a/src/gui/options/OptionsView.h b/src/gui/options/OptionsView.h
index 20431d3f9..5b519c4f2 100644
--- a/src/gui/options/OptionsView.h
+++ b/src/gui/options/OptionsView.h
@@ -27,6 +27,7 @@ class OptionsView: public ui::Window
 	ui::Button * ambientAirTempPreview;
 	ui::DropDown * gravityMode;
 	ui::DropDown * edgeMode;
+	ui::DropDown * temperatureScale;
 	ui::DropDown * scale;
 	ui::Checkbox * resizable;
 	ui::Checkbox * fullscreen;
@@ -41,8 +42,8 @@ class OptionsView: public ui::Window
 	ui::Checkbox * perfectCirclePressure;
 	ui::ScrollPanel * scrollPanel;
 	float customGravityX, customGravityY;
-	bool initializedAirTempPreview = false;
 	void UpdateAmbientAirTempPreview(float airTemp, bool isValid);
+	void AmbientAirTempToTextBox(float airTemp);
 	void UpdateAirTemp(String temp, bool isDefocus);
 public:
 	OptionsView();
diff --git a/src/lua/TPTScriptInterface.cpp b/src/lua/TPTScriptInterface.cpp
index e5c110108..2fdd2f1c7 100644
--- a/src/lua/TPTScriptInterface.cpp
+++ b/src/lua/TPTScriptInterface.cpp
@@ -8,6 +8,7 @@
 #include <cmath>
 
 #include "Config.h"
+#include "Format.h"
 
 #include "simulation/Simulation.h"
 #include "simulation/Air.h"
@@ -271,6 +272,21 @@ AnyType TPTScriptInterface::tptS_set(std::deque<String> * words)
 	//Selector
 	int newValue = 0;
 	float newValuef = 0.0f;
+	if (property.Value() == "temp")
+	{
+		// convert non-string temperature values to strings to format::StringToTemperature can take care of them
+		switch (value.GetType())
+		{
+		case TypeNumber:
+			value = StringType(String::Build(((NumberType)value).Value()));
+			break;
+		case TypeFloat:
+			value = StringType(String::Build(((FloatType)value).Value()));
+			break;
+		default:
+			break;
+		}
+	}
 	if (value.GetType() == TypeNumber)
 	{
 		newValuef = float(newValue = ((NumberType)value).Value());
@@ -283,13 +299,14 @@ AnyType TPTScriptInterface::tptS_set(std::deque<String> * words)
 	{
 		if (property.Value() == "temp")
 		{
-			String newString = ((StringType)value).Value();
-			if (newString.at(newString.length()-1) == 'C')
-				newValuef = atof(newString.SubstrFromEnd(1).ToUtf8().c_str())+273.15;
-			else if (newString.at(newString.length()-1) == 'F')
-				newValuef = (atof(newString.SubstrFromEnd(1).ToUtf8().c_str())-32.0f)*5/9+273.15f;
-			else
+			try
+			{
+				newValuef = format::StringToTemperature(((StringType)value).Value(), c->GetTemperatureScale());
+			}
+			catch (const std::exception &ex)
+			{
 				throw GeneralException("Invalid value for assignment");
+			}
 		}
 		else
 		{