474 lines
13 KiB
C++
474 lines
13 KiB
C++
#include "AttackerMgr.hpp"
|
|
|
|
#include "AbstractModel.hpp"
|
|
#include "BlipUtil.hpp"
|
|
#include "CommandPlayerAttackers.hpp"
|
|
#include "ChainedVehicleMgr.hpp"
|
|
#include "eScriptTaskStatus.hpp"
|
|
#include "eTaskCombatPedFlags.hpp"
|
|
#include "eTaskThreatResponseFlags.hpp"
|
|
#include "get_ground_z.hpp"
|
|
#include "Gui.hpp"
|
|
#include "PathFind.hpp"
|
|
#include "pointers.hpp"
|
|
#include "Script.hpp"
|
|
#include "Util.hpp"
|
|
#include "VehicleType.hpp"
|
|
|
|
namespace Stand
|
|
{
|
|
void AttackerMgr::push(AbstractEntity& attacker, AbstractPlayer victim, Hash weapon, const bool immortality, const Hash vehicle_hash, const bool fill_vehicles, AttackerType type)
|
|
{
|
|
attacker.setCanMigrate(false);
|
|
|
|
if (type != AttackerType::NORMAL)
|
|
{
|
|
initAudioBanks(type);
|
|
}
|
|
|
|
auto victim_ped = victim.getPed();
|
|
|
|
PED::SET_PED_SEEING_RANGE(attacker, 99999.0f);
|
|
PED::SET_PED_HEARING_RANGE(attacker, 99999.0f);
|
|
PED::SET_PED_TARGET_LOSS_RESPONSE(attacker, 2);
|
|
PED::SET_PED_CAN_BE_KNOCKED_OFF_VEHICLE(attacker, 1);
|
|
PED::SET_PED_RELATIONSHIP_GROUP_HASH(attacker, ATSTRINGHASH("ARMY"));
|
|
PED::SET_BLOCKING_OF_NON_TEMPORARY_EVENTS(attacker, true);
|
|
PED::SET_PED_FIRING_PATTERN(attacker, ATSTRINGHASH("FIRING_PATTERN_FULL_AUTO"));
|
|
|
|
// CPED_RESET_FLAG_IgnoreCombatManager
|
|
// CPED_RESET_FLAG_AllowPullingPedOntoRoute
|
|
// CPED_RESET_FLAG_UseTighterAvoidanceSettings
|
|
|
|
auto blip = HUD::ADD_BLIP_FOR_ENTITY(attacker);
|
|
HUD::SET_BLIP_AS_FRIENDLY(blip, FALSE);
|
|
HUD::SET_BLIP_SCALE(blip, 0.5f);
|
|
|
|
ENTITY::SET_ENTITY_LOAD_COLLISION_FLAG(attacker, true, 1);
|
|
TASK::TASK_SET_BLOCKING_OF_NON_TEMPORARY_EVENTS(attacker, true);
|
|
|
|
auto vehicle_model = AbstractModel(vehicle_hash);
|
|
auto attacker_model = attacker.getModel();
|
|
|
|
if (vehicle_model.isPlane())
|
|
{
|
|
int task;
|
|
TASK::OPEN_SEQUENCE_TASK(&task);
|
|
TASK::TASK_COMBAT_PED(attacker, victim_ped, eTaskCombatPedFlags::COMBAT_PED_PREVENT_CHANGING_TARGET, eTaskThreatResponseFlags::TASK_THREAT_RESPONSE_CAN_FIGHT_ARMED_PEDS_WHEN_NOT_ARMED);
|
|
TASK::SET_SEQUENCE_TO_REPEAT(task, TRUE);
|
|
TASK::CLOSE_SEQUENCE_TASK(task);
|
|
TASK::TASK_PERFORM_SEQUENCE(attacker, task);
|
|
TASK::CLEAR_SEQUENCE_TASK(&task);
|
|
}
|
|
else
|
|
{
|
|
TASK::TASK_COMBAT_PED(attacker, victim_ped, eTaskCombatPedFlags::COMBAT_PED_PREVENT_CHANGING_TARGET, eTaskThreatResponseFlags::TASK_THREAT_RESPONSE_CAN_FIGHT_ARMED_PEDS_WHEN_NOT_ARMED);
|
|
}
|
|
|
|
if (immortality)
|
|
{
|
|
attacker.godmodeEnable();
|
|
}
|
|
|
|
if (vehicle_hash != 0)
|
|
{
|
|
ENTITY::SET_ENTITY_VISIBLE(attacker, FALSE, FALSE);
|
|
}
|
|
|
|
if (attacker_model.isAnimal())
|
|
{
|
|
weapon = ATSTRINGHASH("WEAPON_ANIMAL");
|
|
}
|
|
|
|
auto last_ped = attackers.empty() ? AbstractEntity::invalid() : attackers.back().attacker;
|
|
auto vehicle = ChainedVehicleMgr::processVehicle(attacker, vehicle_model, last_ped, immortality, fill_vehicles, false);
|
|
|
|
if (vehicle.isValid())
|
|
{
|
|
customiseVehicle(vehicle, type);
|
|
}
|
|
|
|
customiseAttacker(attacker, type);
|
|
|
|
attacker.giveWeapons({ weapon }, true);
|
|
attackers.emplace_back(AttackerGroup{ attacker, victim, vehicle, type });
|
|
}
|
|
|
|
void AttackerMgr::clear(compactplayer_t p)
|
|
{
|
|
for (auto i = attackers.begin(); i != attackers.end(); )
|
|
{
|
|
AttackerGroup& g = *i;
|
|
|
|
if (g.victim == p)
|
|
{
|
|
g.pop();
|
|
i = attackers.erase(i);
|
|
}
|
|
else
|
|
{
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
|
|
void AttackerMgr::onTick()
|
|
{
|
|
for (auto i = attackers.begin(); i != attackers.end(); )
|
|
{
|
|
const auto& player = i->victim;
|
|
auto& attacker = i->attacker;
|
|
|
|
if (!player.exists()
|
|
|| !attacker.isValid()
|
|
|| g_gui.isUnloadPending()
|
|
)
|
|
{
|
|
i->pop();
|
|
i = attackers.erase(i);
|
|
}
|
|
else if (attacker.isDead())
|
|
{
|
|
if (auto blip = HUD::GET_BLIP_FROM_ENTITY(attacker))
|
|
{
|
|
BlipUtil::remove(blip);
|
|
}
|
|
++i;
|
|
}
|
|
else
|
|
{
|
|
auto& vehicle = i->vehicle;
|
|
AbstractModel vehicle_model = vehicle.getModel();
|
|
|
|
if (vehicle.exists()) // isValid is insufficient: https://www.guilded.gg/stand/groups/x3ZgB10D/channels/47eb8ea9-21be-4642-8916-b062b4f9cac0/forums/1910839838
|
|
{
|
|
onTickVehicle(*i);
|
|
}
|
|
|
|
onTickAttacker(*i);
|
|
|
|
if (auto ped = player.getPed(); ped.exists())
|
|
{
|
|
applyTasks(attacker, vehicle, ped, i->type);
|
|
|
|
PED::SET_RELATIONSHIP_BETWEEN_GROUPS(5, ATSTRINGHASH("ARMY"), PED::GET_PED_RELATIONSHIP_GROUP_HASH(ped));
|
|
}
|
|
|
|
if (PED::GET_PED_RELATIONSHIP_GROUP_HASH(attacker) != ATSTRINGHASH("ARMY"))
|
|
{
|
|
PED::SET_PED_RELATIONSHIP_GROUP_HASH(attacker, ATSTRINGHASH("ARMY"));
|
|
}
|
|
|
|
if (auto accuracy = getAccuracyForPlayer(player); (accuracy / 100.0f) != PED::GET_COMBAT_FLOAT(attacker, 6))
|
|
{
|
|
PED::SET_PED_ACCURACY(attacker, accuracy);
|
|
}
|
|
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool AttackerMgr::canCreateAttackers(compactplayer_t p)
|
|
{
|
|
return attackers.size() <= 40 && !locked_players.contains(p);
|
|
}
|
|
|
|
int AttackerMgr::getAccuracyForPlayer(const AbstractPlayer& player)
|
|
{
|
|
if (player.exists())
|
|
{
|
|
SOUP_IF_LIKELY (auto cmd = player.getCommandAsList())
|
|
{
|
|
SOUP_IF_LIKELY (auto trolling = cmd->resolveChildByMenuName(LOC("TROLL")))
|
|
{
|
|
SOUP_IF_LIKELY (auto attackers = trolling->as<CommandList>()->resolveChildByMenuName(LOC("ATTKRS")))
|
|
{
|
|
SOUP_IF_LIKELY (auto a = attackers->as<CommandPlayerAttackers>(); a->accuracy)
|
|
{
|
|
a->accuracy->onTickInGameViewport();
|
|
return a->accuracy->value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 100;
|
|
}
|
|
|
|
void AttackerMgr::customiseVehicle(AbstractEntity& veh, AttackerType type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case AttackerType::NORMAL:
|
|
veh.fullyUpgrade();
|
|
break;
|
|
|
|
case AttackerType::PHANTOM:
|
|
{
|
|
veh.setHeadlightsVariation(10);
|
|
|
|
AUDIO::SET_VEHICLE_RADIO_ENABLED(veh, FALSE);
|
|
|
|
VEHICLE::SET_VEHICLE_DOORS_LOCKED_FOR_ALL_PLAYERS(veh, TRUE);
|
|
VEHICLE::SET_DONT_ALLOW_PLAYER_TO_ENTER_VEHICLE_IF_LOCKED_FOR_PLAYER(veh, TRUE);
|
|
VEHICLE::SET_VEHICLE_EXTRA(veh, 12, TRUE);
|
|
VEHICLE::SET_VEHICLE_COLOURS(veh, 32, 32);
|
|
VEHICLE::SET_VEHICLE_CUSTOM_PRIMARY_COLOUR(veh, 40, 0, 0);
|
|
VEHICLE::SET_VEHICLE_CUSTOM_SECONDARY_COLOUR(veh, 40, 0, 0);
|
|
VEHICLE::SET_VEHICLE_EXTRA_COLOUR_5(veh, 1);
|
|
VEHICLE::SET_VEHICLE_EXTRA_COLOUR_6(veh, 6);
|
|
VEHICLE::SET_VEHICLE_WINDOW_TINT(veh, 3);
|
|
VEHICLE::SET_VEHICLE_NEON_COLOUR(veh, 255, 255, 255);
|
|
VEHICLE::SET_VEHICLE_NUMBER_PLATE_TEXT(veh, soup::ObfusString("SPOOKY").c_str());
|
|
VEHICLE::SET_VEHICLE_LIGHTS(veh, 2);
|
|
VEHICLE::SET_VEHICLE_LIGHT_MULTIPLIER(veh, 15.0f);
|
|
|
|
AUDIO::REQUEST_SCRIPT_AUDIO_BANK("DLC_TUNER/DLC_Tuner_Phantom_Car", TRUE, Util::get_session_players_bitflag());
|
|
AUDIO::PLAY_SOUND_FROM_ENTITY(-1, "Spawn_In_Game", veh, "DLC_Tuner_Halloween_Phantom_Car_Sounds", TRUE, FALSE);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void AttackerMgr::customiseAttacker(AbstractEntity& ped, AttackerType type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case AttackerType::NORMAL:
|
|
break;
|
|
|
|
case AttackerType::PHANTOM:
|
|
{
|
|
AUDIO::DISABLE_PED_PAIN_AUDIO(ped, TRUE);
|
|
AUDIO::STOP_PED_SPEAKING_SYNCED(ped, TRUE);
|
|
ENTITY::SET_ENTITY_ALPHA(ped, 0, FALSE);
|
|
ENTITY::SET_ENTITY_VISIBLE(ped, FALSE, FALSE);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void AttackerMgr::applyTasks(AbstractEntity& attacker, AbstractEntity& veh, AbstractEntity& victim, AttackerType type)
|
|
{
|
|
const bool is_driver = veh.getDriver() == attacker;
|
|
|
|
switch (type)
|
|
{
|
|
case AttackerType::NORMAL:
|
|
{
|
|
const auto model = veh.getModel();
|
|
|
|
if (is_driver)
|
|
{
|
|
if (model.isPlane() // This is only done specifically for planes because they are the only vehicle type which exhibits increased aggression from it.
|
|
|| TASK::GET_SCRIPT_TASK_STATUS(attacker, ATSTRINGHASH("script_task_vehicle_mission")) != eScriptTaskStatus::PERFORMING_TASK
|
|
)
|
|
{
|
|
switch (model.getVehicleType())
|
|
{
|
|
case VEHICLE_TYPE_BOAT:
|
|
case VEHICLE_TYPE_SUBMARINE:
|
|
TASK::TASK_BOAT_MISSION(attacker, veh, 0, victim, 0.0f, 0.0f, 0.0f, 4, 200.0f, 786469, -1.0f, 7);
|
|
break;
|
|
|
|
case VEHICLE_TYPE_HELI:
|
|
TASK::TASK_HELI_MISSION(attacker, veh, 0, victim, 0.0f, 0.0f, 0.0f, 9, 9999.0f, 40.0f, -1.0f, 9999, 20, -1.0f, 0);
|
|
break;
|
|
|
|
case VEHICLE_TYPE_PLANE:
|
|
TASK::TASK_PLANE_MISSION(attacker, veh, 0, victim, 0.0f, 0.0f, 0.0f, 6, -1.0f, -1.0f, -1.0f, 300.0f, 100.0f, TRUE);
|
|
break;
|
|
|
|
default:
|
|
VEHICLE::MODIFY_VEHICLE_TOP_SPEED(veh, 40.0f);
|
|
PED::SET_DRIVER_AGGRESSIVENESS(attacker, 1.0f);
|
|
TASK::TASK_VEHICLE_MISSION_PED_TARGET(attacker, veh, victim, 22, 9999.0f, 0, -1.0f, -1.0f, TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!(model.isPlane() && is_driver) && !victim.isDead()) // Giving the task to plane pilots will interrupt cannon fire. We want to give the task to passengers instead.
|
|
{
|
|
if (TASK::GET_SCRIPT_TASK_STATUS(attacker, ATSTRINGHASH("script_task_combat")) != eScriptTaskStatus::PERFORMING_TASK)
|
|
{
|
|
TASK::TASK_COMBAT_PED(attacker, victim, eTaskCombatPedFlags::COMBAT_PED_PREVENT_CHANGING_TARGET, eTaskThreatResponseFlags::TASK_THREAT_RESPONSE_CAN_FIGHT_ARMED_PEDS_WHEN_NOT_ARMED);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case AttackerType::PHANTOM:
|
|
{
|
|
if (is_driver)
|
|
{
|
|
if (TASK::GET_SCRIPT_TASK_STATUS(attacker, ATSTRINGHASH("script_task_vehicle_mission")) != eScriptTaskStatus::PERFORMING_TASK)
|
|
{
|
|
TASK::TASK_VEHICLE_MISSION_PED_TARGET(attacker, veh, victim, 6, 100.0f, 0, 0.0f, 0.0f, TRUE);
|
|
}
|
|
}
|
|
else if (TASK::GET_SCRIPT_TASK_STATUS(attacker, ATSTRINGHASH("script_task_combat")) != eScriptTaskStatus::PERFORMING_TASK)
|
|
{
|
|
TASK::TASK_COMBAT_PED(attacker, victim, eTaskCombatPedFlags::COMBAT_PED_PREVENT_CHANGING_TARGET, eTaskThreatResponseFlags::TASK_THREAT_RESPONSE_CAN_FIGHT_ARMED_PEDS_WHEN_NOT_ARMED);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void AttackerMgr::onTickVehicle(AttackerGroup& group)
|
|
{
|
|
auto& vehicle = group.vehicle;
|
|
auto& attacker = group.attacker;
|
|
|
|
if (!vehicle.isOwner())
|
|
{
|
|
NETWORK::NETWORK_REQUEST_CONTROL_OF_ENTITY(vehicle);
|
|
}
|
|
|
|
switch (group.type)
|
|
{
|
|
case AttackerType::NORMAL:
|
|
{
|
|
if (attacker.getVehicle() == vehicle
|
|
&& !ENTITY::IS_ENTITY_VISIBLE(attacker)
|
|
)
|
|
{
|
|
ENTITY::SET_ENTITY_VISIBLE(attacker, TRUE, FALSE);
|
|
}
|
|
|
|
if (!ENTITY::IS_ENTITY_VISIBLE(vehicle)) // 1/100 aircraft randomy go invisible when getting their task. R* bug afaik.
|
|
{
|
|
ENTITY::SET_ENTITY_VISIBLE(vehicle, TRUE, FALSE);
|
|
}
|
|
|
|
if (vehicle.getModel().isPlane())
|
|
{
|
|
group.processPlanePositionUpdate();
|
|
|
|
if (!VEHICLE::_ARE_MISSILE_BAYS_DEPLOYED(vehicle))
|
|
{
|
|
VEHICLE::_SET_DEPLOY_MISSILE_BAYS(vehicle, TRUE); // Needs to be called per-tick.
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case AttackerType::PHANTOM:
|
|
{
|
|
int& sound_id = group.sound_id;
|
|
int& ptfx_handle = group.ptfx_handle;
|
|
|
|
if (ptfx_handle == -1)
|
|
{
|
|
if (STREAMING::HAS_NAMED_PTFX_ASSET_LOADED("scr_tn_phantom"))
|
|
{
|
|
GRAPHICS::USE_PARTICLE_FX_ASSET("scr_tn_phantom");
|
|
ptfx_handle = GRAPHICS::START_NETWORKED_PARTICLE_FX_LOOPED_ON_ENTITY("scr_tn_phantom_flames", group.vehicle, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 180.0f, 1.0f, FALSE, TRUE, FALSE, 1065353216, 1065353216, 1065353216, 0);
|
|
}
|
|
else
|
|
{
|
|
STREAMING::REQUEST_NAMED_PTFX_ASSET("scr_tn_phantom");
|
|
}
|
|
}
|
|
else if (!GRAPHICS::DOES_PARTICLE_FX_LOOPED_EXIST(ptfx_handle))
|
|
{
|
|
ptfx_handle = -1;
|
|
}
|
|
|
|
if (sound_id == -1)
|
|
{
|
|
sound_id = AUDIO::GET_SOUND_ID();
|
|
AUDIO::REQUEST_SCRIPT_AUDIO_BANK("DLC_TUNER/DLC_Tuner_Phantom_Car", TRUE, Util::get_session_players_bitflag());
|
|
AUDIO::PLAY_SOUND_FROM_ENTITY(sound_id, "Flames_Loop", group.vehicle, "DLC_Tuner_Halloween_Phantom_Car_Sounds", TRUE, FALSE);
|
|
}
|
|
else if (AUDIO::HAS_SOUND_FINISHED(sound_id))
|
|
{
|
|
AUDIO::RELEASE_SOUND_ID(sound_id);
|
|
sound_id = -1;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void AttackerMgr::onTickAttacker(AttackerGroup& group)
|
|
{
|
|
auto& attacker = group.attacker;
|
|
|
|
if (!attacker.isOwner())
|
|
{
|
|
NETWORK::NETWORK_REQUEST_CONTROL_OF_ENTITY(attacker);
|
|
}
|
|
|
|
switch (group.type)
|
|
{
|
|
case AttackerType::PHANTOM:
|
|
{
|
|
if (ENTITY::IS_ENTITY_VISIBLE(attacker))
|
|
{
|
|
ENTITY::SET_ENTITY_VISIBLE(attacker, FALSE, FALSE);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void AttackerMgr::initAudioBanks(AttackerType type)
|
|
{
|
|
if (*pointers::is_session_started)
|
|
{
|
|
CGameScriptId dummy_script{};
|
|
dummy_script.m_unk = 0;
|
|
dummy_script.m_hash = 0;
|
|
dummy_script.m_name[0] = 0;
|
|
dummy_script.m_timestamp = 0;
|
|
dummy_script.instance_id = -1;
|
|
dummy_script.m_position_hash = 0;
|
|
|
|
for (auto hash : getAudioBankDependencies(type))
|
|
{
|
|
pointers::CAudioBankRequestEvent_Trigger(hash, &dummy_script, true, Util::get_session_players_bitflag());
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<Hash> AttackerMgr::getAudioBankDependencies(AttackerType type) noexcept
|
|
{
|
|
switch (type)
|
|
{
|
|
case AttackerType::PHANTOM:
|
|
return { ATSTRINGHASH("DLC_TUNER/DLC_Tuner_Phantom_Car") };
|
|
}
|
|
|
|
SOUP_ASSERT(false);
|
|
}
|
|
|
|
bool AttackerMgr::isEntityAttacker(AbstractEntity& entity)
|
|
{
|
|
for (auto& g : attackers)
|
|
{
|
|
if (g.attacker == entity || g.vehicle == entity)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void AttackerMgr::lockAttackerCreation(compactplayer_t p)
|
|
{
|
|
locked_players.emplace(p);
|
|
}
|
|
|
|
void AttackerMgr::unlockAttackerCreation(compactplayer_t p)
|
|
{
|
|
SOUP_IF_LIKELY (auto i = locked_players.find(p); i != locked_players.end())
|
|
{
|
|
locked_players.erase(i);
|
|
}
|
|
}
|
|
}
|