Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/ui/Action.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ enum class ActionId {
LEVEL_EDITOR_SET_OBJECT_CLASS_TO_PLACE,
LEVEL_EDITOR_OBJECT_CLASS_TO_PLACE_CHANGED,
LEVEL_EDITOR_SET_LAYER_ENABLED,
LEVEL_EDITOR_CLONE_SELECTION,
FOCUS_GAME_OBJECT,
FOCUS_OBJECT_DATA,
EXPORT_RESOURCE,
Expand Down
30 changes: 24 additions & 6 deletions src/ui/common/editors/Reflection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ class RenderStaticReflectionEditor {
constexpr static size_t arity = 1;
static inline const char* currentMemberName{};
static inline bool defaultOpen{};
static inline const RflClass* currentRflClass{};
static inline const ImGuiTextFilter* filter{};
typedef bool result_type;


Expand All @@ -43,6 +45,10 @@ class RenderStaticReflectionEditor {
return edited;
}

static bool FilterActive() { return filter != nullptr && filter->IsActive(); }
static bool Matches(const RflClass* rflClass) { for (auto& member : rflClass->GetMembers()) if (Matches(member)) return true; return false; }
static bool Matches(const RflClassMember& member) { auto* memberClass = static_cast<const RflClass*>(member.GetClass()); return !FilterActive() || filter->PassFilter(member.GetName()) || memberClass != nullptr && Matches(memberClass); }

template<typename T, bool allowSliders = true>
static bool InputRflScalar(T& obj, const PrimitiveInfo<T>& info) {
if (info.range) {
Expand Down Expand Up @@ -204,6 +210,10 @@ class RenderStaticReflectionEditor {

template<typename F>
static bool visit_field(opaque_obj& obj, const FieldInfo& info, F f) {
if (currentRflClass != nullptr)
for (auto& member : currentRflClass->GetMembers())
if (!strcmp(member.GetName(), info.name) && !Matches(member))
return false;
return NameScope(info.name, [&]() { return f(obj); });
}

Expand All @@ -216,8 +226,8 @@ class RenderStaticReflectionEditor {
static bool visit_struct(opaque_obj& obj, const StructureInfo& info, F f) {
ImGui::PushID(&obj);
bool edited{};
bool isOpen{ ImGui::TreeNodeEx(currentMemberName, RenderStaticReflectionEditor::defaultOpen ? ImGuiTreeNodeFlags_DefaultOpen : ImGuiTreeNodeFlags_None) };
const auto* rflClass = (const he2sdk::ucsl::GameInterface::RflSystem::RflClass*)info.rflClass;
bool isOpen{ ImGui::TreeNodeEx(currentMemberName, RenderStaticReflectionEditor::defaultOpen || FilterActive() ? ImGuiTreeNodeFlags_DefaultOpen : ImGuiTreeNodeFlags_None) };
const auto* rflClass = static_cast<const RflClass*>(info.rflClass);

if (ImGui::BeginDragDropSource()) {
RflDragDropData dndData{ rflClass, obj };
Expand Down Expand Up @@ -251,7 +261,9 @@ class RenderStaticReflectionEditor {

if (isOpen) {
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x * 0.5f);
auto* savedRflClass = currentRflClass; currentRflClass = rflClass;
edited = f(obj);
currentRflClass = savedRflClass;
ImGui::TreePop();
}
ImGui::PopID();
Expand Down Expand Up @@ -343,8 +355,8 @@ class RenderResettableReflectionEditor {
static bool visit_struct(opaque_obj& obj, opaque_obj& orig, const StructureInfo& info, F f) {
ImGui::PushID(&obj);
bool edited{};
bool isOpen{ ImGui::TreeNodeEx(RenderStaticReflectionEditor::currentMemberName, RenderStaticReflectionEditor::defaultOpen ? ImGuiTreeNodeFlags_DefaultOpen : ImGuiTreeNodeFlags_None) };
const auto* rflClass = (const he2sdk::ucsl::GameInterface::RflSystem::RflClass*)info.rflClass;
bool isOpen{ ImGui::TreeNodeEx(RenderStaticReflectionEditor::currentMemberName, RenderStaticReflectionEditor::defaultOpen || RenderStaticReflectionEditor::FilterActive() ? ImGuiTreeNodeFlags_DefaultOpen : ImGuiTreeNodeFlags_None) };
const auto* rflClass = static_cast<const RflClass*>(info.rflClass);

if (ImGui::BeginDragDropSource()) {
RflDragDropData dndData{ rflClass, obj };
Expand Down Expand Up @@ -385,7 +397,9 @@ class RenderResettableReflectionEditor {

if (isOpen) {
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x * 0.5f);
auto* savedRflClass = RenderStaticReflectionEditor::currentRflClass; RenderStaticReflectionEditor::currentRflClass = rflClass;
edited |= f(obj, orig);
RenderStaticReflectionEditor::currentRflClass = savedRflClass;
ImGui::TreePop();
}
ImGui::PopID();
Expand All @@ -398,18 +412,22 @@ class RenderResettableReflectionEditor {
}
};

bool ReflectionEditor(const char* label, void* reflectionData, const hh::fnd::RflClass* rflClass, bool defaultOpen) {
bool ReflectionEditor(const char* label, void* reflectionData, const hh::fnd::RflClass* rflClass, bool defaultOpen, const ImGuiTextFilter* filter) {
ImGui::BeginGroup();
RenderStaticReflectionEditor::defaultOpen = defaultOpen;
RenderStaticReflectionEditor::filter = filter;
bool edited = RenderStaticReflectionEditor::NameScope(label, [&]() { return ucsl::reflection::traversals::traversal<RenderStaticReflectionEditor>{}(reflectionData, ucsl::reflection::providers::rflclass<he2sdk::ucsl::GameInterface>::reflect(rflClass)); });
RenderStaticReflectionEditor::filter = nullptr;
ImGui::EndGroup();
return edited;
}

bool ResettableReflectionEditor(const char* label, void* reflectionData, void* originalReflectionData, const hh::fnd::RflClass* rflClass) {
bool ResettableReflectionEditor(const char* label, void* reflectionData, void* originalReflectionData, const hh::fnd::RflClass* rflClass, const ImGuiTextFilter* filter) {
ImGui::BeginGroup();
RenderStaticReflectionEditor::defaultOpen = false;
RenderStaticReflectionEditor::filter = filter;
bool edited = RenderStaticReflectionEditor::NameScope(label, [&]() { return ucsl::reflection::traversals::traversal<RenderResettableReflectionEditor>{}(reflectionData, originalReflectionData, ucsl::reflection::providers::rflclass<he2sdk::ucsl::GameInterface>::reflect(rflClass)); });
RenderStaticReflectionEditor::filter = nullptr;
ImGui::EndGroup();
return edited;
}
6 changes: 4 additions & 2 deletions src/ui/common/editors/Reflection.h
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
#pragma once

struct ImGuiTextFilter;

inline float rflMinFloatStep{ 0.01f };
inline unsigned int rflSliderCutOff{ 3000 };

bool ReflectionEditor(const char* label, void* reflectionData, const hh::fnd::RflClass* rflClass, bool defaultOpen = false);
bool ReflectionEditor(const char* label, void* reflectionData, const hh::fnd::RflClass* rflClass, bool defaultOpen = false, const ImGuiTextFilter* filter = nullptr);

template<typename T, typename = decltype(T::rflClass)>
static bool Editor(const char* label, T& reflectionData, bool defaultOpen = false) {
return ReflectionEditor(label, &reflectionData, &RESOLVE_STATIC_VARIABLE(T::rflClass), defaultOpen);
}

bool ResettableReflectionEditor(const char* label, void* reflectionData, void* originalReflectionData, const hh::fnd::RflClass* rflClass);
bool ResettableReflectionEditor(const char* label, void* reflectionData, void* originalReflectionData, const hh::fnd::RflClass* rflClass, const ImGuiTextFilter* filter = nullptr);

template<typename T, typename = decltype(T::rflClass)>
static bool Editor(const char* label, T& reflectionData, T& originalReflectionData) {
Expand Down
104 changes: 104 additions & 0 deletions src/ui/operation-modes/behaviors/DragDropPlacement.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#pragma once
#include <imgui_internal.h>
#include <ui/Action.h>
#include <ui/operation-modes/OperationModeBehavior.h>
#include <ui/operation-modes/OperationMode.h>
#include <utilities/math/MathUtils.h>
#include "ForwardDeclarations.h"
#include "Selection.h"

template<typename OpModeContext>
class DragDropPlacementBehavior : public OperationModeBehavior {
using Traits = DragDropPlacementBehaviorTraits<OpModeContext>;
Traits traits;

hh::game::ObjectData* previewObject{};
bool wasDraggingGameObjectClass{};

void ClearPreview() {
if (!previewObject)
return;
traits.DeletePreviewObject(previewObject);
previewObject = nullptr;
}

static bool IsMouseOverScene() {
ImGuiContext& g = *GImGui;
return g.HoveredWindow == nullptr || (g.HoveredWindow->Flags & ImGuiWindowFlags_NoInputs) != 0;
}

bool GetDropLocation(csl::math::Vector3& outLocation) const {
if (auto* gameManager = hh::game::GameManager::GetInstance())
if (auto* cameraSrv = gameManager->GetService<hh::game::CameraManager>())
if (auto* camera = cameraSrv->GetTopComponent(0)) {
Eigen::Projective3f inverseCameraMatrix =
camera->viewportData.GetInverseViewMatrix() * camera->viewportData.projMatrix.inverse();
Ray3f ray = ScreenPosToWorldRay(ImGui::GetMousePos(), inverseCameraMatrix);

if (auto* physWorld = gameManager->GetService<hh::physics::PhysicsWorld>()) {
csl::ut::MoveArray<hh::physics::PhysicsQueryResult> results{ hh::fnd::MemoryRouter::GetTempAllocator() };
if (physWorld->RayCastAllHits(ray.start, ray.end, 0xFFFFFFFF, results)) {
for (auto& result : results) {
if (result.collider->filterCategory == 10) {
outLocation = result.hitLocation;
return true;
}
}
}
}

Eigen::Vector3f fallback = ray.start + (ray.end - ray.start).normalized() * 50.0f;
outLocation = { fallback.x(), fallback.y(), fallback.z() };
return true;
}
return false;
}

public:
static constexpr unsigned int id = 14;
virtual unsigned int GetId() override { return id; }

DragDropPlacementBehavior(csl::fnd::IAllocator* allocator, OperationMode<OpModeContext>& operationMode)
: OperationModeBehavior{ allocator, operationMode }, traits{ operationMode.GetContext() } {}

~DragDropPlacementBehavior() { ClearPreview(); }

void Render() override {
auto* payload = ImGui::GetDragDropPayload();
bool isDraggingGameObjectClass = payload != nullptr && strcmp(payload->DataType, "GameObjectClass") == 0;

if (isDraggingGameObjectClass && traits.CanPlace()) {
bool overScene = IsMouseOverScene();
csl::math::Vector3 location{};
bool hasLocation = overScene && GetDropLocation(location);

if (hasLocation) {
if (!previewObject) {
auto* objectClass = *static_cast<const hh::game::GameObjectClass* const*>(payload->Data);
previewObject = traits.SpawnPreviewObject(objectClass, location);
} else {
traits.MovePreviewObject(previewObject, location);
}
}

if (overScene && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
hh::game::ObjectData* toFinalize = previewObject;
if (!toFinalize && hasLocation) {
auto* objectClass = *static_cast<const hh::game::GameObjectClass* const*>(payload->Data);
toFinalize = traits.SpawnPreviewObject(objectClass, location);
}
if (toFinalize) {
previewObject = nullptr;
if (auto* selection = operationMode.GetBehavior<SelectionBehavior<OpModeContext>>())
selection->Select(toFinalize);
Dispatch(SceneChangedAction{});
}
}
} else if (!isDraggingGameObjectClass && wasDraggingGameObjectClass) {
ClearPreview();
}

wasDraggingGameObjectClass = isDraggingGameObjectClass;
}
};

1 change: 1 addition & 0 deletions src/ui/operation-modes/behaviors/ForwardDeclarations.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ template<typename OpModeContext> struct MousePicking3DBehaviorTraits;
template<typename OpModeContext> struct MousePickingPhysicsBehaviorTraits;
template<typename OpModeContext> struct MousePicking3DRecursiveRaycastBehaviorTraits;
template<typename OpModeContext> struct ObjectLocationVisual3DBehaviorTraits;
template<typename OpModeContext> struct DragDropPlacementBehaviorTraits;
template<typename OpModeContext> struct PlacementBehaviorTraits;
template<typename OpModeContext> struct ScreenSpaceManipulationBehaviorTraits;
template<typename OpModeContext> struct SelectionBehaviorTraits;
Expand Down
6 changes: 6 additions & 0 deletions src/ui/operation-modes/behaviors/Gizmo.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ template<typename OpModeContext>
class GizmoBehavior : public OperationModeBehavior {
ImGuizmo::OPERATION gizmoOperation{ ImGuizmo::TRANSLATE };
ImGuizmo::MODE gizmoMode{ ImGuizmo::LOCAL };
bool wasUsing{ false };

public:
using ChangeCoordinateSystemAction = Action<ActionId::CHANGE_COORDINATE_SYSTEM>;
Expand Down Expand Up @@ -159,6 +160,11 @@ class GizmoBehavior : public OperationModeBehavior {
ImGuiIO& io = ImGui::GetIO();
ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y);

bool isUsing = ImGuizmo::IsUsing();
if (isUsing && !wasUsing && (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)))
Dispatch(Action<ActionId::LEVEL_EDITOR_CLONE_SELECTION>{});
wasUsing = isUsing;

if (ImGuizmo::Manipulate(camera->viewportData.viewMatrix.data(), camera->viewportData.projMatrix.data(), gizmoOperation, gizmoMode, selectionTransform.data(), NULL, NULL))
selTransform->SetSelectionTransform(selectionTransform);
}
Expand Down
24 changes: 23 additions & 1 deletion src/ui/operation-modes/behaviors/GroundContextMenu.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ class GroundContextMenuBehavior : public OperationModeBehavior {

virtual void Render() override {
#ifndef DEVTOOLS_TARGET_SDK_wars
auto& updater = hh::game::GameApplication::GetInstance()->GetGameUpdater();
static bool restoreObjectPause = false;
if (restoreObjectPause) {
updater.flags.set(hh::game::GameUpdater::Flags::OBJECT_PAUSE, true);
restoreObjectPause = false;
}

auto* mousePicking = operationMode.GetBehavior<MousePickingBehavior<OpModeContext>>();

if (mousePicking->picked && mousePicking->mouseButton == ImGuiMouseButton_Right)
Expand All @@ -29,8 +36,23 @@ class GroundContextMenuBehavior : public OperationModeBehavior {
if (auto* levelInfo = hh::game::GameManager::GetInstance()->GetService<app::level::LevelInfo>())
if (auto* player = static_cast<app::player::Player*>(hh::fnd::MessageManager::GetInstance()->GetMessengerByHandle(levelInfo->GetPlayerObject(0))))
if (auto* playerKine = player->GetComponent<app::player::GOCPlayerKinematicParams>())
if (ImGui::Selectable("Teleport player"))
if (ImGui::Selectable("Teleport player")) {
playerKine->SetPosition({ pickedLocation.x(), pickedLocation.y(), pickedLocation.z(), 0.0f });
if (updater.flags.test(hh::game::GameUpdater::Flags::OBJECT_PAUSE)) {
updater.flags.set(hh::game::GameUpdater::Flags::OBJECT_PAUSE, false);
updater.flags.set(hh::game::GameUpdater::Flags::DEBUG_STEP_FRAME, true);
restoreObjectPause = true;
} else if (updater.flags.test(hh::game::GameUpdater::Flags::DEBUG_PAUSE)) {
updater.flags.set(hh::game::GameUpdater::Flags::DEBUG_STEP_FRAME, true);
}
#ifdef DEVTOOLS_TARGET_SDK_miller
if (auto* physAnim = player->GetComponent<hh::pba::GOCPhysicalAnimationBullet>()) {
physAnim->SetEnabled(false);
physAnim->SetEnabled(true);
physAnim->Reset();
}
#endif
}
ImGui::EndPopup();
};
#endif
Expand Down
1 change: 1 addition & 0 deletions src/ui/operation-modes/modes/level-editor/Actions.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ namespace ui::operation_modes::modes::level_editor {
using SetObjectClassToPlaceAction = Action<ActionId::LEVEL_EDITOR_SET_OBJECT_CLASS_TO_PLACE, const hh::game::GameObjectClass*>;
using ObjectClassToPlaceChangedAction = Action<ActionId::LEVEL_EDITOR_OBJECT_CLASS_TO_PLACE_CHANGED>;
using SetLayerEnabledAction = Action<ActionId::LEVEL_EDITOR_SET_LAYER_ENABLED, SetLayerEnabledPayload>;
using CloneSelectionAction = Action<ActionId::LEVEL_EDITOR_CLONE_SELECTION>;
}
24 changes: 24 additions & 0 deletions src/ui/operation-modes/modes/level-editor/Behaviors.h
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,29 @@ namespace ui::operation_modes::modes::level_editor {
static constexpr bool allowRotate = true;
static constexpr bool allowScale = false;
};

template<> struct DragDropPlacementBehaviorTraits<Context> : BehaviorTraitsImpl<Context> {
using BehaviorTraitsImpl::BehaviorTraitsImpl;
bool CanPlace() const { return context.placementTargetLayer != nullptr; }
hh::game::ObjectData* SpawnPreviewObject(const hh::game::GameObjectClass* objectClass, const csl::math::Vector3& location) {
auto* savedClass = context.objectClassToPlace;
context.objectClassToPlace = objectClass;
auto* obj = context.SpawnObject(location);
context.objectClassToPlace = savedClass;
return obj;
}
void MovePreviewObject(hh::game::ObjectData* obj, const csl::math::Vector3& location) {
Eigen::Affine3f transform = ObjectTransformDataToAffine3f(obj->transform);
transform.translation() = Eigen::Vector3f{ location.x(), location.y(), location.z() };
UpdateAbsoluteTransform(transform, *obj);
context.RecalculateDependentTransforms(obj);
}
void DeletePreviewObject(hh::game::ObjectData* obj) {
csl::ut::MoveArray<hh::game::ObjectData*> objs{ hh::fnd::MemoryRouter::GetTempAllocator() };
objs.push_back(obj);
context.DeleteObjects(objs);
}
};
}

#include <ui/operation-modes/behaviors/Clipboard.h>
Expand All @@ -200,3 +223,4 @@ namespace ui::operation_modes::modes::level_editor {
#include <ui/operation-modes/behaviors/GroundContextMenu.h>
#include <ui/operation-modes/behaviors/DebugCommentsVisual.h>
#include <ui/operation-modes/behaviors/ObjectLocationVisual3D.h>
#include <ui/operation-modes/behaviors/DragDropPlacement.h>
32 changes: 31 additions & 1 deletion src/ui/operation-modes/modes/level-editor/LevelEditor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,30 @@ namespace ui::operation_modes::modes::level_editor {
AddBehavior<ObjectLocationVisual3DBehavior>();
AddBehavior<GroundContextMenuBehavior>();
AddBehavior<DebugCommentsVisualBehavior>();
AddBehavior<DragDropPlacementBehavior>();

auto* gameManager = GameManager::GetInstance();

gameManager->AddListener(this);

if (auto* objWorld = gameManager->GetService<ObjectWorld>())
if (auto* objWorld = gameManager->GetService<ObjectWorld>()) {
objWorld->AddWorldListener(this);

auto& chunks = objWorld->GetWorldChunks();
if (!chunks.empty()) {
auto* firstChunk = chunks[0];
ProcessAction(SetFocusedChunkAction{ firstChunk });

auto& layers = firstChunk->GetLayers();
if (!layers.empty()) {
ObjectWorldChunkLayer* firstLayer = layers[0];
for (auto* layer : layers)
if (strcmp(layer->GetName(), firstLayer->GetName()) < 0)
firstLayer = layer;
GetContext().placementTargetLayer = firstLayer;
}
}
}
}

LevelEditor::~LevelEditor() {
Expand Down Expand Up @@ -104,6 +121,19 @@ namespace ui::operation_modes::modes::level_editor {
GetContext().SetLayerEnabled(payload.layerName, payload.enabled);
break;
}
case CloneSelectionAction::id: {
auto& context = GetContext();
if (!context.placementTargetLayer) break;
auto& selection = GetBehavior<SelectionBehavior<Context>>()->GetSelection();
csl::ut::MoveArray<ObjectData*> clones{ hh::fnd::MemoryRouter::GetTempAllocator() };
for (auto* obj : selection) {
auto* clone = context.CopyObjectForPlacement(obj);
context.SpawnObject(clone);
clones.push_back(clone);
}
GetBehavior<SelectionBehavior<Context>>()->Select(clones);
break;
}
}
}

Expand Down
Loading