Add SpawnCharacterAction

- Added ModMenuModule::SpawnPedActionSegment
- Added ModMenuModule::SpawnCharacterAction
This commit is contained in:
izawartka 2026-08-09 23:37:31 +02:00
parent 2550ab2910
commit ff91ccfe17
6 changed files with 312 additions and 0 deletions

View File

@ -458,6 +458,7 @@
<ClInclude Include="src\modules\modmenu\quick-action-registry.h" />
<ClInclude Include="src\modules\modmenu\quick-actions\clear-wanted-level.h" />
<ClInclude Include="src\modules\modmenu\quick-actions\explode-vehicle.h" />
<ClInclude Include="src\modules\modmenu\quick-actions\spawn-character.h" />
<ClInclude Include="src\modules\modmenu\quick-actions\switch-camera-mode.h" />
<ClInclude Include="src\modules\modmenu\quick-actions\explode-all-vehicles.h" />
<ClInclude Include="src\modules\modmenu\quick-actions\fix-vehicle.h" />
@ -496,6 +497,7 @@
<ClInclude Include="src\modules\modmenu\menus\saved-vehicles-menu.h" />
<ClInclude Include="src\modules\modmenu\segments\segments.h" />
<ClInclude Include="src\modules\modmenu\segments\spawn-object-segment.h" />
<ClInclude Include="src\modules\modmenu\segments\spawn-ped-action-segment.h" />
<ClInclude Include="src\modules\modmenu\segments\spawn-ped-segment.h" />
<ClInclude Include="src\modules\modmenu\segments\spawn-saved-vehicle-segment.h" />
<ClInclude Include="src\modules\modmenu\segments\spawn-vehicle-segment.h" />
@ -748,6 +750,7 @@
<ClCompile Include="src\modules\modmenu\menus\world-menu.cpp" />
<ClCompile Include="src\modules\modmenu\quick-action-manager.cpp" />
<ClCompile Include="src\modules\modmenu\quick-actions\explode-vehicle.cpp" />
<ClCompile Include="src\modules\modmenu\quick-actions\spawn-character.cpp" />
<ClCompile Include="src\modules\modmenu\quick-actions\switch-camera-mode.cpp" />
<ClCompile Include="src\modules\modmenu\quick-actions\clear-wanted-level.cpp" />
<ClCompile Include="src\modules\modmenu\quick-actions\explode-all-vehicles.cpp" />
@ -786,6 +789,7 @@
<ClCompile Include="src\modules\modmenu\segments\position-segment.cpp" />
<ClCompile Include="src\modules\modmenu\menus\saved-vehicles-menu.cpp" />
<ClCompile Include="src\modules\modmenu\segments\spawn-object-segment.cpp" />
<ClCompile Include="src\modules\modmenu\segments\spawn-ped-action-segment.cpp" />
<ClCompile Include="src\modules\modmenu\segments\spawn-ped-segment.cpp" />
<ClCompile Include="src\modules\modmenu\segments\spawn-saved-vehicle-segment.cpp" />
<ClCompile Include="src\modules\modmenu\segments\spawn-vehicle-segment.cpp" />

View File

@ -226,6 +226,8 @@
<ClCompile Include="src\modules\keyboard\events\key-down.cpp" />
<ClCompile Include="src\modules\keyboard\events\key-up.cpp" />
<ClCompile Include="src\modules\keyboard\root.cpp" />
<ClCompile Include="src\modules\modmenu\segments\spawn-ped-action-segment.cpp" />
<ClCompile Include="src\modules\modmenu\quick-actions\spawn-character.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\converters\ammo.h" />
@ -586,6 +588,8 @@
<ClInclude Include="src\modules\keyboard\events\keyboard.h" />
<ClInclude Include="src\modules\keyboard\keyboard.h" />
<ClInclude Include="src\modules\keyboard\root.h" />
<ClInclude Include="src\modules\modmenu\segments\spawn-ped-action-segment.h" />
<ClInclude Include="src\modules\modmenu\quick-actions\spawn-character.h" />
</ItemGroup>
<ItemGroup>
<None Include="GTA2.props" />

View File

@ -0,0 +1,109 @@
#include "spawn-character.h"
#include "../cheats/ped-templates.h"
#include "../toast-manager.h"
#include "../utils/spawn-ped-utils.h"
#include "../quick-action-registry.h"
static const std::string typeId = "ModMenu_SpawnCharacter";
static const std::wstring typeLabel = L"Spawn character";
ModMenuModule::SpawnCharacterAction::SpawnCharacterAction()
{
m_label = typeLabel;
}
ModMenuModule::SpawnCharacterAction::~SpawnCharacterAction()
{}
const std::string& ModMenuModule::SpawnCharacterAction::GetTypeId()
{
return typeId;
}
const std::wstring& ModMenuModule::SpawnCharacterAction::GetTypeLabel()
{
return typeLabel;
}
ModMenuModule::SpawnPedActionSegment* ModMenuModule::SpawnCharacterAction::CreateSegmentInstance()
{
return new SpawnPedActionSegment();
}
void ModMenuModule::SpawnCharacterAction::Execute()
{
if (!m_data.has_value()) {
spdlog::error("SpawnCharacterAction::Execute: No data to execute action.");
return;
}
PedTemplatesCheat* pedTemplatesCheat = PedTemplatesCheat::GetInstance();
if (!pedTemplatesCheat->IsEnabled()) {
spdlog::error("SpawnCharacterAction::Execute: PedTemplatesCheat is not enabled.");
return;
}
const SpawnPedActionSegmentData& data = m_data.value();
const PedTemplate* pedTemplate = pedTemplatesCheat->GetTemplate(data.pedTemplateName);
if (!pedTemplate) {
ToastManager::GetInstance()->Show({ L"Failed to spawn " + data.pedTemplateName + L",", ToastType::Error});
ToastManager::GetInstance()->Show({ L"Character template not found", ToastType::Error });
return;
}
if (Utils::SpawnPed::SpawnPedAtPlayer(pedTemplate->data, data.inNearestCar)) {
ModMenuModule::ToastManager::GetInstance()->Show({ L"Spawned " + data.pedTemplateName });
}
else {
ModMenuModule::ToastManager::GetInstance()->Show({ L"Failed to spawn " + data.pedTemplateName, ToastType::Error });
}
}
const std::wstring& ModMenuModule::SpawnCharacterAction::GetLabel() const
{
return m_label;
}
std::vector<uint8_t> ModMenuModule::SpawnCharacterAction::SerializeData() const
{
if (!m_data.has_value()) {
spdlog::error("SpawnCharacterAction::SerializeData: No data to serialize.");
return {};
}
const SpawnPedActionSegmentData& data = m_data.value();
std::vector<uint8_t> buffer;
size_t bufferSize = data.pedTemplateName.size() * sizeof(wchar_t) + 1;
buffer.resize(bufferSize);
std::memcpy(buffer.data(), data.pedTemplateName.data(), bufferSize);
buffer[buffer.size() - 1] = data.inNearestCar ? 1 : 0;
return buffer;
}
bool ModMenuModule::SpawnCharacterAction::DeserializeData(const std::vector<uint8_t>& data)
{
if ((data.size() - 1) % sizeof(wchar_t) != 0) {
spdlog::error("SpawnCharacterAction::DeserializeData: Invalid data size.");
return false;
}
size_t wcharCount = (data.size() - 1) / sizeof(wchar_t);
std::wstring pedTemplateName(wcharCount, L'\0');
std::memcpy(&pedTemplateName[0], data.data(), data.size() - 1);
bool inNearestCar = data[data.size() - 1] != 0;
m_data = SpawnPedActionSegmentData{ pedTemplateName, inNearestCar };
OnDataChange();
return true;
}
void ModMenuModule::SpawnCharacterAction::OnDataChange()
{
if (m_data.has_value()) {
m_label = L"Spawn " + m_data->pedTemplateName;
}
else {
m_label = typeLabel;
}
}
REGISTER_QUICK_ACTION(SpawnCharacterAction)

View File

@ -0,0 +1,26 @@
#pragma once
#include "../common.h"
#include "../quick-action-base.h"
#include "../segments/spawn-ped-action-segment.h"
namespace ModMenuModule {
class SpawnCharacterAction : public QuickActionWithSegment<SpawnPedActionSegmentData> {
public:
SpawnCharacterAction();
virtual ~SpawnCharacterAction();
static const std::string& GetTypeId();
static const std::wstring& GetTypeLabel();
static SpawnPedActionSegment* CreateSegmentInstance();
void Execute() override;
const std::wstring& GetLabel() const override;
std::vector<uint8_t> SerializeData() const override;
bool DeserializeData(const std::vector<uint8_t>& data) override;
private:
virtual void OnDataChange() override;
std::wstring m_label;
};
}

View File

@ -0,0 +1,134 @@
#include "spawn-ped-action-segment.h"
#include "../cheats/ped-templates.h"
#include "../root.h"
#include "../../../converters/yes-no.h"
#include "ped-preview-segment.h"
#include "../toast-manager.h"
ModMenuModule::SpawnPedActionSegment::SpawnPedActionSegment()
{
m_pedPreviewSegment = SegmentSupport::CreateSegment<PedPreviewSegment>();
}
ModMenuModule::SpawnPedActionSegment::~SpawnPedActionSegment()
{
SegmentSupport::DeleteSegment(m_pedPreviewSegment);
}
bool ModMenuModule::SpawnPedActionSegment::ValidateSegment()
{
if (!m_pedPreviewSegment || !m_templateNameController) return false;
PedTemplatesCheat* pedTemplatesCheat = PedTemplatesCheat::GetInstance();
const std::wstring& pedTemplateName = m_templateNameController->GetValue().value();
const auto* pedTemplate = pedTemplatesCheat->GetTemplate(pedTemplateName);
if (!pedTemplate) {
spdlog::warn(L"SpawnPedActionSegment: Validation failed, ped template \"{}\" not found.", pedTemplateName);
ToastManager::GetInstance()->Show({ L"Selected template not found", ToastType::Warning });
return false;
}
return true;
}
std::optional<ModMenuModule::SpawnPedActionSegmentData> ModMenuModule::SpawnPedActionSegment::GetSegmentData() const
{
if (!m_templateNameController || !m_inNearestCarController) {
spdlog::warn("SpawnPedActionSegment: Cannot get segment data, controllers not initialized");
return std::nullopt;
}
return SpawnPedActionSegmentData{
m_templateNameController->GetValue().value_or(L""),
m_inNearestCarController->GetValue().value_or(false)
};
}
bool ModMenuModule::SpawnPedActionSegment::SetSegmentData(const SpawnPedActionSegmentData& data)
{
if (!m_templateNameController || !m_inNearestCarController) {
spdlog::warn("SpawnPedActionSegment: Cannot set segment data, controllers not initialized");
return false;
}
m_templateNameController->SetValue(data.pedTemplateName);
m_inNearestCarController->SetValue(data.inNearestCar);
UpdatePreview();
return true;
}
bool ModMenuModule::SpawnPedActionSegment::Attach(ModMenuModule::MenuBase* menu, UiModule::Component* parent)
{
SegmentBase::CreateSegment(menu, parent);
UiModule::RootModule* uiRoot = UiModule::RootModule::GetInstance();
const auto& options = ModMenuModule::RootModule::GetInstance()->GetOptions();
PersistenceModule::PersistenceManager* persistence = PersistenceModule::PersistenceManager::GetInstance();
// template name
PedTemplatesCheat* pedTemplatesCheat = PedTemplatesCheat::GetInstance();
const auto& templateNameOptions = pedTemplatesCheat->GetTemplatesList();
if (!templateNameOptions.size()) {
spdlog::warn("SpawnPedActionSegment: No ped templates available");
return false;
}
auto* templateNameText = m_menuController->CreateItem<UiModule::Text>(m_vertCont, L"", options.textSize);
m_templateNameController = m_menuController->CreateLatestItemController<UiModule::SelectController<std::wstring>>(
templateNameText,
templateNameOptions,
templateNameOptions[0],
UiModule::SelectControllerOptions{ L"Template: #", L"#" }
);
m_templateNameController->SetSaveCallback(std::bind(&SpawnPedActionSegment::OnTemplateControllerSave, this, std::placeholders::_1));
// in nearest car
auto* inNearestCarText = m_menuController->CreateItem<UiModule::Text>(m_vertCont, L"", options.textSize);
m_inNearestCarController = m_menuController->CreateLatestItemController<UiModule::SelectController<bool>>(
inNearestCarText,
std::vector<bool>{ false, true },
false,
UiModule::SelectControllerOptions{ L"In nearest vehicle: #", L"#" }
);
m_inNearestCarController->SetConverter<YesNoConverter>();
// sprite preview
uiRoot->AddComponent<UiModule::Spacer>(m_vertCont, 0, options.menuSpacerHeight);
UpdatePreview();
SegmentSupport::AttachSegment(m_pedPreviewSegment, menu, m_vertCont);
return true;
}
void ModMenuModule::SpawnPedActionSegment::Detach()
{
m_templateNameController = nullptr;
m_inNearestCarController = nullptr;
SegmentSupport::DetachSegment(m_pedPreviewSegment);
SegmentBase::DestroySegment();
}
void ModMenuModule::SpawnPedActionSegment::OnTemplateControllerSave(const std::wstring& templateName)
{
UpdatePreview();
}
void ModMenuModule::SpawnPedActionSegment::UpdatePreview()
{
if (!m_pedPreviewSegment || !m_templateNameController) return;
PedTemplatesCheat* pedTemplatesCheat = PedTemplatesCheat::GetInstance();
const std::wstring& pedTemplateName = m_templateNameController->GetValue().value();
const auto* pedTemplate = pedTemplatesCheat->GetTemplate(pedTemplateName);
if (!pedTemplate) {
spdlog::warn(L"SpawnPedActionSegment: Cannot update preview, ped template \"{}\" not found.", pedTemplateName);
return;
}
m_pedPreviewSegment->SetPedData(pedTemplate->data);
}

View File

@ -0,0 +1,35 @@
#pragma once
#include "../common.h"
#include "../segment-base.h"
#include "../segment-support.h"
namespace ModMenuModule {
class PedPreviewSegment;
struct SpawnPedActionSegmentData {
std::wstring pedTemplateName;
bool inNearestCar;
};
class SpawnPedActionSegment : public Segment<SpawnPedActionSegmentData>, public SegmentSupport {
public:
SpawnPedActionSegment();
virtual ~SpawnPedActionSegment();
bool ValidateSegment();
virtual std::optional<SpawnPedActionSegmentData> GetSegmentData() const override;
virtual bool SetSegmentData(const SpawnPedActionSegmentData& data) override;
private:
virtual bool Attach(ModMenuModule::MenuBase* menu, UiModule::Component* parent) override;
virtual void Detach() override;
void OnTemplateControllerSave(const std::wstring& templateName);
void UpdatePreview();
UiModule::SelectController<std::wstring>* m_templateNameController = nullptr;
UiModule::SelectController<bool>* m_inNearestCarController = nullptr;
PedPreviewSegment* m_pedPreviewSegment = nullptr;
};
}