diff --git a/libultraship/libultraship/Lib/Fast3D/gfx_direct3d11.cpp b/libultraship/libultraship/Lib/Fast3D/gfx_direct3d11.cpp index 4664a0faa..f1661f426 100644 --- a/libultraship/libultraship/Lib/Fast3D/gfx_direct3d11.cpp +++ b/libultraship/libultraship/Lib/Fast3D/gfx_direct3d11.cpp @@ -96,7 +96,6 @@ static struct { uint32_t msaa_num_quality_levels[D3D11_MAX_MULTISAMPLE_SAMPLE_COUNT]; ComPtr device; - ComPtr swap_chain; ComPtr context; ComPtr rasterizer_state; ComPtr depth_stencil_state; @@ -252,7 +251,24 @@ static void gfx_d3d11_init(void) { }); // Create the swap chain - d3d.swap_chain = gfx_dxgi_create_swap_chain(d3d.device.Get()); + gfx_dxgi_create_swap_chain(d3d.device.Get(), []() { + d3d.framebuffers[0].render_target_view.Reset(); + d3d.textures[d3d.framebuffers[0].texture_id].texture.Reset(); + d3d.context->ClearState(); + d3d.context->Flush(); + + d3d.last_shader_program = nullptr; + d3d.last_vertex_buffer_stride = 0; + d3d.last_blend_state.Reset(); + d3d.last_resource_views[0].Reset(); + d3d.last_resource_views[1].Reset(); + d3d.last_sampler_states[0].Reset(); + d3d.last_sampler_states[1].Reset(); + d3d.last_depth_test = -1; + d3d.last_depth_mask = -1; + d3d.last_zmode_decal = -1; + d3d.last_primitive_topology = D3D_PRIMITIVE_TOPOLOGY_UNDEFINED; + }); // Create D3D Debug device if in debug mode @@ -266,7 +282,7 @@ static void gfx_d3d11_init(void) { // Check the size of the window DXGI_SWAP_CHAIN_DESC1 swap_chain_desc; - ThrowIfFailed(d3d.swap_chain->GetDesc1(&swap_chain_desc)); + ThrowIfFailed(gfx_dxgi_get_swap_chain()->GetDesc1(&swap_chain_desc)); d3d.textures[fb.texture_id].width = swap_chain_desc.Width; d3d.textures[fb.texture_id].height = swap_chain_desc.Height; fb.msaa_level = 1; @@ -303,8 +319,6 @@ static void gfx_d3d11_init(void) { ThrowIfFailed(d3d.device->CreateBuffer(&constant_buffer_desc, nullptr, d3d.per_frame_cb.GetAddressOf()), gfx_dxgi_get_h_wnd(), "Failed to create per-frame constant buffer."); - d3d.context->PSSetConstantBuffers(0, 1, d3d.per_frame_cb.GetAddressOf()); - // Create per-draw constant buffer constant_buffer_desc.Usage = D3D11_USAGE_DYNAMIC; @@ -316,8 +330,6 @@ static void gfx_d3d11_init(void) { ThrowIfFailed(d3d.device->CreateBuffer(&constant_buffer_desc, nullptr, d3d.per_draw_cb.GetAddressOf()), gfx_dxgi_get_h_wnd(), "Failed to create per-draw constant buffer."); - d3d.context->PSSetConstantBuffers(1, 1, d3d.per_draw_cb.GetAddressOf()); - // Create compute shader that can be used to retrieve depth buffer values const char* shader_source = R"( @@ -737,6 +749,8 @@ static void gfx_d3d11_on_resize(void) { static void gfx_d3d11_start_frame(void) { // Set per-frame constant buffer + ID3D11Buffer* buffers[2] = { d3d.per_frame_cb.Get(), d3d.per_draw_cb.Get() }; + d3d.context->PSSetConstantBuffers(0, 2, buffers); d3d.per_frame_cb_data.noise_frame++; if (d3d.per_frame_cb_data.noise_frame > 150) { @@ -803,15 +817,16 @@ static void gfx_d3d11_update_framebuffer_parameters(int fb_id, uint32_t width, u if (msaa_level <= 1) { ThrowIfFailed(d3d.device->CreateShaderResourceView(tex.texture.Get(), nullptr, tex.resource_view.ReleaseAndGetAddressOf())); } - } else if (diff) { + } else if (diff || (render_target && tex.texture.Get() == nullptr)) { DXGI_SWAP_CHAIN_DESC1 desc1; - ThrowIfFailed(d3d.swap_chain->GetDesc1(&desc1)); + IDXGISwapChain1* swap_chain = gfx_dxgi_get_swap_chain(); + ThrowIfFailed(swap_chain->GetDesc1(&desc1)); if (desc1.Width != width || desc1.Height != height) { fb.render_target_view.Reset(); tex.texture.Reset(); - ThrowIfFailed(d3d.swap_chain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, desc1.Flags)); + ThrowIfFailed(swap_chain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, desc1.Flags)); } - ThrowIfFailed(d3d.swap_chain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID *)tex.texture.ReleaseAndGetAddressOf())); + ThrowIfFailed(swap_chain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID *)tex.texture.ReleaseAndGetAddressOf())); } if (render_target) { ThrowIfFailed(d3d.device->CreateRenderTargetView(tex.texture.Get(), nullptr, fb.render_target_view.ReleaseAndGetAddressOf())); diff --git a/libultraship/libultraship/Lib/Fast3D/gfx_dxgi.cpp b/libultraship/libultraship/Lib/Fast3D/gfx_dxgi.cpp index 49abe0b28..d257d7b08 100644 --- a/libultraship/libultraship/Lib/Fast3D/gfx_dxgi.cpp +++ b/libultraship/libultraship/Lib/Fast3D/gfx_dxgi.cpp @@ -34,15 +34,8 @@ #define WINCLASS_NAME L"N64GAME" #define GFX_API_NAME "DirectX" -#ifdef VERSION_EU -#define FRAME_INTERVAL_US_NUMERATOR_ 60000 -#define FRAME_INTERVAL_US_DENOMINATOR 3 -#else -#define FRAME_INTERVAL_US_NUMERATOR_ 50000 -#define FRAME_INTERVAL_US_DENOMINATOR 3 -#endif - -#define FRAME_INTERVAL_US_NUMERATOR (FRAME_INTERVAL_US_NUMERATOR_ * dxgi.frame_divisor) +#define FRAME_INTERVAL_NS_NUMERATOR 1000000000 +#define FRAME_INTERVAL_NS_DENOMINATOR (dxgi.target_fps) using namespace Microsoft::WRL; // For ComPtr @@ -66,14 +59,19 @@ static struct { ComPtr factory; ComPtr swap_chain; HANDLE waitable_object; + ComPtr swap_chain_device; // D3D11 Device or D3D12 Command Queue + std::function before_destroy_swap_chain_fn; uint64_t qpc_init, qpc_freq; - uint64_t frame_timestamp; // in units of 1/FRAME_INTERVAL_US_DENOMINATOR microseconds + uint64_t frame_timestamp; // in units of 1/FRAME_INTERVAL_NS_DENOMINATOR nanoseconds std::map frame_stats; std::set> pending_frame_stats; bool dropped_frame; bool zero_latency; + float detected_hz; UINT length_in_vsync_frames; - uint32_t frame_divisor; + uint32_t target_fps; + uint32_t maximum_frame_latency; + uint32_t applied_maximum_frame_latency; HANDLE timer; bool use_timer; LARGE_INTEGER previous_present_time; @@ -143,6 +141,22 @@ static void run_as_dpi_aware(Fun f) { } } +static void apply_maximum_frame_latency(bool first) { + ComPtr swap_chain2; + if (dxgi.swap_chain->QueryInterface(__uuidof(IDXGISwapChain2), &swap_chain2) == S_OK) { + ThrowIfFailed(swap_chain2->SetMaximumFrameLatency(dxgi.maximum_frame_latency)); + if (first) { + dxgi.waitable_object = swap_chain2->GetFrameLatencyWaitableObject(); + WaitForSingleObject(dxgi.waitable_object, INFINITE); + } + } else { + ComPtr device1; + ThrowIfFailed(dxgi.swap_chain->GetDevice(__uuidof(IDXGIDevice1), &device1)); + ThrowIfFailed(device1->SetMaximumFrameLatency(dxgi.maximum_frame_latency)); + } + dxgi.applied_maximum_frame_latency = dxgi.maximum_frame_latency; +} + static void toggle_borderless_window_full_screen(bool enable, bool call_callback) { // Windows 7 + flip mode + waitable object can't go to exclusive fullscreen, // so do borderless instead. If DWM is enabled, this means we get one monitor @@ -271,7 +285,8 @@ void gfx_dxgi_init(const char *game_name, bool start_in_fullscreen) { dxgi.qpc_init = qpc_init.QuadPart; dxgi.qpc_freq = qpc_freq.QuadPart; - dxgi.frame_divisor = 1; + dxgi.target_fps = 60; + dxgi.maximum_frame_latency = 1; dxgi.timer = CreateWaitableTimer(nullptr, false, nullptr); // Prepare window title @@ -367,8 +382,8 @@ static void gfx_dxgi_handle_events(void) { }*/ } -static uint64_t qpc_to_us(uint64_t qpc) { - return qpc / dxgi.qpc_freq * 1000000 + qpc % dxgi.qpc_freq * 1000000 / dxgi.qpc_freq; +static uint64_t qpc_to_ns(uint64_t qpc) { + return qpc / dxgi.qpc_freq * 1000000000 + qpc % dxgi.qpc_freq * 1000000000 / dxgi.qpc_freq; } static uint64_t qpc_to_100ns(uint64_t qpc) { @@ -406,7 +421,7 @@ static bool gfx_dxgi_start_frame(void) { dxgi.use_timer = false; - dxgi.frame_timestamp += FRAME_INTERVAL_US_NUMERATOR; + dxgi.frame_timestamp += FRAME_INTERVAL_NS_NUMERATOR; if (dxgi.frame_stats.size() >= 2) { DXGI_FRAME_STATISTICS *first = &dxgi.frame_stats.begin()->second; @@ -421,14 +436,16 @@ static bool gfx_dxgi_start_frame(void) { } double estimated_vsync_interval = (double)sync_qpc_diff / (double)sync_vsync_diff; - uint64_t estimated_vsync_interval_us = qpc_to_us(estimated_vsync_interval); - //printf("Estimated vsync_interval: %d\n", (int)estimated_vsync_interval_us); - if (estimated_vsync_interval_us < 2 || estimated_vsync_interval_us > 1000000) { + uint64_t estimated_vsync_interval_ns = qpc_to_ns(estimated_vsync_interval); + //printf("Estimated vsync_interval: %d\n", (int)estimated_vsync_interval_ns); + if (estimated_vsync_interval_ns < 2000 || estimated_vsync_interval_ns > 1000000000) { // Unreasonable, maybe a monitor change - estimated_vsync_interval_us = 16666; - estimated_vsync_interval = estimated_vsync_interval_us * dxgi.qpc_freq / 1000000; + estimated_vsync_interval_ns = 16666666; + estimated_vsync_interval = estimated_vsync_interval_ns * dxgi.qpc_freq / 1000000000; } + dxgi.detected_hz = (float)((double)1000000000 / (double)estimated_vsync_interval_ns); + UINT queued_vsyncs = 0; bool is_first = true; for (const std::pair& p : dxgi.pending_frame_stats) { @@ -440,21 +457,21 @@ static bool gfx_dxgi_start_frame(void) { } uint64_t last_frame_present_end_qpc = (last->SyncQPCTime.QuadPart - dxgi.qpc_init) + estimated_vsync_interval * queued_vsyncs; - uint64_t last_end_us = qpc_to_us(last_frame_present_end_qpc); + uint64_t last_end_ns = qpc_to_ns(last_frame_present_end_qpc); - double vsyncs_to_wait = (double)(int64_t)(dxgi.frame_timestamp / FRAME_INTERVAL_US_DENOMINATOR - last_end_us) / estimated_vsync_interval_us; - //printf("ts: %llu, last_end_us: %llu, Init v: %f\n", dxgi.frame_timestamp / 3, last_end_us, vsyncs_to_wait); + double vsyncs_to_wait = (double)(int64_t)(dxgi.frame_timestamp / FRAME_INTERVAL_NS_DENOMINATOR - last_end_ns) / estimated_vsync_interval_ns; + //printf("ts: %llu, last_end_ns: %llu, Init v: %f\n", dxgi.frame_timestamp / 3, last_end_ns, vsyncs_to_wait); if (vsyncs_to_wait <= 0) { // Too late - if ((int64_t)(dxgi.frame_timestamp / FRAME_INTERVAL_US_DENOMINATOR - last_end_us) < -66666) { + if ((int64_t)(dxgi.frame_timestamp / FRAME_INTERVAL_NS_DENOMINATOR - last_end_ns) < -66666666) { // The application must have been paused or similar - vsyncs_to_wait = round(((double)FRAME_INTERVAL_US_NUMERATOR / FRAME_INTERVAL_US_DENOMINATOR) / estimated_vsync_interval_us); + vsyncs_to_wait = round(((double)FRAME_INTERVAL_NS_NUMERATOR / FRAME_INTERVAL_NS_DENOMINATOR) / estimated_vsync_interval_ns); if (vsyncs_to_wait < 1) { vsyncs_to_wait = 1; } - dxgi.frame_timestamp = FRAME_INTERVAL_US_DENOMINATOR * (last_end_us + vsyncs_to_wait * estimated_vsync_interval_us); + dxgi.frame_timestamp = FRAME_INTERVAL_NS_DENOMINATOR * (last_end_ns + vsyncs_to_wait * estimated_vsync_interval_ns); } else { // Drop frame //printf("Dropping frame\n"); @@ -464,9 +481,9 @@ static bool gfx_dxgi_start_frame(void) { } double orig_wait = vsyncs_to_wait; if (floor(vsyncs_to_wait) != vsyncs_to_wait) { - uint64_t left = last_end_us + floor(vsyncs_to_wait) * estimated_vsync_interval_us; - uint64_t right = last_end_us + ceil(vsyncs_to_wait) * estimated_vsync_interval_us; - uint64_t adjusted_desired_time = dxgi.frame_timestamp / FRAME_INTERVAL_US_DENOMINATOR + (last_end_us + (FRAME_INTERVAL_US_NUMERATOR / FRAME_INTERVAL_US_DENOMINATOR) > dxgi.frame_timestamp / FRAME_INTERVAL_US_DENOMINATOR ? 2000 : -2000); + uint64_t left = last_end_ns + floor(vsyncs_to_wait) * estimated_vsync_interval_ns; + uint64_t right = last_end_ns + ceil(vsyncs_to_wait) * estimated_vsync_interval_ns; + uint64_t adjusted_desired_time = dxgi.frame_timestamp / FRAME_INTERVAL_NS_DENOMINATOR + (last_end_ns + (FRAME_INTERVAL_NS_NUMERATOR / FRAME_INTERVAL_NS_DENOMINATOR) > dxgi.frame_timestamp / FRAME_INTERVAL_NS_DENOMINATOR ? 2000000 : -2000000); int64_t diff_left = adjusted_desired_time - left; int64_t diff_right = right - adjusted_desired_time; if (diff_left < 0) { @@ -506,7 +523,7 @@ static void gfx_dxgi_swap_buffers_begin(void) { LARGE_INTEGER t; if (dxgi.use_timer) { QueryPerformanceCounter(&t); - int64_t next = qpc_to_100ns(dxgi.previous_present_time.QuadPart) + 10 * FRAME_INTERVAL_US_NUMERATOR / FRAME_INTERVAL_US_DENOMINATOR; + int64_t next = qpc_to_100ns(dxgi.previous_present_time.QuadPart) + FRAME_INTERVAL_NS_NUMERATOR / (FRAME_INTERVAL_NS_DENOMINATOR * 100); int64_t left = next - qpc_to_100ns(t.QuadPart); if (left > 0) { LARGE_INTEGER li; @@ -531,6 +548,32 @@ static void gfx_dxgi_swap_buffers_end(void) { QueryPerformanceCounter(&t0); QueryPerformanceCounter(&t1); + if (dxgi.applied_maximum_frame_latency > dxgi.maximum_frame_latency) { + // There seems to be a bug that if latency is decreased, there is no effect of that operation, so recreate swap chain + if (dxgi.waitable_object != nullptr) { + if (!dxgi.dropped_frame) { + // Wait the last time on this swap chain + WaitForSingleObject(dxgi.waitable_object, INFINITE); + } + CloseHandle(dxgi.waitable_object); + dxgi.waitable_object = nullptr; + } + + dxgi.before_destroy_swap_chain_fn(); + + dxgi.swap_chain.Reset(); + + gfx_dxgi_create_swap_chain(dxgi.swap_chain_device.Get(), move(dxgi.before_destroy_swap_chain_fn)); + + dxgi.frame_timestamp = 0; + dxgi.frame_stats.clear(); + dxgi.pending_frame_stats.clear(); + + return; // Make sure we don't wait a second time on the waitable object, since that would hang the program + } else if (dxgi.applied_maximum_frame_latency != dxgi.maximum_frame_latency) { + apply_maximum_frame_latency(false); + } + if (!dxgi.dropped_frame) { if (dxgi.waitable_object != nullptr) { WaitForSingleObject(dxgi.waitable_object, INFINITE); @@ -554,8 +597,20 @@ static double gfx_dxgi_get_time(void) { return (double)(t.QuadPart - dxgi.qpc_init) / dxgi.qpc_freq; } -static void gfx_dxgi_set_frame_divisor(int divisor) { - dxgi.frame_divisor = divisor; +static void gfx_dxgi_set_target_fps(int fps) { + uint32_t old_fps = dxgi.target_fps; + uint64_t t0 = dxgi.frame_timestamp / old_fps; + uint32_t t1 = dxgi.frame_timestamp % old_fps; + dxgi.target_fps = fps; + dxgi.frame_timestamp = t0 * dxgi.target_fps + t1 * dxgi.target_fps / old_fps; +} + +static void gfx_dxgi_set_maximum_frame_latency(int latency) { + dxgi.maximum_frame_latency = latency; +} + +static float gfx_dxgi_get_detected_hz() { + return dxgi.detected_hz; } void gfx_dxgi_create_factory_and_device(bool debug, int d3d_version, bool (*create_device_fn)(IDXGIAdapter1 *adapter, bool test_only)) { @@ -592,12 +647,12 @@ void gfx_dxgi_create_factory_and_device(bool debug, int d3d_version, bool (*crea SetWindowTextW(dxgi.h_wnd, w_title); } -ComPtr gfx_dxgi_create_swap_chain(IUnknown *device) { +void gfx_dxgi_create_swap_chain(IUnknown *device, std::function&& before_destroy_fn) { bool win8 = IsWindows8OrGreater(); // DXGI_SCALING_NONE is only supported on Win8 and beyond bool dxgi_13 = dxgi.CreateDXGIFactory2 != nullptr; // DXGI 1.3 introduced waitable object DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {}; - swap_chain_desc.BufferCount = 2; + swap_chain_desc.BufferCount = 3; swap_chain_desc.Width = 0; swap_chain_desc.Height = 0; swap_chain_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; @@ -617,28 +672,24 @@ ComPtr gfx_dxgi_create_swap_chain(IUnknown *device) { }); ThrowIfFailed(dxgi.factory->MakeWindowAssociation(dxgi.h_wnd, DXGI_MWA_NO_ALT_ENTER)); - ComPtr swap_chain2; - if (dxgi.swap_chain->QueryInterface(__uuidof(IDXGISwapChain2), &swap_chain2) == S_OK) { - ThrowIfFailed(swap_chain2->SetMaximumFrameLatency(1)); - dxgi.waitable_object = swap_chain2->GetFrameLatencyWaitableObject(); - WaitForSingleObject(dxgi.waitable_object, INFINITE); - } else { - ComPtr device1; - ThrowIfFailed(device->QueryInterface(IID_PPV_ARGS(&device1))); - ThrowIfFailed(device1->SetMaximumFrameLatency(1)); - } + apply_maximum_frame_latency(true); ThrowIfFailed(dxgi.swap_chain->GetDesc1(&swap_chain_desc)); dxgi.current_width = swap_chain_desc.Width; dxgi.current_height = swap_chain_desc.Height; - return dxgi.swap_chain; + dxgi.swap_chain_device = device; + dxgi.before_destroy_swap_chain_fn = std::move(before_destroy_fn); } HWND gfx_dxgi_get_h_wnd(void) { return dxgi.h_wnd; } +IDXGISwapChain1* gfx_dxgi_get_swap_chain() { + return dxgi.swap_chain.Get(); +} + void ThrowIfFailed(HRESULT res) { if (FAILED(res)) { fprintf(stderr, "Error: 0x%08X\n", res); @@ -668,7 +719,9 @@ extern "C" struct GfxWindowManagerAPI gfx_dxgi_api = { gfx_dxgi_swap_buffers_begin, gfx_dxgi_swap_buffers_end, gfx_dxgi_get_time, - gfx_dxgi_set_frame_divisor, + gfx_dxgi_set_target_fps, + gfx_dxgi_set_maximum_frame_latency, + gfx_dxgi_get_detected_hz, }; #endif diff --git a/libultraship/libultraship/Lib/Fast3D/gfx_dxgi.h b/libultraship/libultraship/Lib/Fast3D/gfx_dxgi.h index d590daa66..134ebed64 100644 --- a/libultraship/libultraship/Lib/Fast3D/gfx_dxgi.h +++ b/libultraship/libultraship/Lib/Fast3D/gfx_dxgi.h @@ -4,9 +4,15 @@ #include "gfx_rendering_api.h" #ifdef DECLARE_GFX_DXGI_FUNCTIONS + +#include + +#include + void gfx_dxgi_create_factory_and_device(bool debug, int d3d_version, bool (*create_device_fn)(IDXGIAdapter1 *adapter, bool test_only)); -Microsoft::WRL::ComPtr gfx_dxgi_create_swap_chain(IUnknown *device); +void gfx_dxgi_create_swap_chain(IUnknown *device, std::function&& before_destroy_fn); HWND gfx_dxgi_get_h_wnd(void); +IDXGISwapChain1* gfx_dxgi_get_swap_chain(); void ThrowIfFailed(HRESULT res); void ThrowIfFailed(HRESULT res, HWND h_wnd, const char *message); #endif diff --git a/libultraship/libultraship/Lib/Fast3D/gfx_pc.cpp b/libultraship/libultraship/Lib/Fast3D/gfx_pc.cpp index 98bab7f78..452b511d8 100644 --- a/libultraship/libultraship/Lib/Fast3D/gfx_pc.cpp +++ b/libultraship/libultraship/Lib/Fast3D/gfx_pc.cpp @@ -2789,6 +2789,9 @@ void gfx_run(Gfx *commands, const std::unordered_map& mtx_replaceme gfx_rapi->start_frame(); gfx_rapi->start_draw_to_framebuffer(game_renders_to_framebuffer ? game_framebuffer : 0, (float)gfx_current_dimensions.height / SCREEN_HEIGHT); gfx_rapi->clear_framebuffer(); + rdp.viewport_or_scissor_changed = true; + rendering_state.viewport = {}; + rendering_state.scissor = {}; gfx_run_dl(commands); gfx_flush(); SohUtils::saveEnvironmentVar("framebuffer", string()); @@ -2825,8 +2828,16 @@ void gfx_end_frame(void) { } } -void gfx_set_framedivisor(int divisor) { - gfx_wapi->set_frame_divisor(divisor); +void gfx_set_target_fps(int fps) { + gfx_wapi->set_target_fps(fps); +} + +void gfx_set_maximum_frame_latency(int latency) { + gfx_wapi->set_maximum_frame_latency(latency); +} + +float gfx_get_detected_hz(void) { + return gfx_wapi->get_detected_hz(); } int gfx_create_framebuffer(uint32_t width, uint32_t height) { diff --git a/libultraship/libultraship/Lib/Fast3D/gfx_pc.h b/libultraship/libultraship/Lib/Fast3D/gfx_pc.h index 2ecb68898..65a6b57ba 100644 --- a/libultraship/libultraship/Lib/Fast3D/gfx_pc.h +++ b/libultraship/libultraship/Lib/Fast3D/gfx_pc.h @@ -67,7 +67,9 @@ struct GfxRenderingAPI* gfx_get_current_rendering_api(void); void gfx_start_frame(void); void gfx_run(Gfx* commands, const std::unordered_map& mtx_replacements); void gfx_end_frame(void); -void gfx_set_framedivisor(int); +void gfx_set_target_fps(int); +void gfx_set_maximum_frame_latency(int latency); +float gfx_get_detected_hz(void); void gfx_texture_cache_clear(); extern "C" int gfx_create_framebuffer(uint32_t width, uint32_t height); void gfx_get_pixel_depth_prepare(float x, float y); diff --git a/libultraship/libultraship/Lib/Fast3D/gfx_sdl2.cpp b/libultraship/libultraship/Lib/Fast3D/gfx_sdl2.cpp index e80097c81..061e3a8ee 100644 --- a/libultraship/libultraship/Lib/Fast3D/gfx_sdl2.cpp +++ b/libultraship/libultraship/Lib/Fast3D/gfx_sdl2.cpp @@ -123,11 +123,10 @@ static uint64_t previous_time; static HANDLE timer; #endif -static int frameDivisor = 1; +static int target_fps = 60; -#define FRAME_INTERVAL_US_NUMERATOR_ 50000 -#define FRAME_INTERVAL_US_DENOMINATOR 3 -#define FRAME_INTERVAL_US_NUMERATOR (FRAME_INTERVAL_US_NUMERATOR_ * frameDivisor) +#define FRAME_INTERVAL_US_NUMERATOR 1000000 +#define FRAME_INTERVAL_US_DENOMINATOR (target_fps) static void gfx_sdl_init(const char *game_name, bool start_in_fullscreen) { SDL_Init(SDL_INIT_VIDEO); @@ -250,6 +249,7 @@ static void gfx_sdl_handle_events(void) { } break; case SDL_QUIT: + SDL_Quit(); // bandaid fix for linux window closing issue exit(0); } } @@ -266,15 +266,16 @@ static uint64_t qpc_to_100ns(uint64_t qpc) { static inline void sync_framerate_with_timer(void) { uint64_t t; - t = SDL_GetPerformanceCounter(); + t = qpc_to_100ns(SDL_GetPerformanceCounter()); - const int64_t next = qpc_to_100ns(previous_time) + 10 * FRAME_INTERVAL_US_NUMERATOR / FRAME_INTERVAL_US_DENOMINATOR; - const int64_t left = next - qpc_to_100ns(t); + const int64_t next = previous_time + 10 * FRAME_INTERVAL_US_NUMERATOR / FRAME_INTERVAL_US_DENOMINATOR; + const int64_t left = next - t; if (left > 0) { #ifdef __linux__ const timespec spec = { 0, left * 100 }; nanosleep(&spec, nullptr); #else + // The accuracy of this timer seems to usually be within +- 1.0 ms LARGE_INTEGER li; li.QuadPart = -left; SetWaitableTimer(timer, &li, 0, nullptr, nullptr, false); @@ -282,7 +283,13 @@ static inline void sync_framerate_with_timer(void) { #endif } - t = SDL_GetPerformanceCounter(); + t = qpc_to_100ns(SDL_GetPerformanceCounter()); + if (left > 0 && t - next < 10000) { + // In case it takes some time for the application to wake up after sleep, + // or inaccurate timer, + // don't let that slow down the framerate. + t = next; + } previous_time = t; } @@ -299,9 +306,16 @@ static double gfx_sdl_get_time(void) { return 0.0; } -static void gfx_sdl_set_framedivisor(int divisor) -{ - frameDivisor = divisor; +static void gfx_sdl_set_target_fps(int fps) { + target_fps = fps; +} + +static void gfx_sdl_set_maximum_frame_latency(int latency) { + // Not supported by SDL :( +} + +static float gfx_sdl_get_detected_hz(void) { + return 0; } struct GfxWindowManagerAPI gfx_sdl = { @@ -317,7 +331,9 @@ struct GfxWindowManagerAPI gfx_sdl = { gfx_sdl_swap_buffers_begin, gfx_sdl_swap_buffers_end, gfx_sdl_get_time, - gfx_sdl_set_framedivisor + gfx_sdl_set_target_fps, + gfx_sdl_set_maximum_frame_latency, + gfx_sdl_get_detected_hz }; #endif diff --git a/libultraship/libultraship/Lib/Fast3D/gfx_window_manager_api.h b/libultraship/libultraship/Lib/Fast3D/gfx_window_manager_api.h index 5d7442390..bd45ccdf4 100644 --- a/libultraship/libultraship/Lib/Fast3D/gfx_window_manager_api.h +++ b/libultraship/libultraship/Lib/Fast3D/gfx_window_manager_api.h @@ -17,7 +17,9 @@ struct GfxWindowManagerAPI { void (*swap_buffers_begin)(void); void (*swap_buffers_end)(void); double (*get_time)(void); // For debug - void (*set_frame_divisor)(int); + void (*set_target_fps)(int fps); + void (*set_maximum_frame_latency)(int latency); + float (*get_detected_hz)(void); }; #endif diff --git a/libultraship/libultraship/SohImGuiImpl.cpp b/libultraship/libultraship/SohImGuiImpl.cpp index f49c58faf..1f5180802 100644 --- a/libultraship/libultraship/SohImGuiImpl.cpp +++ b/libultraship/libultraship/SohImGuiImpl.cpp @@ -46,9 +46,9 @@ bool oldCursorState = true; #define EXPERIMENTAL() \ ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 50, 50, 255)); \ - ImGui::Text("Experimental"); \ - ImGui::PopStyleColor(); \ - ImGui::Separator(); + ImGui::Text("Experimental"); \ + ImGui::PopStyleColor(); \ + ImGui::Separator(); #define TOGGLE_BTN ImGuiKey_F1 #define HOOK(b) if(b) needs_save = true; OSContPad* pads; @@ -66,29 +66,38 @@ namespace SohImGui { std::vector CustomTexts; int SelectedLanguage = CVar_GetS32("gLanguages", 0); //Default Language to 0=English 1=German 2=French int SelectedHUD = CVar_GetS32("gHudColors", 1); //Default colors to Gamecube. - float hearts_colors[3] = {0,0,0}; - float hearts_dd_colors[3] = {0,0,0}; - float a_btn_colors[3] = {0,0,0}; - float b_btn_colors[3] = {0,0,0}; - float c_btn_colors[3] = {0,0,0}; - float start_btn_colors[3] = {0,0,0}; - float magic_border_colors[3] = {0,0,0}; - float magic_remaining_colors[3] = {0,0,0}; - float magic_use_colors[3] = {0,0,0}; - float minimap_colors[3] = {0,0,0}; - float rupee_colors[3] = {0,0,0}; - float smolekey_colors[3] = {0,0,0}; - float kokiri_col[3] = { 0.118f, 0.41f, 0.106f }; - float goron_col[3] = { 0.392f, 0.078f, 0.0f }; - float zora_col[3] = { 0.0f, 0.235f, 0.392f }; - float navi_idle_i_col[3] = { 0.0f, 0.0f, 0.0f }; - float navi_idle_o_col[3] = { 0.0f, 0.0f, 0.0f }; - float navi_npc_i_col[3] = { 0.0f, 0.0f, 0.0f }; - float navi_npc_o_col[3] = { 0.0f, 0.0f, 0.0f }; - float navi_enemy_i_col[3] = { 0.0f, 0.0f, 0.0f }; - float navi_enemy_o_col[3] = { 0.0f, 0.0f, 0.0f }; - float navi_prop_i_col[3] = { 0.0f, 0.0f, 0.0f }; - float navi_prop_o_col[3] = { 0.0f, 0.0f, 0.0f }; + ImVec4 hearts_colors; + ImVec4 hearts_dd_colors; + ImVec4 a_btn_colors; + ImVec4 b_btn_colors; + ImVec4 c_btn_colors; + ImVec4 start_btn_colors; + ImVec4 magic_border_colors; + ImVec4 magic_remaining_colors; + ImVec4 magic_use_colors; + ImVec4 minimap_colors; + ImVec4 rupee_colors; + ImVec4 smolekey_colors; + ImVec4 kokiri_col; + ImVec4 goron_col; + ImVec4 zora_col; + ImVec4 navi_idle_i_col; + ImVec4 navi_idle_o_col; + ImVec4 navi_npc_i_col; + ImVec4 navi_npc_o_col; + ImVec4 navi_enemy_i_col; + ImVec4 navi_enemy_o_col; + ImVec4 navi_prop_i_col; + ImVec4 navi_prop_o_col; + + const char* RainbowColorCvarList[] = { + //This is the list of possible CVars that has rainbow effect. + "gTunic_Kokiri_","gTunic_Goron_","gTunic_Zora_", + "gCCHeartsPrim","gDDCCHeartsPrim", + "gCCABtnPrim","gCCBBtnPrim","gCCCBtnPrim","gCCStartBtnPrim", + "gCCMagicBorderPrim","gCCMagicPrim","gCCMagicUsePrim", + "gCCMinimapPrim","gCCRupeePrim","gCCKeysPrim" + }; const char* filters[3] = { "Three-Point", @@ -99,6 +108,15 @@ namespace SohImGui { std::map> windowCategories; std::map customWindows; + int ClampFloatToInt(float value, int min, int max){ + return fmin(fmax(value,min),max); + } + + void Tooltip(const char* text) { + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", text); + } + void ImGuiWMInit() { switch (impl.backend) { case Backend::SDL: @@ -240,82 +258,64 @@ namespace SohImGui { stbi_image_free(img_data); } - void LoadInterfaceEditor(){//This function is necessary as without it IMGui wont load the updated float array. - hearts_colors[0] = (float)CVar_GetS32("gCCHeartsPrimR", 255)/255; - hearts_colors[1] = (float)CVar_GetS32("gCCHeartsPrimG", 70)/255; - hearts_colors[2] = (float)CVar_GetS32("gCCHeartsPrimB", 50)/255; - hearts_dd_colors[0] = (float)CVar_GetS32("gDDCCHeartsPrimR", 255)/255; - hearts_dd_colors[1] = (float)CVar_GetS32("gDDCCHeartsPrimG", 255)/255; - hearts_dd_colors[2] = (float)CVar_GetS32("gDDCCHeartsPrimB", 255)/255; - a_btn_colors[0] = (float)CVar_GetS32("gCCABtnPrimR", 90)/255; - a_btn_colors[1] = (float)CVar_GetS32("gCCABtnPrimG", 90)/255; - a_btn_colors[2] = (float)CVar_GetS32("gCCABtnPrimB", 255)/255; - b_btn_colors[0] = (float)CVar_GetS32("gCCBBtnPrimR", 0)/255; - b_btn_colors[1] = (float)CVar_GetS32("gCCBBtnPrimG", 150)/255; - b_btn_colors[2] = (float)CVar_GetS32("gCCBBtnPrimB", 0)/255; - c_btn_colors[0] = (float)CVar_GetS32("gCCCBtnPrimR", 255)/255; - c_btn_colors[1] = (float)CVar_GetS32("gCCCBtnPrimG", 160)/255; - c_btn_colors[2] = (float)CVar_GetS32("gCCCBtnPrimB", 0)/255; - start_btn_colors[0] = (float)CVar_GetS32("gCCStartBtnPrimR", 120)/255; - start_btn_colors[1] = (float)CVar_GetS32("gCCStartBtnPrimG", 120)/255; - start_btn_colors[2] = (float)CVar_GetS32("gCCStartBtnPrimB", 120)/255; - magic_border_colors[0] = (float)CVar_GetS32("gCCMagicBorderPrimR", 255)/255; - magic_border_colors[1] = (float)CVar_GetS32("gCCMagicBorderPrimG", 255)/255; - magic_border_colors[2] = (float)CVar_GetS32("gCCMagicBorderPrimB", 255)/255; - magic_use_colors[0] = (float)CVar_GetS32("gCCMagicPrimR", 250)/255; - magic_use_colors[1] = (float)CVar_GetS32("gCCMagicPrimG", 250)/255; - magic_use_colors[2] = (float)CVar_GetS32("gCCMagicPrimB", 0)/255; - magic_remaining_colors[0] = (float)CVar_GetS32("gCCMagicUsePrimR", 0)/255; - magic_remaining_colors[1] = (float)CVar_GetS32("gCCMagicUsePrimG", 200)/255; - magic_remaining_colors[2] = (float)CVar_GetS32("gCCMagicUsePrimB", 0)/255; - minimap_colors[0] = (float)CVar_GetS32("gCCMinimapPrimR", 0)/255; - minimap_colors[1] = (float)CVar_GetS32("gCCMinimapPrimG", 255)/255; - minimap_colors[2] = (float)CVar_GetS32("gCCMinimapPrimB", 255)/255; - rupee_colors[0] = (float)CVar_GetS32("gCCRupeePrimR", 200)/255; - rupee_colors[1] = (float)CVar_GetS32("gCCRupeePrimG", 255)/255; - rupee_colors[2] = (float)CVar_GetS32("gCCRupeePrimB", 100)/255; - smolekey_colors[0] = (float)CVar_GetS32("gCCKeysPrimR", 200)/255; - smolekey_colors[1] = (float)CVar_GetS32("gCCKeysPrimG", 230)/255; - smolekey_colors[2] = (float)CVar_GetS32("gCCKeysPrimB", 255)/255; - kokiri_col[0] = (float)CVar_GetS32("gTunic_Kokiri_R", 30)/255; - kokiri_col[1] = (float)CVar_GetS32("gTunic_Kokiri_G", 105)/255; - kokiri_col[2] = (float)CVar_GetS32("gTunic_Kokiri_B", 27)/255; - goron_col[0] = (float)CVar_GetS32("gTunic_Goron_R", 100)/255; - goron_col[1] = (float)CVar_GetS32("gTunic_Goron_G", 20)/255; - goron_col[2] = (float)CVar_GetS32("gTunic_Goron_B", 0)/255; - zora_col[0] = (float)CVar_GetS32("gTunic_Zora_R", 0)/255; - zora_col[1] = (float)CVar_GetS32("gTunic_Zora_G", 60)/255; - zora_col[2] = (float)CVar_GetS32("gTunic_Zora_B", 100)/255; - navi_idle_i_col[0] = (float)CVar_GetS32("gNavi_Idle_Inner_R", 255)/255; - navi_idle_i_col[1] = (float)CVar_GetS32("gNavi_Idle_Inner_G", 255)/255; - navi_idle_i_col[2] = (float)CVar_GetS32("gNavi_Idle_Inner_B", 255)/255; - navi_idle_o_col[0] = (float)CVar_GetS32("gNavi_Idle_Outer_R", 115)/255; - navi_idle_o_col[1] = (float)CVar_GetS32("gNavi_Idle_Outer_G", 230)/255; - navi_idle_o_col[2] = (float)CVar_GetS32("gNavi_Idle_Outer_B", 255)/255; - navi_npc_i_col[0] = (float)CVar_GetS32("gNavi_NPC_Inner_R", 100)/255; - navi_npc_i_col[1] = (float)CVar_GetS32("gNavi_NPC_Inner_G", 100)/255; - navi_npc_i_col[2] = (float)CVar_GetS32("gNavi_NPC_Inner_B", 255)/255; - navi_npc_o_col[0] = (float)CVar_GetS32("gNavi_NPC_Outer_R", 90)/255; - navi_npc_o_col[1] = (float)CVar_GetS32("gNavi_NPC_Outer_G", 90)/255; - navi_npc_o_col[2] = (float)CVar_GetS32("gNavi_NPC_Outer_B", 255)/255; - navi_enemy_i_col[0] = (float)CVar_GetS32("gNavi_Enemy_Inner_R", 255)/255; - navi_enemy_i_col[1] = (float)CVar_GetS32("gNavi_Enemy_Inner_G", 255)/255; - navi_enemy_i_col[2] = (float)CVar_GetS32("gNavi_Enemy_Inner_B", 0)/255; - navi_enemy_o_col[0] = (float)CVar_GetS32("gNavi_Enemy_Outer_R", 220)/255; - navi_enemy_o_col[1] = (float)CVar_GetS32("gNavi_Enemy_Outer_G", 220)/255; - navi_enemy_o_col[2] = (float)CVar_GetS32("gNavi_Enemy_Outer_B", 0)/255; - navi_prop_i_col[0] = (float)CVar_GetS32("gNavi_Prop_Inner_R", 0)/255; - navi_prop_i_col[1] = (float)CVar_GetS32("gNavi_Prop_Inner_G", 255)/255; - navi_prop_i_col[2] = (float)CVar_GetS32("gNavi_Prop_Inner_B", 0)/255; - navi_prop_o_col[0] = (float)CVar_GetS32("gNavi_Prop_Outer_R", 0)/255; - navi_prop_o_col[1] = (float)CVar_GetS32("gNavi_Prop_Outer_G", 220)/255; - navi_prop_o_col[2] = (float)CVar_GetS32("gNavi_Prop_Outer_B", 0)/255; - if (CVar_GetS32("gHudColors", 1) ==0) { - SelectedHUD = 0; - } else if (CVar_GetS32("gHudColors", 1) == 1) { - SelectedHUD = 1; - } else if (CVar_GetS32("gHudColors", 1) == 2) { - SelectedHUD = 2; + void LoadRainbowColor() { + for (uint16_t s=0; s <= sizeof(RainbowColorCvarList); s++) { + std::string cvarName = RainbowColorCvarList[s]; + std::string Cvar_Red = cvarName; + Cvar_Red += "R"; + std::string Cvar_Green = cvarName; + Cvar_Green += "G"; + std::string Cvar_Blue = cvarName; + Cvar_Blue += "B"; + std::string Cvar_RBM = cvarName; + Cvar_RBM += "RBM"; + std::string RBM_HUE = cvarName; + RBM_HUE+="Hue"; + f32 Canon = 10.f*s; + ImVec4 NewColor; + const f32 deltaTime = 1.0f / ImGui::GetIO().Framerate; + f32 hue = CVar_GetFloat(RBM_HUE.c_str(), 0.0f); + f32 newHue = hue + CVar_GetS32("gColorRainbowSpeed", 1) * 36.0f * deltaTime; + if (newHue >= 360) + newHue = 0; + CVar_SetFloat(RBM_HUE.c_str(), newHue); + f32 current_hue = CVar_GetFloat(RBM_HUE.c_str(), 0); + u8 i = current_hue / 60 + 1; + u8 a = (-current_hue / 60.0f + i) * 255; + u8 b = (current_hue / 60.0f + (1 - i)) * 255; + + switch (i) { + case 1: NewColor.x = 255; NewColor.y = b; NewColor.z = 0; break; + case 2: NewColor.x = a; NewColor.y = 255; NewColor.z = 0; break; + case 3: NewColor.x = 0; NewColor.y = 255; NewColor.z = b; break; + case 4: NewColor.x = 0; NewColor.y = a; NewColor.z = 255; break; + case 5: NewColor.x = b; NewColor.y = 0; NewColor.z = 255; break; + case 6: NewColor.x = 255; NewColor.y = 0; NewColor.z = a; break; + } + + if(CVar_GetS32(Cvar_RBM.c_str(), 0) != 0) { + CVar_SetS32(Cvar_Red.c_str(), ClampFloatToInt(NewColor.x,0,255)); + CVar_SetS32(Cvar_Green.c_str(), ClampFloatToInt(NewColor.y,0,255)); + CVar_SetS32(Cvar_Blue.c_str(), ClampFloatToInt(NewColor.z,0,255)); + } + } + } + + void LoadPickersColors(ImVec4& ColorArray, const char* cvarname, const ImVec4& default_colors, bool has_alpha) { + std::string Cvar_Red = cvarname; + Cvar_Red += "R"; + std::string Cvar_Green = cvarname; + Cvar_Green += "G"; + std::string Cvar_Blue = cvarname; + Cvar_Blue += "B"; + std::string Cvar_Alpha = cvarname; + Cvar_Alpha += "A"; + + ColorArray.x = (float)CVar_GetS32(Cvar_Red.c_str(), default_colors.x)/255; + ColorArray.y = (float)CVar_GetS32(Cvar_Green.c_str(), default_colors.y)/255; + ColorArray.z = (float)CVar_GetS32(Cvar_Blue.c_str(), default_colors.z)/255; + if (has_alpha) { + ColorArray.w = (float)CVar_GetS32(Cvar_Alpha.c_str(), default_colors.w)/255; } } @@ -348,10 +348,10 @@ namespace SohImGui { } for (size_t pixel = 0; pixel < texBuffer.size() / 4; pixel++) { - texBuffer[pixel * 4 + 0] *= tint.x; - texBuffer[pixel * 4 + 1] *= tint.y; - texBuffer[pixel * 4 + 2] *= tint.z; - texBuffer[pixel * 4 + 3] *= tint.w; + texBuffer[pixel * 4 + 0] *= (uint8_t)tint.x; + texBuffer[pixel * 4 + 1] *= (uint8_t)tint.y; + texBuffer[pixel * 4 + 2] *= (uint8_t)tint.z; + texBuffer[pixel * 4 + 3] *= (uint8_t)tint.w; } const auto asset = new GameAsset{ api->new_texture() }; @@ -436,7 +436,7 @@ namespace SohImGui { } } - void EnhancementRadioButton(std::string text, std::string cvarName, int id) { + void EnhancementRadioButton(const char* text, const char* cvarName, int id) { /*Usage : EnhancementRadioButton("My Visible Name","gMyCVarName", MyID); First arg is the visible name of the Radio button @@ -447,118 +447,219 @@ namespace SohImGui { EnhancementRadioButton("German", "gLanguages", 1); EnhancementRadioButton("French", "gLanguages", 2); */ - int val = CVar_GetS32(cvarName.c_str(), 0); - if (ImGui::RadioButton(text.c_str(), id == val)) { - CVar_SetS32(cvarName.c_str(), (int)id); + std::string make_invisible = "##"; + make_invisible += text; + make_invisible += cvarName; + + int val = CVar_GetS32(cvarName, 0); + if (ImGui::RadioButton(make_invisible.c_str(), id == val)) { + CVar_SetS32(cvarName, id); + needs_save = true; + } + ImGui::SameLine(); + ImGui::Text("%s", text); + } + + void EnhancementCheckbox(const char* text, const char* cvarName) + { + bool val = (bool)CVar_GetS32(cvarName, 0); + if (ImGui::Checkbox(text, &val)) { + CVar_SetS32(cvarName, val); needs_save = true; } } - void EnhancementCheckbox(std::string text, std::string cvarName) + void EnhancementButton(const char* text, const char* cvarName) { - bool val = (bool)CVar_GetS32(cvarName.c_str(), 0); - if (ImGui::Checkbox(text.c_str(), &val)) { - CVar_SetS32(cvarName.c_str(), val); + bool val = (bool)CVar_GetS32(cvarName, 0); + if (ImGui::Button(text)) { + CVar_SetS32(cvarName, !val); + CVar_SetS32(cvarName, !val); needs_save = true; } } - void EnhancementButton(std::string text, std::string cvarName) + void EnhancementSliderInt(const char* text, const char* id, const char* cvarName, int min, int max, const char* format) { - bool val = (bool)CVar_GetS32(cvarName.c_str(), 0); - if (ImGui::Button(text.c_str())) { - CVar_SetS32(cvarName.c_str(), !val); - needs_save = true; - } - } + int val = CVar_GetS32(cvarName, 0); - void EnhancementSliderInt(std::string text, std::string id, std::string cvarName, int min, int max, std::string format) - { - int val = CVar_GetS32(cvarName.c_str(), 0); + ImGui::Text(text, val); - ImGui::Text(text.c_str(), val); - - if (ImGui::SliderInt(id.c_str(), &val, min, max, format.c_str())) + if (ImGui::SliderInt(id, &val, min, max, format)) { - CVar_SetS32(cvarName.c_str(), val); + CVar_SetS32(cvarName, val); needs_save = true; } if (val < min) { val = min; - CVar_SetS32(cvarName.c_str(), val); + CVar_SetS32(cvarName, val); needs_save = true; } if (val > max) { val = max; - CVar_SetS32(cvarName.c_str(), val); + CVar_SetS32(cvarName, val); needs_save = true; } } - void EnhancementSliderFloat(std::string text, std::string id, std::string cvarName, float min, float max, std::string format, float defaultValue, bool isPercentage) + void EnhancementSliderFloat(const char* text, const char* id, const char* cvarName, float min, float max, const char* format, float defaultValue, bool isPercentage) { - float val = CVar_GetFloat(cvarName.c_str(), defaultValue); + float val = CVar_GetFloat(cvarName, defaultValue); if (!isPercentage) - ImGui::Text(text.c_str(), val); + ImGui::Text(text, val); else - ImGui::Text(text.c_str(), static_cast(100 * val)); + ImGui::Text(text, static_cast(100 * val)); - if (ImGui::SliderFloat(id.c_str(), &val, min, max, format.c_str())) + if (ImGui::SliderFloat(id, &val, min, max, format)) { - CVar_SetFloat(cvarName.c_str(), val); + CVar_SetFloat(cvarName, val); needs_save = true; } if (val < min) { val = min; - CVar_SetFloat(cvarName.c_str(), val); + CVar_SetFloat(cvarName, val); needs_save = true; } if (val > max) { val = max; - CVar_SetFloat(cvarName.c_str(), val); + CVar_SetFloat(cvarName, val); needs_save = true; } } - int ClampFloatToInt(float value, int min, int max){ - return fmin(fmax(value,min),max); + void RandomizeColor(const char* cvarName, ImVec4* colors) { + std::string Cvar_Red = cvarName; + Cvar_Red += "R"; + std::string Cvar_Green = cvarName; + Cvar_Green += "G"; + std::string Cvar_Blue = cvarName; + Cvar_Blue += "B"; + std::string Cvar_RBM = cvarName; + Cvar_RBM += "RBM"; + std::string MakeInvisible = "##"; + MakeInvisible += cvarName; + MakeInvisible += "Random"; + std::string FullName = "Random"; + FullName+=MakeInvisible; + if (ImGui::Button(FullName.c_str())) { + s16 RND_R = rand() % (255 - 0); + s16 RND_G = rand() % (255 - 0); + s16 RND_B = rand() % (255 - 0); + colors->x = (float)RND_R/255; + colors->y = (float)RND_G/255; + colors->z = (float)RND_B/255; + CVar_SetS32(Cvar_Red.c_str(), ClampFloatToInt(colors->x*255,0,255)); + CVar_SetS32(Cvar_Green.c_str(), ClampFloatToInt(colors->y*255,0,255)); + CVar_SetS32(Cvar_Blue.c_str(), ClampFloatToInt(colors->z*255,0,255)); + CVar_SetS32(Cvar_RBM.c_str(), 0); //On click disable rainbow mode. + needs_save = true; + } + Tooltip("Clicking this button will make a random color for the colors at it's right.\nPrevious color will be overwrite and will not be recoverable"); } - void EnhancementColor3(std::string text, std::string cvarName, float ColorRGB[3], bool TitleSameLine) { - //Simplified. + void RainbowColor(const char* cvarName, ImVec4* colors) { + std::string Cvar_RBM = cvarName; + Cvar_RBM += "RBM"; + std::string MakeInvisible = "Rainbow"; + MakeInvisible += "##"; + MakeInvisible += cvarName; + MakeInvisible += "Rainbow"; + + EnhancementCheckbox(MakeInvisible.c_str(), Cvar_RBM.c_str()); + Tooltip("Clicking this button will make color cycling\nPrevious color will be overwrite and will not be recoverable"); + } + + void ResetColor(const char* cvarName, ImVec4* colors, ImVec4 defaultcolors, bool has_alpha) { + std::string Cvar_Red = cvarName; + Cvar_Red += "R"; + std::string Cvar_Green = cvarName; + Cvar_Green += "G"; + std::string Cvar_Blue = cvarName; + Cvar_Blue += "B"; + std::string Cvar_Alpha = cvarName; + Cvar_Alpha += "A"; + std::string Cvar_RBM = cvarName; + Cvar_RBM += "RBM"; + std::string MakeInvisible = "Reset"; + MakeInvisible += "##"; + MakeInvisible += cvarName; + MakeInvisible += "Reset"; + if (ImGui::Button(MakeInvisible.c_str())) { + colors->x = defaultcolors.x/255; + colors->y = defaultcolors.y/255; + colors->z = defaultcolors.z/255; + if (has_alpha) { colors->w = defaultcolors.w/255;}; + CVar_SetS32(Cvar_Red.c_str(), ClampFloatToInt(colors->x*255,0,255)); + CVar_SetS32(Cvar_Green.c_str(), ClampFloatToInt(colors->y*255,0,255)); + CVar_SetS32(Cvar_Blue.c_str(), ClampFloatToInt(colors->z*255,0,255)); + if (has_alpha) { CVar_SetS32(Cvar_Alpha.c_str(), ClampFloatToInt(colors->w*255,0,255)); }; + CVar_SetS32(Cvar_RBM.c_str(), 0); //On click disable rainbow mode. + needs_save = true; + } + Tooltip("Clicking this button will to the game original color (GameCube version)\nPrevious color will be overwrite and will not be recoverable"); + } + + void EnhancementColor(const char* text, const char* cvarName, ImVec4 ColorRGBA, ImVec4 default_colors, bool allow_rainbow, bool has_alpha, bool TitleSameLine) { + std::string Cvar_Red = cvarName; + Cvar_Red += "R"; + std::string Cvar_Green = cvarName; + Cvar_Green += "G"; + std::string Cvar_Blue = cvarName; + Cvar_Blue += "B"; + std::string Cvar_Alpha = cvarName; + Cvar_Alpha += "A"; + std::string Cvar_RBM = cvarName; + Cvar_RBM += "RBM"; + + LoadPickersColors(ColorRGBA, cvarName, default_colors, has_alpha); ImGuiColorEditFlags flags = ImGuiColorEditFlags_None; + if (!TitleSameLine){ - ImGui::Text("%s", text.c_str()); + ImGui::Text("%s", text); flags = ImGuiColorEditFlags_NoLabel; } - if (ImGui::ColorEdit3(text.c_str(), ColorRGB, flags)) { - CVar_SetS32((cvarName+"R").c_str(), ClampFloatToInt(ColorRGB[0]*255,0,255)); - CVar_SetS32((cvarName+"G").c_str(), ClampFloatToInt(ColorRGB[1]*255,0,255)); - CVar_SetS32((cvarName+"B").c_str(), ClampFloatToInt(ColorRGB[2]*255,0,255)); - needs_save = true; + if (!has_alpha) { + if (ImGui::ColorEdit3(text, (float *)&ColorRGBA, flags)) { + CVar_SetS32(Cvar_Red.c_str(), ClampFloatToInt(ColorRGBA.x*255,0,255)); + CVar_SetS32(Cvar_Green.c_str(), ClampFloatToInt(ColorRGBA.y*255,0,255)); + CVar_SetS32(Cvar_Blue.c_str(), ClampFloatToInt(ColorRGBA.z*255,0,255)); + needs_save = true; + } + } else { + if (ImGui::ColorEdit4(text, (float *)&ColorRGBA, flags)) { + CVar_SetS32(Cvar_Red.c_str(), ClampFloatToInt(ColorRGBA.x*255,0,255)); + CVar_SetS32(Cvar_Green.c_str(), ClampFloatToInt(ColorRGBA.y*255,0,255)); + CVar_SetS32(Cvar_Blue.c_str(), ClampFloatToInt(ColorRGBA.z*255,0,255)); + CVar_SetS32(Cvar_Alpha.c_str(), ClampFloatToInt(ColorRGBA.w*255,0,255)); + needs_save = true; + } + + } + ImGui::SameLine(); + ResetColor(cvarName, &ColorRGBA, default_colors, has_alpha); + ImGui::SameLine(); + RandomizeColor(cvarName, &ColorRGBA); + if (allow_rainbow) { + //Not all draw support rainbow, like Navi. + ImGui::SameLine(); + RainbowColor(cvarName, &ColorRGBA); } } - void Tooltip(std::string text) { - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("%s", text.c_str()); - } - - void DrawMainMenuAndCalculateGameSize() { + void DrawMainMenuAndCalculateGameSize(void) { console->Update(); ImGuiBackendNewFrame(); ImGuiWMNewFrame(); ImGui::NewFrame(); - LoadInterfaceEditor(); const std::shared_ptr wnd = GlobalCtx2::GetInstance()->GetWindow(); ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoBackground | @@ -633,7 +734,7 @@ namespace SohImGui { auto menuLabel = "Controller " + std::to_string(i + 1); if (ImGui::BeginMenu(menuLabel.c_str())) { - EnhancementSliderFloat("Gyro Sensitivity: %d %%", "##GYROSCOPE", StringHelper::Sprintf("gCont%i_GyroSensitivity", i), 0.0f, 1.0f, "", 1.0f, true); + EnhancementSliderFloat("Gyro Sensitivity: %d %%", "##GYROSCOPE", StringHelper::Sprintf("gCont%i_GyroSensitivity", i).c_str(), 0.0f, 1.0f, "", 1.0f, true); if (ImGui::Button("Recalibrate Gyro")) { @@ -644,7 +745,7 @@ namespace SohImGui { ImGui::Separator(); - EnhancementSliderFloat("Rumble Strength: %d %%", "##RUMBLE", StringHelper::Sprintf("gCont%i_RumbleStrength", i), 0.0f, 1.0f, "", 1.0f, true); + EnhancementSliderFloat("Rumble Strength: %d %%", "##RUMBLE", StringHelper::Sprintf("gCont%i_RumbleStrength", i).c_str(), 0.0f, 1.0f, "", 1.0f, true); ImGui::EndMenu(); } @@ -676,6 +777,34 @@ namespace SohImGui { Tooltip("Activates anti-aliasing when above 1, up to 8x for 8 samples for every pixel"); gfx_msaa_level = CVar_GetS32("gMSAAValue", 1); + if (impl.backend == Backend::DX11) + { + const char* cvar = "gExtraLatencyThreshold"; + int val = CVar_GetS32(cvar, 80); + val = MAX(MIN(val, 250), 0); + int fps = val; + + if (fps == 0) + { + ImGui::Text("Jitter fix: Off"); + } + else + { + ImGui::Text("Jitter fix: >= %d FPS", fps); + } + + if (ImGui::SliderInt("##ExtraLatencyThreshold", &val, 0, 250, "", ImGuiSliderFlags_AlwaysClamp)) + { + CVar_SetS32(cvar, val); + needs_save = true; + } + + Tooltip("When Interpolation FPS setting is at least this threshold,\n" + "add one frame of input lag (e.g. 16.6 ms for 60 FPS) in order to avoid jitter.\n" + "This setting allows the CPU to work on one frame while GPU works on the previous frame.\n" + "This setting should be used when your computer is too slow to do CPU + GPU work in time."); + } + EXPERIMENTAL(); ImGui::Text("Texture Filter (Needs reload)"); GfxRenderingAPI* gapi = gfx_get_current_rendering_api(); @@ -723,17 +852,57 @@ namespace SohImGui { Tooltip("Allows equiping the tunic and boots to c-buttons"); EnhancementCheckbox("MM Bunny Hood", "gMMBunnyHood"); Tooltip("Wearing the Bunny Hood grants a speed increase like in Majora's Mask"); + EnhancementCheckbox("Better Owl", "gBetterOwl"); + Tooltip("The default response to Kaepora Gaebora is always that you understood what he said"); EnhancementCheckbox("Disable Navi Call Audio", "gDisableNaviCallAudio"); Tooltip("Disables the voice audio when Navi calls you"); - ImGui::EndMenu(); } if (ImGui::BeginMenu("Graphics")) { + if (ImGui::BeginMenu("Animated Link in Pause Menu")) { + ImGui::Text("Rotation"); + EnhancementRadioButton("Disabled", "gPauseLiveRotation", 0); + EnhancementRadioButton("Rotate Link with D-pad", "gPauseLiveRotation", 1); + Tooltip("Allow you to rotate Link on the Equipment menu with the DPAD\nUse DPAD-Up or DPAD-Down to reset Link's rotation"); + EnhancementRadioButton("Rotate Link with C-buttons", "gPauseLiveRotation", 2); + Tooltip("Allow you to rotate Link on the Equipment menu with the C-buttons\nUse C-Up or C-Down to reset Link's rotation"); + + if (CVar_GetS32("gPauseLiveRotation", 0) != 0) { + EnhancementSliderInt("Rotation Speed: %d", "##MinRotationSpeed", "gPauseLiveLinkRotationSpeed", 1, 20, ""); + } + ImGui::Separator(); + ImGui::Text("Static loop"); + EnhancementRadioButton("Disabled", "gPauseLiveLink", 0); + EnhancementRadioButton("Idle (standing)", "gPauseLiveLink", 1); + EnhancementRadioButton("Idle (look around)", "gPauseLiveLink", 2); + EnhancementRadioButton("Idle (belt)", "gPauseLiveLink", 3); + EnhancementRadioButton("Idle (shield)", "gPauseLiveLink", 4); + EnhancementRadioButton("Idle (test sword)", "gPauseLiveLink", 5); + EnhancementRadioButton("Idle (yawn)", "gPauseLiveLink", 6); + EnhancementRadioButton("Battle Stance", "gPauseLiveLink", 7); + EnhancementRadioButton("Walking (no shield)", "gPauseLiveLink", 8); + EnhancementRadioButton("Walking (holding shield)", "gPauseLiveLink", 9); + EnhancementRadioButton("Running (no shield)", "gPauseLiveLink", 10); + EnhancementRadioButton("Running (holding shield)", "gPauseLiveLink", 11); + EnhancementRadioButton("Hand on hip", "gPauseLiveLink", 12); + EnhancementRadioButton("Spin attack charge", "gPauseLiveLink", 13); + EnhancementRadioButton("Look at hand", "gPauseLiveLink", 14); + ImGui::Separator(); + ImGui::Text("Randomize"); + EnhancementRadioButton("Random", "gPauseLiveLink", 15); + Tooltip("Randomize the animation played each time you open the menu"); + EnhancementRadioButton("Random cycle", "gPauseLiveLink", 16); + Tooltip("andomize the animation played on hte menu after a certain time"); + if (CVar_GetS32("gPauseLiveLink", 0) >= 16) { + EnhancementSliderInt("Frame to wait: %d", "##MinFrameCount", "gMinFrameCount", 1, 1000, ""); + } + + ImGui::EndMenu(); + } EnhancementCheckbox("N64 Mode", "gN64Mode"); Tooltip("Sets aspect ratio to 4:3 and lowers resolution to 240p, the N64's native resolution"); - EnhancementCheckbox("Animated Link in Pause Menu", "gPauseLiveLink"); EnhancementCheckbox("Enable 3D Dropped items/projectiles", "gNewDrops"); Tooltip("Change most 2D items & projectiles to their a 3D version"); EnhancementCheckbox("Dynamic Wallet Icon", "gDynamicWalletIcon"); @@ -760,7 +929,46 @@ namespace SohImGui { EXPERIMENTAL(); - EnhancementCheckbox("60FPS Interpolation", "g60FPS"); + const char* fps_cvar = "gInterpolationFPS"; + { + int val = CVar_GetS32(fps_cvar, 20); + val = MAX(MIN(val, 250), 20); + int fps = val; + + if (fps == 20) + { + ImGui::Text("Frame interpolation: Off"); + } + else + { + ImGui::Text("Frame interpolation: %d FPS", fps); + } + + if (ImGui::SliderInt("##FPSInterpolation", &val, 20, 250, "", ImGuiSliderFlags_AlwaysClamp)) + { + CVar_SetS32(fps_cvar, val); + needs_save = true; + } + + Tooltip("Interpolate extra frames to get smoother graphics.\n" + "Set to match your monitor's refresh rate, or a divisor of it.\n" + "A higher target FPS than your monitor's refresh rate will just waste resources,\n" + "and might give a worse result.\n" + "For consistent input lag, set this value and your monitor's refresh rate to a multiple of 20.\n" + "Ctrl+Click for keyboard input."); + } + if (impl.backend == Backend::DX11) + { + if (ImGui::Button("Match Refresh Rate")) + { + int hz = roundf(gfx_get_detected_hz()); + if (hz >= 20 && hz <= 250) + { + CVar_SetS32(fps_cvar, hz); + needs_save = true; + } + } + } EnhancementCheckbox("Disable LOD", "gDisableLOD"); Tooltip("Turns off the level of detail setting, making models always use their higher poly variants"); @@ -867,36 +1075,36 @@ namespace SohImGui { if (ImGui::BeginTabBar("Cosmetics Editor", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) { if (ImGui::BeginTabItem("Navi")) { EnhancementCheckbox("Custom colors for Navi", "gUseNaviCol"); - Tooltip("Enable/Disable custom Navi's colors. \nIf disabled you will have original colors for Navi.\nColors are refreshed when Navi goes back in your pockets"); - EnhancementColor3("Navi Idle Inner", "gNavi_Idle_Inner_", navi_idle_i_col, false); + Tooltip("Enable/Disable custom Navi's colors. \nIf disabled you will have original colors for Navi.\nColors are refreshed when Navi goes back in your pockets."); + EnhancementColor("Navi Idle Inner", "gNavi_Idle_Inner_", navi_idle_i_col, ImVec4(255,255,255,255), false); Tooltip("Inner color for Navi (idle flying around)"); - EnhancementColor3("Navi Idle Outer", "gNavi_Idle_Outer_", navi_idle_o_col, false); + EnhancementColor("Navi Idle Outer", "gNavi_Idle_Outer_", navi_idle_o_col, ImVec4(115,230,255,255), false); Tooltip("Outer color for Navi (idle flying around)"); ImGui::Separator(); - EnhancementColor3("Navi NPC Inner", "gNavi_NPC_Inner_", navi_npc_i_col, false); + EnhancementColor("Navi NPC Inner", "gNavi_NPC_Inner_", navi_npc_i_col, ImVec4(100,100,255,255), false); Tooltip("Inner color for Navi (when Navi fly around NPCs)"); - EnhancementColor3("Navi NPC Outer", "gNavi_NPC_Outer_", navi_npc_o_col, false); + EnhancementColor("Navi NPC Outer", "gNavi_NPC_Outer_", navi_npc_o_col, ImVec4(90,90,255,255), false); Tooltip("Outer color for Navi (when Navi fly around NPCs)"); ImGui::Separator(); - EnhancementColor3("Navi Enemy Inner", "gNavi_Enemy_Inner_", navi_enemy_i_col, false); + EnhancementColor("Navi Enemy Inner", "gNavi_Enemy_Inner_", navi_enemy_i_col, ImVec4(255,255,0,255), false); Tooltip("Inner color for Navi (when Navi fly around Enemies or Bosses)"); - EnhancementColor3("Navi Enemy Outer", "gNavi_Enemy_Outer_", navi_enemy_o_col, false); + EnhancementColor("Navi Enemy Outer", "gNavi_Enemy_Outer_", navi_enemy_o_col, ImVec4(220,220,0,255), false); Tooltip("Outer color for Navi (when Navi fly around Enemies or Bosses)"); ImGui::Separator(); - EnhancementColor3("Navi Prop Inner", "gNavi_Prop_Inner_", navi_prop_i_col, false); + EnhancementColor("Navi Prop Inner", "gNavi_Prop_Inner_", navi_prop_i_col, ImVec4(0,255,0,255), false); Tooltip("Inner color for Navi (when Navi fly around props (signs etc))"); - EnhancementColor3("Navi Prop Outer", "gNavi_Prop_Outer_", navi_prop_o_col, false); + EnhancementColor("Navi Prop Outer", "gNavi_Prop_Outer_", navi_prop_o_col, ImVec4(0,220,0,255), false); Tooltip("Outer color for Navi (when Navi fly around props (signs etc))"); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Tunics")) { EnhancementCheckbox("Custom colors on tunics", "gUseTunicsCol"); - Tooltip("Enable/Disable custom Link's tunics colors. \nIf disabled you will have original colors for Link's tunics"); - EnhancementColor3("Kokiri Tunic", "gTunic_Kokiri_", kokiri_col, false); + Tooltip("Enable/Disable custom Link's tunics colors. \nIf disabled you will have original colors for Link's tunics."); + EnhancementColor("Kokiri Tunic", "gTunic_Kokiri_", kokiri_col, ImVec4(30,105,27,255)); ImGui::Separator(); - EnhancementColor3("Goron Tunic", "gTunic_Goron_", goron_col, false); + EnhancementColor("Goron Tunic", "gTunic_Goron_", goron_col, ImVec4(100,20,0,255)); ImGui::Separator(); - EnhancementColor3("Zora Tunic", "gTunic_Zora_", zora_col, false); + EnhancementColor("Zora Tunic", "gTunic_Zora_", zora_col, ImVec4(0,60,100,255)); ImGui::EndTabItem(); } ImGui::EndTabBar(); @@ -912,39 +1120,39 @@ namespace SohImGui { ImGui::Begin("Interface Editor", nullptr, ImGuiWindowFlags_NoFocusOnAppearing); if (ImGui::BeginTabBar("Interface Editor", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) { if (ImGui::BeginTabItem("Hearts")) { - EnhancementColor3("Hearts inner", "gCCHeartsPrim", hearts_colors, false); + EnhancementColor("Hearts inner", "gCCHeartsPrim", hearts_colors, ImVec4(255,70,50,255)); Tooltip("Hearts inner color (red in original)\nAffect both Normal Hearts and the ones in Double Defense"); - EnhancementColor3("Hearts double def", "gDDCCHeartsPrim", hearts_dd_colors, false); - Tooltip("Hearts outline color (white in original)\nAffect Double Defense outline only"); + EnhancementColor("Hearts double def", "gDDCCHeartsPrim", hearts_dd_colors, ImVec4(255,255,255,255)); + Tooltip("Hearts outline color (white in original)\nAffect Double Defense outline only."); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Buttons")) { - EnhancementColor3("A Buttons", "gCCABtnPrim", a_btn_colors, false); - Tooltip("A Buttons colors (Green in original Gamecube)\nAffect A buttons colors on interface, in shops, messages boxes, ocarina notes and inventory cursors"); - EnhancementColor3("B Buttons", "gCCBBtnPrim", b_btn_colors, false); + EnhancementColor("A Buttons", "gCCABtnPrim", a_btn_colors, ImVec4(90,90,255,255)); + Tooltip("A Buttons colors (Green in original Gamecube)\nAffect A buttons colors on interface, in shops, messages boxes, ocarina notes and inventory cursors."); + EnhancementColor("B Buttons", "gCCBBtnPrim", b_btn_colors, ImVec4(0,150,0,255)); Tooltip("B Button colors (Red in original Gamecube)\nAffect B button colors on interface"); - EnhancementColor3("C Buttons", "gCCCBtnPrim", c_btn_colors, false); + EnhancementColor("C Buttons", "gCCCBtnPrim", c_btn_colors, ImVec4(255,160,0,255)); Tooltip("C Buttons colors (Yellowish / Oranges in originals)\nAffect C buttons colors on interface, in inventory and ocarina notes"); - EnhancementColor3("Start Buttons", "gCCStartBtnPrim", start_btn_colors, false); + EnhancementColor("Start Buttons", "gCCStartBtnPrim", start_btn_colors, ImVec4(120,120,120,255)); Tooltip("Start Button colors (gray in Gamecube)\nAffect Start button colors in inventory"); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Magic Bar")) { - EnhancementColor3("Magic bar borders", "gCCMagicBorderPrim", magic_border_colors, false); - Tooltip("Affect the border of the magic bar when being used\nWhite flash in original game"); - EnhancementColor3("Magic bar main color", "gCCMagicPrim", magic_remaining_colors, false); - Tooltip("Affect the magic bar color\nGreen in original game"); - EnhancementColor3("Magic bar being used", "gCCMagicUsePrim", magic_use_colors, false); - Tooltip("Affect the magic bar when being used\nYellow in original game"); + EnhancementColor("Magic bar borders", "gCCMagicBorderPrim", magic_border_colors, ImVec4(255,255,255,255)); + Tooltip("Affect the border of the magic bar when being used\nWhite flash in original game."); + EnhancementColor("Magic bar main color", "gCCMagicPrim", magic_remaining_colors, ImVec4(0,200,0,255)); + Tooltip("Affect the magic bar color\nGreen in original game."); + EnhancementColor("Magic bar being used", "gCCMagicUsePrim", magic_use_colors, ImVec4(250,250,0,255)); + Tooltip("Affect the magic bar when being used\nYellow in original game."); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Misc")) { - EnhancementColor3("Minimap color", "gCCMinimapPrim", minimap_colors, false); - Tooltip("Affect the Dungeon and Overworld minimaps"); - EnhancementColor3("Rupee icon color", "gCCRupeePrim", rupee_colors, false); - Tooltip("Affect the Rupee icon on interface\nGreen by default"); - EnhancementColor3("Small Keys icon color", "gCCKeysPrim", smolekey_colors, false); - Tooltip("Affect the Small keys icon on interface\nGray by default"); + EnhancementColor("Minimap color", "gCCMinimapPrim", minimap_colors, ImVec4(0,255,255,255)); + Tooltip("Affect the Dungeon and Overworld minimaps."); + EnhancementColor("Rupee icon color", "gCCRupeePrim", rupee_colors, ImVec4(120,120,120,255)); + Tooltip("Affect the Rupee icon on interface\nGreen by default."); + EnhancementColor("Small Keys icon color", "gCCKeysPrim", smolekey_colors, ImVec4(200,230,255,255)); + Tooltip("Affect the Small keys icon on interface\nGray by default."); ImGui::EndTabItem(); } ImGui::EndTabBar(); @@ -960,7 +1168,7 @@ namespace SohImGui { varName.erase(std::ranges::remove_if(varName, isspace).begin(), varName.end()); std::string toggleName = "g" + varName + "Enabled"; - EnhancementCheckbox(name, toggleName); + EnhancementCheckbox(name.c_str(), toggleName.c_str()); customWindows[name].enabled = CVar_GetS32(toggleName.c_str(), 0); } ImGui::EndMenu(); @@ -1011,19 +1219,19 @@ namespace SohImGui { main_pos.y -= top_left_pos.y; ImVec2 size = ImGui::GetContentRegionAvail(); ImVec2 pos = ImVec2(0, 0); - gfx_current_dimensions.width = size.x * gfx_current_dimensions.internal_mul; - gfx_current_dimensions.height = size.y * gfx_current_dimensions.internal_mul; - gfx_current_game_window_viewport.x = main_pos.x; - gfx_current_game_window_viewport.y = main_pos.y; - gfx_current_game_window_viewport.width = size.x; - gfx_current_game_window_viewport.height = size.y; + gfx_current_dimensions.width = (uint32_t)(size.x * gfx_current_dimensions.internal_mul); + gfx_current_dimensions.height = (uint32_t)(size.y * gfx_current_dimensions.internal_mul); + gfx_current_game_window_viewport.x = (int16_t)main_pos.x; + gfx_current_game_window_viewport.y = (int16_t)main_pos.y; + gfx_current_game_window_viewport.width = (int16_t)size.x; + gfx_current_game_window_viewport.height = (int16_t)size.y; if (CVar_GetS32("gN64Mode", 0)) { gfx_current_dimensions.width = 320; gfx_current_dimensions.height = 240; const int sw = size.y * 320 / 240; - gfx_current_game_window_viewport.x += (size.x - sw) / 2; + gfx_current_game_window_viewport.x += ((int)size.x - sw) / 2; gfx_current_game_window_viewport.width = sw; pos = ImVec2(size.x / 2 - sw / 2, 0); size = ImVec2(sw, size.y); @@ -1032,12 +1240,12 @@ namespace SohImGui { overlay->Draw(); } - void DrawFramebufferAndGameInput() { - ImVec2 main_pos = ImGui::GetWindowPos(); + void DrawFramebufferAndGameInput(void) { + const ImVec2 main_pos = ImGui::GetWindowPos(); ImVec2 size = ImGui::GetContentRegionAvail(); ImVec2 pos = ImVec2(0, 0); if (CVar_GetS32("gN64Mode", 0)) { - const int sw = size.y * 320 / 240; + const float sw = size.y * 320.0f / 240.0f; pos = ImVec2(size.x / 2 - sw / 2, 0); size = ImVec2(sw, size.y); } @@ -1109,6 +1317,8 @@ namespace SohImGui { ImGui::UpdatePlatformWindows(); ImGui::RenderPlatformWindowsDefault(); } + //Placed here so it does the rainbow effects even if menu is not on. + LoadRainbowColor(); } void CancelFrame() { diff --git a/libultraship/libultraship/SohImGuiImpl.h b/libultraship/libultraship/SohImGuiImpl.h index 210a26db9..421c71778 100644 --- a/libultraship/libultraship/SohImGuiImpl.h +++ b/libultraship/libultraship/SohImGuiImpl.h @@ -63,12 +63,14 @@ namespace SohImGui { extern bool needs_save; void Init(WindowImpl window_impl); void Update(EventImpl event); - - void EnhancementRadioButton(std::string text, std::string cvarName, int value); - void EnhancementCheckbox(std::string text, std::string cvarName); - void EnhancementSliderInt(std::string text, std::string id, std::string cvarName, int min, int max, std::string format); - void EnhancementSliderFloat(std::string text, std::string id, std::string cvarName, float min, float max, std::string format, float defaultValue); - void EnhancementColor3(std::string text, std::string cvarName, float ColorRGB[3], bool TitleSameLine); + void Tooltip(const char* text); + + void EnhancementRadioButton(const char* text, const char* cvarName, int id); + void EnhancementCheckbox(const char* text, const char* cvarName); + void EnhancementButton(const char* text, const char* cvarName); + void EnhancementSliderInt(const char* text, const char* id, const char* cvarName, int min, int max, const char* format); + void EnhancementSliderFloat(const char* text, const char* id, const char* cvarName, float min, float max, const char* format, float defaultValue, bool isPercentage); + void EnhancementColor(const char* text, const char* cvarName, ImVec4 ColorRGBA, ImVec4 default_colors, bool allow_rainbow = true, bool has_alpha=false, bool TitleSameLine=false); void DrawMainMenuAndCalculateGameSize(void); @@ -79,7 +81,10 @@ namespace SohImGui { void BindCmd(const std::string& cmd, CommandEntry entry); void AddWindow(const std::string& category, const std::string& name, WindowDrawFunc drawFunc); void LoadResource(const std::string& name, const std::string& path, const ImVec4& tint = ImVec4(1, 1, 1, 1)); - void LoadInterfaceEditor(); + void LoadPickersColors(ImVec4& ColorArray, const char* cvarname, const ImVec4& default_colors, bool has_alpha=false); + void RandomizeColor(const char* cvarName, ImVec4* colors); + void RainbowColor(const char* cvarName, ImVec4* colors); + void ResetColor(const char* cvarName, ImVec4* colors, ImVec4 defaultcolors, bool has_alpha); ImTextureID GetTextureByID(int id); ImTextureID GetTextureByName(const std::string& name); } diff --git a/libultraship/libultraship/Window.cpp b/libultraship/libultraship/Window.cpp index 5c2cf3cae..8319332ad 100644 --- a/libultraship/libultraship/Window.cpp +++ b/libultraship/libultraship/Window.cpp @@ -272,13 +272,14 @@ namespace Ship { gfx_run(Commands, m); gfx_end_frame(); } - gfx_run(Commands, {}); - gfx_end_frame(); } - void Window::SetFrameDivisor(int divisor) { - gfx_set_framedivisor(divisor); - //gfx_set_framedivisor(0); + void Window::SetTargetFps(int fps) { + gfx_set_target_fps(fps); + } + + void Window::SetMaximumFrameLatency(int latency) { + gfx_set_maximum_frame_latency(latency); } void Window::GetPixelDepthPrepare(float x, float y) { diff --git a/libultraship/libultraship/Window.h b/libultraship/libultraship/Window.h index 04886c53e..a3087bf0c 100644 --- a/libultraship/libultraship/Window.h +++ b/libultraship/libultraship/Window.h @@ -20,7 +20,8 @@ namespace Ship { void Init(); void StartFrame(); void RunCommands(Gfx* Commands, const std::vector>& mtx_replacements); - void SetFrameDivisor(int divisor); + void SetTargetFps(int fps); + void SetMaximumFrameLatency(int latency); void GetPixelDepthPrepare(float x, float y); uint16_t GetPixelDepth(float x, float y); void ToggleFullscreen(); diff --git a/soh/Makefile b/soh/Makefile index be46f55e7..74a112ecb 100644 --- a/soh/Makefile +++ b/soh/Makefile @@ -18,8 +18,8 @@ WARN := \ -funsigned-char \ -m32 -mhard-float -fno-stack-protector -fno-common -fno-zero-initialized-in-bss -fno-strict-aliasing -fno-inline-functions -fno-inline-small-functions -fno-toplevel-reorder -ffreestanding -fwrapv \ -CXXFLAGS := $(WARN) -std=c++20 -D_GNU_SOURCE -fpermissive -no-pie -nostdlib -march=i386 -CFLAGS := $(WARN) -std=c99 -D_GNU_SOURCE -no-pie -nostdlib -march=i386 +CXXFLAGS := $(WARN) -std=c++20 -D_GNU_SOURCE -fpermissive -no-pie -nostdlib -march=i386 -msse2 -mfpmath=sse +CFLAGS := $(WARN) -std=c99 -D_GNU_SOURCE -no-pie -nostdlib -march=i386 -msse2 -mfpmath=sse LDFLAGS := -m32 CPPFLAGS := -MMD diff --git a/soh/assets/xml/GC_NMQ_PAL_F/objects/object_triforce_spot.xml b/soh/assets/xml/GC_NMQ_PAL_F/objects/object_triforce_spot.xml index a9d52dd96..87d449458 100644 --- a/soh/assets/xml/GC_NMQ_PAL_F/objects/object_triforce_spot.xml +++ b/soh/assets/xml/GC_NMQ_PAL_F/objects/object_triforce_spot.xml @@ -1,6 +1,6 @@ - + diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index e8e1b31c1..9401d514a 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -175,8 +175,6 @@ extern "C" void Graph_StartFrame() { // C->C++ Bridge extern "C" void Graph_ProcessGfxCommands(Gfx* commands) { - OTRGlobals::Instance->context->GetWindow()->SetFrameDivisor(CVar_GetS32("g60FPS", 0) == 0 ? R_UPDATE_RATE : 1); - if (!audio.initialized) { audio.initialized = true; std::thread([]() { @@ -226,15 +224,45 @@ extern "C" void Graph_ProcessGfxCommands(Gfx* commands) { audio.cv_to_thread.notify_one(); std::vector> mtx_replacements; - if (CVar_GetS32("g60FPS", 0) != 0) { - int to = R_UPDATE_RATE; - for (int i = 1; i < to; i++) { - mtx_replacements.push_back(FrameInterpolation_Interpolate(i / (float)to)); + int target_fps = CVar_GetS32("gInterpolationFPS", 20); + static int last_fps; + static int last_update_rate; + static int time; + int fps = target_fps; + int original_fps = 60 / R_UPDATE_RATE; + + if (target_fps == 20 || original_fps > target_fps) { + fps = original_fps; + } + + if (last_fps != fps || last_update_rate != R_UPDATE_RATE) { + time = 0; + } + + // time_base = fps * original_fps (one second) + int next_original_frame = fps; + + while (time + original_fps <= next_original_frame) { + time += original_fps; + if (time != next_original_frame) { + mtx_replacements.push_back(FrameInterpolation_Interpolate((float)time / next_original_frame)); + } else { + mtx_replacements.emplace_back(); } } + time -= fps; + + OTRGlobals::Instance->context->GetWindow()->SetTargetFps(fps); + + int threshold = CVar_GetS32("gExtraLatencyThreshold", 80); + OTRGlobals::Instance->context->GetWindow()->SetMaximumFrameLatency(threshold > 0 && target_fps >= threshold ? 2 : 1); + OTRGlobals::Instance->context->GetWindow()->RunCommands(commands, mtx_replacements); + last_fps = fps; + last_update_rate = R_UPDATE_RATE; + { std::unique_lock Lock(audio.mutex); while (audio.processing) { diff --git a/soh/soh/frame_interpolation.cpp b/soh/soh/frame_interpolation.cpp index bd85d41ce..44c255db1 100644 --- a/soh/soh/frame_interpolation.cpp +++ b/soh/soh/frame_interpolation.cpp @@ -451,7 +451,7 @@ void FrameInterpolation_StartRecord(void) { current_recording = {}; current_path.clear(); current_path.push_back(¤t_recording.root_path); - if (CVar_GetS32("g60FPS", 0) != 0) { + if (CVar_GetS32("gInterpolationFPS", 20) != 20) { is_recording = true; } } diff --git a/soh/soh/z_message_OTR.cpp b/soh/soh/z_message_OTR.cpp index ac1f013c2..8c1d26a75 100644 --- a/soh/soh/z_message_OTR.cpp +++ b/soh/soh/z_message_OTR.cpp @@ -19,9 +19,50 @@ MessageTableEntry* OTRMessage_LoadTable(const char* filePath, bool isNES) { if (file == nullptr) return nullptr; - MessageTableEntry* table = (MessageTableEntry*)malloc(sizeof(MessageTableEntry) * file->messages.size()); + // Allocate room for an additional message + MessageTableEntry* table = (MessageTableEntry*)malloc(sizeof(MessageTableEntry) * (file->messages.size() + 1)); for (int i = 0; i < file->messages.size(); i++) { + // Look for Owl Text + if (file->messages[i].id == 0x2066) { + // Create a new message based on the Owl Text + char* kaeporaPatch = (char*)malloc(sizeof(char) * file->messages[i].msg.size()); + file->messages[i].msg.copy(kaeporaPatch, file->messages[i].msg.size(), 0); + + // Swap the order of yes and no in this new message + if (filePath == "text/nes_message_data_static/nes_message_data_static") { + kaeporaPatch[26] = 'Y'; + kaeporaPatch[27] = 'e'; + kaeporaPatch[28] = 's'; + kaeporaPatch[29] = 1; + kaeporaPatch[30] = 'N'; + kaeporaPatch[31] = 'o'; + } else if (filePath == "text/ger_message_data_static/ger_message_data_static") { + kaeporaPatch[30] = 'J'; + kaeporaPatch[31] = 'a'; + kaeporaPatch[32] = '!'; + kaeporaPatch[33] = 1; + kaeporaPatch[34] = 'N'; + kaeporaPatch[35] = 'e'; + kaeporaPatch[36] = 'i'; + kaeporaPatch[37] = 'n'; + } else { + kaeporaPatch[26] = 'O'; + kaeporaPatch[27] = 'u'; + kaeporaPatch[28] = 'i'; + kaeporaPatch[29] = 1; + kaeporaPatch[30] = 'N'; + kaeporaPatch[31] = 'o'; + kaeporaPatch[32] = 'n'; + } + + // load data into message + table[file->messages.size()].textId = 0x71B3; + table[file->messages.size()].typePos = (file->messages[i].textboxType << 4) | file->messages[i].textboxYPos; + table[file->messages.size()].segment = kaeporaPatch; + table[file->messages.size()].msgSize = file->messages[i].msg.size(); + } + table[i].textId = file->messages[i].id; table[i].typePos = (file->messages[i].textboxType << 4) | file->messages[i].textboxYPos; table[i].segment = file->messages[i].msg.c_str(); diff --git a/soh/src/code/z_actor.c b/soh/src/code/z_actor.c index ba5d4dcd6..bdb2fe78c 100644 --- a/soh/src/code/z_actor.c +++ b/soh/src/code/z_actor.c @@ -2290,8 +2290,8 @@ void Actor_DrawFaroresWindPointer(GlobalContext* globalCtx) { ((void)0, gSaveContext.respawn[RESPAWN_MODE_TOP].pos.y) + yOffset, ((void)0, gSaveContext.respawn[RESPAWN_MODE_TOP].pos.z), 255, 255, 255, lightRadius); - CLOSE_DISPS(globalCtx->state.gfxCtx, "../z_actor.c", 5474); } + CLOSE_DISPS(globalCtx->state.gfxCtx, "../z_actor.c", 5474); } void func_80030488(GlobalContext* globalCtx) { diff --git a/soh/src/code/z_lifemeter.c b/soh/src/code/z_lifemeter.c index 30b18683f..e45850a73 100644 --- a/soh/src/code/z_lifemeter.c +++ b/soh/src/code/z_lifemeter.c @@ -128,6 +128,13 @@ void HealthMeter_Init(GlobalContext* globalCtx) { HeartDDOutline[0] = CVar_GetS32("gDDCCHeartsPrimR", 90); HeartDDOutline[1] = CVar_GetS32("gDDCCHeartsPrimG", 90); HeartDDOutline[2] = CVar_GetS32("gDDCCHeartsPrimB", 90); + } else { + HeartInner[0] = HEARTS_PRIM_R; + HeartInner[1] = HEARTS_PRIM_G; + HeartInner[2] = HEARTS_PRIM_B; + HeartDDOutline[0] = HEARTS_DD_PRIM_R; + HeartDDOutline[1] = HEARTS_DD_PRIM_G; + HeartDDOutline[2] = HEARTS_DD_PRIM_B; } interfaceCtx->unk_228 = 0x140; @@ -194,12 +201,12 @@ void HealthMeter_Update(GlobalContext* globalCtx) { HeartDDOutline[1] = CVar_GetS32("gDDCCHeartsPrimG", sHeartsDDPrim[0][1]); HeartDDOutline[2] = CVar_GetS32("gDDCCHeartsPrimB", sHeartsDDPrim[0][2]); } else { - HeartInner[0] = sHeartsPrimColors[0][0]; - HeartInner[1] = sHeartsPrimColors[0][1]; - HeartInner[2] = sHeartsPrimColors[0][2]; - HeartDDOutline[0] = sHeartsDDPrim[0][0]; - HeartDDOutline[1] = sHeartsDDPrim[0][1]; - HeartDDOutline[2] = sHeartsDDPrim[0][2]; + HeartInner[0] = HEARTS_PRIM_R; + HeartInner[1] = HEARTS_PRIM_G; + HeartInner[2] = HEARTS_PRIM_B; + HeartDDOutline[0] = HEARTS_DD_PRIM_R; + HeartDDOutline[1] = HEARTS_DD_PRIM_G; + HeartDDOutline[2] = HEARTS_DD_PRIM_B; } if (interfaceCtx->unk_200 != 0) { @@ -299,33 +306,33 @@ void HealthMeter_Update(GlobalContext* globalCtx) { sBeatingHeartsDDEnv[1] = (u8)(gFactor + HeartDDInner[1]) & 0xFF; sBeatingHeartsDDEnv[2] = (u8)(bFactor + HeartDDInner[2]) & 0xFF; } else { - sHeartsDDPrim[2][0] = HeartInner[0]; - sHeartsDDPrim[2][1] = HeartInner[1]; - sHeartsDDPrim[2][2] = HeartInner[2]; + sHeartsDDPrim[2][0] = HEARTS_PRIM_R; + sHeartsDDPrim[2][1] = HEARTS_PRIM_G; + sHeartsDDPrim[2][2] = HEARTS_PRIM_B; - sHeartsDDPrim[1][0] = sHeartsDDPrimColors[ddType][0]; - sHeartsDDPrim[1][1] = sHeartsDDPrimColors[ddType][1]; - sHeartsDDPrim[1][2] = sHeartsDDPrimColors[ddType][2]; + sHeartsDDPrim[1][0] = HEARTS_DD_PRIM_R; + sHeartsDDPrim[1][1] = HEARTS_DD_PRIM_G; + sHeartsDDPrim[1][2] = HEARTS_DD_PRIM_B; - sHeartsDDEnv[1][0] = sHeartsDDEnvColors[ddType][0]; - sHeartsDDEnv[1][1] = sHeartsDDEnvColors[ddType][1]; - sHeartsDDEnv[1][2] = sHeartsDDEnvColors[ddType][2]; + sHeartsDDEnv[1][0] = HEARTS_PRIM_R; + sHeartsDDEnv[1][1] = HEARTS_PRIM_G; + sHeartsDDEnv[1][2] = HEARTS_PRIM_B; rFactor = sHeartsDDPrimFactors[ddType][0] * ddFactor; gFactor = sHeartsDDPrimFactors[ddType][1] * ddFactor; bFactor = sHeartsDDPrimFactors[ddType][2] * ddFactor; - sBeatingHeartsDDPrim[0] = (u8)(rFactor + HeartDDOutline[0]) & 0xFF; - sBeatingHeartsDDPrim[1] = (u8)(gFactor + HeartDDOutline[1]) & 0xFF; - sBeatingHeartsDDPrim[2] = (u8)(bFactor + HeartDDOutline[2]) & 0xFF; + sBeatingHeartsDDPrim[0] = (u8)(rFactor + HEARTS_DD_PRIM_R) & 0xFF; + sBeatingHeartsDDPrim[1] = (u8)(gFactor + HEARTS_DD_PRIM_G) & 0xFF; + sBeatingHeartsDDPrim[2] = (u8)(bFactor + HEARTS_DD_PRIM_B) & 0xFF; rFactor = sHeartsDDEnvFactors[ddType][0] * ddFactor; gFactor = sHeartsDDEnvFactors[ddType][1] * ddFactor; bFactor = sHeartsDDEnvFactors[ddType][2] * ddFactor; - sBeatingHeartsDDEnv[0] = (u8)(rFactor + HeartDDInner[0]) & 0xFF; - sBeatingHeartsDDEnv[1] = (u8)(gFactor + HeartDDInner[1]) & 0xFF; - sBeatingHeartsDDEnv[2] = (u8)(bFactor + HeartDDInner[2]) & 0xFF; + sBeatingHeartsDDEnv[0] = (u8)(rFactor + HEARTS_PRIM_R) & 0xFF; + sBeatingHeartsDDEnv[1] = (u8)(gFactor + HEARTS_PRIM_G) & 0xFF; + sBeatingHeartsDDEnv[2] = (u8)(bFactor + HEARTS_PRIM_B) & 0xFF; } } diff --git a/soh/src/code/z_message_PAL.c b/soh/src/code/z_message_PAL.c index 33bc9a95e..84cf20e71 100644 --- a/soh/src/code/z_message_PAL.c +++ b/soh/src/code/z_message_PAL.c @@ -272,6 +272,13 @@ void Message_FindMessage(GlobalContext* globalCtx, u16 textId) { const char** languageSegmentTable; Font* font; const char* seg; + u16 bufferId = textId; + // Use the better owl message if better owl is enabled + if (CVar_GetS32("gBetterOwl", 0) != 0 && (bufferId == 0x2066 || bufferId == 0x607B || + bufferId == 0x10C2 || bufferId == 0x10C6 || bufferId == 0x206A)) + { + bufferId = 0x71B3; + } if (gSaveContext.language == LANGUAGE_GER) messageTableEntry = sGerMessageEntryTablePtr; @@ -287,7 +294,7 @@ void Message_FindMessage(GlobalContext* globalCtx, u16 textId) { while (messageTableEntry->textId != 0xFFFF) { font = &globalCtx->msgCtx.font; - if (messageTableEntry->textId == textId) { + if (messageTableEntry->textId == bufferId) { foundSeg = messageTableEntry->segment; font->charTexBuf[0] = messageTableEntry->typePos; @@ -298,14 +305,14 @@ void Message_FindMessage(GlobalContext* globalCtx, u16 textId) { // "Message found!!!" osSyncPrintf(" メッセージが,見つかった!!! = %x " "(data=%x) (data0=%x) (data1=%x) (data2=%x) (data3=%x)\n", - textId, font->msgOffset, font->msgLength, foundSeg, seg, nextSeg); + bufferId, font->msgOffset, font->msgLength, foundSeg, seg, nextSeg); return; } messageTableEntry++; } // "Message not found!!!" - osSyncPrintf(" メッセージが,見つからなかった!!! = %x\n", textId); + osSyncPrintf(" メッセージが,見つからなかった!!! = %x\n", bufferId); font = &globalCtx->msgCtx.font; messageTableEntry = sNesMessageEntryTablePtr; diff --git a/soh/src/code/z_parameter.c b/soh/src/code/z_parameter.c index dcfcdb05b..a9e9e4f9e 100644 --- a/soh/src/code/z_parameter.c +++ b/soh/src/code/z_parameter.c @@ -2884,7 +2884,26 @@ void Interface_DrawItemButtons(GlobalContext* globalCtx) { gDPSetEnvColor(OVERLAY_DISP++, 0, 0, 0, 0); gDPSetCombineLERP(OVERLAY_DISP++, PRIMITIVE, ENVIRONMENT, TEXEL0, ENVIRONMENT, TEXEL0, 0, PRIMITIVE, 0, PRIMITIVE, ENVIRONMENT, TEXEL0, ENVIRONMENT, TEXEL0, 0, PRIMITIVE, 0); - + + //There is probably a more elegant way to do it. + char* doAction = actionsTbl[3]; + char newName[512]; + if (gSaveContext.language != LANGUAGE_ENG) { + size_t length = strlen(doAction); + strcpy(newName, doAction); + if (gSaveContext.language == LANGUAGE_FRA) { + newName[length - 6] = 'F'; + newName[length - 5] = 'R'; + newName[length - 4] = 'A'; + } else if (gSaveContext.language == LANGUAGE_GER) { + newName[length - 6] = 'G'; + newName[length - 5] = 'E'; + newName[length - 4] = 'R'; + } + doAction = newName; + } + memcpy(interfaceCtx->doActionSegment + DO_ACTION_TEX_SIZE * 2, ResourceMgr_LoadTexByName(doAction), DO_ACTION_TEX_SIZE); + gDPLoadTextureBlock_4b(OVERLAY_DISP++, interfaceCtx->doActionSegment + DO_ACTION_TEX_SIZE * 2, G_IM_FMT_IA, DO_ACTION_TEX_WIDTH, DO_ACTION_TEX_HEIGHT, 0, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD); diff --git a/soh/src/code/z_player_lib.c b/soh/src/code/z_player_lib.c index af95f26df..cf7fbd51c 100644 --- a/soh/src/code/z_player_lib.c +++ b/soh/src/code/z_player_lib.c @@ -744,26 +744,38 @@ void func_8008F470(GlobalContext* globalCtx, void** skeleton, Vec3s* jointTable, #else gSPSegment(POLY_OPA_DISP++, 0x09, SEGMENTED_TO_VIRTUAL(sMouthTextures[eyeIndex])); #endif - Color_RGB8 NewColor[3]; - color = &sTunicColors[tunic]; - if (CVar_GetS32("gUseTunicsCol",0) == 1) { - if (tunic == PLAYER_TUNIC_KOKIRI || tunic == PLAYER_TUNIC_GORON || tunic == PLAYER_TUNIC_ZORA) { - NewColor[PLAYER_TUNIC_KOKIRI].r = CVar_GetS32("gTunic_Kokiri_R", sTunicColors[PLAYER_TUNIC_KOKIRI].r); - NewColor[PLAYER_TUNIC_KOKIRI].g = CVar_GetS32("gTunic_Kokiri_G", sTunicColors[PLAYER_TUNIC_KOKIRI].g); - NewColor[PLAYER_TUNIC_KOKIRI].b = CVar_GetS32("gTunic_Kokiri_B", sTunicColors[PLAYER_TUNIC_KOKIRI].b); - NewColor[PLAYER_TUNIC_GORON].r = CVar_GetS32("gTunic_Goron_R", sTunicColors[PLAYER_TUNIC_GORON].r); - NewColor[PLAYER_TUNIC_GORON].g = CVar_GetS32("gTunic_Goron_G", sTunicColors[PLAYER_TUNIC_GORON].g); - NewColor[PLAYER_TUNIC_GORON].b = CVar_GetS32("gTunic_Goron_B", sTunicColors[PLAYER_TUNIC_GORON].b); - NewColor[PLAYER_TUNIC_ZORA].r = CVar_GetS32("gTunic_Zora_R", sTunicColors[PLAYER_TUNIC_ZORA].r); - NewColor[PLAYER_TUNIC_ZORA].g = CVar_GetS32("gTunic_Zora_G", sTunicColors[PLAYER_TUNIC_ZORA].g); - NewColor[PLAYER_TUNIC_ZORA].b = CVar_GetS32("gTunic_Zora_B", sTunicColors[PLAYER_TUNIC_ZORA].b); + + Color_RGB8 sTemp; + Color_RGB8 sOriginalTunicColors[] = { + { 30, 105, 27 }, + { 100, 20, 0 }, + { 0, 60, 100 }, + }; + color = &sTemp; + if (tunic == PLAYER_TUNIC_KOKIRI && CVar_GetS32("gUseTunicsCol",0)) { + color->r = CVar_GetS32("gTunic_Kokiri_R", sTunicColors[PLAYER_TUNIC_KOKIRI].r); + color->g = CVar_GetS32("gTunic_Kokiri_G", sTunicColors[PLAYER_TUNIC_KOKIRI].g); + color->b = CVar_GetS32("gTunic_Kokiri_B", sTunicColors[PLAYER_TUNIC_KOKIRI].b); + } else if (tunic == PLAYER_TUNIC_GORON && CVar_GetS32("gUseTunicsCol",0)) { + color->r = CVar_GetS32("gTunic_Goron_R", sTunicColors[PLAYER_TUNIC_GORON].r); + color->g = CVar_GetS32("gTunic_Goron_G", sTunicColors[PLAYER_TUNIC_GORON].g); + color->b = CVar_GetS32("gTunic_Goron_B", sTunicColors[PLAYER_TUNIC_GORON].b); + } else if (tunic == PLAYER_TUNIC_ZORA && CVar_GetS32("gUseTunicsCol",0)) { + color->r = CVar_GetS32("gTunic_Zora_R", sTunicColors[PLAYER_TUNIC_ZORA].r); + color->g = CVar_GetS32("gTunic_Zora_G", sTunicColors[PLAYER_TUNIC_ZORA].g); + color->b = CVar_GetS32("gTunic_Zora_B", sTunicColors[PLAYER_TUNIC_ZORA].b); + } else if (!CVar_GetS32("gUseTunicsCol",0)){ + if (tunic >= 3) { + color->r = sOriginalTunicColors[0].r; + color->g = sOriginalTunicColors[0].g; + color->b = sOriginalTunicColors[0].b; } else { - NewColor[PLAYER_TUNIC_KOKIRI].r = CVar_GetS32("gTunic_Kokiri_R", sTunicColors[PLAYER_TUNIC_KOKIRI].r); - NewColor[PLAYER_TUNIC_KOKIRI].g = CVar_GetS32("gTunic_Kokiri_G", sTunicColors[PLAYER_TUNIC_KOKIRI].g); - NewColor[PLAYER_TUNIC_KOKIRI].b = CVar_GetS32("gTunic_Kokiri_B", sTunicColors[PLAYER_TUNIC_KOKIRI].b); + color->r = sOriginalTunicColors[tunic].r; + color->g = sOriginalTunicColors[tunic].g; + color->b = sOriginalTunicColors[tunic].b; } - color = NewColor; } + gDPSetEnvColor(POLY_OPA_DISP++, color->r, color->g, color->b, 0); sDListsLodOffset = lod * 2; @@ -1649,26 +1661,118 @@ void func_80091A24(GlobalContext* globalCtx, void* seg04, void* seg06, SkelAnime CLOSE_DISPS(globalCtx->state.gfxCtx, "../z_player_lib.c", 3288); } +uintptr_t SelectedAnim = 0; // Current Animaiton on the menu +s16 EquipedStance; // Link's current mode (Two handed, One handed...) +s16 FrameCountSinceLastAnim = 0; // Time since last animation +s16 MinFrameCount; // Frame to wait before checking if we need to change the animation + void func_8009214C(GlobalContext* globalCtx, u8* segment, SkelAnime* skelAnime, Vec3f* pos, Vec3s* rot, f32 scale, s32 sword, s32 tunic, s32 shield, s32 boots) { + Input* p1Input = &globalCtx->state.input[0]; Vec3f eye = { 0.0f, 0.0f, -400.0f }; Vec3f at = { 0.0f, 0.0f, 0.0f }; Vec3s* destTable; Vec3s* srcTable; s32 i; + bool canswitchrnd = false; + s16 SelectedMode = CVar_GetS32("gPauseLiveLink", 1); + MinFrameCount = CVar_GetS32("gMinFrameCount", 200); gSegments[4] = VIRTUAL_TO_PHYSICAL(segment + 0x3800); gSegments[6] = VIRTUAL_TO_PHYSICAL(segment + 0x8800); - if (CVar_GetS32("gPauseLiveLink", 0) || CVar_GetS32("gPauseTriforce", 0)) { - uintptr_t anim = gPlayerAnim_003238; // idle + uintptr_t* PauseMenuAnimSet[15][4] = { + { 0, 0, 0, 0 }, // 0 = none + // IDLE // Two Handed // No shield // Kid Hylian Shield + { gPlayerAnim_003238, gPlayerAnim_002BE0, gPlayerAnim_003240, gPlayerAnim_003240 }, // Idle + { gPlayerAnim_003200, gPlayerAnim_003200, gPlayerAnim_003200, gPlayerAnim_003200 }, // Idle look around + { gPlayerAnim_0033E0, gPlayerAnim_0033E0, gPlayerAnim_0033E0, gPlayerAnim_0033E0 }, // Idle Belt + { gPlayerAnim_003418, gPlayerAnim_003418, gPlayerAnim_003418, gPlayerAnim_003418 }, // Idle shield adjust + { gPlayerAnim_003420, gPlayerAnim_003428, gPlayerAnim_003420, gPlayerAnim_003420 }, // Idle test sword + { gPlayerAnim_0033F0, gPlayerAnim_0033F0, gPlayerAnim_0033F0, gPlayerAnim_0033F0 }, // Idle yawn + { gPlayerAnim_0025D0, gPlayerAnim_002BD0, gPlayerAnim_0025D0, gPlayerAnim_0025D0 }, // Battle Stance + { gPlayerAnim_003290, gPlayerAnim_002BF8, gPlayerAnim_003290, gPlayerAnim_003290 }, // Walking (No shield) + { gPlayerAnim_003268, gPlayerAnim_002BF8, gPlayerAnim_003268, gPlayerAnim_003268 }, // Walking (Holding shield) + { gPlayerAnim_003138, gPlayerAnim_002B40, gPlayerAnim_003138, gPlayerAnim_003138 }, // Running (No shield) + { gPlayerAnim_003140, gPlayerAnim_002B40, gPlayerAnim_003140, gPlayerAnim_003140 }, // Running (Holding shield) + { gPlayerAnim_0031A8, gPlayerAnim_0031A8, gPlayerAnim_0031A8, gPlayerAnim_0031A8 }, // Hand on hip + { gPlayerAnim_002AF0, gPlayerAnim_002928, gPlayerAnim_002AF0, gPlayerAnim_002AF0 }, // Spin Charge + { gPlayerAnim_002820, gPlayerAnim_002820, gPlayerAnim_002820, gPlayerAnim_002820 }, // Look at hand + }; + s16 AnimArraySize = ARRAY_COUNT(PauseMenuAnimSet); - if (CUR_EQUIP_VALUE(EQUIP_SWORD) >= 3) - anim = gPlayerAnim_002BE0; // Two Handed Anim - else if (CUR_EQUIP_VALUE(EQUIP_SHIELD) == 0) - anim = gPlayerAnim_003240; - else if (CUR_EQUIP_VALUE(EQUIP_SHIELD) == 2 && LINK_AGE_IN_YEARS == YEARS_CHILD) - anim = gPlayerAnim_003240; + if (CVar_GetS32("gPauseLiveLink", !0) || CVar_GetS32("gPauseTriforce", 0)) { + uintptr_t anim = 0; // Initialise anim + + if (CUR_EQUIP_VALUE(EQUIP_SWORD) >= 3) { + EquipedStance = 1; + } else if (CUR_EQUIP_VALUE(EQUIP_SHIELD) == 0) { + EquipedStance = 2; + } else if (CUR_EQUIP_VALUE(EQUIP_SHIELD) == 2 && LINK_AGE_IN_YEARS == YEARS_CHILD) { + EquipedStance = 3; + } else { + // Link is idle so revert to 0 + EquipedStance = 0; + } + + if (SelectedMode == 16) { + // Apply Random function + s16 SwitchAtFrame = 0; + s16 CurAnimDuration = 0; + if (FrameCountSinceLastAnim == 0) { + // When opening Kaleido this will be passed one time + SelectedAnim = rand() % (AnimArraySize - 0); + if (SelectedAnim == 0) { + // prevent loading 0 that would result to a crash. + SelectedAnim = 1; + } + } else if (FrameCountSinceLastAnim >= 1) { + SwitchAtFrame = Animation_GetLastFrame(PauseMenuAnimSet[SelectedAnim][EquipedStance]); + CurAnimDuration = Animation_GetLastFrame(PauseMenuAnimSet[SelectedAnim][EquipedStance]); + if (SwitchAtFrame < MinFrameCount) { + // Animation frame count is lower than minimal wait time then we wait for another round. + // This will be looped to always add current animation time if that still lower than minimum time + while (SwitchAtFrame < MinFrameCount) { + SwitchAtFrame = SwitchAtFrame + CurAnimDuration; + } + } else if (CurAnimDuration >= MinFrameCount) { + // Since we have more (or same) animation time than min duration we set the wait time to animation + // time. + SwitchAtFrame = CurAnimDuration; + } + if (FrameCountSinceLastAnim >= SwitchAtFrame) { + SelectedAnim = rand() % (AnimArraySize - 0); + if (SelectedAnim == 0) { + // prevent loading 0 that would result to a crash. + SelectedAnim = 1; + } + FrameCountSinceLastAnim = 1; + } + anim = PauseMenuAnimSet[SelectedAnim][EquipedStance]; + } + FrameCountSinceLastAnim++; + } else if (SelectedMode == 15) { + // When opening Kaleido this will be passed one time + if (FrameCountSinceLastAnim < 1) { + SelectedAnim = rand() % (AnimArraySize - 0); + FrameCountSinceLastAnim++; + if (SelectedAnim == 0) { + // prevent loading 0 that would result to a crash. + SelectedAnim = 1; + } + FrameCountSinceLastAnim = 1; + } + if (CHECK_BTN_ALL(p1Input->press.button, BTN_B) || CHECK_BTN_ALL(p1Input->press.button, BTN_START)) { + FrameCountSinceLastAnim = 0; + } + anim = PauseMenuAnimSet[SelectedAnim][EquipedStance]; + } else if (SelectedMode < 16) { + // Not random so we place our CVar as SelectedAnim + SelectedAnim = SelectedMode; + anim = PauseMenuAnimSet[SelectedAnim][EquipedStance]; + } + + anim = PauseMenuAnimSet[SelectedAnim][EquipedStance]; //anim = gPlayerAnim_003428; // Use for biggoron sword? diff --git a/soh/src/code/z_sram.c b/soh/src/code/z_sram.c index a774636f7..16835d7ad 100644 --- a/soh/src/code/z_sram.c +++ b/soh/src/code/z_sram.c @@ -836,7 +836,7 @@ void Sram_InitSram(GameState* gameState, SramContext* sramCtx) { for (i = 0; i < ARRAY_COUNTU(sZeldaMagic) - 3; i++) { if (sZeldaMagic[i + SRAM_HEADER_MAGIC] != sramCtx->readBuff[i + SRAM_HEADER_MAGIC]) { osSyncPrintf("SRAM破壊!!!!!!\n"); // "SRAM destruction! ! ! ! ! !" - gSaveContext.language = sramCtx->readBuff[SRAM_HEADER_LANGUAGE]; + gSaveContext.language = CVar_GetS32("gLanguages", 0); memcpy(sramCtx->readBuff, sZeldaMagic, sizeof(sZeldaMagic)); sramCtx->readBuff[SRAM_HEADER_LANGUAGE] = gSaveContext.language; Sram_WriteSramHeader(sramCtx); @@ -845,7 +845,7 @@ void Sram_InitSram(GameState* gameState, SramContext* sramCtx) { gSaveContext.audioSetting = sramCtx->readBuff[SRAM_HEADER_SOUND] & 3; gSaveContext.zTargetSetting = sramCtx->readBuff[SRAM_HEADER_ZTARGET] & 1; - gSaveContext.language = sramCtx->readBuff[SRAM_HEADER_LANGUAGE]; + gSaveContext.language = CVar_GetS32("gLanguages", 0); if (gSaveContext.language >= LANGUAGE_MAX) { gSaveContext.language = LANGUAGE_ENG; diff --git a/soh/src/overlays/actors/ovl_En_Owl/z_en_owl.c b/soh/src/overlays/actors/ovl_En_Owl/z_en_owl.c index 054f00b4d..aef838499 100644 --- a/soh/src/overlays/actors/ovl_En_Owl/z_en_owl.c +++ b/soh/src/overlays/actors/ovl_En_Owl/z_en_owl.c @@ -366,7 +366,9 @@ void func_80ACA7E0(EnOwl* this, GlobalContext* globalCtx) { void EnOwl_ConfirmKokiriMessage(EnOwl* this, GlobalContext* globalCtx) { if (Message_GetState(&globalCtx->msgCtx) == TEXT_STATE_CHOICE && Message_ShouldAdvance(globalCtx)) { - switch (globalCtx->msgCtx.choiceIndex) { + // swap the order of the responses if better owl is enabled + uint8_t index = CVar_GetS32("gBetterOwl", 0) == 0 ? globalCtx->msgCtx.choiceIndex : (1 - globalCtx->msgCtx.choiceIndex); + switch (index) { case OWL_REPEAT: Message_ContinueTextbox(globalCtx, 0x2065); break; @@ -393,7 +395,9 @@ void EnOwl_WaitOutsideKokiri(EnOwl* this, GlobalContext* globalCtx) { void func_80ACA998(EnOwl* this, GlobalContext* globalCtx) { if (Message_GetState(&globalCtx->msgCtx) == TEXT_STATE_CHOICE && Message_ShouldAdvance(globalCtx)) { - switch (globalCtx->msgCtx.choiceIndex) { + // swap the order of the responses if better owl is enabled + uint8_t index = CVar_GetS32("gBetterOwl", 0) == 0 ? globalCtx->msgCtx.choiceIndex : (1 - globalCtx->msgCtx.choiceIndex); + switch (index) { case OWL_REPEAT: Message_ContinueTextbox(globalCtx, 0x2069); this->actionFunc = func_80ACAA54; @@ -437,7 +441,9 @@ void EnOwl_WaitHyruleCastle(EnOwl* this, GlobalContext* globalCtx) { void func_80ACAB88(EnOwl* this, GlobalContext* globalCtx) { if (Message_GetState(&globalCtx->msgCtx) == TEXT_STATE_CHOICE && Message_ShouldAdvance(globalCtx)) { - switch (globalCtx->msgCtx.choiceIndex) { + // swap the order of the responses if better owl is enabled + uint8_t index = CVar_GetS32("gBetterOwl", 0) == 0 ? globalCtx->msgCtx.choiceIndex : (1 - globalCtx->msgCtx.choiceIndex); + switch (index) { case OWL_REPEAT: // obtained zelda's letter if (gSaveContext.eventChkInf[4] & 1) { @@ -478,7 +484,9 @@ void EnOwl_WaitKakariko(EnOwl* this, GlobalContext* globalCtx) { void func_80ACAD34(EnOwl* this, GlobalContext* globalCtx) { if (Message_GetState(&globalCtx->msgCtx) == TEXT_STATE_CHOICE && Message_ShouldAdvance(globalCtx)) { - switch (globalCtx->msgCtx.choiceIndex) { + // swap the order of the responses if better owl is enabled + uint8_t index = CVar_GetS32("gBetterOwl", 0) == 0 ? globalCtx->msgCtx.choiceIndex : (1 - globalCtx->msgCtx.choiceIndex); + switch (index) { case OWL_REPEAT: Message_ContinueTextbox(globalCtx, 0x206F); this->actionFunc = func_80ACADF0; @@ -514,7 +522,9 @@ void EnOwl_WaitGerudo(EnOwl* this, GlobalContext* globalCtx) { void func_80ACAEB8(EnOwl* this, GlobalContext* globalCtx) { if (Message_GetState(&globalCtx->msgCtx) == TEXT_STATE_CHOICE && Message_ShouldAdvance(globalCtx)) { - switch (globalCtx->msgCtx.choiceIndex) { + // swap the order of the responses if better owl is enabled + uint8_t index = CVar_GetS32("gBetterOwl", 0) == 0 ? globalCtx->msgCtx.choiceIndex : (1 - globalCtx->msgCtx.choiceIndex); + switch (index) { case OWL_REPEAT: Message_ContinueTextbox(globalCtx, 0x2071); this->actionFunc = func_80ACAF74; @@ -634,7 +644,9 @@ void EnOwl_WaitDeathMountainShortcut(EnOwl* this, GlobalContext* globalCtx) { void func_80ACB344(EnOwl* this, GlobalContext* globalCtx) { if (Message_GetState(&globalCtx->msgCtx) == TEXT_STATE_CHOICE && Message_ShouldAdvance(globalCtx)) { - switch (globalCtx->msgCtx.choiceIndex) { + // swap the order of the responses if better owl is enabled + uint8_t index = CVar_GetS32("gBetterOwl", 0) == 0 ? globalCtx->msgCtx.choiceIndex : (1 - globalCtx->msgCtx.choiceIndex); + switch (index) { case OWL_REPEAT: Message_ContinueTextbox(globalCtx, 0x607A); break; @@ -657,7 +669,9 @@ void func_80ACB3E0(EnOwl* this, GlobalContext* globalCtx) { void func_80ACB440(EnOwl* this, GlobalContext* globalCtx) { if (Message_GetState(&globalCtx->msgCtx) == TEXT_STATE_CHOICE && Message_ShouldAdvance(globalCtx)) { - switch (globalCtx->msgCtx.choiceIndex) { + // swap the order of the responses if better owl is enabled + uint8_t index = CVar_GetS32("gBetterOwl", 0) == 0 ? globalCtx->msgCtx.choiceIndex : (1 - globalCtx->msgCtx.choiceIndex); + switch (index) { case OWL_REPEAT: Message_ContinueTextbox(globalCtx, 0x10C1); this->actionFunc = func_80ACB4FC; @@ -692,7 +706,9 @@ void EnOwl_WaitLWPreSaria(EnOwl* this, GlobalContext* globalCtx) { void func_80ACB5C4(EnOwl* this, GlobalContext* globalCtx) { if (Message_GetState(&globalCtx->msgCtx) == TEXT_STATE_CHOICE && Message_ShouldAdvance(globalCtx)) { - switch (globalCtx->msgCtx.choiceIndex) { + // swap the order of the responses if better owl is enabled + uint8_t index = CVar_GetS32("gBetterOwl", 0) == 0 ? globalCtx->msgCtx.choiceIndex : (1 - globalCtx->msgCtx.choiceIndex); + switch (index) { case OWL_REPEAT: Message_ContinueTextbox(globalCtx, 0x10C5); this->actionFunc = func_80ACB680; diff --git a/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c b/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c index 570eedd1f..03a93e949 100644 --- a/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c +++ b/soh/src/overlays/gamestates/ovl_file_choose/z_file_choose.c @@ -5,6 +5,8 @@ #include "textures/title_static/title_static.h" #include "textures/parameter_static/parameter_static.h" +#include "soh/frame_interpolation.h" + static s16 sUnused = 106; static s16 sScreenFillAlpha = 255; @@ -1136,6 +1138,8 @@ void FileChoose_ConfigModeDraw(GameState* thisx) { FileChoose_SetWindowVtx(&this->state); FileChoose_SetWindowContentVtx(&this->state); + FrameInterpolation_RecordOpenChild(this, this->configMode); + if ((this->configMode != CM_NAME_ENTRY) && (this->configMode != CM_START_NAME_ENTRY)) { gDPPipeSync(POLY_OPA_DISP++); gDPSetCombineMode(POLY_OPA_DISP++, G_CC_MODULATEIA_PRIM, G_CC_MODULATEIA_PRIM); @@ -1227,6 +1231,8 @@ void FileChoose_ConfigModeDraw(GameState* thisx) { gDPPipeSync(POLY_OPA_DISP++); FileChoose_SetView(this, 0.0f, 0.0f, 64.0f); + FrameInterpolation_RecordCloseChild(); + CLOSE_DISPS(this->state.gfxCtx, "../z_file_choose.c", 2352); } @@ -1669,7 +1675,9 @@ void FileChoose_Main(GameState* thisx) { FileChoose_PulsateCursor(&this->state); gFileSelectUpdateFuncs[this->menuMode](&this->state); + FrameInterpolation_StartRecord(); gFileSelectDrawFuncs[this->menuMode](&this->state); + FrameInterpolation_StopRecord(); // do not draw controls text in the options menu if ((this->configMode <= CM_NAME_ENTRY_TO_MAIN) || (this->configMode >= CM_UNUSED_DELAY)) { diff --git a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_equipment.c b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_equipment.c index 719eaa3f3..125981bff 100644 --- a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_equipment.c +++ b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_equipment.c @@ -89,11 +89,17 @@ void KaleidoScope_DrawEquipmentImage(GlobalContext* globalCtx, void* source, u32 CLOSE_DISPS(globalCtx->state.gfxCtx, "../z_kaleido_equipment.c", 122); } +Vec3s link_kaleido_rot = { 0, 32300, 0 }; // Default rotation link face us. + void KaleidoScope_DrawPlayerWork(GlobalContext* globalCtx) { PauseContext* pauseCtx = &globalCtx->pauseCtx; Vec3f pos; - Vec3s rot; + //Vec3s rot; // Removed for not having it use din the function f32 scale; + Input* input = &globalCtx->state.input[0]; + s16 RotationSpeed = 150 * CVar_GetS32("gPauseLiveLinkRotationSpeed", 0); + bool AllowCRotation = (CVar_GetS32("gPauseLiveLinkRotation", 0) == 2) ? true : false; + bool AllowDPadRotation = (CVar_GetS32("gPauseLiveLinkRotation", 0) == 1) ? true : false; if (LINK_AGE_IN_YEARS == YEARS_CHILD) { pos.x = 2.0f; @@ -112,11 +118,29 @@ void KaleidoScope_DrawPlayerWork(GlobalContext* globalCtx) { scale = 0.047f; } - rot.y = 32300; - rot.x = rot.z = 0; + link_kaleido_rot.x = link_kaleido_rot.z = 0; + + if (AllowDPadRotation && CHECK_BTN_ALL(input->cur.button, BTN_DLEFT) || + AllowCRotation && CHECK_BTN_ALL(input->cur.button, BTN_CLEFT)) { + link_kaleido_rot.y = link_kaleido_rot.y - RotationSpeed; + } else if (AllowDPadRotation && CHECK_BTN_ALL(input->cur.button, BTN_DRIGHT) || + AllowCRotation && CHECK_BTN_ALL(input->cur.button, BTN_CRIGHT)) { + link_kaleido_rot.y = link_kaleido_rot.y + RotationSpeed; + } + + if (AllowDPadRotation && CHECK_BTN_ALL(input->press.button, BTN_DUP) || + AllowDPadRotation && CHECK_BTN_ALL(input->press.button, BTN_DDOWN)) { + link_kaleido_rot.y = 32300; + } else if (AllowCRotation && CHECK_BTN_ALL(input->press.button, BTN_CUP) || + AllowCRotation && CHECK_BTN_ALL(input->press.button, BTN_CDOWN)) { + link_kaleido_rot.y = 32300; + } + + link_kaleido_rot.x = 0; + extern int fbTest; gsSPSetFB(globalCtx->state.gfxCtx->polyOpa.p++, fbTest); - func_8009214C(globalCtx, pauseCtx->playerSegment, &pauseCtx->playerSkelAnime, &pos, &rot, scale, + func_8009214C(globalCtx, pauseCtx->playerSegment, &pauseCtx->playerSkelAnime, &pos, &link_kaleido_rot, scale, CUR_EQUIP_VALUE(EQUIP_SWORD), CUR_EQUIP_VALUE(EQUIP_TUNIC) - 1, CUR_EQUIP_VALUE(EQUIP_SHIELD), CUR_EQUIP_VALUE(EQUIP_BOOTS) - 1); gsSPResetFB(globalCtx->state.gfxCtx->polyOpa.p++); diff --git a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope_PAL.c b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope_PAL.c index d9f038059..43ae671e6 100644 --- a/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope_PAL.c +++ b/soh/src/overlays/misc/ovl_kaleido_scope/z_kaleido_scope_PAL.c @@ -976,14 +976,23 @@ void KaleidoScope_DrawCursor(GlobalContext* globalCtx, u16 pageIndex) { temp = pauseCtx->unk_1E4; if (CVar_GetS32("gHudColors", 1) == 0) { + sCursorColors[1][0] = 255; + sCursorColors[1][1] = 255; + sCursorColors[1][2] = 0; sCursorColors[2][0] = 0; sCursorColors[2][1] = 50; sCursorColors[2][2] = 255; } else if (CVar_GetS32("gHudColors", 1) == 1) { + sCursorColors[1][0] = 255; + sCursorColors[1][1] = 255; + sCursorColors[1][2] = 0; sCursorColors[2][0] = 0; sCursorColors[2][1] = 255; sCursorColors[2][2] = 50; } else if (CVar_GetS32("gHudColors", 1) == 2) { + sCursorColors[1][0] = CVar_GetS32("gCCCBtnPrimR", 255); + sCursorColors[1][1] = CVar_GetS32("gCCCBtnPrimG", 255); + sCursorColors[1][2] = CVar_GetS32("gCCCBtnPrimB", 0); sCursorColors[2][0] = CVar_GetS32("gCCABtnPrimR", 0); sCursorColors[2][1] = CVar_GetS32("gCCABtnPrimG", 255); sCursorColors[2][2] = CVar_GetS32("gCCABtnPrimB", 50); @@ -1060,6 +1069,12 @@ void KaleidoScope_DrawPages(GlobalContext* globalCtx, GraphicsContext* gfxCtx) { { 0, 0, 0 }, { 255, 255, 0 }, { 0, 255, 50 }, { 0, 0, 0 }, { 0, 0, 0 }, { 0, 255, 50 }, }; if (CVar_GetS32("gHudColors", 1) == 0) { + D_8082ACF4[4][0] = 255; + D_8082ACF4[4][1] = 255; + D_8082ACF4[4][2] = 0; + D_8082ACF4[7][0] = 255; + D_8082ACF4[7][1] = 255; + D_8082ACF4[7][2] = 0; D_8082ACF4[8][0] = 0; D_8082ACF4[8][1] = 50; D_8082ACF4[8][2] = 255; @@ -1067,6 +1082,12 @@ void KaleidoScope_DrawPages(GlobalContext* globalCtx, GraphicsContext* gfxCtx) { D_8082ACF4[11][1] = 50; D_8082ACF4[11][2] = 255; } else if (CVar_GetS32("gHudColors", 1) == 1) { + D_8082ACF4[4][0] = 255; + D_8082ACF4[4][1] = 255; + D_8082ACF4[4][2] = 0; + D_8082ACF4[7][0] = 255; + D_8082ACF4[7][1] = 255; + D_8082ACF4[7][2] = 0; D_8082ACF4[8][0] = 0; D_8082ACF4[8][1] = 255; D_8082ACF4[8][2] = 50; @@ -1074,6 +1095,12 @@ void KaleidoScope_DrawPages(GlobalContext* globalCtx, GraphicsContext* gfxCtx) { D_8082ACF4[11][1] = 255; D_8082ACF4[11][2] = 50; } else if (CVar_GetS32("gHudColors", 1) == 2) { + D_8082ACF4[4][0] = CVar_GetS32("gCCCBtnPrimR", 255); + D_8082ACF4[4][1] = CVar_GetS32("gCCCBtnPrimG", 255); + D_8082ACF4[4][2] = CVar_GetS32("gCCCBtnPrimB", 0); + D_8082ACF4[7][0] = CVar_GetS32("gCCCBtnPrimR", 255); + D_8082ACF4[7][1] = CVar_GetS32("gCCCBtnPrimG", 255); + D_8082ACF4[7][2] = CVar_GetS32("gCCCBtnPrimB", 0); D_8082ACF4[8][0] = CVar_GetS32("gCCABtnPrimR", 0); D_8082ACF4[8][1] = CVar_GetS32("gCCABtnPrimG", 255); D_8082ACF4[8][2] = CVar_GetS32("gCCABtnPrimB", 50);