diff --git src/simutrans/dataobj/scenario.cc src/simutrans/dataobj/scenario.cc
index 4ff25a196..394a6a7f8 100644
--- src/simutrans/dataobj/scenario.cc
+++ src/simutrans/dataobj/scenario.cc
@@ -49,6 +49,36 @@ const int NETWORK_CACHE_TIME = 10000;
 static plainstringhashtable_tpl<plainstring> cached_text_files;
 
 
+
+tool_rule_t::tool_rule_t(rule_type type_, uint16 toolnr_, sint16 waytype_, const char* param_)
+	: type(type_)
+	, toolnr(toolnr_)
+	, waytype(waytype_ < 0 ? (sint16)ignore_wt : waytype_)
+	, cube_corners{ koord3d::invalid, koord3d::invalid }
+	, error()
+{
+	if (toolnr == (GENERAL_TOOL|TOOL_SCHEDULE_INS)  ||  toolnr == (GENERAL_TOOL | TOOL_SCHEDULE_ADD)) {
+		// paramter is pointer to binary => not checking
+		parameter_hash = 0;
+		return;
+	}
+
+	parameter_hash = string_to_hash(param_)&0x7FFFFFFul;
+}
+
+
+tool_rule_t::tool_rule_t(uint16 toolnr_, sint16 waytype_, const char* param_, koord nw, koord se, sint8 hmin_, sint8 hmax_)
+	: type(rule_type::FORBID_IN_CUBE)
+	, toolnr(toolnr_)
+	, waytype(waytype_ < 0 ? (sint16)waytype_t::ignore_wt : waytype_)
+	, cube_corners{ { nw, hmin_ }, { se, hmax_ } }
+	, error()
+{
+	parameter_hash = string_to_hash(param_) & 0x7FFFFFFul;
+}
+
+
+
 scenario_t::scenario_t(karte_t *w) :
 	description_text("get_short_description"),
 	info_text("get_info_text"),
@@ -74,7 +104,7 @@ scenario_t::scenario_t(karte_t *w) :
 scenario_t::~scenario_t()
 {
 	delete script;
-	clear_rules();
+	clear_all_rules();
 	cached_text_files.clear();
 }
 
@@ -188,268 +218,153 @@ void scenario_t::koord_sq2w(koord &k) const
 }
 
 
-const char* scenario_t::get_forbidden_text()
+const char *scenario_t::get_forbidden_text()
 {
 	static cbuffer_t buf;
+
 	buf.clear();
 	buf.append("<h1>Forbidden stuff:</h1><br>");
-	for(uint pnr=0; pnr<MAX_PLAYER_COUNT;  pnr++) {
-		for(uint32 i=0; i<forbidden_tools[pnr].get_count(); i++) {
-			scenario_t::forbidden_t &f = *forbidden_tools[pnr][i];
-			buf.printf("[%d] Player = %d, Tool = %d", i, pnr, f.toolnr);
-			if (f.waytype!=ignore_wt) {
-				buf.printf(", Waytype = %d", f.waytype);
-			}
-			if (f.parameter_hash != 0) {
-				buf.printf(", Default_parameter_hash = \"%ld\", ", f.parameter_hash);
-			}
-			if (f.type == forbidden_t::forbid_tool_rect) {
-				if (-128<f.hmin ||  f.hmax<127) {
-					buf.printf(", Cube = (%s,%d) x ", f.pos_nw.get_str(), f.hmin);
-					buf.printf("(%s,%d)", f.pos_se.get_str(), f.hmax);
-				}
-				else {
-					buf.printf(", Rect = (%s) x ", f.pos_nw.get_str());
-					buf.printf("(%s)", f.pos_se.get_str());
-				}
-			}
-			buf.printf("<br>");
-		}
-	}
-	return buf;
-}
 
+	for(uint32 i=0; i< this->rule_stack.get_count(); i++) {
+		const tool_rule_t &r = rule_stack[i];
 
-sint32 scenario_t::forbidden_t::diff(const forbidden_t& other) const
-{
-	sint32 diff = (sint32)type - (sint32)other.type;
-	if (diff == 0) {
-		diff = (sint32)toolnr - (sint32)other.toolnr;
-		if (diff == 0) {
-			// trick is waytype invalid is at end => finding also all previous waytypes first
-			diff = (sint32)waytype - (sint32)other.waytype;
-			if (diff == 0) {
-				// trick is emopty hash is at end => finding also all previous hashes first
-				diff = (sint32)parameter_hash - (sint32)other.parameter_hash;
-				if (diff == 0  &&  type == forbidden_t::allow_tool_rect) {
-					diff = pos_nw.x - other.pos_nw.x;
-					if (diff == 0) {
-						diff = pos_nw.y - other.pos_nw.y;
-						if (diff == 0) {
-							diff = (pos_nw.x - pos_se.x) * (pos_nw.y - pos_se.y) - (other.pos_nw.x - other.pos_se.x) * (other.pos_nw.y - other.pos_se.y);
-						}
-					}
+		buf.printf("[%d] ", i);
+
+		if (r.player_mask) {
+			buf.append("Players=(");
+			int n = 0;
+			for (uint32 p = 0; p < MAX_PLAYER_COUNT; ++p) {
+				if (n++ > 0) buf.printf(", ");
+				if (r.player_mask & (1 << p)) {
+					buf.append(world()->get_player(p)->get_name());
 				}
 			}
+			buf.append("), ");
 		}
-	}
-	return diff;
-}
 
+		buf.printf("Tool = %d", r.toolnr);
 
-bool scenario_t::forbidden_t::operator <(const forbidden_t &other) const
-{
-	return diff(other) < 0;
-}
+		if (r.waytype!=ignore_wt) {
+			buf.printf(", Waytype = %d", r.waytype);
+		}
 
+		if (r.parameter_hash != tool_rule_t::EMPTY_HASH) {
+			buf.printf(", Default_parameter_hash = \"%ld\", ", r.parameter_hash);
+		}
 
-bool scenario_t::forbidden_t::operator ==(const forbidden_t &other) const
-{
-	bool eq = diff(other)==0;
-	if (eq  &&  type==forbid_tool_rect) {
-		eq = eq && (hmin == other.hmin) && (hmax == other.hmax);
-		eq = eq && (pos_nw == other.pos_nw);
-		eq = eq && (pos_se == other.pos_se);
+		if (r.type == tool_rule_t::rule_type::FORBID_IN_CUBE) {
+			if (std::min(r.cube_corners[0].z, r.cube_corners[1].z) > -128 ||
+				std::max(r.cube_corners[0].z, r.cube_corners[1].z) < +127)
+			{
+				buf.printf(", Cube = (%hu,%hu,%hhu) x (%hu,%hu,%hhu)",
+					r.cube_corners[0].x, r.cube_corners[0].y, r.cube_corners[0].z,
+					r.cube_corners[1].x, r.cube_corners[1].y, r.cube_corners[1].z
+				);
+			}
+			else {
+				buf.printf(", Rect = (%hu,%hu) x (%hu,%hu)",
+					r.cube_corners[0].x, r.cube_corners[0].y,
+					r.cube_corners[1].x, r.cube_corners[1].y
+				);
+			}
+		}
+		buf.printf("<br>");
 	}
-	return eq;
-}
-
 
-scenario_t::forbidden_t::forbidden_t(const forbidden_t& other) :
-	type(other.type), toolnr(other.toolnr),
-	waytype(other.waytype), parameter_hash(other.parameter_hash), pos_nw(other.pos_nw), pos_se(other.pos_se),
-	hmin(other.hmin), hmax(other.hmax), error(other.error)
-{
+	return buf;
 }
 
 
-void scenario_t::forbidden_t::rotate90(const sint16 y_size)
+void tool_rule_t::rotate90(const sint16 y_size)
 {
 	switch(type) {
-		case forbid_tool_rect: {
-			pos_nw.rotate90(y_size);
-			pos_se.rotate90(y_size);
-			sint16 x = pos_nw.x; pos_nw.x = pos_se.x; pos_se.x = x;
+		case rule_type::FORBID_IN_CUBE: {
+			cube_corners[0].rotate90(y_size);
+			cube_corners[1].rotate90(y_size);
+
+			const sint16 tmp = cube_corners[0].x;
+			cube_corners[0].x = cube_corners[1].x;
+			cube_corners[1].x = tmp;
+			break;
 		}
-		default: ;
+
+		default:
+			break;
 	}
 }
 
 
-uint32 scenario_t::find_first(const forbidden_t &other, uint player_nr) const
+void scenario_t::intern_add_rule(const tool_rule_t &rule, uint8 player_nr)
 {
-	if (forbidden_tools[player_nr].empty()  ||  *forbidden_tools[player_nr].back() < other) {
-		// empty vector, or everything is smaller
-		return forbidden_tools[player_nr].get_count();
-	}
-	if (other < *forbidden_tools[player_nr][0]) {
-		// everything is larger
-		return forbidden_tools[player_nr].get_count();
-	}
-	else if ( other <= *forbidden_tools[player_nr][0] ) {
-		return 0;
-	}
-	// now: low < other <= high
-	uint32 low = 0, high = forbidden_tools[player_nr].get_count()-1;
-	while(low+1 < high) {
-		uint32 mid = (low+high) / 2;
-		if (*forbidden_tools[player_nr][mid] < other) {
-			low = mid;
-			// now low < other
-		}
-		else {
-			high = mid;
-			// now other <= high
-		}
-	};
-	// still: low < other <= high
-	return high;
+	this->rule_stack.append(rule);
+	this->rule_stack.back().player_mask |= 1 << player_nr;
+	need_toolbar_update = true;
 }
 
 
-// only match type and toolnumber and return frist match (other could follow)
-uint32 scenario_t::find_first_type_tool_wt(const forbidden_t& other, uint player_nr) const
+void scenario_t::intern_remove_rule(const tool_rule_t &rule, uint8 player_nr)
 {
-	if (forbidden_tools[player_nr].empty()) {
-		// empty
-		return 0;
-	}
+	intern_add_rule(rule, player_nr);
 
-	if(forbidden_tools[player_nr].back()->type < other.type  ||  (forbidden_tools[player_nr].back()->type == other.type  &&  forbidden_tools[player_nr].back()->toolnr < other.toolnr)  ) {
-		// everything is smaller
-		return forbidden_tools[player_nr].get_count();
-	}
-	if (forbidden_tools[player_nr][0]->type > other.type  ||  (forbidden_tools[player_nr][0]->type == other.type  &&  forbidden_tools[player_nr][0]->toolnr > other.toolnr)) {
-		// everything is smaller
-		return forbidden_tools[player_nr].get_count();
+	switch (rule.type) {
+		case tool_rule_t::rule_type::ALLOW:          this->rule_stack.back().type = tool_rule_t::rule_type::FORBID;         break;
+		case tool_rule_t::rule_type::FORBID:         this->rule_stack.back().type = tool_rule_t::rule_type::ALLOW;          break;
+		case tool_rule_t::rule_type::ALLOW_IN_CUBE:  this->rule_stack.back().type = tool_rule_t::rule_type::FORBID_IN_CUBE; break;
+		case tool_rule_t::rule_type::FORBID_IN_CUBE: this->rule_stack.back().type = tool_rule_t::rule_type::ALLOW_IN_CUBE;  break;
 	}
-	else if(forbidden_tools[player_nr][0]->type == other.type  &&  forbidden_tools[player_nr][0]->toolnr == other.toolnr  ) {
-		// first is matching
-		return 0;
-	}
-	// now binary search: low < other <= high
-	uint32 low = 0, high = forbidden_tools[player_nr].get_count() - 1;
-	uint32 mid = high;
-	while (low + 1 < high) {
-		mid = (low + high) / 2;
-		sint32 result = forbidden_tools[player_nr][mid]->diff(other);
-		if(result<0) {
-			low = mid;
-			// now low < other
-		}
-		else if(result>0) {
-			high = mid;
-			// now other <= high
-		}
-		else {
-			// exact match
-			return mid;
-		}
-	};
-	// did we find something?
-	if (forbidden_tools[player_nr][high]->toolnr == other.toolnr  &&  forbidden_tools[player_nr][high]->type == other.type) {
-		return high;
-	}
-	return forbidden_tools[player_nr].get_count();
 }
 
 
-void scenario_t::intern_forbid(forbidden_t* test, uint player_nr, bool add_rule)
+void scenario_t::intern_compress_rules()
 {
-	bool changed = false;
-	forbidden_t::forbid_type type = test->type;
-	if (test->waytype < 0) {
-		test->waytype = 0;
-	}
-
-	bool current_add = add_rule;
-	for (int i=0; i<1+add_rule; i++) {
-		if(add_rule  &&  type!=forbidden_t::forbid_tool) {
-			if (type == forbidden_t::allow_tool_rect) {
-				// before adding an allow rule, remove a identical forbid rule
-				test->type = (i == 0) ? forbidden_t::forbid_tool_rect : forbidden_t::allow_tool_rect;
-			}
-			else if (type == forbidden_t::forbid_tool_rect) {
-				// before adding a forbind rule, remove a identical allowed rule
-				test->type = (i == 0) ? forbidden_t::allow_tool_rect : forbidden_t::forbid_tool_rect;
-			}
-			current_add = i; // first pass remove, next pass add
-		}
-
-		for (uint32 i = find_first(*test, player_nr); i < forbidden_tools[player_nr].get_count() && *forbidden_tools[player_nr][i] <= *test; i++) {
-			if (*test == *forbidden_tools[player_nr][i]) {
-				// entry exists already
-				delete test;
-				if (!current_add) {
-					delete forbidden_tools[player_nr][i];
-					forbidden_tools[player_nr].remove_at(i);
-					changed = true;
-				}
-				goto end;
-			}
-		}
-	}
-	// entry does not exist
-	if (add_rule) {
-		forbidden_tools[player_nr].insert_ordered(test, scenario_t::forbidden_t::compare);
-		changed = true;
-	}
-end:
-	if (changed  &&  type==forbidden_t::forbid_tool) {
-		need_toolbar_update = true;
-	}
+	// TODO
 }
 
-void scenario_t::call_forbid_tool(forbidden_t *test, uint player_nr, bool forbid)
+
+void scenario_t::call_forbid_tool(tool_rule_t *test, uint player_nr, bool forbid)
 {
 	if (env_t::server) {
 		// send information over network
 		nwc_scenario_rules_t *nws = new nwc_scenario_rules_t(welt->get_sync_steps() + 1, welt->get_map_counter());
-		nws->rule = test;
-		nws->forbid = forbid;
+		nws->rule      = test;
+		nws->forbid    = forbid;
 		nws->player_nr = player_nr;
 		network_send_all(nws, false);
 	}
+	else if (forbid) {
+		// apply immediately
+		intern_add_rule(*test, player_nr);
+	}
 	else {
-		// directly apply
-		intern_forbid(test, player_nr, forbid);
+		intern_remove_rule(*test, player_nr);
 	}
 }
 
+
 void scenario_t::forbid_tool(uint8 player_nr, uint16 tool_id)
 {
-	forbidden_t *test = new forbidden_t(forbidden_t::forbid_tool, tool_id, ignore_wt);
+	tool_rule_t *test = new tool_rule_t(tool_rule_t::rule_type::FORBID, tool_id, ignore_wt);
 	call_forbid_tool(test, player_nr, true);
 }
 
 
 void scenario_t::clear_forbid_tool(uint8 player_nr, uint16 tool_id)
 {
-	forbidden_t *test = new forbidden_t(forbidden_t::forbid_tool, tool_id, ignore_wt);
+	tool_rule_t *test = new tool_rule_t(tool_rule_t::rule_type::FORBID, tool_id, ignore_wt);
 	call_forbid_tool(test, player_nr, false);
 }
 
 
 void scenario_t::forbid_way_tool(uint8 player_nr, uint16 tool_id, waytype_t wt, const char* param)
 {
-	forbidden_t *test = new forbidden_t(forbidden_t::forbid_tool, tool_id, wt, param);
+	tool_rule_t *test = new tool_rule_t(tool_rule_t::rule_type::FORBID, tool_id, wt, param);
 	call_forbid_tool(test, player_nr, true);
 }
 
 
 void scenario_t::clear_forbid_way_tool(uint8 player_nr, uint16 tool_id, waytype_t wt, const char* param)
 {
-	forbidden_t *test = new forbidden_t(forbidden_t::forbid_tool, tool_id, wt, param);
+	tool_rule_t *test = new tool_rule_t(tool_rule_t::rule_type::FORBID, tool_id, wt, param);
 	call_forbid_tool(test, player_nr, false);
 }
 
@@ -479,7 +394,7 @@ void scenario_t::forbid_way_tool_cube(uint8 player_nr, uint16 tool_id, waytype_t
 	sint8 hmin( min(pos_nw_0.z, pos_se_0.z) );
 	sint8 hmax( max(pos_nw_0.z, pos_se_0.z) );
 
-	forbidden_t *test = new forbidden_t(tool_id, wt, param, pos_nw, pos_se, hmin, hmax);
+	tool_rule_t *test = new tool_rule_t(tool_id, wt, param, pos_nw, pos_se, hmin, hmax);
 	test->error = err;
 	call_forbid_tool(test, player_nr, true);
 }
@@ -492,8 +407,8 @@ void scenario_t::clear_way_tool_cube(uint8 player_nr, uint16 tool_id, waytype_t
 	sint8 hmin(min(pos_nw_0.z, pos_se_0.z));
 	sint8 hmax(max(pos_nw_0.z, pos_se_0.z));
 
-	forbidden_t* test = new forbidden_t(tool_id, wt, param, pos_nw, pos_se, hmin, hmax);
-	test->type = allow ? forbidden_t::allow_tool_rect : forbidden_t::forbid_tool_rect;
+	tool_rule_t* test = new tool_rule_t(tool_id, wt, param, pos_nw, pos_se, hmin, hmax);
+	test->type = allow ? tool_rule_t::rule_type::ALLOW_IN_CUBE : tool_rule_t::rule_type::FORBID_IN_CUBE;
 	call_forbid_tool(test, player_nr, false);
 }
 
@@ -505,62 +420,114 @@ void scenario_t::allow_way_tool_cube(uint8 player_nr, uint16 tool_id, waytype_t
 	sint8 hmin( min(pos_nw_0.z, pos_se_0.z) );
 	sint8 hmax( max(pos_nw_0.z, pos_se_0.z) );
 
-	forbidden_t *test = new forbidden_t(tool_id, wt, param, pos_nw, pos_se, hmin, hmax);
-	test->type = forbidden_t::allow_tool_rect;
+	tool_rule_t *test = new tool_rule_t(tool_id, wt, param, pos_nw, pos_se, hmin, hmax);
+	test->type = tool_rule_t::rule_type::ALLOW_IN_CUBE;
 	call_forbid_tool(test, player_nr, true);
 }
 
 
-void scenario_t::clear_rules()
+void scenario_t::clear_all_rules()
 {
-	for (uint pnr = 0; pnr < MAX_PLAYER_COUNT; pnr++) {
-		clear_ptr_vector(forbidden_tools[pnr]);
-	}
+	rule_stack.clear();
 	need_toolbar_update = true;
 }
 
 
 // intern helper function, searches with wildcards
-sint32 scenario_t::matching_rule(const uint8 player_nr, const forbidden_t &test, koord3d pos ) const
+// sint32 scenario_t::matching_rule(const uint8 player_nr, const tool_rule_t &test, koord3d pos ) const
+// {
+// 	if (!forbidden_tools[player_nr].empty()) {
+// 		tool_rule_t test_wildcard = test;
+// 		test_wildcard.parameter_hash = 0; // to find any hash
+// 		test_wildcard.waytype = ignore_wt;
+//
+// 		// find if there is a a wildcard matchiung tool
+// 		for (uint32 i = find_first_type_tool_wt(test_wildcard, player_nr); i < forbidden_tools[player_nr].get_count(); i++) {
+// 			// there is something, we need to test more
+// 			tool_rule_t const& f = *forbidden_tools[player_nr][i];
+// 			if (f.type != test.type  ||  f.toolnr != test.toolnr  ||  f.waytype > test.waytype) {
+// 				// reached end of forbidden tools with this id => done
+// 				break;
+// 			}
+// 			if (f.waytype == ignore_wt  ||  f.waytype == test.waytype) {
+// 				if (f.parameter_hash == tool_rule_t::EMPTY_HASH  ||  f.parameter_hash == test.parameter_hash) {
+// 					// parameter matches too => forbidden
+// 					if (f.type == tool_rule_t::forbid_tool) {
+// 						return i;	// matching rule found
+// 					}
+// 					// need to match retangle
+// 					if (f.pos_nw.x <= pos.x && f.pos_nw.y <= pos.y && pos.x <= f.pos_se.x && pos.y <= f.pos_se.y) {
+// 						// check height
+// 						if (f.hmin <= pos.z && pos.z <= f.hmax) {
+// 							return i;
+// 						}
+// 					}
+// 				}
+// 			}
+// 		}
+// 	}
+// 	return -1;
+// }
+
+
+/// @returns true if @p pos is inside the cube spanned by @p corner1, @p corner2
+static inline bool is_inside_cube(koord3d pos, koord3d corner1, koord3d corner2)
 {
-	if (!forbidden_tools[player_nr].empty()) {
-		forbidden_t test_wildcard = test;
-		test_wildcard.parameter_hash = 0; // to find any hash
-		test_wildcard.waytype = ignore_wt;
-
-		// find if there is a a wildcard matchiung tool
-		for (uint32 i = find_first_type_tool_wt(test_wildcard, player_nr); i < forbidden_tools[player_nr].get_count(); i++) {
-			// there is something, we need to test more
-			forbidden_t const& f = *forbidden_tools[player_nr][i];
-			if (f.type != test.type  ||  f.toolnr != test.toolnr  ||  f.waytype > test.waytype) {
-				// reached end of forbidden tools with this id => done
-				break;
-			}
-			if (f.waytype == ignore_wt  ||  f.waytype == test.waytype) {
-				if (f.parameter_hash == forbidden_t::EMPTY_HASH  ||  f.parameter_hash == test.parameter_hash) {
-					// parameter matches too => forbidden
-					if (f.type == forbidden_t::forbid_tool) {
-						return i;	// matching rule found
-					}
-					// need to match retangle
-					if (f.pos_nw.x <= pos.x && f.pos_nw.y <= pos.y && pos.x <= f.pos_se.x && pos.y <= f.pos_se.y) {
-						// check height
-						if (f.hmin <= pos.z && pos.z <= f.hmax) {
-							return i;
-						}
-					}
-				}
-			}
+	const sint16 min_x = std::min(corner1.x, corner2.x);
+	const sint16 max_x = std::max(corner1.x, corner2.x);
+	const sint16 min_y = std::min(corner1.y, corner2.y);
+	const sint16 max_y = std::max(corner1.y, corner2.y);
+	const sint8  min_z = std::min(corner1.z, corner2.z);
+	const sint8  max_z = std::max(corner1.z, corner2.z);
+
+	return
+		pos.x >= min_x && pos.x <= max_x &&
+		pos.y >= min_y && pos.y <= max_y &&
+		pos.z >= min_z && pos.z <= max_z;
+}
+
+
+const tool_rule_t *scenario_t::find_conflicting_rule(uint8 player_nr, uint16 tool_id, sint16 waytype, const char *param, koord3d pos)
+{
+	const tool_rule_t *conflicting = NULL;
+
+	for (const tool_rule_t &r : this->rule_stack) {
+		if ((r.player_mask & (1 << player_nr)) == 0)                 continue; // does not apply to this player
+		if (r.toolnr != tool_id)                                     continue; // does not apply to this tool
+		if (waytype != waytype_t::ignore_wt && r.waytype != waytype) continue; // does not apply to this waytype
+
+		if (r.parameter_hash && param && string_to_hash(param, strlen(param)) != r.parameter_hash) {
+			continue;
+		}
+
+		if ((r.type == tool_rule_t::rule_type::ALLOW_IN_CUBE || r.type == tool_rule_t::rule_type::FORBID_IN_CUBE) &&
+			!is_inside_cube(pos, r.cube_corners[0], r.cube_corners[1]))
+		{
+			continue; // out of applicable area
+		}
+
+		// Now we actually know that this tool applies
+		// Do not break out of the loop since a different rule might cancel this one
+		if (r.type == tool_rule_t::rule_type::ALLOW || r.type == tool_rule_t::rule_type::ALLOW_IN_CUBE) {
+			conflicting = NULL;
+		}
+		else if (!conflicting) {
+			conflicting = &r; // Use the more general rule first
 		}
 	}
-	return -1;
+
+	return conflicting;
 }
 
 
 
 void scenario_t::clear_player_rules(uint8 player_nr)
 {
-	clear_ptr_vector(forbidden_tools[player_nr]);
+	// rule will be deleted/merged on rule compression
+	for (auto &r : this->rule_stack) {
+		r.player_mask &= ~(1 << player_nr);
+	}
+
 	need_toolbar_update = true;
 }
 
@@ -573,19 +540,11 @@ bool scenario_t::is_tool_allowed(const player_t* player, uint16 tool_id, sint16
 
 	// first test the list
 	uint8 player_nr = player ? player->get_player_nr() : PLAYER_UNOWNED;
-	forbidden_t test(forbidden_t::forbid_tool, tool_id, wt, param);
-	sint32 idx = matching_rule(player_nr, test, koord3d::invalid);
-	if (idx == -1  &&  player_nr != PLAYER_UNOWNED) {
-		// retry as public player
-		player_nr = PLAYER_UNOWNED;
-		idx = matching_rule(player_nr, test, koord3d::invalid);
-	}
-	if (idx >= 0) {
-		// we found a forbidden rule
-		const char* err = forbidden_tools[player_nr][idx]->error.c_str();
-		if (err == NULL) {
-			err = "";
-		}
+	const struct tool_rule_t *rule = find_conflicting_rule(player_nr, tool_id, wt, param, koord3d::invalid);
+
+	if (rule) {
+		const char *err = rule->error.c_str();
+		if (!err) err = "";
 		return false;
 	}
 
@@ -600,7 +559,7 @@ bool scenario_t::is_tool_allowed(const player_t* player, uint16 tool_id, sint16
 }
 
 
-const char* scenario_t::is_work_allowed_here(const player_t* player, uint16 tool_id, sint16 wt, const char* param, koord3d pos)
+const char *scenario_t::is_work_allowed_here(const player_t* player, uint16 tool_id, sint16 wt, const char* param, koord3d pos)
 {
 	if (what_scenario != SCRIPTED  &&  what_scenario != SCRIPTED_NETWORK) {
 		return NULL;
@@ -608,51 +567,15 @@ const char* scenario_t::is_work_allowed_here(const player_t* player, uint16 tool
 
 	// first test for allowed tools
 	uint8 player_nr = player ? player->get_player_nr() : PLAYER_UNOWNED;
-	forbidden_t test(forbidden_t::allow_tool_rect, tool_id, wt, param);
-	sint32 idx = matching_rule(player_nr, test, pos);
-	if (idx == -1 && player_nr != PLAYER_UNOWNED) {
-		// retry as public player
-		player_nr = PLAYER_UNOWNED;
-		idx = matching_rule(player_nr, test, pos);
-	}
-	if (idx == -1) {
-		// not allowed => test for forbidden area
-		player_nr = player ? player->get_player_nr() : PLAYER_UNOWNED;
-		test.type = forbidden_t::forbid_tool;
-		idx = matching_rule(player_nr, test, koord3d::invalid);
-		if (idx == -1 && player_nr != PLAYER_UNOWNED) {
-			// retry as public player
-			player_nr = PLAYER_UNOWNED;
-			idx = matching_rule(player_nr, test, koord3d::invalid);
-		}
-		if (idx >= 0) {
-			// we found a forbidden rule
-			const char* err = forbidden_tools[player_nr][idx]->error.c_str();
-			if (err == NULL) {
-				err = "";
-			}
-			return err;
-		}
-		// not found => test rectangles
-		if (idx == -1) {
-			// not found
-			player_nr = player ? player->get_player_nr() : PLAYER_UNOWNED;
-			test.type = forbidden_t::forbid_tool_rect;
-			idx = matching_rule(player_nr, test, pos);
-			if (idx == -1 && player_nr != PLAYER_UNOWNED) {
-				// retry as public player
-				player_nr = PLAYER_UNOWNED;
-				idx = matching_rule(player_nr, test, pos);
-			}
-			if (idx >= 0) {
-				// we found a forbidden rule
-				const char* err = forbidden_tools[player_nr][idx]->error.c_str();
-				if (err == NULL) {
-					err = "";
-				}
-				return err;
-			}
+
+	const tool_rule_t *rule = find_conflicting_rule(player_nr, tool_id, wt, param, pos);
+
+	if (rule) {
+		const char* err = rule->error.c_str();
+		if (err == NULL) {
+			err = "";
 		}
+		return err;
 	}
 
 	// then call the script
@@ -834,6 +757,8 @@ void scenario_t::step()
 	// update toolbars if necessary
 	if (need_toolbar_update) {
 		tool_t::update_toolbars();
+		intern_compress_rules();
+
 		need_toolbar_update = false;
 
 		// reset active tool if now forbidden
@@ -1082,18 +1007,25 @@ void scenario_t::rdwr(loadsave_t *file)
 
 	// load forbidden tool
 	if (file->is_loading()) {
-		clear_rules();
+		clear_all_rules();
 	}
 
-	for (uint pnr = 0; pnr < MAX_PLAYER_COUNT; pnr++) {
-		uint32 count = forbidden_tools[pnr].get_count();
-		file->rdwr_long(count);
+	if (file->is_version_atleast(124, 4)) {
+		assert(false); // TODO
+	}
+	else {
+		uint32 n = 0;
+		for (uint pnr = 0; pnr < MAX_PLAYER_COUNT; pnr++) {
+			uint32 count = rule_stack.get_count();
+			file->rdwr_long(count);
+
+			for (uint32 i = 0; i < count; i++) {
+				if (file->is_loading()) {
+					rule_stack.append(tool_rule_t{});
+				}
 
-		for (uint32 i = 0; i < count; i++) {
-			if (file->is_loading()) {
-				forbidden_tools[pnr].append(new forbidden_t());
+				rule_stack[n++].rdwr(file);
 			}
-			forbidden_tools[pnr][i]->rdwr(file);
 		}
 	}
 
@@ -1120,10 +1052,8 @@ void scenario_t::rdwr(loadsave_t *file)
 
 void scenario_t::rotate90(const sint16 y_size)
 {
-	for (uint pnr = 0; pnr < MAX_PLAYER_COUNT; pnr++) {
-		for (uint32 i = 0; i < forbidden_tools[pnr].get_count(); i++) {
-			forbidden_tools[pnr][i]->rotate90(y_size);
-		}
+	for (tool_rule_t &r : this->rule_stack) {
+		r.rotate90(y_size);
 	}
 }
 
diff --git src/simutrans/dataobj/scenario.h src/simutrans/dataobj/scenario.h
index f1482af3a..e5cd6c115 100644
--- src/simutrans/dataobj/scenario.h
+++ src/simutrans/dataobj/scenario.h
@@ -24,6 +24,82 @@ class karte_t;
 class schedule_t;
 class depot_t;
 
+
+/**
+ * Struct to store information about forbidden tools
+ *
+ * Necessary in network games: there, the list of forbidden tools
+ * is transferred to clients. Needed to apply conditions to e.g. way-building
+ * tools or to have toolbars reflect allowed tools.
+ */
+struct tool_rule_t
+{
+	static const uint32 EMPTY_HASH = 0;
+
+	enum class rule_type : uint16
+	{
+		FORBID = 1,
+		ALLOW  = 2,
+
+		FORBID_IN_CUBE = 3,
+		ALLOW_IN_CUBE  = 4
+	};
+
+	rule_type type = rule_type::FORBID;
+
+	uint16 player_mask = 0xFFFFu; ///< Bitset  of players that this rule applies to
+
+	/// id of tool to be allowed or forbdden, as set by constructors of classes derived from
+	/// tool_t, @see tool/simtool.h
+	uint16 toolnr = 0;
+
+	/// waytype of tool, @see waytype_t
+	sint16 waytype;
+
+	uint32 parameter_hash = EMPTY_HASH;
+
+	koord3d cube_corners[2];
+
+	/// error message to be displayed if user tries to work with the tool
+	plainstring error;
+
+	/// constructor: forbid tool/etc for a certain player
+	tool_rule_t(rule_type type_ = rule_type::FORBID, uint16 toolnr_ = 0, sint16 waytype_ = ignore_wt, const char *param_ = NULL);
+
+	/// constructor: forbid tool for a certain player at certain locations (and heights)
+	tool_rule_t(uint16 toolnr_, sint16 waytype_, const char *param_, koord nw, koord se, sint8 hmin_=-128, sint8 hmax_=127);
+
+	// copy constructor
+	tool_rule_t(const tool_rule_t&) = default;
+
+	/**
+	 * templated load/save support
+	 */
+	template<class T> void rdwr(T *file)
+	{
+		uint8 t = (uint8)type;
+		file->rdwr_byte(t);
+		type = (rule_type)t;
+
+		file->rdwr_short(toolnr);
+		file->rdwr_short(waytype);
+		file->rdwr_long(parameter_hash);
+
+		sint16 value;
+		value = cube_corners[0].x; file->rdwr_short(value); cube_corners[0].x = value;
+		value = cube_corners[0].y; file->rdwr_short(value); cube_corners[0].y = value;
+		value = cube_corners[0].x; file->rdwr_short(value); cube_corners[1].x = value;
+		value = cube_corners[0].y; file->rdwr_short(value); cube_corners[1].y = value;
+		file->rdwr_byte(cube_corners[0].z);
+		file->rdwr_byte(cube_corners[1].z);
+
+		file->rdwr_str(error);
+	}
+
+	void rotate90(const sint16 y_size);
+};
+
+
 /**
  * @class scenario_t
  * Controls scenarios in connection to a simutrans world.
@@ -75,124 +151,14 @@ private:
 
 	/// @{
 	/// @name Interface to forbid tools in-game
-	/**
-	 * Struct to store information about forbidden tools
-	 *
-	 * Necessary in network games: there, the list of forbidden tools
-	 * is transferred to clients. Needed to apply conditions to e.g. way-building
-	 * tools or to have toolbars reflect allowed tools.
-	 */
-	struct forbidden_t {
-
-		static const uint32 EMPTY_HASH=0;
-
-		enum forbid_type {
-			forbid_tool			= 1,
-			allow_tool_rect 	= 2,
-			forbid_tool_rect	= 3
-		};
-
-		forbid_type type;
-		/// id of tool to be forbidden, as set by constructors of classes derived from
-		/// tool_t, @see tool/simtool.h
-		uint16 toolnr;
-		/// waytype of tool, @see waytype_t
-		sint16 waytype;
-		uint32 parameter_hash;
-		koord pos_nw, pos_se;
-		sint8 hmin, hmax;
-		/// error message to be displayed if user tries to work with the tool
-		plainstring error;
-
-		/// constructor: forbid tool/etc for a certain player
-		forbidden_t(forbid_type type_=forbid_tool, uint16 toolnr_=0, sint16 waytype_= ignore_wt, const char *param_=NULL) :
-			type(type_), toolnr(toolnr_), waytype(waytype_ < 0 ? (sint16)ignore_wt : waytype_),
-			pos_nw(koord::invalid), pos_se(koord::invalid), hmin(-128), hmax(127), error()
-		{
-			if (toolnr == (GENERAL_TOOL|TOOL_SCHEDULE_INS)  ||  toolnr == (GENERAL_TOOL | TOOL_SCHEDULE_ADD)) {
-				// paramter is pointer to binary => not checking
-				parameter_hash = 0;
-				return;
-			}
-			parameter_hash = string_to_hash(param_)&0x7FFFFFFul;
-		}
-
-		/// constructor: forbid tool for a certain player at certain locations (and heights)
-		forbidden_t(uint16 toolnr_, sint16 waytype_, const char *param_, koord nw, koord se, sint8 hmin_=-128, sint8 hmax_=127) :
-			type(forbid_tool_rect), toolnr(toolnr_), waytype(waytype_ < 0 ? (sint16)ignore_wt : waytype_), pos_nw(nw), pos_se(se), hmin(hmin_), hmax(hmax_), error()
-		{
-			parameter_hash = string_to_hash(param_) & 0x7FFFFFFul;
-		}
-
-		// copy constructor
-		forbidden_t(const forbidden_t&);
-
-		/**
-		 * @returns difference
-		 */
-		sint32 diff(const forbidden_t&) const;
-
-		/**
-		 * @returns if this < other, compares: type, playernr, tool, wt, parameter
-		 * DIRTY: (a <= b)  &&  (b <= a)  DOES NOT imply  a == b
-		 */
-		bool operator <(const forbidden_t &) const;
-
-		bool operator <=(const forbidden_t &other) const { return !(other < *this); }
-
-		static bool compare(const forbidden_t *a, const forbidden_t *b)
-		{
-			return a->diff(*b) < 0;
-		}
-
-		/**
-		 * compares everything (including coordinates)
-		 * DIRTY: (a <= b)  &&  (b <= a)  DOES NOT imply  a == b
-		 */
-		bool operator ==(const forbidden_t &other) const;
-
-		/**
-		 * templated load/save support
-		 */
-		template<class T> void rdwr(T *file)
-		{
-			uint8 t = (uint8)type;
-			file->rdwr_byte(t);
-			type= (forbid_type)t;
-			file->rdwr_short(toolnr);
-			file->rdwr_short(waytype);
-			file->rdwr_long(parameter_hash);
-			file->rdwr_short(pos_nw.x); file->rdwr_short(pos_nw.y);
-			file->rdwr_short(pos_se.x); file->rdwr_short(pos_se.y);
-			file->rdwr_byte(hmin);
-			file->rdwr_byte(hmax);
-			file->rdwr_str(error);
-		}
-
-		void rotate90(const sint16 y_size);
-
-	private:
-		const forbidden_t& operator=(const forbidden_t&);
-	};
 
-	/// list of forbidden tools for each player (last is all players)
-	vector_tpl<forbidden_t*>forbidden_tools[MAX_PLAYER_COUNT];
+	/// list of forbidden tools
+	vector_tpl<tool_rule_t> rule_stack;
 
 	/// set to true if rules changed to update toolbars,
 	/// toolbars and active tools will be updated in next call to step()
 	bool need_toolbar_update;
 
-	/**
-	 * helper function:
-	 * 
-	 * @param other given record and the player_nr to test for (or PLAYER_UNOWNED)
-	 * @param player_nr player
-	 * @returns first index i such that
-	 *          forbidden_tools[i-1] < other <= forbidden_tools[i] <= other
-	 *          or returns  forbidden_tools.get_count() if no such index is found
-	 */
-	uint32 find_first(const forbidden_t &other, uint player_nr) const;
-
 	/**
 	 * helper function:
 	 * 
@@ -202,19 +168,13 @@ private:
 	 *          that the type, toolnumber, and waytype matches (but parameter may be wrong)
 	 *          or returns  forbidden_tools.get_count() if no such index is found
 	 */
-	uint32 find_first_type_tool_wt(const forbidden_t& other, uint player_nr) const;
+	uint32 find_first_type_tool_wt(const tool_rule_t& other, uint player_nr) const;
 
-	/**
-	 * Helper function:
-	 * Puts/removes new record into/from forbidden_tools list, checks for identical entries.
-	 * Only call this method from call_forbid_tool(forbidden_t *,bool)
-	 * 
-	 * @param test must be pointer to allocated memory, will be invalid after call
-	 * @param player_nr player
-	 * @param add_rule if true add rule, if false removes rule from list
-	 * @returns value 1 if added rule, and 2 deleted previous rule, return 0 on error
-	 */
-	void intern_forbid(forbidden_t *test, uint player_nr, bool add_rule);
+	/// Helper function: Adds rule to the rule stack, marks the stack as dirty.
+	void intern_add_rule(const tool_rule_t &test, uint8 player_nr);
+	void intern_remove_rule(const tool_rule_t &test, uint8 player_nr);
+
+	void intern_compress_rules();
 
 	/**
 	 * Helper function: works on forbidden_tools directly (if not in network-mode)
@@ -224,11 +184,11 @@ private:
 	 * @param player_nr player
 	 * @param forbid if true forbids, if false allows the record
 	 */
-	void call_forbid_tool(forbidden_t *test, uint player_nr, bool forbid);
+	void call_forbid_tool(tool_rule_t *test, uint player_nr, bool forbid);
 	/// @}
 
-		// internal function, returns the idx of the first matching rule of 0xFFFFFF
-	sint32 matching_rule(const uint8 player, const forbidden_t& test, koord3d pos) const;
+	// internal function, returns the idx of the first matching rule of 0xFFFFFF
+	// sint32 matching_rule(const uint8 player, const tool_rule_t& test, koord3d pos) const;
 
 	/// bit set if player has won / lost
 	uint16 won;
@@ -239,14 +199,13 @@ private:
 	void update_won_lost(uint16 new_won, uint16 new_lost);
 
 public:
-
 	scenario_t(karte_t *w);
 	~scenario_t();
 
 	/**
 	 * Initializes scripted scenario
 	 */
-	const char* init( const char *scenario_base, const char *scenario_name, karte_t *welt );
+	const char *init( const char *scenario_base, const char *scenario_name, karte_t *welt );
 
 	/**
 	 * Load file with translations. Tries to load files in the following order
@@ -329,7 +288,16 @@ public:
 	dynamic_string debug_text;
 	/// @}
 
-	enum {INFO=0, GOAL, RULE, RESULT, ABOUT, SCRIPT_DEBUG, DESCRIPTION, ALL=-1};
+	enum {
+		INFO=0,
+		GOAL,
+		RULE,
+		RESULT,
+		ABOUT,
+		SCRIPT_DEBUG,
+		DESCRIPTION,
+		ALL=-1
+	};
 
 	/**
 	 * Called to update the scenario texts
@@ -452,7 +420,7 @@ public:
 	 * Clears all rules.
 	 * @ingroup squirrel-scen-api
 	 */
-	void clear_rules();
+	void clear_all_rules();
 
 	/**
 	 * Clears all rules for a player selec
@@ -536,6 +504,9 @@ public:
 
 	friend class nwc_scenario_t; ///< to access vm, update_won_lost()
 	friend class nwc_scenario_rules_t; ///< to access forbidden_tool stuff
+
+private:
+	const tool_rule_t *find_conflicting_rule(uint8 player_nr, uint16 tool_id, sint16 waytype, const char *param, koord3d pos);
 };
 
 #endif
diff --git src/simutrans/network/network_cmd_scenario.cc src/simutrans/network/network_cmd_scenario.cc
index 4c7c9b0ec..c456e0691 100644
--- src/simutrans/network/network_cmd_scenario.cc
+++ src/simutrans/network/network_cmd_scenario.cc
@@ -101,7 +101,14 @@ void nwc_scenario_rules_t::do_command(karte_t *welt)
 {
 	scenario_t *scen = welt->get_scenario();
 
-	scen->intern_forbid(rule, player_nr, forbid);
+	if (forbid) {
+		scen->intern_add_rule(*rule, player_nr);
+	}
+	else {
+		scen->intern_remove_rule(*rule, player_nr);
+	}
+
+	delete rule;
 	rule = NULL; // pointer is now invalid
 }
 
@@ -117,11 +124,11 @@ void nwc_scenario_rules_t::rdwr()
 
 
 nwc_scenario_rules_t::nwc_scenario_rules_t(const nwc_scenario_rules_t& nwr)
-: network_broadcast_world_command_t(NWC_SCENARIO_RULES, nwr.get_sync_step(), nwr.get_map_counter())
+	: network_broadcast_world_command_t(NWC_SCENARIO_RULES, nwr.get_sync_step(), nwr.get_map_counter())
 {
 	forbid = nwr.forbid;
 	player_nr = nwr.player_nr;
-	rule = new scenario_t::forbidden_t(*nwr.rule);
+	rule = new tool_rule_t(*nwr.rule);
 }
 
 
diff --git src/simutrans/network/network_cmd_scenario.h src/simutrans/network/network_cmd_scenario.h
index 6d8df0272..4d9ea3fbb 100644
--- src/simutrans/network/network_cmd_scenario.h
+++ src/simutrans/network/network_cmd_scenario.h
@@ -62,14 +62,14 @@ private:
  */
 class nwc_scenario_rules_t : public network_broadcast_world_command_t {
 public:
-	nwc_scenario_rules_t(uint32 sync_step=0, uint32 map_counter=0) : network_broadcast_world_command_t(NWC_SCENARIO_RULES, sync_step, map_counter), rule( new scenario_t::forbidden_t()), forbid(true) { }
+	nwc_scenario_rules_t(uint32 sync_step=0, uint32 map_counter=0) : network_broadcast_world_command_t(NWC_SCENARIO_RULES, sync_step, map_counter), rule( new tool_rule_t()), forbid(true) { }
 	~nwc_scenario_rules_t() { delete rule; }
 
 	void do_command(karte_t *) OVERRIDE;
 	void rdwr() OVERRIDE;
 	network_broadcast_world_command_t* clone(karte_t *) OVERRIDE;
 
-	scenario_t::forbidden_t *rule;
+	tool_rule_t *rule;
 	bool forbid;
 	uint16 player_nr;
 
diff --git src/simutrans/script/api/api_scenario.cc src/simutrans/script/api/api_scenario.cc
index 9fc80a06d..cdda4a248 100644
--- src/simutrans/script/api/api_scenario.cc
+++ src/simutrans/script/api/api_scenario.cc
@@ -373,7 +373,7 @@ void export_scenario(HSQUIRRELVM vm)
 	 * @note Only available in scenario mode.
 	 * @ingroup scen_only
 	 */
-	STATIC register_method(vm, &scenario_t::clear_rules, "clear");
+	STATIC register_method(vm, &scenario_t::clear_all_rules, "clear");
 
 	/**
 	 * Clear all forbidding rules for a selec player
