From 083ceb44239b9a26648e0059ca9ce2ad551424a0 Mon Sep 17 00:00:00 2001 From: David Chavez Date: Wed, 28 Sep 2022 04:41:17 +0200 Subject: [PATCH] Feature: Crowd Control Integration (#1568) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Start effects * Disable input to game when typing in console * Add gravity support * noUI placeholder * Add rest of effects to console * Remove z_play code * Add rupee modification * Add OneHit KO (#27) * few fix and paper Link * Better method and now use the reset flag * Revert "Better method and now use the reset flag" This reverts commit 2aafcc1df29686940945ebab87372360a06a3cf7. * Revert "few fix and paper Link" This reverts commit 65e76dcfeec3924ca9977c931703b4c3232a9733. * Paper Link & few fixes (#28) * Implement pacifist mode (#30) * Implement cucco storm (#31) * Add no UI functionality (#32) * Enable CrowdControl on windows (#33) * Use std::format and implement wallmaster * Implement defense modifier * Implement no_z and clean up * Implement reverse controls * Some fixes while testing CC connection * Implement speed modifier and fix defese modifier * Fail magic effects if magic is not acquired * Fix queue system * Implement rainstorm * Some cleanup * Use IS_ZERO to handle very low near zero values * Split some effects * Fix emptying magic * Don’t run cucco on pre-rendered backgrounds * Use correct method for updating ruppees * Fix decreasing speed * Remove old SDL stuff * Remove old fixes * Enable Crowd Control for both debug and release * Add missing returns * Cleanup event firing * Further clean up on event firing * Fix some bugs * CC fixes and enemy spawning (#35) * Fix icetraps * Fix title screen * Fix pause screen * Fix death screen timer & Code cleanup * Fix timer during textboxes * Code cleanup * Add: Multiple enemy spawning * More enemies + more code cleanup (#36) * Enums for returning effect states * Add more enemies * Update CrowdControl.cpp * Remove enums from enemies * Fix up flow for events (#37) # Conflicts: # soh/soh/Enhancements/crowd-control/CrowdControl.cpp * Fix spawn position of likelike * CC temp enemy fixes (#38) * Check for pause in pacifist and allow button presses (#39) * Fix Pacifist mode (#41) * First attempt pacifier fix * Real fix for pacifist mode * Comment * Remove cutscene and long delay from cucco_storm (#40) * Some PR Fixes * Use standard types * Handle JSON parsing error and free memory * Add CC configuration file * Add: Giving deku shield option. Fix: Giant Lonk (#42) * Small stalfos fix (#43) * Syntax Improvements (#44) * Revert bools to uint32_t * Add comment about bools * Fix cucco storm, fix empty heart (#45) * Protect commands vector with mutex * prefix effects with chaosEffect (#46) Co-authored-by: briaguya Co-authored-by: Baoulettes Co-authored-by: aMannus Co-authored-by: briaguya <70942617+briaguya-ai@users.noreply.github.com> Co-authored-by: briaguya --- CMakeLists.txt | 2 +- libultraship/libultraship/Window.cpp | 4 +- soh/CMakeLists.txt | 26 +- soh/include/functions.h | 4 + .../crowd-control/CrowdControl.cpp | 538 +++++++++++++++ .../Enhancements/crowd-control/CrowdControl.h | 91 +++ soh/soh/Enhancements/crowd-control/soh.cs | 90 +++ soh/soh/Enhancements/debugconsole.cpp | 638 ++++++++++++++++-- soh/soh/Enhancements/debugconsole.h | 28 + soh/soh/GameMenuBar.cpp | 2 + soh/soh/OTRGlobals.cpp | 10 + soh/src/code/padmgr.c | 21 + soh/src/code/z_parameter.c | 35 +- soh/src/code/z_play.c | 6 + soh/src/code/z_player_lib.c | 7 + soh/src/overlays/actors/ovl_En_Niw/z_en_niw.c | 8 +- soh/src/overlays/actors/ovl_En_Niw/z_en_niw.h | 13 + .../actors/ovl_player_actor/z_player.c | 55 +- 18 files changed, 1531 insertions(+), 47 deletions(-) create mode 100644 soh/soh/Enhancements/crowd-control/CrowdControl.cpp create mode 100644 soh/soh/Enhancements/crowd-control/CrowdControl.h create mode 100644 soh/soh/Enhancements/crowd-control/soh.cs diff --git a/CMakeLists.txt b/CMakeLists.txt index 3994a2499..536bb4d25 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,7 @@ set(VCPKG_TRIPLET x64-windows-static) set(VCPKG_TARGET_TRIPLET x64-windows-static) vcpkg_bootstrap() -vcpkg_install_packages(zlib bzip2 libpng SDL2 GLEW glfw3) +vcpkg_install_packages(zlib bzip2 libpng SDL2 SDL2-net GLEW glfw3) endif() ################################################################################ diff --git a/libultraship/libultraship/Window.cpp b/libultraship/libultraship/Window.cpp index 1abed96ee..539ed73c9 100644 --- a/libultraship/libultraship/Window.cpp +++ b/libultraship/libultraship/Window.cpp @@ -86,8 +86,8 @@ extern "C" { pad->err_no = 0; pad->gyro_x = 0; pad->gyro_y = 0; - - if (SohImGui::GetInputEditor()->IsOpened()) return; + + if (SohImGui::GetInputEditor()->IsOpened()) return; Ship::Window::GetInstance()->GetControlDeck()->WriteToPad(pad); Ship::ExecuteHooks(pad); diff --git a/soh/CMakeLists.txt b/soh/CMakeLists.txt index aa6e3f60b..92352e981 100644 --- a/soh/CMakeLists.txt +++ b/soh/CMakeLists.txt @@ -243,6 +243,13 @@ set(Header_Files__soh__Enhancements__item_tables source_group("Header Files\\soh\\Enhancements\\item-tables" FILES ${Header_Files__soh__Enhancements__item_tables}) +if (CMAKE_SYSTEM_NAME STREQUAL "Windows") +set(Header_Files__soh__Enhancements__crowd_control + "soh/Enhancements/crowd-control/CrowdControl.h" +) +source_group("Header Files\\soh\\Enhancements\\crowd-control" FILES ${Header_Files__soh__Enhancements__crowd_control}) +endif() + set(Source_Files__soh "soh/GbiWrap.cpp" "soh/OTRAudio.h" @@ -378,6 +385,13 @@ set(Source_Files__soh__Enhancements__item_tables source_group("Source Files\\soh\\Enhancements\\item-tables" FILES ${Source_Files__soh__Enhancements__item_tables}) +if (CMAKE_SYSTEM_NAME STREQUAL "Windows") +set(Source_Files__soh__Enhancements__crowd_control + "soh/Enhancements/crowd-control/CrowdControl.cpp" +) +source_group("Source Files\\soh\\Enhancements\\crowd-control" FILES ${Source_Files__soh__Enhancements__crowd_control}) +endif() + set(Source_Files__src__boot "src/boot/assert.c" "src/boot/boot_main.c" @@ -1588,6 +1602,7 @@ set(ALL_FILES ${Header_Files__soh__Enhancements__randomizer__3drando} ${Header_Files__soh__Enhancements__item_tables} ${Header_Files__soh__Enhancements__custom_message} + ${Header_Files__soh__Enhancements__crowd_control} ${Source_Files__soh} ${Source_Files__soh__Enhancements} ${Source_Files__soh__Enhancements__controls} @@ -1599,6 +1614,7 @@ set(ALL_FILES ${Source_Files__soh__Enhancements__randomizer__3drando__location_access} ${Source_Files__soh__Enhancements__item_tables} ${Source_Files__soh__Enhancements__custom_message} + ${Source_Files__soh__Enhancements__crowd_control} ${Source_Files__src__boot} ${Source_Files__src__buffers} ${Source_Files__src__code} @@ -1680,6 +1696,11 @@ endif() find_package(SDL2) set(SDL2-INCLUDE ${SDL2_INCLUDE_DIRS}) +if (CMAKE_SYSTEM_NAME STREQUAL "Windows") + find_package(SDL2-net) + set(SDL2-NET-INCLUDE ${SDL_NET_INCLUDE_DIRS}) +endif() + target_include_directories(${PROJECT_NAME} PRIVATE assets ${CMAKE_CURRENT_SOURCE_DIR}/include/ ${CMAKE_CURRENT_SOURCE_DIR}/src/ @@ -1690,6 +1711,7 @@ target_include_directories(${PROJECT_NAME} PRIVATE assets ${CMAKE_CURRENT_SOURCE_DIR}/../libultraship/libultraship/Lib/Fast3D/U64/PR ${CMAKE_CURRENT_SOURCE_DIR}/../ZAPDTR/ZAPDUtils ${SDL2-INCLUDE} + ${SDL2-NET-INCLUDE} ${CMAKE_CURRENT_SOURCE_DIR}/assets/ . ) @@ -1700,12 +1722,13 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Windows") "$<$:" "_DEBUG;" "_CRT_SECURE_NO_WARNINGS;" - "ENABLE_DX11" + "ENABLE_DX11;" ">" "$<$:" "NDEBUG" ">" "INCLUDE_GAME_PRINTF;" + "ENABLE_CROWD_CONTROL;" "UNICODE;" "_UNICODE" STORMLIB_NO_AUTO_LINK @@ -1941,6 +1964,7 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Windows") "glu32;" "SDL2::SDL2;" "SDL2::SDL2main;" + "SDL2::SDL2_net;" "glfw;" "winmm;" "imm32;" diff --git a/soh/include/functions.h b/soh/include/functions.h index 0e751530d..a1eaa3286 100644 --- a/soh/include/functions.h +++ b/soh/include/functions.h @@ -1076,6 +1076,8 @@ void Interface_SetDoAction(GlobalContext* globalCtx, u16 action); void Interface_SetNaviCall(GlobalContext* globalCtx, u16 naviCallState); void Interface_LoadActionLabelB(GlobalContext* globalCtx, u16 action); s32 Health_ChangeBy(GlobalContext* globalCtx, s16 healthChange); +void Health_GiveHearts(s16 hearts); +void Health_RemoveHearts(s16 hearts); void Rupees_ChangeBy(s16 rupeeChange); void Inventory_ChangeAmmo(s16 item, s16 ammoChange); void Magic_Fill(GlobalContext* globalCtx); @@ -1091,6 +1093,7 @@ f32 Path_OrientAndGetDistSq(Actor* actor, Path* path, s16 waypoint, s16* yaw); void Path_CopyLastPoint(Path* path, Vec3f* dest); void FrameAdvance_Init(FrameAdvanceContext* frameAdvCtx); s32 FrameAdvance_Update(FrameAdvanceContext* frameAdvCtx, Input* input); +u8 PlayerGrounded(Player* player); void Player_SetBootData(GlobalContext* globalCtx, Player* player); s32 Player_InBlockingCsMode(GlobalContext* globalCtx, Player* player); s32 Player_InCsMode(GlobalContext* globalCtx); @@ -1103,6 +1106,7 @@ void Player_SetModelGroup(Player* player, s32 modelGroup); void func_8008EC70(Player* player); void Player_SetEquipmentData(GlobalContext* globalCtx, Player* player); void Player_UpdateBottleHeld(GlobalContext* globalCtx, Player* player, s32 item, s32 actionParam); +void func_80837C0C(GlobalContext* globalCtx, Player* this, s32 arg2, f32 arg3, f32 arg4, s16 arg5, s32 arg6); void func_8008EDF0(Player* player); void func_8008EE08(Player* player); void func_8008EEAC(GlobalContext* globalCtx, Actor* actor); diff --git a/soh/soh/Enhancements/crowd-control/CrowdControl.cpp b/soh/soh/Enhancements/crowd-control/CrowdControl.cpp new file mode 100644 index 000000000..4ffc61fdf --- /dev/null +++ b/soh/soh/Enhancements/crowd-control/CrowdControl.cpp @@ -0,0 +1,538 @@ +#ifdef ENABLE_CROWD_CONTROL + +#include "CrowdControl.h" +#include +#include +#include +#include +#include + +extern "C" { +#include +#include "variables.h" +#include "functions.h" +#include "macros.h" +extern GlobalContext* gGlobalCtx; +} + +#include "../debugconsole.h" + +#define CMD_EXECUTE SohImGui::GetConsole()->Dispatch + +void CrowdControl::InitCrowdControl() { + SDLNet_Init(); + + if (SDLNet_ResolveHost(&ip, "127.0.0.1", 43384) == -1) { + printf("SDLNet_ResolveHost: %s\n", SDLNet_GetError()); + } + + ccThreadReceive = std::thread(&CrowdControl::ReceiveFromCrowdControl, this); +} + +void CrowdControl::RunCrowdControl(CCPacket* packet) { + bool paused = false; + EffectResult lastResult = EffectResult::NotReady; + bool isTimed = packet->timeRemaining > 0; + + while (connected) { + EffectResult effectResult = ExecuteEffect(packet->effectType.c_str(), packet->effectValue, 0); + if (effectResult == EffectResult::Success) { + // If we have a success after being previously paused, we fire the Resumed event + if (paused && packet->timeRemaining > 0) { + paused = false; + nlohmann::json dataSend; + dataSend["id"] = packet->packetId; + dataSend["type"] = 0; + dataSend["timeRemaining"] = packet->timeRemaining; + dataSend["status"] = EffectResult::Resumed; + + std::string jsonResponse = dataSend.dump(); + SDLNet_TCP_Send(tcpsock, jsonResponse.c_str(), jsonResponse.size() + 1); + } + + // If time remaining has reached 0 or was 0, we have finished let's remove the command and end the thread + if (packet->timeRemaining <= 0) { + receivedCommandsMutex.lock(); + receivedCommands.erase(std::remove(receivedCommands.begin(), receivedCommands.end(), packet), receivedCommands.end()); + receivedCommandsMutex.unlock(); + RemoveEffect(packet->effectType.c_str()); + + // If not timed, let's fire the one and only success + if (!isTimed) { + nlohmann::json dataSend; + dataSend["id"] = packet->packetId; + dataSend["type"] = 0; + dataSend["timeRemaining"] = packet->timeRemaining; + dataSend["status"] = EffectResult::Success; + + std::string jsonResponse = dataSend.dump(); + SDLNet_TCP_Send(tcpsock, jsonResponse.c_str(), jsonResponse.size() + 1); + + delete packet; + } + + return; + } + + // Decrement remaining time by the second that elapsed between thread runs + packet->timeRemaining -= 1000; + // We don't want to emit repeated events, so ensure we only emit changes in events + if (effectResult != lastResult) { + lastResult = effectResult; + + nlohmann::json dataSend; + dataSend["id"] = packet->packetId; + dataSend["type"] = 0; + dataSend["timeRemaining"] = packet->timeRemaining; + dataSend["status"] = effectResult; + + std::string jsonResponse = dataSend.dump(); + SDLNet_TCP_Send(tcpsock, jsonResponse.c_str(), jsonResponse.size() + 1); + } + } else if (effectResult == EffectResult::Retry && paused == false && packet->timeRemaining > 0) { + paused = true; + nlohmann::json dataSend; + dataSend["id"] = packet->packetId; + dataSend["type"] = 0; + dataSend["timeRemaining"] = packet->timeRemaining; + dataSend["status"] = EffectResult::Paused; + + std::string jsonResponse = dataSend.dump(); + SDLNet_TCP_Send(tcpsock, jsonResponse.c_str(), jsonResponse.size() + 1); + } + + std::this_thread::sleep_for(std::chrono::seconds(1)); + } +} + +void CrowdControl::ReceiveFromCrowdControl() +{ + printf("Waiting for a connection from Crowd Control..."); + + while (!connected && CVar_GetS32("gCrowdControl", 0)) { + tcpsock = SDLNet_TCP_Open(&ip); + + if (tcpsock) { + connected = true; + printf("Connected to Crowd Control!"); + break; + } + } + + while (connected && CVar_GetS32("gCrowdControl", 0) && tcpsock) { + int len = SDLNet_TCP_Recv(tcpsock, &received, sizeof(received)); + + if (!len || !tcpsock || len == -1) { + printf("SDLNet_TCP_Recv: %s\n", SDLNet_GetError()); + break; + } + + try { + nlohmann::json dataReceived = nlohmann::json::parse(received); + + CCPacket* packet = new CCPacket(); + packet->packetId = dataReceived["id"]; + auto parameters = dataReceived["parameters"]; + if (parameters.size() > 0) { + packet->effectValue = dataReceived["parameters"][0]; + } + packet->effectType = dataReceived["code"].get(); + + if (packet->effectType == "high_gravity" || + packet->effectType == "low_gravity") { + packet->effectCategory = "gravity"; + packet->timeRemaining = 30000; + } + else if (packet->effectType == "damage_multiplier" + || packet->effectType == "defense_multiplier" + ) { + packet->effectCategory = "defense"; + packet->timeRemaining = 30000; + } + else if (packet->effectType == "giant_link" || + packet->effectType == "minish_link" || + packet->effectType == "invisible" || + packet->effectType == "paper_link") { + packet->effectCategory = "link_size"; + packet->timeRemaining = 30000; + } + else if (packet->effectType == "freeze" || + packet->effectType == "damage" || + packet->effectType == "heal" || + packet->effectType == "knockback" || + packet->effectType == "electrocute" || + packet->effectType == "burn" || + packet->effectType == "kill") { + packet->effectCategory = "link_damage"; + } + else if (packet->effectType == "hover_boots" || + packet->effectType == "iron_boots") { + packet->effectCategory = "boots"; + packet->timeRemaining = 30000; + } + else if (packet->effectType == "add_heart_container" || + packet->effectType == "remove_heart_container") { + packet->effectCategory = "heart_container"; + } + else if (packet->effectType == "no_ui") { + packet->effectCategory = "ui"; + packet->timeRemaining = 60000; + } + else if (packet->effectType == "fill_magic" || + packet->effectType == "empty_magic") { + packet->effectCategory = "magic"; + } + else if (packet->effectType == "ohko") { + packet->effectCategory = "ohko"; + packet->timeRemaining = 30000; + } + else if (packet->effectType == "pacifist") { + packet->effectCategory = "pacifist"; + packet->timeRemaining = 15000; + } + else if (packet->effectType == "rainstorm") { + packet->effectCategory = "weather"; + packet->timeRemaining = 30000; + } + else if (packet->effectType == "reverse") { + packet->effectCategory = "controls"; + packet->timeRemaining = 60000; + } + else if (packet->effectType == "add_rupees" + || packet->effectType == "remove_rupees" + ) { + packet->effectCategory = "rupees"; + } + else if (packet->effectType == "increase_speed" + || packet->effectType == "decrease_speed" + ) { + packet->effectCategory = "speed"; + packet->timeRemaining = 30000; + } + else if (packet->effectType == "no_z") { + packet->effectCategory = "no_z"; + packet->timeRemaining = 30000; + } + else if (packet->effectType == "spawn_wallmaster" || + packet->effectType == "spawn_arwing" || + packet->effectType == "spawn_darklink" || + packet->effectType == "spawn_stalfos" || + packet->effectType == "spawn_wolfos" || + packet->effectType == "spawn_freezard" || + packet->effectType == "spawn_keese" || + packet->effectType == "spawn_icekeese" || + packet->effectType == "spawn_firekeese" || + packet->effectType == "spawn_tektite" || + packet->effectType == "spawn_likelike" || + packet->effectType == "cucco_storm") { + packet->effectCategory = "spawn"; + } + else { + packet->effectCategory = "none"; + packet->timeRemaining = 0; + } + + // Check if running effect is possible + EffectResult effectResult = ExecuteEffect(packet->effectType.c_str(), packet->effectValue, 1); + if (effectResult == EffectResult::Retry || effectResult == EffectResult::Failure) { + nlohmann::json dataSend; + dataSend["id"] = packet->packetId; + dataSend["type"] = 0; + dataSend["timeRemaining"] = packet->timeRemaining; + dataSend["status"] = effectResult; + + std::string jsonResponse = dataSend.dump(); + SDLNet_TCP_Send(tcpsock, jsonResponse.c_str(), jsonResponse.size() + 1); + } else { + bool anotherEffectOfCategoryActive = false; + for (CCPacket* pack : receivedCommands) { + if (pack != packet && pack->effectCategory == packet->effectCategory && pack->packetId < packet->packetId) { + anotherEffectOfCategoryActive = true; + + nlohmann::json dataSend; + dataSend["id"] = packet->packetId; + dataSend["type"] = 0; + dataSend["timeRemaining"] = packet->timeRemaining; + dataSend["status"] = EffectResult::Retry; + + std::string jsonResponse = dataSend.dump(); + SDLNet_TCP_Send(tcpsock, jsonResponse.c_str(), jsonResponse.size() + 1); + + break; + } + } + + if (anotherEffectOfCategoryActive != true) { + receivedCommandsMutex.lock(); + receivedCommands.push_back(packet); + receivedCommandsMutex.unlock(); + std::thread t = std::thread(&CrowdControl::RunCrowdControl, this, packet); + t.detach(); + } + } + } catch (nlohmann::json::parse_error& e) { + printf("Error parsing JSON: %s\n", e.what()); + continue; + } + } + + if (connected) { + SDLNet_TCP_Close(tcpsock); + SDLNet_Quit(); + connected = false; + } +} + +CrowdControl::EffectResult CrowdControl::ExecuteEffect(std::string effectId, uint32_t value, bool dryRun) { + // Don't execute effect and don't advance timer when the player is not in a proper loaded savefile + // and when they're busy dying. + if (gGlobalCtx == NULL || gGlobalCtx->gameOverCtx.state > 0 || gSaveContext.fileNum < 0 || gSaveContext.fileNum > 2) { + return EffectResult::Retry; + } + + Player* player = GET_PLAYER(gGlobalCtx); + + if (player != NULL) { + if (effectId == "add_heart_container") { + if (gSaveContext.healthCapacity >= 0x140) { + return EffectResult::Failure; + } + + if (dryRun == 0) CMD_EXECUTE("add_heart_container"); + return EffectResult::Success; + } else if (effectId == "remove_heart_container") { + if ((gSaveContext.healthCapacity - 0x10) <= 0) { + return EffectResult::Failure; + } + + if (dryRun == 0) CMD_EXECUTE("remove_heart_container"); + return EffectResult::Success; + } else if (effectId == "fill_magic") { + if (!gSaveContext.magicAcquired) { + return EffectResult::Failure; + } + + if (gSaveContext.magic >= (gSaveContext.doubleMagic + 1) + 0x30) { + return EffectResult::Failure; + } + + if (dryRun == 0) CMD_EXECUTE("fill_magic"); + return EffectResult::Success; + } else if (effectId == "empty_magic") { + if (!gSaveContext.magicAcquired || gSaveContext.magic <= 0) { + return EffectResult::Failure; + } + + if (dryRun == 0) CMD_EXECUTE("empty_magic"); + return EffectResult::Success; + } else if (effectId == "add_rupees") { + if (dryRun == 0) CMD_EXECUTE(std::format("update_rupees {}", value)); + return EffectResult::Success; + } else if (effectId == "remove_rupees") { + if (gSaveContext.rupees - value < 0) { + return EffectResult::Failure; + } + + if (dryRun == 0) CMD_EXECUTE(std::format("update_rupees -{}", value)); + return EffectResult::Success; + } + } + + if (player != NULL && !Player_InBlockingCsMode(gGlobalCtx, player) && gGlobalCtx->pauseCtx.state == 0 + && gGlobalCtx->msgCtx.msgMode == 0) { + if (effectId == "high_gravity") { + if (dryRun == 0) CMD_EXECUTE("gravity 2"); + return EffectResult::Success; + } else if (effectId == "low_gravity") { + if (dryRun == 0) CMD_EXECUTE("gravity 0"); + return EffectResult::Success; + } else if (effectId == "kill" + || effectId == "freeze" + || effectId == "burn" + || effectId == "electrocute" + || effectId == "cucco_storm" + ) { + if (PlayerGrounded(player)) { + if (dryRun == 0) CMD_EXECUTE(std::format("{}", effectId)); + return EffectResult::Success; + } + return EffectResult::Failure; + } else if (effectId == "heal" + || effectId == "knockback" + ) { + if (dryRun == 0) CMD_EXECUTE(std::format("{} {}", effectId, value)); + return EffectResult::Success; + } else if (effectId == "giant_link" + || effectId == "minish_link" + || effectId == "no_ui" + || effectId == "invisible" + || effectId == "paper_link" + || effectId == "no_z" + || effectId == "ohko" + || effectId == "pacifist" + || effectId == "rainstorm" + ) { + if (dryRun == 0) CMD_EXECUTE(std::format("{} 1", effectId)); + return EffectResult::Success; + } else if (effectId == "reverse") { + if (dryRun == 0) CMD_EXECUTE("reverse_controls 1"); + return EffectResult::Success; + } else if (effectId == "iron_boots") { + if (dryRun == 0) CMD_EXECUTE("boots iron"); + return EffectResult::Success; + } else if (effectId == "hover_boots") { + if (dryRun == 0) CMD_EXECUTE("boots hover"); + return EffectResult::Success; + } else if (effectId == "give_dekushield") { + if (dryRun == 0) CMD_EXECUTE("givedekushield"); + return EffectResult::Success; + } else if (effectId == "spawn_wallmaster" + || effectId == "spawn_arwing" + || effectId == "spawn_darklink" + || effectId == "spawn_stalfos" + || effectId == "spawn_wolfos" + || effectId == "spawn_freezard" + || effectId == "spawn_keese" + || effectId == "spawn_icekeese" + || effectId == "spawn_firekeese" + || effectId == "spawn_tektite" + || effectId == "spawn_likelike" + ) { + if (dryRun == 0) { + if (CrowdControl::SpawnEnemy(effectId)) { + return EffectResult::Success; + } else { + return EffectResult::Failure; + } + } + return EffectResult::Success; + } else if (effectId == "increase_speed") { + if (dryRun == 0) CMD_EXECUTE("speed_modifier 2"); + return EffectResult::Success; + } else if (effectId == "decrease_speed") { + if (dryRun == 0) CMD_EXECUTE("speed_modifier -2"); + return EffectResult::Success; + } else if (effectId == "damage_multiplier") { + if (dryRun == 0) CMD_EXECUTE(std::format("defense_modifier -{}", value)); + return EffectResult::Success; + } else if (effectId == "defense_multiplier") { + if (dryRun == 0) CMD_EXECUTE(std::format("defense_modifier {}", value)); + return EffectResult::Success; + } else if (effectId == "damage") { + if ((gSaveContext.healthCapacity - 0x10) <= 0) { + return EffectResult::Failure; + } + + if (dryRun == 0) CMD_EXECUTE(std::format("{} {}", effectId, value)); + return EffectResult::Success; + } + } + + return EffectResult::Retry; +} + +bool CrowdControl::SpawnEnemy(std::string effectId) { + Player* player = GET_PLAYER(gGlobalCtx); + + int enemyId = 0; + int enemyParams = 0; + float posXOffset = 0; + float posYOffset = 0; + float posZOffset = 0; + + if (effectId == "spawn_wallmaster") { + enemyId = 17; + } else if (effectId == "spawn_arwing") { + enemyId = 315; + enemyParams = 1; + posYOffset = 100; + } else if (effectId == "spawn_darklink") { + enemyId = 51; + posXOffset = 75; + posYOffset = 50; + } else if (effectId == "spawn_stalfos") { + enemyId = 2; + enemyParams = 2; + posXOffset = 75; + posYOffset = 50; + } else if (effectId == "spawn_wolfos") { + enemyId = 431; + posXOffset = 75; + posYOffset = 50; + } else if (effectId == "spawn_freezard") { + enemyId = 289; + posXOffset = 75; + posYOffset = 50; + } else if (effectId == "spawn_keese") { + enemyId = 19; + enemyParams = 2; + posXOffset = 75; + posYOffset = 50; + } else if (effectId == "spawn_icekeese") { + enemyId = 19; + enemyParams = 4; + posXOffset = 75; + posYOffset = 50; + } else if (effectId == "spawn_firekeese") { + enemyId = 19; + enemyParams = 1; + posXOffset = 75; + posYOffset = 50; + } else if (effectId == "spawn_tektite") { + enemyId = 27; + posXOffset = 75; + posYOffset = 50; + } else if (effectId == "spawn_likelike") { + enemyId = 221; + posXOffset = 75; + posYOffset = 50; + } + + return Actor_Spawn(&gGlobalCtx->actorCtx, gGlobalCtx, enemyId, player->actor.world.pos.x + posXOffset, + player->actor.world.pos.y + posYOffset, player->actor.world.pos.z + posZOffset, 0, 0, 0, enemyParams); + +} + +void CrowdControl::RemoveEffect(std::string effectId) { + if (gGlobalCtx == NULL) { + return; + } + + Player* player = GET_PLAYER(gGlobalCtx); + + if (player != NULL) { + if (effectId == "giant_link" + || effectId == "minish_link" + || effectId == "no_ui" + || effectId == "invisible" + || effectId == "paper_link" + || effectId == "no_z" + || effectId == "ohko" + || effectId == "pacifist" + || effectId == "rainstorm" + ) { + CMD_EXECUTE(std::format("{} 0", effectId)); + return; + } else if (effectId == "iron_boots" || effectId == "hover_boots") { + CMD_EXECUTE("boots kokiri"); + return; + } else if (effectId == "high_gravity" || effectId == "low_gravity") { + CMD_EXECUTE("gravity 1"); + return; + } else if (effectId == "reverse") { + CMD_EXECUTE("reverse_controls 0"); + return; + } else if (effectId == "increase_speed" + || effectId == "decrease_speed" + ) { + CMD_EXECUTE("speed_modifier 0"); + return; + } else if (effectId == "damage_multiplier" + || effectId == "defense_multiplier" + ) { + CMD_EXECUTE("defense_modifier 0"); + return; + } + } +} +#endif diff --git a/soh/soh/Enhancements/crowd-control/CrowdControl.h b/soh/soh/Enhancements/crowd-control/CrowdControl.h new file mode 100644 index 000000000..d35e19ef8 --- /dev/null +++ b/soh/soh/Enhancements/crowd-control/CrowdControl.h @@ -0,0 +1,91 @@ +#ifdef ENABLE_CROWD_CONTROL + +#ifndef _CROWDCONTROL_C +#define _CROWDCONTROL_C +#endif + +#include "stdint.h" + +#ifdef __cplusplus +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class CrowdControl { + private: + typedef struct CCPacket { + uint32_t packetId; + std::string effectType; + uint32_t effectValue; + std::string effectCategory; + long timeRemaining; + } CCPacket; + + enum EffectResult { + /// The effect executed successfully. + Success = 0x00, + /// The effect failed to trigger, but is still available for use. Viewer(s) will be refunded. You probably don't want this. + Failure = 0x01, + /// Same as but the effect is no longer available for use for the remainder of the game. You probably don't want this. + Unavailable = 0x02, + /// The effect cannot be triggered right now, try again in a few seconds. This is the "normal" failure response. + Retry = 0x03, + /// INTERNAL USE ONLY. The effect has been queued for execution after the current one ends. + Queue = 0x04, + /// INTERNAL USE ONLY. The effect triggered successfully and is now active until it ends. + Running = 0x05, + /// The timed effect has been paused and is now waiting. + Paused = 0x06, + /// The timed effect has been resumed and is counting down again. + Resumed = 0x07, + /// The timed effect has finished. + Finished = 0x08, + /// The processor isn't ready to start or has shut down. + NotReady = 0xFF + }; + + enum ResponseType { + EffectRequest = 0x00, + Login = 0xF0, + LoginSuccess = 0xF1, + Disconnect = 0xFE, + KeepAlive = 0xFF + }; + + struct Response { + int id; + EffectResult status; + long timeRemaining; + ResponseType type = ResponseType::EffectRequest; + }; + + std::thread ccThreadReceive; + + TCPsocket tcpsock; + IPaddress ip; + + bool connected; + + char received[512]; + + std::vector receivedCommands; + std::mutex receivedCommandsMutex; + + void RunCrowdControl(CCPacket* packet); + void ReceiveFromCrowdControl(); + EffectResult ExecuteEffect(std::string effectId, uint32_t value, bool dryRun); + bool SpawnEnemy(std::string effectId); + void RemoveEffect(std::string effectId); + + public: + static CrowdControl* Instance; + void InitCrowdControl(); +}; +#endif +#endif diff --git a/soh/soh/Enhancements/crowd-control/soh.cs b/soh/soh/Enhancements/crowd-control/soh.cs new file mode 100644 index 000000000..e8649fcde --- /dev/null +++ b/soh/soh/Enhancements/crowd-control/soh.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using CrowdControl.Common; +using CrowdControl.Games.Packs; +using ConnectorType = CrowdControl.Common.ConnectorType; + +public class SoH : SimpleTCPPack +{ + public override string Host { get; } = "127.0.0.1"; + + public override ushort Port { get; } = 43384; + + public SoH(IPlayer player, Func responseHandler, Action statusUpdateHandler) : base(player, responseHandler, statusUpdateHandler) { } + + public override Game Game { get; } = new Game(90, "Ship of Harkinian", "SoH", "PC", ConnectorType.SimpleTCPConnector); + + private Dictionary _enemyType = new Dictionary() + { + {"wallmaster", ("Wallmaster")}, + {"arwing", ("Arwing")}, + {"darklink", ("Dark Link")}, + {"stalfos", ("Stalfos")}, + {"wolfos", ("Wolfos")}, + {"freezard", ("Freezard")}, + {"keese", ("Keese")}, + {"icekeese", ("Ice Keese")}, + {"firekeese", ("Fire Keese")}, + {"tektite", ("Tektite")}, + {"likelike", ("Like-Like")} + }; + + public override List Effects + { + get + { + List effects = new List + { + new Effect("Add Heart Container", "add_heart_container"), + new Effect("Remove Heart Container", "remove_heart_container"), + new Effect("Damage Multiplier", "damage_multiplier", new[] { "damdefmulti" }) { Duration = 30 }, + new Effect("Defense Multiplier", "defense_multiplier", new[] { "damdefmulti" }) { Duration = 30 }, + new Effect("Freeze Link", "freeze"), + new Effect("Giant Lonk", "giant_link") { Duration = 30 }, + new Effect("Empty Heart", "damage", new[] { "health20" }), + new Effect("Fill Heart", "heal", new[] { "health20" }), + new Effect("Kill Player", "kill"), + new Effect("High Gravity", "high_gravity") { Duration = 30 }, + new Effect("Hover Boots", "hover_boots") { Duration = 30 }, + new Effect("Invisible Link", "invisible") { Duration = 30 }, + new Effect("No UI", "no_ui") { Duration = 60 }, + new Effect("Low Gravity", "low_gravity") { Duration = 30 }, + new Effect("Fill Magic", "fill_magic"), + new Effect("Empty Magic", "empty_magic"), + new Effect("Minish Link", "minish_link") { Duration = 30 }, + new Effect("No Z Button", "no_z") { Duration = 30 }, + new Effect("One-Hit KO", "ohko") { Duration = 30 }, + new Effect("Pacifist", "pacifist") { Duration = 15 }, + new Effect("Paper Link", "paper_link") { Duration = 30 }, + new Effect("Rainstorm", "rainstorm") { Duration = 30 }, + new Effect("Reverse Controls", "reverse") { Duration = 60 }, + new Effect("Give Rupees", "add_rupees", new[] { "rupees999" }), + new Effect("Take Rupees", "remove_rupees", new[] { "rupees999" }), + new Effect("Increase Speed", "increase_speed") { Duration = 30 }, + new Effect("Decrease Speed", "decrease_speed") { Duration = 30 }, + new Effect("Cucco Swarm", "cucco_storm"), + new Effect("Knockback Link", "knockback", new[] { "knockbackstrength" }), + new Effect("Burn Link", "burn"), + new Effect("Electrocute Link", "electrocute"), + new Effect("Iron Boots", "iron_boots") { Duration = 30 }, + new Effect("Give Deku Shield", "give_dekushield"), + new Effect("Spawn Enemy", "spawn_enemy", ItemKind.Folder), + }; + + effects.AddRange(_enemyType.Select(t => new Effect($"Spawn {t.Value}", $"spawn_{t.Key}", "spawn_enemy"))); + + return effects; + } + } + + //Slider ranges need to be defined + public override List ItemTypes => new List + { + new ItemType("Rupees", "rupees999", ItemType.Subtype.Slider, "{\"min\":1,\"max\":999}"), + new ItemType("Health", "health20", ItemType.Subtype.Slider, "{\"min\":1,\"max\":20}"), + new ItemType("Damage/Defense Multiplier", "damdefmulti", ItemType.Subtype.Slider, "{\"min\":1,\"max\":10}"), + new ItemType("Knockback Strength", "knockbackstrength", ItemType.Subtype.Slider, "{\"min\":1,\"max\":3}") + }; + +} diff --git a/soh/soh/Enhancements/debugconsole.cpp b/soh/soh/Enhancements/debugconsole.cpp index 36c9f9ea0..7c4e6e127 100644 --- a/soh/soh/Enhancements/debugconsole.cpp +++ b/soh/soh/Enhancements/debugconsole.cpp @@ -1,5 +1,6 @@ #include "debugconsole.h" #include +#include #include "savestates.h" #include @@ -26,9 +27,24 @@ extern GlobalContext* gGlobalCtx; } #include +#include "overlays/actors/ovl_En_Niw/z_en_niw.h" #define CMD_REGISTER SohImGui::GetConsole()->AddCommand +uint32_t chaosEffectNoUI; +uint32_t chaosEffectGiantLink; +uint32_t chaosEffectMinishLink; +uint32_t chaosEffectPaperLink; +uint32_t chaosEffectResetLinkScale; +uint32_t chaosEffectInvisibleLink; +uint32_t chaosEffectOneHitKO; +uint32_t chaosEffectPacifistMode; +int32_t chaosEffectDefenseModifier; +uint32_t chaosEffectNoZ; +uint32_t chaosEffectReverseControls; +uint32_t chaosEffectGravityLevel = GRAVITY_LEVEL_NORMAL; +int32_t chaosEffectSpeedModifier; + static bool ActorSpawnHandler(std::shared_ptr Console, const std::vector& args) { if ((args.size() != 9) && (args.size() != 3) && (args.size() != 6)) { SohImGui::GetConsole()->SendErrorMessage("Not enough arguments passed to actorspawn"); @@ -58,7 +74,7 @@ static bool ActorSpawnHandler(std::shared_ptr Console, const std: if (args[8][0] != ',') { spawnPoint.rot.z = std::stoi(args[8]); } - case 5: + case 6: if (args[3][0] != ',') { spawnPoint.pos.x = std::stoi(args[3]); } @@ -78,6 +94,17 @@ static bool ActorSpawnHandler(std::shared_ptr Console, const std: return CMD_SUCCESS; } +static bool GiveDekuShieldHandler(std::shared_ptr Console, const std::vector&) { + // Give Deku Shield to the player, and automatically equip it when they're child and have no shield currently equiped. + Player* player = GET_PLAYER(gGlobalCtx); + Item_Give(gGlobalCtx, ITEM_SHIELD_DEKU); + if (LINK_IS_CHILD && player->currentShield == PLAYER_SHIELD_NONE) { + player->currentShield = PLAYER_SHIELD_DEKU; + Inventory_ChangeEquipment(EQUIP_SHIELD, PLAYER_SHIELD_DEKU); + } + SohImGui::GetConsole()->SendInfoMessage("[SOH] Gave Deku Shield"); + return CMD_SUCCESS; +} static bool KillPlayerHandler(std::shared_ptr Console, const std::vector&) { gSaveContext.health = 0; @@ -111,7 +138,6 @@ static bool SetPlayerHealthHandler(std::shared_ptr Console, const return CMD_SUCCESS; } - static bool LoadSceneHandler(std::shared_ptr Console, const std::vector&) { gSaveContext.respawnFlag = 0; gSaveContext.seqId = 0xFF; @@ -120,9 +146,10 @@ static bool LoadSceneHandler(std::shared_ptr Console, const std:: return CMD_SUCCESS; } -static bool RuppeHandler(std::shared_ptr Console, const std::vector& args) { - if (args.size() < 2) +static bool RupeeHandler(std::shared_ptr Console, const std::vector& args) { + if (args.size() < 2) { return CMD_FAILED; + } int rupeeAmount; try { @@ -432,6 +459,431 @@ static bool StateSlotSelectHandler(std::shared_ptr Console, const return CMD_SUCCESS; } +static bool InvisibleHandler(std::shared_ptr Console, const std::vector& args) { + if (args.size() != 2) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] Unexpected arguments passed"); + return CMD_FAILED; + } + + try { + chaosEffectInvisibleLink = std::stoi(args[1], nullptr, 10) == 0 ? 0 : 1; + if (!chaosEffectInvisibleLink) { + Player* player = GET_PLAYER(gGlobalCtx); + player->actor.shape.shadowDraw = ActorShadow_DrawFeet; + } + + return CMD_SUCCESS; + } catch (std::invalid_argument const& ex) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] Invisible value must be a number."); + return CMD_FAILED; + } +} + +static bool GiantLinkHandler(std::shared_ptr Console, const std::vector& args) { + if (args.size() != 2) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] Unexpected arguments passed"); + return CMD_FAILED; + } + + try { + chaosEffectGiantLink = std::stoi(args[1], nullptr, 10) == 0 ? 0 : 1; + if (chaosEffectGiantLink) { + chaosEffectPaperLink = 0; + chaosEffectMinishLink = 0; + } else { + chaosEffectResetLinkScale = 1; + } + + return CMD_SUCCESS; + } catch (std::invalid_argument const& ex) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] Giant value must be a number."); + return CMD_FAILED; + } +} + +static bool MinishLinkHandler(std::shared_ptr Console, const std::vector& args) { + if (args.size() != 2) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] Unexpected arguments passed"); + return CMD_FAILED; + } + + try { + chaosEffectMinishLink = std::stoi(args[1], nullptr, 10) == 0 ? 0 : 1; + if (chaosEffectMinishLink) { + chaosEffectPaperLink = 0; + chaosEffectGiantLink = 0; + } else { + chaosEffectResetLinkScale = 1; + } + + return CMD_SUCCESS; + } catch (std::invalid_argument const& ex) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] Minish value must be a number."); + return CMD_FAILED; + } +} + +static bool AddHeartContainerHandler(std::shared_ptr Console, const std::vector& args) { + if (gSaveContext.healthCapacity >= 0x140) + return CMD_FAILED; + + Health_GiveHearts(1); + return CMD_SUCCESS; +} + +static bool RemoveHeartContainerHandler(std::shared_ptr Console, const std::vector& args) { + if ((gSaveContext.healthCapacity - 0x10) < 3) + return CMD_FAILED; + + Health_RemoveHearts(1); + return CMD_SUCCESS; +} + +static bool GravityHandler(std::shared_ptr Console, const std::vector& args) { + if (args.size() != 2) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] Unexpected arguments passed"); + return CMD_FAILED; + } + + try { + chaosEffectGravityLevel = Ship::Math::clamp(std::stoi(args[1], nullptr, 10), GRAVITY_LEVEL_LIGHT, GRAVITY_LEVEL_HEAVY); + return CMD_SUCCESS; + } catch (std::invalid_argument const& ex) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] Minish value must be a number."); + return CMD_FAILED; + } +} + +static bool NoUIHandler(std::shared_ptr Console, const std::vector& args) { + if (args.size() != 2) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] Unexpected arguments passed"); + return CMD_FAILED; + } + + try { + chaosEffectNoUI = std::stoi(args[1], nullptr, 10) == 0 ? 0 : 1; + return CMD_SUCCESS; + } catch (std::invalid_argument const& ex) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] No UI value must be a number."); + return CMD_FAILED; + } +} + +static bool FreezeHandler(std::shared_ptr Console, const std::vector& args) { + gSaveContext.pendingIceTrapCount++; + return CMD_SUCCESS; +} + +static bool DefenseModifierHandler(std::shared_ptr Console, const std::vector& args) { + if (args.size() != 2) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] Unexpected arguments passed"); + return CMD_FAILED; + } + + try { + chaosEffectDefenseModifier = std::stoi(args[1], nullptr, 10); + return CMD_SUCCESS; + } catch (std::invalid_argument const& ex) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] Defense modifier value must be a number."); + return CMD_FAILED; + } +} + +static bool DamageHandler(std::shared_ptr Console, const std::vector& args) { + if (args.size() != 2) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] Unexpected arguments passed"); + return CMD_FAILED; + } + + try { + int value = std::stoi(args[1], nullptr, 10); + if (value < 0) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] Invalid value passed. Value must be greater than 0"); + return CMD_FAILED; + } + + Player* player = GET_PLAYER(gGlobalCtx); + + Health_ChangeBy(gGlobalCtx, -value * 0x10); + func_80837C0C(gGlobalCtx, player, 0, 0, 0, 0, 0); + player->invincibilityTimer = 28; + + return CMD_SUCCESS; + } catch (std::invalid_argument const& ex) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] Damage value must be a number."); + return CMD_FAILED; + } +} + +static bool HealHandler(std::shared_ptr Console, const std::vector& args) { + if (args.size() != 2) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] Unexpected arguments passed"); + return CMD_FAILED; + } + + try { + int value = std::stoi(args[1], nullptr, 10); + if (value < 0) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] Invalid value passed. Value must be greater than 0"); + return CMD_FAILED; + } + + Health_ChangeBy(gGlobalCtx, value * 0x10); + return CMD_SUCCESS; + } catch (std::invalid_argument const& ex) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] Heal value must be a number."); + return CMD_FAILED; + } +} + +static bool FillMagicHandler(std::shared_ptr Console, const std::vector& args) { + Magic_Fill(gGlobalCtx); + return CMD_SUCCESS; +} + +static bool EmptyMagicHandler(std::shared_ptr Console, const std::vector& args) { + gSaveContext.magic = 0; + return CMD_SUCCESS; +} + +static bool NoZHandler(std::shared_ptr Console, const std::vector& args) { + if (args.size() != 2) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] Unexpected arguments passed"); + return CMD_FAILED; + } + + try { + chaosEffectNoZ = std::stoi(args[1], nullptr, 10) == 0 ? 0 : 1; + return CMD_SUCCESS; + } catch (std::invalid_argument const& ex) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] NoZ value must be a number."); + return CMD_FAILED; + } +} + +static bool OneHitKOHandler(std::shared_ptr Console, const std::vector& args) { + if (args.size() != 2) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] Unexpected arguments passed"); + return CMD_FAILED; + } + + try { + chaosEffectOneHitKO = std::stoi(args[1], nullptr, 10) == 0 ? 0 : 1; + return CMD_SUCCESS; + } catch (std::invalid_argument const& ex) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] One-hit KO value must be a number."); + return CMD_FAILED; + } +} + +static bool PacifistHandler(std::shared_ptr Console, const std::vector& args) { + if (args.size() != 2) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] Unexpected arguments passed"); + return CMD_FAILED; + } + + try { + chaosEffectPacifistMode = std::stoi(args[1], nullptr, 10) == 0 ? 0 : 1; + // Force interface to update to make the buttons transparent + gSaveContext.unk_13E8 = 50; + Interface_Update(gGlobalCtx); + return CMD_SUCCESS; + } catch (std::invalid_argument const& ex) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] Pacifist value must be a number."); + return CMD_FAILED; + } +} + +static bool PaperLinkHandler(std::shared_ptr Console, const std::vector& args) { + if (args.size() != 2) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] Unexpected arguments passed"); + return CMD_FAILED; + } + + try { + chaosEffectPaperLink = std::stoi(args[1], nullptr, 10) == 0 ? 0 : 1; + if (chaosEffectPaperLink) { + chaosEffectMinishLink = 0; + chaosEffectGiantLink = 0; + } else { + chaosEffectResetLinkScale = 1; + } + return CMD_SUCCESS; + } catch (std::invalid_argument const& ex) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] Paper Link value must be a number."); + return CMD_FAILED; + } +} + +static bool RainstormHandler(std::shared_ptr Console, const std::vector& args) { + if (args.size() != 2) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] Unexpected arguments passed"); + return CMD_FAILED; + } + + try { + uint32_t rainstorm = std::stoi(args[1], nullptr, 10) == 0 ? 0 : 1; + if (rainstorm) { + gGlobalCtx->envCtx.unk_F2[0] = 20; // rain intensity target + gGlobalCtx->envCtx.gloomySkyMode = 1; // start gloomy sky + if ((gWeatherMode != 0) || gGlobalCtx->envCtx.unk_17 != 0) { + gGlobalCtx->envCtx.unk_DE = 1; + } + gGlobalCtx->envCtx.lightningMode = LIGHTNING_MODE_ON; + Environment_PlayStormNatureAmbience(gGlobalCtx); + } else { + gGlobalCtx->envCtx.unk_F2[0] = 0; + if (gGlobalCtx->csCtx.state == CS_STATE_IDLE) { + Environment_StopStormNatureAmbience(gGlobalCtx); + } else if (func_800FA0B4(SEQ_PLAYER_BGM_MAIN) == NA_BGM_NATURE_AMBIENCE) { + Audio_SetNatureAmbienceChannelIO(NATURE_CHANNEL_LIGHTNING, CHANNEL_IO_PORT_1, 0); + Audio_SetNatureAmbienceChannelIO(NATURE_CHANNEL_RAIN, CHANNEL_IO_PORT_1, 0); + } + osSyncPrintf("\n\n\nE_wether_flg=[%d]", gWeatherMode); + osSyncPrintf("\nrain_evt_trg=[%d]\n\n", gGlobalCtx->envCtx.gloomySkyMode); + if (gWeatherMode == 0 && (gGlobalCtx->envCtx.gloomySkyMode == 1)) { + gGlobalCtx->envCtx.gloomySkyMode = 2; // end gloomy sky + } else { + gGlobalCtx->envCtx.gloomySkyMode = 0; + gGlobalCtx->envCtx.unk_DE = 0; + } + gGlobalCtx->envCtx.lightningMode = LIGHTNING_MODE_LAST; + } + + + return CMD_SUCCESS; + } catch (std::invalid_argument const& ex) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] rainstorm value must be a number."); + return CMD_FAILED; + } +} + +static bool ReverseControlsHandler(std::shared_ptr Console, const std::vector& args) { + if (args.size() != 2) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] Unexpected arguments passed"); + return CMD_FAILED; + } + + try { + chaosEffectReverseControls = std::stoi(args[1], nullptr, 10) == 0 ? 0 : 1; + return CMD_SUCCESS; + } catch (std::invalid_argument const& ex) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] Reverse controls value must be a number."); + return CMD_FAILED; + } +} + +static bool UpdateRupeesHandler(std::shared_ptr Console, const std::vector& args) { + if (args.size() != 2) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] Unexpected arguments passed"); + return CMD_FAILED; + } + + try { + int value = std::stoi(args[1], nullptr, 10); + Rupees_ChangeBy(value); + return CMD_SUCCESS; + } catch (std::invalid_argument const& ex) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] Rupee value must be a number."); + return CMD_FAILED; + } +} + +static bool SpeedModifierHandler(std::shared_ptr Console, const std::vector& args) { + if (args.size() != 2) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] Unexpected arguments passed"); + return CMD_FAILED; + } + + try { + chaosEffectSpeedModifier = std::stoi(args[1], nullptr, 10); + return CMD_SUCCESS; + } catch (std::invalid_argument const& ex) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] Speed modifier value must be a number."); + return CMD_FAILED; + } +} + +const static std::map boots { + { "kokiri", PLAYER_BOOTS_KOKIRI }, + { "iron", PLAYER_BOOTS_IRON }, + { "hover", PLAYER_BOOTS_HOVER }, +}; + +static bool BootsHandler(std::shared_ptr Console, const std::vector& args) { + if (args.size() != 2) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] Unexpected arguments passed"); + return CMD_FAILED; + } + + const auto& it = boots.find(args[1]); + if (it == boots.end()) { + SohImGui::GetConsole()->SendErrorMessage("Invalid boot type. Options are 'kokiri', 'iron' and 'hover'"); + return CMD_FAILED; + } + + Player* player = GET_PLAYER(gGlobalCtx); + player->currentBoots = it->second; + Inventory_ChangeEquipment(EQUIP_BOOTS, it->second + 1); + Player_SetBootData(gGlobalCtx, player); + + return CMD_SUCCESS; +} + +static bool KnockbackHandler(std::shared_ptr Console, const std::vector& args) { + if (args.size() != 2) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] Unexpected arguments passed"); + return CMD_FAILED; + } + + try { + int value = std::stoi(args[1], nullptr, 10); + if (value < 0) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] Invalid value passed. Value must be greater than 0"); + return CMD_FAILED; + } + + Player* player = GET_PLAYER(gGlobalCtx); + func_8002F71C(gGlobalCtx, &player->actor, value * 5, player->actor.world.rot.y + 0x8000, value * 5); + + return CMD_SUCCESS; + } catch (std::invalid_argument const& ex) { + SohImGui::GetConsole()->SendErrorMessage("[SOH] Knockback value must be a number."); + return CMD_FAILED; + } +} + +static bool ElectrocuteHandler(std::shared_ptr Console, const std::vector& args) { + Player* player = GET_PLAYER(gGlobalCtx); + if (PlayerGrounded(player)) { + func_80837C0C(gGlobalCtx, player, 4, 0, 0, 0, 0); + return CMD_SUCCESS; + } + + return CMD_FAILED; +} + +static bool BurnHandler(std::shared_ptr Console, const std::vector& args) { + Player* player = GET_PLAYER(gGlobalCtx); + if (PlayerGrounded(player)) { + for (int i = 0; i < 18; i++) { + player->flameTimers[i] = Rand_S16Offset(0, 200); + } + player->isBurning = true; + func_80837C0C(gGlobalCtx, player, 0, 0, 0, 0, 0); + return CMD_FAILED; + } + return CMD_SUCCESS; +} + +static bool CuccoStormHandler(std::shared_ptr Console, const std::vector& args) { + Player* player = GET_PLAYER(gGlobalCtx); + EnNiw* cucco = (EnNiw*)Actor_Spawn(&gGlobalCtx->actorCtx, gGlobalCtx, ACTOR_EN_NIW, player->actor.world.pos.x, + player->actor.world.pos.y + 2200, player->actor.world.pos.z, 0, 0, 0, 0); + cucco->actionFunc = func_80AB70A0_nocutscene; + return CMD_SUCCESS; +} + #define VARTYPE_INTEGER 0 #define VARTYPE_FLOAT 1 #define VARTYPE_STRING 2 @@ -492,7 +944,6 @@ static bool SetCVarHandler(std::shared_ptr Console, const std::ve return CMD_SUCCESS; } - static bool GetCVarHandler(std::shared_ptr Console, const std::vector& args) { if (args.size() < 2) return CMD_FAILED; @@ -520,14 +971,41 @@ static bool GetCVarHandler(std::shared_ptr Console, const std::ve } void DebugConsole_Init(void) { + // Console + CMD_REGISTER("file_select", { FileSelectHandler, "Returns to the file select." }); + CMD_REGISTER("reset", { ResetHandler, "Resets the game." }); + CMD_REGISTER("quit", { QuitHandler, "Quits the game." }); + + // Save States + CMD_REGISTER("save_state", { SaveStateHandler, "Save a state." }); + CMD_REGISTER("load_state", { LoadStateHandler, "Load a state." }); + CMD_REGISTER("set_slot", { StateSlotSelectHandler, "Selects a SaveState slot", { + { "Slot number", Ship::ArgumentType::NUMBER, } + }}); + + // Map & Location + CMD_REGISTER("void", { VoidHandler, "Voids out of the current map." }); + CMD_REGISTER("reload", { ReloadHandler, "Reloads the current map." }); + CMD_REGISTER("fw", { FWHandler,"Spawns the player where Farore's Wind is set." }); + CMD_REGISTER("entrance", { EntranceHandler, "Sends player to the entered entrance (hex)", { + { "entrance", Ship::ArgumentType::NUMBER } + }}); + + // Gameplay CMD_REGISTER("kill", { KillPlayerHandler, "Commit suicide." }); + CMD_REGISTER("map", { LoadSceneHandler, "Load up kak?" }); - CMD_REGISTER("rupee", { RuppeHandler, "Set your rupee counter.", { + + CMD_REGISTER("rupee", { RupeeHandler, "Set your rupee counter.", { {"amount", Ship::ArgumentType::NUMBER } }}); - CMD_REGISTER("bItem", { BHandler, "Set an item to the B button.", { { "Item ID", Ship::ArgumentType::NUMBER } } }); - CMD_REGISTER("health", { SetPlayerHealthHandler, "Set the health of the player.", { { "health", Ship::ArgumentType::NUMBER } + + CMD_REGISTER("bItem", { BHandler, "Set an item to the B button.", { + { "Item ID", Ship::ArgumentType::NUMBER } }}); + + CMD_REGISTER("givedekushield", { GiveDekuShieldHandler, "Gives a deku shield and equips it when Link is a child with no shield equiped." }); + CMD_REGISTER("spawn", { ActorSpawnHandler, "Spawn an actor.", { { "actor_id", Ship::ArgumentType::NUMBER }, { "data", Ship::ArgumentType::NUMBER }, { "x", Ship::ArgumentType::PLAYER_POS, true }, @@ -537,40 +1015,122 @@ void DebugConsole_Init(void) { { "ry", Ship::ArgumentType::PLAYER_ROT, true }, { "rz", Ship::ArgumentType::PLAYER_ROT, true } }}); - CMD_REGISTER("pos", { SetPosHandler, "Sets the position of the player.", { { "x", Ship::ArgumentType::PLAYER_POS, true }, - { "y", Ship::ArgumentType::PLAYER_POS, true }, - { "z", Ship::ArgumentType::PLAYER_POS, true } + + CMD_REGISTER("pos", { SetPosHandler, "Sets the position of the player.", { + { "x", Ship::ArgumentType::PLAYER_POS, true }, + { "y", Ship::ArgumentType::PLAYER_POS, true }, + { "z", Ship::ArgumentType::PLAYER_POS, true } }}); - CMD_REGISTER("set", { SetCVarHandler, - "Sets a console variable.", - { { "varName", Ship::ArgumentType::TEXT }, { "varValue", Ship::ArgumentType::TEXT } } }); - CMD_REGISTER("get", { GetCVarHandler, "Gets a console variable.", { { "varName", Ship::ArgumentType::TEXT } } }); - CMD_REGISTER("reset", { ResetHandler, "Resets the game." }); - CMD_REGISTER("ammo", { AmmoHandler, "Changes ammo of an item.", - { { "item", Ship::ArgumentType::TEXT }, { "count", Ship::ArgumentType::NUMBER } } }); - CMD_REGISTER("bottle", { BottleHandler, - "Changes item in a bottle slot.", - { { "item", Ship::ArgumentType::TEXT }, { "slot", Ship::ArgumentType::NUMBER } } }); + CMD_REGISTER("set", { SetCVarHandler, "Sets a console variable.", { + { "varName", Ship::ArgumentType::TEXT }, + { "varValue", Ship::ArgumentType::TEXT } + }}); - CMD_REGISTER("item", { ItemHandler, - "Sets item ID in arg 1 into slot arg 2. No boundary checks. Use with caution.", - { { "slot", Ship::ArgumentType::NUMBER }, { "item id", Ship::ArgumentType::NUMBER } } }); - CMD_REGISTER("entrance", { EntranceHandler, - "Sends player to the entered entrance (hex)", - { { "entrance", Ship::ArgumentType::NUMBER } } }); - CMD_REGISTER("void", {VoidHandler, "Voids out of the current map.",}); - CMD_REGISTER("reload", {ReloadHandler, "Reloads the current map.",}); - CMD_REGISTER("file_select", {FileSelectHandler, "Returns to the file select.",}); - CMD_REGISTER("fw", {FWHandler,"Spawns the player where Farore's Wind is set.", }); - CMD_REGISTER("quit", {QuitHandler, "Quits the game.",}); + CMD_REGISTER("get", { GetCVarHandler, "Gets a console variable.", { + { "varName", Ship::ArgumentType::TEXT } + }}); + + CMD_REGISTER("ammo", { AmmoHandler, "Changes ammo of an item.", { + { "item", Ship::ArgumentType::TEXT }, + { "count", Ship::ArgumentType::NUMBER } + }}); + + CMD_REGISTER("bottle", { BottleHandler, "Changes item in a bottle slot.", { + { "item", Ship::ArgumentType::TEXT }, + { "slot", Ship::ArgumentType::NUMBER } + }}); + + CMD_REGISTER("item", { ItemHandler, "Sets item ID in arg 1 into slot arg 2. No boundary checks. Use with caution.", { + { "slot", Ship::ArgumentType::NUMBER }, + { "item id", Ship::ArgumentType::NUMBER } + }}); + + CMD_REGISTER("invisible", { InvisibleHandler, "Activate Link's Elvish cloak, making him appear invisible.", { + { "value", Ship::ArgumentType::NUMBER } + }}); + + CMD_REGISTER("giant_link", { GiantLinkHandler, "Turn Link into a giant Lonky boi.", { + { "value", Ship::ArgumentType::NUMBER } + }}); + + CMD_REGISTER("minish_link", { MinishLinkHandler, "Turn Link into a minish boi.", { + { "value", Ship::ArgumentType::NUMBER } + }}); + + CMD_REGISTER("add_heart_container", { AddHeartContainerHandler, "Give Link a heart! The maximum amount of hearts is 20!" }); + + CMD_REGISTER("remove_heart_container", { RemoveHeartContainerHandler, "Remove a heart from Link. The minimal amount of hearts is 3." }); + + CMD_REGISTER("gravity", { GravityHandler, "Set gravity level.", { + { "value", Ship::ArgumentType::NUMBER } + }}); + + CMD_REGISTER("no_ui", { NoUIHandler, "Disables the UI.", { + { "value", Ship::ArgumentType::NUMBER } + }}); + + CMD_REGISTER("freeze", { FreezeHandler, "Freezes Link in place" }); + + CMD_REGISTER("defense_modifier", { DefenseModifierHandler, "Sets the defense modifier.", { + { "value", Ship::ArgumentType::NUMBER } + }}); + + CMD_REGISTER("damage", { DamageHandler, "Deal damage to Link.", { + { "value", Ship::ArgumentType::NUMBER } + }}); + + CMD_REGISTER("heal", { HealHandler, "Heals Link.", { + { "value", Ship::ArgumentType::NUMBER } + }}); + + CMD_REGISTER("fill_magic", { FillMagicHandler, "Fills magic." }); + + CMD_REGISTER("empty_magic", { EmptyMagicHandler, "Empties magic." }); + + CMD_REGISTER("no_z", { NoZHandler, "Disables Z-button presses.", { + { "value", Ship::ArgumentType::NUMBER } + }}); + + CMD_REGISTER("ohko", { OneHitKOHandler, "Activates one hit KO. Any damage kills Link and he cannot gain health in this mode.", { + { "value", Ship::ArgumentType::NUMBER } + }}); + + CMD_REGISTER("pacifist", { PacifistHandler, "Activates pacifist mode. Prevents Link from using his weapon.", { + { "value", Ship::ArgumentType::NUMBER } + }}); + + CMD_REGISTER("paper_link", { PaperLinkHandler, "Link but made out of paper.", { + { "value", Ship::ArgumentType::NUMBER } + }}); + + CMD_REGISTER("rainstorm", { RainstormHandler, "Activates rainstorm." }); + + CMD_REGISTER("reverse_controls", { ReverseControlsHandler, "Reverses the controls.", { + { "value", Ship::ArgumentType::NUMBER } + }}); + + CMD_REGISTER("update_rupees", { UpdateRupeesHandler, "Adds rupees.", { + { "value", Ship::ArgumentType::NUMBER } + }}); + + CMD_REGISTER("speed_modifier", { SpeedModifierHandler, "Sets the speed modifier.", { + { "value", Ship::ArgumentType::NUMBER } + }}); + + CMD_REGISTER("boots", { BootsHandler, "Activates boots.", { + { "type", Ship::ArgumentType::TEXT }, + }}); + + CMD_REGISTER("knockback", { KnockbackHandler, "Knocks Link back.", { + { "value", Ship::ArgumentType::NUMBER } + }}); + + CMD_REGISTER("electrocute", { ElectrocuteHandler, "Electrocutes Link." }); + + CMD_REGISTER("burn", { BurnHandler, "Burns Link." }); + + CMD_REGISTER("cucco_storm", { CuccoStormHandler, "Cucco Storm" }); - CMD_REGISTER("save_state", { SaveStateHandler, "Save a state." }); - CMD_REGISTER("load_state", { LoadStateHandler, "Load a state." }); - CMD_REGISTER("set_slot", { StateSlotSelectHandler, "Selects a SaveState slot", { { - "Slot number", - Ship::ArgumentType::NUMBER, - } - } }); CVar_Load(); } diff --git a/soh/soh/Enhancements/debugconsole.h b/soh/soh/Enhancements/debugconsole.h index ffd0cb0cb..40c56bf6d 100644 --- a/soh/soh/Enhancements/debugconsole.h +++ b/soh/soh/Enhancements/debugconsole.h @@ -1,3 +1,31 @@ #pragma once +#include "stdint.h" + +#define GRAVITY_LEVEL_NORMAL 1.0f +#define GRAVITY_LEVEL_LIGHT 0.0f +#define GRAVITY_LEVEL_HEAVY 2.0f + +#ifdef __cplusplus +extern "C" { +#endif +// bools are exported as uint32_t for compatibility with C code +extern uint32_t chaosEffectNoUI; +extern uint32_t chaosEffectGiantLink; +extern uint32_t chaosEffectMinishLink; +extern uint32_t chaosEffectPaperLink; +extern uint32_t chaosEffectResetLinkScale; +extern uint32_t chaosEffectInvisibleLink; +extern uint32_t chaosEffectOneHitKO; +extern uint32_t chaosEffectPacifistMode; +extern int32_t chaosEffectDefenseModifier; +extern uint32_t chaosEffectNoZ; +extern uint32_t chaosEffectReverseControls; + +extern uint32_t chaosEffectGravityLevel; +extern int32_t chaosEffectSpeedModifier; +#ifdef __cplusplus +} +#endif + void DebugConsole_Init(void); diff --git a/soh/soh/GameMenuBar.cpp b/soh/soh/GameMenuBar.cpp index c28158331..799322510 100644 --- a/soh/soh/GameMenuBar.cpp +++ b/soh/soh/GameMenuBar.cpp @@ -1492,6 +1492,8 @@ namespace GameMenuBar { } ImGui::PopStyleVar(3); ImGui::PopStyleColor(1); + UIWidgets::PaddedEnhancementCheckbox("Crowd Control", "gCrowdControl", true, false); + UIWidgets::Tooltip("Enables CrowdControl. Will attempt to connect to the local Crowd Control server."); UIWidgets::PaddedSeparator(); diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index 9a49f736a..cb97aea62 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -64,6 +64,11 @@ #include "Enhancements/item-tables/ItemTableManager.h" #include "GameMenuBar.hpp" +#ifdef ENABLE_CROWD_CONTROL +#include "Enhancements/crowd-control/CrowdControl.h" +CrowdControl* CrowdControl::Instance; +#endif + OTRGlobals* OTRGlobals::Instance; SaveManager* SaveManager::Instance; CustomMessageManager* CustomMessageManager::Instance; @@ -349,6 +354,11 @@ extern "C" void InitOTR() { InitItemTracker(); OTRExtScanner(); VanillaItemTable_Init(); + +#ifdef ENABLE_CROWD_CONTROL + CrowdControl::Instance = new CrowdControl(); + CrowdControl::Instance->InitCrowdControl(); +#endif } extern "C" void DeinitOTR() { diff --git a/soh/src/code/padmgr.c b/soh/src/code/padmgr.c index 98b97dc71..e7f8bcf14 100644 --- a/soh/src/code/padmgr.c +++ b/soh/src/code/padmgr.c @@ -1,6 +1,8 @@ #include "global.h" #include "vt.h" +#include "soh/Enhancements/debugconsole.h" + //#include #ifdef _MSC_VER @@ -226,6 +228,25 @@ void PadMgr_ProcessInputs(PadMgr* padMgr) { switch (padnow1->err_no) { case 0: input->cur = *padnow1; + + if (chaosEffectNoZ) { + input->cur.button &= ~(BTN_Z); + } + + if (chaosEffectReverseControls) { + if (input->cur.stick_x == -128) { + input->cur.stick_x = 127; + } else { + input->cur.stick_x *= -1; + } + + if (input->cur.stick_y == -128) { + input->cur.stick_y = 127; + } else { + input->cur.stick_y *= -1; + } + } + if (!padMgr->ctrlrIsConnected[i]) { padMgr->ctrlrIsConnected[i] = true; osSyncPrintf(VT_FGCOL(YELLOW)); diff --git a/soh/src/code/z_parameter.c b/soh/src/code/z_parameter.c index eb6640d09..f87ba7a25 100644 --- a/soh/src/code/z_parameter.c +++ b/soh/src/code/z_parameter.c @@ -11,6 +11,8 @@ #include #endif +#include "soh/Enhancements/debugconsole.h" + static uint16_t _doActionTexWidth, _doActionTexHeight = -1; static uint16_t DO_ACTION_TEX_WIDTH() { @@ -935,7 +937,11 @@ void func_80083108(GlobalContext* globalCtx) { Interface_ChangeAlpha(50); } } else if (msgCtx->msgMode == MSGMODE_NONE) { - if ((func_8008F2F8(globalCtx) >= 2) && (func_8008F2F8(globalCtx) < 5)) { + if (chaosEffectPacifistMode) { + gSaveContext.buttonStatus[0] = gSaveContext.buttonStatus[1] = gSaveContext.buttonStatus[2] = + gSaveContext.buttonStatus[3] = gSaveContext.buttonStatus[5] = gSaveContext.buttonStatus[6] = + gSaveContext.buttonStatus[7] = gSaveContext.buttonStatus[8] = BTN_DISABLED; + } else if ((func_8008F2F8(globalCtx) >= 2) && (func_8008F2F8(globalCtx) < 5)) { if (gSaveContext.buttonStatus[0] != BTN_DISABLED) { sp28 = 1; } @@ -2913,6 +2919,15 @@ s32 Health_ChangeBy(GlobalContext* globalCtx, s16 healthChange) { osSyncPrintf("***** 増減=%d (now=%d, max=%d) ***", healthChange, gSaveContext.health, gSaveContext.healthCapacity); + // If one-hit ko mode is on, any damage kills you and you cannot gain health. + if (chaosEffectOneHitKO) { + if (healthChange < 0) { + gSaveContext.health = 0; + } + + return 0; + } + // clang-format off if (healthChange > 0) { Audio_PlaySoundGeneral(NA_SE_SY_HP_RECOVER, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8); @@ -2922,6 +2937,14 @@ s32 Health_ChangeBy(GlobalContext* globalCtx, s16 healthChange) { } // clang-format on + if (chaosEffectDefenseModifier != 0 && healthChange < 0) { + if (chaosEffectDefenseModifier > 0) { + healthChange /= chaosEffectDefenseModifier; + } else { + healthChange *= abs(chaosEffectDefenseModifier); + } + } + gSaveContext.health += healthChange; if (gSaveContext.health > gSaveContext.healthCapacity) { @@ -2956,6 +2979,10 @@ void Health_GiveHearts(s16 hearts) { gSaveContext.healthCapacity += hearts * 0x10; } +void Health_RemoveHearts(s16 hearts) { + gSaveContext.healthCapacity -= hearts * 0x10; +} + void Rupees_ChangeBy(s16 rupeeChange) { gSaveContext.rupeeAccumulator += rupeeChange; } @@ -3022,7 +3049,7 @@ void Inventory_ChangeAmmo(s16 item, s16 ammoChange) { void Magic_Fill(GlobalContext* globalCtx) { if (gSaveContext.magicAcquired) { gSaveContext.unk_13F2 = gSaveContext.unk_13F0; - gSaveContext.unk_13F6 = (gSaveContext.doubleMagic * 0x30) + 0x30; + gSaveContext.unk_13F6 = (gSaveContext.doubleMagic + 1) * 0x30; gSaveContext.unk_13F0 = 9; } } @@ -4739,6 +4766,10 @@ void Interface_Draw(GlobalContext* globalCtx) { s16 svar6; bool fullUi = !CVar_GetS32("gMinimalUI", 0) || !R_MINIMAP_DISABLED || globalCtx->pauseCtx.state != 0; + if (chaosEffectNoUI) { + return; + } + OPEN_DISPS(globalCtx->state.gfxCtx); // Invalidate Do Action textures as they may have changed diff --git a/soh/src/code/z_play.c b/soh/src/code/z_play.c index 4dfd66e3f..f25163c5e 100644 --- a/soh/src/code/z_play.c +++ b/soh/src/code/z_play.c @@ -6,6 +6,8 @@ #include "soh/Enhancements/gameconsole.h" #include #include "soh/frame_interpolation.h" +#include "soh/Enhancements/debugconsole.h" +#include #include @@ -1544,6 +1546,10 @@ void Gameplay_Main(GameState* thisx) { } +u8 PlayerGrounded(Player* player) { + return player->actor.bgCheckFlags & 1; +} + // original name: "Game_play_demo_mode_check" s32 Gameplay_InCsMode(GlobalContext* globalCtx) { return (globalCtx->csCtx.state != CS_STATE_IDLE) || Player_InCsMode(globalCtx); diff --git a/soh/src/code/z_player_lib.c b/soh/src/code/z_player_lib.c index 522e7d851..4fa0542fc 100644 --- a/soh/src/code/z_player_lib.c +++ b/soh/src/code/z_player_lib.c @@ -5,6 +5,8 @@ #include "objects/object_triforce_spot/object_triforce_spot.h" #include "overlays/actors/ovl_Demo_Effect/z_demo_effect.h" +#include "soh/Enhancements/debugconsole.h" + typedef struct { /* 0x00 */ u8 flag; /* 0x02 */ u16 textId; @@ -1037,6 +1039,11 @@ s32 func_80090014(GlobalContext* globalCtx, s32 limbIndex, Gfx** dList, Vec3f* p } } + if (chaosEffectInvisibleLink) { + this->actor.shape.shadowDraw = NULL; + *dList = NULL; + } + return false; } diff --git a/soh/src/overlays/actors/ovl_En_Niw/z_en_niw.c b/soh/src/overlays/actors/ovl_En_Niw/z_en_niw.c index f136e3bf0..f2722b457 100644 --- a/soh/src/overlays/actors/ovl_En_Niw/z_en_niw.c +++ b/soh/src/overlays/actors/ovl_En_Niw/z_en_niw.c @@ -764,6 +764,12 @@ void func_80AB70A0(EnNiw* this, GlobalContext* globalCtx) { this->actionFunc = func_80AB70F8; } +void func_80AB70A0_nocutscene(EnNiw* this, GlobalContext* globalCtx) { + this->timer5 = 10; + this->unk_2A2 = 1; + this->actionFunc = func_80AB70F8; +} + void func_80AB70F8(EnNiw* this, GlobalContext* globalCtx) { this->sfxTimer1 = 100; @@ -1228,4 +1234,4 @@ void EnNiw_Reset(void) { D_80AB85E0 = 0; sLowerRiverSpawned = false; sUpperRiverSpawned = false; -} \ No newline at end of file +} diff --git a/soh/src/overlays/actors/ovl_En_Niw/z_en_niw.h b/soh/src/overlays/actors/ovl_En_Niw/z_en_niw.h index 5c481e5e6..9942e2473 100644 --- a/soh/src/overlays/actors/ovl_En_Niw/z_en_niw.h +++ b/soh/src/overlays/actors/ovl_En_Niw/z_en_niw.h @@ -77,4 +77,17 @@ typedef struct EnNiw { /* 0x0358 */ EnNiwFeather feathers[20]; } EnNiw; // size = 0x07B8 +#ifdef __cplusplus +#define this thisx +extern "C" +{ +#endif + void func_80AB70A0(EnNiw* this, GlobalContext* globalCtx); + void func_80AB70A0_nocutscene(EnNiw* this, GlobalContext* globalCtx); +#ifdef __cplusplus +#undef this +}; +#undef this +#endif + #endif diff --git a/soh/src/overlays/actors/ovl_player_actor/z_player.c b/soh/src/overlays/actors/ovl_player_actor/z_player.c index 5d5ca125b..1a0877373 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -21,7 +21,8 @@ #include "objects/object_link_child/object_link_child.h" #include "textures/icon_item_24_static/icon_item_24_static.h" #include -#include +#include "soh/Enhancements/item-tables/ItemTableTypes.h" +#include "soh/Enhancements/debugconsole.h" typedef enum { /* 0x00 */ KNOB_ANIM_ADULT_L, @@ -6021,9 +6022,19 @@ void func_8083DFE0(Player* this, f32* arg1, s16* arg2) { if (this->swordState == 0) { float maxSpeed = R_RUN_SPEED_LIMIT / 100.0f; + + if (chaosEffectSpeedModifier != 0) { + if (chaosEffectSpeedModifier > 0) { + maxSpeed *= chaosEffectSpeedModifier; + } else { + maxSpeed /= abs(chaosEffectSpeedModifier); + } + } + if (CVar_GetS32("gMMBunnyHood", 0) != 0 && this->currentMask == PLAYER_MASK_BUNNY) { maxSpeed *= 1.5f; } + this->linearVelocity = CLAMP(this->linearVelocity, -maxSpeed, maxSpeed); } @@ -7630,9 +7641,18 @@ void func_80842180(Player* this, GlobalContext* globalCtx) { func_80837268(this, &sp2C, &sp2A, 0.018f, globalCtx); if (!func_8083C484(this, &sp2C, &sp2A)) { + if (chaosEffectSpeedModifier != 0) { + if (chaosEffectSpeedModifier > 0) { + sp2C *= chaosEffectSpeedModifier; + } else { + sp2C /= abs(chaosEffectSpeedModifier); + } + } + if (CVar_GetS32("gMMBunnyHood", 0) != 0 && this->currentMask == PLAYER_MASK_BUNNY) { sp2C *= 1.5f; } + func_8083DF68(this, sp2C, sp2A); func_8083DDC8(this, globalCtx); @@ -10888,6 +10908,39 @@ void Player_Update(Actor* thisx, GlobalContext* globalCtx) { MREG(53) = this->actor.world.pos.y; MREG(54) = this->actor.world.pos.z; MREG(55) = this->actor.world.rot.y; + + if (chaosEffectGiantLink) { + this->actor.scale.x = 0.02f; + this->actor.scale.y = 0.02f; + this->actor.scale.z = 0.02f; + } + + if (chaosEffectMinishLink) { + this->actor.scale.x = 0.001f; + this->actor.scale.y = 0.001f; + this->actor.scale.z = 0.001f; + } + + if (chaosEffectPaperLink) { + this->actor.scale.x = 0.001f; + this->actor.scale.y = 0.01f; + this->actor.scale.z = 0.01f; + } + + if (chaosEffectResetLinkScale) { + this->actor.scale.x = 0.01f; + this->actor.scale.y = 0.01f; + this->actor.scale.z = 0.01f; + chaosEffectResetLinkScale = 0; + } + + if (chaosEffectGravityLevel == GRAVITY_LEVEL_HEAVY) { + this->actor.gravity = -4.0f; + } + + if (chaosEffectGravityLevel == GRAVITY_LEVEL_LIGHT) { + this->actor.gravity = -0.3f; + } } static struct_80858AC8 D_80858AC8;