Squad Spawner (#1250)

This commit is contained in:
DayibBaba 2023-04-23 17:23:00 +02:00 committed by GitHub
parent de251b2e57
commit 5299fe52ef
16 changed files with 1155 additions and 30 deletions

View File

@ -3,7 +3,7 @@ include(FetchContent)
FetchContent_Declare(
gtav_classes
GIT_REPOSITORY https://github.com/Yimura/GTAV-Classes.git
GIT_TAG 4f76d41ff4c39c296606238cbfa9fd4bceee3a54
GIT_TAG 57548ccd8742f0355fc674e8f4dedee119d640e5
GIT_PROGRESS TRUE
CONFIGURE_COMMAND ""
BUILD_COMMAND ""

View File

@ -7,6 +7,7 @@
#include "services/context_menu/context_menu_service.hpp"
#include "services/orbital_drone/orbital_drone.hpp"
#include "services/vehicle/vehicle_control_service.hpp"
#include "services/squad_spawner/squad_spawner.hpp"
#include "thread_pool.hpp"
@ -217,4 +218,14 @@ namespace big
script::get_current()->yield();
}
}
void backend::squad_spawner()
{
while (true)
{
g_squad_spawner_service.tick();
script::get_current()->yield();
}
}
}

View File

@ -20,5 +20,6 @@ namespace big
static void world_loop();
static void orbital_drone();
static void vehicle_control();
static void squad_spawner();
};
}

View File

@ -1919,3 +1919,16 @@ enum class eVehicleSeats
OUTSIDE_LEFT,
OUTSIDE_RIGHT,
};
enum class eCombatAbilityLevel{
POOR,
AVERAGE,
PROFESSIONAL
};
NLOHMANN_JSON_SERIALIZE_ENUM(eCombatAbilityLevel,
{
{eCombatAbilityLevel::POOR, "poor"},
{eCombatAbilityLevel::AVERAGE, "average"},
{eCombatAbilityLevel::PROFESSIONAL, "professional"}
})

View File

@ -22,6 +22,8 @@ namespace big
static void nav_item(std::pair<tabs, navigation_struct>&, int);
static void input_text_with_hint(const std::string_view label, const std::string_view hint, char* buf, size_t buf_size, ImGuiInputTextFlags_ flag = ImGuiInputTextFlags_None, std::function<void()> cb = nullptr);
static void input_text_with_hint(const std::string_view label, const std::string_view hint, std::string* buf, ImGuiInputTextFlags_ flag = ImGuiInputTextFlags_None, std::function<void()> cb = nullptr);
static void input_text(const std::string_view label, char* buf, size_t buf_size, ImGuiInputTextFlags_ flag = ImGuiInputTextFlags_None, std::function<void()> cb = nullptr);
static bool selectable(const std::string_view, bool);

View File

@ -1,5 +1,6 @@
#include "fiber_pool.hpp"
#include "gui/components/components.hpp"
#include "misc/cpp/imgui_stdlib.h"
#include "natives.hpp"
namespace big
@ -15,4 +16,16 @@ namespace big
PAD::DISABLE_ALL_CONTROL_ACTIONS(0);
});
}
void components::input_text_with_hint(const std::string_view label, const std::string_view hint, std::string* buf, ImGuiInputTextFlags_ flag, std::function<void()> cb)
{
if (ImGui::InputTextWithHint(label.data(), hint.data(), buf, flag))
if (cb)
g_fiber_pool->queue_job(std::move(cb));
if (ImGui::IsItemActive())
g_fiber_pool->queue_job([] {
PAD::DISABLE_ALL_CONTROL_ACTIONS(0);
});
}
}

View File

@ -26,8 +26,10 @@
#include "services/player_database/player_database_service.hpp"
#include "services/players/player_service.hpp"
#include "services/script_patcher/script_patcher_service.hpp"
#include "services/squad_spawner/squad_spawner.hpp"
#include "services/vehicle/handling_service.hpp"
#include "services/vehicle/vehicle_control_service.hpp"
#include "services/squad_spawner/squad_spawner.hpp"
#include "thread_pool.hpp"
#include "version.hpp"
@ -121,6 +123,7 @@ BOOL APIENTRY DllMain(HMODULE hmod, DWORD reason, PVOID)
g_script_mgr.add_script(std::make_unique<script>(&backend::world_loop, "World"));
g_script_mgr.add_script(std::make_unique<script>(&backend::orbital_drone, "Orbital Drone"));
g_script_mgr.add_script(std::make_unique<script>(&backend::vehicle_control, "Vehicle control"));
g_script_mgr.add_script(std::make_unique<script>(&backend::squad_spawner, "Squad spawner"));
g_script_mgr.add_script(std::make_unique<script>(&context_menu_service::context_menu, "Context Menu"));
LOG(INFO) << "Scripts registered.";

View File

@ -28,6 +28,7 @@ namespace big
WORLD,
SPAWN_PED,
SQUAD_SPAWNER,
CREATOR,
TRAIN,
BLACKHOLE,
@ -116,6 +117,7 @@ namespace big
view::world,
{
{tabs::SPAWN_PED, {"GUI_TAB_SPAWN_PED", view::spawn_ped}},
{tabs::SQUAD_SPAWNER, {"Squad spawner", view::squad_spawner}},
{tabs::CREATOR, {"GUI_TAB_CREATOR", view::creator}},
{tabs::TRAIN, {"GUI_TAB_TRAIN", view::train}},
{tabs::BLACKHOLE, {"GUI_TAB_BLACKHOLE", view::blackhole}},

View File

@ -0,0 +1,259 @@
#include "squad_spawner.hpp"
#include "gta/joaat.hpp"
#include "util/math.hpp"
#include "util/pathfind.hpp"
#include "util/ped.hpp"
#include "util/vehicle.hpp"
namespace big
{
squad_member squad_spawner::spawn_squad_member(squad s)
{
auto handle = ped::spawn(ePedType::PED_TYPE_CIVMALE, rage::joaat(s.m_ped_model), 0, s.m_spawn_pos, 0, true);
if (entity::take_control_of(handle))
{
PED::SET_BLOCKING_OF_NON_TEMPORARY_EVENTS(handle, true);
PED::SET_PED_CAN_BE_DRAGGED_OUT(handle, false);
PED::SET_RAGDOLL_BLOCKING_FLAGS(handle, 1 | 16); //Block player ragdoll impacts, bullet and colission
if (s.m_ped_proofs[4])
PED::SET_RAGDOLL_BLOCKING_FLAGS(handle, 128); //Check for melee proof and disable corresponding ragdoll
PED::SET_PED_COMBAT_ATTRIBUTES(handle, 2, true);
PED::SET_PED_COMBAT_ATTRIBUTES(handle, 4, true);
PED::SET_PED_COMBAT_ATTRIBUTES(handle, 5, true);
PED::SET_PED_COMBAT_ATTRIBUTES(handle, 21, true);
PED::SET_PED_COMBAT_ATTRIBUTES(handle, 23, true);
PED::SET_PED_COMBAT_ATTRIBUTES(handle, 25, true);
PED::SET_PED_COMBAT_ATTRIBUTES(handle, 26, true);
PED::SET_PED_COMBAT_ATTRIBUTES(handle, 28, true);
PED::SET_PED_COMBAT_ATTRIBUTES(handle, 46, true);
PED::SET_PED_COMBAT_ATTRIBUTES(handle, 58, true);
PED::SET_PED_COMBAT_ATTRIBUTES(handle, 59, true);
PED::SET_PED_COMBAT_ATTRIBUTES(handle, 69, true);
PED::SET_PED_COMBAT_ATTRIBUTES(handle, 70, true);
PED::SET_PED_CONFIG_FLAG(handle, 26, true);
PED::SET_PED_CONFIG_FLAG(handle, 42, true);
PED::SET_PED_CONFIG_FLAG(handle, 44, true);
PED::SET_PED_CONFIG_FLAG(handle, 45, false);
PED::SET_PED_CONFIG_FLAG(handle, 229, true);
PED::SET_PED_CONFIG_FLAG(handle, 241, true);
PED::SET_PED_CONFIG_FLAG(handle, 268, true);
ENTITY::SET_ENTITY_PROOFS(handle, s.m_ped_proofs[1], s.m_ped_proofs[2], s.m_ped_proofs[4], false, s.m_ped_proofs[3], 0, 0, 0);
PED::SET_PED_SUFFERS_CRITICAL_HITS(handle, !s.m_ped_proofs[0]); //Headshot bool is true to disable, hence the unary prefix '!'
TASK::SET_PED_PATH_MAY_ENTER_WATER(handle, true);
TASK::SET_PED_PATH_PREFER_TO_AVOID_WATER(handle, true);
if (s.should_override_health())
{
PED::SET_PED_MAX_HEALTH(handle, s.m_ped_health);
ENTITY::SET_ENTITY_HEALTH(handle, s.m_ped_health, 0);
}
if (s.should_override_armor())
{
PED::SET_PED_ARMOUR(handle, s.m_ped_armor);
}
if (s.does_squad_have_vehicle() && s.m_weapon_model != "WEAPON_UNARMED")
{
WEAPON::GIVE_WEAPON_TO_PED(handle, rage::joaat("WEAPON_MICROSMG"), 999, false, false);
}
WEAPON::GIVE_WEAPON_TO_PED(handle, rage::joaat(s.m_weapon_model), 999, false, true);
PED::SET_PED_ACCURACY(handle, s.m_ped_accuracy);
PED::SET_PED_COMBAT_ABILITY(handle, (int)s.m_combat_ability_level);
ENTITY::SET_ENTITY_INVINCIBLE(handle, s.m_ped_invincibility);
HUD::SET_PED_HAS_AI_BLIP(handle, true);
HUD::SET_PED_AI_BLIP_FORCED_ON(handle, true);
}
return squad_member(handle, reinterpret_cast<CPed*>(g_pointers->m_gta.m_handle_to_ptr(handle)), -1);
}
std::pair<Vehicle, CVehicle*> squad_spawner::spawn_squad_vehicle(squad s)
{
auto handle = vehicle::spawn(rage::joaat(s.m_vehicle_model), s.m_spawn_pos, s.m_spawn_heading);
VEHICLE::SET_VEHICLE_ON_GROUND_PROPERLY(handle, 5);
ENTITY::SET_ENTITY_INVINCIBLE(handle, s.m_veh_invincibility);
VEHICLE::SET_VEHICLE_ENGINE_ON(handle, true, true, false);
return std::pair<Vehicle, CVehicle*>(handle, reinterpret_cast<CVehicle*>(g_pointers->m_gta.m_handle_to_ptr(handle)));
}
bool squad_spawner::find_suitable_spawn_pos(squad& s)
{
Hash veh_model_hash = rage::joaat(s.m_vehicle_model);
int node_search_flag = (VEHICLE::IS_THIS_MODEL_A_BOAT(veh_model_hash) || VEHICLE::IS_THIS_MODEL_A_JETSKI(veh_model_hash)) ? 2 : 0;
Vector3 new_pos = s.m_spawn_pos;
if (!s.should_override_spawn_distance())
{
switch (s.m_spawn_distance_mode)
{
case eSquadSpawnDistance::CLOSEBY: s.m_spawn_distance = 25.f; break;
case eSquadSpawnDistance::FAR_AWAY: s.m_spawn_distance = 100.f; break;
case eSquadSpawnDistance::ON_TARGET: s.m_spawn_distance = 10.f; break;
case eSquadSpawnDistance::MODERATELY_DISTANCED: s.m_spawn_distance = 70.f; break;
}
}
static auto reset_spawn_pos_to_offset = [&]() -> void {
Ped player_ped_handle = g_pointers->m_gta.m_ptr_to_handle(s.target->get_ped());
s.m_spawn_pos = ENTITY::GET_OFFSET_FROM_ENTITY_IN_WORLD_COORDS(player_ped_handle, 0, -7, 0);
//LOG(INFO) << "Squad spawner: No suitable spot found, spawning at an offset";
g_notification_service->push_warning("Squad Spawner", "No suitable spot found, spawning at an offset");
};
for (int i = 0; i < 10; i++)
{
if (pathfind::find_random_location_in_vicinity_precise(s.m_spawn_pos, new_pos, s.m_spawn_heading, node_search_flag, s.m_spawn_distance, 200))
break;
}
//Reset if all searches failed with an allowance of up to 50.0f
if (math::distance_between_vectors(new_pos, s.m_spawn_pos) > (s.m_spawn_distance + 50.f) || new_pos == s.m_spawn_pos)
{
reset_spawn_pos_to_offset();
return false;
}
s.m_spawn_pos = new_pos;
return true;
}
bool squad_spawner::spawn_squad(squad s, player_ptr target_player, bool override_spawn_pos = false, Vector3 custom_pos = {})
{
s.target = target_player;
if (!s.target->get_net_game_player() || s.m_squad_size < 1 || !STREAMING::IS_MODEL_VALID(rage::joaat(s.m_ped_model)))
{
g_notification_service->push_error("Squad spawner", "Error spawning squad");
return false;
}
if (std::string(s.m_name).empty()){
s.m_name = std::string(std::to_string(s.m_squad_size) + std::string("_").append(s.m_ped_model).append("_").append(std::to_string(s.m_internal_id)));
}
Hash veh_model_hash = rage::joaat(s.m_vehicle_model);
s.current_target_ped = g_pointers->m_gta.m_ptr_to_handle(s.target->get_ped());
float heading;
//Check if squad size is suitable in case a vehicle is defined
if (s.does_squad_have_vehicle())
{
if (VEHICLE::GET_VEHICLE_MODEL_NUMBER_OF_SEATS(veh_model_hash) < s.m_squad_size)
{
s.m_squad_size = VEHICLE::GET_VEHICLE_MODEL_NUMBER_OF_SEATS(veh_model_hash);
g_notification_service->push_warning("Squad Spawner", "The squad vehicle has insufficient seats, decreasing the squad size");
}
}
//Decide spawn location
if (!override_spawn_pos)
{
s.m_spawn_pos = ENTITY::GET_ENTITY_COORDS(s.current_target_ped, true);
if (s.m_spawn_distance_mode == eSquadSpawnDistance::ON_TARGET)
{
s.m_spawn_pos = ENTITY::GET_OFFSET_FROM_ENTITY_IN_WORLD_COORDS(s.current_target_ped, 0.0, -7.f, 0.0);
}
else
{
g_squad_spawner_service.find_suitable_spawn_pos(s);
}
}
else
{
s.m_spawn_pos = custom_pos;
}
if (VEHICLE::IS_THIS_MODEL_A_PLANE(veh_model_hash) || VEHICLE::IS_THIS_MODEL_A_HELI(veh_model_hash))
s.m_spawn_pos.z += 50.f;
//Spawn squad vehicle
if (s.does_squad_have_vehicle())
{
std::pair<Vehicle, CVehicle*> squad_veh = squad_spawner::spawn_squad_vehicle(s);
s.m_veh_handle = squad_veh.first;
s.m_veh_ptr = squad_veh.second;
}
bool veh_spawned = ENTITY::DOES_ENTITY_EXIST(s.m_veh_handle);
//Spawn squad members
const Vector3 original_pos = s.m_spawn_pos;
for (int i = 0; i < s.m_squad_size; i++)
{
//Find random position for each consecutive member if disperse is enabled
if (i > 0 && s.m_disperse && !s.does_squad_have_vehicle())
squad_spawner::find_suitable_spawn_pos(s);
s.m_members.push_back(squad_spawner::spawn_squad_member(s));
//Catch position change of Disperse and revert
s.m_spawn_pos = original_pos;
if (entity::take_control_of(s.m_members[i].handle))
{
if (veh_spawned)
PED::SET_PED_INTO_VEHICLE(s.m_members[i].handle, s.m_veh_handle, (i - 1));
PED::SET_PED_RELATIONSHIP_GROUP_HASH(s.m_members[i].handle, RAGE_JOAAT("HATES_PLAYER"));
squad_spawner::build_and_perform_sequence(s, i);
if (s.m_stay_in_veh)
PED::SET_PED_COMBAT_ATTRIBUTES(s.m_members[i].handle, 3, false);
}
}
if (s.m_spawn_behind_same_velocity && s.does_squad_have_vehicle() && veh_spawned && target_player->get_current_vehicle())
{
Vehicle target_vehicle = g_pointers->m_gta.m_ptr_to_handle(target_player->get_current_vehicle());
if (ENTITY::GET_ENTITY_SPEED(target_vehicle) > 25.f && entity::take_control_of(s.m_veh_handle))
{
Vector3 velocity = ENTITY::GET_ENTITY_VELOCITY(target_vehicle);
Vector3 behindpos = ENTITY::GET_OFFSET_FROM_ENTITY_IN_WORLD_COORDS(target_vehicle, 0.f, -7.f, 0.f);
float heading = ENTITY::GET_ENTITY_HEADING(target_vehicle);
ENTITY::SET_ENTITY_COORDS(s.m_veh_handle, behindpos.x, behindpos.y, behindpos.z, 0, 0, 0, false);
ENTITY::SET_ENTITY_HEADING(s.m_veh_handle, heading);
ENTITY::SET_ENTITY_VELOCITY(s.m_veh_handle, velocity.x, velocity.y, velocity.z);
}
}
if (s.has_squad_spawned())
{
m_active_squads.push_back(s);
return true;
}
return false;
}
void squad_spawner::tick()
{
for (auto& s : m_active_squads)
{
if (s.is_squad_alive())
{
for (auto& m : s.m_members)
{
//This can be used to track attacker progress regarding their task. Anything above -1 means it is in progress.
m.task_sequence_progress = TASK::GET_SEQUENCE_PROGRESS(m.handle);
}
}
else
{
std::erase_if(g_squad_spawner_service.m_active_squads, [s](squad s_) {
return s.get_id() == s_.get_id();
});
}
}
}
}

View File

@ -0,0 +1,226 @@
#pragma once
#include "fiber_pool.hpp"
#include "gta/enums.hpp"
#include "natives.hpp"
namespace big
{
struct squad_member
{
Ped handle;
CPed* ptr;
int task_sequence_progress = -1;
int task_sequence = 0;
~squad_member()
{
TASK::CLEAR_SEQUENCE_TASK(&task_sequence);
}
Vector3 get_pos()
{
rage::fvector3* pos{};
if (ptr && ptr->m_navigation && ptr->m_navigation->get_position())
pos = ptr->m_navigation->get_position();
else
return {};
return {pos->x, pos->y, pos->z};
}
bool is_ped_alive()
{
return ENTITY::DOES_ENTITY_EXIST(handle) && !ENTITY::IS_ENTITY_DEAD(handle, false);
}
};
enum class eSquadSpawnDistance
{
CUSTOM,
ON_TARGET,
CLOSEBY,
MODERATELY_DISTANCED,
FAR_AWAY
};
/*
This struct is ambiguous in its employment, hence the division of its content.
It serves as a template for the UI and an active object to track and modify dynamically.
The constuctor is only meant to initialize its static variables and increment the Id.
*/
struct squad
{
//Static variables
std::string m_name;
std::string m_description;
std::string m_ped_model;
std::string m_weapon_model;
std::string m_vehicle_model;
bool m_ped_invincibility;
bool m_veh_invincibility;
bool m_ped_proofs[5]; // 0 headshot, 1 bullet, 2 flame, 3 melee, 4 explosion
float m_ped_health; //Leave at 0 to default
float m_ped_armor; //Leave at 0 to default
float m_ped_accuracy;
float m_spawn_distance;
int m_squad_size;
eSquadSpawnDistance m_spawn_distance_mode;
eCombatAbilityLevel m_combat_ability_level;
bool m_stay_in_veh;
bool m_spawn_behind_same_velocity; //Spawns behind a moving target with the same velocity as the targets vehicle
bool m_disperse; //Spawns attackers that are on foot on seperate positions
/*
Leave vehicle_model empty to spawn a squad on foot
Ped proofs array is indexed as follows; 0 headshot, 1 bullet, 2 flame, 3 melee, 4 explosion
Leave health and armor at 0 to default
Leave spawn_distance at 0 to let the spawn_distance_mode to handle it
*/
squad(){};
squad(std::string name, std::string ped_model, std::string weapon_model, std::string vehicle_model, int squad_size, bool ped_invincibility = false, bool veh_invincibility = false, bool ped_proofs[5] = {}, float ped_health = 0, float ped_armor = 0, float spawn_distance = 0, float ped_accuracy = 50.f, eSquadSpawnDistance spawn_distance_mode = eSquadSpawnDistance::CLOSEBY, eCombatAbilityLevel combat_ability_level = eCombatAbilityLevel::AVERAGE, bool stay_in_veh = false, bool spawn_behind_same_velocity = false, std::string description = "", bool disperse = false)
{
m_internal_id = ++m_instance_count;
m_name = name;
m_description = description;
m_ped_model = ped_model;
m_weapon_model = weapon_model;
m_vehicle_model = vehicle_model;
m_squad_size = squad_size;
m_ped_invincibility = ped_invincibility;
m_veh_invincibility = veh_invincibility;
for (int i = 0; i < 5; i++)
m_ped_proofs[i] = ped_proofs[i];
m_ped_health = ped_health;
m_ped_armor = ped_armor;
m_spawn_distance = spawn_distance;
m_ped_accuracy = ped_accuracy;
m_spawn_distance_mode = spawn_distance_mode;
m_combat_ability_level = combat_ability_level;
m_stay_in_veh = stay_in_veh;
m_spawn_behind_same_velocity = spawn_behind_same_velocity;
m_disperse = disperse;
}
int get_id() const
{
return m_internal_id;
}
bool does_squad_have_description()
{
return !std::string(m_description).empty();
}
bool does_squad_have_vehicle()
{
return !std::string(m_vehicle_model).empty();
}
bool should_override_health()
{
if (m_ped_health > 0)
return true;
return false;
}
bool should_override_armor()
{
if (m_ped_armor > 0)
return true;
return false;
}
bool should_override_spawn_distance()
{
if (m_spawn_distance > 0)
return true;
return false;
}
bool has_squad_spawned()
{
return m_members.size() > 0;
}
bool is_squad_alive()
{
for (auto& p : m_members)
if (p.is_ped_alive())
return true;
return false;
}
squad_member get_a_member_thats_alive()
{
for (auto& p : m_members)
if (p.is_ped_alive())
return p;
return {};
}
//Dynamic variables
std::vector<squad_member> m_members{};
player_ptr target = nullptr;
Ped current_target_ped = 0;
Vehicle m_veh_handle = 0;
CVehicle* m_veh_ptr = nullptr;
Vector3 m_spawn_pos{};
float m_spawn_heading = 0;
int m_internal_id = 0;
Vector3 get_veh_pos()
{
rage::fvector3* pos{};
if (m_veh_ptr && m_veh_ptr->m_navigation && m_veh_ptr->m_navigation->get_position())
pos = m_veh_ptr->m_navigation->get_position();
else
return {};
return {pos->x, pos->y, pos->z};
}
private:
inline static int m_instance_count;
};
NLOHMANN_JSON_SERIALIZE_ENUM(eSquadSpawnDistance, {{eSquadSpawnDistance::CUSTOM, "custom"}, {eSquadSpawnDistance::ON_TARGET, "on target"}, {eSquadSpawnDistance::CLOSEBY, "closeby"}, {eSquadSpawnDistance::MODERATELY_DISTANCED, "moderately distanced"}, {eSquadSpawnDistance::FAR_AWAY, "far away"}})
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(squad, m_name, m_description, m_ped_model, m_weapon_model, m_vehicle_model, m_ped_invincibility, m_veh_invincibility, m_ped_proofs, m_ped_health, m_ped_armor, m_ped_accuracy, m_spawn_distance, m_squad_size, m_spawn_distance_mode, m_combat_ability_level, m_stay_in_veh, m_spawn_behind_same_velocity, m_disperse);
class squad_spawner
{
public:
std::vector<squad> m_templates;
std::vector<squad> m_active_squads;
squad_spawner()
{
load_default_templates();
}
private:
bool find_suitable_spawn_pos(squad&);
squad_member spawn_squad_member(squad);
std::pair<Vehicle, CVehicle*> spawn_squad_vehicle(squad);
public:
void load_default_templates();
std::filesystem::path get_file_path();
bool spawn_squad(squad, player_ptr target_player, bool override_spawn_pos, Vector3 custom_pos);
bool save_squad(squad);
bool delete_squad(squad);
bool fetch_squads();
void tick();
void terminate_squads();
void terminate_squad(squad*);
void build_and_perform_sequence(squad&, int member);
};
inline squad_spawner g_squad_spawner_service;
}

View File

@ -0,0 +1,80 @@
#include "squad_spawner.hpp"
#include "util/entity.hpp"
namespace big
{
void squad_spawner::terminate_squads()
{
for (auto& s : m_active_squads)
{
if (entity::take_control_of(s.m_veh_handle))
{
entity::delete_entity(s.m_veh_handle);
}
for (auto& m : s.m_members)
{
if (entity::take_control_of(m.handle))
{
ENTITY::SET_ENTITY_HEALTH(m.handle, 0, false);
entity::delete_entity(m.handle);
}
}
}
}
void squad_spawner::terminate_squad(squad* s)
{
if (!s)
return;
if (entity::take_control_of(s->m_veh_handle))
{
entity::delete_entity(s->m_veh_handle);
}
for (auto& m : s->m_members)
{
if (entity::take_control_of(m.handle))
{
ENTITY::SET_ENTITY_HEALTH(m.handle, 0, false);
entity::delete_entity(m.handle);
}
}
}
void squad_spawner::build_and_perform_sequence(squad& s, int member)
{
TASK::OPEN_SEQUENCE_TASK(&s.m_members[member].task_sequence);
if (member == 0 && s.does_squad_have_vehicle())
{
if (VEHICLE::IS_THIS_MODEL_A_CAR(rage::joaat(s.m_vehicle_model)))
{
TASK::TASK_VEHICLE_MISSION_PED_TARGET(0, s.m_veh_handle, s.current_target_ped, s.m_stay_in_veh ? 6 : 4, 100.f, 786468, 12.f, 5.f, true);
if (!s.m_stay_in_veh)
TASK::TASK_LEAVE_ANY_VEHICLE(0, 0, 0);
}
else if (VEHICLE::IS_THIS_MODEL_A_HELI(rage::joaat(s.m_vehicle_model)))
{
TASK::TASK_HELI_MISSION(0, s.m_veh_handle, 0, s.current_target_ped, 0, 0, 0, 6, 200.f, 30.f, -1, 50.f, 20.f, -1, 128 | 4096);
VEHICLE::SET_HELI_BLADES_FULL_SPEED(s.m_veh_handle);
}
else if (VEHICLE::IS_THIS_MODEL_A_PLANE(rage::joaat(s.m_vehicle_model)))
{
TASK::TASK_PLANE_MISSION(0, s.m_veh_handle, 0, s.current_target_ped, 0, 0, 0, 6, 300, 35.f, -1, 100.f, 20.f, true);
VEHICLE::SET_VEHICLE_FORWARD_SPEED(s.m_veh_handle, 30.f);
}
else if (VEHICLE::IS_THIS_MODEL_A_BOAT(rage::joaat(s.m_vehicle_model)))
{
TASK::TASK_BOAT_MISSION(0, s.m_veh_handle, 0, s.current_target_ped, 0, 0, 0, 6, 300, 786468, 10.f, 7);
}
}
TASK::TASK_COMBAT_PED(0, s.current_target_ped, 67108864, 16); //flag 67108864 should prevent peds from attaining other targets
TASK::SET_SEQUENCE_TO_REPEAT(s.m_members[member].task_sequence, 1);
TASK::CLOSE_SEQUENCE_TASK(s.m_members[member].task_sequence);
TASK::TASK_PERFORM_SEQUENCE(s.m_members[member].handle, s.m_members[member].task_sequence);
}
}

View File

@ -0,0 +1,107 @@
#include "squad_spawner.hpp"
namespace big
{
std::filesystem::path squad_spawner::get_file_path()
{
return g_file_manager->get_project_folder("squad_spawner").get_path();
}
bool squad_spawner::fetch_squads()
{
g_squad_spawner_service.m_templates.clear();
bool success = false;
std::ifstream read;
try
{
for (const auto& path : std::filesystem::directory_iterator(get_file_path()))
{
nlohmann::json j;
if (path.path().extension() == ".json")
{
read.open(path.path(), std::ifstream::in);
if (read.is_open())
{
read >> j;
read.close();
}
squad new_squad{};
LOG(INFO) << "TEST1";
from_json(j, new_squad);
LOG(INFO) << "TEST2";
g_squad_spawner_service.m_templates.push_back(new_squad);
LOG(INFO) << "TEST3";
}
}
success = true;
}
catch (std::exception e)
{
LOG(WARNING) << "Squad Spawner fetching files failed: " << e.what();
}
g_squad_spawner_service.load_default_templates();
return success;
}
bool squad_spawner::save_squad(squad s)
{
for (auto s_ : g_squad_spawner_service.m_templates)
if (s_.m_name.compare(s.m_name) == 0)
return false;
std::ofstream write;
std::string savename = s.m_name;
savename.append(".json");
std::filesystem::path path = get_file_path() / savename;
nlohmann::json j;
to_json(j, s);
write.open(path, std::ofstream::out | std::ofstream::trunc);
try
{
if (write.is_open())
{
write << std::setw(4) << j << std::endl;
write.close();
g_notification_service->push("Squad spawner", std::string("Succesfully saved ").append(s.m_name));
fetch_squads();
return true;
}
}
catch (std::exception e)
{
LOG(WARNING) << "Squad Spawner saving squad failed: " << e.what();
}
return false;
}
bool squad_spawner::delete_squad(squad s)
{
std::string savename = s.m_name;
savename.append(".json");
std::filesystem::path path = get_file_path() / savename;
std::filesystem::remove(path);
return fetch_squads();
}
void squad_spawner::load_default_templates()
{
bool ped_proofs[5] = {0, 0, 0, 0, 0};
bool ped_proofs_annoying[5] = {1, 0, 0, 1, 1};
m_templates.push_back(squad("Swat team", "s_m_y_swat_01", "WEAPON_SMG", "riot", 4, false, false, ped_proofs, 400, 400, 0, 75, eSquadSpawnDistance::MODERATELY_DISTANCED, eCombatAbilityLevel::PROFESSIONAL, false, false, "An elite team of swat operatives that will quickly swarm the target"));
m_templates.push_back(squad("Secret service", "s_m_m_highsec_01", "WEAPON_CARBINERIFLE", "oracle", 4, false, false, ped_proofs, 0, 200, 0, 75, eSquadSpawnDistance::MODERATELY_DISTANCED, eCombatAbilityLevel::PROFESSIONAL, false, false, "MIB"));
m_templates.push_back(squad("Ballas Gang", "ig_ballasog", "WEAPON_MICROSMG", "chino2", 2, false, false, ped_proofs, 0, 0, 0, 50, eSquadSpawnDistance::MODERATELY_DISTANCED, eCombatAbilityLevel::POOR, true, false, "A classic driveby from the local Ballas gang"));
m_templates.push_back(squad("Grove Gang", "g_m_y_famca_01", "WEAPON_PISTOL", "chino", 2, false, false, ped_proofs, 0, 0, 0, 50, eSquadSpawnDistance::MODERATELY_DISTANCED, eCombatAbilityLevel::POOR, true, false, "A classic driveby from the local Grove gang"));
m_templates.push_back(squad("Robbers", "g_m_m_chicold_01", "WEAPON_SMG_MK2", "baller5", 4, false, false, ped_proofs, 0, 0, 0, 65, eSquadSpawnDistance::MODERATELY_DISTANCED, eCombatAbilityLevel::AVERAGE, false, false, "Mysterious mercenaries that hide behind snow masks"));
m_templates.push_back(squad("Shotgunners", "g_m_y_lost_03", "WEAPON_AUTOSHOTGUN", "daemon", 2, false, false, ped_proofs, 0, 0, 0, 65, eSquadSpawnDistance::MODERATELY_DISTANCED, eCombatAbilityLevel::AVERAGE, false, false, "A duo biker gang that utilize sawn off shotguns on their Harley's"));
m_templates.push_back(squad("Machete jesus", "u_m_m_jesus_01", "WEAPON_MACHETE", "", 1, false, false, ped_proofs_annoying, 0, 0, 0, 10, eSquadSpawnDistance::CLOSEBY, eCombatAbilityLevel::AVERAGE, false, false, "Christ has had enough of the sins"));
m_templates.push_back(squad("Annoying security guard", "mp_m_securoguard_01", "WEAPON_STUNGUN_MP", "", 1, false, false, ped_proofs_annoying, 0, 0, 0, 100, eSquadSpawnDistance::CLOSEBY, eCombatAbilityLevel::PROFESSIONAL, false, false, "The mall security guard with superiority issues"));
m_templates.push_back(squad("Heavy attack choppers", "s_m_y_swat_01", "WEAPON_MG", "valkyrie", 4, false, false, ped_proofs, 0, 0, 0, 100, eSquadSpawnDistance::MODERATELY_DISTANCED, eCombatAbilityLevel::PROFESSIONAL, false, false, "Very deadly attack chopper eqquiped with a cannon"));
m_templates.push_back(squad("Fighter jet", "s_m_m_pilot_02", "WEAPON_UNARMED", "lazer", 1, false, false, ped_proofs, 0, 0, 0, 100, eSquadSpawnDistance::FAR_AWAY, eCombatAbilityLevel::PROFESSIONAL, false, false, "Tedious yet precise form of attack with a Fighter jet"));
m_templates.push_back(squad("Mobile squad", "s_m_m_highsec_01", "WEAPON_MICROSMG", "komoda", 4, false, false, ped_proofs, 0, 0, 0, 100, eSquadSpawnDistance::FAR_AWAY, eCombatAbilityLevel::PROFESSIONAL, true, true, "This squad makes use of 'Vehicle catchup'"));
m_templates.push_back(squad("Altruists", "a_m_m_acult_01", "WEAPON_SNSPISTOL", "", 8, false, false, ped_proofs, 0, 0, 0, 100, eSquadSpawnDistance::CLOSEBY, eCombatAbilityLevel::PROFESSIONAL, false, false, "Cannibals from the alrtuist cult will surround the victim using 'Disperse'", true));
}
}

View File

@ -1,10 +1,12 @@
#pragma once
#include "gta/enums.hpp"
#include "math.hpp"
#include "natives.hpp"
#include "pointers.hpp"
#include "math.hpp"
#include "script.hpp"
#include "gta/enums.hpp"
#include <random>
namespace big::pathfind
@ -12,7 +14,8 @@ namespace big::pathfind
inline bool load_path_nodes(Vector3 coords)
{
if(PATHFIND::ARE_NODES_LOADED_FOR_AREA(coords.x, coords.y, coords.z, coords.y)) return true;
if (PATHFIND::ARE_NODES_LOADED_FOR_AREA(coords.x, coords.y, coords.z, coords.y))
return true;
PATHFIND::REQUEST_PATH_NODES_IN_AREA_THIS_FRAME(coords.x, coords.y, coords.z, coords.y);
@ -27,7 +30,8 @@ namespace big::pathfind
inline bool load_navmesh_area(Vector3 coords, float radius)
{
if(PATHFIND::ARE_ALL_NAVMESH_REGIONS_LOADED()) return true;
if (PATHFIND::ARE_ALL_NAVMESH_REGIONS_LOADED())
return true;
PATHFIND::ADD_NAVMESH_REQUIRED_REGION(coords.x, coords.z, radius);
@ -71,37 +75,87 @@ namespace big::pathfind
return false;
}
inline bool find_random_location_in_vicinity(Vector3 coords, Vector3& outcoords, float& outheading, int flag, int vicinity)
inline bool find_random_vehicle_node(Vector3 center, Vector3& outcoords, float radius, bool avoid_dead_ends, bool avoid_highways, int min_lanes = 0)
{
int rand1 = rand() % 4;
Vector3 changedcoords = coords;
switch (rand1)
{
case 1: changedcoords.x += rand() % vicinity + vicinity / 2; break;
case 2: changedcoords.x -= rand() % vicinity + vicinity / 2; break;
case 3: changedcoords.y += rand() % vicinity + vicinity / 2; break;
case 4: changedcoords.y -= rand() % vicinity + vicinity / 2; break;
}
int node_id;
if (load_path_nodes(center))
return PATHFIND::GET_RANDOM_VEHICLE_NODE(center.x, center.y, center.z, radius, 0, avoid_dead_ends, avoid_highways, &outcoords, &node_id);
else
return false;
}
find_closest_vehicle_node(changedcoords, outcoords, outheading, flag);
inline void apply_distance_to_random_direction(Vector3& outcoords, float distance)
{
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(0, 1);
if (math::distance_between_vectors(outcoords, changedcoords) > vicinity)
if (dis(gen))
{
if (find_safe_pos_ped(changedcoords, outcoords, true, flag))
return true;
else
{
outcoords = coords;
return false;
}
dis(gen) ? outcoords.x += distance : outcoords.x -= distance;
}
else
return true;
return false;
{
dis(gen) ? outcoords.y += distance : outcoords.y -= distance;
}
}
inline bool find_random_location_in_vicinity(Vector3 coords, Vector3& outcoords, float& outheading, int flag, int vicinity)
{
outcoords = coords;
apply_distance_to_random_direction(outcoords, vicinity);
Vector3 changed_coords = outcoords;
if (!find_closest_vehicle_node(outcoords, outcoords, outheading, flag) || math::distance_between_vectors(outcoords, coords) > vicinity || math::distance_between_vectors(outcoords, coords) < (vicinity / 2))
{
outcoords = coords;
if (!pathfind::find_safe_pos_ped(changed_coords, outcoords, false, 0))
{
outcoords = coords;
}
}
return outcoords != coords;
}
/*
The precision means the algorithm will try and get a position as close to the desired distance as possible
Param precision goes up to a value of 200 meaning how many positions it will try and filter from
Might prove resource demanding based on hardware
*/
inline bool find_random_location_in_vicinity_precise(Vector3 coords, Vector3& outcoords, float& outheading, int flag, float vicinity, int precision = 50)
{
if (precision > 200)
precision = 200;
std::vector<Vector3> found_locations{};
//Find random positions
for (int i = 0; i < precision; i++)
{
Vector3 new_pos{};
find_random_location_in_vicinity(coords, new_pos, outheading, flag, vicinity);
found_locations.push_back(new_pos);
}
Vector3 best_location = found_locations[0];
//Measure the distance of the position to the given vicinity distance
static float distance_to_vicinity = std::abs(vicinity - math::distance_between_vectors(best_location, coords));
for (auto l : found_locations)
{
float new_distance_to_vicinity = std::abs(vicinity - math::distance_between_vectors(l, coords));
//If the new distance is smaller, that means we have a position that is closer to the edge
if (new_distance_to_vicinity < distance_to_vicinity)
{
distance_to_vicinity = new_distance_to_vicinity;
best_location = l;
}
}
outcoords = best_location;
return outcoords != coords;
}
}

View File

@ -67,6 +67,10 @@ namespace big
NETWORK::SHUTDOWN_AND_LOAD_MOST_RECENT_SAVE();
});
components::button("Remove Black Screen", [] {
CAM::DO_SCREEN_FADE_IN(0);
});
components::button("Tp To Safe Pos", [] {
Vector3 safepos{};
float heading;

View File

@ -47,6 +47,7 @@ namespace big
static void fun_vehicle();
static void vehicle_control();
static void spawn_ped();
static void squad_spawner();
static void time_and_weather();
static void spoofing();
static void teleport();

View File

@ -0,0 +1,349 @@
#include "services/gta_data/gta_data_service.hpp"
#include "services/squad_spawner/squad_spawner.hpp"
#include "misc/cpp/imgui_stdlib.h"
#include "views/view.hpp"
namespace big
{
void view::squad_spawner()
{
const char* const spawn_distance_modes[5]{"Custom", "On target", "Closeby", "Moderately distanced", "Far away"};
const char* const combat_ability_levels[3]{"Poor", "Average", "Professional"};
static squad new_template{};
static player_ptr victim = g_player_service->get_selected();
ImGui::Text("Victim");
ImGui::SetNextItemWidth(200);
if (ImGui::BeginCombo("##victim", victim->get_name()))
{
auto self = g_player_service->get_self();
if (ImGui::Selectable(self->get_name(), self->id() == victim->id()))
victim = self;
for (auto p : g_player_service->players() | std::ranges::views::values)
{
if (ImGui::Selectable(p->get_name(), p->id() == victim->id()))
{
victim = p;
if (g.player.spectating)
g_player_service->set_selected(victim);
}
}
ImGui::EndCombo();
}
ImGui::SameLine();
if (ImGui::Checkbox("SPECTATE"_T.data(), &g.player.spectating))
{
g_player_service->set_selected(victim);
};
if (victim->id() != g_player_service->get_selected()->id() && victim->is_valid())
{
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.5f, 0.3f, 1.0f));
ImGui::Text("Warning: Victim and selected player are not the same");
ImGui::PopStyleColor();
}
ImGui::Separator();
static squad deletion_squad;
if (!std::string(deletion_squad.m_name).empty())
ImGui::OpenPopup("##deletesquad");
if (ImGui::BeginPopupModal("##deletesquad"))
{
ImGui::Text("Are you sure you want to delete %s?", deletion_squad.m_name);
if (ImGui::Button("Yes"))
{
g_squad_spawner_service.delete_squad(deletion_squad);
deletion_squad.m_name = "";
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("No"))
{
deletion_squad.m_name = "";
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
ImGui::SetNextItemWidth(200);
if (ImGui::BeginCombo("Choose From Templates", "Templates"))
{
components::button("Fetch Custom Squads", [] {
g_squad_spawner_service.fetch_squads();
});
for (auto& temp : g_squad_spawner_service.m_templates)
{
if (ImGui::Selectable(temp.m_name.data()))
{
if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
{
deletion_squad = temp;
}
else
{
new_template = temp;
}
}
if (ImGui::IsItemHovered() && temp.does_squad_have_description())
ImGui::SetTooltip(temp.m_description.data());
}
ImGui::EndCombo();
}
ImGui::Separator();
ImGui::BeginGroup(); //Main variables
ImGui::Text("Squad Details");
ImGui::Spacing();
ImGui::PushItemWidth(250);
components::input_text_with_hint("##name", "Name", &new_template.m_name);
components::input_text_with_hint("##pedmodel", "Ped model", &new_template.m_ped_model);
auto ped_found = std::find_if(g_gta_data_service->peds().begin(), g_gta_data_service->peds().end(), [=](const auto& pair) {
return pair.second.m_name == new_template.m_ped_model;
});
if (!std::string(new_template.m_ped_model).empty() && ped_found == g_gta_data_service->peds().end())
{
if (ImGui::ListBoxHeader("##pedlist", ImVec2(250, 200)))
{
for (auto& p : g_gta_data_service->peds() | std::ranges::views::values)
{
std::string p_model = p.m_name;
std::string filter = new_template.m_ped_model;
std::transform(p_model.begin(), p_model.end(), p_model.begin(), ::tolower);
std::transform(filter.begin(), filter.end(), filter.begin(), ::tolower);
if (p_model.find(filter) != std::string::npos && ImGui::Selectable(p.m_name))
{
new_template.m_ped_model = p.m_name;
}
}
ImGui::ListBoxFooter();
}
}
components::input_text_with_hint("##vehmodel",
"Vehicle model",
&new_template.m_vehicle_model);
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Leave empty to spawn on foot");
auto veh_found = std::find_if(g_gta_data_service->vehicles().begin(), g_gta_data_service->vehicles().end(), [=](const auto& pair) {
return pair.second.m_name == new_template.m_vehicle_model;
});
if (!new_template.m_vehicle_model.empty() && veh_found == g_gta_data_service->vehicles().end())
{
if (ImGui::ListBoxHeader("##vehlist", ImVec2(250, 200)))
{
for (auto& p : g_gta_data_service->vehicles() | std::ranges::views::values)
{
std::string p_model = p.m_name;
std::string filter = new_template.m_vehicle_model;
std::transform(p_model.begin(), p_model.end(), p_model.begin(), ::tolower);
std::transform(filter.begin(), filter.end(), filter.begin(), ::tolower);
if (p_model.find(filter) != std::string::npos && ImGui::Selectable(p.m_name))
{
new_template.m_vehicle_model = p.m_name;
}
}
ImGui::ListBoxFooter();
}
}
components::input_text_with_hint("##weapmodel",
"Weapon model",
&new_template.m_weapon_model);
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Leave empty to spawn unarmed, beware that a player can only attain 3 melee attackers at a time");
auto weap_found = std::find_if(g_gta_data_service->weapons().begin(), g_gta_data_service->weapons().end(), [=](const auto& pair) {
return pair.second.m_name == new_template.m_weapon_model;
});
if (!std::string(new_template.m_weapon_model).empty() && weap_found == g_gta_data_service->weapons().end())
{
if (ImGui::ListBoxHeader("##weaplist", ImVec2(250, 200)))
{
for (auto& p : g_gta_data_service->weapons() | std::ranges::views::values)
{
std::string p_model = p.m_name;
std::string filter = new_template.m_weapon_model;
std::transform(p_model.begin(), p_model.end(), p_model.begin(), ::tolower);
std::transform(filter.begin(), filter.end(), filter.begin(), ::tolower);
if (p_model.find(filter) != std::string::npos && ImGui::Selectable(p.m_name))
{
new_template.m_weapon_model = p.m_name;
}
}
ImGui::ListBoxFooter();
}
}
ImGui::Spacing();
ImGui::Text("Spawn Distance");
if (ImGui::BeginCombo("##spawndistance", spawn_distance_modes[(int)new_template.m_spawn_distance_mode]))
{
for (int i = 0; i < 5; i++)
{
if (ImGui::Selectable(spawn_distance_modes[i], (int)new_template.m_spawn_distance_mode == i))
new_template.m_spawn_distance_mode = (eSquadSpawnDistance)i;
}
ImGui::EndCombo();
}
ImGui::Text("Squad Size");
ImGui::SliderInt("##squadsize", &new_template.m_squad_size, 1, 8);
ImGui::PopItemWidth();
ImGui::EndGroup();
ImGui::SameLine();
ImGui::BeginGroup(); //General actions
ImGui::Text("Actions");
ImGui::Spacing();
components::button(std::string("Terminate " + std::to_string(g_squad_spawner_service.m_active_squads.size()) + " squads"), [] {
g_squad_spawner_service.terminate_squads();
});
components::button("Reset Fields", [] {
new_template.m_spawn_distance_mode = (eSquadSpawnDistance)1;
new_template.m_combat_ability_level = (eCombatAbilityLevel)2;
new_template.m_name[0] = '\0';
new_template.m_description[0] = '\0';
new_template.m_ped_model[0] = '\0';
new_template.m_vehicle_model[0] = '\0';
new_template.m_weapon_model[0] = '\0';
new_template.m_squad_size = 0;
new_template.m_ped_invincibility = 0;
new_template.m_veh_invincibility = 0;
new_template.m_ped_health = 0;
new_template.m_ped_armor = 0;
new_template.m_ped_accuracy = 0;
new_template.m_spawn_distance = 0;
for (int i = 0; i < sizeof(new_template.m_ped_proofs) / sizeof(new_template.m_ped_proofs[0]); i++)
new_template.m_ped_proofs[i] = false;
new_template.m_stay_in_veh = 0;
new_template.m_spawn_behind_same_velocity = 0;
new_template.m_disperse = 0;
});
ImGui::EndGroup();
ImGui::Spacing();
if (ImGui::TreeNode("Advanced Options"))
{
ImGui::BeginGroup(); //Toggleables
ImGui::Checkbox("Disperse", &new_template.m_disperse);
if (ImGui::IsItemHovered())
ImGui::SetTooltip("If the squad is on foot, will scatter units within the spawn distance");
ImGui::Checkbox("Vehicle catch up", &new_template.m_spawn_behind_same_velocity);
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Will spawn the mobile squad behind the target with identical velocity if applicable.\nOnly for squads with a vehicle.");
ImGui::Checkbox("Stay In Vehicle", &new_template.m_stay_in_veh);
ImGui::Checkbox("Ped God Mode", &new_template.m_ped_invincibility);
ImGui::Checkbox("Vehicle God Mode", &new_template.m_veh_invincibility);
ImGui::Checkbox("Headshot Proof", &new_template.m_ped_proofs[0]);
ImGui::Checkbox("Bullet Proof", &new_template.m_ped_proofs[1]);
ImGui::Checkbox("Flame Proof", &new_template.m_ped_proofs[2]);
ImGui::Checkbox("Melee Proof", &new_template.m_ped_proofs[3]);
ImGui::Checkbox("Explosion Proof", &new_template.m_ped_proofs[4]);
ImGui::EndGroup();
ImGui::SameLine();
ImGui::BeginGroup(); //Slideables
ImGui::PushItemWidth(200);
ImGui::Text("Ped Health");
ImGui::SliderFloat("##pedhealth", &new_template.m_ped_health, 100, 2000);
ImGui::Text("Ped Armor");
ImGui::SliderFloat("##pedarmor", &new_template.m_ped_armor, 0, 2000);
ImGui::Text("Ped Accuracy");
ImGui::SliderFloat("##pedaccuracy", &new_template.m_ped_accuracy, 0, 100);
ImGui::Text("Custom Spawn Distance");
ImGui::SliderFloat("##customspawndistance", &new_template.m_spawn_distance, 10, 500);
ImGui::EndGroup();
if (ImGui::IsItemHovered())
ImGui::SetTooltip("Leave these values at 0 to default, except for accuracy.");
ImGui::SameLine();
ImGui::BeginGroup(); //Chooseables
ImGui::Text("Combat Ability");
if (ImGui::BeginCombo("##combatability", combat_ability_levels[(int)new_template.m_combat_ability_level]))
{
for (int i = 0; i < 3; i++)
{
if (ImGui::Selectable(combat_ability_levels[i], (int)new_template.m_combat_ability_level == i))
new_template.m_combat_ability_level = (eCombatAbilityLevel)i;
}
ImGui::EndCombo();
}
ImGui::PopItemWidth();
ImGui::EndGroup();
components::input_text_with_hint("##new_template.m_description",
"Squad new_template.m_description",
&new_template.m_description);
ImGui::TreePop();
}
static auto check_validity = [=](bool save) -> bool {
if (!victim->is_valid() && !save)
{
g_notification_service->push_error("Squad spawner", "Choose a victim first");
return false;
}
if (std::string(new_template.m_ped_model).empty())
{
g_notification_service->push_error("Squad spawner", "A ped model is required");
return false;
}
return true;
};
static auto check_if_exists = [=](std::string squad_name) -> bool {
bool exists = false;
for (auto& s : g_squad_spawner_service.m_templates)
{
if (s.m_name.compare(squad_name) == 0)
{
exists = true;
break;
}
}
return exists;
};
components::button("Spawn Squad", [] {
if (check_validity(false))
g_squad_spawner_service.spawn_squad({new_template.m_name, new_template.m_ped_model, new_template.m_weapon_model, new_template.m_vehicle_model, new_template.m_squad_size, new_template.m_ped_invincibility, new_template.m_veh_invincibility, new_template.m_ped_proofs, new_template.m_ped_health, new_template.m_ped_armor, new_template.m_spawn_distance, new_template.m_ped_accuracy, new_template.m_spawn_distance_mode, new_template.m_combat_ability_level, new_template.m_stay_in_veh, new_template.m_spawn_behind_same_velocity, new_template.m_description, new_template.m_disperse},
victim,
false,
{});
});
ImGui::SameLine();
components::button("Save", [] {
if (check_validity(true) && !check_if_exists(new_template.m_name))
g_squad_spawner_service.save_squad(new_template);
});
}
}