diff --git a/src/cargotype.h b/src/cargotype.h
index 06e2661461..4c83c3ceba 100644
--- a/src/cargotype.h
+++ b/src/cargotype.h
@@ -65,6 +65,8 @@ enum CargoClass {
 
 static const byte INVALID_CARGO_BITNUM = 0xFF; ///< Constant representing invalid cargo
 
+static const uint TOWN_PRODUCTION_DIVISOR = 256;
+
 /** Specification of a cargo type. */
 struct CargoSpec {
 	CargoLabel label;                ///< Unique label of the cargo type.
@@ -80,6 +82,7 @@ struct CargoSpec {
 	bool is_freight;                 ///< Cargo type is considered to be freight (affects train freight multiplier).
 	TownAcceptanceEffect town_acceptance_effect; ///< The effect that delivering this cargo type has on towns. Also affects destination of subsidies.
 	TownProductionEffect town_production_effect{INVALID_TPE}; ///< The effect on town cargo production.
+	uint16_t town_production_multiplier{TOWN_PRODUCTION_DIVISOR}; ///< Town production multipler, if commanded by TownProductionEffect.
 	uint8_t callback_mask;             ///< Bitmask of cargo callbacks that have to be called
 
 	StringID name;                   ///< Name of this type of cargo.
diff --git a/src/newgrf.cpp b/src/newgrf.cpp
index 4781e460a6..22455baf61 100644
--- a/src/newgrf.cpp
+++ b/src/newgrf.cpp
@@ -3091,6 +3091,24 @@ static ChangeInfoResult CargoChangeInfo(uint cid, int numinfo, int prop, ByteRea
 				cs->multiplier = std::max<uint16_t>(1u, buf->ReadWord());
 				break;
 
+			case 0x1E: { // Town production substitute type
+				uint8_t substitute_type = buf->ReadByte();
+
+				switch (substitute_type) {
+					case 0x00: cs->town_production_effect = TPE_PASSENGERS; break;
+					case 0x02: cs->town_production_effect = TPE_MAIL; break;
+					default:
+						GrfMsg(1, "CargoChangeInfo: Unknown town production substitute value {}, setting to none.", substitute_type);
+						[[fallthrough]];
+					case 0xFF: cs->town_production_effect = TPE_NONE; break;
+				}
+				break;
+			}
+
+			case 0x1F: // Town production multiplier
+				cs->town_production_multiplier = std::max<uint16_t>(1U, buf->ReadWord());
+				break;
+
 			default:
 				ret = CIR_UNKNOWN;
 				break;
diff --git a/src/table/cargo_const.h b/src/table/cargo_const.h
index b7fc6213fd..f182e039df 100644
--- a/src/table/cargo_const.h
+++ b/src/table/cargo_const.h
@@ -44,7 +44,7 @@
  * @param classes      Classes of this cargo type. @see CargoClass
  */
 #define MK(bt, label, colour, weight, mult, ip, td1, td2, freight, tae, str_plural, str_singular, str_volume, classes) \
-		{label, bt, colour, colour, weight, mult, classes, ip, {td1, td2}, freight, tae, INVALID_TPE, 0, \
+		{label, bt, colour, colour, weight, mult, classes, ip, {td1, td2}, freight, tae, INVALID_TPE, TOWN_PRODUCTION_DIVISOR, 0, \
 		MK_STR_CARGO_PLURAL(str_plural), MK_STR_CARGO_SINGULAR(str_singular), str_volume, MK_STR_QUANTITY(str_plural), MK_STR_ABBREV(str_plural), \
 		MK_SPRITE(str_plural), nullptr, nullptr, 0}
 
diff --git a/src/town_cmd.cpp b/src/town_cmd.cpp
index 212c979eba..a2c3df76fb 100644
--- a/src/town_cmd.cpp
+++ b/src/town_cmd.cpp
@@ -558,7 +558,7 @@ static void TownGenerateCargoOriginal(Town *t, TownProductionEffect tpe, uint8_t
 		uint32_t r = Random();
 		if (GB(r, 0, 8) < rate) {
 			CargoID cid = cs->Index();
-			uint amt = GB(r, 0, 8) / 8 + 1;
+			uint amt = (GB(r, 0, 8) * cs->town_production_multiplier / TOWN_PRODUCTION_DIVISOR) / 8 + 1;
 
 			TownGenerateCargo(t, cid, amt, stations, true);
 		}
@@ -583,7 +583,7 @@ static void TownGenerateCargoBinominal(Town *t, TownProductionEffect tpe, uint8_
 		uint32_t genmask = (genmax >= 32) ? 0xFFFFFFFF : ((1 << genmax) - 1);
 
 		/* Mask random value by potential pax and count number of actual pax. */
-		uint amt = CountBits(r & genmask);
+		uint amt = CountBits(r & genmask) * cs->town_production_multiplier / TOWN_PRODUCTION_DIVISOR;
 
 		TownGenerateCargo(t, cid, amt, stations, true);
 	}