- Status
- Offline
- Joined
- Mar 3, 2026
- Messages
- 606
- Reaction score
- 7
Why stick to supercars when you can mount a Mountain Lion? Found this snippet for an animal riding system adapted for the latest builds. The original author was struggling with frozen entities, but the logic here handles it by ditching standard AI tasks in favor of manual velocity injection.
The Technical Approach
Most researchers fail because they try to use TASK_WANDER_PED or standard movement natives while a player is attached. In most cases, the engine kills the AI pathfinding the moment the player becomes a parent entity. This source bypasses that by calculating forward and strafe vectors based on player input and applying SET_ENTITY_VELOCITY directly to the animal's handle.
Key Features:
Movement Logic Breakdown
When the entity is mounted, the script captures the raw control normals. To prevent the "frozen" state, the script clears current tasks every frame and forces the velocity. This is the only way to ensure responsive movement in a solo or private session without the game's internal animal AI trying to override your inputs.
Full code:
Implementation Notes:
— You need a FiberPool or a similar task scheduler to handle the STREAMING requests and yields.
— The animation used (missarmenian1driving_taunts@lamar_1) is a placeholder but works surprisingly well for the mounting pose.
— If you use this in public sessions, expect a quick manual review or desync issues with other players seeing you hover.
Anyone tried porting this to a networked environment with proper sync trees yet?
The Technical Approach
Most researchers fail because they try to use TASK_WANDER_PED or standard movement natives while a player is attached. In most cases, the engine kills the AI pathfinding the moment the player becomes a parent entity. This source bypasses that by calculating forward and strafe vectors based on player input and applying SET_ENTITY_VELOCITY directly to the animal's handle.
Key Features:
- Velocity-based movement (W/S for forward/back, A/D for steering).
- Auto-mounting via ATTACH_ENTITY_TO_ENTITY with custom Z-offsets per animal.
- Protection layer: God mode, anti-ragdoll, and blocking non-temporary events.
- Supported animals: Deer, Boar, Cow, Mountain Lion, Coyote, Panther, Chop, and even marine life like Dolphins and Orcas.
Code:
static const std::vector<AnimalDef> s_Animals = {
{"Deer", 0xD86B5A95, 0.30f},
{"Boar", 0xCE5FF074, 0.30f},
{"Cow", 0xFCFA9E1E, 0.50f},
{"Mountain Lion", 0x1250D7BA, 0.25f},
{"Coyote", 0x644AC75E, 0.25f},
{"Panther", 0xE71D5E68, 0.25f},
{"Chop", 0x14EC17EA, 0.20f},
{"Dolphin", 0x8BBAB455, 0.30f},
{"Killer Whale", 0x8D8AC8B9, 0.55f},
{"Humpback", 0x471BE4B2, 0.65f}
};
Movement Logic Breakdown
When the entity is mounted, the script captures the raw control normals. To prevent the "frozen" state, the script clears current tasks every frame and forces the velocity. This is the only way to ensure responsive movement in a solo or private session without the game's internal animal AI trying to override your inputs.
Code:
float rad = heading * 3.14159265f / 180.0f;
const float forwardSpeed = 4.5f;
const float strafeInfluence = 1.25f;
float vx = (std::sin(-rad) * forwardSpeed * moveInput) + (std::cos(-rad) * side * strafeInfluence);
float vy = (std::cos(-rad) * forwardSpeed * moveInput) - (std::sin(-rad) * side * strafeInfluence);
ENTITY::SET_ENTITY_VELOCITY(animal, vx, vy, currentVelocity.z);
Full code:
Code:
// ============================================================
// Animal Rider - Mount System
// ============================================================
struct AnimalDef
{
const char* name;
uint32_t hash;
float mountZ;
};
static const std::vector<AnimalDef> s_Animals = {
{"Deer", 0xD86B5A95, 0.30f},
{"Boar", 0xCE5FF074, 0.30f},
{"Cow", 0xFCFA9E1E, 0.50f},
{"Mountain Lion", 0x1250D7BA, 0.25f},
{"Coyote", 0x644AC75E, 0.25f},
{"Panther", 0xE71D5E68, 0.25f},
{"Chop", 0x14EC17EA, 0.20f},
{"Dolphin", 0x8BBAB455, 0.30f},
{"Killer Whale", 0x8D8AC8B9, 0.55f},
{"Humpback", 0x471BE4B2, 0.65f},
};
static int s_ActiveAnimal = 0;
static bool s_Dismounting = false;
static bool s_AnimalGodMode = true;
static std::string s_StatusMsg = "Pick an animal to ride.";
static constexpr const char* MOUNT_ANIM_DICT = "missarmenian1driving_taunts@lamar_1";
static constexpr const char* MOUNT_ANIM_NAME = "cmonmy[removed]";
static bool IsControlPressedEither(int pad, int control)
{
return PAD::IS_CONTROL_PRESSED(pad, control) || PAD::IS_DISABLED_CONTROL_PRESSED(pad, control);
}
static bool IsControlJustPressedEither(int pad, int control)
{
return PAD::IS_CONTROL_JUST_PRESSED(pad, control) || PAD::IS_DISABLED_CONTROL_JUST_PRESSED(pad, control);
}
static void ProtectAnimal(int animal)
{
if (!ENTITY:OES_ENTITY_EXIST(animal))
return;
ENTITY::SET_ENTITY_INVINCIBLE(animal, TRUE, FALSE);
PED::SET_BLOCKING_OF_NON_TEMPORARY_EVENTS(animal, TRUE);
PED::SET_PED_CAN_RAGDOLL(animal, FALSE);
PED::SET_PED_CONFIG_FLAG(animal, 202, TRUE);
int maxHp = ENTITY::GET_ENTITY_MAX_HEALTH(animal);
ENTITY::SET_ENTITY_HEALTH(animal, maxHp, 0, 0);
}
static void Dismount()
{
if (s_Dismounting || s_ActiveAnimal == 0)
return;
s_Dismounting = true;
int animal = s_ActiveAnimal;
s_ActiveAnimal = 0;
s_StatusMsg = "Dismounted. Pick an animal to ride again.";
FiberPool::Push([animal] {
auto selfPed = Self::GetPed();
int pid = selfPed.GetHandle();
auto pos = selfPed.GetPosition();
ENTITY:ETACH_ENTITY(pid, TRUE, TRUE);
ScriptMgr::Yield(80ms);
ENTITY::SET_ENTITY_COORDS(pid, pos.x, pos.y, pos.z + 0.5f, FALSE, FALSE, FALSE, FALSE);
ScriptMgr::Yield(80ms);
TASK::CLEAR_PED_TASKS_IMMEDIATELY(pid);
ScriptMgr::Yield(80ms);
if (ENTITY:OES_ENTITY_EXIST(animal))
{
ENTITY::SET_ENTITY_AS_MISSION_ENTITY(animal, TRUE, TRUE);
ScriptMgr::Yield(50ms);
if (ENTITY:OES_ENTITY_EXIST(animal))
PED:ELETE_PED(&const_cast<int&>(animal));
}
s_Dismounting = false;
Notifications::Show("Animal Rider", "Dismounted.", NotificationType::Info, 2000);
});
}
static void SpawnAndMount(const AnimalDef& def)
{
if (s_Dismounting)
{
Notifications::Show("Animal Rider", "Still dismounting, wait...", NotificationType::Warning);
return;
}
if (s_ActiveAnimal != 0)
{
Notifications::Show("Animal Rider", "Already riding! Press F to dismount.", NotificationType::Warning);
return;
}
FiberPool::Push([def] {
auto selfPed = Self::GetPed();
int pid = selfPed.GetHandle();
auto pos = selfPed.GetPosition();
float heading = selfPed.GetHeading();
uint32_t hash = def.hash;
// Load model
STREAMING::REQUEST_MODEL(hash);
int t = 0;
while (!STREAMING::HAS_MODEL_LOADED(hash) && t < 200)
{
ScriptMgr::Yield();
t++;
}
if (!STREAMING::HAS_MODEL_LOADED(hash))
{
Notifications::Show("Animal Rider", std::string("Failed to load: ") + def.name, NotificationType::Error);
STREAMING::SET_MODEL_AS_NO_LONGER_NEEDED(hash);
return;
}
// Spawn in front of player
float rad = heading * 3.14159265f / 180.0f;
float spawnX = pos.x + std::sin(-rad) * 2.0f;
float spawnY = pos.y + std::cos(-rad) * 2.0f;
int animal = PED::CREATE_PED(28, hash, spawnX, spawnY, pos.z, heading, TRUE, FALSE);
ScriptMgr::Yield(50ms);
if (!ENTITY:OES_ENTITY_EXIST(animal))
{
Notifications::Show("Animal Rider", std::string("Failed to spawn: ") + def.name, NotificationType::Error);
STREAMING::SET_MODEL_AS_NO_LONGER_NEEDED(hash);
return;
}
// Freeze & protect before anything
ENTITY::FREEZE_ENTITY_POSITION(animal, TRUE);
ProtectAnimal(animal);
ENTITY::SET_ENTITY_AS_MISSION_ENTITY(animal, TRUE, TRUE);
ENTITY::SET_ENTITY_LOD_DIST(animal, 9999);
ENTITY::SET_ENTITY_VISIBLE(animal, TRUE, FALSE);
// Teleport player to spawn position
ENTITY::FREEZE_ENTITY_POSITION(pid, TRUE);
ENTITY::SET_ENTITY_COORDS(pid, spawnX, spawnY, pos.z, FALSE, FALSE, FALSE, FALSE);
s_ActiveAnimal = animal;
// Attach player to animal
ENTITY::ATTACH_ENTITY_TO_ENTITY(
pid, animal, -1,
0.0f, 0.1f, def.mountZ,
0.0f, 0.0f, 0.0f,
FALSE, FALSE, FALSE, TRUE, 2, TRUE, FALSE);
ScriptMgr::Yield(100ms);
// Play riding animation
STREAMING::REQUEST_ANIM_DICT(MOUNT_ANIM_DICT);
t = 0;
while (!STREAMING::HAS_ANIM_DICT_LOADED(MOUNT_ANIM_DICT) && t < 200)
{
ScriptMgr::Yield();
t++;
}
TASK::TASK_PLAY_ANIM(pid, MOUNT_ANIM_DICT, MOUNT_ANIM_NAME, 8.0f, 1.0f, -1, 2, 1.0f, FALSE, FALSE, FALSE);
// Unfreeze
ENTITY::FREEZE_ENTITY_POSITION(animal, FALSE);
ENTITY::FREEZE_ENTITY_POSITION(pid, FALSE);
STREAMING::SET_MODEL_AS_NO_LONGER_NEEDED(hash);
s_StatusMsg = std::string("Riding ") + def.name + " | W = Move F = Dismount";
Notifications::Show("Animal Rider", std::string("Riding ") + def.name + "! Press F to dismount.", NotificationType::Success);
});
}
// Called from looped command to handle controls
void AnimalRiderTick()
{
if (s_ActiveAnimal == 0 || s_Dismounting)
return;
int animal = s_ActiveAnimal;
int pid = Self::GetPed().GetHandle();
// Protect animal every frame
if (s_AnimalGodMode)
ProtectAnimal(animal);
// Keep visible
ENTITY::SET_ENTITY_VISIBLE(animal, TRUE, FALSE);
ENTITY::SET_ENTITY_LOD_DIST(animal, 9999);
// Check death
bool animalDead = !ENTITY:OES_ENTITY_EXIST(animal) || ENTITY::IS_ENTITY_DEAD(animal, FALSE);
bool pidDead = ENTITY::IS_ENTITY_DEAD(pid, FALSE);
if (animalDead || pidDead)
{
s_ActiveAnimal = 0;
s_StatusMsg = "Dismounted (death).";
ENTITY:ETACH_ENTITY(pid, TRUE, TRUE);
TASK::CLEAR_PED_TASKS_IMMEDIATELY(pid);
FiberPool::Push([animal] {
ScriptMgr::Yield(300ms);
if (ENTITY:OES_ENTITY_EXIST(animal))
{
ENTITY::SET_ENTITY_AS_MISSION_ENTITY(animal, TRUE, TRUE);
PED:ELETE_PED(&const_cast<int&>(animal));
}
});
return;
}
// F to dismount (control 23)
if (IsControlJustPressedEither(0, 23))
{
Dismount();
return;
}
if (ENTITY:OES_ENTITY_EXIST(animal))
{
const bool forwardPressed = IsControlPressedEither(0, 32);
const bool backwardPressed = IsControlPressedEither(0, 33);
const float side = PAD::GET_CONTROL_NORMAL(0, 30);
float heading = ENTITY::GET_ENTITY_HEADING(animal);
if (std::fabs(side) > 0.01f)
{
heading += side * 4.0f;
ENTITY::SET_ENTITY_HEADING(animal, heading);
}
Vector3 currentVelocity = ENTITY::GET_ENTITY_VELOCITY(animal);
float moveInput = 0.0f;
if (forwardPressed)
moveInput += 1.0f;
if (backwardPressed)
moveInput -= 0.65f;
if (std::fabs(moveInput) > 0.01f)
{
// Direct velocity works more reliably here than AI walk tasks while mounted.
TASK::CLEAR_PED_TASKS_IMMEDIATELY(animal);
float rad = heading * 3.14159265f / 180.0f;
const float forwardSpeed = 4.5f;
const float strafeInfluence = 1.25f;
float vx = (std::sin(-rad) * forwardSpeed * moveInput) + (std::cos(-rad) * side * strafeInfluence);
float vy = (std::cos(-rad) * forwardSpeed * moveInput) - (std::sin(-rad) * side * strafeInfluence);
ENTITY::SET_ENTITY_VELOCITY(animal, vx, vy, currentVelocity.z);
}
else
{
ENTITY::SET_ENTITY_VELOCITY(animal, currentVelocity.x * 0.25f, currentVelocity.y * 0.25f, currentVelocity.z);
}
}
}
// ============================================================
// UI Builder
// ============================================================
std::shared_ptr<Category> BuildFunOptionsMenu()
{
auto menu = std::make_shared<Category>("Fun Options");
// --- Existing Fun Commands ---
auto funGroup = std::make_shared<Group>("Fun Toggles");
funGroup->AddItem(std::make_shared<BoolCommandItem>("selfdrunkmode"_J));
funGroup->AddItem(std::make_shared<BoolCommandItem>("selfragdollmode"_J));
funGroup->AddItem(std::make_shared<BoolCommandItem>("selfspinningcar"_J));
funGroup->AddItem(std::make_shared<BoolCommandItem>("selfrainbowcar"_J));
// --- Animal Rider ---
auto riderGroup = std::make_shared<Group>("Animal Rider");
riderGroup->AddItem(std::make_shared<ImGuiItem>([] {
if (s_ActiveAnimal != 0)
ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), "%s", s_StatusMsg.c_str());
else if (s_Dismounting)
ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.1f, 1.0f), "Dismounting, please wait...");
else
ImGui::TextColored(ImVec4(0.9f, 0.8f, 0.2f, 1.0f), "%s", s_StatusMsg.c_str());
ImGui::Separator();
ImGui::Spacing();
if (s_ActiveAnimal != 0)
{
// God mode toggle
if (s_AnimalGodMode)
ImGui::TextColored(ImVec4(0.3f, 1.0f, 0.3f, 1.0f), "Mount God Mode: ON");
else
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "Mount God Mode: OFF");
if (ImGui::Button("Toggle God Mode", ImVec2(ImGui::GetContentRegionAvail().x, 0)))
{
s_AnimalGodMode = !s_AnimalGodMode;
if (ENTITY:OES_ENTITY_EXIST(s_ActiveAnimal))
{
ENTITY::SET_ENTITY_INVINCIBLE(s_ActiveAnimal, s_AnimalGodMode ? TRUE : FALSE, FALSE);
PED::SET_PED_CAN_RAGDOLL(s_ActiveAnimal, s_AnimalGodMode ? FALSE : TRUE);
}
}
ImGui::Spacing();
ImGui::Text("W / Left / Right - Move & steer");
ImGui::Text("F - Dismount");
ImGui::Spacing();
if (ImGui::Button("Dismount Now", ImVec2(ImGui::GetContentRegionAvail().x, 0)))
Dismount();
}
else if (!s_Dismounting)
{
// God mode toggle (pre-spawn)
if (s_AnimalGodMode)
ImGui::TextColored(ImVec4(0.3f, 1.0f, 0.3f, 1.0f), "Mount God Mode: ON");
else
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "Mount God Mode: OFF");
if (ImGui::Button("Toggle God Mode##pre", ImVec2(ImGui::GetContentRegionAvail().x, 0)))
s_AnimalGodMode = !s_AnimalGodMode;
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
ImGui::Text("Spawn an animal to ride:");
ImGui::Spacing();
for (size_t i = 0; i < s_Animals.size(); i++)
{
const auto& def = s_Animals[i];
std::string label = std::string(def.name) + "##a" + std::to_string(i);
if (ImGui::Button(label.c_str(), ImVec2(ImGui::GetContentRegionAvail().x, 0)))
{
SpawnAndMount(def);
}
}
ImGui::Spacing();
ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "Spawns at your position and auto-mounts.");
}
}));
menu->AddItem(funGroup);
menu->AddItem(riderGroup);
return menu;
}
Implementation Notes:
— You need a FiberPool or a similar task scheduler to handle the STREAMING requests and yields.
— The animation used (missarmenian1driving_taunts@lamar_1) is a placeholder but works surprisingly well for the mounting pose.
— If you use this in public sessions, expect a quick manual review or desync issues with other players seeing you hover.
Anyone tried porting this to a networked environment with proper sync trees yet?