CrowdControl additions & improvements (#5104)

* CrowdControl additions & improvements

* Update after dev merge

* clang format

* Revert "clang format"

This reverts commit 1be5ad18f5.

* clang format
This commit is contained in:
aMannus 2025-07-01 19:54:26 +02:00 committed by GitHub
commit 48d2193fec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 103 additions and 397 deletions

View file

@ -82,7 +82,7 @@ static void RollRandomTrap(uint32_t seed) {
case ADD_SPEED_TRAP:
Audio_PlaySoundGeneral(NA_SE_VO_KZ_MOVE, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale,
&gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb);
GameInteractor::State::RunSpeedModifier = -2;
GameInteractor::State::MovementSpeedMultiplier = 0.5f;
statusTimer = 200;
Notification::Emit({ .message = "Speed Decreased!" });
break;
@ -112,7 +112,7 @@ static void RollRandomTrap(uint32_t seed) {
static void OnPlayerUpdate() {
Player* player = GET_PLAYER(gPlayState);
if (statusTimer == 0) {
GameInteractor::State::RunSpeedModifier = 0;
GameInteractor::State::MovementSpeedMultiplier = 1.0f;
}
if (eventTimer == 0) {
switch (roll) {

View file

@ -1130,7 +1130,7 @@ static bool SpeedModifierHandler(std::shared_ptr<Ship::Console> Console, const s
ERROR_MESSAGE("[SOH] Unexpected arguments passed");
return 1;
}
GameInteractionEffectBase* effect = new GameInteractionEffect::ModifyRunSpeedModifier();
GameInteractionEffectBase* effect = new GameInteractionEffect::ModifyMovementSpeedMultiplier();
try {
dynamic_cast<ParameterizedGameInteractionEffect*>(effect)->parameters[0] = std::stoi(args[1], nullptr, 10);

View file

@ -11,6 +11,7 @@ have functions to both enable and disable said effect.
#include "GameInteractionEffect.h"
#include "GameInteractor.h"
#include <libultraship/bridge.h>
#include "soh/Enhancements/cosmetics/CosmeticsEditor.h"
extern "C" {
#include <z64.h>
@ -371,19 +372,23 @@ void ForceEquipBoots::_Remove() {
GameInteractor::RawAction::ForceEquipBoots(EQUIP_VALUE_BOOTS_KOKIRI);
}
// MARK: - ModifyRunSpeedModifier
GameInteractionEffectQueryResult ModifyRunSpeedModifier::CanBeApplied() {
// MARK: - ModifyMovementSpeedMultiplier
GameInteractionEffectQueryResult ModifyMovementSpeedMultiplier::CanBeApplied() {
if (!GameInteractor::IsSaveLoaded() || GameInteractor::IsGameplayPaused()) {
return GameInteractionEffectQueryResult::TemporarilyNotPossible;
} else {
return GameInteractionEffectQueryResult::Possible;
}
}
void ModifyRunSpeedModifier::_Apply() {
GameInteractor::State::RunSpeedModifier = parameters[0];
void ModifyMovementSpeedMultiplier::_Apply() {
if (parameters[0] == -2) {
GameInteractor::State::MovementSpeedMultiplier = 0.5f;
} else if (parameters[0] == 2) {
GameInteractor::State::MovementSpeedMultiplier = 2.0f;
}
}
void ModifyRunSpeedModifier::_Remove() {
GameInteractor::State::RunSpeedModifier = 0;
void ModifyMovementSpeedMultiplier::_Remove() {
GameInteractor::State::MovementSpeedMultiplier = 1.0f;
}
// MARK: - OneHitKO
@ -485,18 +490,6 @@ void SetCollisionViewer::_Remove() {
GameInteractor::RawAction::SetCollisionViewer(false);
}
// MARK: - SetCosmeticsColor
GameInteractionEffectQueryResult SetCosmeticsColor::CanBeApplied() {
if (!GameInteractor::IsSaveLoaded()) {
return GameInteractionEffectQueryResult::TemporarilyNotPossible;
} else {
return GameInteractionEffectQueryResult::Possible;
}
}
void SetCosmeticsColor::_Apply() {
GameInteractor::RawAction::SetCosmeticsColor(parameters[0], parameters[1]);
}
// MARK: - RandomizeCosmetics
GameInteractionEffectQueryResult RandomizeCosmetics::CanBeApplied() {
if (!GameInteractor::IsSaveLoaded()) {
@ -506,7 +499,7 @@ GameInteractionEffectQueryResult RandomizeCosmetics::CanBeApplied() {
}
}
void RandomizeCosmetics::_Apply() {
GameInteractor::RawAction::RandomizeCosmeticsColors(true);
CosmeticsEditor_RandomizeAll();
}
// MARK: - PressButton

View file

@ -156,7 +156,7 @@ class ForceEquipBoots : public RemovableGameInteractionEffect, public Parameteri
void _Remove() override;
};
class ModifyRunSpeedModifier : public RemovableGameInteractionEffect, public ParameterizedGameInteractionEffect {
class ModifyMovementSpeedMultiplier : public RemovableGameInteractionEffect, public ParameterizedGameInteractionEffect {
GameInteractionEffectQueryResult CanBeApplied() override;
void _Apply() override;
void _Remove() override;
@ -200,11 +200,6 @@ class SetCollisionViewer : public RemovableGameInteractionEffect {
void _Remove() override;
};
class SetCosmeticsColor : public GameInteractionEffectBase, public ParameterizedGameInteractionEffect {
GameInteractionEffectQueryResult CanBeApplied() override;
void _Apply() override;
};
class RandomizeCosmetics : public GameInteractionEffectBase {
GameInteractionEffectQueryResult CanBeApplied() override;
void _Apply() override;

View file

@ -76,7 +76,7 @@ uint8_t GameInteractor_PacifistModeActive();
uint8_t GameInteractor_DisableZTargetingActive();
uint8_t GameInteractor_ReverseControlsActive();
int32_t GameInteractor_DefenseModifier();
int32_t GameInteractor_RunSpeedModifier();
float GameInteractor_MovementSpeedMultiplier();
GIGravityLevel GameInteractor_GravityLevel();
uint32_t GameInteractor_GetEmulatedButtons();
void GameInteractor_SetEmulatedButtons(uint32_t buttons);
@ -204,7 +204,7 @@ class GameInteractor {
static bool DisableZTargetingActive;
static bool ReverseControlsActive;
static int32_t DefenseModifier;
static int32_t RunSpeedModifier;
static float MovementSpeedMultiplier;
static GIGravityLevel GravityLevel;
static uint32_t EmulatedButtons;
static uint8_t RandomBombFuseTimerActive;
@ -566,8 +566,6 @@ class GameInteractor {
static void ClearAssignedButtons(uint8_t buttonSet);
static void SetTimeOfDay(uint32_t time);
static void SetCollisionViewer(bool active);
static void SetCosmeticsColor(uint8_t cosmeticCategory, uint8_t colorValue);
static void RandomizeCosmeticsColors(bool excludeBiddingWarColors);
static void EmulateButtonPress(int32_t button);
static void AddOrTakeAmmo(int16_t amount, int16_t item);
static void EmulateRandomButtonPress(uint32_t chancePercentage = 100);
@ -575,8 +573,10 @@ class GameInteractor {
static void SetPlayerInvincibility(bool active);
static void ClearCutscenePointer();
static GameInteractionEffectQueryResult SpawnEnemyWithOffset(uint32_t enemyId, int32_t enemyParams);
static GameInteractionEffectQueryResult SpawnActor(uint32_t actorId, int32_t actorParams);
static GameInteractionEffectQueryResult SpawnEnemyWithOffset(uint32_t enemyId, int32_t enemyParams,
std::string nameTag = "");
static GameInteractionEffectQueryResult SpawnActor(uint32_t actorId, int32_t actorParams,
std::string nameTag = "");
};
};

View file

@ -1,9 +1,9 @@
#include "GameInteractor.h"
#include <libultraship/bridge.h>
#include "soh/Enhancements/cosmetics/CosmeticsEditor.h"
#include "soh/Enhancements/randomizer/3drando/random.hpp"
#include <math.h>
#include "soh/Enhancements/debugger/colViewer.h"
#include "soh/Enhancements/nametag.h"
extern "C" {
#include "variables.h"
@ -405,125 +405,6 @@ void GameInteractor::RawAction::SetCollisionViewer(bool active) {
}
}
void GameInteractor::RawAction::SetCosmeticsColor(uint8_t cosmeticCategory, uint8_t colorValue) {
Color_RGBA8 newColor;
newColor.r = 255;
newColor.g = 255;
newColor.b = 255;
newColor.a = 255;
switch (colorValue) {
case GI_COLOR_RED:
newColor.r = 200;
newColor.g = 30;
newColor.b = 30;
break;
case GI_COLOR_GREEN:
newColor.r = 50;
newColor.g = 200;
newColor.b = 50;
break;
case GI_COLOR_BLUE:
newColor.r = 50;
newColor.g = 50;
newColor.b = 200;
break;
case GI_COLOR_ORANGE:
newColor.r = 200;
newColor.g = 120;
newColor.b = 0;
break;
case GI_COLOR_YELLOW:
newColor.r = 234;
newColor.g = 240;
newColor.b = 33;
break;
case GI_COLOR_PURPLE:
newColor.r = 144;
newColor.g = 13;
newColor.b = 178;
break;
case GI_COLOR_PINK:
newColor.r = 215;
newColor.g = 93;
newColor.b = 246;
break;
case GI_COLOR_BROWN:
newColor.r = 108;
newColor.g = 72;
newColor.b = 15;
break;
case GI_COLOR_BLACK:
newColor.r = 0;
newColor.g = 0;
newColor.b = 0;
break;
default:
break;
}
switch (cosmeticCategory) {
case GI_COSMETICS_TUNICS:
CVarSetColor(CVAR_COSMETIC("Link.KokiriTunic.Value"), newColor);
CVarSetInteger(CVAR_COSMETIC("Link.KokiriTunic.Changed"), 1);
CVarSetColor(CVAR_COSMETIC("Link.GoronTunic.Value"), newColor);
CVarSetInteger(CVAR_COSMETIC("Link.GoronTunic.Changed"), 1);
CVarSetColor(CVAR_COSMETIC("Link.ZoraTunic.Value"), newColor);
CVarSetInteger(CVAR_COSMETIC("Link.ZoraTunic.Changed"), 1);
break;
case GI_COSMETICS_NAVI:
CVarSetColor(CVAR_COSMETIC("Navi.EnemyPrimary.Value"), newColor);
CVarSetInteger(CVAR_COSMETIC("Navi.EnemyPrimary.Changed"), 1);
CVarSetColor(CVAR_COSMETIC("Navi.EnemySecondary.Value"), newColor);
CVarSetInteger(CVAR_COSMETIC("Navi.EnemySecondary.Changed"), 1);
CVarSetColor(CVAR_COSMETIC("Navi.IdlePrimary.Value"), newColor);
CVarSetInteger(CVAR_COSMETIC("Navi.IdlePrimary.Changed"), 1);
CVarSetColor(CVAR_COSMETIC("Navi.IdleSecondary.Value"), newColor);
CVarSetInteger(CVAR_COSMETIC("Navi.IdleSecondary.Changed"), 1);
CVarSetColor(CVAR_COSMETIC("Navi.NPCPrimary.Value"), newColor);
CVarSetInteger(CVAR_COSMETIC("Navi.NPCPrimary.Changed"), 1);
CVarSetColor(CVAR_COSMETIC("Navi.NPCSecondary.Value"), newColor);
CVarSetInteger(CVAR_COSMETIC("Navi.NPCSecondary.Changed"), 1);
CVarSetColor(CVAR_COSMETIC("Navi.PropsPrimary.Value"), newColor);
CVarSetInteger(CVAR_COSMETIC("Navi.PropsPrimary.Changed"), 1);
CVarSetColor(CVAR_COSMETIC("Navi.PropsSecondary.Value"), newColor);
CVarSetInteger(CVAR_COSMETIC("Navi.PropsSecondary.Changed"), 1);
break;
case GI_COSMETICS_HAIR:
CVarSetColor(CVAR_COSMETIC("Link.Hair.Value"), newColor);
CVarSetInteger(CVAR_COSMETIC("Link.Hair.Changed"), 1);
break;
}
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
ApplyOrResetCustomGfxPatches();
}
void GameInteractor::RawAction::RandomizeCosmeticsColors(bool excludeBiddingWarColors) {
const char* cvarsToLock[12] = {
CVAR_COSMETIC("Link.KokiriTunic.Locked"), CVAR_COSMETIC("Link.GoronTunic.Locked"),
CVAR_COSMETIC("Link.ZoraTunic.Locked"), CVAR_COSMETIC("Navi.EnemyPrimary.Locked"),
CVAR_COSMETIC("Navi.EnemySecondary.Locked"), CVAR_COSMETIC("Navi.IdlePrimary.Locked"),
CVAR_COSMETIC("Navi.IdleSecondary.Locked"), CVAR_COSMETIC("Navi.NPCPrimary.Locked"),
CVAR_COSMETIC("Navi.NPCSecondary.Locked"), CVAR_COSMETIC("Navi.PropsPrimary.Locked"),
CVAR_COSMETIC("Navi.PropsSecondary.Locked"), CVAR_COSMETIC("Link.Hair.Locked")
};
if (excludeBiddingWarColors) {
for (uint8_t i = 0; i < 12; i++) {
CVarSetInteger(cvarsToLock[i], 1);
}
}
CosmeticsEditor_RandomizeAll();
if (excludeBiddingWarColors) {
for (uint8_t i = 0; i < 12; i++) {
CVarSetInteger(cvarsToLock[i], 0);
}
}
}
void GameInteractor::RawAction::EmulateButtonPress(int32_t button) {
GameInteractor::State::EmulatedButtons |= button;
}
@ -580,8 +461,8 @@ void GameInteractor::RawAction::ClearCutscenePointer() {
gPlayState->csCtx.segment = &null_cs;
}
GameInteractionEffectQueryResult GameInteractor::RawAction::SpawnEnemyWithOffset(uint32_t enemyId,
int32_t enemyParams) {
GameInteractionEffectQueryResult GameInteractor::RawAction::SpawnEnemyWithOffset(uint32_t enemyId, int32_t enemyParams,
std::string nameTag) {
if (!GameInteractor::CanSpawnActor()) {
return GameInteractionEffectQueryResult::TemporarilyNotPossible;
@ -651,15 +532,29 @@ GameInteractionEffectQueryResult GameInteractor::RawAction::SpawnEnemyWithOffset
pos.x += 10;
pos.y += 10;
pos.z += 10;
if (Actor_Spawn(&gPlayState->actorCtx, gPlayState, enemyId, pos.x, pos.y, pos.z, 0, 0, 0, enemyParams, 0) ==
NULL) {
Actor* actor =
Actor_Spawn(&gPlayState->actorCtx, gPlayState, enemyId, pos.x, pos.y, pos.z, 0, 0, 0, enemyParams, 0);
if (actor == NULL) {
return GameInteractionEffectQueryResult::TemporarilyNotPossible;
}
if (nameTag != "" && CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("EnemyNameTags"), 0)) {
NameTag_RegisterForActor(actor, nameTag.c_str());
}
if (CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("SpawnedEnemiesIgnoredIngame"), 0)) {
Actor_ChangeCategory(gPlayState, &gPlayState->actorCtx, actor, ACTORCAT_NPC);
}
}
return GameInteractionEffectQueryResult::Possible;
} else {
if (Actor_Spawn(&gPlayState->actorCtx, gPlayState, enemyId, pos.x, pos.y, pos.z, 0, 0, 0, enemyParams, 0) !=
NULL) {
Actor* actor =
Actor_Spawn(&gPlayState->actorCtx, gPlayState, enemyId, pos.x, pos.y, pos.z, 0, 0, 0, enemyParams, 0);
if (actor != NULL) {
if (nameTag != "" && CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("EnemyNameTags"), 0)) {
NameTag_RegisterForActor(actor, nameTag.c_str());
}
if (CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("SpawnedEnemiesIgnoredIngame"), 0)) {
Actor_ChangeCategory(gPlayState, &gPlayState->actorCtx, actor, ACTORCAT_NPC);
}
return GameInteractionEffectQueryResult::Possible;
}
}
@ -667,7 +562,8 @@ GameInteractionEffectQueryResult GameInteractor::RawAction::SpawnEnemyWithOffset
return GameInteractionEffectQueryResult::TemporarilyNotPossible;
}
GameInteractionEffectQueryResult GameInteractor::RawAction::SpawnActor(uint32_t actorId, int32_t actorParams) {
GameInteractionEffectQueryResult GameInteractor::RawAction::SpawnActor(uint32_t actorId, int32_t actorParams,
std::string nameTag) {
if (!GameInteractor::CanSpawnActor()) {
return GameInteractionEffectQueryResult::TemporarilyNotPossible;
@ -684,6 +580,9 @@ GameInteractionEffectQueryResult GameInteractor::RawAction::SpawnActor(uint32_t
return GameInteractionEffectQueryResult::TemporarilyNotPossible;
}
if (nameTag != "" && CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("EnemyNameTags"), 0)) {
NameTag_RegisterForActor((Actor*)cucco, nameTag.c_str());
}
cucco->actionFunc = func_80AB70A0_nocutscene;
return GameInteractionEffectQueryResult::Possible;
} else if (actorId == ACTOR_EN_BOM) {
@ -703,8 +602,15 @@ GameInteractionEffectQueryResult GameInteractor::RawAction::SpawnActor(uint32_t
return GameInteractionEffectQueryResult::Possible;
} else {
// Generic spawn an actor at Link's position
if (Actor_Spawn(&gPlayState->actorCtx, gPlayState, actorId, player->actor.world.pos.x,
player->actor.world.pos.y, player->actor.world.pos.z, 0, 0, 0, actorParams, 0) != NULL) {
Actor* actor = Actor_Spawn(&gPlayState->actorCtx, gPlayState, actorId, player->actor.world.pos.x,
player->actor.world.pos.y, player->actor.world.pos.z, 0, 0, 0, actorParams, 0);
if (actor != NULL) {
if (nameTag != "" && CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("EnemyNameTags"), 0)) {
NameTag_RegisterForActor((Actor*)actor, nameTag.c_str());
}
if (CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("SpawnedEnemiesIgnoredIngame"), 0)) {
Actor_ChangeCategory(gPlayState, &gPlayState->actorCtx, actor, ACTORCAT_NPC);
}
return GameInteractionEffectQueryResult::Possible;
}
}

View file

@ -10,7 +10,7 @@ bool GameInteractor::State::PacifistModeActive = 0;
bool GameInteractor::State::DisableZTargetingActive = 0;
bool GameInteractor::State::ReverseControlsActive = 0;
int32_t GameInteractor::State::DefenseModifier = 0;
int32_t GameInteractor::State::RunSpeedModifier = 0;
float GameInteractor::State::MovementSpeedMultiplier = 1.0f;
GIGravityLevel GameInteractor::State::GravityLevel = GI_GRAVITY_LEVEL_NORMAL;
uint32_t GameInteractor::State::EmulatedButtons = 0;
uint8_t GameInteractor::State::RandomBombFuseTimerActive = 0;
@ -81,8 +81,8 @@ int32_t GameInteractor_DefenseModifier() {
}
// MARK: - GameInteractor::State::DisableCameraRotationActive
int32_t GameInteractor_RunSpeedModifier() {
return GameInteractor::State::RunSpeedModifier;
float GameInteractor_MovementSpeedMultiplier() {
return GameInteractor::State::MovementSpeedMultiplier;
}
// MARK: - GameInteractor::State::DisableCameraRotationActive

View file

@ -136,9 +136,11 @@ void CrowdControl::EmitMessage(uint32_t eventId, long timeRemaining, EffectResul
CrowdControl::EffectResult CrowdControl::ExecuteEffect(Effect* effect) {
GameInteractionEffectQueryResult giResult;
if (effect->category == kEffectCatSpawnEnemy) {
giResult = GameInteractor::RawAction::SpawnEnemyWithOffset(effect->spawnParams[0], effect->spawnParams[1]);
giResult = GameInteractor::RawAction::SpawnEnemyWithOffset(effect->spawnParams[0], effect->spawnParams[1],
effect->viewerName);
} else if (effect->category == kEffectCatSpawnActor) {
giResult = GameInteractor::RawAction::SpawnActor(effect->spawnParams[0], effect->spawnParams[1]);
giResult =
GameInteractor::RawAction::SpawnActor(effect->spawnParams[0], effect->spawnParams[1], effect->viewerName);
} else {
giResult = GameInteractor::ApplyEffect(effect->giEffect);
}
@ -185,6 +187,7 @@ CrowdControl::Effect* CrowdControl::ParseMessage(nlohmann::json dataReceived) {
Effect* effect = new Effect();
effect->lastExecutionResult = EffectResult::Initiate;
effect->id = dataReceived["id"];
effect->viewerName = dataReceived["viewer"];
auto parameters = dataReceived["parameters"];
uint32_t receivedParameter = 0;
auto effectName = dataReceived["code"].get<std::string>();
@ -301,13 +304,13 @@ CrowdControl::Effect* CrowdControl::ParseMessage(nlohmann::json dataReceived) {
case kEffectIncreaseSpeed:
effect->category = kEffectCatSpeed;
effect->timeRemaining = 30000;
effect->giEffect = new GameInteractionEffect::ModifyRunSpeedModifier();
effect->giEffect = new GameInteractionEffect::ModifyMovementSpeedMultiplier();
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = 2;
break;
case kEffectDecreaseSpeed:
effect->category = kEffectCatSpeed;
effect->timeRemaining = 30000;
effect->giEffect = new GameInteractionEffect::ModifyRunSpeedModifier();
effect->giEffect = new GameInteractionEffect::ModifyMovementSpeedMultiplier();
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = -2;
break;
case kEffectLowGravity:
@ -620,147 +623,6 @@ CrowdControl::Effect* CrowdControl::ParseMessage(nlohmann::json dataReceived) {
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_TP_DEST_PRELUDE;
break;
// Tunic Color (Bidding War)
case kEffectTunicRed:
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_RED;
break;
case kEffectTunicGreen:
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_GREEN;
break;
case kEffectTunicBlue:
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_BLUE;
break;
case kEffectTunicOrange:
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_ORANGE;
break;
case kEffectTunicYellow:
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_YELLOW;
break;
case kEffectTunicPurple:
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_PURPLE;
break;
case kEffectTunicPink:
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_PINK;
break;
case kEffectTunicBrown:
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_BROWN;
break;
case kEffectTunicBlack:
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_TUNICS;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_BLACK;
break;
// Navi Color (Bidding War)
case kEffectNaviRed:
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_RED;
break;
case kEffectNaviGreen:
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_GREEN;
break;
case kEffectNaviBlue:
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_BLUE;
break;
case kEffectNaviOrange:
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_ORANGE;
break;
case kEffectNaviYellow:
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_YELLOW;
break;
case kEffectNaviPurple:
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_PURPLE;
break;
case kEffectNaviPink:
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_PINK;
break;
case kEffectNaviBrown:
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_BROWN;
break;
case kEffectNaviBlack:
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_NAVI;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_BLACK;
break;
// Link's Hair Color (Bidding War)
case kEffectHairRed:
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_RED;
break;
case kEffectHairGreen:
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_GREEN;
break;
case kEffectHairBlue:
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_BLUE;
break;
case kEffectHairOrange:
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_ORANGE;
break;
case kEffectHairYellow:
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_YELLOW;
break;
case kEffectHairPurple:
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_PURPLE;
break;
case kEffectHairPink:
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_PINK;
break;
case kEffectHairBrown:
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_BROWN;
break;
case kEffectHairBlack:
effect->giEffect = new GameInteractionEffect::SetCosmeticsColor();
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[0] = GI_COSMETICS_HAIR;
dynamic_cast<ParameterizedGameInteractionEffect*>(effect->giEffect)->parameters[1] = GI_COLOR_BLACK;
break;
default:
break;
}

View file

@ -56,6 +56,7 @@ class CrowdControl : public Network {
uint32_t category = 0;
long timeRemaining;
GameInteractionEffectBase* giEffect;
std::string viewerName;
// Metadata used while executing (only for timed effects)
bool isPaused;

View file

@ -124,50 +124,5 @@ public class ShipOfHarkinian : SimpleTCPPack<SimpleTCPServerConnector>
new("Requiem Destination", "tp_requiem") { Category = "Teleport Player", Price = 100, Description = "Teleport the player to Desert Colossus." },
new("Nocturne Destination", "tp_nocturne") { Category = "Teleport Player", Price = 100, Description = "Teleport the player to the Raveyard." },
new("Prelude Destination", "tp_prelude") { Category = "Teleport Player", Price = 100, Description = "Teleport the player to the Temple of Time." },
// Tunic Color (Bidding War)
new("Tunic Color", "tunic", ItemKind.BidWar)
{
Parameters = new ParameterDef("Color", "color_tunic_param",
new Parameter("Red", "red"),
new Parameter("Green", "green"),
new Parameter("Blue", "blue"),
new Parameter("Orange", "orange"),
new Parameter("Yellow", "yellow"),
new Parameter("Purple", "purple"),
new Parameter("Pink", "pink"),
new Parameter("Brown", "brown"),
new Parameter("Black", "black"))
},
// Navi Color (Bidding War)
new("Navi Color", "navi", ItemKind.BidWar)
{
Parameters = new ParameterDef("Color", "color_navi_param",
new Parameter("Red", "red"),
new Parameter("Green", "green"),
new Parameter("Blue", "blue"),
new Parameter("Orange", "orange"),
new Parameter("Yellow", "yellow"),
new Parameter("Purple", "purple"),
new Parameter("Pink", "pink"),
new Parameter("Brown", "brown"),
new Parameter("Black", "black"))
},
// Link's Hair Color (Bidding War)
new("Link's Hair Color", "hair", ItemKind.BidWar)
{
Parameters = new ParameterDef("Color", "color_hair_param",
new Parameter("Red", "red"),
new Parameter("Green", "green"),
new Parameter("Blue", "blue"),
new Parameter("Orange", "orange"),
new Parameter("Yellow", "yellow"),
new Parameter("Purple", "purple"),
new Parameter("Pink", "pink"),
new Parameter("Brown", "brown"),
new Parameter("Black", "black"))
}
};
}

View file

@ -242,8 +242,8 @@ GameInteractionEffectBase* Sail::EffectFromJson(nlohmann::json payload) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "ModifyRunSpeedModifier") {
auto effect = new GameInteractionEffect::ModifyRunSpeedModifier();
} else if (name == "ModifyMovementSpeedMultiplier") {
auto effect = new GameInteractionEffect::ModifyMovementSpeedMultiplier();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
@ -282,13 +282,6 @@ GameInteractionEffectBase* Sail::EffectFromJson(nlohmann::json payload) {
return effect;
} else if (name == "SetCollisionViewer") {
return new GameInteractionEffect::SetCollisionViewer();
} else if (name == "SetCosmeticsColor") {
auto effect = new GameInteractionEffect::SetCosmeticsColor();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
}
return effect;
} else if (name == "RandomizeCosmetics") {
return new GameInteractionEffect::RandomizeCosmetics();
} else if (name == "PressButton") {

View file

@ -96,21 +96,15 @@ void SohMenu::AddMenuNetwork() {
AddSidebarEntry("Network", path.sidebarName, 3);
path.column = SECTION_COLUMN_1;
AddWidget(path, "About Crowd Control", WIDGET_SEPARATOR_TEXT);
AddWidget(path,
"Crowd Control is a platform that allows viewers to interact "
"with a streamer's game in real time.\n"
"\n"
"Click the question mark to copy the link to the Crowd Control "
"website to your clipboard.",
"Please head over to www.crowdcontrol.live for more information!",
WIDGET_TEXT);
AddWidget(path, ICON_FA_CLIPBOARD "##CrowdControl", WIDGET_BUTTON)
.Callback([](WidgetInfo& info) {
ImGui::SetClipboardText("https://crowdcontrol.live");
Notification::Emit({
.message = "Copied to clipboard",
});
})
.Options(ButtonOptions().Tooltip("https://crowdcontrol.live"));
AddWidget(path, "Connect to Crowd Control", WIDGET_SEPARATOR_TEXT);
AddWidget(path, "Host & Port", WIDGET_CUSTOM).CustomFunction([](WidgetInfo& info) {
ImGui::BeginDisabled(CrowdControl::Instance->isEnabled || CVarGetInteger(CVAR_SETTING("DisableChanges"), 0));
ImGui::Text("%s", info.name.c_str());
@ -155,14 +149,25 @@ void SohMenu::AddMenuNetwork() {
CrowdControl::Instance->Enable();
}
});
AddWidget(path, "Connecting...##CrowdControl", WIDGET_TEXT).PreFunc([](WidgetInfo& info) {
AddWidget(path, "Connecting...", WIDGET_TEXT).PreFunc([](WidgetInfo& info) {
info.isHidden = !CrowdControl::Instance->isEnabled;
if (CrowdControl::Instance->isConnected) {
info.name = "Connected##CrowdControl";
info.name = "Connected";
} else {
info.name = "Connecting...##CrowdControl";
info.name = "Connecting...";
}
});
AddWidget(path, "Additional Settings", WIDGET_SEPARATOR_TEXT);
AddWidget(path, "Enemy Name Tags", WIDGET_CVAR_CHECKBOX)
.CVar(CVAR_REMOTE_CROWD_CONTROL("EnemyNameTags"))
.RaceDisable(true)
.Options(CheckboxOptions().Tooltip(
"When viewers spawn enemies, the enemy will have a name tag above them with the viewer's name."));
AddWidget(path, "Spawned Enemies Ignored Ingame", WIDGET_CVAR_CHECKBOX)
.CVar(CVAR_REMOTE_CROWD_CONTROL("SpawnedEnemiesIgnoredIngame"))
.RaceDisable(true)
.Options(CheckboxOptions().Tooltip("Enemies spawned by CrowdControl won't be considered for \"clear enemy "
"rooms\", so they don't need to be killed to complete these rooms."));
}
} // namespace SohGui

View file

@ -1284,8 +1284,16 @@ void Actor_UpdatePos(Actor* actor) {
}
void Actor_UpdateVelocityXZGravity(Actor* actor) {
actor->velocity.x = Math_SinS(actor->world.rot.y) * actor->speedXZ;
actor->velocity.z = Math_CosS(actor->world.rot.y) * actor->speedXZ;
Player* player = GET_PLAYER(gPlayState);
uint8_t inCutscene = player->stateFlags1 & PLAYER_STATE1_CLIMBING_LADDER ||
player->stateFlags1 & PLAYER_STATE1_IN_CUTSCENE ||
player->stateFlags2 & PLAYER_STATE2_CRAWLING;
f32 speedModifier = 1.0f;
if (actor->id == ACTOR_PLAYER && !inCutscene) {
speedModifier = GameInteractor_MovementSpeedMultiplier();
}
actor->velocity.x = Math_SinS(actor->world.rot.y) * actor->speedXZ * speedModifier;
actor->velocity.z = Math_CosS(actor->world.rot.y) * actor->speedXZ * speedModifier;
actor->velocity.y += actor->gravity;
if (actor->velocity.y < actor->minVelocityY) {

View file

@ -58,6 +58,11 @@ void EnAttackNiw_Init(Actor* thisx, PlayState* play) {
this->actor.flags &= ~ACTOR_FLAG_ATTENTION_ENABLED;
this->actor.shape.rot.y = this->actor.world.rot.y = (Rand_ZeroOne() - 0.5f) * 60000.0f;
this->actionFunc = func_809B5670;
if (CVarGetInteger("gCrowdControl", 0) &&
CVarGetInteger(CVAR_REMOTE_CROWD_CONTROL("SpawnedEnemiesIgnoredIngame"), 0)) {
Actor_ChangeCategory(gPlayState, &gPlayState->actorCtx, this, ACTORCAT_NPC);
}
}
void EnAttackNiw_Destroy(Actor* thisx, PlayState* play) {

View file

@ -7124,15 +7124,6 @@ void func_8083DFE0(Player* this, f32* arg1, s16* arg2) {
if (this->meleeWeaponState == 0) {
float maxSpeed = R_RUN_SPEED_LIMIT / 100.0f;
int32_t giSpeedModifier = GameInteractor_RunSpeedModifier();
if (giSpeedModifier != 0) {
if (giSpeedModifier > 0) {
maxSpeed *= giSpeedModifier;
} else {
maxSpeed /= abs(giSpeedModifier);
}
}
if (CVarGetInteger(CVAR_ENHANCEMENT("MMBunnyHood"), BUNNY_HOOD_VANILLA) == BUNNY_HOOD_FAST_AND_JUMP &&
this->currentMask == PLAYER_MASK_BUNNY) {
maxSpeed *= 1.5f;
@ -8873,14 +8864,6 @@ void Player_Action_80842180(Player* this, PlayState* play) {
Player_GetMovementSpeedAndYaw(this, &sp2C, &sp2A, SPEED_MODE_CURVED, play);
if (!func_8083C484(this, &sp2C, &sp2A)) {
int32_t giSpeedModifier = GameInteractor_RunSpeedModifier();
if (giSpeedModifier != 0) {
if (giSpeedModifier > 0) {
sp2C *= giSpeedModifier;
} else {
sp2C /= abs(giSpeedModifier);
}
}
if (CVarGetInteger(CVAR_ENHANCEMENT("MMBunnyHood"), BUNNY_HOOD_VANILLA) != BUNNY_HOOD_VANILLA &&
this->currentMask == PLAYER_MASK_BUNNY) {