diff --git a/files.cmake b/files.cmake index 53aa7a2636..d0bcad2d30 100644 --- a/files.cmake +++ b/files.cmake @@ -1450,6 +1450,7 @@ set(DUSK_FILES src/dusk/imgui/ImGuiBloomWindow.hpp src/dusk/imgui/ImGuiMenuTools.cpp src/dusk/imgui/ImGuiMenuTools.hpp + src/dusk/imgui/ImGuiActorSpawner.cpp src/dusk/imgui/ImGuiProcessOverlay.cpp src/dusk/imgui/ImGuiCameraOverlay.cpp src/dusk/imgui/ImGuiHeapOverlay.cpp diff --git a/src/dusk/imgui/ImGuiActorSpawner.cpp b/src/dusk/imgui/ImGuiActorSpawner.cpp new file mode 100644 index 0000000000..f3aefd4c41 --- /dev/null +++ b/src/dusk/imgui/ImGuiActorSpawner.cpp @@ -0,0 +1,142 @@ +#include "imgui.h" + +#include "ImGuiMenuTools.hpp" +#include "d/actor/d_a_alink.h" +#include "d/d_com_inf_game.h" +#include "f_op/f_op_actor_mng.h" +#include "SSystem/SComponent/c_sxyz.h" +#include "SSystem/SComponent/c_xyz.h" + +namespace dusk { +namespace { + +struct ActorSpawnerState { + int actorId = 0; + int params = -1; + int argument = -1; + int angleX = 0; + int angleY = 0; + int angleZ = 0; + float scaleX = 1.0f; + float scaleY = 1.0f; + float scaleZ = 1.0f; + bool usePlayerRoom = true; + int manualRoom = 0; + int spawnCount = 1; + bool hasResult = false; + unsigned int lastResult = 0; + int lastAttempted = 0; +}; + +ActorSpawnerState s_state; + +} // namespace + +void ImGuiMenuTools::ShowActorSpawner() { + if (!m_showActorSpawner) { + return; + } + + if (!ImGui::Begin("Actor Spawner", &m_showActorSpawner)) { + ImGui::End(); + return; + } + + daAlink_c* player = (daAlink_c*)dComIfGp_getPlayer(0); + + ImGui::SeparatorText("Actor"); + ImGui::InputInt("Actor ID", &s_state.actorId); + ImGui::InputInt("Params (hex)", &s_state.params, 0, 0, ImGuiInputTextFlags_CharsHexadecimal); + ImGui::InputInt("Argument", &s_state.argument); + s_state.argument = (s_state.argument < -128) ? -128 : (s_state.argument > 127) ? 127 : s_state.argument; + + ImGui::SeparatorText("Angle"); + ImGui::InputInt("Angle X", &s_state.angleX); + ImGui::InputInt("Angle Y", &s_state.angleY); + ImGui::InputInt("Angle Z", &s_state.angleZ); + + ImGui::SeparatorText("Scale"); + ImGui::InputFloat("Scale X", &s_state.scaleX, 0.1f, 1.0f); + ImGui::InputFloat("Scale Y", &s_state.scaleY, 0.1f, 1.0f); + ImGui::InputFloat("Scale Z", &s_state.scaleZ, 0.1f, 1.0f); + + ImGui::SeparatorText("Spawn"); + ImGui::InputInt("Count", &s_state.spawnCount); + if (s_state.spawnCount < 1) { + s_state.spawnCount = 1; + } + + ImGui::SeparatorText("Position"); + ImGui::Checkbox("Use player room", &s_state.usePlayerRoom); + if (!s_state.usePlayerRoom) { + ImGui::InputInt("Room No", &s_state.manualRoom); + } + + if (player != nullptr) { + ImGui::Text("Spawn pos: %.2f, %.2f, %.2f", + player->current.pos.x, player->current.pos.y, player->current.pos.z); + } else { + ImGui::TextDisabled("Player not available"); + } + + ImGui::Separator(); + + bool canSpawn = player != nullptr; + if (!canSpawn) { + ImGui::BeginDisabled(); + } + + if (ImGui::Button("Spawn", ImVec2(-1, 0))) { + cXyz pos = player->current.pos; + csXyz angle; + angle.set((s16)s_state.angleX, (s16)s_state.angleY, (s16)s_state.angleZ); + cXyz scale(s_state.scaleX, s_state.scaleY, s_state.scaleZ); + int roomNo = s_state.usePlayerRoom ? player->current.roomNo : s_state.manualRoom; + + layer_class* savedLayer = fpcLy_CurrentLayer(); + base_process_class* playScene = fpcM_SearchByName(fpcNm_PLAY_SCENE_e); + if (playScene != nullptr) { + fpcLy_SetCurrentLayer(&((process_node_class*)playScene)->layer); + } + + s_state.lastResult = 0; + s_state.lastAttempted = s_state.spawnCount; + for (int i = 0; i < s_state.spawnCount; ++i) { + unsigned int result = fopAcM_create( + (s16)s_state.actorId, + (u32)s_state.params, + &pos, + roomNo, + &angle, + &scale, + (s8)s_state.argument + ); + if (result != 0) { + s_state.lastResult = result; + } + } + s_state.hasResult = true; + + fpcLy_SetCurrentLayer(savedLayer); + } + + if (!canSpawn) { + ImGui::EndDisabled(); + } + + if (s_state.hasResult) { + if (s_state.lastResult != 0) { + if (s_state.lastAttempted == 1) { + ImGui::Text("Spawned: proc ID %u", s_state.lastResult); + } else { + ImGui::Text("Spawned %d (last proc ID %u)", s_state.lastAttempted, s_state.lastResult); + } + } else { + ImGui::TextColored(ImVec4(1, 0.4f, 0.4f, 1), "Spawn failed (returned 0)"); + } + } + + ImGui::End(); +} + +} // namespace dusk diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index dca2a88d79..de62596308 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -375,6 +375,7 @@ namespace dusk { m_menuTools.ShowAudioDebug(); m_menuTools.ShowSaveEditor(); m_menuTools.ShowStateShare(); + m_menuTools.ShowActorSpawner(); } // Hide mouse cursor if the F1 menu is not open and the cursor is idle for 3 seconds. diff --git a/src/dusk/imgui/ImGuiMenuTools.cpp b/src/dusk/imgui/ImGuiMenuTools.cpp index 306298c28d..1cb0ec474c 100644 --- a/src/dusk/imgui/ImGuiMenuTools.cpp +++ b/src/dusk/imgui/ImGuiMenuTools.cpp @@ -107,6 +107,7 @@ namespace dusk { ImGui::MenuItem("Audio Debug", hotkeys::SHOW_AUDIO_DEBUG, &m_showAudioDebug); ImGui::MenuItem("Bloom", nullptr, &m_showBloomWindow); ImGui::MenuItem("Stub Log", nullptr, &m_showStubLog); + ImGui::MenuItem("Actor Spawner", nullptr, &m_showActorSpawner); if (!dusk::IsGameLaunched) { ImGui::EndDisabled(); diff --git a/src/dusk/imgui/ImGuiMenuTools.hpp b/src/dusk/imgui/ImGuiMenuTools.hpp index 6b925b7bf3..1c17ddbbe9 100644 --- a/src/dusk/imgui/ImGuiMenuTools.hpp +++ b/src/dusk/imgui/ImGuiMenuTools.hpp @@ -28,6 +28,7 @@ namespace dusk { void ShowSaveEditor(); void ShowStateShare(); void ShowInputViewer(); + void ShowActorSpawner(); private: bool m_showDebugOverlay = false; @@ -70,6 +71,7 @@ namespace dusk { bool m_showInputViewer = false; bool m_showInputViewerGyro = false; + bool m_showActorSpawner = false; int m_inputOverlayCorner = 3; std::string m_controllerName; };