mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-08-26 14:56:52 -04:00
Compare commits
13 Commits
rm-typo
...
better-tools
| Author | SHA1 | Date | |
|---|---|---|---|
| fbe7b57132 | |||
| d3f10221b8 | |||
| 75fd5ad1c4 | |||
| bf4c727d04 | |||
| ea1f50fe27 | |||
| 99180d02f7 | |||
| 5b64e4fd2c | |||
| 692c04e936 | |||
| e17d0f21fc | |||
| 91e77b1051 | |||
| 0b1a2c10b6 | |||
| e855e6471f | |||
| 848f635798 |
+12
-5
@@ -244,7 +244,7 @@ set(DOLZEL_FILES
|
|||||||
src/CaptureScreen.cpp
|
src/CaptureScreen.cpp
|
||||||
)
|
)
|
||||||
if(DEBUG)
|
if(DEBUG)
|
||||||
list(APPEND DOLZEL_FILES src/d/d_event_debug.cpp)
|
list(APPEND DOLZEL_FILES src/d/d_event_debug.cpp)
|
||||||
endif(DEBUG)
|
endif(DEBUG)
|
||||||
|
|
||||||
set(Z2AUDIOLIB_FILES
|
set(Z2AUDIOLIB_FILES
|
||||||
@@ -1404,10 +1404,10 @@ set(REL_FILES
|
|||||||
)
|
)
|
||||||
|
|
||||||
set(DOLPHIN_FILES
|
set(DOLPHIN_FILES
|
||||||
libs/dolphin/src/gf/GFGeometry.cpp
|
libs/dolphin/src/gf/GFGeometry.cpp
|
||||||
libs/dolphin/src/gf/GFLight.cpp
|
libs/dolphin/src/gf/GFLight.cpp
|
||||||
libs/dolphin/src/gf/GFPixel.cpp
|
libs/dolphin/src/gf/GFPixel.cpp
|
||||||
libs/dolphin/src/gf/GFTev.cpp
|
libs/dolphin/src/gf/GFTev.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(DUSK_FILES
|
set(DUSK_FILES
|
||||||
@@ -1433,11 +1433,16 @@ set(DUSK_FILES
|
|||||||
src/dusk/dvd_asset.hpp
|
src/dusk/dvd_asset.hpp
|
||||||
src/dusk/extras.c
|
src/dusk/extras.c
|
||||||
src/dusk/frame_interpolation.cpp
|
src/dusk/frame_interpolation.cpp
|
||||||
|
src/dusk/commands.cpp
|
||||||
|
src/dusk/commands.hpp
|
||||||
src/dusk/game_clock.cpp
|
src/dusk/game_clock.cpp
|
||||||
src/dusk/game_mode.cpp
|
src/dusk/game_mode.cpp
|
||||||
src/dusk/gamepad_color.cpp
|
src/dusk/gamepad_color.cpp
|
||||||
src/dusk/globals.cpp
|
src/dusk/globals.cpp
|
||||||
src/dusk/gyro.cpp
|
src/dusk/gyro.cpp
|
||||||
|
src/dusk/game_combos.cpp
|
||||||
|
src/dusk/d_trigger_view.cpp
|
||||||
|
#src/dusk/m_Do_ext_dusk.cpp
|
||||||
src/dusk/hq_minimap.cpp
|
src/dusk/hq_minimap.cpp
|
||||||
src/dusk/imgui/ImGuiActorSpawner.cpp
|
src/dusk/imgui/ImGuiActorSpawner.cpp
|
||||||
src/dusk/imgui/ImGuiBloomWindow.cpp
|
src/dusk/imgui/ImGuiBloomWindow.cpp
|
||||||
@@ -1521,6 +1526,8 @@ set(DUSK_FILES
|
|||||||
src/dusk/touch_camera.cpp
|
src/dusk/touch_camera.cpp
|
||||||
src/dusk/ui/achievements.cpp
|
src/dusk/ui/achievements.cpp
|
||||||
src/dusk/ui/achievements.hpp
|
src/dusk/ui/achievements.hpp
|
||||||
|
src/dusk/ui/command_console.cpp
|
||||||
|
src/dusk/ui/command_console.hpp
|
||||||
src/dusk/ui/bool_button.cpp
|
src/dusk/ui/bool_button.cpp
|
||||||
src/dusk/ui/bool_button.hpp
|
src/dusk/ui/bool_button.hpp
|
||||||
src/dusk/ui/button.cpp
|
src/dusk/ui/button.cpp
|
||||||
|
|||||||
@@ -1351,6 +1351,9 @@ enum dStage_SaveTbl {
|
|||||||
|
|
||||||
const char* dStage_getName2(s16, s8);
|
const char* dStage_getName2(s16, s8);
|
||||||
dStage_objectNameInf* dStage_searchName(const char*);
|
dStage_objectNameInf* dStage_searchName(const char*);
|
||||||
|
#if TARGET_PC
|
||||||
|
dStage_objectNameInf* dStage_searchNameCI(const char*);
|
||||||
|
#endif
|
||||||
static int dStage_stageKeepTresureInit(dStage_dt_c*, void*, int, void*);
|
static int dStage_stageKeepTresureInit(dStage_dt_c*, void*, int, void*);
|
||||||
static int dStage_filiInfo2Init(dStage_dt_c*, void*, int, void*);
|
static int dStage_filiInfo2Init(dStage_dt_c*, void*, int, void*);
|
||||||
static int dStage_mapPathInitCommonLayer(dStage_dt_c*, void*, int, void*);
|
static int dStage_mapPathInitCommonLayer(dStage_dt_c*, void*, int, void*);
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
*, *:before, *:after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: visible;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
console {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 10dp;
|
||||||
|
left: 10dp;
|
||||||
|
width: 50%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: rgba(0, 0, 0, 60%);
|
||||||
|
pointer-events: auto;
|
||||||
|
font-family: "Noto Mono";
|
||||||
|
font-size: 14dp;
|
||||||
|
color: #FFFFFF;
|
||||||
|
transition: background-color 0.8s linear-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
output {
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
max-height: 480dp;
|
||||||
|
padding: 4dp 8dp;
|
||||||
|
line-height: 1.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
output[open] {
|
||||||
|
height: 480dp;
|
||||||
|
max-height: 480dp;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
console:not([open]) {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
console:not([open])[fading] {
|
||||||
|
background-color: rgba(0, 0, 0, 0%);
|
||||||
|
}
|
||||||
|
|
||||||
|
console[open] {
|
||||||
|
background-color: rgba(0, 0, 0, 60%);
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
line {
|
||||||
|
display: block;
|
||||||
|
white-space: nowrap;
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity 0.8s linear-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
output:not([open]) line[fading] {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
output:not([open]) line[expired] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
output[open] line {
|
||||||
|
opacity: 1;
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
line.cmd {
|
||||||
|
color: #FFD966;
|
||||||
|
}
|
||||||
|
|
||||||
|
console input {
|
||||||
|
display: none;
|
||||||
|
width: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 40%);
|
||||||
|
border: 0dp;
|
||||||
|
border-top: 1dp rgba(255, 255, 255, 20%);
|
||||||
|
color: #FFFFFF;
|
||||||
|
font-family: "Noto Mono";
|
||||||
|
font-size: 14dp;
|
||||||
|
padding: 4dp 8dp;
|
||||||
|
}
|
||||||
|
|
||||||
|
console[open] input {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
@@ -30,6 +30,7 @@
|
|||||||
|
|
||||||
#if TARGET_PC
|
#if TARGET_PC
|
||||||
#include "dusk/camera_operators.hpp"
|
#include "dusk/camera_operators.hpp"
|
||||||
|
#include "dusk/commands.hpp"
|
||||||
#include "dusk/frame_interpolation.h"
|
#include "dusk/frame_interpolation.h"
|
||||||
#include "dusk/logging.h"
|
#include "dusk/logging.h"
|
||||||
#include "dusk/action_bindings.h"
|
#include "dusk/action_bindings.h"
|
||||||
@@ -1054,6 +1055,11 @@ void dCamera_c::debugDrawInit() {
|
|||||||
bool dCamera_c::Run() {
|
bool dCamera_c::Run() {
|
||||||
#if TARGET_PC
|
#if TARGET_PC
|
||||||
ResetView();
|
ResetView();
|
||||||
|
if (dusk::isCameraDetached()) {
|
||||||
|
mFrameCounter++;
|
||||||
|
mTicks++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (executeDebugFlyCam() || dusk::mods::camera_run_operators(this)) {
|
if (executeDebugFlyCam() || dusk::mods::camera_run_operators(this)) {
|
||||||
mFrameCounter++;
|
mFrameCounter++;
|
||||||
mTicks++;
|
mTicks++;
|
||||||
@@ -11123,6 +11129,9 @@ camera_class* dCam_getCamera() {
|
|||||||
|
|
||||||
dCamera_c* dCam_getBody() {
|
dCamera_c* dCam_getBody() {
|
||||||
camera_process_class* camera = (camera_process_class*)dCam_getCamera();
|
camera_process_class* camera = (camera_process_class*)dCam_getCamera();
|
||||||
|
#if TARGET_PC
|
||||||
|
if (camera == nullptr) { return nullptr; }
|
||||||
|
#endif
|
||||||
return &camera->mCamera;
|
return &camera->mCamera;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
#include <typeindex>
|
#include <typeindex>
|
||||||
#include "JSystem/JKernel/JKRHeap.h"
|
#include "JSystem/JKernel/JKRHeap.h"
|
||||||
#include "absl/container/flat_hash_map.h"
|
#include "absl/container/flat_hash_map.h"
|
||||||
#include "client/TracyScoped.hpp"
|
#include "tracy/Tracy.hpp"
|
||||||
#include "dusk/frame_interpolation.h"
|
#include "dusk/frame_interpolation.h"
|
||||||
#include "helpers/gx_helper.h"
|
#include "helpers/gx_helper.h"
|
||||||
#include "dusk/logging.h"
|
#include "dusk/logging.h"
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
|
|
||||||
#if TARGET_PC
|
#if TARGET_PC
|
||||||
#include "dusk/autosave.h"
|
#include "dusk/autosave.h"
|
||||||
|
#include "dusk/d_trigger_view.h"
|
||||||
#include "dusk/memory.h"
|
#include "dusk/memory.h"
|
||||||
#include "dusk/ui/ui.hpp"
|
#include "dusk/ui/ui.hpp"
|
||||||
#endif
|
#endif
|
||||||
@@ -675,6 +676,10 @@ static int dScnPly_Draw(dScnPly_c* i_this) {
|
|||||||
dComIfG_Ccsp()->Draw();
|
dComIfG_Ccsp()->Draw();
|
||||||
dComIfG_Bgsp().Draw();
|
dComIfG_Bgsp().Draw();
|
||||||
|
|
||||||
|
#if TARGET_PC
|
||||||
|
dusk::TriggerView::execute();
|
||||||
|
#endif
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
dPath_Draw();
|
dPath_Draw();
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1537,6 +1537,27 @@ dStage_objectNameInf* dStage_searchName(char const* objName) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if TARGET_PC
|
||||||
|
dStage_objectNameInf* dStage_searchNameCI(char const* objName) {
|
||||||
|
dStage_objectNameInf* obj = l_objectName;
|
||||||
|
|
||||||
|
for (u32 i = 0; i < ARRAY_SIZEU(l_objectName); i++) {
|
||||||
|
const char* a = obj->name;
|
||||||
|
const char* b = objName;
|
||||||
|
while (*a && *b && tolower((unsigned char)*a) == tolower((unsigned char)*b)) {
|
||||||
|
++a;
|
||||||
|
++b;
|
||||||
|
}
|
||||||
|
if (*a == '\0' && *b == '\0') {
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
obj++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
const char* dStage_getName(s16 procName, s8 argument) {
|
const char* dStage_getName(s16 procName, s8 argument) {
|
||||||
static char tmp_name[dStage_NAME_LENGTH];
|
static char tmp_name[dStage_NAME_LENGTH];
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,880 @@
|
|||||||
|
#include "commands.hpp"
|
||||||
|
|
||||||
|
#include "JSystem/JUtility/JUTGamePad.h"
|
||||||
|
#include "SSystem/SComponent/c_math.h"
|
||||||
|
#include "SSystem/SComponent/c_sxyz.h"
|
||||||
|
#include "SSystem/SComponent/c_xyz.h"
|
||||||
|
#include "c/c_damagereaction.h"
|
||||||
|
#include "d/actor/d_a_alink.h"
|
||||||
|
#include "d/d_com_inf_actor.h"
|
||||||
|
#include "d/d_com_inf_game.h"
|
||||||
|
#include "d/d_camera.h"
|
||||||
|
#include "d/d_kankyo.h"
|
||||||
|
#include "d/d_stage.h"
|
||||||
|
#include "dusk/game_clock.h"
|
||||||
|
#include "f_op/f_op_actor_mng.h"
|
||||||
|
#include "f_pc/f_pc_layer.h"
|
||||||
|
#include "f_pc/f_pc_layer_iter.h"
|
||||||
|
#include "f_pc/f_pc_manager.h"
|
||||||
|
#include "f_pc/f_pc_node.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "fmt/format.h"
|
||||||
|
|
||||||
|
namespace dusk {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
static constexpr int kMaxHistory = 64;
|
||||||
|
|
||||||
|
static std::vector<std::string> SplitArgs(std::string_view input) {
|
||||||
|
std::vector<std::string> args;
|
||||||
|
std::string cur;
|
||||||
|
for (char c : input) {
|
||||||
|
if (c == ' ' || c == '\t') {
|
||||||
|
if (!cur.empty()) {
|
||||||
|
args.push_back(std::move(cur));
|
||||||
|
cur.clear();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cur += c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!cur.empty()) {
|
||||||
|
args.push_back(std::move(cur));
|
||||||
|
}
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::optional<long> ParseLong(const std::string& s) {
|
||||||
|
if (s.empty()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
char* end = nullptr;
|
||||||
|
const long v = std::strtol(s.c_str(), &end, 0);
|
||||||
|
return (end != s.c_str() && *end == '\0') ? std::optional<long>{v} : std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::optional<float> ParseFloat(const std::string& s) {
|
||||||
|
if (s.empty()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
char* end = nullptr;
|
||||||
|
const float v = std::strtof(s.c_str(), &end);
|
||||||
|
return (end != s.c_str() && *end == '\0') ? std::optional<float>{v} : std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* ActorShortName(s16 profname) {
|
||||||
|
const char* n = dStage_getName(profname, -1);
|
||||||
|
return n ? n : "?";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolves @found / @link / @<id> to a process pointer, outputting an error on failure
|
||||||
|
static base_process_class* ParseProcArg(
|
||||||
|
const std::string& s, unsigned int foundProcId, const CommandOutput& output) {
|
||||||
|
if (s.empty() || s[0] != '@') {
|
||||||
|
output("Error: proc reference must start with @");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
const std::string inner = s.substr(1);
|
||||||
|
unsigned int id;
|
||||||
|
if (inner == "found") {
|
||||||
|
if (foundProcId == 0) {
|
||||||
|
output("Error: @found is not set");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
id = foundProcId;
|
||||||
|
} else if (inner == "link") {
|
||||||
|
auto* player = dComIfGp_getPlayer(0);
|
||||||
|
if (player == nullptr) {
|
||||||
|
output("Error: player not available");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
id = (unsigned int)fpcM_GetID(player);
|
||||||
|
} else {
|
||||||
|
const auto v = ParseLong(inner);
|
||||||
|
if (!v) {
|
||||||
|
output("Error: invalid proc ID");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
id = (unsigned int)*v;
|
||||||
|
}
|
||||||
|
auto* proc = fpcM_SearchByID(id);
|
||||||
|
if (proc == nullptr) {
|
||||||
|
output(fmt::format(FMT_STRING("Error: proc {} not found"), id));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return proc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Like ParseProcArg but ensures the proc is an actor
|
||||||
|
static fopAc_ac_c* ParseActorArg(
|
||||||
|
const std::string& s, unsigned int foundProcId, const CommandOutput& output) {
|
||||||
|
auto* proc = ParseProcArg(s, foundProcId, output);
|
||||||
|
if (proc == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if (!fopAcM_IsActor(proc)) {
|
||||||
|
output("Error: proc is not an actor");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return static_cast<fopAc_ac_c*>(proc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::optional<s16> ParseActorId(const std::string& s) {
|
||||||
|
if (const auto v = ParseLong(s)) {
|
||||||
|
return (s16)*v;
|
||||||
|
}
|
||||||
|
const auto* entry = dStage_searchNameCI(s.c_str());
|
||||||
|
return entry ? std::optional<s16>{entry->procname} : std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::optional<cXyz> ParseXYZ(const std::vector<std::string>& args, size_t i) {
|
||||||
|
const auto x = ParseFloat(args[i]), y = ParseFloat(args[i + 1]), z = ParseFloat(args[i + 2]);
|
||||||
|
return (x && y && z) ? std::optional<cXyz>{cXyz(*x, *y, *z)} : std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool TryAngle(
|
||||||
|
s16& out, const std::vector<std::string>& args, size_t i, const CommandOutput& output) {
|
||||||
|
if (args.size() <= i) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const auto a = ParseLong(args[i]);
|
||||||
|
if (!a) {
|
||||||
|
output("Error: invalid angle");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out = (s16)*a;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string actorLine(const base_process_class* proc) {
|
||||||
|
const auto* ac = static_cast<const fopAc_ac_c*>(proc);
|
||||||
|
return fmt::format(FMT_STRING("procId={} 0x{:04X} ({}) @ ({:.2f}, {:.2f}, {:.2f}) room={}"),
|
||||||
|
(unsigned int)proc->id, (unsigned int)(u16)proc->profname, ActorShortName(proc->profname),
|
||||||
|
ac->current.pos.x, ac->current.pos.y, ac->current.pos.z, (int)ac->current.roomNo);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void recurseLayer(void* p, int (*callback)(void*, void*), void* ctx) {
|
||||||
|
auto* proc = static_cast<base_process_class*>(p);
|
||||||
|
if (fpcBs_Is_JustOfType(g_fpcNd_type, proc->subtype)) {
|
||||||
|
fpcLyIt_OnlyHere(&static_cast<process_node_class*>(p)->layer, callback, ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ListContext {
|
||||||
|
s16 targetId;
|
||||||
|
std::vector<std::string>* output;
|
||||||
|
};
|
||||||
|
struct FindContext {
|
||||||
|
s16 targetId;
|
||||||
|
std::vector<base_process_class*> matches;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int ListActorCallback(void* p, void* ctx) {
|
||||||
|
auto* proc = static_cast<base_process_class*>(p);
|
||||||
|
auto* context = static_cast<ListContext*>(ctx);
|
||||||
|
if (fopAcM_IsActor(proc) && (context->targetId < 0 || proc->profname == context->targetId)) {
|
||||||
|
context->output->push_back(" " + actorLine(proc));
|
||||||
|
}
|
||||||
|
recurseLayer(p, ListActorCallback, ctx);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int FindActorCallback(void* p, void* ctx) {
|
||||||
|
auto* proc = static_cast<base_process_class*>(p);
|
||||||
|
auto* context = static_cast<FindContext*>(ctx);
|
||||||
|
if (fopAcM_IsActor(proc) && proc->profname == context->targetId) {
|
||||||
|
context->matches.push_back(proc);
|
||||||
|
}
|
||||||
|
recurseLayer(p, FindActorCallback, ctx);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CameraFlyState {
|
||||||
|
bool active = false;
|
||||||
|
cXyz startEye;
|
||||||
|
cXyz startCenter;
|
||||||
|
cXyz endEye;
|
||||||
|
cXyz endCenter;
|
||||||
|
float duration = 0.0f;
|
||||||
|
float elapsed = 0.0f;
|
||||||
|
};
|
||||||
|
CameraFlyState s_cameraFly;
|
||||||
|
|
||||||
|
static dCamera_c* getCamera() { return dCam_getBody(); }
|
||||||
|
|
||||||
|
static cXyz anglesToDir(s16 h, s16 v) {
|
||||||
|
return cXyz(cM_scos(v) * cM_ssin(h), cM_ssin(v), cM_scos(v) * cM_scos(h));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cameraToAngles(dCamera_c* cam, s16& h, s16& v) {
|
||||||
|
const cXyz eye = cam->iEye();
|
||||||
|
const cXyz ctr = cam->iCenter();
|
||||||
|
const float dx = ctr.x - eye.x;
|
||||||
|
const float dy = ctr.y - eye.y;
|
||||||
|
const float dz = ctr.z - eye.z;
|
||||||
|
const float hDist = sqrtf(dx * dx + dz * dz);
|
||||||
|
h = cM_atan2s(dx, dz);
|
||||||
|
v = cM_atan2s(dy, hDist);
|
||||||
|
}
|
||||||
|
|
||||||
|
static float smoothstep(float t) {
|
||||||
|
t = std::clamp(t, 0.0f, 1.0f);
|
||||||
|
return t * t * (3.0f - 2.0f * t);
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr float kCamTargetDist = 100.0f;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
bool isCameraDetached() { return s_cameraFly.active; }
|
||||||
|
|
||||||
|
void processCameraCommands() {
|
||||||
|
if (!s_cameraFly.active) { return; }
|
||||||
|
dCamera_c* cam = getCamera();
|
||||||
|
if (cam == nullptr) { s_cameraFly.active = false; return; }
|
||||||
|
|
||||||
|
cXyz eye, center;
|
||||||
|
|
||||||
|
if (s_cameraFly.duration <= 0.0f) {
|
||||||
|
eye = s_cameraFly.endEye;
|
||||||
|
center = s_cameraFly.endCenter;
|
||||||
|
} else {
|
||||||
|
s_cameraFly.elapsed += dusk::game_clock::kSimPeriod;
|
||||||
|
const float t = smoothstep(s_cameraFly.elapsed / s_cameraFly.duration);
|
||||||
|
eye = s_cameraFly.startEye + (s_cameraFly.endEye - s_cameraFly.startEye) * t;
|
||||||
|
center = s_cameraFly.startCenter + (s_cameraFly.endCenter - s_cameraFly.startCenter) * t;
|
||||||
|
|
||||||
|
if (s_cameraFly.elapsed >= s_cameraFly.duration) {
|
||||||
|
eye = s_cameraFly.endEye;
|
||||||
|
center = s_cameraFly.endCenter;
|
||||||
|
s_cameraFly.startEye = s_cameraFly.endEye;
|
||||||
|
s_cameraFly.startCenter = s_cameraFly.endCenter;
|
||||||
|
s_cameraFly.duration = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cam->Reset(center, eye);
|
||||||
|
cam->mDebugFlyCam.initialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void runCommand(std::string_view cmdLine, CommandState& state, const CommandOutput& output) {
|
||||||
|
if (state.echoEnabled) {
|
||||||
|
output(fmt::format(FMT_STRING("> {}"), cmdLine));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cmdLine.empty()) {
|
||||||
|
if (state.history.empty() || state.history.back() != cmdLine) {
|
||||||
|
state.history.push_back(std::string(cmdLine));
|
||||||
|
if ((int)state.history.size() > kMaxHistory) {
|
||||||
|
state.history.erase(state.history.begin());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto args = SplitArgs(cmdLine);
|
||||||
|
if (args.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto& cmd = args[0];
|
||||||
|
|
||||||
|
auto requirePlayer = [&]() -> daAlink_c* {
|
||||||
|
auto* p = (daAlink_c*)dComIfGp_getPlayer(0);
|
||||||
|
if (p == nullptr) {
|
||||||
|
output("Error: player not available");
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (cmd == "tp") {
|
||||||
|
auto* player = requirePlayer();
|
||||||
|
if (player == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.size() >= 2 && args[1].starts_with('@')) {
|
||||||
|
auto* ac = ParseActorArg(args[1], state.foundProcId, output);
|
||||||
|
if (ac == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.size() >= 3 && args[2].starts_with('@')) {
|
||||||
|
auto* destAc = ParseActorArg(args[2], state.foundProcId, output);
|
||||||
|
if (destAc == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const cXyz destPos = destAc->current.pos;
|
||||||
|
ac->current.pos = destPos;
|
||||||
|
output(fmt::format(FMT_STRING("Moved actor {} to ({:.2f}, {:.2f}, {:.2f})"), ac->id,
|
||||||
|
destPos.x, destPos.y, destPos.z));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.size() >= 5) {
|
||||||
|
const auto pos = ParseXYZ(args, 2);
|
||||||
|
if (!pos) {
|
||||||
|
output("Error: invalid coordinates");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ac->current.pos = *pos;
|
||||||
|
if (!TryAngle(ac->shape_angle.y, args, 5, output)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
output(fmt::format(FMT_STRING("Moved actor {} to ({:.2f}, {:.2f}, {:.2f})"), ac->id,
|
||||||
|
pos->x, pos->y, pos->z));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
player->current.pos = ac->current.pos;
|
||||||
|
output(fmt::format(FMT_STRING("Teleported to actor {} ({:.2f}, {:.2f}, {:.2f})"),
|
||||||
|
ac->id, ac->current.pos.x, ac->current.pos.y, ac->current.pos.z));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.size() < 4) {
|
||||||
|
output("Usage: tp <x> <y> <z> [angle] | tp @<procId> [<x> <y> <z> [angle] | @link]");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto pos = ParseXYZ(args, 1);
|
||||||
|
if (!pos) {
|
||||||
|
output("Error: invalid coordinates");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
player->current.pos = *pos;
|
||||||
|
if (!TryAngle(player->shape_angle.y, args, 4, output)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
output(fmt::format(
|
||||||
|
FMT_STRING("Teleported to ({:.2f}, {:.2f}, {:.2f})"), pos->x, pos->y, pos->z));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd == "spawn") {
|
||||||
|
if (args.size() < 2) {
|
||||||
|
output("Usage: spawn <actorId> [params] [x y z] [angle]");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto* player = requirePlayer();
|
||||||
|
if (player == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto actorId = ParseActorId(args[1]);
|
||||||
|
if (!actorId) {
|
||||||
|
output("Error: unknown actor ID or name");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long paramsL = -1;
|
||||||
|
if (args.size() >= 3) {
|
||||||
|
const auto p = ParseLong(args[2]);
|
||||||
|
if (!p) {
|
||||||
|
output("Error: invalid params");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
paramsL = *p;
|
||||||
|
}
|
||||||
|
|
||||||
|
cXyz pos = player->current.pos;
|
||||||
|
if (args.size() >= 6) {
|
||||||
|
const auto spawnPos = ParseXYZ(args, 3);
|
||||||
|
if (!spawnPos) {
|
||||||
|
output("Error: invalid spawn coordinates");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pos = *spawnPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
s16 angleY = 0;
|
||||||
|
if (!TryAngle(angleY, args, 6, output)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
csXyz angle;
|
||||||
|
angle.set(0, angleY, 0);
|
||||||
|
cXyz scale(1.0f, 1.0f, 1.0f);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
unsigned int result = fopAcM_create(
|
||||||
|
*actorId, (u32)paramsL, &pos, player->current.roomNo, &angle, &scale, (s8)-1);
|
||||||
|
fpcLy_SetCurrentLayer(savedLayer);
|
||||||
|
|
||||||
|
output(result != 0 ? fmt::format(FMT_STRING("Spawned actorId=0x{:04X} procId={}"),
|
||||||
|
(unsigned int)(u16)*actorId, result) :
|
||||||
|
fmt::format(FMT_STRING("Failed to spawn actorId=0x{:04X}"),
|
||||||
|
(unsigned int)(u16)*actorId));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd == "reset") {
|
||||||
|
JUTGamePad::C3ButtonReset::sResetSwitchPushing = true;
|
||||||
|
output("Soft reset triggered");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd == "warp") {
|
||||||
|
if (args.size() < 4) {
|
||||||
|
output("Usage: warp <stageName> <point> <roomNo> [layer=-1]");
|
||||||
|
output(" e.g. warp F_SP121 0 0");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto pointL = ParseLong(args[2]), roomL = ParseLong(args[3]);
|
||||||
|
if (!pointL || !roomL) {
|
||||||
|
output("Error: invalid point or room number");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long layerL = -1;
|
||||||
|
if (args.size() >= 5) {
|
||||||
|
const auto l = ParseLong(args[4]);
|
||||||
|
if (!l) {
|
||||||
|
output("Error: invalid layer");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
layerL = *l;
|
||||||
|
}
|
||||||
|
state.lastWarpStage = args[1];
|
||||||
|
dComIfGp_setNextStage(state.lastWarpStage.c_str(), (s16)*pointL, (s8)*roomL, (s8)layerL);
|
||||||
|
output(fmt::format(FMT_STRING("Warping to {} point={} room={} layer={}"), args[1],
|
||||||
|
(int)(s16)*pointL, (int)(s8)*roomL, (int)(s8)layerL));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd == "list") {
|
||||||
|
s16 targetId = -1;
|
||||||
|
if (args.size() >= 2) {
|
||||||
|
const auto id = ParseActorId(args[1]);
|
||||||
|
if (!id) {
|
||||||
|
output("Error: unknown actor ID or name");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
targetId = *id;
|
||||||
|
}
|
||||||
|
std::vector<std::string> results;
|
||||||
|
ListContext ctx{targetId, &results};
|
||||||
|
fpcLyIt_OnlyHere(fpcLy_RootLayer(), ListActorCallback, &ctx);
|
||||||
|
output(results.empty() ? "No matching actors found" :
|
||||||
|
fmt::format(FMT_STRING("Found {} actor(s):"), results.size()));
|
||||||
|
for (const auto& r : results) {
|
||||||
|
output(r);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd == "killall") {
|
||||||
|
if (args.size() < 2) {
|
||||||
|
output("Usage: killall <actorId|name>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto targetId = ParseActorId(args[1]);
|
||||||
|
if (!targetId) {
|
||||||
|
output("Error: unknown actor ID or name");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
FindContext ctx{*targetId, {}};
|
||||||
|
fpcLyIt_OnlyHere(fpcLy_RootLayer(), FindActorCallback, &ctx);
|
||||||
|
for (auto* proc : ctx.matches) {
|
||||||
|
fpcM_Delete(proc);
|
||||||
|
}
|
||||||
|
output(fmt::format(FMT_STRING("Deleted {} actor(s) of type {} ({})"),
|
||||||
|
(int)ctx.matches.size(), (unsigned int)(u16)*targetId, ActorShortName(*targetId)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd == "heal") {
|
||||||
|
auto* player = requirePlayer();
|
||||||
|
if (player == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const u16 maxLife = dComIfGs_getMaxLife() / 5 * 4;
|
||||||
|
u16 newLife = maxLife;
|
||||||
|
if (args.size() >= 2) {
|
||||||
|
const auto amount = ParseLong(args[1]);
|
||||||
|
if (!amount) {
|
||||||
|
output("Error: invalid amount");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
newLife =
|
||||||
|
(u16)std::max(0L, std::min((long)maxLife, (long)dComIfGs_getLife() + *amount));
|
||||||
|
}
|
||||||
|
dComIfGs_setLife(newLife);
|
||||||
|
output(fmt::format(FMT_STRING("Health: {}/{}"), (int)newLife, (int)maxLife));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd == "kill") {
|
||||||
|
if (args.size() >= 2) {
|
||||||
|
auto* proc = ParseProcArg(args[1], state.foundProcId, output);
|
||||||
|
if (proc == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fpcM_Delete(proc);
|
||||||
|
output(fmt::format(FMT_STRING("Deleted proc {}"), proc->id));
|
||||||
|
} else {
|
||||||
|
auto* player = requirePlayer();
|
||||||
|
if (player == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dComIfGs_setLife(0);
|
||||||
|
output("Set Link's health to 0");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd == "freeze" || cmd == "unfreeze") {
|
||||||
|
const bool doFreeze = (cmd == "freeze");
|
||||||
|
if (args.size() < 2) {
|
||||||
|
g_dComIfAc_gameInfo.mPause = doFreeze;
|
||||||
|
output(doFreeze ? "Global freeze on" : "Global freeze off");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto* ac = ParseActorArg(args[1], state.foundProcId, output);
|
||||||
|
if (ac == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (doFreeze) {
|
||||||
|
fpcM_PauseEnable(ac, 1);
|
||||||
|
output(fmt::format(FMT_STRING("Froze actor {}"), (unsigned int)ac->id));
|
||||||
|
} else {
|
||||||
|
fpcM_PauseDisable(ac, 1);
|
||||||
|
output(fmt::format(FMT_STRING("Unfroze actor {}"), (unsigned int)ac->id));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd == "rate") {
|
||||||
|
if (args.size() >= 2) {
|
||||||
|
const auto hz = ParseLong(args[1]);
|
||||||
|
if (!hz || *hz <= 0) {
|
||||||
|
output("Error: rate must be a positive integer");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dusk::game_clock::set_sim_rate((float)*hz);
|
||||||
|
}
|
||||||
|
output(fmt::format(FMT_STRING("Sim rate: {:.0f} hz"), dusk::game_clock::get_sim_rate()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd == "ebf") {
|
||||||
|
if (args.size() >= 2) {
|
||||||
|
const auto val = ParseLong(args[1]);
|
||||||
|
if (!val || *val < 0 || *val > 255) {
|
||||||
|
output("Error: value must be 0-255");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cDmr_SkipInfo = (u8)*val;
|
||||||
|
}
|
||||||
|
output(fmt::format(FMT_STRING("EBF = {}"), (int)cDmr_SkipInfo));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd == "time") {
|
||||||
|
if (args.size() >= 2) {
|
||||||
|
const auto t = ParseFloat(args[1]);
|
||||||
|
if (!t || *t < 0.0f || *t > 360.0f) {
|
||||||
|
output("Error: time must be a float between 0 and 360");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dKy_instant_timechg(*t);
|
||||||
|
}
|
||||||
|
output(fmt::format(FMT_STRING("Time: {:.2f} ({}:{:02d})"), dComIfGs_getTime(),
|
||||||
|
dKy_getdaytime_hour(), dKy_getdaytime_minute()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd == "rupees") {
|
||||||
|
const u16 maxRupees = dComIfGs_getRupeeMax();
|
||||||
|
if (args.size() >= 2) {
|
||||||
|
const auto amount = ParseLong(args[1]);
|
||||||
|
if (!amount) {
|
||||||
|
output("Error: invalid amount");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dComIfGs_setRupee((u16)std::max(0L, std::min((long)maxRupees, *amount)));
|
||||||
|
}
|
||||||
|
output(fmt::format(FMT_STRING("Rupees: {}/{}"), (int)dComIfGs_getRupee(), (int)maxRupees));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd == "find") {
|
||||||
|
if (args.size() < 2) {
|
||||||
|
if (state.foundProcId == 0) {
|
||||||
|
output("@found is not set");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto* proc = fpcM_SearchByID(state.foundProcId);
|
||||||
|
output(proc != nullptr && fopAcM_IsActor(proc) ?
|
||||||
|
"@found = " + actorLine(proc) :
|
||||||
|
fmt::format(FMT_STRING("@found = {} (no longer exists)"),
|
||||||
|
(unsigned int)state.foundProcId));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto targetId = ParseActorId(args[1]);
|
||||||
|
if (!targetId) {
|
||||||
|
output("Error: unknown actor ID or name");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int targetN = 1;
|
||||||
|
if (args.size() >= 3) {
|
||||||
|
const auto n = ParseLong(args[2]);
|
||||||
|
if (!n || *n < 1) {
|
||||||
|
output("Error: index must be >= 1");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
targetN = (int)*n;
|
||||||
|
}
|
||||||
|
|
||||||
|
FindContext ctx{*targetId, {}};
|
||||||
|
fpcLyIt_OnlyHere(fpcLy_RootLayer(), FindActorCallback, &ctx);
|
||||||
|
|
||||||
|
if (ctx.matches.empty()) {
|
||||||
|
output(fmt::format(FMT_STRING("No actors found for '{}'"), args[1]));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(ctx.matches.begin(), ctx.matches.end(),
|
||||||
|
[](const base_process_class* a, const base_process_class* b) { return a->id < b->id; });
|
||||||
|
|
||||||
|
if (targetN > (int)ctx.matches.size()) {
|
||||||
|
output(fmt::format(
|
||||||
|
FMT_STRING("Error: only {} actor(s) of that type exist"), (int)ctx.matches.size()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* picked = ctx.matches[(size_t)(targetN - 1)];
|
||||||
|
state.foundProcId = picked->id;
|
||||||
|
output(fmt::format(
|
||||||
|
FMT_STRING("@found [{}/{}] {}"), targetN, (int)ctx.matches.size(), actorLine(picked)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd == "camera") {
|
||||||
|
const std::string sub = args.size() >= 2 ? args[1] : "";
|
||||||
|
|
||||||
|
if (sub == "attach") {
|
||||||
|
s_cameraFly.active = false;
|
||||||
|
output("Camera attached");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* cam = getCamera();
|
||||||
|
if (cam == nullptr) { output("Error: camera not available"); return; }
|
||||||
|
|
||||||
|
if (sub == "detach") {
|
||||||
|
s_cameraFly.active = true;
|
||||||
|
s_cameraFly.duration = 0.0f;
|
||||||
|
s_cameraFly.startEye = s_cameraFly.endEye = cam->iEye();
|
||||||
|
s_cameraFly.startCenter = s_cameraFly.endCenter = cam->iCenter();
|
||||||
|
output("Camera detached");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sub == "tp") {
|
||||||
|
// camera tp @<ref>
|
||||||
|
if (args.size() >= 3 && !args[2].empty() && args[2][0] == '@') {
|
||||||
|
auto* ac = ParseActorArg(args[2], state.foundProcId, output);
|
||||||
|
if (ac == nullptr) { return; }
|
||||||
|
const cXyz actorPos = ac->current.pos;
|
||||||
|
const cXyz dir = anglesToDir(ac->shape_angle.y, 0);
|
||||||
|
const cXyz newEye = cXyz(actorPos.x - dir.x * kCamTargetDist,
|
||||||
|
actorPos.y - dir.y * kCamTargetDist + 50.0f,
|
||||||
|
actorPos.z - dir.z * kCamTargetDist);
|
||||||
|
s_cameraFly.active = true; s_cameraFly.duration = 0.0f;
|
||||||
|
s_cameraFly.startEye = s_cameraFly.endEye = newEye;
|
||||||
|
s_cameraFly.startCenter = s_cameraFly.endCenter = actorPos;
|
||||||
|
output(fmt::format(FMT_STRING("Camera moved to actor {}"), ac->id));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// camera tp x y z [h] [v]
|
||||||
|
if (args.size() < 5) {
|
||||||
|
output("Usage: camera tp <x> <y> <z> [h_angle] [v_angle] | camera tp @<ref>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto pos = ParseXYZ(args, 2);
|
||||||
|
if (!pos) { output("Error: invalid coordinates"); return; }
|
||||||
|
s16 h = 0, v = 0;
|
||||||
|
cameraToAngles(cam, h, v);
|
||||||
|
if (args.size() >= 6) {
|
||||||
|
const auto hv = ParseLong(args[5]);
|
||||||
|
if (!hv) { output("Error: invalid h_angle"); return; }
|
||||||
|
h = (s16)*hv;
|
||||||
|
}
|
||||||
|
if (args.size() >= 7) {
|
||||||
|
const auto vv = ParseLong(args[6]);
|
||||||
|
if (!vv) { output("Error: invalid v_angle"); return; }
|
||||||
|
v = (s16)*vv;
|
||||||
|
}
|
||||||
|
const cXyz dir = anglesToDir(h, v);
|
||||||
|
const cXyz center = cXyz(pos->x + dir.x * kCamTargetDist,
|
||||||
|
pos->y + dir.y * kCamTargetDist,
|
||||||
|
pos->z + dir.z * kCamTargetDist);
|
||||||
|
s_cameraFly.active = true; s_cameraFly.duration = 0.0f;
|
||||||
|
s_cameraFly.startEye = s_cameraFly.endEye = *pos;
|
||||||
|
s_cameraFly.startCenter = s_cameraFly.endCenter = center;
|
||||||
|
output(fmt::format(FMT_STRING("Camera teleported to ({:.2f}, {:.2f}, {:.2f})"),
|
||||||
|
pos->x, pos->y, pos->z));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sub == "pos") {
|
||||||
|
s16 h = 0, v = 0;
|
||||||
|
cameraToAngles(cam, h, v);
|
||||||
|
const cXyz eye = cam->iEye();
|
||||||
|
output(fmt::format(FMT_STRING("Camera: ({:.2f}, {:.2f}, {:.2f}) h={} v={}"),
|
||||||
|
eye.x, eye.y, eye.z, (int)h, (int)v));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sub == "fly") {
|
||||||
|
// camera fly <time> <x> <y> <z> [h] [v] | camera fly <time> @<ref>
|
||||||
|
if (args.size() < 4) {
|
||||||
|
output("Usage: camera fly <time> <x> <y> <z> [h_angle] [v_angle] | camera fly <time> @<ref>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto t = ParseFloat(args[2]);
|
||||||
|
if (!t || *t <= 0) { output("Error: invalid time"); return; }
|
||||||
|
const float flyTime = *t;
|
||||||
|
|
||||||
|
cXyz targetPos;
|
||||||
|
s16 h = 0, v = 0;
|
||||||
|
|
||||||
|
if (!args[3].empty() && args[3][0] == '@') {
|
||||||
|
auto* ac = ParseActorArg(args[3], state.foundProcId, output);
|
||||||
|
if (ac == nullptr) { return; }
|
||||||
|
targetPos = ac->current.pos;
|
||||||
|
h = ac->shape_angle.y;
|
||||||
|
v = 0;
|
||||||
|
} else {
|
||||||
|
if (args.size() < 6) {
|
||||||
|
output("Usage: camera fly <time> <x> <y> <z> [h_angle] [v_angle] | camera fly <time> @<ref>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto pos = ParseXYZ(args, 3);
|
||||||
|
if (!pos) { output("Error: invalid coordinates"); return; }
|
||||||
|
targetPos = *pos;
|
||||||
|
cameraToAngles(cam, h, v);
|
||||||
|
if (args.size() >= 7) {
|
||||||
|
const auto hv = ParseLong(args[6]); if (!hv) { output("Error: invalid h_angle"); return; } h = (s16)*hv;
|
||||||
|
}
|
||||||
|
if (args.size() >= 8) {
|
||||||
|
const auto vv = ParseLong(args[7]); if (!vv) { output("Error: invalid v_angle"); return; } v = (s16)*vv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cXyz dir = anglesToDir(h, v);
|
||||||
|
const cXyz endCenter = cXyz(targetPos.x + dir.x * kCamTargetDist,
|
||||||
|
targetPos.y + dir.y * kCamTargetDist,
|
||||||
|
targetPos.z + dir.z * kCamTargetDist);
|
||||||
|
|
||||||
|
s_cameraFly.active = true;
|
||||||
|
s_cameraFly.startEye = cam->iEye();
|
||||||
|
s_cameraFly.startCenter = cam->iCenter();
|
||||||
|
s_cameraFly.endEye = targetPos;
|
||||||
|
s_cameraFly.endCenter = endCenter;
|
||||||
|
s_cameraFly.duration = flyTime;
|
||||||
|
s_cameraFly.elapsed = 0.0f;
|
||||||
|
output(fmt::format(FMT_STRING("Camera flying to ({:.2f}, {:.2f}, {:.2f}) over {:.1f}s"),
|
||||||
|
targetPos.x, targetPos.y, targetPos.z, flyTime));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
output("Usage: camera detach | attach | tp ... | fly ... | pos");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd == "echo") {
|
||||||
|
if (args.size() >= 2 && args[1] == "off") {
|
||||||
|
state.echoEnabled = false;
|
||||||
|
output("Echo disabled");
|
||||||
|
} else if (args.size() >= 2 && args[1] == "on") {
|
||||||
|
state.echoEnabled = true;
|
||||||
|
output("Echo enabled");
|
||||||
|
} else {
|
||||||
|
output(state.echoEnabled ? "Echo: on" : "Echo: off");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd == "transform") {
|
||||||
|
auto* player = requirePlayer();
|
||||||
|
if (player == nullptr) { return; }
|
||||||
|
player->procCoMetamorphoseInit();
|
||||||
|
output("Transforming");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd == "angle") {
|
||||||
|
auto* player = requirePlayer();
|
||||||
|
if (player == nullptr) { return; }
|
||||||
|
if (args.size() >= 2) {
|
||||||
|
const auto a = ParseLong(args[1]);
|
||||||
|
if (!a) { output("Error: invalid angle"); return; }
|
||||||
|
player->shape_angle.y = (s16)*a;
|
||||||
|
output(fmt::format(FMT_STRING("Angle set to {}"), (int)(s16)*a));
|
||||||
|
} else {
|
||||||
|
output(fmt::format(FMT_STRING("Angle: {}"), (int)player->shape_angle.y));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd == "pos") {
|
||||||
|
auto* player = requirePlayer();
|
||||||
|
if (player == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
output(fmt::format(FMT_STRING("pos: {:.4f} {:.4f} {:.4f}"), player->current.pos.x,
|
||||||
|
player->current.pos.y, player->current.pos.z));
|
||||||
|
output(
|
||||||
|
fmt::format(FMT_STRING("stage: {} room: {} entry: {}"), dComIfGp_getStartStageName(),
|
||||||
|
(int)player->current.roomNo, (int)dComIfGp_getStartStagePoint()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd == "help") {
|
||||||
|
output("@<ref> = @<procId> | @found | @link");
|
||||||
|
output("");
|
||||||
|
output("angle [value] Get or set Link's facing angle");
|
||||||
|
output("camera detach | attach Detach or reattach camera");
|
||||||
|
output("camera pos Print camera position and angles");
|
||||||
|
output("camera tp <x> <y> <z> [h] [v] Teleport camera (h/v are s16 angles)");
|
||||||
|
output("camera tp @<ref> Teleport camera to actor");
|
||||||
|
output("camera fly <t> <x> <y> <z> [h] [v] Fly camera over t seconds (h/v are s16 angles)");
|
||||||
|
output("camera fly <t> @<ref> Fly camera to actor");
|
||||||
|
output("ebf [0-255] Get or set cDmr_SkipInfo");
|
||||||
|
output("echo on | off Enable or disable command echo");
|
||||||
|
output("find <id|name> [n=1] Store nth actor as @found");
|
||||||
|
output("freeze [@<ref>] Freeze globally, or freeze actor");
|
||||||
|
output("heal [amount] Heal to max, or by relative amount");
|
||||||
|
output("kill Set Link health to 0");
|
||||||
|
output("kill @<ref> Delete proc");
|
||||||
|
output("killall <id|name> Delete all actors of a type");
|
||||||
|
output("list [id|name] List actors in scene");
|
||||||
|
output("pos Print player position and stage");
|
||||||
|
output("rate [hz] Get or set sim rate (1-480, default 30)");
|
||||||
|
output("reset Soft reset");
|
||||||
|
output("rupees [amount] Get or set rupee count");
|
||||||
|
output("spawn <id|name> [params] [x y z] [angle] Spawn actor");
|
||||||
|
output("time [0-360] Get or set time of day");
|
||||||
|
output("tp <x> <y> <z> [angle] Teleport Link to coords");
|
||||||
|
output("tp @<ref> Teleport Link to actor");
|
||||||
|
output("tp @<ref> <x> <y> <z> [angle] Move actor to coords");
|
||||||
|
output("tp @<ref> @<ref> Move actor to actor");
|
||||||
|
output("transform Force transform");
|
||||||
|
output("unfreeze [@<ref>] Unfreeze globally, or unfreeze actor");
|
||||||
|
output("warp <stage> <point> <room> [layer] Warp to stage");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
output(fmt::format(FMT_STRING("Unknown command '{}' (try 'help')"), cmd));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace dusk
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace dusk {
|
||||||
|
|
||||||
|
using CommandOutput = std::function<void(std::string)>;
|
||||||
|
|
||||||
|
struct CommandState {
|
||||||
|
std::vector<std::string> history;
|
||||||
|
unsigned int foundProcId = 0;
|
||||||
|
std::string lastWarpStage;
|
||||||
|
bool echoEnabled = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Execute a single command line. Calls output() for every line of response.
|
||||||
|
// Manages history internally; callers need only hold a CommandState.
|
||||||
|
void runCommand(std::string_view cmdLine, CommandState& state, const CommandOutput& output);
|
||||||
|
|
||||||
|
// Per-sim-tick update for camera fly animation.
|
||||||
|
void processCameraCommands();
|
||||||
|
|
||||||
|
// Returns true when the dev camera is detached (suppresses the game's camera update).
|
||||||
|
bool isCameraDetached();
|
||||||
|
|
||||||
|
} // namespace dusk
|
||||||
@@ -0,0 +1,417 @@
|
|||||||
|
#include "dusk/d_trigger_view.h"
|
||||||
|
#include "dusk/settings.h"
|
||||||
|
#include "d/d_debug_viewer.h"
|
||||||
|
#include "d/d_com_inf_game.h"
|
||||||
|
#include "d/d_stage.h"
|
||||||
|
#include "d/d_path.h"
|
||||||
|
#include "d/d_attention.h"
|
||||||
|
#include "d/actor/d_a_alink.h"
|
||||||
|
#include "d/actor/d_a_kytag08.h"
|
||||||
|
#include "d/actor/d_a_e_rb.h"
|
||||||
|
#include "d/actor/d_a_e_rd.h"
|
||||||
|
#include "f_op/f_op_actor_mng.h"
|
||||||
|
#include "f_op/f_op_actor_tag.h"
|
||||||
|
#include "f_pc/f_pc_name.h"
|
||||||
|
#include "m_Do/m_Do_mtx.h"
|
||||||
|
#include "SSystem/SComponent/c_tag.h"
|
||||||
|
#include "SSystem/SComponent/c_list.h"
|
||||||
|
#include "JSystem/JMath/JMath.h"
|
||||||
|
|
||||||
|
namespace dusk {
|
||||||
|
namespace TriggerView {
|
||||||
|
|
||||||
|
static u8 s_opacity;
|
||||||
|
|
||||||
|
typedef void (*DrawCallback)(fopAc_ac_c*);
|
||||||
|
|
||||||
|
static void searchActorForCallback(s16 actorName, DrawCallback callback) {
|
||||||
|
node_class* node = g_fopAcTg_Queue.mpHead;
|
||||||
|
|
||||||
|
for (int i = 0; i < g_fopAcTg_Queue.mSize; i++) {
|
||||||
|
if (node != NULL) {
|
||||||
|
create_tag_class* tag = (create_tag_class*)node;
|
||||||
|
fopAc_ac_c* actorData = (fopAc_ac_c*)tag->mpTagData;
|
||||||
|
|
||||||
|
bool checkAll = actorName == -1;
|
||||||
|
if (actorData != NULL && (fopAcM_GetName(actorData) == actorName || checkAll)) {
|
||||||
|
callback(actorData);
|
||||||
|
}
|
||||||
|
node = node->mpNextNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drawSceneExit(fopAc_ac_c* actor) {
|
||||||
|
struct daScex_c : public fopAc_ac_c {
|
||||||
|
/* 0x568 */ Mtx mMatrix;
|
||||||
|
};
|
||||||
|
daScex_c* scex = (daScex_c*)actor;
|
||||||
|
|
||||||
|
cXyz points[8];
|
||||||
|
points[0].set(-actor->scale.x, actor->scale.y, -actor->scale.z);
|
||||||
|
points[1].set(actor->scale.x, actor->scale.y, -actor->scale.z);
|
||||||
|
points[2].set(-actor->scale.x, actor->scale.y, actor->scale.z);
|
||||||
|
points[3].set(actor->scale.x, actor->scale.y, actor->scale.z);
|
||||||
|
points[4].set(-actor->scale.x, 0.0f, -actor->scale.z);
|
||||||
|
points[5].set(actor->scale.x, 0.0f, -actor->scale.z);
|
||||||
|
points[6].set(-actor->scale.x, 0.0f, actor->scale.z);
|
||||||
|
points[7].set(actor->scale.x, 0.0f, actor->scale.z);
|
||||||
|
|
||||||
|
mDoMtx_inverse(scex->mMatrix, mDoMtx_stack_c::get());
|
||||||
|
mDoMtx_multVecArray(mDoMtx_stack_c::get(), points, points, 8);
|
||||||
|
|
||||||
|
GXColor color = {0xFF, 0x00, 0xFF, s_opacity};
|
||||||
|
dDbVw_drawCube8pXlu(points, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drawMidnaStop(fopAc_ac_c* actor) {
|
||||||
|
struct daMidnaStop_c : public fopAc_ac_c {
|
||||||
|
/* 0x568 */ u8 data[0x5C0 - 0x568];
|
||||||
|
/* 0x5C0 */ f32 mRangeXZ;
|
||||||
|
/* 0x5C4 */ f32 mRangeY;
|
||||||
|
};
|
||||||
|
daMidnaStop_c* mstop = (daMidnaStop_c*)actor;
|
||||||
|
|
||||||
|
GXColor color = {0x4A, 0x36, 0xBA, s_opacity};
|
||||||
|
dDbVw_drawCylinderXlu(mstop->current.pos, mstop->scale.x * 100.0f, mstop->scale.y, color, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drawPlumTag(fopAc_ac_c* actor) {
|
||||||
|
GXColor color = {0x00, 0xFF, 0x00, s_opacity};
|
||||||
|
dDbVw_drawCylinderXlu(actor->current.pos, actor->scale.x * 100.0f, 1000000.0f, color, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drawPlumSearch(fopAc_ac_c* actor) {
|
||||||
|
GXColor color = {0xFF, 0x00, 0x00, s_opacity};
|
||||||
|
const f32 search_dist = 500.0f;
|
||||||
|
dDbVw_drawCircleXlu(actor->attention_info.position, search_dist + 160.0f, color, 1, 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drawSwitchArea(fopAc_ac_c* actor) {
|
||||||
|
struct daSwc00_c : public fopAc_ac_c {
|
||||||
|
/* 0x568 */ cXyz mStart;
|
||||||
|
/* 0x574 */ cXyz mEnd;
|
||||||
|
/* 0x580 */ u16 mEventID;
|
||||||
|
/* 0x582 */ u8 mAction;
|
||||||
|
};
|
||||||
|
daSwc00_c* swc = (daSwc00_c*)actor;
|
||||||
|
int shape_type = (fopAcM_GetParam(actor) >> 0x12) & 3;
|
||||||
|
|
||||||
|
GXColor color = {0x00, 0x00, 0xFF, s_opacity};
|
||||||
|
if (shape_type == 3) {
|
||||||
|
dDbVw_drawCylinderXlu(swc->current.pos, JMAFastSqrt(swc->scale.x) - 30.0f, swc->scale.y, color, 1);
|
||||||
|
} else if (shape_type == 0) {
|
||||||
|
cXyz size = swc->mEnd - swc->mStart;
|
||||||
|
size *= 0.5f;
|
||||||
|
cXyz pos = swc->mStart + size;
|
||||||
|
csXyz angle(swc->current.angle.x, swc->current.angle.y, swc->current.angle.z);
|
||||||
|
dDbVw_drawCubeXlu(pos, size, angle, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drawEventArea(fopAc_ac_c* actor) {
|
||||||
|
u8 type = (actor->shape_angle.z & 0xFF);
|
||||||
|
if (type == 0xFF) {
|
||||||
|
type = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == 15 || type == 16) {
|
||||||
|
GXColor color = {0xFF, 0xFF, 0x00, s_opacity};
|
||||||
|
cXyz points[8];
|
||||||
|
points[0].set(-actor->scale.x, actor->scale.y, -actor->scale.z);
|
||||||
|
points[1].set(actor->scale.x, actor->scale.y, -actor->scale.z);
|
||||||
|
points[2].set(-actor->scale.x, actor->scale.y, actor->scale.z);
|
||||||
|
points[3].set(actor->scale.x, actor->scale.y, actor->scale.z);
|
||||||
|
points[4].set(-actor->scale.x, 0.0f, -actor->scale.z);
|
||||||
|
points[5].set(actor->scale.x, 0.0f, -actor->scale.z);
|
||||||
|
points[6].set(-actor->scale.x, 0.0f, actor->scale.z);
|
||||||
|
points[7].set(actor->scale.x, 0.0f, actor->scale.z);
|
||||||
|
|
||||||
|
mDoMtx_stack_c::transS(actor->home.pos.x, actor->home.pos.y, actor->home.pos.z);
|
||||||
|
mDoMtx_stack_c::YrotS(actor->current.angle.y);
|
||||||
|
mDoMtx_multVecArray(mDoMtx_stack_c::get(), points, points, 8);
|
||||||
|
|
||||||
|
dDbVw_drawCube8pXlu(points, color);
|
||||||
|
} else {
|
||||||
|
GXColor outer_color = {0xFF, 0x00, 0x00, s_opacity};
|
||||||
|
GXColor inner_color = {0x00, 0xFF, 0x00, s_opacity};
|
||||||
|
cXyz pos = actor->current.pos;
|
||||||
|
|
||||||
|
daAlink_c* player = (daAlink_c*)dComIfGp_getPlayer(0);
|
||||||
|
if (player != NULL && pos.y < player->mLinkAcch.GetGroundH()) {
|
||||||
|
pos.y = player->mLinkAcch.GetGroundH() + 100.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
const f32 inner_scale = 0.83f;
|
||||||
|
dDbVw_drawCircleXlu(pos, actor->scale.x * inner_scale, inner_color, 1, 20);
|
||||||
|
dDbVw_drawCircleXlu(pos, actor->scale.x, outer_color, 1, 20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drawEventTag(fopAc_ac_c* actor) {
|
||||||
|
GXColor color = {0x00, 0xC8, 0xFF, s_opacity};
|
||||||
|
u16 area_type = actor->home.angle.x & 0x8000;
|
||||||
|
|
||||||
|
if (area_type == 0x8000) {
|
||||||
|
cXyz start(actor->current.pos.x - (actor->scale.x * 0.5f), actor->current.pos.y,
|
||||||
|
actor->current.pos.z - (actor->scale.z * 0.5f));
|
||||||
|
cXyz end(actor->current.pos.x + (actor->scale.x * 0.5f),
|
||||||
|
actor->current.pos.y + actor->scale.y,
|
||||||
|
actor->current.pos.z + (actor->scale.z * 0.5f));
|
||||||
|
|
||||||
|
cXyz points[8];
|
||||||
|
points[0].set(start.x, start.y, start.z);
|
||||||
|
points[1].set(start.x, start.y, end.z);
|
||||||
|
points[2].set(end.x, start.y, end.z);
|
||||||
|
points[3].set(end.x, start.y, start.z);
|
||||||
|
points[4].set(start.x, end.y, start.z);
|
||||||
|
points[5].set(start.x, end.y, end.z);
|
||||||
|
points[6].set(end.x, end.y, end.z);
|
||||||
|
points[7].set(end.x, end.y, start.z);
|
||||||
|
|
||||||
|
dDbVw_drawCube8pXlu(points, color);
|
||||||
|
} else {
|
||||||
|
cXyz pos = actor->current.pos;
|
||||||
|
pos.y -= actor->scale.y;
|
||||||
|
dDbVw_drawCylinderXlu(pos, actor->scale.x, actor->scale.y * 2, color, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drawTWGate(fopAc_ac_c* actor) {
|
||||||
|
GXColor color = {0xFF, 0xFF, 0xFF, s_opacity};
|
||||||
|
dDbVw_drawCylinderXlu(actor->current.pos, actor->scale.x * 100.0f, actor->scale.y * 100.0f, color, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static u8 s_pathColorIndex = 0;
|
||||||
|
|
||||||
|
static void drawPaths(dStage_dPath_c* paths) {
|
||||||
|
static const GXColor colors[8] = {
|
||||||
|
{0xFF, 0xFF, 0xFF}, {0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00}, {0x00, 0xFF, 0x00},
|
||||||
|
{0x00, 0x00, 0xFF}, {0xFF, 0xFF, 0x00}, {0xFF, 0x00, 0xFF}, {0x00, 0xFF, 0xFF},
|
||||||
|
};
|
||||||
|
|
||||||
|
cXyz cubeSize = {30.0f, 30.0f, 30.0f};
|
||||||
|
csXyz cubeAngle = {0, 0, 0};
|
||||||
|
|
||||||
|
for (int i = 0; i < (int)paths->num; i++) {
|
||||||
|
dPath* path = &paths->m_path[i];
|
||||||
|
GXColor color = colors[(s_pathColorIndex++) & 7];
|
||||||
|
color.a = s_opacity;
|
||||||
|
cXyz a, b;
|
||||||
|
|
||||||
|
if (dPath_ChkClose(path) && path->m_num > 2) {
|
||||||
|
a = (Vec)path->m_points[0].m_position;
|
||||||
|
b = (Vec)path->m_points[(int)path->m_num - 1].m_position;
|
||||||
|
dDbVw_drawLineXlu(a, b, color, 1, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int j = 0; j < (int)path->m_num - 1; j++) {
|
||||||
|
a = (Vec)path->m_points[j].m_position;
|
||||||
|
b = (Vec)path->m_points[j + 1].m_position;
|
||||||
|
dDbVw_drawLineXlu(a, b, color, 1, 10);
|
||||||
|
dDbVw_drawCubeXlu(a, cubeSize, cubeAngle, color);
|
||||||
|
}
|
||||||
|
dDbVw_drawCubeXlu(b, cubeSize, cubeAngle, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drawStagePaths() {
|
||||||
|
dStage_dPath_c* stagePaths = g_dComIfG_gameInfo.play.getStage().getPath2Inf();
|
||||||
|
if (stagePaths != nullptr) {
|
||||||
|
drawPaths(stagePaths);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drawCurrentRoomPaths() {
|
||||||
|
fopAc_ac_c* player = dComIfGp_getPlayer(0);
|
||||||
|
if (player == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 roomNo = fopAcM_GetRoomNo(player);
|
||||||
|
if (roomNo < 0 || roomNo >= 64) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dStage_dPath_c* roomPaths = dStage_roomControl_c::mStatus[roomNo].mRoomDt.getPath2Inf();
|
||||||
|
if (roomPaths != nullptr) {
|
||||||
|
drawPaths(roomPaths);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drawCheckpointTag(fopAc_ac_c* actor) {
|
||||||
|
struct daTagChgRestart_c : public fopAc_ac_c {
|
||||||
|
/* 0x568 */ cXyz mVertices[4];
|
||||||
|
};
|
||||||
|
daTagChgRestart_c* chk = (daTagChgRestart_c*)actor;
|
||||||
|
|
||||||
|
GXColor color = {0x29, 0xF0, 0xFF, s_opacity};
|
||||||
|
cXyz points[8];
|
||||||
|
|
||||||
|
mDoMtx_stack_c::transS(actor->current.pos.x, actor->current.pos.y, actor->current.pos.z);
|
||||||
|
mDoMtx_stack_c::YrotM(actor->current.angle.y);
|
||||||
|
|
||||||
|
points[0] = chk->mVertices[0];
|
||||||
|
points[1] = chk->mVertices[1];
|
||||||
|
points[2] = chk->mVertices[3];
|
||||||
|
points[3] = chk->mVertices[2];
|
||||||
|
points[4] = chk->mVertices[0];
|
||||||
|
points[5] = chk->mVertices[1];
|
||||||
|
points[6] = chk->mVertices[3];
|
||||||
|
points[7] = chk->mVertices[2];
|
||||||
|
|
||||||
|
mDoMtx_multVecArray(mDoMtx_stack_c::get(), points, points, 8);
|
||||||
|
|
||||||
|
fopAc_ac_c* player = dComIfGp_getPlayer(0);
|
||||||
|
if (player != nullptr) {
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
points[i].y = player->current.pos.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
points[i].y += 1000.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
dDbVw_drawCube8pXlu(points, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drawTransformDists(fopAc_ac_c* actor) {
|
||||||
|
if (fopAcM_GetGroup(actor) == 4 && !(actor->actor_status & fopAcStts_UNK_0x8000000_e)) {
|
||||||
|
GXColor near_color = {0x00, 0xFF, 0x00, s_opacity};
|
||||||
|
GXColor far_color = {0xFF, 0x00, 0x00, s_opacity};
|
||||||
|
|
||||||
|
const f32 near_dist = 400.0f;
|
||||||
|
const f32 far_dist = 5000.0f;
|
||||||
|
|
||||||
|
dDbVw_drawCircleXlu(actor->eyePos, near_dist, near_color, 1, 20);
|
||||||
|
dDbVw_drawCircleXlu(actor->eyePos, far_dist, far_color, 1, 20);
|
||||||
|
|
||||||
|
const s16 view_range = 0x4000;
|
||||||
|
cXyz offset(0.0f, 0.0f, far_dist);
|
||||||
|
cXyz endpos;
|
||||||
|
|
||||||
|
mDoMtx_stack_c::transS(actor->eyePos.x, actor->eyePos.y, actor->eyePos.z);
|
||||||
|
mDoMtx_stack_c::YrotM(actor->shape_angle.y);
|
||||||
|
mDoMtx_stack_c::YrotM(-view_range);
|
||||||
|
mDoMtx_stack_c::multVec(&offset, &endpos);
|
||||||
|
dDbVw_drawLineXlu(actor->eyePos, endpos, far_color, 1, 10);
|
||||||
|
|
||||||
|
mDoMtx_stack_c::transS(actor->eyePos.x, actor->eyePos.y, actor->eyePos.z);
|
||||||
|
mDoMtx_stack_c::YrotM(actor->shape_angle.y);
|
||||||
|
mDoMtx_stack_c::YrotM(view_range);
|
||||||
|
mDoMtx_stack_c::multVec(&offset, &endpos);
|
||||||
|
dDbVw_drawLineXlu(actor->eyePos, endpos, far_color, 1, 10);
|
||||||
|
|
||||||
|
mDoMtx_stack_c::transS(actor->eyePos.x, actor->eyePos.y, actor->eyePos.z);
|
||||||
|
mDoMtx_stack_c::YrotM(actor->shape_angle.y);
|
||||||
|
mDoMtx_stack_c::multVec(&offset, &endpos);
|
||||||
|
dDbVw_drawLineXlu(actor->eyePos, endpos, far_color, 1, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drawAttentionDists(fopAc_ac_c* actor) {
|
||||||
|
if (fopAcM_GetGroup(actor) != 4) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GXColor lock_color = {0x00, 0x00, 0xFF, s_opacity};
|
||||||
|
GXColor talk_color = {0x00, 0xFF, 0x00, s_opacity};
|
||||||
|
|
||||||
|
dist_entry& lock_inf = dAttention_c::getDistTable(actor->attention_info.distances[fopAc_attn_LOCK_e]);
|
||||||
|
dist_entry& talk_inf = dAttention_c::getDistTable(actor->attention_info.distances[fopAc_attn_TALK_e]);
|
||||||
|
|
||||||
|
dDbVw_drawCircleXlu(actor->attention_info.position, lock_inf.mDistMax, lock_color, 1, 20);
|
||||||
|
dDbVw_drawCircleXlu(actor->attention_info.position, talk_inf.mDistMax, talk_color, 1, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drawPurpleMistAvoid(fopAc_ac_c* actor) {
|
||||||
|
kytag08_class* tag = (kytag08_class*)actor;
|
||||||
|
|
||||||
|
GXColor avoidColor = {0x00, 0xFF, 0x00, s_opacity};
|
||||||
|
GXColor targetColor = {0xFF, 0x00, 0xFF, s_opacity};
|
||||||
|
|
||||||
|
dDbVw_drawCircleXlu(tag->mAvoidPos, tag->mSize.x * 45.0f * tag->mSizeScale, avoidColor, 1, 20);
|
||||||
|
|
||||||
|
cXyz cubeSize(10.0f, 10.0f, 10.0f);
|
||||||
|
csXyz cubeAngle(0, 0, 0);
|
||||||
|
dDbVw_drawCubeXlu(tag->mAvoidPos, cubeSize, cubeAngle, avoidColor);
|
||||||
|
dDbVw_drawCubeXlu(tag->mTargetAvoidPos, cubeSize, cubeAngle, targetColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drawLeeverData(fopAc_ac_c* actor) {
|
||||||
|
e_rb_class* leever = (e_rb_class*)actor;
|
||||||
|
|
||||||
|
if (leever->isChild) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GXColor color = {0xFF, 0x00, 0x00, s_opacity};
|
||||||
|
GXColor color2 = {0x00, 0x00, 0xFF, s_opacity};
|
||||||
|
|
||||||
|
cXyz pos = actor->current.pos;
|
||||||
|
daAlink_c* player = (daAlink_c*)dComIfGp_getPlayer(0);
|
||||||
|
if (player != nullptr && pos.y < player->mLinkAcch.GetGroundH()) {
|
||||||
|
pos.y = player->mLinkAcch.GetGroundH() + 100.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
dDbVw_drawCircleXlu(pos, leever->appearRange * 100.0f, color, 1, 20);
|
||||||
|
dDbVw_drawCircleXlu(pos, leever->field_0xa69 * 100.0f, color2, 1, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
void execute() {
|
||||||
|
const auto& settings = getTransientSettings().triggerView;
|
||||||
|
s_opacity = (u8)(255.0f * (settings.opacity / 100.0f));
|
||||||
|
|
||||||
|
if (settings.loadZones) {
|
||||||
|
searchActorForCallback(fpcNm_SCENE_EXIT_e, drawSceneExit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.midnaStops) {
|
||||||
|
searchActorForCallback(fpcNm_Tag_Mstop_e, drawMidnaStop);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.switchAreas) {
|
||||||
|
searchActorForCallback(fpcNm_SWC00_e, drawSwitchArea);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.eventAreas) {
|
||||||
|
searchActorForCallback(fpcNm_TAG_EVENT_e, drawEventTag);
|
||||||
|
searchActorForCallback(fpcNm_TAG_EVTAREA_e, drawEventArea);
|
||||||
|
searchActorForCallback(fpcNm_TAG_MYNA2_e, drawPlumTag);
|
||||||
|
searchActorForCallback(fpcNm_MYNA2_e, drawPlumSearch);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.twilightGates) {
|
||||||
|
searchActorForCallback(fpcNm_Tag_TWGate_e, drawTWGate);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.paths) {
|
||||||
|
s_pathColorIndex = 0;
|
||||||
|
drawStagePaths();
|
||||||
|
drawCurrentRoomPaths();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.checkpoints) {
|
||||||
|
searchActorForCallback(fpcNm_Tag_ChgRestart_e, drawCheckpointTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.transformDists) {
|
||||||
|
searchActorForCallback(-1, drawTransformDists);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.attentionDists) {
|
||||||
|
searchActorForCallback(-1, drawAttentionDists);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.purpleMistAvoid) {
|
||||||
|
searchActorForCallback(fpcNm_KYTAG08_e, drawPurpleMistAvoid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.leevers) {
|
||||||
|
searchActorForCallback(fpcNm_E_RB_e, drawLeeverData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace TriggerView
|
||||||
|
} // namespace dusk
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace dusk {
|
||||||
|
namespace TriggerView {
|
||||||
|
void execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -50,6 +50,16 @@ void reset() {
|
|||||||
s_simTickActive = false;
|
s_simTickActive = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_sim_rate(float hz) {
|
||||||
|
const float maximumHz = aurora::time::kMaximumTimeScale / kSimPeriod;
|
||||||
|
aurora::time::set_scale(std::clamp(hz, 1.0f, maximumHz) * kSimPeriod);
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
float get_sim_rate() {
|
||||||
|
return aurora::time::scale() / kSimPeriod;
|
||||||
|
}
|
||||||
|
|
||||||
const FrameTiming& advance() {
|
const FrameTiming& advance() {
|
||||||
const auto nativeNow = native_clock::now();
|
const auto nativeNow = native_clock::now();
|
||||||
const auto gameNow = game_clock::now();
|
const auto gameNow = game_clock::now();
|
||||||
|
|||||||
@@ -28,4 +28,8 @@ float sample_interpolation_step();
|
|||||||
|
|
||||||
float consume_interval(const void* consumer);
|
float consume_interval(const void* consumer);
|
||||||
|
|
||||||
|
// Sets the effective simulation rate through the game clock time scale.
|
||||||
|
void set_sim_rate(float hz);
|
||||||
|
float get_sim_rate();
|
||||||
|
|
||||||
} // namespace dusk::game_clock
|
} // namespace dusk::game_clock
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
#include "dusk/game_combos.h"
|
||||||
|
|
||||||
|
#include "SSystem/SComponent/c_API_controller_pad.h"
|
||||||
|
#include "SSystem/SComponent/c_xyz.h"
|
||||||
|
#include "d/actor/d_a_alink.h"
|
||||||
|
#include "d/d_com_inf_game.h"
|
||||||
|
#include "dusk/settings.h"
|
||||||
|
#include "m_Do/m_Do_controller_pad.h"
|
||||||
|
|
||||||
|
namespace dusk {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
cXyz s_savedTeleportPos{};
|
||||||
|
s16 s_savedTeleportAngle = 0;
|
||||||
|
bool s_hasTeleportPos = false;
|
||||||
|
|
||||||
|
static daAlink_c* getPlayer() {
|
||||||
|
return (daAlink_c*)dComIfGp_getPlayer(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void consumeButtons(u32 mask) {
|
||||||
|
mDoCPd_c::getCpadInfo(PAD_1).mPressedButtonFlags &= ~mask;
|
||||||
|
mDoCPd_c::getCpadInfo(PAD_1).mButtonFlags &= ~mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Table: holdMask, trigMask, strict, condition, action, consumeMask, exclusive
|
||||||
|
static const GameCombo kCombos[] = {
|
||||||
|
// Move Link (L+R+Y), pass-through, non-exclusive
|
||||||
|
{
|
||||||
|
PAD_TRIGGER_R | PAD_TRIGGER_L,
|
||||||
|
PAD_BUTTON_Y,
|
||||||
|
false,
|
||||||
|
[] { return (bool)getSettings().game.enableMoveLinkCombo; },
|
||||||
|
[] { getTransientSettings().moveLinkActive = !getTransientSettings().moveLinkActive; },
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
// Quick Transform (R+Y, strictly only R held)
|
||||||
|
{
|
||||||
|
PAD_TRIGGER_R,
|
||||||
|
PAD_BUTTON_Y,
|
||||||
|
true,
|
||||||
|
[] { return getPlayer() != nullptr; },
|
||||||
|
[] { getPlayer()->handleQuickTransform(); },
|
||||||
|
PAD_BUTTON_Y,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
// Wolf Howl (R+X)
|
||||||
|
{
|
||||||
|
PAD_TRIGGER_R,
|
||||||
|
PAD_BUTTON_X,
|
||||||
|
false,
|
||||||
|
[] { return getPlayer() != nullptr; },
|
||||||
|
[] { getPlayer()->handleWolfHowl(); },
|
||||||
|
PAD_BUTTON_X,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
// Teleport save (R+D-pad Up), consumes D-pad Up, exclusive
|
||||||
|
{
|
||||||
|
PAD_TRIGGER_R,
|
||||||
|
PAD_BUTTON_UP,
|
||||||
|
false,
|
||||||
|
[] { return getSettings().game.enableTeleportCombo && getPlayer() != nullptr; },
|
||||||
|
[] {
|
||||||
|
auto* p = getPlayer();
|
||||||
|
s_savedTeleportPos = p->current.pos;
|
||||||
|
s_savedTeleportAngle = p->shape_angle.y;
|
||||||
|
s_hasTeleportPos = true;
|
||||||
|
},
|
||||||
|
PAD_BUTTON_UP,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
// Teleport load (R+D-pad Down), consumes D-pad Down, exclusive
|
||||||
|
{
|
||||||
|
PAD_TRIGGER_R,
|
||||||
|
PAD_BUTTON_DOWN,
|
||||||
|
false,
|
||||||
|
[] {
|
||||||
|
return getSettings().game.enableTeleportCombo && s_hasTeleportPos &&
|
||||||
|
getPlayer() != nullptr;
|
||||||
|
},
|
||||||
|
[] {
|
||||||
|
auto* p = getPlayer();
|
||||||
|
p->current.pos = s_savedTeleportPos;
|
||||||
|
p->shape_angle.y = s_savedTeleportAngle;
|
||||||
|
p->mNormalSpeed = 0.0f;
|
||||||
|
},
|
||||||
|
PAD_BUTTON_DOWN,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
// Moon Jump (R+A, hold), continuous, pass-through, non-exclusive
|
||||||
|
{
|
||||||
|
PAD_TRIGGER_R | PAD_BUTTON_A,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
[] { return getSettings().game.moonJump && getPlayer() != nullptr; },
|
||||||
|
[] { getPlayer()->speed.y = 56.0f; },
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void processGameCombos() {
|
||||||
|
if (!getSettings().game.enableMoveLinkCombo) {
|
||||||
|
getTransientSettings().moveLinkActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const u32 held = mDoCPd_c::getHold(PAD_1);
|
||||||
|
const u32 trig = mDoCPd_c::getTrig(PAD_1);
|
||||||
|
|
||||||
|
for (const auto& combo : kCombos) {
|
||||||
|
if ((held & combo.holdMask) != combo.holdMask) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (combo.strict && (held & ~combo.trigMask) != combo.holdMask) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (combo.trigMask != 0 && !(trig & combo.trigMask)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (combo.condition != nullptr && !combo.condition()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
combo.action();
|
||||||
|
if (combo.consumeMask != 0) {
|
||||||
|
consumeButtons(combo.consumeMask);
|
||||||
|
}
|
||||||
|
if (combo.exclusive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace dusk
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <dolphin/types.h>
|
||||||
|
|
||||||
|
namespace dusk {
|
||||||
|
|
||||||
|
struct GameCombo {
|
||||||
|
using ConditionFn = bool(*)();
|
||||||
|
using ActionFn = void(*)();
|
||||||
|
|
||||||
|
u32 holdMask; // all of these must be held (getHold)
|
||||||
|
u32 trigMask; // at least one must be newly triggered (getTrig); 0 = fires every frame while holdMask is held
|
||||||
|
bool strict; // if true: (held & ~trigMask) must equal holdMask exactly, no extra buttons held
|
||||||
|
ConditionFn condition; // extra game-state guard; nullptr = no extra check
|
||||||
|
ActionFn action; // runs when the combo is fired
|
||||||
|
u32 consumeMask; // buttons to clear after firing; 0 = pass-through
|
||||||
|
bool exclusive; // stop evaluating future combos if this fires
|
||||||
|
};
|
||||||
|
|
||||||
|
void processGameCombos();
|
||||||
|
|
||||||
|
} // namespace dusk
|
||||||
@@ -264,13 +264,6 @@ namespace dusk {
|
|||||||
}
|
}
|
||||||
previousTurboActive = turboActive;
|
previousTurboActive = turboActive;
|
||||||
previousSlowActive = slowDown;
|
previousSlowActive = slowDown;
|
||||||
|
|
||||||
if (frame_interp::get_ui_tick_pending() && mDoMain::developmentMode == 1 && (mDoCPd_c::getHold(PAD_1) & (PAD_TRIGGER_R | PAD_TRIGGER_L)) == (PAD_TRIGGER_R | PAD_TRIGGER_L) && mDoCPd_c::getTrigY(PAD_1)) {
|
|
||||||
getTransientSettings().moveLinkActive = !getTransientSettings().moveLinkActive;
|
|
||||||
}
|
|
||||||
if (mDoMain::developmentMode != 1) {
|
|
||||||
getTransientSettings().moveLinkActive = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImGuiConsole::PreDraw() {
|
void ImGuiConsole::PreDraw() {
|
||||||
|
|||||||
@@ -89,6 +89,26 @@ namespace dusk {
|
|||||||
ImGui::EndMenu();
|
ImGui::EndMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto& triggerView = getTransientSettings().triggerView;
|
||||||
|
if (ImGui::BeginMenu("Trigger View")) {
|
||||||
|
ImGui::Checkbox("Load Zones", &triggerView.loadZones);
|
||||||
|
ImGui::Checkbox("Event Areas", &triggerView.eventAreas);
|
||||||
|
ImGui::Checkbox("Event Tags", &triggerView.eventTags);
|
||||||
|
ImGui::Checkbox("Switch Areas", &triggerView.switchAreas);
|
||||||
|
ImGui::Checkbox("Midna Stops", &triggerView.midnaStops);
|
||||||
|
ImGui::Checkbox("Twilight Gates", &triggerView.twilightGates);
|
||||||
|
ImGui::Checkbox("Checkpoints", &triggerView.checkpoints);
|
||||||
|
ImGui::Checkbox("Paths", &triggerView.paths);
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::Checkbox("Transform Distances", &triggerView.transformDists);
|
||||||
|
ImGui::Checkbox("Attention Distances", &triggerView.attentionDists);
|
||||||
|
ImGui::Checkbox("Purple Mist Avoid", &triggerView.purpleMistAvoid);
|
||||||
|
ImGui::Checkbox("Leever Ranges", &triggerView.leevers);
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::SliderFloat("Opacity##triggers", &triggerView.opacity, 0.0f, 100.0f);
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
if (!dusk::IsGameLaunched) {
|
if (!dusk::IsGameLaunched) {
|
||||||
ImGui::BeginDisabled();
|
ImGui::BeginDisabled();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,6 +158,8 @@ UserSettings g_userSettings = {
|
|||||||
.removeQuestMapMarkers {"game.removeQuestMapMarkers", false},
|
.removeQuestMapMarkers {"game.removeQuestMapMarkers", false},
|
||||||
.showInputViewer {"game.showInputViewer", false},
|
.showInputViewer {"game.showInputViewer", false},
|
||||||
.showInputViewerGyro {"game.showInputViewerGyro", false},
|
.showInputViewerGyro {"game.showInputViewerGyro", false},
|
||||||
|
.enableMoveLinkCombo {"game.enableMoveLinkCombo", false},
|
||||||
|
.enableTeleportCombo {"game.enableTeleportCombo", false},
|
||||||
.lastSelectedGameModeId {"game.lastSelectedGameModeId", gamemode::kVanillaGameModeId}
|
.lastSelectedGameModeId {"game.lastSelectedGameModeId", gamemode::kVanillaGameModeId}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -308,6 +310,8 @@ void registerSettings() {
|
|||||||
Register(g_userSettings.game.removeQuestMapMarkers);
|
Register(g_userSettings.game.removeQuestMapMarkers);
|
||||||
Register(g_userSettings.game.showInputViewer);
|
Register(g_userSettings.game.showInputViewer);
|
||||||
Register(g_userSettings.game.showInputViewerGyro);
|
Register(g_userSettings.game.showInputViewerGyro);
|
||||||
|
Register(g_userSettings.game.enableMoveLinkCombo);
|
||||||
|
Register(g_userSettings.game.enableTeleportCombo);
|
||||||
Register(g_userSettings.game.lastSelectedGameModeId);
|
Register(g_userSettings.game.lastSelectedGameModeId);
|
||||||
Register(g_userSettings.game.fastSpinner);
|
Register(g_userSettings.game.fastSpinner);
|
||||||
Register(g_userSettings.game.infiniteHearts);
|
Register(g_userSettings.game.infiniteHearts);
|
||||||
@@ -399,6 +403,21 @@ static TransientSettings g_transientSettings = {
|
|||||||
.colliderViewOpacity = 50.0f,
|
.colliderViewOpacity = 50.0f,
|
||||||
.drawRange = 100.0f,
|
.drawRange = 100.0f,
|
||||||
},
|
},
|
||||||
|
.triggerView = {
|
||||||
|
.loadZones = false,
|
||||||
|
.eventAreas = false,
|
||||||
|
.switchAreas = false,
|
||||||
|
.eventTags = false,
|
||||||
|
.midnaStops = false,
|
||||||
|
.twilightGates = false,
|
||||||
|
.checkpoints = false,
|
||||||
|
.paths = false,
|
||||||
|
.transformDists = false,
|
||||||
|
.attentionDists = false,
|
||||||
|
.purpleMistAvoid = false,
|
||||||
|
.leevers = false,
|
||||||
|
.opacity = 75.0f,
|
||||||
|
},
|
||||||
.turboMode = false,
|
.turboMode = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -285,6 +285,8 @@ struct UserSettings {
|
|||||||
ConfigVar<bool> removeQuestMapMarkers;
|
ConfigVar<bool> removeQuestMapMarkers;
|
||||||
ConfigVar<bool> showInputViewer;
|
ConfigVar<bool> showInputViewer;
|
||||||
ConfigVar<bool> showInputViewerGyro;
|
ConfigVar<bool> showInputViewerGyro;
|
||||||
|
ConfigVar<bool> enableMoveLinkCombo;
|
||||||
|
ConfigVar<bool> enableTeleportCombo;
|
||||||
|
|
||||||
ConfigVar<std::string> lastSelectedGameModeId;
|
ConfigVar<std::string> lastSelectedGameModeId;
|
||||||
} game;
|
} game;
|
||||||
@@ -328,8 +330,25 @@ struct CollisionViewSettings {
|
|||||||
float drawRange;
|
float drawRange;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct TriggerViewSettings {
|
||||||
|
bool loadZones;
|
||||||
|
bool eventAreas;
|
||||||
|
bool switchAreas;
|
||||||
|
bool eventTags;
|
||||||
|
bool midnaStops;
|
||||||
|
bool twilightGates;
|
||||||
|
bool checkpoints;
|
||||||
|
bool paths;
|
||||||
|
bool transformDists;
|
||||||
|
bool attentionDists;
|
||||||
|
bool purpleMistAvoid;
|
||||||
|
bool leevers;
|
||||||
|
float opacity;
|
||||||
|
};
|
||||||
|
|
||||||
struct TransientSettings {
|
struct TransientSettings {
|
||||||
CollisionViewSettings collisionView;
|
CollisionViewSettings collisionView;
|
||||||
|
TriggerViewSettings triggerView;
|
||||||
bool turboMode;
|
bool turboMode;
|
||||||
bool moveLinkActive;
|
bool moveLinkActive;
|
||||||
bool stateShareLoadActive;
|
bool stateShareLoadActive;
|
||||||
|
|||||||
@@ -0,0 +1,304 @@
|
|||||||
|
#include "command_console.hpp"
|
||||||
|
|
||||||
|
#include <RmlUi/Core.h>
|
||||||
|
#include <RmlUi/Core/Elements/ElementFormControlInput.h>
|
||||||
|
#include <aurora/rmlui.hpp>
|
||||||
|
|
||||||
|
#include "dusk/settings.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
#include "ui.hpp"
|
||||||
|
|
||||||
|
namespace dusk::ui {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
const Rml::String kDocumentSource = R"RML(
|
||||||
|
<rml>
|
||||||
|
<head>
|
||||||
|
<link type="text/rcss" href="res/rml/command_console.rcss" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<console id="console">
|
||||||
|
<output id="console-output"></output>
|
||||||
|
<input id="console-input" type="text" maxlength="255" />
|
||||||
|
</console>
|
||||||
|
</body>
|
||||||
|
</rml>
|
||||||
|
)RML";
|
||||||
|
|
||||||
|
bool is_command(std::string_view text) {
|
||||||
|
return text.size() >= 2 && text[0] == '>' && text[1] == ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
CommandConsole::CommandConsole() : Document(kDocumentSource, false, DocumentScope::CommandConsole) {
|
||||||
|
mConsole = mDocument ? mDocument->GetElementById("console") : nullptr;
|
||||||
|
mOutput = mDocument ? mDocument->GetElementById("console-output") : nullptr;
|
||||||
|
auto* rawInput = mDocument ? mDocument->GetElementById("console-input") : nullptr;
|
||||||
|
mInput = rmlui_dynamic_cast<Rml::ElementFormControlInput*>(rawInput);
|
||||||
|
|
||||||
|
listen(
|
||||||
|
Rml::EventId::Keydown,
|
||||||
|
[this](Rml::Event& event) {
|
||||||
|
if (!mInputActive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto key = static_cast<Rml::Input::KeyIdentifier>(
|
||||||
|
event.GetParameter<int>("key_identifier", Rml::Input::KI_UNKNOWN));
|
||||||
|
if (key == Rml::Input::KI_RETURN) {
|
||||||
|
execute_from_input();
|
||||||
|
event.StopImmediatePropagation();
|
||||||
|
} else if (key == Rml::Input::KI_ESCAPE) {
|
||||||
|
hide(false);
|
||||||
|
event.StopImmediatePropagation();
|
||||||
|
} else if (key == Rml::Input::KI_UP) {
|
||||||
|
navigate_history(-1);
|
||||||
|
event.StopImmediatePropagation();
|
||||||
|
} else if (key == Rml::Input::KI_DOWN) {
|
||||||
|
navigate_history(1);
|
||||||
|
event.StopImmediatePropagation();
|
||||||
|
} else if (key == Rml::Input::KI_PRIOR) {
|
||||||
|
scroll_messages(-1);
|
||||||
|
event.StopImmediatePropagation();
|
||||||
|
} else if (key == Rml::Input::KI_NEXT) {
|
||||||
|
scroll_messages(1);
|
||||||
|
event.StopImmediatePropagation();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
true);
|
||||||
|
|
||||||
|
listen(mOutput, Rml::EventId::Scroll, [this](Rml::Event&) {
|
||||||
|
const float bottom =
|
||||||
|
std::max(0.0f, mOutput->GetScrollHeight() - mOutput->GetClientHeight());
|
||||||
|
mStickToBottom = mOutput->GetScrollTop() >= bottom - 4.0f;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CommandConsole::handle_nav_command(Rml::Event&, NavCommand) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandConsole::update() {
|
||||||
|
if (!getSettings().backend.enableAdvancedSettings || is_prelaunch_open()) {
|
||||||
|
close_input();
|
||||||
|
Document::hide(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
update_message_lifetimes();
|
||||||
|
if (!mInputActive && !has_onscreen_messages()) {
|
||||||
|
Document::hide(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Document::visible()) {
|
||||||
|
Document::show();
|
||||||
|
}
|
||||||
|
if (mInputActive && mStickToBottom && mOutput != nullptr) {
|
||||||
|
mOutput->SetScrollTop(mOutput->GetScrollHeight() - mOutput->GetClientHeight());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandConsole::show() {
|
||||||
|
if (mInputActive || !getSettings().backend.enableAdvancedSettings || is_prelaunch_open()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mInputActive = true;
|
||||||
|
mHistoryPos = -1;
|
||||||
|
mStickToBottom = true;
|
||||||
|
if (mConsole != nullptr) {
|
||||||
|
mConsole->SetAttribute("open", "");
|
||||||
|
}
|
||||||
|
if (mOutput != nullptr) {
|
||||||
|
mOutput->SetAttribute("open", "");
|
||||||
|
}
|
||||||
|
if (mInput != nullptr) {
|
||||||
|
mInput->SetValue("");
|
||||||
|
}
|
||||||
|
Document::show();
|
||||||
|
focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CommandConsole::focus() {
|
||||||
|
if (mInputActive && mInput != nullptr) {
|
||||||
|
aurora::rmlui::set_input_type(aurora::rmlui::InputType::Text);
|
||||||
|
return mInput->Focus(true);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandConsole::hide(bool close) {
|
||||||
|
close_input();
|
||||||
|
if (close) {
|
||||||
|
Document::hide(true);
|
||||||
|
} else if (!has_onscreen_messages()) {
|
||||||
|
Document::hide(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto* doc = top_document()) {
|
||||||
|
doc->focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CommandConsole::visible() const {
|
||||||
|
return mInputActive && Document::visible();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CommandConsole::active() const {
|
||||||
|
return mInputActive && Document::active();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandConsole::close_input() {
|
||||||
|
if (!mInputActive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mInputActive = false;
|
||||||
|
mHistoryPos = -1;
|
||||||
|
if (mConsole != nullptr) {
|
||||||
|
mConsole->RemoveAttribute("open");
|
||||||
|
}
|
||||||
|
if (mInput != nullptr) {
|
||||||
|
mInput->SetValue("");
|
||||||
|
mInput->Blur();
|
||||||
|
}
|
||||||
|
if (mOutput != nullptr) {
|
||||||
|
mOutput->RemoveAttribute("open");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandConsole::execute_from_input() {
|
||||||
|
if (mInput == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const Rml::String value = mInput->GetValue();
|
||||||
|
hide(false);
|
||||||
|
if (!value.empty()) {
|
||||||
|
runCommand(value, mState, [this](std::string text) { append_message(std::move(text)); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandConsole::navigate_history(int direction) {
|
||||||
|
if (mState.history.empty() || mInput == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const int prev = mHistoryPos;
|
||||||
|
if (direction < 0) {
|
||||||
|
if (mHistoryPos == -1) {
|
||||||
|
mHistoryPos = (int)mState.history.size() - 1;
|
||||||
|
} else if (mHistoryPos > 0) {
|
||||||
|
--mHistoryPos;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (mHistoryPos != -1 && ++mHistoryPos >= (int)mState.history.size()) {
|
||||||
|
mHistoryPos = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (prev != mHistoryPos) {
|
||||||
|
const char* str = mHistoryPos >= 0 ? mState.history[mHistoryPos].c_str() : "";
|
||||||
|
mInput->SetValue(str);
|
||||||
|
const int end = static_cast<int>(Rml::StringUtilities::LengthUTF8(mInput->GetValue()));
|
||||||
|
mInput->SetSelectionRange(end, end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandConsole::scroll_messages(int pages) {
|
||||||
|
if (mOutput == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const float bottom = std::max(0.0f, mOutput->GetScrollHeight() - mOutput->GetClientHeight());
|
||||||
|
const float pageHeight = std::max(1.0f, mOutput->GetClientHeight() * 0.9f);
|
||||||
|
const float scrollTop =
|
||||||
|
std::clamp(mOutput->GetScrollTop() + static_cast<float>(pages) * pageHeight, 0.0f, bottom);
|
||||||
|
mOutput->SetScrollTop(scrollTop);
|
||||||
|
mStickToBottom = scrollTop >= bottom - 4.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandConsole::append_message(std::string text) {
|
||||||
|
auto* element = append(mOutput, "line");
|
||||||
|
if (element != nullptr) {
|
||||||
|
element->SetClass("cmd", is_command(text));
|
||||||
|
append_text(element, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto now = clock::now();
|
||||||
|
mMessages.push_back({
|
||||||
|
.text = std::move(text),
|
||||||
|
.element = element,
|
||||||
|
.fadeAt = now + kMessageDuration - kFadeDuration,
|
||||||
|
.hideAt = now + kMessageDuration,
|
||||||
|
});
|
||||||
|
|
||||||
|
while (mMessages.size() > kMaxMessageHistory) {
|
||||||
|
if (auto* oldElement = mMessages.front().element; oldElement != nullptr) {
|
||||||
|
if (auto* parent = oldElement->GetParentNode()) {
|
||||||
|
parent->RemoveChild(oldElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mMessages.pop_front();
|
||||||
|
}
|
||||||
|
limit_visible_messages();
|
||||||
|
if (mConsole != nullptr) {
|
||||||
|
mConsole->RemoveAttribute("fading");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getSettings().backend.enableAdvancedSettings && !is_prelaunch_open() &&
|
||||||
|
!Document::visible())
|
||||||
|
{
|
||||||
|
Document::show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandConsole::limit_visible_messages() {
|
||||||
|
std::size_t visibleCount = 0;
|
||||||
|
for (auto it = mMessages.rbegin(); it != mMessages.rend(); ++it) {
|
||||||
|
if (it->expired) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (++visibleCount <= kMaxVisibleLines) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
it->expired = true;
|
||||||
|
if (it->element != nullptr) {
|
||||||
|
it->element->SetAttribute("expired", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandConsole::update_message_lifetimes() {
|
||||||
|
const auto now = clock::now();
|
||||||
|
for (auto& message : mMessages) {
|
||||||
|
if (!message.fading && now >= message.fadeAt) {
|
||||||
|
message.fading = true;
|
||||||
|
if (message.element != nullptr) {
|
||||||
|
message.element->SetAttribute("fading", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!message.expired && now >= message.hideAt) {
|
||||||
|
message.expired = true;
|
||||||
|
if (message.element != nullptr) {
|
||||||
|
message.element->SetAttribute("expired", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool hasVisibleMessages = has_onscreen_messages();
|
||||||
|
const bool hasOpaqueMessages = std::ranges::any_of(
|
||||||
|
mMessages, [](const auto& message) { return !message.fading && !message.expired; });
|
||||||
|
if (mConsole != nullptr) {
|
||||||
|
if (hasVisibleMessages && !hasOpaqueMessages) {
|
||||||
|
mConsole->SetAttribute("fading", "");
|
||||||
|
} else {
|
||||||
|
mConsole->RemoveAttribute("fading");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CommandConsole::has_onscreen_messages() const {
|
||||||
|
return std::ranges::any_of(mMessages, [](const auto& message) { return !message.expired; });
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace dusk::ui
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "document.hpp"
|
||||||
|
#include "dusk/commands.hpp"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <deque>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Rml {
|
||||||
|
class ElementFormControlInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace dusk::ui {
|
||||||
|
|
||||||
|
class CommandConsole : public Document {
|
||||||
|
public:
|
||||||
|
CommandConsole();
|
||||||
|
|
||||||
|
void update() override;
|
||||||
|
void show() override;
|
||||||
|
bool focus() override;
|
||||||
|
void hide(bool close) override;
|
||||||
|
bool visible() const override;
|
||||||
|
bool active() const override;
|
||||||
|
bool permanent() const override { return true; }
|
||||||
|
|
||||||
|
bool input_active() const { return mInputActive; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct MessageLine {
|
||||||
|
std::string text;
|
||||||
|
Rml::Element* element = nullptr;
|
||||||
|
clock::time_point fadeAt;
|
||||||
|
clock::time_point hideAt;
|
||||||
|
bool fading = false;
|
||||||
|
bool expired = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr auto kMessageDuration = std::chrono::seconds{6};
|
||||||
|
static constexpr auto kFadeDuration = std::chrono::milliseconds{800};
|
||||||
|
static constexpr std::size_t kMaxVisibleLines = 24;
|
||||||
|
static constexpr std::size_t kMaxMessageHistory = 500;
|
||||||
|
|
||||||
|
Rml::Element* mConsole = nullptr;
|
||||||
|
Rml::Element* mOutput = nullptr;
|
||||||
|
Rml::ElementFormControlInput* mInput = nullptr;
|
||||||
|
|
||||||
|
std::deque<MessageLine> mMessages;
|
||||||
|
int mHistoryPos = -1;
|
||||||
|
bool mInputActive = false;
|
||||||
|
bool mStickToBottom = true;
|
||||||
|
|
||||||
|
CommandState mState;
|
||||||
|
|
||||||
|
bool handle_nav_command(Rml::Event& event, NavCommand cmd) override;
|
||||||
|
|
||||||
|
void append_message(std::string text);
|
||||||
|
void close_input();
|
||||||
|
void execute_from_input();
|
||||||
|
bool has_onscreen_messages() const;
|
||||||
|
void limit_visible_messages();
|
||||||
|
void navigate_history(int direction);
|
||||||
|
void scroll_messages(int pages);
|
||||||
|
void update_message_lifetimes();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace dusk::ui
|
||||||
@@ -84,6 +84,7 @@ void Document::show() {
|
|||||||
focus();
|
focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mPendingClose = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Document::hide(bool close) {
|
void Document::hide(bool close) {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ public:
|
|||||||
bool has_focus() const;
|
bool has_focus() const;
|
||||||
virtual bool visible() const;
|
virtual bool visible() const;
|
||||||
virtual bool active() const;
|
virtual bool active() const;
|
||||||
|
virtual bool permanent() const { return false; }
|
||||||
virtual bool obscures_game() const { return false; }
|
virtual bool obscures_game() const { return false; }
|
||||||
virtual void cover() {
|
virtual void cover() {
|
||||||
mWasVisible = visible();
|
mWasVisible = visible();
|
||||||
@@ -70,6 +71,7 @@ public:
|
|||||||
Document::hide(close);
|
Document::hide(close);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool pending_close() const { return mPendingClose; }
|
||||||
bool closed() const { return mClosed; }
|
bool closed() const { return mClosed; }
|
||||||
|
|
||||||
bool handle_nav_event(Rml::Event& event);
|
bool handle_nav_event(Rml::Event& event);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
#include "dusk/speedrun.h"
|
#include "dusk/speedrun.h"
|
||||||
#include "dusk/ui/prelaunch.hpp"
|
#include "dusk/ui/prelaunch.hpp"
|
||||||
#include "editor.hpp"
|
#include "editor.hpp"
|
||||||
|
#include "f_op/f_op_scene_mng.h"
|
||||||
#include "f_pc/f_pc_manager.h"
|
#include "f_pc/f_pc_manager.h"
|
||||||
#include "f_pc/f_pc_name.h"
|
#include "f_pc/f_pc_name.h"
|
||||||
#include "imgui.h"
|
#include "imgui.h"
|
||||||
@@ -166,6 +167,10 @@ void MenuBar::build_tabs() {
|
|||||||
if (getSettings().game.liveSplitEnabled) {
|
if (getSettings().game.liveSplitEnabled) {
|
||||||
dusk::speedrun::reset();
|
dusk::speedrun::reset();
|
||||||
}
|
}
|
||||||
|
auto* playScene = fpcM_SearchByName(fpcNm_PLAY_SCENE_e);
|
||||||
|
if (playScene != nullptr) {
|
||||||
|
fopScnM_ChangeReq((scene_class*)playScene, fpcNm_NAME_SCENE_e, 0x7FFF, 0);
|
||||||
|
}
|
||||||
hide(false);
|
hide(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ public:
|
|||||||
void update() override;
|
void update() override;
|
||||||
bool focus() override;
|
bool focus() override;
|
||||||
bool visible() const override;
|
bool visible() const override;
|
||||||
|
bool permanent() const override { return true; }
|
||||||
|
|
||||||
static void refresh_tabs();
|
static void refresh_tabs();
|
||||||
|
|
||||||
|
|||||||
@@ -1192,10 +1192,7 @@ void Prelaunch::update() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void return_to_prelaunch() noexcept {
|
void return_to_prelaunch() noexcept {
|
||||||
close_documents_except(DocumentScope::MenuBar);
|
close_all_documents();
|
||||||
if (auto* menuBar = find_document(DocumentScope::MenuBar)) {
|
|
||||||
menuBar->force_hide(false);
|
|
||||||
}
|
|
||||||
push_document(std::make_unique<Prelaunch>(), true);
|
push_document(std::make_unique<Prelaunch>(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1552,6 +1552,20 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
|||||||
"Recording Mode",
|
"Recording Mode",
|
||||||
"Disables the game HUD and all background music.<br/><br/>Useful for recording footage.");
|
"Disables the game HUD and all background music.<br/><br/>Useful for recording footage.");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
add_tab("Tools", [this](Rml::Element* content) {
|
||||||
|
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
|
||||||
|
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
|
||||||
|
|
||||||
|
leftPane.add_section("Link");
|
||||||
|
add_speedrun_disabled_option(leftPane, rightPane, getSettings().game.enableMoveLinkCombo,
|
||||||
|
"Move Link (L+R+Y)",
|
||||||
|
"Enables the L+R+Y button combo to toggle freely repositioning Link.");
|
||||||
|
add_speedrun_disabled_option(leftPane, rightPane, getSettings().game.enableTeleportCombo,
|
||||||
|
"Teleport (R+D-pad Up/Down)",
|
||||||
|
"R+D-pad Up stores Link's current position.<br/>"
|
||||||
|
"R+D-pad Down teleports Link back to it.");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void SettingsWindow::update() {
|
void SettingsWindow::update() {
|
||||||
|
|||||||
+40
-4
@@ -18,6 +18,7 @@
|
|||||||
#include "dusk/config.hpp"
|
#include "dusk/config.hpp"
|
||||||
#include "dusk/io.hpp"
|
#include "dusk/io.hpp"
|
||||||
#include <borealis/io.hpp>
|
#include <borealis/io.hpp>
|
||||||
|
#include "command_console.hpp"
|
||||||
#include "icon_provider.hpp"
|
#include "icon_provider.hpp"
|
||||||
#include "input.hpp"
|
#include "input.hpp"
|
||||||
#include "mod_texture_provider.hpp"
|
#include "mod_texture_provider.hpp"
|
||||||
@@ -68,6 +69,7 @@ void restyle_scope(DocumentScope scope) {
|
|||||||
|
|
||||||
std::deque<Toast> sToasts;
|
std::deque<Toast> sToasts;
|
||||||
bool sMenuNotificationRequested = false;
|
bool sMenuNotificationRequested = false;
|
||||||
|
bool sConsoleShortcutHeld = false;
|
||||||
|
|
||||||
// Sometimes gamepads can connect and disconnect quickly, especially during
|
// Sometimes gamepads can connect and disconnect quickly, especially during
|
||||||
// connection negotiation. In this case, we'll receive an _ADDED event for a
|
// connection negotiation. In this case, we'll receive an _ADDED event for a
|
||||||
@@ -106,6 +108,7 @@ void shutdown() noexcept {
|
|||||||
sDocumentStack.clear();
|
sDocumentStack.clear();
|
||||||
sPassiveDocuments.clear();
|
sPassiveDocuments.clear();
|
||||||
sConnectedGamepads.clear();
|
sConnectedGamepads.clear();
|
||||||
|
sConsoleShortcutHeld = false;
|
||||||
input::reset_input_state();
|
input::reset_input_state();
|
||||||
input::release_input_block();
|
input::release_input_block();
|
||||||
sInitialized = false;
|
sInitialized = false;
|
||||||
@@ -215,6 +218,28 @@ void handle_event(const SDL_Event& event) noexcept {
|
|||||||
}
|
}
|
||||||
sConnectedGamepads.erase(event.gdevice.which);
|
sConnectedGamepads.erase(event.gdevice.which);
|
||||||
}
|
}
|
||||||
|
if (event.type == SDL_EVENT_WINDOW_FOCUS_LOST) {
|
||||||
|
sConsoleShortcutHeld = false;
|
||||||
|
}
|
||||||
|
if (event.type == SDL_EVENT_KEY_UP && event.key.key == SDLK_SLASH && sConsoleShortcutHeld) {
|
||||||
|
sConsoleShortcutHeld = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_SLASH &&
|
||||||
|
getSettings().backend.enableAdvancedSettings)
|
||||||
|
{
|
||||||
|
auto* console = static_cast<CommandConsole*>(find_document(DocumentScope::CommandConsole));
|
||||||
|
if (sConsoleShortcutHeld) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (console != nullptr && !console->input_active() && !event.key.repeat) {
|
||||||
|
sConsoleShortcutHeld = true;
|
||||||
|
bring_document_to_front(*console);
|
||||||
|
console->show();
|
||||||
|
input::sync_input_block();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
input::handle_event(event);
|
input::handle_event(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,6 +285,17 @@ Document& push_document(std::unique_ptr<Document> doc, bool show, bool passive)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void bring_document_to_front(Document& doc) noexcept {
|
||||||
|
const auto it = std::ranges::find_if(
|
||||||
|
sDocumentStack, [&doc](const auto& entry) { return entry.get() == &doc; });
|
||||||
|
if (it == sDocumentStack.end() || std::next(it) == sDocumentStack.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto entry = std::move(*it);
|
||||||
|
sDocumentStack.erase(it);
|
||||||
|
sDocumentStack.push_back(std::move(entry));
|
||||||
|
}
|
||||||
|
|
||||||
void uncover_top_document() noexcept {
|
void uncover_top_document() noexcept {
|
||||||
if (auto* doc = top_document()) {
|
if (auto* doc = top_document()) {
|
||||||
doc->uncover();
|
doc->uncover();
|
||||||
@@ -276,10 +312,10 @@ Document* find_document(DocumentScope scope) noexcept {
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void close_documents_except(DocumentScope scope) noexcept {
|
void close_all_documents() noexcept {
|
||||||
for (auto& doc : sDocumentStack) {
|
for (auto& doc : sDocumentStack) {
|
||||||
if (!doc->closed() && doc->scope() != scope) {
|
if (!doc->closed()) {
|
||||||
doc->force_hide(true);
|
doc->force_hide(!doc->permanent());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
input::sync_input_block();
|
input::sync_input_block();
|
||||||
@@ -287,7 +323,7 @@ void close_documents_except(DocumentScope scope) noexcept {
|
|||||||
|
|
||||||
bool any_document_visible() noexcept {
|
bool any_document_visible() noexcept {
|
||||||
return std::any_of(sDocumentStack.begin(), sDocumentStack.end(),
|
return std::any_of(sDocumentStack.begin(), sDocumentStack.end(),
|
||||||
[](const auto& doc) { return doc && doc->visible(); });
|
[](const auto& doc) { return doc && doc->visible() && !doc->pending_close(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_prelaunch_open() noexcept {
|
bool is_prelaunch_open() noexcept {
|
||||||
|
|||||||
+3
-1
@@ -19,6 +19,7 @@ using clock = std::chrono::steady_clock;
|
|||||||
|
|
||||||
enum class DocumentScope : u8 {
|
enum class DocumentScope : u8 {
|
||||||
None,
|
None,
|
||||||
|
CommandConsole,
|
||||||
Prelaunch,
|
Prelaunch,
|
||||||
Window,
|
Window,
|
||||||
MenuBar,
|
MenuBar,
|
||||||
@@ -87,12 +88,13 @@ void update() noexcept;
|
|||||||
|
|
||||||
Document& push_document(
|
Document& push_document(
|
||||||
std::unique_ptr<Document> doc, bool show = true, bool passive = false) noexcept;
|
std::unique_ptr<Document> doc, bool show = true, bool passive = false) noexcept;
|
||||||
|
void bring_document_to_front(Document& doc) noexcept;
|
||||||
bool register_scoped_styles(DocumentScope scope, std::string id, const std::string& rcss) noexcept;
|
bool register_scoped_styles(DocumentScope scope, std::string id, const std::string& rcss) noexcept;
|
||||||
void unregister_scoped_styles(DocumentScope scope, std::string_view id) noexcept;
|
void unregister_scoped_styles(DocumentScope scope, std::string_view id) noexcept;
|
||||||
void apply_scoped_styles(Document& doc) noexcept;
|
void apply_scoped_styles(Document& doc) noexcept;
|
||||||
void uncover_top_document() noexcept;
|
void uncover_top_document() noexcept;
|
||||||
Document* find_document(DocumentScope scope) noexcept;
|
Document* find_document(DocumentScope scope) noexcept;
|
||||||
void close_documents_except(DocumentScope scope) noexcept;
|
void close_all_documents() noexcept;
|
||||||
bool any_document_visible() noexcept;
|
bool any_document_visible() noexcept;
|
||||||
bool is_prelaunch_open() noexcept;
|
bool is_prelaunch_open() noexcept;
|
||||||
bool game_obscured_below(const Document& doc) noexcept;
|
bool game_obscured_below(const Document& doc) noexcept;
|
||||||
|
|||||||
@@ -760,23 +760,7 @@ static void duskExecute() {
|
|||||||
isRecording = false;
|
isRecording = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mDoCPd_c::getHoldR(PAD_1) && mDoCPd_c::getTrigX(PAD_1)) {
|
|
||||||
if (const auto link = g_dComIfG_gameInfo.play.getPlayer(0)) {
|
|
||||||
dynamic_cast<daAlink_c*>(link)->handleWolfHowl();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((mDoCPd_c::getHold(PAD_1) & (PAD_TRIGGER_R | PAD_TRIGGER_L)) == PAD_TRIGGER_R && mDoCPd_c::getTrigY(PAD_1)) {
|
|
||||||
if (const auto link = g_dComIfG_gameInfo.play.getPlayer(0)) {
|
|
||||||
dynamic_cast<daAlink_c*>(link)->handleQuickTransform();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dusk::getSettings().game.moonJump && (mDoCPd_c::getHoldR(PAD_1) && mDoCPd_c::getHoldA(PAD_1))) {
|
|
||||||
if (const auto link = g_dComIfG_gameInfo.play.getPlayer(0)) {
|
|
||||||
link->speed.y = 56.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dusk::getSettings().game.fastSpinner && mDoCPd_c::getHoldR(PAD_1)) {
|
if (dusk::getSettings().game.fastSpinner && mDoCPd_c::getHoldR(PAD_1)) {
|
||||||
if (const auto link = g_dComIfG_gameInfo.play.getPlayer(0)) {
|
if (const auto link = g_dComIfG_gameInfo.play.getPlayer(0)) {
|
||||||
|
|||||||
@@ -60,6 +60,8 @@
|
|||||||
#include "dusk/frame_interpolation.h"
|
#include "dusk/frame_interpolation.h"
|
||||||
#include "dusk/game_clock.h"
|
#include "dusk/game_clock.h"
|
||||||
#include "dusk/gyro.h"
|
#include "dusk/gyro.h"
|
||||||
|
#include "dusk/commands.hpp"
|
||||||
|
#include "dusk/game_combos.h"
|
||||||
#include "dusk/imgui/ImGuiConsole.hpp"
|
#include "dusk/imgui/ImGuiConsole.hpp"
|
||||||
#include "dusk/imgui/ImGuiEngine.hpp"
|
#include "dusk/imgui/ImGuiEngine.hpp"
|
||||||
#include "dusk/iso_validate.hpp"
|
#include "dusk/iso_validate.hpp"
|
||||||
@@ -71,6 +73,7 @@
|
|||||||
#include "dusk/mouse.h"
|
#include "dusk/mouse.h"
|
||||||
#include "dusk/os.h"
|
#include "dusk/os.h"
|
||||||
#include "dusk/presentation.hpp"
|
#include "dusk/presentation.hpp"
|
||||||
|
#include "dusk/ui/command_console.hpp"
|
||||||
#include "dusk/ui/menu_bar.hpp"
|
#include "dusk/ui/menu_bar.hpp"
|
||||||
#include "dusk/ui/overlay.hpp"
|
#include "dusk/ui/overlay.hpp"
|
||||||
#include "dusk/ui/prelaunch.hpp"
|
#include "dusk/ui/prelaunch.hpp"
|
||||||
@@ -304,7 +307,9 @@ void main01(void) {
|
|||||||
mDoCPd_c::read();
|
mDoCPd_c::read();
|
||||||
dusk::mouse::read();
|
dusk::mouse::read();
|
||||||
dusk::gyro::read(dusk::game_clock::kSimPeriod);
|
dusk::gyro::read(dusk::game_clock::kSimPeriod);
|
||||||
|
dusk::processGameCombos();
|
||||||
fapGm_Execute();
|
fapGm_Execute();
|
||||||
|
dusk::processCameraCommands();
|
||||||
mDoAud_Execute();
|
mDoAud_Execute();
|
||||||
dusk::game_clock::commit_sim_tick();
|
dusk::game_clock::commit_sim_tick();
|
||||||
}
|
}
|
||||||
@@ -333,10 +338,12 @@ void main01(void) {
|
|||||||
mDoCPd_c::read();
|
mDoCPd_c::read();
|
||||||
dusk::mouse::read();
|
dusk::mouse::read();
|
||||||
dusk::gyro::read(timing.dt);
|
dusk::gyro::read(timing.dt);
|
||||||
|
dusk::processGameCombos();
|
||||||
|
|
||||||
// EXECUTE GAME LOGIC & RENDER
|
// EXECUTE GAME LOGIC & RENDER
|
||||||
// This calls mDoGph_Painter -> JFWDisplay -> GX Functions
|
// This calls mDoGph_Painter -> JFWDisplay -> GX Functions
|
||||||
fapGm_Execute();
|
fapGm_Execute();
|
||||||
|
dusk::processCameraCommands();
|
||||||
|
|
||||||
mDoAud_Execute();
|
mDoAud_Execute();
|
||||||
dusk::game_clock::commit_sim_tick();
|
dusk::game_clock::commit_sim_tick();
|
||||||
@@ -779,6 +786,7 @@ int game_main(int argc, char* argv[]) {
|
|||||||
dusk::ui::push_document(std::make_unique<dusk::ui::Overlay>(), true, true);
|
dusk::ui::push_document(std::make_unique<dusk::ui::Overlay>(), true, true);
|
||||||
dusk::ui::push_document(std::make_unique<dusk::ui::TouchControls>(), false, true);
|
dusk::ui::push_document(std::make_unique<dusk::ui::TouchControls>(), false, true);
|
||||||
dusk::ui::push_document(std::make_unique<dusk::ui::MenuBar>(), false);
|
dusk::ui::push_document(std::make_unique<dusk::ui::MenuBar>(), false);
|
||||||
|
dusk::ui::push_document(std::make_unique<dusk::ui::CommandConsole>(), false);
|
||||||
|
|
||||||
// Invalidate a bad saved isoPath so that Dusklight can't get blocked from starting up.
|
// Invalidate a bad saved isoPath so that Dusklight can't get blocked from starting up.
|
||||||
// This is only a metadata check; full hash verification is handled by the prelaunch UI.
|
// This is only a metadata check; full hash verification is handled by the prelaunch UI.
|
||||||
|
|||||||
Reference in New Issue
Block a user