Index: binaries/data/mods/public/art/actors/actor.rnc
===================================================================
--- binaries/data/mods/public/art/actors/actor.rnc	(revision 18094)
+++ binaries/data/mods/public/art/actors/actor.rnc	(working copy)
@@ -38,6 +38,7 @@
             element animations {
                 element animation {
                     attribute name { text } &
+                    attribute frequency { xsd:nonNegativeInteger }? &
                     attribute file { text }? &
                     attribute speed { xsd:nonNegativeInteger } &
                     attribute event { xsd:decimal { minInclusive = "0" maxInclusive = "1" } }? &
Index: binaries/data/mods/public/art/actors/actor.rng
===================================================================
--- binaries/data/mods/public/art/actors/actor.rng	(revision 18094)
+++ binaries/data/mods/public/art/actors/actor.rng	(working copy)
@@ -94,6 +94,11 @@
                       <interleave>
                         <attribute name="name"/>
                         <optional>
+                          <attribute name="frequency">
+                            <data type="nonNegativeInteger"/>
+                          </attribute>
+                        </optional>
+                        <optional>
                           <attribute name="file"/>
                         </optional>
                         <attribute name="speed">
Index: binaries/data/mods/public/art/actors/units/athenians/cavalry_javelinist_b.xml
===================================================================
--- binaries/data/mods/public/art/actors/units/athenians/cavalry_javelinist_b.xml	(revision 18094)
+++ binaries/data/mods/public/art/actors/units/athenians/cavalry_javelinist_b.xml	(working copy)
@@ -5,7 +5,6 @@
     <variant frequency="1" name="Base">
       <animations>
         <animation file="quadraped/horse_idle_a.psa" name="Idle" speed="50"/>
-        <animation file="quadraped/horse_walk.psa" name="Walk" speed="120"/>
         <animation file="quadraped/horse_gallop.psa" name="Run" speed="50"/>
         <animation file="quadraped/horse_walk.psa" name="carry_meat" speed="120"/>
         <animation event=".5" file="quadraped/horse_attack_a.psa" name="attack_ranged" speed="20"/>
@@ -15,7 +14,7 @@
       </animations>
       <mesh>skeletal/horse.pmd</mesh>
       <props>
-        <prop actor="units/hellenes/cavalry_javelinist_b_r.xml" attachpoint="rider"/>
+        <prop actor="units/athenians/cavalry_javelinist_b_r.xml" attachpoint="rider"/>
       </props>
     </variant>
   </group>
@@ -43,6 +42,12 @@
         <prop actor="particle/dust_cav.xml" attachpoint="root"/>
       </props>
     </variant>
+    <variant name="Walk">
+      <animations>
+        <animation file="quadraped/horse_walk.psa" frequency="1" name="walk_slow" speed="120"/>
+        <animation file="quadraped/horse_gallop.psa" frequency="1" name="walk_fast" speed="50"/>
+      </animations>
+    </variant>
   </group>
   <material>objectcolor.xml</material>
 </actor>
Index: binaries/data/mods/public/art/actors/units/athenians/cavalry_javelinist_b_r.xml
===================================================================
--- binaries/data/mods/public/art/actors/units/athenians/cavalry_javelinist_b_r.xml	(revision 18094)
+++ binaries/data/mods/public/art/actors/units/athenians/cavalry_javelinist_b_r.xml	(working copy)
@@ -5,7 +5,6 @@
     <variant frequency="100" name="Base">
       <animations>
         <animation file="biped/cavalryidle.psa" name="Idle" speed="50"/>
-        <animation file="biped/cavalryidle.psa" name="Walk" speed="50"/>
         <animation file="biped/rider_gallop.psa" name="Run" speed="40"/>
         <animation event="0.59" file="biped/rider_javelin_atk_a.psa" load="0" name="attack_ranged" speed="50"/>
         <animation event="0.5" file="cavalry/sword/attack/rider_sword_shield_atk_a.psa" name="attack_slaughter" speed="400"/>
@@ -26,6 +25,12 @@
   </group>
   <group>
     <variant frequency="100" name="Idle"/>
+    <variant name="Walk">
+      <animations>
+        <animation file="biped/cavalryidle.psa" name="walk_slow" frequency="1" speed="50"/>
+        <animation file="cavalry/sword/attack/rider_sword_shield_atk_a.psa" name="walk_fast" frequency="1" speed="400"/>
+      </animations>
+    </variant>
     <variant name="attack_slaughter">
       <props>
         <prop actor="props/units/weapons/spartan_sword.xml" attachpoint="r_hand"/>
Index: binaries/data/mods/public/art/variants/variant.rnc
===================================================================
--- binaries/data/mods/public/art/variants/variant.rnc	(revision 18094)
+++ binaries/data/mods/public/art/variants/variant.rnc	(working copy)
@@ -34,6 +34,7 @@
 	element animations {
 		element animation {
 			attribute name { text } &
+			attribute frequency { xsd:nonNegativeInteger }? &
 			attribute file { text }? &
 			attribute speed { xsd:nonNegativeInteger } &
 			attribute event { xsd:decimal { minInclusive = "0" maxInclusive = "1" } }? &
Index: binaries/data/mods/public/art/variants/variant.rng
===================================================================
--- binaries/data/mods/public/art/variants/variant.rng	(revision 18094)
+++ binaries/data/mods/public/art/variants/variant.rng	(working copy)
@@ -83,6 +83,11 @@
 		  <interleave>
 			<attribute name="name"/>
 			<optional>
+				<attribute name="frequency">
+					<data type="nonNegativeInteger"/>
+				</attribute>
+			</optional>
+			<optional>
 			  <attribute name="file"/>
 			</optional>
 			<attribute name="speed">
Index: source/graphics/Model.cpp
===================================================================
--- source/graphics/Model.cpp	(revision 18094)
+++ source/graphics/Model.cpp	(working copy)
@@ -1,4 +1,4 @@
-/* Copyright (C) 2013 Wildfire Games.
+/* Copyright (C) 2016 Wildfire Games.
  * This file is part of 0 A.D.
  *
  * 0 A.D. is free software: you can redistribute it and/or modify
@@ -255,7 +255,7 @@
 /////////////////////////////////////////////////////////////////////////////////////////////////////////////
 // BuildAnimation: load raw animation frame animation from given file, and build a 
 // animation specific to this model
-CSkeletonAnim* CModel::BuildAnimation(const VfsPath& pathname, const CStr& name, float speed, float actionpos, float actionpos2, float soundpos)
+CSkeletonAnim* CModel::BuildAnimation(const VfsPath& pathname, const CStr& name, int frequency, float speed, float actionpos, float actionpos2, float soundpos)
 {
 	CSkeletonAnimDef* def = m_SkeletonAnimManager.GetAnimation(pathname);
 	if (!def)
@@ -263,6 +263,7 @@
 
 	CSkeletonAnim* anim = new CSkeletonAnim();
 	anim->m_Name = name;
+	anim->m_Frequency = frequency;
 	anim->m_AnimDef = def;
 	anim->m_Speed = speed;
 
Index: source/graphics/Model.h
===================================================================
--- source/graphics/Model.h	(revision 18094)
+++ source/graphics/Model.h	(working copy)
@@ -1,4 +1,4 @@
-/* Copyright (C) 2013 Wildfire Games.
+/* Copyright (C) 2016 Wildfire Games.
  * This file is part of 0 A.D.
  *
  * 0 A.D. is free software: you can redistribute it and/or modify
@@ -201,6 +201,7 @@
 	 * animation specific to this model.
 	 * @param pathname animation file to load
 	 * @param name animation name (e.g. "idle")
+	 * @param frequency influences the random choices 
 	 * @param speed animation speed as a factor of the default animation speed
 	 * @param actionpos offset of 'action' event, in range [0, 1]
 	 * @param actionpos2 offset of 'action2' event, in range [0, 1]
@@ -207,7 +208,7 @@
 	 * @param sound offset of 'sound' event, in range [0, 1]
 	 * @return new animation, or NULL on error
 	 */
-	CSkeletonAnim* BuildAnimation(const VfsPath& pathname, const CStr& name, float speed, float actionpos, float actionpos2, float soundpos);
+	CSkeletonAnim* BuildAnimation(const VfsPath& pathname, const CStr& name, int frequency, float speed, float actionpos, float actionpos2, float soundpos);
 
 	/**
 	 * Add a prop to the model on the given point.
Index: source/graphics/ObjectBase.cpp
===================================================================
--- source/graphics/ObjectBase.cpp	(revision 18094)
+++ source/graphics/ObjectBase.cpp	(working copy)
@@ -163,6 +163,8 @@
 				{
 					if (ae.Name == at_name)
 						anim.m_AnimName = ae.Value;
+					else if (ae.Name == at_frequency)
+						anim.m_Frequency = ae.Value.ToInt();
 					else if (ae.Name == at_file)
 						anim.m_FileName = VfsPath("art/animation") / ae.Value.FromUTF8();
 					else if (ae.Name == at_speed)
Index: source/graphics/ObjectBase.h
===================================================================
--- source/graphics/ObjectBase.h	(revision 18094)
+++ source/graphics/ObjectBase.h	(working copy)
@@ -42,9 +42,10 @@
 	struct Anim
 	{
 		// constructor
-		Anim() : m_Speed(1.f), m_ActionPos(-1.f), m_ActionPos2(-1.f), m_SoundPos(-1.f) {}
+		Anim() : m_Frequency(0), m_Speed(1.f), m_ActionPos(-1.f), m_ActionPos2(-1.f), m_SoundPos(-1.f) {}
 		// name of the animation - "Idle", "Run", etc
 		CStr m_AnimName;
+		int m_Frequency;
 		// filename of the animation - manidle.psa, manrun.psa, etc
 		VfsPath m_FileName;
 		// animation speed, as specified in XML actor file
Index: source/graphics/ObjectEntry.cpp
===================================================================
--- source/graphics/ObjectEntry.cpp	(revision 18094)
+++ source/graphics/ObjectEntry.cpp	(working copy)
@@ -161,7 +161,7 @@
 
 		if (!it->second.m_FileName.empty())
 		{
-			CSkeletonAnim* anim = model->BuildAnimation(it->second.m_FileName, name, it->second.m_Speed, it->second.m_ActionPos, it->second.m_ActionPos2, it->second.m_SoundPos);
+			CSkeletonAnim* anim = model->BuildAnimation(it->second.m_FileName, name, it->second.m_Frequency, it->second.m_Speed, it->second.m_ActionPos, it->second.m_ActionPos2, it->second.m_SoundPos);
 			if (anim)
 				m_Animations.insert(std::make_pair(name, anim));
 		}
@@ -248,15 +248,24 @@
 
 CSkeletonAnim* CObjectEntry::GetRandomAnimation(const CStr& animationName) const
 {
-	SkeletonAnimMap::const_iterator lower = m_Animations.lower_bound(animationName);
-	SkeletonAnimMap::const_iterator upper = m_Animations.upper_bound(animationName);
-	size_t count = std::distance(lower, upper);
-	if (count == 0)
-		return NULL;
+	std::vector<CSkeletonAnim*> anims = GetAnimations(animationName);
 
-	size_t id = rand(0, count);
-	std::advance(lower, id);
-	return lower->second;
+	int totalFreq = 0;
+	for (CSkeletonAnim* anim : anims)
+		totalFreq += anim->m_Frequency;
+
+	if (totalFreq == 0)
+		return anims[rand(0, anims.size())];
+
+	int r = rand(0, totalFreq);
+	for (CSkeletonAnim* anim : anims)
+	{
+		r -= anim->m_Frequency;
+		if (r < 0)
+			return anim;
+	}
+	LOGERROR("No animation found");
+	return NULL;
 }
 
 std::vector<CSkeletonAnim*> CObjectEntry::GetAnimations(const CStr& animationName) const
@@ -265,7 +274,23 @@
 
 	SkeletonAnimMap::const_iterator lower = m_Animations.lower_bound(animationName);
 	SkeletonAnimMap::const_iterator upper = m_Animations.upper_bound(animationName);
+
 	for (SkeletonAnimMap::const_iterator it = lower; it != upper; ++it)
 		anims.push_back(it->second);
+
+	if (anims.size() == 0)
+		for (const std::pair<CStr, CSkeletonAnim*>& anim : m_Animations)
+			if (anim.second->m_Frequency > 0)
+				anims.push_back(anim.second);
+
+	if (anims.size() == 0)
+	{
+		SkeletonAnimMap::const_iterator lower = m_Animations.lower_bound("idle");
+		SkeletonAnimMap::const_iterator upper = m_Animations.upper_bound("idle");
+		for (SkeletonAnimMap::const_iterator it = lower; it != upper; ++it)
+			anims.push_back(it->second);
+	}
+
+	ENSURE(anims.size() > 0);
 	return anims;
 }
Index: source/graphics/ObjectEntry.h
===================================================================
--- source/graphics/ObjectEntry.h	(revision 18094)
+++ source/graphics/ObjectEntry.h	(working copy)
@@ -1,4 +1,4 @@
-/* Copyright (C) 2013 Wildfire Games.
+/* Copyright (C) 2016 Wildfire Games.
  * This file is part of 0 A.D.
  *
  * 0 A.D. is free software: you can redistribute it and/or modify
@@ -63,11 +63,20 @@
 
 	std::wstring m_ProjectileModelName;
 
-	// Returns a randomly-chosen animation matching the given name.
-	// If none is found, returns NULL.
+	/**
+	 * Returns a randomly-chosen animation matching the given name.
+	 * The chosen animation is picked randomly from the GetAnimations list
+	 * with the frequencies as weights (if there are any defined).
+	 * This method should always return an animation
+	 */
 	CSkeletonAnim* GetRandomAnimation(const CStr& animationName) const;
 
-	// Returns all the animations matching the given name.
+	/**
+	 * Returns all the animations matching the given name.
+	 * - Prefers the animations names like the animationName
+	 * - Second choice are animations with a frequency
+	 * - Last choice are the Idle animations (which are always added)
+	 */
 	std::vector<CSkeletonAnim*> GetAnimations(const CStr& animationName) const;
 
 	// corresponding model
Index: source/graphics/SkeletonAnim.h
===================================================================
--- source/graphics/SkeletonAnim.h	(revision 18094)
+++ source/graphics/SkeletonAnim.h	(working copy)
@@ -1,4 +1,4 @@
-/* Copyright (C) 2010 Wildfire Games.
+/* Copyright (C) 2016 Wildfire Games.
  * This file is part of 0 A.D.
  *
  * 0 A.D. is free software: you can redistribute it and/or modify
@@ -34,6 +34,8 @@
 public:
 	// the name of the action which uses this animation (e.g. "idle")
 	CStr m_Name;
+	// frequency of the animation
+	int m_Frequency;
 	// the raw animation frame data; may be NULL if this is a static 'animation'
 	CSkeletonAnimDef* m_AnimDef;
 	// speed at which this animation runs, as a factor of the AnimDef default speed
Index: source/graphics/UnitAnimation.cpp
===================================================================
--- source/graphics/UnitAnimation.cpp	(revision 18094)
+++ source/graphics/UnitAnimation.cpp	(working copy)
@@ -1,4 +1,4 @@
-/* Copyright (C) 2012 Wildfire Games.
+/* Copyright (C) 2016 Wildfire Games.
  * This file is part of 0 A.D.
  *
  * 0 A.D. is free software: you can redistribute it and/or modify
@@ -41,7 +41,7 @@
 }
 
 CUnitAnimation::CUnitAnimation(entity_id_t ent, CModel* model, CObjectEntry* object)
-	: m_Entity(ent), m_State("idle"), m_Looping(true),
+	: m_Entity(ent), m_State("idle"), m_AnimationName("idle"), m_Looping(true),
 	  m_Speed(1.f), m_SyncRepeatTime(0.f), m_OriginalSpeed(1.f), m_Desync(0.f)
 {
 	ReloadUnit(model, object);
@@ -56,26 +56,23 @@
 {
 	SModelAnimState state;
 
-	state.anims = object->GetAnimations(m_State);
-
-	if (state.anims.empty())
-		state.anims = object->GetAnimations("idle");
-	ENSURE(!state.anims.empty()); // there must always be an idle animation
-
 	state.model = model;
-	state.animIdx = rand(0, state.anims.size());
+	state.object = object;
+	state.anim = object->GetRandomAnimation(m_State);
 	state.time = 0.f;
 	state.pastLoadPos = false;
 	state.pastActionPos = false;
 	state.pastSoundPos = false;
 
+	ENSURE(state.anim != NULL); // there must always be an idle animation
+
 	m_AnimStates.push_back(state);
 
-	model->SetAnimation(state.anims[state.animIdx], !m_Looping);
+	model->SetAnimation(state.anim, !m_Looping);
 
 	// Detect if this unit has any non-static animations
-	for (size_t i = 0; i < state.anims.size(); i++)
-		if (state.anims[i]->m_AnimDef != NULL)
+	for (CSkeletonAnim* anim : object->GetAnimations(m_State))
+		if (anim->m_AnimDef != NULL)
 			m_AnimStatesAreStatic = false;
 
 	// Recursively add all props
@@ -111,6 +108,7 @@
 	if (name != m_State)
 	{
 		m_State = name;
+		m_AnimationName = name;
 
 		ReloadUnit(m_Model, m_Object);
 	}
@@ -129,13 +127,13 @@
 	// Update all the synced prop models to each coincide with actionTime
 	for (std::vector<SModelAnimState>::iterator it = m_AnimStates.begin(); it != m_AnimStates.end(); ++it)
 	{
-		CSkeletonAnimDef* animDef = it->anims[it->animIdx]->m_AnimDef;
+		CSkeletonAnimDef* animDef = it->anim->m_AnimDef;
 		if (animDef == NULL)
 			continue; // ignore static animations
 
 		float duration = animDef->GetDuration();
 
-		float actionPos = it->anims[it->animIdx]->m_ActionPos;
+		float actionPos = it->anim->m_ActionPos;
 		bool hasActionPos = (actionPos != -1.f);
 
 		if (!hasActionPos)
@@ -162,15 +160,15 @@
 	// Advance all of the prop models independently
 	for (std::vector<SModelAnimState>::iterator it = m_AnimStates.begin(); it != m_AnimStates.end(); ++it)
 	{
-		CSkeletonAnimDef* animDef = it->anims[it->animIdx]->m_AnimDef;
+		CSkeletonAnimDef* animDef = it->anim->m_AnimDef;
 		if (animDef == NULL)
 			continue; // ignore static animations
 
 		float duration = animDef->GetDuration();
 
-		float actionPos = it->anims[it->animIdx]->m_ActionPos;
-		float loadPos = it->anims[it->animIdx]->m_ActionPos2;
-		float soundPos = it->anims[it->animIdx]->m_SoundPos;
+		float actionPos = it->anim->m_ActionPos;
+		float loadPos = it->anim->m_ActionPos2;
+		float soundPos = it->anim->m_SoundPos;
 		bool hasActionPos = (actionPos != -1.f);
 		bool hasLoadPos = (loadPos != -1.f);
 		bool hasSoundPos = (soundPos != -1.f);
@@ -180,7 +178,7 @@
 		if (m_SyncRepeatTime && hasActionPos)
 			speed = duration / m_SyncRepeatTime;
 		else
-			speed = m_Speed * it->anims[it->animIdx]->m_Speed;
+			speed = m_Speed * it->anim->m_Speed;
 
 		// Convert from real time to scaled animation time
 		float advance = time * speed;
@@ -232,17 +230,31 @@
 			// Wrap the timer around
 			it->time = fmod(it->time + advance, duration);
 
-			// If there's a choice of multiple animations, pick a new random one
-			if (it->anims.size() > 1)
+			// Pick a new random animation
+			CSkeletonAnim* anim;
+			if (it->model == m_Model)
 			{
-				size_t newAnimIdx = rand(0, it->anims.size());
-				if (newAnimIdx != it->animIdx)
-				{
-					it->animIdx = newAnimIdx;
-					it->model->SetAnimation(it->anims[it->animIdx], !m_Looping);
-				}
+				// we're handling the root model
+				// choose animations from the complete state
+				anim = it->object->GetRandomAnimation(m_State);
+				m_AnimationName = anim->m_Name;
+				// if we use a new animation name,
+				// update the animations of all non-root models
+				if (it->anim->m_Name != m_AnimationName)
+					for (SModelAnimState animState : m_AnimStates)
+						if (animState.model != m_Model)
+							animState.model->SetAnimation(animState.object->GetRandomAnimation(m_AnimationName));
 			}
+			else
+				// choose animations that match the root
+				anim = it->object->GetRandomAnimation(m_AnimationName);
 
+			if (anim != it->anim)
+			{
+				it->anim = anim;
+				it->model->SetAnimation(anim, !m_Looping);
+			}
+
 			it->pastActionPos = false;
 			it->pastLoadPos = false;
 			it->pastSoundPos = false;
Index: source/graphics/UnitAnimation.h
===================================================================
--- source/graphics/UnitAnimation.h	(revision 18094)
+++ source/graphics/UnitAnimation.h	(working copy)
@@ -1,4 +1,4 @@
-/* Copyright (C) 2011 Wildfire Games.
+/* Copyright (C) 2016 Wildfire Games.
  * This file is part of 0 A.D.
  *
  * 0 A.D. is free software: you can redistribute it and/or modify
@@ -95,8 +95,8 @@
 	struct SModelAnimState
 	{
 		CModel* model;
-		std::vector<CSkeletonAnim*> anims;
-		size_t animIdx;
+		CSkeletonAnim* anim;
+		const CObjectEntry* object;
 		float time;
 		bool pastLoadPos;
 		bool pastActionPos;
@@ -117,6 +117,7 @@
 	CModel* m_Model;
 	const CObjectEntry* m_Object;
 	CStr m_State;
+	CStr m_AnimationName;
 	bool m_Looping;
 	float m_OriginalSpeed;
 	float m_Speed;
