Rework flow impl on top of FlowService

This commit is contained in:
Luke Street
2026-08-21 01:43:22 -06:00
parent 5d1dda7e94
commit 932c3aaba7
11 changed files with 1108 additions and 925 deletions
+301 -344
View File
@@ -1,6 +1,5 @@
#include "messages.hpp"
#include "custom_flow_ids.hpp"
#include "randomizer_context.hpp"
#include "stages.h"
#include "tools.h"
@@ -12,21 +11,17 @@
#include "d/d_save.h"
#include "d/d_stage.h"
#include <mods/bits.hpp>
#include <mods/svc/flow.hpp>
#include <mods/svc/log.hpp>
#include <fmt/format.h>
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstring>
#include <limits>
#include <span>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
@@ -34,17 +29,11 @@ namespace randomizer::messages {
namespace {
constexpr uint16_t kMessageGroupCount = 9;
constexpr uint16_t kLegacyQueryEventFlag = 53;
constexpr uint16_t kLegacyQueryChangeTime = 54;
constexpr uint16_t kLegacyQueryReturnToSpawn = 55;
constexpr uint8_t kLegacyEventNoOp = 43;
constexpr uint8_t kLegacyEventChangeTime = 44;
constexpr uint8_t kLegacyEventReturnToSpawn = 45;
constexpr uint8_t kLegacyEventSetTrackerFlag = 46;
constexpr uint8_t kLegacyEventRemoveTradeItem = 47;
constexpr uint32_t kPoeSoulGetMessage = 325;
constexpr uint32_t kSkyCharacterGetMessage = 335;
using LegacyIdMap = std::unordered_map<uint16_t, uint16_t>;
using MessageIdMaps = std::array<LegacyIdMap, kMessageGroupCount>;
using MessageIdMaps = std::array<std::unordered_map<std::string, MessageId>, kMessageGroupCount>;
using NodeIdMaps = std::array<std::unordered_map<std::string, uint16_t>, kMessageGroupCount>;
mods::flow::Query s_eventFlagQuery;
mods::flow::Query s_changeTimeQuery;
@@ -61,6 +50,15 @@ uint32_t read_parameter(const uint8_t parameters[4]) {
static_cast<uint32_t>(parameters[2]) << 8 | parameters[3];
}
std::array<uint8_t, 4> parameter_bytes(uint32_t parameter) {
return {
static_cast<uint8_t>(parameter >> 24),
static_cast<uint8_t>(parameter >> 16),
static_cast<uint8_t>(parameter >> 8),
static_cast<uint8_t>(parameter),
};
}
uint16_t query_event_flag(ModContext*, const FlowQueryContext* query, void*) {
if (query == nullptr || query->parameter >= std::size(dSv_event_flag_c::saveBitLabels)) {
return 0;
@@ -120,65 +118,46 @@ std::vector<uint8_t> encoded_text(const std::string& text) {
return result;
}
MessageEntryData default_message_entry() {
MessageEntryData entry{};
entry.bytes[8] = 0x24;
entry.bytes[12] = 0xff;
entry.bytes[16] = 0x02;
entry.bytes[17] = 0x03;
entry.bytes[18] = 0x04;
return entry;
}
MessageEntryData message_entry(const RandomizerContext& context, uint32_t legacyKey) {
MessageEntryData entry = default_message_entry();
const auto found = context.mAttributeOverrides.find(legacyKey);
if (found != context.mAttributeOverrides.end()) {
std::memcpy(entry.bytes, found->second.data(), sizeof(entry.bytes));
std::fill_n(entry.bytes, 6, uint8_t{});
}
return entry;
}
std::vector<uint32_t> text_keys(const RandomizerContext& context, uint16_t group) {
std::unordered_set<uint32_t> unique;
for (const auto& [language, overrides] : context.mTextOverrides) {
for (const auto& [key, text] : overrides) {
if (key >> 16 == group) {
unique.insert(key);
}
}
}
std::vector<uint32_t> result{unique.begin(), unique.end()};
std::ranges::sort(result);
return result;
mods::flow::MessageStyle message_style(const RandomizerContext::MessageStyleData& source) {
return mods::flow::MessageStyle{}
.event_label_id(source.eventLabelId)
.speaker(source.speaker)
.box_kind(static_cast<MessageBoxKind>(source.boxKind))
.draw_type(static_cast<MessageDrawType>(source.drawType))
.box_position(static_cast<MessageBoxPosition>(source.boxPosition))
.line_alignment(source.lineAlignment)
.speaker_mood(source.speakerMood)
.camera_attr(source.cameraAttr)
.talk_anim(source.talkAnim)
.face_anim(source.faceAnim)
.trailing_data(source.trailingData);
}
ModResult register_custom_messages(const RandomizerContext& context, MessageIdMaps& messageIds) {
const auto keys = text_keys(context, CUSTOM_BMG_GROUP);
for (uint16_t group = 0; group < kMessageGroupCount; ++group) {
for (const uint32_t key : keys) {
std::vector<mods::flow::MessageVariant> variants;
for (const auto& [language, overrides] : context.mTextOverrides) {
const auto found = overrides.find(key);
if (found == overrides.end() || language < MESSAGE_LANGUAGE_ENGLISH ||
language > MESSAGE_LANGUAGE_ITALIAN)
{
continue;
}
variants.emplace_back(static_cast<MessageLanguage>(language),
message_entry(context, key), encoded_text(found->second));
}
if (variants.empty()) {
return MOD_INVALID_ARGUMENT;
}
auto message = mods::flow::register_message(group, variants);
if (!message) {
return message.result();
}
messageIds[group].emplace(static_cast<uint16_t>(key), message.id());
s_messages.push_back(std::move(message));
for (const auto& definition : context.mCustomMessages) {
if (definition.group >= kMessageGroupCount || definition.name.empty() ||
messageIds[definition.group].contains(definition.name))
{
return MOD_INVALID_ARGUMENT;
}
const auto style = message_style(definition.style);
std::vector<mods::flow::MessageVariant> variants;
for (const auto& [language, text] : definition.text) {
if (language < MESSAGE_LANGUAGE_ENGLISH || language > MESSAGE_LANGUAGE_ITALIAN) {
continue;
}
variants.emplace_back(static_cast<MessageLanguage>(language),
style.data(), encoded_text(text));
}
if (variants.empty()) {
return MOD_INVALID_ARGUMENT;
}
auto message = mods::flow::register_message(definition.group, variants);
if (!message) {
return message.result();
}
messageIds[definition.group].emplace(definition.name, message.id());
s_messages.push_back(std::move(message));
}
return MOD_OK;
}
@@ -200,14 +179,12 @@ bool formatted_override(
}
uint32_t value = 0;
// For item counts, execItemGet hasn't run yet, so add one to the count
// Message resolution runs before the acquisition updates these counters.
switch (key) {
case 325: // Group 0, id 325
// Poe Soul get item text
case kPoeSoulGetMessage:
value = dComIfGs_getPohSpiritNum() + 1;
break;
case 335: // Group 0, id 335
// Sky book characters get item text
case kSkyCharacterGetMessage:
value = getAncientDocumentNum() + 1;
break;
default:
@@ -222,338 +199,318 @@ bool formatted_override(
}
ModResult register_native_overrides(const RandomizerContext& context) {
for (uint16_t group = 0; group < kMessageGroupCount; ++group) {
for (const uint32_t key : text_keys(context, group)) {
for (const auto& [language, overrides] : context.mTextOverrides) {
const auto found = overrides.find(key);
if (found == overrides.end() || language < MESSAGE_LANGUAGE_ENGLISH ||
language > MESSAGE_LANGUAGE_ITALIAN)
{
continue;
}
mods::flow::MessageOverride message;
if (key == 325 || key == 335) {
message = mods::flow::override_message_fn(group, static_cast<uint16_t>(key),
static_cast<MessageLanguage>(language), formatted_override);
} else {
const auto text = encoded_text(found->second);
message = mods::flow::override_message(group, static_cast<uint16_t>(key),
static_cast<MessageLanguage>(language), std::span{text});
}
if (!message) {
return message.result();
}
s_overrides.push_back(std::move(message));
for (const auto& [language, overrides] : context.mTextOverrides) {
if (language < MESSAGE_LANGUAGE_ENGLISH || language > MESSAGE_LANGUAGE_ITALIAN) {
continue;
}
for (const auto& [key, value] : overrides) {
const uint16_t group = key >> 16;
const uint16_t messageId = static_cast<uint16_t>(key);
mods::flow::MessageOverride message;
if (key == kPoeSoulGetMessage || key == kSkyCharacterGetMessage) {
message = mods::flow::override_message_fn(group, messageId,
static_cast<MessageLanguage>(language), formatted_override);
} else {
const auto text = encoded_text(value);
message = mods::flow::override_message(
group, messageId, static_cast<MessageLanguage>(language), std::span{text});
}
if (!message) {
return message.result();
}
s_overrides.push_back(std::move(message));
}
}
return MOD_OK;
}
std::vector<uint16_t> custom_node_ids(const RandomizerContext& context) {
std::vector<uint16_t> result;
for (const auto& [key, node] : context.mFlowPatches) {
if (key >> 16 == CUSTOM_BMG_GROUP) {
result.push_back(static_cast<uint16_t>(key));
}
bool resolve_query(const std::string& name, FlowQueryId& out) {
static const std::unordered_map<std::string, FlowQueryId> builtins{
{"event flag", FLOW_QUERY_EVENT_FLAG},
{"rupees", FLOW_QUERY_RUPEES},
{"item owned", FLOW_QUERY_ITEM_OWNED},
{"empty bottles", FLOW_QUERY_EMPTY_BOTTLES},
{"select 2 cancel", FLOW_QUERY_SELECT_2_CANCEL},
{"select 3 cancel", FLOW_QUERY_SELECT_3_CANCEL},
};
if (const auto found = builtins.find(name); found != builtins.end()) {
out = found->second;
return true;
}
std::ranges::sort(result);
return result;
if (name == "randomizer event flag") {
out = s_eventFlagQuery.id();
} else if (name == "randomizer change time") {
out = s_changeTimeQuery.id();
} else if (name == "randomizer return to spawn") {
out = s_returnToSpawnQuery.id();
} else {
return false;
}
return true;
}
ModResult allocate_shared_node_ids(const std::array<FlowGraphHandle, kMessageGroupCount>& handles,
size_t count, std::array<std::vector<uint16_t>, kMessageGroupCount>& allocated,
LegacyIdMap& nodeIds, const std::vector<uint16_t>& legacyIds) {
if (count == 0) {
return MOD_OK;
bool resolve_event(const std::string& name, FlowEventId& out) {
static const std::unordered_map<std::string, FlowEventId> builtins{
{"remove rupees", FLOW_EVENT_REMOVE_RUPEES},
{"start event", FLOW_EVENT_START_EVENT},
{"select vertical", FLOW_EVENT_SELECT_VERTICAL},
{"set switch", FLOW_EVENT_SET_SWITCH},
{"shop sold out", FLOW_EVENT_SHOP_SOLD_OUT},
{"add donation", FLOW_EVENT_ADD_DONATION},
{"no-op", FLOW_EVENT_UNUSED_42},
};
if (const auto found = builtins.find(name); found != builtins.end()) {
out = found->second;
return true;
}
std::array<std::unordered_set<uint16_t>, kMessageGroupCount> allocatedSets;
std::vector<uint16_t> common;
while (common.size() < count) {
for (size_t group = 0; group < handles.size(); ++group) {
uint16_t id = 0;
const ModResult result = svc_flow->allocate_node(mod_ctx, handles[group], &id);
if (result != MOD_OK) {
return result;
}
allocated[group].push_back(id);
allocatedSets[group].insert(id);
}
common.clear();
for (const uint16_t candidate : allocatedSets.front()) {
const bool present = std::ranges::all_of(
allocatedSets, [candidate](const auto& ids) { return ids.contains(candidate); });
if (present) {
common.push_back(candidate);
}
}
if (name == "randomizer change time") {
out = s_changeTimeEvent.id();
} else if (name == "randomizer return to spawn") {
out = s_returnToSpawnEvent.id();
} else if (name == "randomizer remove trade item") {
out = s_removeTradeItemEvent.id();
} else {
return false;
}
std::ranges::sort(common);
for (size_t i = 0; i < legacyIds.size(); ++i) {
nodeIds.emplace(legacyIds[i], common[i]);
}
return MOD_OK;
return true;
}
ModResult remap_target(uint16_t legacy, const LegacyIdMap& nodeIds, uint16_t& out) {
if (legacy == mods::flow::kEnd || legacy < BASE_CUSTOM_MSG_AND_FLOW_ID) {
out = legacy;
return MOD_OK;
bool resolve_reference(const RandomizerContext::FlowReference& reference, uint16_t group,
const NodeIdMaps& nodeIds, uint16_t& out) {
if (reference.nativeId.has_value()) {
out = reference.nativeId.value();
return true;
}
const auto found = nodeIds.find(legacy);
if (found == nodeIds.end()) {
return MOD_INVALID_ARGUMENT;
const auto found = nodeIds[group].find(reference.name);
if (found == nodeIds[group].end()) {
return false;
}
out = found->second;
return MOD_OK;
return true;
}
ModResult remap_query(FlowNodeData& node) {
const uint16_t legacy = mods::read_bits<uint16_t>(node.bytes + 2);
FlowQueryId query = legacy;
switch (legacy) {
case kLegacyQueryEventFlag:
query = s_eventFlagQuery.id();
break;
case kLegacyQueryChangeTime:
query = s_changeTimeQuery.id();
break;
case kLegacyQueryReturnToSpawn:
query = s_returnToSpawnQuery.id();
break;
default:
if (legacy >= FLOW_QUERY_BUILTIN_COUNT) {
bool resolve_message(const RandomizerContext::FlowReference& reference, uint16_t group,
const MessageIdMaps& messageIds, uint16_t& out) {
if (reference.nativeId.has_value()) {
out = reference.nativeId.value();
return true;
}
const auto found = messageIds[group].find(reference.name);
if (found == messageIds[group].end()) {
return false;
}
out = found->second;
return true;
}
ModResult add_custom_node(const RandomizerContext::FlowNode& flow,
const MessageIdMaps& messageIds, mods::flow::GraphBuilder& builder,
mods::flow::NodeRef& out) {
if (flow.type == RandomizerContext::FlowNodeType::MESSAGE) {
uint16_t message = 0;
if (!resolve_message(flow.message, flow.group, messageIds, message)) {
return MOD_INVALID_ARGUMENT;
}
break;
}
mods::write_bits(node.bytes + 2, query);
return MOD_OK;
}
ModResult remap_event(FlowNodeData& node) {
switch (node.bytes[1]) {
case kLegacyEventNoOp:
node.bytes[1] = FLOW_EVENT_UNUSED_42;
break;
case kLegacyEventChangeTime:
node.bytes[1] = s_changeTimeEvent.id();
break;
case kLegacyEventReturnToSpawn:
node.bytes[1] = s_returnToSpawnEvent.id();
break;
case kLegacyEventSetTrackerFlag:
// Older seed files may contain the retired pre-grant tracker event.
node.bytes[1] = FLOW_EVENT_UNUSED_42;
break;
case kLegacyEventRemoveTradeItem:
node.bytes[1] = s_removeTradeItemEvent.id();
break;
default:
if (node.bytes[1] >= FLOW_EVENT_BUILTIN_COUNT) {
return MOD_INVALID_ARGUMENT;
}
break;
}
return MOD_OK;
}
FlowNodeData raw_node(uint64_t value) {
FlowNodeData node{};
std::memcpy(node.bytes, &value, sizeof(node.bytes));
return node;
}
ModResult remap_node(const RandomizerContext& context, uint32_t legacyKey, uint16_t group,
FlowGraphHandle handle, const LegacyIdMap& nodeIds, const MessageIdMaps& messageIds,
FlowNodeData& node) {
switch (node.bytes[0]) {
case 1: {
const uint16_t legacyMessage = mods::read_bits<uint16_t>(node.bytes + 2);
if (legacyMessage >= BASE_CUSTOM_MSG_AND_FLOW_ID) {
const auto found = messageIds[group].find(legacyMessage);
if (found == messageIds[group].end()) {
return MOD_INVALID_ARGUMENT;
}
mods::write_bits(node.bytes + 2, found->second);
}
uint16_t target = 0;
const ModResult result =
remap_target(mods::read_bits<uint16_t>(node.bytes + 4), nodeIds, target);
if (result != MOD_OK) {
return result;
}
mods::write_bits(node.bytes + 4, target);
out = builder.add_message(message);
return MOD_OK;
}
case 2: {
ModResult result = remap_query(node);
if (result != MOD_OK) {
return result;
}
const auto overrides = context.mFlowPatchesBranchOverrides.find(legacyKey);
if (overrides == context.mFlowPatchesBranchOverrides.end()) {
return MOD_OK;
}
if (overrides->second.empty() || overrides->second.size() != node.bytes[1] ||
overrides->second.size() > std::numeric_limits<uint16_t>::max())
{
if (flow.type == RandomizerContext::FlowNodeType::BRANCH) {
FlowQueryId query = 0;
if (flow.parameters > 0xffff || !resolve_query(flow.operation, query)) {
return MOD_INVALID_ARGUMENT;
}
out = builder.add_branch(query, static_cast<uint16_t>(flow.parameters));
return MOD_OK;
}
FlowEventId event = 0;
if (!resolve_event(flow.operation, event)) {
return MOD_INVALID_ARGUMENT;
}
out = builder.add_event(event, parameter_bytes(flow.parameters));
return MOD_OK;
}
ModResult wire_custom_node(const RandomizerContext::FlowNode& flow, uint16_t group,
const NodeIdMaps& nodeIds, mods::flow::NodeRef node) {
if (flow.type == RandomizerContext::FlowNodeType::BRANCH) {
std::vector<uint16_t> targets;
targets.reserve(overrides->second.size());
for (const uint16_t legacyTarget : overrides->second) {
targets.reserve(flow.results.size());
for (const auto& result : flow.results) {
uint16_t target = 0;
result = remap_target(legacyTarget, nodeIds, target);
if (result != MOD_OK) {
return result;
if (!resolve_reference(result, group, nodeIds, target)) {
return MOD_INVALID_ARGUMENT;
}
targets.push_back(target);
}
uint16_t firstEdge = 0;
result = svc_flow->add_edges(
mod_ctx, handle, targets.data(), static_cast<uint16_t>(targets.size()), &firstEdge);
if (result == MOD_OK) {
mods::write_bits(node.bytes + 6, firstEdge);
}
return result;
node.results(targets);
return MOD_OK;
}
case 3: {
ModResult result = remap_event(node);
if (result != MOD_OK || node.bytes[1] == FLOW_EVENT_JUMP_FLOW) {
return result;
}
const uint16_t legacyEdge = mods::read_bits<uint16_t>(node.bytes + 2);
if (legacyEdge < BASE_CUSTOM_MSG_AND_FLOW_ID) {
return MOD_OK;
}
uint16_t target = 0;
result = remap_target(legacyEdge, nodeIds, target);
if (result != MOD_OK) {
return result;
}
uint16_t edge = 0;
result = svc_flow->add_edges(mod_ctx, handle, &target, 1, &edge);
if (result == MOD_OK) {
mods::write_bits(node.bytes + 2, edge);
}
return result;
}
default:
uint16_t target = 0;
if (!resolve_reference(flow.next, group, nodeIds, target)) {
return MOD_INVALID_ARGUMENT;
}
node.next(target);
return MOD_OK;
}
void remap_actor_record(std::vector<uint8_t>& bytes, const LegacyIdMap& nodeIds) {
if (bytes.size() < sizeof(stage_actor_data_class)) {
return;
ModResult apply_flow_patch(const RandomizerContext::FlowNode& flow, uint16_t group,
const MessageIdMaps& messageIds, const NodeIdMaps& nodeIds,
mods::flow::GraphBuilder& builder) {
if (!flow.patchIndex.has_value()) {
return MOD_INVALID_ARGUMENT;
}
stage_actor_data_class actor{};
std::memcpy(&actor, bytes.data(), sizeof(actor));
if (std::memcmp(actor.name, "Obj_kn2", 7) != 0) {
return;
if (flow.type == RandomizerContext::FlowNodeType::MESSAGE) {
uint16_t message = 0;
uint16_t target = 0;
if (!resolve_message(flow.message, group, messageIds, message) ||
!resolve_reference(flow.next, group, nodeIds, target))
{
return MOD_INVALID_ARGUMENT;
}
builder.patch_node(flow.patchIndex.value(), mods::flow::message(0, message, target));
return MOD_OK;
}
const uint16_t legacy = static_cast<uint16_t>(actor.base.angle.x);
const auto found = nodeIds.find(legacy);
if (found == nodeIds.end()) {
return;
if (flow.type == RandomizerContext::FlowNodeType::BRANCH) {
FlowQueryId query = 0;
if (flow.parameters > 0xffff || !resolve_query(flow.operation, query)) {
return MOD_INVALID_ARGUMENT;
}
std::vector<uint16_t> targets;
targets.reserve(flow.results.size());
for (const auto& result : flow.results) {
uint16_t target = 0;
if (!resolve_reference(result, group, nodeIds, target)) {
return MOD_INVALID_ARGUMENT;
}
targets.push_back(target);
}
builder.patch_branch(flow.patchIndex.value(), query,
static_cast<uint16_t>(flow.parameters), targets);
return MOD_OK;
}
actor.base.angle.x = static_cast<int16_t>(found->second);
std::memcpy(bytes.data(), &actor, sizeof(actor));
FlowEventId event = 0;
uint16_t target = 0;
if (!resolve_event(flow.operation, event) ||
!resolve_reference(flow.next, group, nodeIds, target))
{
return MOD_INVALID_ARGUMENT;
}
builder.patch_event(
flow.patchIndex.value(), event, parameter_bytes(flow.parameters), target);
return MOD_OK;
}
void remap_actor_flows(RandomizerContext& context, const LegacyIdMap& nodeIds) {
for (auto& [stage, patches] : context.mObjectPatches) {
for (auto& [crc, bytes] : patches) {
remap_actor_record(bytes, nodeIds);
ModResult resolve_actor_flow(RandomizerContext::ActorData& actor, uint16_t group,
const NodeIdMaps& nodeIds) {
if (actor.flow.empty()) {
return MOD_OK;
}
const auto found = nodeIds[group].find(actor.flow);
if (found == nodeIds[group].end() || actor.bytes.size() < sizeof(stage_actor_data_class)) {
return MOD_INVALID_ARGUMENT;
}
stage_actor_data_class data{};
std::memcpy(&data, actor.bytes.data(), sizeof(data));
data.base.angle.x = static_cast<int16_t>(found->second);
std::memcpy(actor.bytes.data(), &data, sizeof(data));
return MOD_OK;
}
ModResult resolve_actor_flows(RandomizerContext& context, const NodeIdMaps& nodeIds) {
auto group_for_key = [](uint32_t key, uint16_t& group) {
const uint32_t stage = key >> 16;
if (stage >= std::size(allStageMessageGroups)) {
return false;
}
group = allStageMessageGroups[stage];
return group < kMessageGroupCount;
};
for (auto& [key, patches] : context.mObjectPatches) {
uint16_t group = 0;
if (!group_for_key(key, group)) {
return MOD_INVALID_ARGUMENT;
}
for (auto& [crc, actor] : patches) {
const ModResult result = resolve_actor_flow(actor, group, nodeIds);
if (result != MOD_OK) {
return result;
}
}
}
for (auto& [stage, additions] : context.mObjectAdditions) {
for (auto& bytes : additions) {
remap_actor_record(bytes, nodeIds);
for (auto& [key, additions] : context.mObjectAdditions) {
uint16_t group = 0;
if (!group_for_key(key, group)) {
return MOD_INVALID_ARGUMENT;
}
for (auto& actor : additions) {
const ModResult result = resolve_actor_flow(actor, group, nodeIds);
if (result != MOD_OK) {
return result;
}
}
}
return MOD_OK;
}
ModResult register_graphs(RandomizerContext& context, const MessageIdMaps& messageIds) {
const auto legacyIds = custom_node_ids(context);
std::array<FlowGraphHandle, kMessageGroupCount> handles{};
std::array<mods::flow::Graph, kMessageGroupCount> guards;
for (uint16_t group = 0; group < kMessageGroupCount; ++group) {
ModResult result = svc_flow->begin_graph(mod_ctx, group, &handles[group]);
if (result != MOD_OK) {
return result;
NodeIdMaps nodeIds;
for (const auto& flow : context.mFlowNodes) {
if (flow.group >= kMessageGroupCount) {
return MOD_INVALID_ARGUMENT;
}
guards[group] = mods::flow::Graph{handles[group], MOD_OK};
}
std::array<std::vector<uint16_t>, kMessageGroupCount> allocated;
LegacyIdMap nodeIds;
ModResult result =
allocate_shared_node_ids(handles, legacyIds.size(), allocated, nodeIds, legacyIds);
if (result != MOD_OK) {
return result;
}
std::unordered_map<uint16_t, uint16_t> dynamicToLegacy;
for (const auto& [legacy, dynamic] : nodeIds) {
dynamicToLegacy.emplace(dynamic, legacy);
}
const FlowNodeData unused = mods::flow::event(FLOW_EVENT_UNUSED_42, 0, {});
for (uint16_t group = 0; group < kMessageGroupCount; ++group) {
for (const uint16_t dynamicId : allocated[group]) {
FlowNodeData node = unused;
const auto legacy = dynamicToLegacy.find(dynamicId);
if (legacy != dynamicToLegacy.end()) {
const uint32_t key = static_cast<uint32_t>(CUSTOM_BMG_GROUP) << 16 | legacy->second;
const auto raw = context.mFlowPatches.find(key);
if (raw == context.mFlowPatches.end()) {
return MOD_INVALID_ARGUMENT;
}
node = raw_node(raw->second);
result = remap_node(context, key, group, handles[group], nodeIds, messageIds, node);
if (result != MOD_OK) {
return result;
}
}
result = svc_flow->fill_node(mod_ctx, handles[group], dynamicId, &node);
if (result != MOD_OK) {
return result;
}
bool hasNodes = false;
for (const auto& flow : context.mFlowNodes) {
hasNodes |= flow.group == group;
}
if (!hasNodes) {
continue;
}
for (const auto& [key, value] : context.mFlowPatches) {
if (key >> 16 != group) {
mods::flow::GraphBuilder builder{group};
std::unordered_map<std::string, mods::flow::NodeRef> refs;
for (const auto& flow : context.mFlowNodes) {
if (flow.group != group || flow.patchIndex.has_value()) {
continue;
}
FlowNodeData node = raw_node(value);
result = remap_node(context, key, group, handles[group], nodeIds, messageIds, node);
if (flow.name.empty() || refs.contains(flow.name)) {
return MOD_INVALID_ARGUMENT;
}
mods::flow::NodeRef ref;
const ModResult result = add_custom_node(flow, messageIds, builder, ref);
if (result != MOD_OK) {
return result;
}
result =
svc_flow->patch_node(mod_ctx, handles[group], static_cast<uint16_t>(key), &node);
refs.emplace(flow.name, ref);
nodeIds[group].emplace(flow.name, ref.id());
}
for (const auto& flow : context.mFlowNodes) {
if (flow.group != group || flow.patchIndex.has_value()) {
continue;
}
const ModResult result = wire_custom_node(flow, group, nodeIds, refs.at(flow.name));
if (result != MOD_OK) {
return result;
}
}
}
for (const FlowGraphHandle handle : handles) {
result = svc_flow->commit_graph(mod_ctx, handle);
if (result != MOD_OK) {
return result;
for (const auto& flow : context.mFlowNodes) {
if (flow.group != group || !flow.patchIndex.has_value()) {
continue;
}
const ModResult result =
apply_flow_patch(flow, group, messageIds, nodeIds, builder);
if (result != MOD_OK) {
return result;
}
}
auto graph = builder.commit();
if (!graph) {
return graph.result();
}
}
for (auto& graph : guards) {
s_graphs.push_back(std::move(graph));
}
remap_actor_flows(context, nodeIds);
return MOD_OK;
return resolve_actor_flows(context, nodeIds);
}
} // namespace