Feature: Crowd Control Integration (#1568)

* 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 2aafcc1df2.

* Revert "few fix and paper Link"

This reverts commit 65e76dcfee.

* 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 <briaguya@alice>

Co-authored-by: Baoulettes <perlouzerie@hotmail.fr>
Co-authored-by: aMannus <mannusmenting@gmail.com>
Co-authored-by: briaguya <70942617+briaguya-ai@users.noreply.github.com>
Co-authored-by: briaguya <briaguya@alice>
This commit is contained in:
David Chavez 2022-09-28 04:41:17 +02:00 committed by GitHub
parent 87125ae334
commit 083ceb4423
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1531 additions and 47 deletions

View File

@ -22,7 +22,7 @@ set(VCPKG_TRIPLET x64-windows-static)
set(VCPKG_TARGET_TRIPLET x64-windows-static) set(VCPKG_TARGET_TRIPLET x64-windows-static)
vcpkg_bootstrap() vcpkg_bootstrap()
vcpkg_install_packages(zlib bzip2 libpng SDL2 GLEW glfw3) vcpkg_install_packages(zlib bzip2 libpng SDL2 SDL2-net GLEW glfw3)
endif() endif()
################################################################################ ################################################################################

View File

@ -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}) 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 set(Source_Files__soh
"soh/GbiWrap.cpp" "soh/GbiWrap.cpp"
"soh/OTRAudio.h" "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}) 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 set(Source_Files__src__boot
"src/boot/assert.c" "src/boot/assert.c"
"src/boot/boot_main.c" "src/boot/boot_main.c"
@ -1588,6 +1602,7 @@ set(ALL_FILES
${Header_Files__soh__Enhancements__randomizer__3drando} ${Header_Files__soh__Enhancements__randomizer__3drando}
${Header_Files__soh__Enhancements__item_tables} ${Header_Files__soh__Enhancements__item_tables}
${Header_Files__soh__Enhancements__custom_message} ${Header_Files__soh__Enhancements__custom_message}
${Header_Files__soh__Enhancements__crowd_control}
${Source_Files__soh} ${Source_Files__soh}
${Source_Files__soh__Enhancements} ${Source_Files__soh__Enhancements}
${Source_Files__soh__Enhancements__controls} ${Source_Files__soh__Enhancements__controls}
@ -1599,6 +1614,7 @@ set(ALL_FILES
${Source_Files__soh__Enhancements__randomizer__3drando__location_access} ${Source_Files__soh__Enhancements__randomizer__3drando__location_access}
${Source_Files__soh__Enhancements__item_tables} ${Source_Files__soh__Enhancements__item_tables}
${Source_Files__soh__Enhancements__custom_message} ${Source_Files__soh__Enhancements__custom_message}
${Source_Files__soh__Enhancements__crowd_control}
${Source_Files__src__boot} ${Source_Files__src__boot}
${Source_Files__src__buffers} ${Source_Files__src__buffers}
${Source_Files__src__code} ${Source_Files__src__code}
@ -1680,6 +1696,11 @@ endif()
find_package(SDL2) find_package(SDL2)
set(SDL2-INCLUDE ${SDL2_INCLUDE_DIRS}) 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 target_include_directories(${PROJECT_NAME} PRIVATE assets
${CMAKE_CURRENT_SOURCE_DIR}/include/ ${CMAKE_CURRENT_SOURCE_DIR}/include/
${CMAKE_CURRENT_SOURCE_DIR}/src/ ${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}/../libultraship/libultraship/Lib/Fast3D/U64/PR
${CMAKE_CURRENT_SOURCE_DIR}/../ZAPDTR/ZAPDUtils ${CMAKE_CURRENT_SOURCE_DIR}/../ZAPDTR/ZAPDUtils
${SDL2-INCLUDE} ${SDL2-INCLUDE}
${SDL2-NET-INCLUDE}
${CMAKE_CURRENT_SOURCE_DIR}/assets/ ${CMAKE_CURRENT_SOURCE_DIR}/assets/
. .
) )
@ -1700,12 +1722,13 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
"$<$<CONFIG:Debug>:" "$<$<CONFIG:Debug>:"
"_DEBUG;" "_DEBUG;"
"_CRT_SECURE_NO_WARNINGS;" "_CRT_SECURE_NO_WARNINGS;"
"ENABLE_DX11" "ENABLE_DX11;"
">" ">"
"$<$<CONFIG:Release>:" "$<$<CONFIG:Release>:"
"NDEBUG" "NDEBUG"
">" ">"
"INCLUDE_GAME_PRINTF;" "INCLUDE_GAME_PRINTF;"
"ENABLE_CROWD_CONTROL;"
"UNICODE;" "UNICODE;"
"_UNICODE" "_UNICODE"
STORMLIB_NO_AUTO_LINK STORMLIB_NO_AUTO_LINK
@ -1941,6 +1964,7 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
"glu32;" "glu32;"
"SDL2::SDL2;" "SDL2::SDL2;"
"SDL2::SDL2main;" "SDL2::SDL2main;"
"SDL2::SDL2_net;"
"glfw;" "glfw;"
"winmm;" "winmm;"
"imm32;" "imm32;"

View File

@ -1076,6 +1076,8 @@ void Interface_SetDoAction(GlobalContext* globalCtx, u16 action);
void Interface_SetNaviCall(GlobalContext* globalCtx, u16 naviCallState); void Interface_SetNaviCall(GlobalContext* globalCtx, u16 naviCallState);
void Interface_LoadActionLabelB(GlobalContext* globalCtx, u16 action); void Interface_LoadActionLabelB(GlobalContext* globalCtx, u16 action);
s32 Health_ChangeBy(GlobalContext* globalCtx, s16 healthChange); s32 Health_ChangeBy(GlobalContext* globalCtx, s16 healthChange);
void Health_GiveHearts(s16 hearts);
void Health_RemoveHearts(s16 hearts);
void Rupees_ChangeBy(s16 rupeeChange); void Rupees_ChangeBy(s16 rupeeChange);
void Inventory_ChangeAmmo(s16 item, s16 ammoChange); void Inventory_ChangeAmmo(s16 item, s16 ammoChange);
void Magic_Fill(GlobalContext* globalCtx); 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 Path_CopyLastPoint(Path* path, Vec3f* dest);
void FrameAdvance_Init(FrameAdvanceContext* frameAdvCtx); void FrameAdvance_Init(FrameAdvanceContext* frameAdvCtx);
s32 FrameAdvance_Update(FrameAdvanceContext* frameAdvCtx, Input* input); s32 FrameAdvance_Update(FrameAdvanceContext* frameAdvCtx, Input* input);
u8 PlayerGrounded(Player* player);
void Player_SetBootData(GlobalContext* globalCtx, Player* player); void Player_SetBootData(GlobalContext* globalCtx, Player* player);
s32 Player_InBlockingCsMode(GlobalContext* globalCtx, Player* player); s32 Player_InBlockingCsMode(GlobalContext* globalCtx, Player* player);
s32 Player_InCsMode(GlobalContext* globalCtx); s32 Player_InCsMode(GlobalContext* globalCtx);
@ -1103,6 +1106,7 @@ void Player_SetModelGroup(Player* player, s32 modelGroup);
void func_8008EC70(Player* player); void func_8008EC70(Player* player);
void Player_SetEquipmentData(GlobalContext* globalCtx, Player* player); void Player_SetEquipmentData(GlobalContext* globalCtx, Player* player);
void Player_UpdateBottleHeld(GlobalContext* globalCtx, Player* player, s32 item, s32 actionParam); 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_8008EDF0(Player* player);
void func_8008EE08(Player* player); void func_8008EE08(Player* player);
void func_8008EEAC(GlobalContext* globalCtx, Actor* actor); void func_8008EEAC(GlobalContext* globalCtx, Actor* actor);

View File

@ -0,0 +1,538 @@
#ifdef ENABLE_CROWD_CONTROL
#include "CrowdControl.h"
#include <libultraship/Cvar.h>
#include <libultraship/Console.h>
#include <libultraship/ImGuiImpl.h>
#include <nlohmann/json.hpp>
#include <regex>
extern "C" {
#include <z64.h>
#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<std::string>();
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

View File

@ -0,0 +1,91 @@
#ifdef ENABLE_CROWD_CONTROL
#ifndef _CROWDCONTROL_C
#define _CROWDCONTROL_C
#endif
#include "stdint.h"
#ifdef __cplusplus
#include <SDL2/SDL_net.h>
#include <cstdint>
#include <thread>
#include <memory>
#include <map>
#include <vector>
#include <iostream>
#include <chrono>
#include <future>
class CrowdControl {
private:
typedef struct CCPacket {
uint32_t packetId;
std::string effectType;
uint32_t effectValue;
std::string effectCategory;
long timeRemaining;
} CCPacket;
enum EffectResult {
/// <summary>The effect executed successfully.</summary>
Success = 0x00,
/// <summary>The effect failed to trigger, but is still available for use. Viewer(s) will be refunded. You probably don't want this.</summary>
Failure = 0x01,
/// <summary>Same as <see cref="Failure"/> but the effect is no longer available for use for the remainder of the game. You probably don't want this.</summary>
Unavailable = 0x02,
/// <summary>The effect cannot be triggered right now, try again in a few seconds. This is the "normal" failure response.</summary>
Retry = 0x03,
/// <summary>INTERNAL USE ONLY. The effect has been queued for execution after the current one ends.</summary>
Queue = 0x04,
/// <summary>INTERNAL USE ONLY. The effect triggered successfully and is now active until it ends.</summary>
Running = 0x05,
/// <summary>The timed effect has been paused and is now waiting.</summary>
Paused = 0x06,
/// <summary>The timed effect has been resumed and is counting down again.</summary>
Resumed = 0x07,
/// <summary>The timed effect has finished.</summary>
Finished = 0x08,
/// <summary>The processor isn't ready to start or has shut down.</summary>
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<CCPacket*> 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

View File

@ -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<CrowdControlBlock, bool> responseHandler, Action<object> statusUpdateHandler) : base(player, responseHandler, statusUpdateHandler) { }
public override Game Game { get; } = new Game(90, "Ship of Harkinian", "SoH", "PC", ConnectorType.SimpleTCPConnector);
private Dictionary<string, string> _enemyType = new Dictionary<string, string>()
{
{"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<Effect> Effects
{
get
{
List<Effect> effects = new List<Effect>
{
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<ItemType> ItemTypes => new List<ItemType>
{
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}")
};
}

View File

@ -1,5 +1,6 @@
#include "debugconsole.h" #include "debugconsole.h"
#include <libultraship/ImGuiImpl.h> #include <libultraship/ImGuiImpl.h>
#include <libultraship/Utils.h>
#include "savestates.h" #include "savestates.h"
#include <libultraship/Console.h> #include <libultraship/Console.h>
@ -26,9 +27,24 @@ extern GlobalContext* gGlobalCtx;
} }
#include <libultraship/Cvar.h> #include <libultraship/Cvar.h>
#include "overlays/actors/ovl_En_Niw/z_en_niw.h"
#define CMD_REGISTER SohImGui::GetConsole()->AddCommand #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<Ship::Console> Console, const std::vector<std::string>& args) { static bool ActorSpawnHandler(std::shared_ptr<Ship::Console> Console, const std::vector<std::string>& args) {
if ((args.size() != 9) && (args.size() != 3) && (args.size() != 6)) { if ((args.size() != 9) && (args.size() != 3) && (args.size() != 6)) {
SohImGui::GetConsole()->SendErrorMessage("Not enough arguments passed to actorspawn"); SohImGui::GetConsole()->SendErrorMessage("Not enough arguments passed to actorspawn");
@ -58,7 +74,7 @@ static bool ActorSpawnHandler(std::shared_ptr<Ship::Console> Console, const std:
if (args[8][0] != ',') { if (args[8][0] != ',') {
spawnPoint.rot.z = std::stoi(args[8]); spawnPoint.rot.z = std::stoi(args[8]);
} }
case 5: case 6:
if (args[3][0] != ',') { if (args[3][0] != ',') {
spawnPoint.pos.x = std::stoi(args[3]); spawnPoint.pos.x = std::stoi(args[3]);
} }
@ -78,6 +94,17 @@ static bool ActorSpawnHandler(std::shared_ptr<Ship::Console> Console, const std:
return CMD_SUCCESS; return CMD_SUCCESS;
} }
static bool GiveDekuShieldHandler(std::shared_ptr<Ship::Console> Console, const std::vector<std::string>&) {
// 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<Ship::Console> Console, const std::vector<std::string>&) { static bool KillPlayerHandler(std::shared_ptr<Ship::Console> Console, const std::vector<std::string>&) {
gSaveContext.health = 0; gSaveContext.health = 0;
@ -111,7 +138,6 @@ static bool SetPlayerHealthHandler(std::shared_ptr<Ship::Console> Console, const
return CMD_SUCCESS; return CMD_SUCCESS;
} }
static bool LoadSceneHandler(std::shared_ptr<Ship::Console> Console, const std::vector<std::string>&) { static bool LoadSceneHandler(std::shared_ptr<Ship::Console> Console, const std::vector<std::string>&) {
gSaveContext.respawnFlag = 0; gSaveContext.respawnFlag = 0;
gSaveContext.seqId = 0xFF; gSaveContext.seqId = 0xFF;
@ -120,9 +146,10 @@ static bool LoadSceneHandler(std::shared_ptr<Ship::Console> Console, const std::
return CMD_SUCCESS; return CMD_SUCCESS;
} }
static bool RuppeHandler(std::shared_ptr<Ship::Console> Console, const std::vector<std::string>& args) { static bool RupeeHandler(std::shared_ptr<Ship::Console> Console, const std::vector<std::string>& args) {
if (args.size() < 2) if (args.size() < 2) {
return CMD_FAILED; return CMD_FAILED;
}
int rupeeAmount; int rupeeAmount;
try { try {
@ -432,6 +459,431 @@ static bool StateSlotSelectHandler(std::shared_ptr<Ship::Console> Console, const
return CMD_SUCCESS; return CMD_SUCCESS;
} }
static bool InvisibleHandler(std::shared_ptr<Ship::Console> Console, const std::vector<std::string>& 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<Ship::Console> Console, const std::vector<std::string>& 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<Ship::Console> Console, const std::vector<std::string>& 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<Ship::Console> Console, const std::vector<std::string>& args) {
if (gSaveContext.healthCapacity >= 0x140)
return CMD_FAILED;
Health_GiveHearts(1);
return CMD_SUCCESS;
}
static bool RemoveHeartContainerHandler(std::shared_ptr<Ship::Console> Console, const std::vector<std::string>& args) {
if ((gSaveContext.healthCapacity - 0x10) < 3)
return CMD_FAILED;
Health_RemoveHearts(1);
return CMD_SUCCESS;
}
static bool GravityHandler(std::shared_ptr<Ship::Console> Console, const std::vector<std::string>& 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<Ship::Console> Console, const std::vector<std::string>& 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<Ship::Console> Console, const std::vector<std::string>& args) {
gSaveContext.pendingIceTrapCount++;
return CMD_SUCCESS;
}
static bool DefenseModifierHandler(std::shared_ptr<Ship::Console> Console, const std::vector<std::string>& 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<Ship::Console> Console, const std::vector<std::string>& 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<Ship::Console> Console, const std::vector<std::string>& 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<Ship::Console> Console, const std::vector<std::string>& args) {
Magic_Fill(gGlobalCtx);
return CMD_SUCCESS;
}
static bool EmptyMagicHandler(std::shared_ptr<Ship::Console> Console, const std::vector<std::string>& args) {
gSaveContext.magic = 0;
return CMD_SUCCESS;
}
static bool NoZHandler(std::shared_ptr<Ship::Console> Console, const std::vector<std::string>& 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<Ship::Console> Console, const std::vector<std::string>& 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<Ship::Console> Console, const std::vector<std::string>& 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<Ship::Console> Console, const std::vector<std::string>& 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<Ship::Console> Console, const std::vector<std::string>& 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<Ship::Console> Console, const std::vector<std::string>& 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<Ship::Console> Console, const std::vector<std::string>& 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<Ship::Console> Console, const std::vector<std::string>& 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<std::string, uint16_t> boots {
{ "kokiri", PLAYER_BOOTS_KOKIRI },
{ "iron", PLAYER_BOOTS_IRON },
{ "hover", PLAYER_BOOTS_HOVER },
};
static bool BootsHandler(std::shared_ptr<Ship::Console> Console, const std::vector<std::string>& 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<Ship::Console> Console, const std::vector<std::string>& 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<Ship::Console> Console, const std::vector<std::string>& 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<Ship::Console> Console, const std::vector<std::string>& 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<Ship::Console> Console, const std::vector<std::string>& 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_INTEGER 0
#define VARTYPE_FLOAT 1 #define VARTYPE_FLOAT 1
#define VARTYPE_STRING 2 #define VARTYPE_STRING 2
@ -492,7 +944,6 @@ static bool SetCVarHandler(std::shared_ptr<Ship::Console> Console, const std::ve
return CMD_SUCCESS; return CMD_SUCCESS;
} }
static bool GetCVarHandler(std::shared_ptr<Ship::Console> Console, const std::vector<std::string>& args) { static bool GetCVarHandler(std::shared_ptr<Ship::Console> Console, const std::vector<std::string>& args) {
if (args.size() < 2) if (args.size() < 2)
return CMD_FAILED; return CMD_FAILED;
@ -520,14 +971,41 @@ static bool GetCVarHandler(std::shared_ptr<Ship::Console> Console, const std::ve
} }
void DebugConsole_Init(void) { 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("kill", { KillPlayerHandler, "Commit suicide." });
CMD_REGISTER("map", { LoadSceneHandler, "Load up kak?" }); 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 } {"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 }, CMD_REGISTER("spawn", { ActorSpawnHandler, "Spawn an actor.", { { "actor_id", Ship::ArgumentType::NUMBER },
{ "data", Ship::ArgumentType::NUMBER }, { "data", Ship::ArgumentType::NUMBER },
{ "x", Ship::ArgumentType::PLAYER_POS, true }, { "x", Ship::ArgumentType::PLAYER_POS, true },
@ -537,40 +1015,122 @@ void DebugConsole_Init(void) {
{ "ry", Ship::ArgumentType::PLAYER_ROT, true }, { "ry", Ship::ArgumentType::PLAYER_ROT, true },
{ "rz", 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 },
CMD_REGISTER("pos", { SetPosHandler, "Sets the position of the player.", {
{ "x", Ship::ArgumentType::PLAYER_POS, true },
{ "y", Ship::ArgumentType::PLAYER_POS, true }, { "y", Ship::ArgumentType::PLAYER_POS, true },
{ "z", 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, CMD_REGISTER("set", { SetCVarHandler, "Sets a console variable.", {
"Changes item in a bottle slot.", { "varName", Ship::ArgumentType::TEXT },
{ { "item", Ship::ArgumentType::TEXT }, { "slot", Ship::ArgumentType::NUMBER } } }); { "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("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,
}
}}); }});
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" });
CVar_Load(); CVar_Load();
} }

View File

@ -1,3 +1,31 @@
#pragma once #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); void DebugConsole_Init(void);

View File

@ -1492,6 +1492,8 @@ namespace GameMenuBar {
} }
ImGui::PopStyleVar(3); ImGui::PopStyleVar(3);
ImGui::PopStyleColor(1); 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(); UIWidgets::PaddedSeparator();

View File

@ -64,6 +64,11 @@
#include "Enhancements/item-tables/ItemTableManager.h" #include "Enhancements/item-tables/ItemTableManager.h"
#include "GameMenuBar.hpp" #include "GameMenuBar.hpp"
#ifdef ENABLE_CROWD_CONTROL
#include "Enhancements/crowd-control/CrowdControl.h"
CrowdControl* CrowdControl::Instance;
#endif
OTRGlobals* OTRGlobals::Instance; OTRGlobals* OTRGlobals::Instance;
SaveManager* SaveManager::Instance; SaveManager* SaveManager::Instance;
CustomMessageManager* CustomMessageManager::Instance; CustomMessageManager* CustomMessageManager::Instance;
@ -349,6 +354,11 @@ extern "C" void InitOTR() {
InitItemTracker(); InitItemTracker();
OTRExtScanner(); OTRExtScanner();
VanillaItemTable_Init(); VanillaItemTable_Init();
#ifdef ENABLE_CROWD_CONTROL
CrowdControl::Instance = new CrowdControl();
CrowdControl::Instance->InitCrowdControl();
#endif
} }
extern "C" void DeinitOTR() { extern "C" void DeinitOTR() {

View File

@ -1,6 +1,8 @@
#include "global.h" #include "global.h"
#include "vt.h" #include "vt.h"
#include "soh/Enhancements/debugconsole.h"
//#include <string.h> //#include <string.h>
#ifdef _MSC_VER #ifdef _MSC_VER
@ -226,6 +228,25 @@ void PadMgr_ProcessInputs(PadMgr* padMgr) {
switch (padnow1->err_no) { switch (padnow1->err_no) {
case 0: case 0:
input->cur = *padnow1; 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]) { if (!padMgr->ctrlrIsConnected[i]) {
padMgr->ctrlrIsConnected[i] = true; padMgr->ctrlrIsConnected[i] = true;
osSyncPrintf(VT_FGCOL(YELLOW)); osSyncPrintf(VT_FGCOL(YELLOW));

View File

@ -11,6 +11,8 @@
#include <assert.h> #include <assert.h>
#endif #endif
#include "soh/Enhancements/debugconsole.h"
static uint16_t _doActionTexWidth, _doActionTexHeight = -1; static uint16_t _doActionTexWidth, _doActionTexHeight = -1;
static uint16_t DO_ACTION_TEX_WIDTH() { static uint16_t DO_ACTION_TEX_WIDTH() {
@ -935,7 +937,11 @@ void func_80083108(GlobalContext* globalCtx) {
Interface_ChangeAlpha(50); Interface_ChangeAlpha(50);
} }
} else if (msgCtx->msgMode == MSGMODE_NONE) { } 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) { if (gSaveContext.buttonStatus[0] != BTN_DISABLED) {
sp28 = 1; sp28 = 1;
} }
@ -2913,6 +2919,15 @@ s32 Health_ChangeBy(GlobalContext* globalCtx, s16 healthChange) {
osSyncPrintf(" 増減=%d (now=%d, max=%d) ", healthChange, gSaveContext.health, osSyncPrintf(" 増減=%d (now=%d, max=%d) ", healthChange, gSaveContext.health,
gSaveContext.healthCapacity); 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 // clang-format off
if (healthChange > 0) { Audio_PlaySoundGeneral(NA_SE_SY_HP_RECOVER, &D_801333D4, 4, if (healthChange > 0) { Audio_PlaySoundGeneral(NA_SE_SY_HP_RECOVER, &D_801333D4, 4,
&D_801333E0, &D_801333E0, &D_801333E8); &D_801333E0, &D_801333E0, &D_801333E8);
@ -2922,6 +2937,14 @@ s32 Health_ChangeBy(GlobalContext* globalCtx, s16 healthChange) {
} }
// clang-format on // clang-format on
if (chaosEffectDefenseModifier != 0 && healthChange < 0) {
if (chaosEffectDefenseModifier > 0) {
healthChange /= chaosEffectDefenseModifier;
} else {
healthChange *= abs(chaosEffectDefenseModifier);
}
}
gSaveContext.health += healthChange; gSaveContext.health += healthChange;
if (gSaveContext.health > gSaveContext.healthCapacity) { if (gSaveContext.health > gSaveContext.healthCapacity) {
@ -2956,6 +2979,10 @@ void Health_GiveHearts(s16 hearts) {
gSaveContext.healthCapacity += hearts * 0x10; gSaveContext.healthCapacity += hearts * 0x10;
} }
void Health_RemoveHearts(s16 hearts) {
gSaveContext.healthCapacity -= hearts * 0x10;
}
void Rupees_ChangeBy(s16 rupeeChange) { void Rupees_ChangeBy(s16 rupeeChange) {
gSaveContext.rupeeAccumulator += rupeeChange; gSaveContext.rupeeAccumulator += rupeeChange;
} }
@ -3022,7 +3049,7 @@ void Inventory_ChangeAmmo(s16 item, s16 ammoChange) {
void Magic_Fill(GlobalContext* globalCtx) { void Magic_Fill(GlobalContext* globalCtx) {
if (gSaveContext.magicAcquired) { if (gSaveContext.magicAcquired) {
gSaveContext.unk_13F2 = gSaveContext.unk_13F0; gSaveContext.unk_13F2 = gSaveContext.unk_13F0;
gSaveContext.unk_13F6 = (gSaveContext.doubleMagic * 0x30) + 0x30; gSaveContext.unk_13F6 = (gSaveContext.doubleMagic + 1) * 0x30;
gSaveContext.unk_13F0 = 9; gSaveContext.unk_13F0 = 9;
} }
} }
@ -4739,6 +4766,10 @@ void Interface_Draw(GlobalContext* globalCtx) {
s16 svar6; s16 svar6;
bool fullUi = !CVar_GetS32("gMinimalUI", 0) || !R_MINIMAP_DISABLED || globalCtx->pauseCtx.state != 0; bool fullUi = !CVar_GetS32("gMinimalUI", 0) || !R_MINIMAP_DISABLED || globalCtx->pauseCtx.state != 0;
if (chaosEffectNoUI) {
return;
}
OPEN_DISPS(globalCtx->state.gfxCtx); OPEN_DISPS(globalCtx->state.gfxCtx);
// Invalidate Do Action textures as they may have changed // Invalidate Do Action textures as they may have changed

View File

@ -6,6 +6,8 @@
#include "soh/Enhancements/gameconsole.h" #include "soh/Enhancements/gameconsole.h"
#include <libultraship/ImGuiImpl.h> #include <libultraship/ImGuiImpl.h>
#include "soh/frame_interpolation.h" #include "soh/frame_interpolation.h"
#include "soh/Enhancements/debugconsole.h"
#include <overlays/actors/ovl_En_Niw/z_en_niw.h>
#include <time.h> #include <time.h>
@ -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" // original name: "Game_play_demo_mode_check"
s32 Gameplay_InCsMode(GlobalContext* globalCtx) { s32 Gameplay_InCsMode(GlobalContext* globalCtx) {
return (globalCtx->csCtx.state != CS_STATE_IDLE) || Player_InCsMode(globalCtx); return (globalCtx->csCtx.state != CS_STATE_IDLE) || Player_InCsMode(globalCtx);

View File

@ -5,6 +5,8 @@
#include "objects/object_triforce_spot/object_triforce_spot.h" #include "objects/object_triforce_spot/object_triforce_spot.h"
#include "overlays/actors/ovl_Demo_Effect/z_demo_effect.h" #include "overlays/actors/ovl_Demo_Effect/z_demo_effect.h"
#include "soh/Enhancements/debugconsole.h"
typedef struct { typedef struct {
/* 0x00 */ u8 flag; /* 0x00 */ u8 flag;
/* 0x02 */ u16 textId; /* 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; return false;
} }

View File

@ -764,6 +764,12 @@ void func_80AB70A0(EnNiw* this, GlobalContext* globalCtx) {
this->actionFunc = func_80AB70F8; 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) { void func_80AB70F8(EnNiw* this, GlobalContext* globalCtx) {
this->sfxTimer1 = 100; this->sfxTimer1 = 100;

View File

@ -77,4 +77,17 @@ typedef struct EnNiw {
/* 0x0358 */ EnNiwFeather feathers[20]; /* 0x0358 */ EnNiwFeather feathers[20];
} EnNiw; // size = 0x07B8 } 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 #endif

View File

@ -21,7 +21,8 @@
#include "objects/object_link_child/object_link_child.h" #include "objects/object_link_child/object_link_child.h"
#include "textures/icon_item_24_static/icon_item_24_static.h" #include "textures/icon_item_24_static/icon_item_24_static.h"
#include <soh/Enhancements/custom-message/CustomMessageTypes.h> #include <soh/Enhancements/custom-message/CustomMessageTypes.h>
#include <soh/Enhancements/item-tables/ItemTableTypes.h> #include "soh/Enhancements/item-tables/ItemTableTypes.h"
#include "soh/Enhancements/debugconsole.h"
typedef enum { typedef enum {
/* 0x00 */ KNOB_ANIM_ADULT_L, /* 0x00 */ KNOB_ANIM_ADULT_L,
@ -6021,9 +6022,19 @@ void func_8083DFE0(Player* this, f32* arg1, s16* arg2) {
if (this->swordState == 0) { if (this->swordState == 0) {
float maxSpeed = R_RUN_SPEED_LIMIT / 100.0f; 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) { if (CVar_GetS32("gMMBunnyHood", 0) != 0 && this->currentMask == PLAYER_MASK_BUNNY) {
maxSpeed *= 1.5f; maxSpeed *= 1.5f;
} }
this->linearVelocity = CLAMP(this->linearVelocity, -maxSpeed, maxSpeed); 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); func_80837268(this, &sp2C, &sp2A, 0.018f, globalCtx);
if (!func_8083C484(this, &sp2C, &sp2A)) { 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) { if (CVar_GetS32("gMMBunnyHood", 0) != 0 && this->currentMask == PLAYER_MASK_BUNNY) {
sp2C *= 1.5f; sp2C *= 1.5f;
} }
func_8083DF68(this, sp2C, sp2A); func_8083DF68(this, sp2C, sp2A);
func_8083DDC8(this, globalCtx); func_8083DDC8(this, globalCtx);
@ -10888,6 +10908,39 @@ void Player_Update(Actor* thisx, GlobalContext* globalCtx) {
MREG(53) = this->actor.world.pos.y; MREG(53) = this->actor.world.pos.y;
MREG(54) = this->actor.world.pos.z; MREG(54) = this->actor.world.pos.z;
MREG(55) = this->actor.world.rot.y; 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; static struct_80858AC8 D_80858AC8;