diff --git a/src/simutrans/descriptor/factory_desc.cc b/src/simutrans/descriptor/factory_desc.cc
index ff0cb036d..8fbb4ba94 100644
--- a/src/simutrans/descriptor/factory_desc.cc
+++ b/src/simutrans/descriptor/factory_desc.cc
@@ -54,6 +54,16 @@ void factory_product_desc_t::calc_checksum(checksum_t *chk) const
 }
 
 
+void factory_catalyst_desc_t::calc_checksum(checksum_t *chk) const
+{
+	chk->input(catalyst_boost);
+	chk->input(capacity);
+	chk->input(supplier_count);
+	chk->input(consumption);
+	chk->input(get_catalyst_type()->get_name());
+}
+
+
 void factory_desc_t::correct_smoke()
 {
 	if(  smokerotations == 0   &&  get_smoke()  ) {
@@ -98,6 +108,7 @@ void factory_desc_t::calc_checksum(checksum_t *chk) const
 	chk->input(color);
 	chk->input(supplier_count);
 	chk->input(product_count);
+	chk->input(catalyst_count);
 	chk->input(fields);
 	chk->input(pax_level);
 	chk->input(electricity_producer);
@@ -122,6 +133,11 @@ void factory_desc_t::calc_checksum(checksum_t *chk) const
 		prod->calc_checksum(chk);
 	}
 
+	for (uint8 i=0; i<catalyst_count; i++) {
+		const factory_catalyst_desc_t *cat = get_catalyst(i);
+		cat->calc_checksum(chk);
+	}
+
 #if MSG_LEVEL>0
 	PAKSET_INFO("obj=factory", "");
 	PAKSET_INFO("productivity=", "%d", productivity);
@@ -183,6 +199,14 @@ void factory_desc_t::calc_checksum(checksum_t *chk) const
 		PAKSET_INFO("outputcapacity", "[%d]=%d", i, prod->get_capacity());
 		PAKSET_INFO("outputfactor", "[%d]=%d", i, prod->get_factor());
 	}
+	for (uint8 i = 0; i < catalyst_count; i++) {
+		const factory_catalyst_desc_t* cat = get_catalyst(i);
+		PAKSET_INFO("catalystgood", "[%d]=%s", i, cat->get_catalyst_type()->get_name());
+		PAKSET_INFO("catalystsupplier", "[%d]=%d", i, cat->get_supplier_count());
+		PAKSET_INFO("catalystcapacity", "[%d]=%d", i, cat->get_capacity());
+		PAKSET_INFO("catalystconsumption", "[%d]=%d", i, cat->get_consumption());
+		PAKSET_INFO("catalystboost", "[%d]=%d", i, cat->get_catalyst_boost());
+	}
 
 #endif
 	// moved here, since the field class write out the field entries
diff --git a/src/simutrans/descriptor/factory_desc.h b/src/simutrans/descriptor/factory_desc.h
index 9dea841c8..33b817e21 100644
--- a/src/simutrans/descriptor/factory_desc.h
+++ b/src/simutrans/descriptor/factory_desc.h
@@ -153,6 +153,37 @@ public:
 };
 
 
+/**
+ * Information about a catalyst good for a factory.
+ *
+ * Catalyst goods do not get turned into products; instead, while they are
+ * available in storage, they boost the productivity of the factory.
+ * Consumption of the catalyst is proportional to current production.
+ * Production continues even if no catalyst goods are delivered - in that
+ * case the catalyst simply contributes no boost.
+ *
+ * Child nodes:
+ *  0   Ware
+ */
+class factory_catalyst_desc_t : public obj_desc_t {
+	friend class factory_catalyst_reader_t;
+
+private:
+	uint16 catalyst_boost;   // 256 == 1.0x boost when fully supplied
+	uint16 capacity;
+	uint16 supplier_count;
+	uint16 consumption;
+
+public:
+	goods_desc_t const* get_catalyst_type() const { return get_child<goods_desc_t>(0); }
+	uint16 get_catalyst_boost() const { return catalyst_boost; }
+	uint16 get_capacity() const { return capacity; }
+	uint16 get_supplier_count() const { return supplier_count; }
+	uint16 get_consumption() const { return consumption; }
+	void calc_checksum(checksum_t *chk) const;
+};
+
+
 /**
  * Information about produced goods of a factory
  *
@@ -192,6 +223,10 @@ public:
  *  n+2 Consumer descriptor 1
  *  n+3 Consumer descriptor 2
  * ... ...
+ *      Catalyst descriptor 1
+ *      Catalyst descriptor 2
+ * ... ...
+ *      Field group descriptor (if any)
  */
 class factory_desc_t : public obj_desc_t {
 	friend class factory_reader_t;
@@ -216,6 +251,7 @@ private:
 	uint8  fields               = 0;    // only if there are any ...
 	uint16 supplier_count       = 0;
 	uint16 product_count        = 0;
+	uint16 catalyst_count       = 0;
 	uint16 pax_level            = 12;
 	uint16 expand_probability   = 0;
 	uint16 expand_minimum       = 0;
@@ -256,11 +292,15 @@ public:
 	{
 		return i < product_count ? get_child<factory_product_desc_t>(2 + supplier_count + i) : 0;
 	}
+	const factory_catalyst_desc_t *get_catalyst(uint16 i) const
+	{
+		return i < catalyst_count ? get_child<factory_catalyst_desc_t>(2 + supplier_count + product_count + i) : 0;
+	}
 	const field_group_desc_t *get_field_group() const {
 		if(!fields) {
 			return NULL;
 		}
-		return get_child<field_group_desc_t>(2 + supplier_count + product_count);
+		return get_child<field_group_desc_t>(2 + supplier_count + product_count + catalyst_count);
 	}
 
 	bool is_consumer_only() const { return product_count    == 0; }
@@ -268,6 +308,7 @@ public:
 
 	uint16 get_supplier_count() const { return supplier_count; }
 	uint16 get_product_count() const { return product_count; }
+	uint16 get_catalyst_count() const { return catalyst_count; }
 
 	/* where to built */
 	site_t get_placement() const { return placement; }
diff --git a/src/simutrans/descriptor/objversion.h b/src/simutrans/descriptor/objversion.h
index 0b5f2ed00..ee9ba3558 100644
--- a/src/simutrans/descriptor/objversion.h
+++ b/src/simutrans/descriptor/objversion.h
@@ -32,6 +32,7 @@ enum obj_type : uint32
 	obj_crossing    = C4ID('C','R','S','S'),
 	obj_cursor      = C4ID('C','U','R','S'),
 	obj_factory     = C4ID('F','A','C','T'),
+	obj_fcatalyst   = C4ID('F','C','A','T'),
 	obj_ffield      = C4ID('F','F','I','E'),
 	obj_ffldclass   = C4ID('F','F','C','L'),
 	obj_field       = C4ID('F','I','E','L'),
diff --git a/src/simutrans/descriptor/reader/factory_reader.cc b/src/simutrans/descriptor/reader/factory_reader.cc
index 7ab8b6f57..76e70aa90 100644
--- a/src/simutrans/descriptor/reader/factory_reader.cc
+++ b/src/simutrans/descriptor/reader/factory_reader.cc
@@ -250,6 +250,42 @@ obj_desc_t *factory_product_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 }
 
 
+obj_desc_t *factory_catalyst_reader_t::read_node(FILE *fp, obj_node_info_t &node)
+{
+	node_body_t p(fp, node.size, get_type_name());
+	if (!p) return NULL;
+
+	// old versions of PAK files have no version stamp.
+	// But we know, the higher most bit was always cleared.
+	const uint16 v = decode_uint16(p);
+	const int version = v & 0x8000 ? v & 0x7FFF : 0;
+
+	factory_catalyst_desc_t *desc = new factory_catalyst_desc_t();
+
+	if(version == 1) {
+		// Versioned node, version 1
+		desc->capacity       = decode_uint16(p);
+		desc->supplier_count = decode_uint16(p);
+		desc->consumption    = decode_uint16(p);
+		desc->catalyst_boost = decode_uint16(p);
+	}
+	else {
+		if( version ) {
+			dbg->fatal( "factory_catalyst_reader_t::read_node()", "Cannot handle too new node version %i", version );
+		}
+	}
+
+	PAKSET_INFO("factory_catalyst_reader_t::read_node()", "version=%d, capacity=%d, count=%d, consumption=%d, boost=%d",
+		version,
+		desc->capacity,
+		desc->supplier_count,
+		desc->consumption,
+		desc->catalyst_boost);
+
+	return desc;
+}
+
+
 obj_desc_t *factory_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 {
 	node_body_t p(fp, node.size, get_type_name());
@@ -267,7 +303,43 @@ obj_desc_t *factory_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 
 	typedef factory_desc_t::site_t site_t;
 
-	if(version == 6) {
+	if(version == 7) {
+		// Versioned node, version 7 with catalyst goods
+		desc->placement            = (site_t)decode_uint16(p);
+		desc->productivity         = decode_uint16(p);
+		desc->range                = decode_uint16(p);
+		desc->distribution_weight  = decode_uint16(p);
+		desc->color                = decode_uint8(p);
+		desc->fields               = decode_uint8(p);
+		desc->supplier_count       = decode_uint16(p);
+		desc->product_count        = decode_uint16(p);
+		desc->catalyst_count       = decode_uint16(p);
+		desc->pax_level            = decode_uint16(p);
+		desc->expand_probability   = rescale_probability( decode_uint16(p) );
+		desc->expand_minimum       = decode_uint16(p);
+		desc->expand_range         = decode_uint16(p);
+		desc->expand_times         = decode_uint16(p);
+		desc->electric_boost       = decode_uint16(p);
+		desc->pax_boost            = decode_uint16(p);
+		desc->mail_boost           = decode_uint16(p);
+		desc->electric_demand      = decode_uint16(p);
+		desc->pax_demand           = decode_uint16(p);
+		desc->mail_demand          = decode_uint16(p);
+		desc->electricity_producer = decode_uint8(p);
+		desc->sound_interval       = decode_uint32(p);
+		desc->sound_id             = decode_sint8(p);
+
+		desc->smokerotations = decode_sint8(p);
+		for( int i = 0; i < 4; i++ ) {
+			desc->smoketile  [i].x = decode_sint16(p);
+			desc->smoketile  [i].y = decode_sint16(p);
+			desc->smokeoffset[i].x = decode_sint16(p);
+			desc->smokeoffset[i].y = decode_sint16(p);
+		}
+		desc->smokeuplift = decode_uint16(p);
+		desc->smokelifetime = decode_uint16(p);
+	}
+	else if(version == 6) {
 		// Versioned node, version 6 with explicit electricity producer information
 		desc->placement            = (site_t)decode_uint16(p);
 		desc->productivity         = decode_uint16(p);
@@ -277,6 +349,7 @@ obj_desc_t *factory_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 		desc->fields               = decode_uint8(p);
 		desc->supplier_count       = decode_uint16(p);
 		desc->product_count        = decode_uint16(p);
+		desc->catalyst_count       = 0;
 		desc->pax_level            = decode_uint16(p);
 		desc->expand_probability   = rescale_probability( decode_uint16(p) );
 		desc->expand_minimum       = decode_uint16(p);
@@ -312,6 +385,7 @@ obj_desc_t *factory_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 		desc->fields              = decode_uint8(p);
 		desc->supplier_count      = decode_uint16(p);
 		desc->product_count       = decode_uint16(p);
+		desc->catalyst_count      = 0;
 		desc->pax_level           = decode_uint16(p);
 		desc->expand_probability  = rescale_probability( decode_uint16(p) );
 		desc->expand_minimum      = decode_uint16(p);
@@ -346,6 +420,7 @@ obj_desc_t *factory_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 		desc->fields              = decode_uint8(p);
 		desc->supplier_count      = decode_uint16(p);
 		desc->product_count       = decode_uint16(p);
+		desc->catalyst_count      = 0;
 		desc->pax_level           = decode_uint16(p);
 		desc->expand_probability  = rescale_probability( decode_uint16(p) );
 		desc->expand_minimum      = decode_uint16(p);
@@ -370,6 +445,7 @@ obj_desc_t *factory_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 		desc->fields              = decode_uint8(p);
 		desc->supplier_count      = decode_uint16(p);
 		desc->product_count       = decode_uint16(p);
+		desc->catalyst_count      = 0;
 		desc->pax_level           = decode_uint16(p);
 		desc->expand_probability  = rescale_probability( decode_uint16(p) );
 		desc->expand_minimum      = decode_uint16(p);
@@ -392,6 +468,7 @@ obj_desc_t *factory_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 		desc->fields              = decode_uint8(p);
 		desc->supplier_count      = decode_uint16(p);
 		desc->product_count       = decode_uint16(p);
+		desc->catalyst_count      = 0;
 		desc->pax_level           = decode_uint16(p);
 		desc->expand_probability  = 0;
 		desc->expand_minimum      = 0;
@@ -413,6 +490,7 @@ obj_desc_t *factory_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 		desc->color               = (uint8)decode_uint16(p);
 		desc->supplier_count      = decode_uint16(p);
 		desc->product_count       = decode_uint16(p);
+		desc->catalyst_count      = 0;
 		desc->pax_level           = decode_uint16(p);
 		desc->fields              = 0;
 		desc->expand_probability  = 0;
@@ -440,6 +518,7 @@ obj_desc_t *factory_reader_t::read_node(FILE *fp, obj_node_info_t &node)
 		desc->color               = (uint8)decode_uint16(p);
 		desc->supplier_count      = decode_uint16(p);
 		desc->product_count       = decode_uint16(p);
+		desc->catalyst_count      = 0;
 		desc->pax_level           = 12;
 		desc->fields              = 0;
 		desc->expand_probability  = 0;
diff --git a/src/simutrans/descriptor/reader/factory_reader.h b/src/simutrans/descriptor/reader/factory_reader.h
index 7ad303383..2e378e0bc 100644
--- a/src/simutrans/descriptor/reader/factory_reader.h
+++ b/src/simutrans/descriptor/reader/factory_reader.h
@@ -73,6 +73,16 @@ public:
 };
 
 
+class factory_catalyst_reader_t : public obj_reader_t
+{
+	OBJ_READER_DEF(factory_catalyst_reader_t, obj_fcatalyst, "factory catalyst");
+
+public:
+	/// @copydoc obj_reader_t::read_node
+	obj_desc_t *read_node(FILE *fp, obj_node_info_t &node) OVERRIDE;
+};
+
+
 class factory_reader_t : public obj_reader_t
 {
 	OBJ_READER_DEF(factory_reader_t, obj_factory, "factory");
diff --git a/src/simutrans/descriptor/reader/sim_reader.cc b/src/simutrans/descriptor/reader/sim_reader.cc
index 9d42eb806..d45cc8c46 100644
--- a/src/simutrans/descriptor/reader/sim_reader.cc
+++ b/src/simutrans/descriptor/reader/sim_reader.cc
@@ -69,6 +69,7 @@ tile_reader_t tile_reader_t::the_instance;
 factory_reader_t factory_reader_t::the_instance;
 factory_supplier_reader_t factory_supplier_reader_t::the_instance;
 factory_product_reader_t factory_product_reader_t::the_instance;
+factory_catalyst_reader_t factory_catalyst_reader_t::the_instance;
 factory_smoke_reader_t factory_smoke_reader_t::the_instance;
 factory_field_group_reader_t factory_field_group_reader_t::the_instance;
 field_class_desc_t* factory_field_group_reader_t::incomplete_field_class_desc = NULL;
diff --git a/src/simutrans/descriptor/writer/factory_writer.cc b/src/simutrans/descriptor/writer/factory_writer.cc
index abb3065f6..b901d76ee 100644
--- a/src/simutrans/descriptor/writer/factory_writer.cc
+++ b/src/simutrans/descriptor/writer/factory_writer.cc
@@ -151,6 +151,22 @@ void factory_supplier_writer_t::write_obj(FILE* outfp, obj_node_t& parent, int c
 }
 
 
+void factory_catalyst_writer_t::write_obj(FILE* outfp, obj_node_t& parent, int capacity, int count, int consumption, int boost, const char* warename)
+{
+	obj_node_t node(this, 10, &parent);
+
+	xref_writer_t::instance()->write_obj(outfp, node, obj_good, warename, true);
+
+	node.write_version(outfp, 1);
+	node.write_uint16(outfp, capacity);
+	node.write_uint16(outfp, count);
+	node.write_uint16(outfp, consumption);
+	node.write_uint16(outfp, boost);
+
+	node.check_and_write_header(outfp);
+}
+
+
 void factory_writer_t::write_obj(FILE* fp, obj_node_t& parent, tabfileobj_t& obj)
 {
 	char const*            const placing     = obj.get("location");
@@ -195,7 +211,7 @@ void factory_writer_t::write_obj(FILE* fp, obj_node_t& parent, tabfileobj_t& obj
 	// how long between sounds
 	uint32 const sound_interval   =  obj.get_int("sound_interval",     0xFFFFFFFFul );
 
-	uint16 total_len = 81;
+	uint16 total_len = 83;
 
 	// must be done here, since it may affect the len of the header!
 	string sound_str = ltrim( obj.get("sound") );
@@ -273,6 +289,28 @@ void factory_writer_t::write_obj(FILE* fp, obj_node_t& parent, tabfileobj_t& obj
 		factory_product_writer_t::instance()->write_obj(fp, node, cap, fac, good);
 	}
 
+	uint16 catalyst_count;
+	for (catalyst_count = 0;; ++catalyst_count) {
+		char buf[40];
+
+		sprintf(buf, "catalystgood[%d]", catalyst_count);
+		const char* good = obj.get(buf);
+
+		if (!good || !*good) {
+			break;
+		}
+		sprintf(buf, "catalystsupplier[%d]", catalyst_count);
+		int supp = obj.get_int(buf, 0);
+		sprintf(buf, "catalystcapacity[%d]", catalyst_count);
+		int cap = obj.get_int(buf, 0);
+		sprintf(buf, "catalystconsumption[%d]", catalyst_count);
+		int consumption = (obj.get_int(buf, 100) * 256) / 100;
+		sprintf(buf, "catalystboost[%d]", catalyst_count);
+		int boost = (obj.get_int(buf, 100) * 256) / 100;
+
+		factory_catalyst_writer_t::instance()->write_obj(fp, node, cap, supp, consumption, boost, good);
+	}
+
 	// fields (careful, are xref'ed)
 	uint8 fields = 0;
 	if(  *obj.get("fields")  ||  *obj.get("fields[0]")  ) {
@@ -310,8 +348,8 @@ void factory_writer_t::write_obj(FILE* fp, obj_node_t& parent, tabfileobj_t& obj
 		xy_off[0] = obj.get_koord( "smokeoffset", koord( 0, 0 ) );
 	}
 
-	// new version with pax_level, and smoke offsets part of factory
-	node.write_version(fp, 6);
+	// new version with catalyst goods
+	node.write_version(fp, 7);
 
 	node.write_uint16(fp, placement);
 	node.write_uint16(fp, productivity);
@@ -321,6 +359,7 @@ void factory_writer_t::write_obj(FILE* fp, obj_node_t& parent, tabfileobj_t& obj
 	node.write_uint8 (fp, fields);
 	node.write_uint16(fp, supplier_count);
 	node.write_uint16(fp, product_count);
+	node.write_uint16(fp, catalyst_count);
 	node.write_uint16(fp, pax_level);
 	node.write_uint16(fp, expand_probability);
 	node.write_uint16(fp, expand_minimum);
diff --git a/src/simutrans/descriptor/writer/factory_writer.h b/src/simutrans/descriptor/writer/factory_writer.h
index 41a3fe4fa..dedf145ab 100644
--- a/src/simutrans/descriptor/writer/factory_writer.h
+++ b/src/simutrans/descriptor/writer/factory_writer.h
@@ -98,6 +98,23 @@ public:
 };
 
 
+class factory_catalyst_writer_t : public obj_writer_t
+{
+private:
+	static factory_catalyst_writer_t the_instance;
+
+	factory_catalyst_writer_t() { register_writer(false); }
+
+public:
+	static factory_catalyst_writer_t* instance() { return &the_instance; }
+
+	obj_type get_type() const OVERRIDE { return obj_fcatalyst; }
+	const char* get_type_name() const OVERRIDE { return "factory catalyst"; }
+
+	void write_obj(FILE* outfp, obj_node_t& parent, int capacity, int count, int consumption, int boost, const char* warename);
+};
+
+
 class factory_writer_t : public obj_writer_t
 {
 private:
diff --git a/src/simutrans/descriptor/writer/sim_writer.cc b/src/simutrans/descriptor/writer/sim_writer.cc
index 3ea2b013d..498b331b0 100644
--- a/src/simutrans/descriptor/writer/sim_writer.cc
+++ b/src/simutrans/descriptor/writer/sim_writer.cc
@@ -71,6 +71,7 @@ tile_writer_t tile_writer_t::the_instance;
 factory_writer_t factory_writer_t::the_instance;
 factory_supplier_writer_t factory_supplier_writer_t::the_instance;
 factory_product_writer_t factory_product_writer_t::the_instance;
+factory_catalyst_writer_t factory_catalyst_writer_t::the_instance;
 factory_smoke_writer_t factory_smoke_writer_t::the_instance;
 factory_field_group_writer_t factory_field_group_writer_t::the_instance;
 factory_field_class_writer_t factory_field_class_writer_t::the_instance;
diff --git a/src/simutrans/gui/fabrik_info.cc b/src/simutrans/gui/fabrik_info.cc
index 67b6e5f49..ef2eb54fb 100644
--- a/src/simutrans/gui/fabrik_info.cc
+++ b/src/simutrans/gui/fabrik_info.cc
@@ -134,6 +134,10 @@ void fabrik_info_t::init(fabrik_t* fab_, const gebaeude_t* gb)
 			boost_mail.set_image(skinverwaltung_t::mail->get_image_id(0), true);
 			add_component(&boost_mail);
 		}
+		if (fab->get_desc()->get_catalyst_count() > 0) {
+			boost_catalyst.set_image(skinverwaltung_t::goods->get_image_id(0), true);
+			add_component(&boost_catalyst);
+		}
 
 		// world view object with boost overlays
 		add_table(1,0)->set_spacing( scr_size(0,0));
@@ -155,6 +159,7 @@ void fabrik_info_t::init(fabrik_t* fab_, const gebaeude_t* gb)
 
 	container_info.set_table_layout(1,0);
 	container_info.add_component(&all_suppliers);
+	container_info.add_component(&all_catalysts);
 	container_info.add_component(&all_consumers);
 	container_info.add_component(&all_cities);
 	container_info.add_component(&txt);
@@ -165,6 +170,7 @@ void fabrik_info_t::init(fabrik_t* fab_, const gebaeude_t* gb)
 	old_consumers_count = 0;
 	old_stops_count = 0;
 	old_cities_count = 0;
+	old_catalysts_count = 0;
 
 	update_info();
 
@@ -256,6 +262,7 @@ void fabrik_info_t::draw(scr_coord pos, scr_size size)
 	boost_electric.set_transparent(fab->get_prodfactor_electric()>0 ? 0 : TRANSPARENT50_FLAG | OUTLINE_FLAG | SYSCOL_IMAGE_TRANSPARENCY);
 	boost_passenger.set_transparent(fab->get_prodfactor_pax()>0 ? 0 : TRANSPARENT50_FLAG | OUTLINE_FLAG | SYSCOL_IMAGE_TRANSPARENCY);
 	boost_mail.set_transparent(fab->get_prodfactor_mail()>0 ? 0 : TRANSPARENT50_FLAG | OUTLINE_FLAG | SYSCOL_IMAGE_TRANSPARENCY);
+	boost_catalyst.set_transparent(fab->get_prodfactor_catalyst()>0 ? 0 : TRANSPARENT50_FLAG | OUTLINE_FLAG | SYSCOL_IMAGE_TRANSPARENCY);
 
 	indicator_color.set_color( gfx->palette_lookup(fabrik_t::status_to_color[fab->get_status()]) );
 
@@ -353,6 +360,22 @@ void fabrik_info_t::update_components()
 		}
 		old_suppliers_count = fab->get_suppliers().get_count();
 	}
+	// catalyst suppliers
+	if(  fab->get_catalyst_suppliers().get_count() != old_catalysts_count ) {
+		all_catalysts.remove_all();
+		all_catalysts.set_table_layout(1,0);
+		all_catalysts.set_margin(scr_size(0,0), scr_size(0,D_V_SPACE));
+		if(  !fab->get_catalyst_suppliers().empty()  ) {
+			all_catalysts.new_component<gui_label_t>("Catalyst suppliers");
+		}
+
+		for(koord k : fab->get_catalyst_suppliers() ) {
+			if(  const fabrik_t *src = fabrik_t::get_fab(k)  ) {
+				all_catalysts.new_component<gui_factory_connection_t>(fab, src->get_pos().get_2d(), true);
+			}
+		}
+		old_catalysts_count = fab->get_catalyst_suppliers().get_count();
+	}
 	// cities
 	if(  fab->get_target_cities().get_count() != old_cities_count  ) {
 		all_cities.remove_all();
diff --git a/src/simutrans/gui/fabrik_info.h b/src/simutrans/gui/fabrik_info.h
index 20e7c9cf4..46b97b455 100644
--- a/src/simutrans/gui/fabrik_info.h
+++ b/src/simutrans/gui/fabrik_info.h
@@ -51,13 +51,13 @@ private:
 
 	gui_tab_panel_t switch_mode;
 
-	gui_image_t boost_electric, boost_passenger, boost_mail;
+	gui_image_t boost_electric, boost_passenger, boost_mail, boost_catalyst;
 
 
 	gui_aligned_container_t container_info, container_details, container_power;
 
-	gui_aligned_container_t all_suppliers, all_consumers, all_stops, all_cities;
-	uint32 old_suppliers_count, old_consumers_count, old_stops_count, old_cities_count;
+	gui_aligned_container_t all_suppliers, all_consumers, all_stops, all_cities, all_catalysts;
+	uint32 old_suppliers_count, old_consumers_count, old_stops_count, old_cities_count, old_catalysts_count;
 
 	gui_scrollpane_t scroll_info;
 
diff --git a/src/simutrans/simfab.cc b/src/simutrans/simfab.cc
index a707c9690..98ee09024 100644
--- a/src/simutrans/simfab.cc
+++ b/src/simutrans/simfab.cc
@@ -361,6 +361,7 @@ void fabrik_t::init_stats()
 	weighted_sum_boost_electric = 0;
 	weighted_sum_boost_pax = 0;
 	weighted_sum_boost_mail = 0;
+	weighted_sum_boost_catalyst = 0;
 	weighted_sum_power = 0;
 	aggregate_weight = 0;
 }
@@ -383,11 +384,13 @@ void fabrik_t::book_weighted_sums(sint64 delta_time)
 	weighted_sum_production += current_prod * delta_time;
 	set_stat( current_prod, FAB_PRODUCTION );
 
-	// electricity, pax and mail boosts
+	// electricity, pax, mail and catalyst boosts
 	weighted_sum_boost_electric += prodfactor_electric * delta_time;
 	set_stat( prodfactor_electric, FAB_BOOST_ELECTRIC );
 	weighted_sum_boost_pax += prodfactor_pax * delta_time;
 	weighted_sum_boost_mail += prodfactor_mail * delta_time;
+	weighted_sum_boost_catalyst += prodfactor_catalyst * delta_time;
+	set_stat( prodfactor_catalyst, FAB_BOOST_CATALYST );
 
 	// power produced or consumed
 	sint64 power = get_power();
@@ -502,6 +505,71 @@ void fabrik_t::update_prodfactor_mail()
 }
 
 
+void fabrik_t::update_scaled_catalyst_demand()
+{
+	for(  uint16 i=0;  i<desc->get_catalyst_count();  i++  ) {
+		const factory_catalyst_desc_t *const cat = desc->get_catalyst(i);
+		// raw catalyst units consumed over a full PRODUCTION_DELTA_T tick at current prodbase
+		scaled_catalyst_demand[i] = (uint32)convert_goods( (sint64)prodbase * (sint64)cat->get_consumption() );
+	}
+}
+
+
+void fabrik_t::update_prodfactor_catalyst(uint32 delta_t)
+{
+	prodfactor_catalyst = 0;
+
+	for(  uint16 i=0;  i<desc->get_catalyst_count();  i++  ) {
+		const factory_catalyst_desc_t *const cat = desc->get_catalyst(i);
+		ware_production_t &ware = catalyst[i];
+
+		if(  cat->get_catalyst_boost()==0  ) {
+			continue;
+		}
+
+		// amount of catalyst wanted for this tick
+		const sint64 desired = ( (sint64)scaled_catalyst_demand[i] * (sint64)delta_t ) >> PRODUCTION_DELTA_T_BITS;
+
+		sint32 satisfaction;
+		if(  desired<=0  ) {
+			// nothing needed right now -> treat as fully satisfied
+			satisfaction = DEFAULT_PRODUCTION_FACTOR;
+		}
+		else if(  (sint64)ware.menge >= desired  ) {
+			satisfaction = DEFAULT_PRODUCTION_FACTOR;
+		}
+		else {
+			satisfaction = (sint32)( ( (sint64)ware.menge << DEFAULT_PRODUCTION_FACTOR_BITS ) / desired );
+		}
+
+		prodfactor_catalyst += (sint32)( ( (sint64)cat->get_catalyst_boost() * (sint64)satisfaction ) >> DEFAULT_PRODUCTION_FACTOR_BITS );
+	}
+}
+
+
+void fabrik_t::consume_catalyst(uint32 delta_t)
+{
+	if(  !currently_producing  ) {
+		// factory is idle this tick - do not eat any catalyst stock
+		return;
+	}
+
+	for(  uint16 i=0;  i<desc->get_catalyst_count();  i++  ) {
+		ware_production_t &ware = catalyst[i];
+
+		// amount of catalyst wanted for this tick
+		const sint64 desired = ( (sint64)scaled_catalyst_demand[i] * (sint64)delta_t ) >> PRODUCTION_DELTA_T_BITS;
+
+		// consume whatever is available; never blocks production if catalyst is missing
+		const sint32 consumed = (sint32)min( (sint64)ware.menge, desired );
+		if(  consumed>0  ) {
+			ware.menge -= consumed;
+			ware.book_stat( consumed, FAB_GOODS_CONSUMED );
+		}
+	}
+}
+
+
 void fabrik_t::recalc_demands_at_target_cities()
 {
 	if (!welt->get_settings().get_factory_enforce_demand()) {
@@ -673,6 +741,7 @@ void fabrik_t::set_base_production(sint32 p)
 	update_scaled_electric_demand();
 	update_scaled_pax_demand();
 	update_scaled_mail_demand();
+	update_scaled_catalyst_demand();
 	update_prodfactor_pax();
 	update_prodfactor_mail();
 	recalc_demands_at_target_cities();
@@ -791,6 +860,7 @@ fabrik_t::fabrik_t(koord3d pos_, player_t* owner, const factory_desc_t* factory_
 	prodfactor_electric = 0;
 	prodfactor_pax = 0;
 	prodfactor_mail = 0;
+	prodfactor_catalyst = 0;
 	if (initial_prod_base < 0) {
 		prodbase = desc->get_productivity() + simrand(desc->get_range());
 	}
@@ -822,6 +892,16 @@ fabrik_t::fabrik_t(koord3d pos_, player_t* owner, const factory_desc_t* factory_
 		output[g].set_typ( product->get_output_type() );
 	}
 
+	// create catalyst information
+	catalyst.resize( factory_desc->get_catalyst_count() );
+	scaled_catalyst_demand.resize( factory_desc->get_catalyst_count() );
+	for(  uint g=0;  g<factory_desc->get_catalyst_count();  ++g  ) {
+		const factory_catalyst_desc_t *const cat = factory_desc->get_catalyst(g);
+		catalyst[g].set_typ( cat->get_catalyst_type() );
+		catalyst[g].max = cat->get_capacity();
+		scaled_catalyst_demand[g] = 0;
+	}
+
 	recalc_storage_capacities();
 	if( welt->get_settings().get_just_in_time() >= 2 ){
 		inactive_inputs = inactive_outputs = inactive_demands = 0;
@@ -867,6 +947,7 @@ fabrik_t::fabrik_t(koord3d pos_, player_t* owner, const factory_desc_t* factory_
 	update_scaled_electric_demand();
 	update_scaled_pax_demand();
 	update_scaled_mail_demand();
+	update_scaled_catalyst_demand();
 }
 
 
@@ -1416,6 +1497,79 @@ void fabrik_t::rdwr(loadsave_t *file)
 		}
 	}
 
+	// now rebuild information for catalyst goods
+	if(  file->is_version_atleast(124, 6)  ) {
+		sint32 catalyst_count;
+		if(  file->is_saving()  ) {
+			catalyst_count = catalyst.get_count();
+		}
+		file->rdwr_long(catalyst_count);
+		if(  file->is_loading()  ) {
+			catalyst.resize( catalyst_count );
+		}
+		for(  i=0;  i<catalyst_count;  i++  ) {
+			ware_production_t &ware = catalyst[i];
+			const char *ware_name = NULL;
+			sint32 menge = ware.menge;
+			if(  file->is_saving()  ) {
+				ware_name = ware.get_typ()->get_name();
+			}
+			file->rdwr_str(ware_name);
+			file->rdwr_long(menge);
+			ware.rdwr( file );
+			if(  file->is_loading()  ) {
+				if (!ware_name) {
+					dbg->fatal("fabrik_t::rdwr", "Invalid catalyst ware at slot %d of factory at %s", i, pos.get_fullstr());
+				}
+				ware.set_typ( goods_manager_t::get_info(ware_name) );
+				ware.menge = menge < 0 ? 0 : menge;
+				free(const_cast<char *>(ware_name));
+			}
+		}
+		if(  file->is_loading()  &&  desc  ) {
+			if(  (uint16)catalyst_count != desc->get_catalyst_count()  ) {
+				// rebuild to match pak descriptor, matching by good type where possible
+				array_tpl<ware_production_t> dummy;
+				dummy.resize(catalyst.get_count());
+				for(uint16 j=0; j<catalyst.get_count(); j++) {
+					dummy[j] = catalyst[j];
+				}
+				catalyst.resize( desc->get_catalyst_count() );
+				for(uint16 j=0; j<desc->get_catalyst_count(); j++) {
+					bool missing = true;
+					const goods_desc_t* goods = desc->get_catalyst(j)->get_catalyst_type();
+					for(uint16 k=0; k<dummy.get_count()  &&  missing; k++) {
+						if (dummy[k].get_typ() == goods) {
+							catalyst[j] = dummy[k];
+							dummy[k].set_typ(NULL);
+							missing = false;
+						}
+					}
+					if (missing) {
+						catalyst[j].set_typ(goods);
+						catalyst[j].menge = 0;
+					}
+					catalyst[j].max = desc->get_catalyst(j)->get_capacity();
+				}
+			}
+			else {
+				for(uint16 j=0; j<desc->get_catalyst_count(); j++) {
+					catalyst[j].max = desc->get_catalyst(j)->get_capacity();
+				}
+			}
+			scaled_catalyst_demand.resize( desc->get_catalyst_count() );
+		}
+	}
+	else if(  file->is_loading()  ) {
+		catalyst.resize( desc ? desc->get_catalyst_count() : 0 );
+		scaled_catalyst_demand.resize( desc ? desc->get_catalyst_count() : 0 );
+		for(uint16 j=0; desc && j<desc->get_catalyst_count(); j++) {
+			catalyst[j].set_typ( desc->get_catalyst(j)->get_catalyst_type() );
+			catalyst[j].max = desc->get_catalyst(j)->get_capacity();
+			catalyst[j].menge = 0;
+		}
+	}
+
 	// restore other information
 	owner_n = welt->sp2num(owner);
 	file->rdwr_long(owner_n);
@@ -1814,6 +1968,23 @@ sint32 fabrik_t::liefere_an(const goods_desc_t *typ, sint32 menge)
 				return menge;
 			}
 		}
+		for(  uint32 c = 0;  c < catalyst.get_count();  c++  ){
+			ware_production_t& ware = catalyst[c];
+			if(  ware.get_typ() == typ  ) {
+				ware.book_stat( -menge, FAB_GOODS_TRANSIT );
+
+				sint32 accepted = menge;
+				if(  ware.menge + accepted > ware.max  ) {
+					accepted = ware.max - ware.menge;
+				}
+				if(  accepted < 0  ) {
+					accepted = 0;
+				}
+				ware.menge += accepted;
+				ware.book_stat(accepted, FAB_GOODS_RECEIVED);
+				return accepted;
+			}
+		}
 	}
 	// ware "typ" wird hier nicht verbraucht
 	return -1;
@@ -1828,6 +1999,12 @@ sint8 fabrik_t::is_needed(const goods_desc_t *typ) const
 			return i.placing_orders;
 		}
 	}
+	for(ware_production_t const& c : catalyst) {
+		if(  c.get_typ() == typ  ) {
+			// catalyst storage never blocks production; simply accept while below capacity
+			return c.menge < c.max;
+		}
+	}
 	return -1;  // not needed here
 }
 
@@ -1919,6 +2096,9 @@ void fabrik_t::step(uint32 delta_t)
 		}
 	};
 
+	// Catalyst goods boost production while available, but never block it; consume stock accordingly.
+	update_prodfactor_catalyst(delta_t);
+
 	// Compute boost amount.
 	const sint32 boost = get_prodfactor();
 
@@ -2420,6 +2600,9 @@ void fabrik_t::step(uint32 delta_t)
 		}
 	}
 
+	// Catalyst goods are only eaten while the factory is actually working this tick.
+	consume_catalyst( delta_t );
+
 	/// Book the weighted sums for statistics.
 
 	book_weighted_sums( delta_t );
@@ -2786,6 +2969,7 @@ void fabrik_t::new_month()
 	weighted_sum_boost_electric = 0;
 	weighted_sum_boost_pax = 0;
 	weighted_sum_boost_mail = 0;
+	weighted_sum_boost_catalyst = 0;
 	weighted_sum_power = 0;
 	aggregate_weight = 0;
 
@@ -3055,6 +3239,25 @@ void fabrik_t::info_prod(cbuffer_t& buf) const
 			}
 		}
 	}
+
+	if (!catalyst.empty()) {
+		buf.append("\n\n");
+		buf.append(translator::translate("Catalysts"));
+
+		for (uint32 index = 0; index < catalyst.get_count(); index++) {
+			const factory_catalyst_desc_t *const cat = desc->get_catalyst(index);
+			goods_desc_t const *const type = catalyst[index].get_typ();
+
+			buf.append("\n - ");
+			buf.printf("%s %d/%d%s, %d%%",
+				translator::translate(type->get_name()),
+				catalyst[index].menge,
+				catalyst[index].max,
+				translator::translate(type->get_mass()),
+				(uint32)((FAB_PRODFACT_UNIT_HALF + (sint32)cat->get_catalyst_boost() * 100) >> DEFAULT_PRODUCTION_FACTOR_BITS)
+			);
+		}
+	}
 }
 
 
@@ -3099,6 +3302,17 @@ void fabrik_t::info_conn(cbuffer_t& buf) const
 			buf.printf("\n - %s", plan->get_haltlist()[i]->get_name() );
 		}
 	}
+
+	if(  !catalyst_suppliers.empty()  ) {
+		buf.append("\n\n");
+		buf.append(translator::translate("Catalyst suppliers"));
+
+		for(  koord const& k : catalyst_suppliers  ) {
+			if(  const fabrik_t *src = fabrik_t::get_fab(k)  ) {
+				buf.printf("\n - %s (%d,%d)", src->get_name(), k.x, k.y);
+			}
+		}
+	}
 }
 
 
@@ -3197,6 +3411,9 @@ void fabrik_t::rotate90( const sint16 y_size )
 	for(koord & i : suppliers) {
 		i.rotate90(y_size);
 	}
+	for(koord & i : catalyst_suppliers) {
+		i.rotate90(y_size);
+	}
 	for(field_data_t & i : fields) {
 		i.location.rotate90(y_size);
 	}
@@ -3300,6 +3517,18 @@ void fabrik_t::add_all_suppliers()
 			}
 		}
 	}
+
+	for(int i=0; i < desc->get_catalyst_count(); i++) {
+		const factory_catalyst_desc_t *cat = desc->get_catalyst(i);
+		const goods_desc_t *ware = cat->get_catalyst_type();
+
+		for(fabrik_t* const fab : welt->get_fab_list()) {
+			// connect to an existing one, if this is an producer of the catalyst good
+			if(fab!=this  &&  fab->get_output_stock(ware) > -1) {
+				add_catalyst_supplier(fab->get_pos().get_2d());
+			}
+		}
+	}
 }
 
 
@@ -3319,7 +3548,30 @@ bool fabrik_t::add_supplier(fabrik_t* fab)
 				return true;
 			}
 	}
-	return false;
+
+	bool found = false;
+	for(int i=0; i < desc->get_catalyst_count(); i++) {
+		const factory_catalyst_desc_t *cat = desc->get_catalyst(i);
+		const goods_desc_t *ware = cat->get_catalyst_type();
+
+		if(  fab!=this  &&  fab->get_output_stock(ware) > -1  ) {
+			add_catalyst_supplier(fab->get_pos().get_2d());
+			found = true;
+		}
+	}
+	return found;
+}
+
+
+void fabrik_t::add_catalyst_supplier(koord k)
+{
+	catalyst_suppliers.insert_unique_ordered( k, RelativeDistanceOrdering(pos.get_2d()) );
+}
+
+
+void fabrik_t::remove_catalyst_supplier(koord k)
+{
+	catalyst_suppliers.remove(k);
 }
 
 
diff --git a/src/simutrans/simfab.h b/src/simutrans/simfab.h
index 023936a0a..444ca5ab6 100644
--- a/src/simutrans/simfab.h
+++ b/src/simutrans/simfab.h
@@ -40,7 +40,8 @@ class leitung_t;
 #define FAB_MAIL_GENERATED          (8)
 #define FAB_MAIL_DEPARTED           (9)
 #define FAB_MAIL_ARRIVED           (10)
-#define MAX_FAB_STAT               (11)
+#define FAB_BOOST_CATALYST         (11)
+#define MAX_FAB_STAT               (12)
 
 // reference lines
 #define FAB_REF_MAX_BOOST_ELECTRIC  (0)
@@ -49,7 +50,8 @@ class leitung_t;
 #define FAB_REF_DEMAND_ELECTRIC     (3)
 #define FAB_REF_DEMAND_PAX          (4)
 #define FAB_REF_DEMAND_MAIL         (5)
-#define MAX_FAB_REF_LINE            (6)
+#define FAB_REF_MAX_BOOST_CATALYST  (6)
+#define MAX_FAB_REF_LINE            (7)
 
 // statistics for goods
 #define MAX_FAB_GOODS_STAT          (4)
@@ -209,6 +211,7 @@ private:
 	sint64 weighted_sum_boost_electric;
 	sint64 weighted_sum_boost_pax;
 	sint64 weighted_sum_boost_mail;
+	sint64 weighted_sum_boost_catalyst;
 	sint64 weighted_sum_power;
 	sint64 aggregate_weight;
 
@@ -261,6 +264,12 @@ private:
 	 */
 	vector_tpl <koord> suppliers;
 
+	/**
+	 * factories supplying catalyst goods to this factory (display/info only,
+	 * catalyst goods are delivered like normal goods but never gate production)
+	 */
+	vector_tpl <koord> catalyst_suppliers;
+
 	/**
 	 * fields of this factory (only for farms etc.)
 	 */
@@ -306,9 +315,14 @@ private:
 	sint32 prodfactor_electric;
 	sint32 prodfactor_pax;
 	sint32 prodfactor_mail;
+	sint32 prodfactor_catalyst;
+
+	array_tpl<ware_production_t> input;    ///< array for input/consumed goods
+	array_tpl<ware_production_t> output;   ///< array for output/produced goods
+	array_tpl<ware_production_t> catalyst; ///< array for catalyst goods (boost production, never consumed as production input)
 
-	array_tpl<ware_production_t> input;  ///< array for input/consumed goods
-	array_tpl<ware_production_t> output; ///< array for output/produced goods
+	/// per-catalyst-slot amount consumed per full PRODUCTION_DELTA_T tick at current prodbase
+	array_tpl<uint32> scaled_catalyst_demand;
 
 	/// Accumulated time since last production
 	uint32 delta_t_sum;
@@ -374,6 +388,7 @@ private:
 	 */
 	void update_scaled_pax_demand();
 	void update_scaled_mail_demand();
+	void update_scaled_catalyst_demand();
 
 	/**
 	 * Update production multipliers for pax and mail
@@ -381,6 +396,18 @@ private:
 	void update_prodfactor_pax();
 	void update_prodfactor_mail();
 
+	/**
+	 * Update production multiplier contributed by catalyst goods, based on
+	 * currently available catalyst stock. Does not consume any stock.
+	 */
+	void update_prodfactor_catalyst(uint32 delta_t);
+
+	/**
+	 * Consume catalyst stock for this tick. Only called if the factory
+	 * actually worked this tick - idle factories do not consume catalysts.
+	 */
+	void consume_catalyst(uint32 delta_t);
+
 	/**
 	 * Re-calculate the pax/mail demands of factory at target cities
 	 */
@@ -508,6 +535,7 @@ public:
 			case FAB_BOOST_ELECTRIC:
 			case FAB_BOOST_PAX:
 			case FAB_BOOST_MAIL:
+			case FAB_BOOST_CATALYST:
 				value = convert_boost(value);
 				break;
 			default: ;
@@ -539,6 +567,13 @@ public:
 
 	const vector_tpl<koord>& get_suppliers() const { return suppliers; }
 
+	/**
+	 * positions of factories supplying catalyst goods to this factory
+	 */
+	const vector_tpl<koord>& get_catalyst_suppliers() const { return catalyst_suppliers; }
+	void add_catalyst_supplier(koord pos);
+	void remove_catalyst_supplier(koord pos);
+
 	/**
 	 * Functions for manipulating the list of connected cities
 	 */
@@ -662,6 +697,7 @@ public:
 	 */
 	const array_tpl<ware_production_t>& get_input() const { return input; }
 	const array_tpl<ware_production_t>& get_output() const { return output; }
+	const array_tpl<ware_production_t>& get_catalyst() const { return catalyst; }
 
 	/**
 	 * Production multipliers
@@ -669,7 +705,8 @@ public:
 	sint32 get_prodfactor_electric() const { return prodfactor_electric; }
 	sint32 get_prodfactor_pax() const { return prodfactor_pax; }
 	sint32 get_prodfactor_mail() const { return prodfactor_mail; }
-	sint32 get_prodfactor() const { return DEFAULT_PRODUCTION_FACTOR + prodfactor_electric + prodfactor_pax + prodfactor_mail; }
+	sint32 get_prodfactor_catalyst() const { return prodfactor_catalyst; }
+	sint32 get_prodfactor() const { return DEFAULT_PRODUCTION_FACTOR + prodfactor_electric + prodfactor_pax + prodfactor_mail + prodfactor_catalyst; }
 
 	/* does not takes month length into account */
 	sint32 get_base_production() const { return prodbase; }
diff --git a/src/simutrans/world/simworld.cc b/src/simutrans/world/simworld.cc
index 45c8bb88b..5cb7d97d0 100644
--- a/src/simutrans/world/simworld.cc
+++ b/src/simutrans/world/simworld.cc
@@ -2541,6 +2541,7 @@ bool karte_t::rem_fab(fabrik_t *fab)
 		for(fabrik_t* const fab : all_factories) {
 			fab->remove_consumer(k);
 			fab->remove_supplier(k);
+			fab->remove_catalyst_supplier(k);
 		}
 
 		// remove all links to cities
