← Back to team overview

widelands-dev team mailing list archive

[Merge] lp:~widelands-dev/widelands/bridges into lp:widelands

 

Benedikt Straub has proposed merging lp:~widelands-dev/widelands/bridges into lp:widelands with lp:~widelands-dev/widelands/ferry as a prerequisite.

Commit message:
Roads where both adjacent triangles are unwalkable are displayed as bridges

Requested reviews:
  Widelands Developers (widelands-dev)
Related bugs:
  Bug #734193 in widelands: "introduce footbridges over water and swamp"
  https://bugs.launchpad.net/widelands/+bug/734193

For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/bridges/+merge/364318

The current road rendering system is too inflexible for this feature. I already had to rework it in the ferry branch to make waterways possible, and I´m using these changes here. So this is a follow-up of the ferries.

Every tribe has its own images for a normal and a busy bridge. Bridges may also be animated. They have a height, which is visible when bobs walk over the bridge.
-- 
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/bridges into lp:widelands.
=== modified file 'data/tribes/atlanteans.lua'
--- data/tribes/atlanteans.lua	2019-03-12 12:00:25 +0000
+++ data/tribes/atlanteans.lua	2019-03-12 12:01:02 +0000
@@ -20,6 +20,10 @@
 --
 --    **animations**: Global animations. Contains subtables for ``frontier`` and ``flag``. Each animation needs the parameters ``pictures`` (table of filenames) and ``hotspot`` (2 integer coordinates), and may also define ``fps`` (integer frames per second).
 --
+--    **bridges**: Contains animations for ``normal_e``, ``normal_se``, ``normal_sw``, ``busy_e``, ``busy_se`` and ``busy_sw``.
+--
+--    **bridge_height**: The height in pixels of each bridge at it's summit at 1x scale.
+--
 --    **roads**: The file paths for the tribe's road textures in 3 subtables ``busy``, ``normal`` and ``waterway``.
 --
 --    **resource_indicators**: The names for the resource indicators. This table contains a subtable for each resource name plus a subtable named "" for no resources. Each subtable is an array, in which the index of each entry is the highest amount of resources the indicator may indicate.
@@ -68,6 +72,34 @@
       }
    },
 
+   bridges = {
+      normal_e = {
+         pictures = path.list_files (dirname .. "images/atlanteans/bridge_normal_e_?.png"),
+         hotspot = { -2, 11 },
+      },
+      normal_se = {
+         pictures = path.list_files (dirname .. "images/atlanteans/bridge_normal_se_?.png"),
+         hotspot = { 5, 2 },
+      },
+      normal_sw = {
+         pictures = path.list_files (dirname .. "images/atlanteans/bridge_normal_sw_?.png"),
+         hotspot = { 36, 3 },
+      },
+      busy_e = {
+         pictures = path.list_files (dirname .. "images/atlanteans/bridge_busy_e_?.png"),
+         hotspot = { -2, 11 },
+      },
+      busy_se = {
+         pictures = path.list_files (dirname .. "images/atlanteans/bridge_busy_se_?.png"),
+         hotspot = { 5, 2 },
+      },
+      busy_sw = {
+         pictures = path.list_files (dirname .. "images/atlanteans/bridge_busy_sw_?.png"),
+         hotspot = { 36, 3 },
+      },
+   },
+   bridge_height = 8,
+
    -- Image file paths for this tribe's road and waterway textures
    roads = {
       busy = {

=== modified file 'data/tribes/barbarians.lua'
--- data/tribes/barbarians.lua	2019-03-12 12:00:25 +0000
+++ data/tribes/barbarians.lua	2019-03-12 12:01:02 +0000
@@ -15,6 +15,34 @@
       }
    },
 
+   bridges = {
+      normal_e = {
+         pictures = path.list_files (dirname .. "images/barbarians/bridge_normal_e_?.png"),
+         hotspot = { -1, 13 },
+      },
+      normal_se = {
+         pictures = path.list_files (dirname .. "images/barbarians/bridge_normal_se_?.png"),
+         hotspot = { 8, 3 },
+      },
+      normal_sw = {
+         pictures = path.list_files (dirname .. "images/barbarians/bridge_normal_sw_?.png"),
+         hotspot = { 41, 3 },
+      },
+      busy_e = {
+         pictures = path.list_files (dirname .. "images/barbarians/bridge_busy_e_?.png"),
+         hotspot = { -1, 13 },
+      },
+      busy_se = {
+         pictures = path.list_files (dirname .. "images/barbarians/bridge_busy_se_?.png"),
+         hotspot = { 8, 3 },
+      },
+      busy_sw = {
+         pictures = path.list_files (dirname .. "images/barbarians/bridge_busy_sw_?.png"),
+         hotspot = { 41, 3 },
+      },
+   },
+   bridge_height = 8,
+
    -- Image file paths for this tribe's road and waterway textures
    roads = {
       busy = {

=== modified file 'data/tribes/empire.lua'
--- data/tribes/empire.lua	2019-03-12 12:00:25 +0000
+++ data/tribes/empire.lua	2019-03-12 12:01:02 +0000
@@ -17,6 +17,34 @@
       }
    },
 
+   bridges = {
+      normal_e = {
+         pictures = path.list_files (dirname .. "images/empire/bridge_normal_e_?.png"),
+         hotspot = { -2, 12 },
+      },
+      normal_se = {
+         pictures = path.list_files (dirname .. "images/empire/bridge_normal_se_?.png"),
+         hotspot = { 5, 2 },
+      },
+      normal_sw = {
+         pictures = path.list_files (dirname .. "images/empire/bridge_normal_sw_?.png"),
+         hotspot = { 36, 3 },
+      },
+      busy_e = {
+         pictures = path.list_files (dirname .. "images/empire/bridge_busy_e_?.png"),
+         hotspot = { -2, 12 },
+      },
+      busy_se = {
+         pictures = path.list_files (dirname .. "images/empire/bridge_busy_se_?.png"),
+         hotspot = { 5, 2 },
+      },
+      busy_sw = {
+         pictures = path.list_files (dirname .. "images/empire/bridge_busy_sw_?.png"),
+         hotspot = { 36, 3 },
+      },
+   },
+   bridge_height = 8,
+
    -- Image file paths for this tribe's road and waterway textures
    roads = {
       busy = {

=== modified file 'data/tribes/frisians.lua'
--- data/tribes/frisians.lua	2019-03-12 12:00:25 +0000
+++ data/tribes/frisians.lua	2019-03-12 12:01:02 +0000
@@ -16,6 +16,34 @@
       }
    },
 
+   bridges = {
+      normal_e = {
+         pictures = path.list_files (dirname .. "images/frisians/bridge_normal_e_?.png"),
+         hotspot = { -2, 12 },
+      },
+      normal_se = {
+         pictures = path.list_files (dirname .. "images/frisians/bridge_normal_se_?.png"),
+         hotspot = { 5, 2 },
+      },
+      normal_sw = {
+         pictures = path.list_files (dirname .. "images/frisians/bridge_normal_sw_?.png"),
+         hotspot = { 36, 3 },
+      },
+      busy_e = {
+         pictures = path.list_files (dirname .. "images/frisians/bridge_busy_e_?.png"),
+         hotspot = { -2, 12 },
+      },
+      busy_se = {
+         pictures = path.list_files (dirname .. "images/frisians/bridge_busy_se_?.png"),
+         hotspot = { 5, 2 },
+      },
+      busy_sw = {
+         pictures = path.list_files (dirname .. "images/frisians/bridge_busy_sw_?.png"),
+         hotspot = { 36, 3 },
+      },
+   },
+   bridge_height = 8,
+
    -- Image file paths for this tribe's road and waterway textures
    roads = {
       busy = {

=== added file 'data/tribes/images/atlanteans/bridge_busy_e_0.png'
Binary files data/tribes/images/atlanteans/bridge_busy_e_0.png	1970-01-01 00:00:00 +0000 and data/tribes/images/atlanteans/bridge_busy_e_0.png	2019-03-12 12:01:02 +0000 differ
=== added file 'data/tribes/images/atlanteans/bridge_busy_se_0.png'
Binary files data/tribes/images/atlanteans/bridge_busy_se_0.png	1970-01-01 00:00:00 +0000 and data/tribes/images/atlanteans/bridge_busy_se_0.png	2019-03-12 12:01:02 +0000 differ
=== added file 'data/tribes/images/atlanteans/bridge_busy_sw_0.png'
Binary files data/tribes/images/atlanteans/bridge_busy_sw_0.png	1970-01-01 00:00:00 +0000 and data/tribes/images/atlanteans/bridge_busy_sw_0.png	2019-03-12 12:01:02 +0000 differ
=== added file 'data/tribes/images/atlanteans/bridge_normal_e_0.png'
Binary files data/tribes/images/atlanteans/bridge_normal_e_0.png	1970-01-01 00:00:00 +0000 and data/tribes/images/atlanteans/bridge_normal_e_0.png	2019-03-12 12:01:02 +0000 differ
=== added file 'data/tribes/images/atlanteans/bridge_normal_se_0.png'
Binary files data/tribes/images/atlanteans/bridge_normal_se_0.png	1970-01-01 00:00:00 +0000 and data/tribes/images/atlanteans/bridge_normal_se_0.png	2019-03-12 12:01:02 +0000 differ
=== added file 'data/tribes/images/atlanteans/bridge_normal_sw_0.png'
Binary files data/tribes/images/atlanteans/bridge_normal_sw_0.png	1970-01-01 00:00:00 +0000 and data/tribes/images/atlanteans/bridge_normal_sw_0.png	2019-03-12 12:01:02 +0000 differ
=== added file 'data/tribes/images/barbarians/bridge_busy_e_0.png'
Binary files data/tribes/images/barbarians/bridge_busy_e_0.png	1970-01-01 00:00:00 +0000 and data/tribes/images/barbarians/bridge_busy_e_0.png	2019-03-12 12:01:02 +0000 differ
=== added file 'data/tribes/images/barbarians/bridge_busy_se_0.png'
Binary files data/tribes/images/barbarians/bridge_busy_se_0.png	1970-01-01 00:00:00 +0000 and data/tribes/images/barbarians/bridge_busy_se_0.png	2019-03-12 12:01:02 +0000 differ
=== added file 'data/tribes/images/barbarians/bridge_busy_sw_0.png'
Binary files data/tribes/images/barbarians/bridge_busy_sw_0.png	1970-01-01 00:00:00 +0000 and data/tribes/images/barbarians/bridge_busy_sw_0.png	2019-03-12 12:01:02 +0000 differ
=== added file 'data/tribes/images/barbarians/bridge_normal_e_0.png'
Binary files data/tribes/images/barbarians/bridge_normal_e_0.png	1970-01-01 00:00:00 +0000 and data/tribes/images/barbarians/bridge_normal_e_0.png	2019-03-12 12:01:02 +0000 differ
=== added file 'data/tribes/images/barbarians/bridge_normal_se_0.png'
Binary files data/tribes/images/barbarians/bridge_normal_se_0.png	1970-01-01 00:00:00 +0000 and data/tribes/images/barbarians/bridge_normal_se_0.png	2019-03-12 12:01:02 +0000 differ
=== added file 'data/tribes/images/barbarians/bridge_normal_sw_0.png'
Binary files data/tribes/images/barbarians/bridge_normal_sw_0.png	1970-01-01 00:00:00 +0000 and data/tribes/images/barbarians/bridge_normal_sw_0.png	2019-03-12 12:01:02 +0000 differ
=== added file 'data/tribes/images/empire/bridge_busy_e_0.png'
Binary files data/tribes/images/empire/bridge_busy_e_0.png	1970-01-01 00:00:00 +0000 and data/tribes/images/empire/bridge_busy_e_0.png	2019-03-12 12:01:02 +0000 differ
=== added file 'data/tribes/images/empire/bridge_busy_se_0.png'
Binary files data/tribes/images/empire/bridge_busy_se_0.png	1970-01-01 00:00:00 +0000 and data/tribes/images/empire/bridge_busy_se_0.png	2019-03-12 12:01:02 +0000 differ
=== added file 'data/tribes/images/empire/bridge_busy_sw_0.png'
Binary files data/tribes/images/empire/bridge_busy_sw_0.png	1970-01-01 00:00:00 +0000 and data/tribes/images/empire/bridge_busy_sw_0.png	2019-03-12 12:01:02 +0000 differ
=== added file 'data/tribes/images/empire/bridge_normal_e_0.png'
Binary files data/tribes/images/empire/bridge_normal_e_0.png	1970-01-01 00:00:00 +0000 and data/tribes/images/empire/bridge_normal_e_0.png	2019-03-12 12:01:02 +0000 differ
=== added file 'data/tribes/images/empire/bridge_normal_se_0.png'
Binary files data/tribes/images/empire/bridge_normal_se_0.png	1970-01-01 00:00:00 +0000 and data/tribes/images/empire/bridge_normal_se_0.png	2019-03-12 12:01:02 +0000 differ
=== added file 'data/tribes/images/empire/bridge_normal_sw_0.png'
Binary files data/tribes/images/empire/bridge_normal_sw_0.png	1970-01-01 00:00:00 +0000 and data/tribes/images/empire/bridge_normal_sw_0.png	2019-03-12 12:01:02 +0000 differ
=== added file 'data/tribes/images/frisians/bridge_busy_e_0.png'
Binary files data/tribes/images/frisians/bridge_busy_e_0.png	1970-01-01 00:00:00 +0000 and data/tribes/images/frisians/bridge_busy_e_0.png	2019-03-12 12:01:02 +0000 differ
=== added file 'data/tribes/images/frisians/bridge_busy_se_0.png'
Binary files data/tribes/images/frisians/bridge_busy_se_0.png	1970-01-01 00:00:00 +0000 and data/tribes/images/frisians/bridge_busy_se_0.png	2019-03-12 12:01:02 +0000 differ
=== added file 'data/tribes/images/frisians/bridge_busy_sw_0.png'
Binary files data/tribes/images/frisians/bridge_busy_sw_0.png	1970-01-01 00:00:00 +0000 and data/tribes/images/frisians/bridge_busy_sw_0.png	2019-03-12 12:01:02 +0000 differ
=== added file 'data/tribes/images/frisians/bridge_normal_e_0.png'
Binary files data/tribes/images/frisians/bridge_normal_e_0.png	1970-01-01 00:00:00 +0000 and data/tribes/images/frisians/bridge_normal_e_0.png	2019-03-12 12:01:02 +0000 differ
=== added file 'data/tribes/images/frisians/bridge_normal_se_0.png'
Binary files data/tribes/images/frisians/bridge_normal_se_0.png	1970-01-01 00:00:00 +0000 and data/tribes/images/frisians/bridge_normal_se_0.png	2019-03-12 12:01:02 +0000 differ
=== added file 'data/tribes/images/frisians/bridge_normal_sw_0.png'
Binary files data/tribes/images/frisians/bridge_normal_sw_0.png	1970-01-01 00:00:00 +0000 and data/tribes/images/frisians/bridge_normal_sw_0.png	2019-03-12 12:01:02 +0000 differ
=== modified file 'src/economy/roadbase.cc'
--- src/economy/roadbase.cc	2019-03-12 12:00:25 +0000
+++ src/economy/roadbase.cc	2019-03-12 12:01:02 +0000
@@ -28,6 +28,8 @@
 #include "logic/map_objects/map_object.h"
 #include "logic/map_objects/tribes/carrier.h"
 #include "logic/map_objects/tribes/tribe_descr.h"
+#include "logic/map_objects/world/terrain_description.h"
+#include "logic/map_objects/world/world.h"
 #include "logic/player.h"
 
 namespace Widelands {
@@ -69,6 +71,68 @@
 	return *flags_[FlagStart];
 }
 
+// This returns true if and only if this is a road that covers the specified edge and
+// both triangles adjacent to that edge are unwalkable
+bool RoadBase::is_bridge(const EditorGameBase& egbase, const FCoords& field, uint8_t dir) const {
+	if (descr().type() != MapObjectType::ROAD) {
+		// waterways can't be bridges...
+		return false;
+	}
+
+	const Map& map = egbase.map();
+
+	FCoords iterate = map.get_fcoords(path_.get_start());
+	const Path::StepVector::size_type nr_steps = path_.get_nsteps();
+	bool found = false;
+	for (Path::StepVector::size_type i = 0; i <= nr_steps; ++i) {
+		if (iterate == field) {
+			if ((i < nr_steps && path_[i] == dir) || (i > 0 && path_[i - 1] == get_reverse_dir(dir))) {
+				found = true;
+				break;
+			}
+			return false;
+		}
+		if (i < nr_steps) {
+			map.get_neighbour(iterate, path_[i], &iterate);
+		}
+	}
+	if (!found) {
+		return false;
+	}
+
+	FCoords fr, fd;
+	switch (dir) {
+		case WALK_SW:
+			fd = field;
+			map.get_ln(field, &fr);
+			break;
+		case WALK_SE:
+			fd = field;
+			fr = field;
+			break;
+		case WALK_NW:
+			map.get_tln(field, &fd);
+			fr = fd;
+			break;
+		case WALK_NE:
+			map.get_trn(field, &fd);
+			map.get_tln(field, &fr);
+			break;
+		case WALK_W:
+			map.get_tln(field, &fd);
+			map.get_ln(field, &fr);
+			break;
+		case WALK_E:
+			map.get_trn(field, &fd);
+			fr = field;
+			break;
+		default:
+			NEVER_HERE();
+	}
+	return (egbase.world().terrain_descr(fd.field->terrain_d()).get_is() & TerrainDescription::Is::kUnwalkable) &&
+			(egbase.world().terrain_descr(fr.field->terrain_r()).get_is() & TerrainDescription::Is::kUnwalkable);
+}
+
 /**
  * Return the cost of getting from fromflag to the other flag.
  */
@@ -107,15 +171,25 @@
 		// mark the road that leads up to this field
 		if (steps > 0) {
 			const Direction dir = get_reverse_dir(path_[steps - 1]);
-			if (dir == WALK_SW || dir == WALK_SE || dir == WALK_E)
-				egbase.set_road(curf, dir, type_);
+			if (dir == WALK_SW || dir == WALK_SE || dir == WALK_E) {
+				uint8_t type = type_;
+				if (is_bridge(egbase, curf, dir)) {
+					type += RoadType::kBridgeNormal - RoadType::kNormal;
+				}
+				egbase.set_road(curf, dir, type);
+			}
 		}
 
 		// mark the road that leads away from this field
 		if (steps < path_.get_nsteps()) {
 			const Direction dir = path_[steps];
-			if (dir == WALK_SW || dir == WALK_SE || dir == WALK_E)
-				egbase.set_road(curf, dir, type_);
+			if (dir == WALK_SW || dir == WALK_SE || dir == WALK_E) {
+				uint8_t type = type_;
+				if (is_bridge(egbase, curf, dir)) {
+					type += RoadType::kBridgeNormal - RoadType::kNormal;
+				}
+				egbase.set_road(curf, dir, type);
+			}
 
 			map.get_neighbour(curf, dir, &curf);
 		}

=== modified file 'src/economy/roadbase.h'
--- src/economy/roadbase.h	2019-03-12 12:00:25 +0000
+++ src/economy/roadbase.h	2019-03-12 12:01:02 +0000
@@ -68,6 +68,8 @@
 		return type_;
 	}
 
+	bool is_bridge(const EditorGameBase&, const FCoords&, uint8_t) const;
+
 	int32_t get_size() const override;
 	bool get_passable() const override;
 	PositionList get_positions(const EditorGameBase&) const override;

=== modified file 'src/graphic/gl/road_program.cc'
--- src/graphic/gl/road_program.cc	2019-03-12 12:00:25 +0000
+++ src/graphic/gl/road_program.cc	2019-03-12 12:01:02 +0000
@@ -79,12 +79,8 @@
 		visible_owner = end.owner;
 	}
 
-	const Image& texture =
-		road_type == Widelands::RoadType::kNormal ?
-			visible_owner->tribe().road_textures().get_normal_texture(
-				start.geometric_coords, direction) : road_type == Widelands::RoadType::kWaterway ?
-			visible_owner->tribe().road_textures().get_waterway_texture(start.geometric_coords, direction) :
-				visible_owner->tribe().road_textures().get_busy_texture(start.geometric_coords, direction);
+	const Image& texture = visible_owner->tribe().road_textures().get_texture(
+			road_type, start.geometric_coords, direction);
 	if (*gl_texture == 0) {
 		*gl_texture = texture.blit_data().texture_id;
 	}
@@ -156,7 +152,8 @@
 		// Road to right neighbor.
 		if (field.rn_index != FieldsToDraw::kInvalidIndex) {
 			const Widelands::RoadType road = static_cast<Widelands::RoadType>(field.road_e);
-			if (road != Widelands::RoadType::kNone) {
+			if (road != Widelands::RoadType::kNone && road != Widelands::RoadType::kBridgeNormal &&
+					road != Widelands::RoadType::kBridgeBusy) {
 				add_road(renderbuffer_width, renderbuffer_height, field,
 				         fields_to_draw.at(field.rn_index), scale, road, kEast, &gl_texture);
 			}
@@ -165,7 +162,8 @@
 		// Road to bottom right neighbor.
 		if (field.brn_index != FieldsToDraw::kInvalidIndex) {
 			const Widelands::RoadType road = static_cast<Widelands::RoadType>(field.road_se);
-			if (road != Widelands::RoadType::kNone) {
+			if (road != Widelands::RoadType::kNone && road != Widelands::RoadType::kBridgeNormal &&
+					road != Widelands::RoadType::kBridgeBusy) {
 				add_road(renderbuffer_width, renderbuffer_height, field,
 				         fields_to_draw.at(field.brn_index), scale, road, kSouthEast, &gl_texture);
 			}
@@ -174,7 +172,8 @@
 		// Road to bottom left neighbor.
 		if (field.bln_index != FieldsToDraw::kInvalidIndex) {
 			const Widelands::RoadType road = static_cast<Widelands::RoadType>(field.road_sw);
-			if (road != Widelands::RoadType::kNone) {
+			if (road != Widelands::RoadType::kNone && road != Widelands::RoadType::kBridgeNormal &&
+					road != Widelands::RoadType::kBridgeBusy) {
 				add_road(renderbuffer_width, renderbuffer_height, field,
 				         fields_to_draw.at(field.bln_index), scale, road, kSouthWest, &gl_texture);
 			}

=== modified file 'src/logic/map_objects/bob.cc'
--- src/logic/map_objects/bob.cc	2019-02-27 17:19:00 +0000
+++ src/logic/map_objects/bob.cc	2019-03-12 12:01:02 +0000
@@ -27,6 +27,7 @@
 #include "base/macros.h"
 #include "base/math.h"
 #include "base/wexception.h"
+#include "economy/roadbase.h"
 #include "economy/route.h"
 #include "economy/transfer.h"
 #include "graphic/rendertarget.h"
@@ -704,34 +705,47 @@
 	const float triangle_w = kTriangleWidth * scale;
 	const float triangle_h = kTriangleHeight * scale;
 
+	bool bridge = false;
 	switch (walking_) {
 	case WALK_NW:
 		map.get_brn(end, &start);
 		spos.x += triangle_w / 2.f;
 		spos.y += triangle_h;
+		bridge = end.field->road_southeast == RoadType::kBridgeNormal ||
+				end.field->road_southeast == RoadType::kBridgeBusy; 
 		break;
 	case WALK_NE:
 		map.get_bln(end, &start);
 		spos.x -= triangle_w / 2.f;
 		spos.y += triangle_h;
+		bridge = end.field->road_southwest == RoadType::kBridgeNormal ||
+				end.field->road_southwest == RoadType::kBridgeBusy; 
 		break;
 	case WALK_W:
 		map.get_rn(end, &start);
 		spos.x += triangle_w;
+		bridge = end.field->road_east == RoadType::kBridgeNormal ||
+				end.field->road_east == RoadType::kBridgeBusy; 
 		break;
 	case WALK_E:
 		map.get_ln(end, &start);
 		spos.x -= triangle_w;
+		bridge = start.field->road_east == RoadType::kBridgeNormal ||
+				start.field->road_east == RoadType::kBridgeBusy;
 		break;
 	case WALK_SW:
 		map.get_trn(end, &start);
 		spos.x += triangle_w / 2.f;
 		spos.y -= triangle_h;
+		bridge = start.field->road_southwest == RoadType::kBridgeNormal ||
+				start.field->road_southwest == RoadType::kBridgeBusy;
 		break;
 	case WALK_SE:
 		map.get_tln(end, &start);
 		spos.x -= triangle_w / 2.f;
 		spos.y -= triangle_h;
+		bridge = start.field->road_southeast == RoadType::kBridgeNormal ||
+				start.field->road_southeast == RoadType::kBridgeBusy;
 		break;
 
 	case IDLE:
@@ -749,6 +763,10 @@
 		   static_cast<float>(game.get_gametime() - walkstart_) / (walkend_ - walkstart_), 0.f, 1.f);
 		epos.x = f * epos.x + (1.f - f) * spos.x;
 		epos.y = f * epos.y + (1.f - f) * spos.y;
+		if (bridge) {
+			epos.y -= game.player(end.field->get_owned_by()).tribe().bridge_height() * scale *
+					(1 - 4 * (f - 0.5f) * (f - 0.5f));
+		}
 	}
 	return epos;
 }

=== modified file 'src/logic/map_objects/tribes/road_textures.cc'
--- src/logic/map_objects/tribes/road_textures.cc	2019-03-12 12:00:25 +0000
+++ src/logic/map_objects/tribes/road_textures.cc	2019-03-12 12:01:02 +0000
@@ -17,10 +17,25 @@
  *
  */
 
+#include "base/wexception.h"
 #include "logic/map_objects/tribes/road_textures.h"
 
 #include <memory>
 
+const Image& RoadTextures::get_texture(
+		const Widelands::RoadType type, const Widelands::Coords& coords, int direction) const {
+	switch (type) {
+		case Widelands::RoadType::kNormal:
+			return get_normal_texture(coords, direction);
+		case Widelands::RoadType::kBusy:
+			return get_busy_texture(coords, direction);
+		case Widelands::RoadType::kWaterway:
+			return get_waterway_texture(coords, direction);
+		default:
+			NEVER_HERE();
+	}
+}
+
 const Image& RoadTextures::get_normal_texture(const Widelands::Coords& coords,
                                               int direction) const {
 	return *normal_textures_.at((coords.x + coords.y + direction) % normal_textures_.size());
@@ -45,3 +60,4 @@
 void RoadTextures::add_waterway_texture(const Image* image) {
 	waterway_textures_.emplace_back(image);
 }
+

=== modified file 'src/logic/map_objects/tribes/road_textures.h'
--- src/logic/map_objects/tribes/road_textures.h	2019-03-12 12:00:25 +0000
+++ src/logic/map_objects/tribes/road_textures.h	2019-03-12 12:01:02 +0000
@@ -24,6 +24,7 @@
 #include <vector>
 
 #include "graphic/image.h"
+#include "logic/roadtype.h"
 #include "logic/widelands_geometry.h"
 
 // Simple container to give access of the road textures of a tribe.
@@ -34,6 +35,7 @@
 	const Image& get_normal_texture(const Widelands::Coords& coords, int direction) const;
 	const Image& get_busy_texture(const Widelands::Coords& coords, int direction) const;
 	const Image& get_waterway_texture(const Widelands::Coords& coords, int direction) const;
+	const Image& get_texture(const Widelands::RoadType type, const Widelands::Coords& coords, int direction) const;
 
 	// Adds a new road texture.
 	void add_normal_road_texture(const Image* texture);

=== modified file 'src/logic/map_objects/tribes/tribe_descr.cc'
--- src/logic/map_objects/tribes/tribe_descr.cc	2019-03-12 12:00:25 +0000
+++ src/logic/map_objects/tribes/tribe_descr.cc	2019-03-12 12:01:02 +0000
@@ -58,7 +58,8 @@
 TribeDescr::TribeDescr(const LuaTable& table,
                        const Widelands::TribeBasicInfo& info,
                        const Tribes& init_tribes)
-   : name_(table.get_string("name")), descname_(info.descname), tribes_(init_tribes) {
+   : name_(table.get_string("name")), descname_(info.descname), tribes_(init_tribes),
+     bridge_height_(table.get_int("bridge_height")) {
 
 	try {
 		initializations_ = info.initializations;
@@ -67,6 +68,14 @@
 		frontier_animation_id_ = g_gr->animations().load(*items_table->get_table("frontier"));
 		flag_animation_id_ = g_gr->animations().load(*items_table->get_table("flag"));
 
+		items_table = table.get_table("bridges");
+		bridge_e_animation_normal_id_ = g_gr->animations().load(*items_table->get_table("normal_e"));
+		bridge_se_animation_normal_id_ = g_gr->animations().load(*items_table->get_table("normal_se"));
+		bridge_sw_animation_normal_id_ = g_gr->animations().load(*items_table->get_table("normal_sw"));
+		bridge_e_animation_busy_id_ = g_gr->animations().load(*items_table->get_table("busy_e"));
+		bridge_se_animation_busy_id_ = g_gr->animations().load(*items_table->get_table("busy_se"));
+		bridge_sw_animation_busy_id_ = g_gr->animations().load(*items_table->get_table("busy_sw"));
+
 		items_table = table.get_table("roads");
 		const auto load_roads = [&items_table](
 		                           const std::string& road_type, std::vector<std::string>* images) {
@@ -369,6 +378,19 @@
 	return flag_animation_id_;
 }
 
+uint32_t TribeDescr::bridge_animation(uint8_t dir, bool busy) const {
+	switch (dir) {
+		case WALK_E: return busy ? bridge_e_animation_busy_id_ : bridge_e_animation_normal_id_;
+		case WALK_SE: return busy ? bridge_se_animation_busy_id_ : bridge_se_animation_normal_id_;
+		case WALK_SW: return busy ? bridge_sw_animation_busy_id_ : bridge_sw_animation_normal_id_;
+		default: NEVER_HERE();
+	}
+}
+
+uint32_t TribeDescr::bridge_height() const {
+	return bridge_height_;
+}
+
 const std::vector<std::string>& TribeDescr::normal_road_paths() const {
 	return normal_road_paths_;
 }

=== modified file 'src/logic/map_objects/tribes/tribe_descr.h'
--- src/logic/map_objects/tribes/tribe_descr.h	2019-03-12 12:00:25 +0000
+++ src/logic/map_objects/tribes/tribe_descr.h	2019-03-12 12:01:02 +0000
@@ -130,6 +130,10 @@
 
 	uint32_t frontier_animation() const;
 	uint32_t flag_animation() const;
+	uint32_t bridge_animation(uint8_t dir, bool busy) const;
+
+	// Bridge height in pixels at 1x scale, for drawing bobs walking over a bridge
+	uint32_t bridge_height() const;
 
 	// A vector of all texture images that can be used for drawing a
 	// (normal|busy) road or a waterway. The images are guaranteed to exist.
@@ -191,6 +195,13 @@
 
 	uint32_t frontier_animation_id_;
 	uint32_t flag_animation_id_;
+	uint32_t bridge_e_animation_normal_id_;
+	uint32_t bridge_se_animation_normal_id_;
+	uint32_t bridge_sw_animation_normal_id_;
+	uint32_t bridge_e_animation_busy_id_;
+	uint32_t bridge_se_animation_busy_id_;
+	uint32_t bridge_sw_animation_busy_id_;
+	uint32_t bridge_height_;
 	std::vector<std::string> normal_road_paths_;
 	std::vector<std::string> busy_road_paths_;
 	std::vector<std::string> waterway_paths_;

=== modified file 'src/logic/roadtype.h'
--- src/logic/roadtype.h	2019-03-12 12:00:25 +0000
+++ src/logic/roadtype.h	2019-03-12 12:01:02 +0000
@@ -29,6 +29,8 @@
 	kNormal = 1,
 	kBusy = 2,
 	kWaterway = 3,
+	kBridgeNormal = 4,
+	kBridgeBusy = 5,
 };
 
 }  // namespace Widelands

=== modified file 'src/wui/interactive_player.cc'
--- src/wui/interactive_player.cc	2019-03-12 12:00:25 +0000
+++ src/wui/interactive_player.cc	2019-03-12 12:01:02 +0000
@@ -353,6 +353,21 @@
 					}
 				}
 			}
+			if (f->road_e == Widelands::RoadType::kBridgeNormal || f->road_e == Widelands::RoadType::kBridgeBusy) {
+				dst->blit_animation(f->rendertarget_pixel, scale, f->owner->tribe().bridge_animation(
+						Widelands::WALK_E, f->road_e == Widelands::RoadType::kBridgeBusy),
+						f->vision == 1 ? 0 : gametime, f->owner->get_playercolor());
+			}
+			if (f->road_sw == Widelands::RoadType::kBridgeNormal || f->road_sw == Widelands::RoadType::kBridgeBusy) {
+				dst->blit_animation(f->rendertarget_pixel, scale, f->owner->tribe().bridge_animation(
+						Widelands::WALK_SW, f->road_sw == Widelands::RoadType::kBridgeBusy),
+						f->vision == 1 ? 0 : gametime, f->owner->get_playercolor());
+			}
+			if (f->road_se == Widelands::RoadType::kBridgeNormal || f->road_se == Widelands::RoadType::kBridgeBusy) {
+				dst->blit_animation(f->rendertarget_pixel, scale, f->owner->tribe().bridge_animation(
+						Widelands::WALK_SE, f->road_se == Widelands::RoadType::kBridgeBusy),
+						f->vision == 1 ? 0 : gametime, f->owner->get_playercolor());
+			}
 
 			draw_border_markers(*f, scale, *fields_to_draw, dst);
 

=== modified file 'src/wui/interactive_spectator.cc'
--- src/wui/interactive_spectator.cc	2019-03-12 12:00:25 +0000
+++ src/wui/interactive_spectator.cc	2019-03-12 12:01:02 +0000
@@ -127,6 +127,22 @@
 	for (size_t idx = 0; idx < fields_to_draw->size(); ++idx) {
 		const FieldsToDraw::Field& field = fields_to_draw->at(idx);
 
+		if (field.road_e == Widelands::RoadType::kBridgeNormal || field.road_e == Widelands::RoadType::kBridgeBusy) {
+			dst->blit_animation(field.rendertarget_pixel, scale, field.owner->tribe().bridge_animation(
+					Widelands::WALK_E, field.road_e == Widelands::RoadType::kBridgeBusy),
+					gametime, field.owner->get_playercolor());
+		}
+		if (field.road_sw == Widelands::RoadType::kBridgeNormal || field.road_sw == Widelands::RoadType::kBridgeBusy) {
+			dst->blit_animation(field.rendertarget_pixel, scale, field.owner->tribe().bridge_animation(
+					Widelands::WALK_SW, field.road_sw == Widelands::RoadType::kBridgeBusy),
+					gametime, field.owner->get_playercolor());
+		}
+		if (field.road_se == Widelands::RoadType::kBridgeNormal || field.road_se == Widelands::RoadType::kBridgeBusy) {
+			dst->blit_animation(field.rendertarget_pixel, scale, field.owner->tribe().bridge_animation(
+					Widelands::WALK_SE, field.road_se == Widelands::RoadType::kBridgeBusy),
+					gametime, field.owner->get_playercolor());
+		}
+
 		draw_border_markers(field, scale, *fields_to_draw, dst);
 
 		Widelands::BaseImmovable* const imm = field.fcoords.field->get_immovable();


Follow ups