Index: source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.cpp
===================================================================
--- source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.cpp	(revision 13653)
+++ source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.cpp	(working copy)
@@ -178,9 +178,11 @@
 		/////////////////////////////////////////////////////////////////////////
 		// Terrain elevation
 		wxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Elevation tools"));
-		wxSizer* gridSizer = new wxGridSizer(3);
+		wxSizer* gridSizer = new wxGridSizer(4);
 		gridSizer->Add(Tooltipped(new ToolButton(scenarioEditor.GetToolManager(), this, _("Modify"), _T("AlterElevation")),
 			_("Brush with left mouse buttons to raise terrain,\nright mouse button to lower it")), wxSizerFlags().Expand());
+		gridSizer->Add(Tooltipped(new ToolButton(scenarioEditor.GetToolManager(), this, _("Pike"), _T("PikeElevation")),
+			_("Brush with left mouse buttons to raise terrain,\nright mouse button to lower it")), wxSizerFlags().Expand());
 		gridSizer->Add(Tooltipped(new ToolButton(scenarioEditor.GetToolManager(), this, _("Smooth"), _T("SmoothElevation")),
 			_("Brush with left mouse button to smooth terrain,\nright mouse button to roughen it")), wxSizerFlags().Expand());
 		gridSizer->Add(Tooltipped(new ToolButton(scenarioEditor.GetToolManager(), this, _("Flatten"), _T("FlattenElevation")),
Index: source/tools/atlas/AtlasUI/ScenarioEditor/Tools/PikeElevation.cpp
===================================================================
--- source/tools/atlas/AtlasUI/ScenarioEditor/Tools/PikeElevation.cpp	(revision 0)
+++ source/tools/atlas/AtlasUI/ScenarioEditor/Tools/PikeElevation.cpp	(working copy)
@@ -0,0 +1,116 @@
+/* Copyright (C) 2009 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "precompiled.h"
+
+#include "ScenarioEditor/ScenarioEditor.h"
+#include "Common/Tools.h"
+#include "Common/Brushes.h"
+#include "GameInterface/Messages.h"
+
+using AtlasMessage::Position;
+
+class PikeElevation : public StateDrivenTool<PikeElevation>
+{
+	DECLARE_DYNAMIC_CLASS(PikeElevation);
+
+	Position m_Pos;
+
+public:
+	PikeElevation()
+	{
+		SetState(&Waiting);
+	}
+
+
+	void OnEnable()
+	{
+		g_Brush_Elevation.MakeActive();
+	}
+
+	void OnDisable()
+	{
+		POST_MESSAGE(BrushPreview, (false, Position()));
+	}
+
+
+	struct sWaiting : public State
+	{
+		bool OnMouse(PikeElevation* obj, wxMouseEvent& evt)
+		{
+			if (evt.LeftDown())
+			{
+				obj->m_Pos = Position(evt.GetPosition());
+				SET_STATE(Piking);
+				return true;
+			}
+			else if (evt.Moving())
+			{
+				POST_MESSAGE(BrushPreview, (true, Position(evt.GetPosition())));
+				return true;
+			}
+			else
+			{
+				return false;
+			}
+		}
+	}
+	Waiting;
+
+
+	struct sPiking : public State
+	{
+		void OnEnter(PikeElevation* obj)
+		{
+			POST_MESSAGE(BrushPreview, (true, obj->m_Pos));
+		}
+
+		void OnLeave(PikeElevation*)
+		{
+			ScenarioEditor::GetCommandProc().FinaliseLastCommand();
+		}
+
+		bool OnMouse(PikeElevation* obj, wxMouseEvent& evt)
+		{
+			if (evt.LeftUp())
+			{
+				SET_STATE(Waiting);
+				return true;
+			}
+			else if (evt.Dragging())
+			{
+				wxPoint pos = evt.GetPosition();
+				obj->m_Pos = Position(pos);
+				POST_MESSAGE(BrushPreview, (true, obj->m_Pos));
+				return true;
+			}
+			else
+			{
+				return false;
+			}
+		}
+
+		void OnTick(PikeElevation* obj, float dt)
+		{
+			POST_COMMAND(PikeElevation, (obj->m_Pos, dt*4096.f*g_Brush_Elevation.GetStrength()));
+			obj->m_Pos = Position::Unchanged();
+		}
+	}
+	Piking;
+};
+
+IMPLEMENT_DYNAMIC_CLASS(PikeElevation, StateDrivenTool<PikeElevation>);
Index: source/tools/atlas/GameInterface/Handlers/ElevationHandlers.cpp
===================================================================
--- source/tools/atlas/GameInterface/Handlers/ElevationHandlers.cpp	(revision 13653)
+++ source/tools/atlas/GameInterface/Handlers/ElevationHandlers.cpp	(working copy)
@@ -365,4 +365,89 @@
 };
 END_COMMAND(FlattenElevation)
 
+
+BEGIN_COMMAND(PikeElevation)
+{
+	TerrainArray m_TerrainDelta;
+	ssize_t m_i0, m_j0, m_i1, m_j1; // dirtied tiles (inclusive lower bound, exclusive upper)
+
+	cPikeElevation()
+	{
+		m_TerrainDelta.Init();
+	}
+
+	void MakeDirty()
+	{
+		g_Game->GetWorld()->GetTerrain()->MakeDirty(m_i0, m_j0, m_i1, m_j1, RENDERDATA_UPDATE_VERTICES);
+		CmpPtr<ICmpTerrain> cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
+		if (cmpTerrain)
+			cmpTerrain->MakeDirty(m_i0, m_j0, m_i1, m_j1);
+	}
+
+	void Do()
+	{
+		int amount = (int)msg->amount;
+
+		// If the framerate is very high, 'amount' is often very
+		// small (even zero) so the integer truncation is significant
+		static float roundingError = 0.0;
+		roundingError += msg->amount - (float)amount;
+		if (roundingError >= 1.f)
+		{
+			amount += (int)roundingError;
+			roundingError -= (float)(int)roundingError;
+		}
+
+		static CVector3D previousPosition;
+		g_CurrentBrush.m_Centre = msg->pos->GetWorldSpace(previousPosition);
+		previousPosition = g_CurrentBrush.m_Centre;
+
+		ssize_t x0, y0;
+		g_CurrentBrush.GetBottomLeft(x0, y0);
+		float h = ((float) g_CurrentBrush.m_H-1)/2.f;
+
+		for (ssize_t dy = 0; dy < g_CurrentBrush.m_H; ++dy)
+		{
+			for (ssize_t dx = 0; dx < g_CurrentBrush.m_W; ++dx)
+			{
+				float b = g_CurrentBrush.Get(dx, dy);
+				if (b)
+				{
+					float x = (float) dx - ((float)g_CurrentBrush.m_H-1)/2.f;
+					float y = (float) dy - ((float)g_CurrentBrush.m_W-1)/2.f;
+					float distance = clamp(1 - (float) sqrt(x*x + y*y)/h,0.f,1.f);
+					m_TerrainDelta.RaiseVertex(x0+dx, y0+dy, (int)(amount*distance));
+				}
+			}
+		}
+		m_i0 = x0 - 1;
+		m_j0 = y0 - 1;
+		m_i1 = x0 + g_CurrentBrush.m_W;
+		m_j1 = y0 + g_CurrentBrush.m_H;
+		MakeDirty();
+	}
+
+	void Undo()
+	{
+		m_TerrainDelta.Undo();
+		MakeDirty();
+	}
+
+	void Redo()
+	{
+		m_TerrainDelta.Redo();
+		MakeDirty();
+	}
+
+	void MergeIntoPrevious(cPikeElevation* prev)
+	{
+		prev->m_TerrainDelta.OverlayWith(m_TerrainDelta);
+		prev->m_i0 = std::min(prev->m_i0, m_i0);
+		prev->m_j0 = std::min(prev->m_j0, m_j0);
+		prev->m_i1 = std::max(prev->m_i1, m_i1);
+		prev->m_j1 = std::max(prev->m_j1, m_j1);
+	}
+};
+END_COMMAND(PikeElevation)
+
 }
Index: source/tools/atlas/GameInterface/Messages.h
===================================================================
--- source/tools/atlas/GameInterface/Messages.h	(revision 13653)
+++ source/tools/atlas/GameInterface/Messages.h	(working copy)
@@ -496,6 +496,11 @@
 		((float, amount))
 		);
 
+COMMAND(PikeElevation, MERGE,
+		((Position, pos))
+		((float, amount))
+		);
+
 struct ePaintTerrainPriority { enum { HIGH, LOW }; };
 COMMAND(PaintTerrain, MERGE,
 		((Position, pos))
