mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-08-25 06:41:30 -04:00
Rework flow impl on top of FlowService
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
# Patches for NPC text flows
|
||||
|
||||
# NOTE: All data is expressed in little endian
|
||||
|
||||
0: # zel_00.bmg
|
||||
# Change the following indices of Midna's flows to jump to our custom choices
|
||||
# instead. These are all the flow jumps that normally determine which "Talk to Midna"
|
||||
@@ -18,9 +16,9 @@
|
||||
- 0x1BC
|
||||
- 0x1C9
|
||||
type: event
|
||||
event: 42
|
||||
event: no-op
|
||||
parameters: 0x00000000
|
||||
next node index: Custom Midna Call Begin
|
||||
next: Custom Midna Call Begin
|
||||
|
||||
1: # zel_01.bmg - Ordon Village
|
||||
# Patch the flow in Bo's house that normally prevents us from leaving without
|
||||
@@ -28,33 +26,29 @@
|
||||
# no matter what
|
||||
- index: 0x8F
|
||||
type: branch
|
||||
num results: 2
|
||||
query: 21
|
||||
query: item owned
|
||||
parameters: 0x0045
|
||||
next node index: 0x4B
|
||||
results:
|
||||
- 0x90
|
||||
- 0x90
|
||||
|
||||
2: # zel_02.bmg
|
||||
# Patch Barnes branch to see if you can buy the bomb bag to use
|
||||
# query 54 (custom event check) instead of query 22 (how many bomb bags the player has)
|
||||
# Patch Barnes to use the randomizer purchase flag instead of counting bomb bags.
|
||||
- index: 0x47C
|
||||
type: branch
|
||||
num results: 2
|
||||
query: 53
|
||||
query: randomizer event flag
|
||||
parameters: 0x004D
|
||||
next node index: 0x340
|
||||
results:
|
||||
- 0x47D
|
||||
- 0x485
|
||||
|
||||
# Change the flow of buying the red potion item in Kak Malo Mart to not check for an empty bottle first
|
||||
# if the item is randomized. Change both branch paths to lead to the same next node
|
||||
- only if: Shop_Items == On
|
||||
index: 0x43F
|
||||
type: branch
|
||||
num results: 2
|
||||
query: 24
|
||||
query: empty bottles
|
||||
parameters: 0x00000001
|
||||
next node index: 0x2EA
|
||||
results:
|
||||
- 0x440
|
||||
- 0x440
|
||||
@@ -64,59 +58,58 @@
|
||||
- only if: Shop_Items == On
|
||||
index: 0x445
|
||||
type: event
|
||||
event: 3
|
||||
event: remove rupees
|
||||
parameters: 0x0000001E
|
||||
next node index: Kakariko Malo Mart Red Potion Sold Out
|
||||
next: Kakariko Malo Mart Red Potion Sold Out
|
||||
|
||||
# Change the flow of buying the wooden shield in Kak Malo Mart to set the sold out flag if shop
|
||||
# items are randomized
|
||||
- only if: Shop_Items == On
|
||||
index: 0x417
|
||||
type: event
|
||||
event: 3
|
||||
event: remove rupees
|
||||
parameters: 0x00000032
|
||||
next node index: Kakariko Malo Mart Wooden Shield Sold Out
|
||||
next: Kakariko Malo Mart Wooden Shield Sold Out
|
||||
|
||||
#3: # zel_03.bmg
|
||||
4: # zel_04.bmg - Castle Town
|
||||
# Patch Charlo to check for 100 rupees instead of 30
|
||||
- index: 0x34A
|
||||
type: branch
|
||||
num results: 2
|
||||
query: 6
|
||||
query: rupees
|
||||
parameters: 0x0064
|
||||
next node index: 0x222
|
||||
results:
|
||||
- 0x34E
|
||||
- 0x342
|
||||
|
||||
# Patch Charlo to take 100 rupees from Link instead of 30
|
||||
- index: 0x34E
|
||||
type: event
|
||||
event: 41
|
||||
event: add donation
|
||||
parameters: 0x00000064
|
||||
next node index: 0x226
|
||||
next: 0x357
|
||||
|
||||
# Patch Charlo to add 100 rupees to his counter instead of 30
|
||||
- index: 0x357
|
||||
type: event
|
||||
event: 3
|
||||
event: remove rupees
|
||||
parameters: 0x00000064
|
||||
next node index: 0x22B
|
||||
next: 0x350
|
||||
|
||||
# Patch Malo Mart door person to not check for time of day.
|
||||
# Change from branch node that checks time of day to event node
|
||||
# that does nothing.
|
||||
- index: 0x1A5
|
||||
type: event
|
||||
event: 42
|
||||
event: no-op
|
||||
parameters: 0x00000000
|
||||
next node index: 0x10C
|
||||
next: 0x1A4
|
||||
|
||||
# Patch Jovani flow so that we can turn in poe souls before completing MDH
|
||||
- index: 0x832
|
||||
type: branch
|
||||
num results: 2
|
||||
query: 1
|
||||
query: event flag
|
||||
parameters: 0x01C8
|
||||
next node index: 0x763
|
||||
results:
|
||||
- 0x834 # Skip over 0x833 and go directly to 0x834
|
||||
- 0x84E
|
||||
@@ -126,54 +119,53 @@
|
||||
# talking to him for the first time with 60 souls
|
||||
- index: 0x844
|
||||
type: message
|
||||
inf index: 0x322
|
||||
next flow index: 0x851
|
||||
message: 0x322
|
||||
next: 0x851
|
||||
|
||||
# Patch Jovani to check if we've received the 20 reward yet at
|
||||
# the end of the 60 reward sequence.
|
||||
- index: 0x81F
|
||||
type: event
|
||||
event: 14
|
||||
event: set switch
|
||||
parameters: 0x00000063
|
||||
next node index: Jovani Check 20 Reward Obtained
|
||||
next: Jovani Check 20 Reward Obtained
|
||||
|
||||
# Patch the flow of showing the Doctor the Invoice to call our custom
|
||||
# event which calls offWarashibeItem instead of setWarashibeItem
|
||||
- index: 0x2D7
|
||||
type: event
|
||||
event: 47
|
||||
event: randomizer remove trade item
|
||||
parameters: 0x00000081
|
||||
next node index: 0x1D3
|
||||
next: 0x2D8
|
||||
|
||||
5: # zel_05.bmg
|
||||
# Patch Yeta flow to always give the map check even if the player has obtained the Ordon Cheese
|
||||
- index: 0x8
|
||||
type: event
|
||||
event: 42
|
||||
event: no-op
|
||||
parameters: 0x00000000
|
||||
next node index: 0x8
|
||||
next: 0x6
|
||||
|
||||
# Patch Yeta flow to always give the map check even if the player has obtained the Ordon Cheese
|
||||
- index: 0x5
|
||||
type: event
|
||||
event: 42
|
||||
event: no-op
|
||||
parameters: 0x00000000
|
||||
next node index: 0x3
|
||||
next: 0xC
|
||||
|
||||
# Patch Gor Liggs to not set the flag for the third key shard
|
||||
# Change event index 17 to event index 42 which does nothing
|
||||
# Patch Gor Liggs to not set the flag for the third key shard.
|
||||
- index: 0x258
|
||||
type: event
|
||||
event: 42
|
||||
event: no-op
|
||||
parameters: 0x00000000
|
||||
next node index: 0x1AC
|
||||
next: 0x248
|
||||
|
||||
# Patch Cave of Ordeals Great Fairy to skip over the check
|
||||
# for an empty bottle the first time
|
||||
- index: 0x850
|
||||
type: message
|
||||
inf index: 0x25B
|
||||
next flow index: 0x202
|
||||
message: 0x25B
|
||||
next: 0x202
|
||||
|
||||
#6: # zel_06.bmg
|
||||
#7: # zel_07.bmg
|
||||
@@ -181,19 +173,18 @@
|
||||
# Skip over the node which deletes auru's memo from the inventory after showing it to Fyer
|
||||
- index: 0x53
|
||||
type: message
|
||||
inf index: 0xA7
|
||||
next flow index: 0x54 # Go directly to 0x54 instead of 0x5D
|
||||
message: 0xA7
|
||||
next: 0x54 # Go directly to 0x54 instead of 0x5D
|
||||
|
||||
# Custom Flow Nodes
|
||||
9:
|
||||
custom:
|
||||
# Start of custom midna call tree. Begin by checking if we can change time
|
||||
# of day to see if we show that choice
|
||||
- name: Custom Midna Call Begin
|
||||
group: 0
|
||||
type: branch
|
||||
num results: 2
|
||||
query: 54
|
||||
query: randomizer change time
|
||||
parameters: 0x0000
|
||||
next node index: 0xFFFF
|
||||
results:
|
||||
- Custom Midna Call 3 Choice Select Setup # Can change time of day
|
||||
- Custom Midna Call 2 Choice Select Setup # Can not change time of day
|
||||
@@ -203,27 +194,29 @@
|
||||
#################################################
|
||||
|
||||
- name: Custom Midna Call 2 Choice Select Setup
|
||||
group: 0
|
||||
type: event
|
||||
event: 13
|
||||
event: select vertical
|
||||
parameters: 0x00000003
|
||||
next node index: Custom Midna Call 2 Choice Intro
|
||||
next: Custom Midna Call 2 Choice Intro
|
||||
|
||||
- name: Custom Midna Call 2 Choice Intro
|
||||
group: 0
|
||||
type: message
|
||||
inf index: Custom Midna Call Need Something Text
|
||||
next flow index: Custom Midna Call 2 Choice
|
||||
message: Custom Midna Call Need Something Text
|
||||
next: Custom Midna Call 2 Choice
|
||||
|
||||
- name: Custom Midna Call 2 Choice
|
||||
group: 0
|
||||
type: message
|
||||
inf index: Custom Midna Call 2 Choice Text
|
||||
next flow index: Custom Midna Call 2 Choice Branch
|
||||
message: Custom Midna Call 2 Choice Text
|
||||
next: Custom Midna Call 2 Choice Branch
|
||||
|
||||
- name: Custom Midna Call 2 Choice Branch
|
||||
group: 0
|
||||
type: branch
|
||||
num results: 3
|
||||
query: 35
|
||||
query: select 2 cancel
|
||||
parameters: 0x0000
|
||||
next node index: 0xFFFF
|
||||
results:
|
||||
- Custom Midna Call Hints # Hints
|
||||
- Custom Midna Call Return to Spawn Branch # Return to Spawn
|
||||
@@ -234,27 +227,29 @@
|
||||
#################################################
|
||||
|
||||
- name: Custom Midna Call 3 Choice Select Setup
|
||||
group: 0
|
||||
type: event
|
||||
event: 13
|
||||
event: select vertical
|
||||
parameters: 0x00000004
|
||||
next node index: Custom Midna Call 3 Choice Intro
|
||||
next: Custom Midna Call 3 Choice Intro
|
||||
|
||||
- name: Custom Midna Call 3 Choice Intro
|
||||
group: 0
|
||||
type: message
|
||||
inf index: Custom Midna Call Need Something Text
|
||||
next flow index: Custom Midna Call 3 Choice
|
||||
message: Custom Midna Call Need Something Text
|
||||
next: Custom Midna Call 3 Choice
|
||||
|
||||
- name: Custom Midna Call 3 Choice
|
||||
group: 0
|
||||
type: message
|
||||
inf index: Custom Midna Call 3 Choice Text
|
||||
next flow index: Custom Midna Call 3 Choice Branch
|
||||
message: Custom Midna Call 3 Choice Text
|
||||
next: Custom Midna Call 3 Choice Branch
|
||||
|
||||
- name: Custom Midna Call 3 Choice Branch
|
||||
group: 0
|
||||
type: branch
|
||||
num results: 4
|
||||
query: 36
|
||||
query: select 3 cancel
|
||||
parameters: 0x0000
|
||||
next node index: 0xFFFF
|
||||
results:
|
||||
- Custom Midna Call Hints # Hints
|
||||
- Custom Midna Call Change Time # Change time of day
|
||||
@@ -266,22 +261,23 @@
|
||||
#################################################
|
||||
|
||||
- name: Custom Midna Call Hints
|
||||
group: 0
|
||||
type: message
|
||||
inf index: Custom Midna Call Hints Text
|
||||
next flow index: 0xFFFF
|
||||
message: Custom Midna Call Hints Text
|
||||
next: 0xFFFF
|
||||
|
||||
- name: Custom Midna Call Change Time
|
||||
group: 0
|
||||
type: event
|
||||
event: 44
|
||||
event: randomizer change time
|
||||
parameters: 0x00000000
|
||||
next node index: 0x116
|
||||
next: 0xFFFF
|
||||
|
||||
- name: Custom Midna Call Return to Spawn Branch
|
||||
group: 0
|
||||
type: branch
|
||||
num results: 3
|
||||
query: 55
|
||||
query: randomizer return to spawn
|
||||
parameters: 0x0000
|
||||
next node index: 0xFFFF
|
||||
results:
|
||||
- Return to Spawn Dungeon Choice Select Setup
|
||||
- Return to Spawn Dungeon No Choice Select Setup
|
||||
@@ -292,27 +288,29 @@
|
||||
#################################################
|
||||
|
||||
- name: Return to Spawn Dungeon Choice Select Setup
|
||||
group: 0
|
||||
type: event
|
||||
event: 13
|
||||
event: select vertical
|
||||
parameters: 0x00000004
|
||||
next node index: Return to Spawn Dungeon Choice Intro
|
||||
next: Return to Spawn Dungeon Choice Intro
|
||||
|
||||
- name: Return to Spawn Dungeon Choice Intro
|
||||
group: 0
|
||||
type: message
|
||||
inf index: Return to Spawn Dungeon Intro Text
|
||||
next flow index: Return to Spawn Dungeon Choice
|
||||
message: Return to Spawn Dungeon Intro Text
|
||||
next: Return to Spawn Dungeon Choice
|
||||
|
||||
- name: Return to Spawn Dungeon Choice
|
||||
group: 0
|
||||
type: message
|
||||
inf index: Return to Spawn Dungeon Choice Text
|
||||
next flow index: Return to Spawn Dungeon Choice Branch
|
||||
message: Return to Spawn Dungeon Choice Text
|
||||
next: Return to Spawn Dungeon Choice Branch
|
||||
|
||||
- name: Return to Spawn Dungeon Choice Branch
|
||||
group: 0
|
||||
type: branch
|
||||
num results: 4
|
||||
query: 36
|
||||
query: select 3 cancel
|
||||
parameters: 0x0000
|
||||
next node index: 0xFFFF
|
||||
results:
|
||||
- Return to Dungeon Spawn # Dungeon entrance
|
||||
- 0xFFFF # Nevermind
|
||||
@@ -324,27 +322,29 @@
|
||||
#################################################
|
||||
|
||||
- name: Return to Spawn Dungeon No Choice Select Setup
|
||||
group: 0
|
||||
type: event
|
||||
event: 13
|
||||
event: select vertical
|
||||
parameters: 0x00000003
|
||||
next node index: Return to Spawn Dungeon No Choice Intro
|
||||
next: Return to Spawn Dungeon No Choice Intro
|
||||
|
||||
- name: Return to Spawn Dungeon No Choice Intro
|
||||
group: 0
|
||||
type: message
|
||||
inf index: Return to Spawn Dungeon Intro Text
|
||||
next flow index: Return to Spawn No Dungeon Choice
|
||||
message: Return to Spawn Dungeon Intro Text
|
||||
next: Return to Spawn Dungeon No Choice
|
||||
|
||||
- name: Return to Spawn Dungeon No Choice
|
||||
group: 0
|
||||
type: message
|
||||
inf index: Return to Spawn Dungeon No Choice Text
|
||||
next flow index: Return to Spawn Dungeon No Choice Branch
|
||||
message: Return to Spawn Dungeon No Choice Text
|
||||
next: Return to Spawn Dungeon No Choice Branch
|
||||
|
||||
- name: Return to Spawn Dungeon No Choice Branch
|
||||
group: 0
|
||||
type: branch
|
||||
num results: 3
|
||||
query: 35
|
||||
query: select 2 cancel
|
||||
parameters: 0x0000
|
||||
next node index: 0xFFFF
|
||||
results:
|
||||
- 0xFFFF # Nevermind
|
||||
- Return to Base Spawn # Spawn
|
||||
@@ -354,40 +354,43 @@
|
||||
# RETURN TO SPAWN EVENTS #
|
||||
#################################################
|
||||
|
||||
# Use custom event index 45 to setup returning to spawn. A non-zero
|
||||
# parameter value indicates to try and use a place override if one
|
||||
# exists for the current stage
|
||||
# A non-zero parameter uses the current stage's place override when one exists.
|
||||
- name: Return to Dungeon Spawn
|
||||
group: 0
|
||||
type: event
|
||||
event: 45
|
||||
event: randomizer return to spawn
|
||||
parameters: 0x00000001
|
||||
next node index: 0x116
|
||||
next: 0xFFFF
|
||||
|
||||
# Use custom event index 45 to setup returning to spawn. A parameter
|
||||
# value of zero indicates to always return to the starting spawn
|
||||
# A zero parameter always returns to the starting spawn.
|
||||
- name: Return to Base Spawn
|
||||
group: 0
|
||||
type: event
|
||||
event: 45
|
||||
event: randomizer return to spawn
|
||||
parameters: 0x00000000
|
||||
next node index: 0x116
|
||||
next: 0xFFFF
|
||||
|
||||
#################################################
|
||||
# KAKARIKO MALO MART #
|
||||
#################################################
|
||||
|
||||
# Run event 24 which sets the shop sold out flag
|
||||
# Set the shop sold-out flag.
|
||||
- name: Kakariko Malo Mart Red Potion Sold Out
|
||||
only if: Shop_Items == On
|
||||
group: 2
|
||||
type: event
|
||||
event: 24
|
||||
event: shop sold out
|
||||
parameters: 0x00000000
|
||||
next node index: 0x2F2
|
||||
next: 0x449
|
||||
|
||||
# Run event 24 which sets the shop sold out flag
|
||||
# Set the shop sold-out flag.
|
||||
- name: Kakariko Malo Mart Wooden Shield Sold Out
|
||||
only if: Shop_Items == On
|
||||
group: 2
|
||||
type: event
|
||||
event: 24
|
||||
event: shop sold out
|
||||
parameters: 0x00000000
|
||||
next node index: 0x2D0
|
||||
next: 0x41C
|
||||
|
||||
#################################################
|
||||
# JOVANI #
|
||||
@@ -396,225 +399,191 @@
|
||||
# Check to see if we've obtained the 20 Poe Soul Reward.
|
||||
# If we haven't then branch to giving the reward
|
||||
- name: Jovani Check 20 Reward Obtained
|
||||
group: 4
|
||||
type: branch
|
||||
num results: 2
|
||||
query: 1
|
||||
query: event flag
|
||||
parameters: 0x0272
|
||||
next node index: 0xFFFF
|
||||
results:
|
||||
- 0xFFFF # Yes, we've obtained the reward, do nothing
|
||||
- Jovani Give 20 Reward
|
||||
|
||||
- name: Jovani Give 20 Reward
|
||||
group: 4
|
||||
type: event
|
||||
event: 8
|
||||
event: start event
|
||||
parameters: 0x00010075
|
||||
next node index: 0x77E
|
||||
next: 0x851
|
||||
|
||||
#################################################
|
||||
# HINT SIGNS #
|
||||
#################################################
|
||||
|
||||
- name: Ordon Hint Sign
|
||||
index: 21100
|
||||
type: message
|
||||
inf index: Ordon Hint Sign Text
|
||||
next flow index: 0xFFFF
|
||||
message: Ordon Hint Sign Text
|
||||
next: 0xFFFF
|
||||
|
||||
- name: South Faron Woods Hint Sign
|
||||
index: 21101
|
||||
type: message
|
||||
inf index: South Faron Woods Hint Sign Text
|
||||
next flow index: 0xFFFF
|
||||
message: South Faron Woods Hint Sign Text
|
||||
next: 0xFFFF
|
||||
|
||||
- name: Sacred Grove Hint Sign
|
||||
index: 21102
|
||||
type: message
|
||||
inf index: Sacred Grove Hint Sign Text
|
||||
next flow index: 0xFFFF
|
||||
message: Sacred Grove Hint Sign Text
|
||||
next: 0xFFFF
|
||||
|
||||
- name: Faron Field Hint Sign
|
||||
index: 21103
|
||||
type: message
|
||||
inf index: Faron Field Hint Sign Text
|
||||
next flow index: 0xFFFF
|
||||
message: Faron Field Hint Sign Text
|
||||
next: 0xFFFF
|
||||
|
||||
- name: Kakariko Gorge Hint Sign
|
||||
index: 21104
|
||||
type: message
|
||||
inf index: Kakariko Gorge Hint Sign Text
|
||||
next flow index: 0xFFFF
|
||||
message: Kakariko Gorge Hint Sign Text
|
||||
next: 0xFFFF
|
||||
|
||||
- name: Kakariko Village Hint Sign
|
||||
index: 21105
|
||||
type: message
|
||||
inf index: Kakariko Village Hint Sign Text
|
||||
next flow index: 0xFFFF
|
||||
message: Kakariko Village Hint Sign Text
|
||||
next: 0xFFFF
|
||||
|
||||
- name: Kakariko Graveyard Hint Sign
|
||||
index: 21106
|
||||
type: message
|
||||
inf index: Kakariko Graveyard Hint Sign Text
|
||||
next flow index: 0xFFFF
|
||||
message: Kakariko Graveyard Hint Sign Text
|
||||
next: 0xFFFF
|
||||
|
||||
- name: Eldin Field Hint Sign
|
||||
index: 21107
|
||||
type: message
|
||||
inf index: Eldin Field Hint Sign Text
|
||||
next flow index: 0xFFFF
|
||||
message: Eldin Field Hint Sign Text
|
||||
next: 0xFFFF
|
||||
|
||||
- name: North Eldin Field Hint Sign
|
||||
index: 21108
|
||||
type: message
|
||||
inf index: North Eldin Field Hint Sign Text
|
||||
next flow index: 0xFFFF
|
||||
message: North Eldin Field Hint Sign Text
|
||||
next: 0xFFFF
|
||||
|
||||
- name: Hidden Village Hint Sign
|
||||
index: 21109
|
||||
type: message
|
||||
inf index: Hidden Village Hint Sign Text
|
||||
next flow index: 0xFFFF
|
||||
message: Hidden Village Hint Sign Text
|
||||
next: 0xFFFF
|
||||
|
||||
- name: Lanayru Field Hint Sign
|
||||
index: 21110
|
||||
type: message
|
||||
inf index: Lanayru Field Hint Sign Text
|
||||
next flow index: 0xFFFF
|
||||
message: Lanayru Field Hint Sign Text
|
||||
next: 0xFFFF
|
||||
|
||||
- name: Beside Castle Town Hint Sign
|
||||
index: 21111
|
||||
type: message
|
||||
inf index: Beside Castle Town Hint Sign Text
|
||||
next flow index: 0xFFFF
|
||||
message: Beside Castle Town Hint Sign Text
|
||||
next: 0xFFFF
|
||||
|
||||
- name: Castle Town Center Hint Sign
|
||||
index: 21112
|
||||
type: message
|
||||
inf index: Castle Town Center Hint Sign Text
|
||||
next flow index: 0xFFFF
|
||||
message: Castle Town Center Hint Sign Text
|
||||
next: 0xFFFF
|
||||
|
||||
- name: Outside South Castle Town Hint Sign
|
||||
index: 21113
|
||||
type: message
|
||||
inf index: Outside South Castle Town Hint Sign Text
|
||||
next flow index: 0xFFFF
|
||||
message: Outside South Castle Town Hint Sign Text
|
||||
next: 0xFFFF
|
||||
|
||||
- name: Lake Hylia Bridge Hint Sign
|
||||
index: 21114
|
||||
type: message
|
||||
inf index: Lake Hylia Bridge Hint Sign Text
|
||||
next flow index: 0xFFFF
|
||||
message: Lake Hylia Bridge Hint Sign Text
|
||||
next: 0xFFFF
|
||||
|
||||
- name: Lake Hylia Hint Sign
|
||||
index: 21115
|
||||
type: message
|
||||
inf index: Lake Hylia Hint Sign Text
|
||||
next flow index: 0xFFFF
|
||||
message: Lake Hylia Hint Sign Text
|
||||
next: 0xFFFF
|
||||
|
||||
- name: Lanayru Spring Hint Sign
|
||||
index: 21116
|
||||
type: message
|
||||
inf index: Lanayru Spring Hint Sign Text
|
||||
next flow index: 0xFFFF
|
||||
message: Lanayru Spring Hint Sign Text
|
||||
next: 0xFFFF
|
||||
|
||||
- name: Lake Lantern Cave Hint Sign
|
||||
index: 21117
|
||||
type: message
|
||||
inf index: Lake Lantern Cave Hint Sign Text
|
||||
next flow index: 0xFFFF
|
||||
message: Lake Lantern Cave Hint Sign Text
|
||||
next: 0xFFFF
|
||||
|
||||
- name: Fishing Hole Hint Sign
|
||||
index: 21118
|
||||
type: message
|
||||
inf index: Fishing Hole Hint Sign Text
|
||||
next flow index: 0xFFFF
|
||||
message: Fishing Hole Hint Sign Text
|
||||
next: 0xFFFF
|
||||
|
||||
- name: Zoras Domain Hint Sign
|
||||
index: 21119
|
||||
type: message
|
||||
inf index: Zoras Domain Hint Sign Text
|
||||
next flow index: 0xFFFF
|
||||
message: Zoras Domain Hint Sign Text
|
||||
next: 0xFFFF
|
||||
|
||||
- name: Snowpeak Hint Sign
|
||||
index: 21120
|
||||
type: message
|
||||
inf index: Snowpeak Hint Sign Text
|
||||
next flow index: 0xFFFF
|
||||
message: Snowpeak Hint Sign Text
|
||||
next: 0xFFFF
|
||||
|
||||
- name: Gerudo Desert Hint Sign
|
||||
index: 21121
|
||||
type: message
|
||||
inf index: Gerudo Desert Hint Sign Text
|
||||
next flow index: 0xFFFF
|
||||
message: Gerudo Desert Hint Sign Text
|
||||
next: 0xFFFF
|
||||
|
||||
- name: Bulblin Camp Hint Sign
|
||||
index: 21122
|
||||
type: message
|
||||
inf index: Bulblin Camp Hint Sign Text
|
||||
next flow index: 0xFFFF
|
||||
message: Bulblin Camp Hint Sign Text
|
||||
next: 0xFFFF
|
||||
|
||||
- name: Forest Temple Hint Sign
|
||||
index: 21123
|
||||
type: message
|
||||
inf index: Forest Temple Hint Sign Text
|
||||
next flow index: 0xFFFF
|
||||
message: Forest Temple Hint Sign Text
|
||||
next: 0xFFFF
|
||||
|
||||
- name: Goron Mines Hint Sign
|
||||
index: 21124
|
||||
type: message
|
||||
inf index: Goron Mines Hint Sign Text
|
||||
next flow index: 0xFFFF
|
||||
message: Goron Mines Hint Sign Text
|
||||
next: 0xFFFF
|
||||
|
||||
- name: Lakebed Temple Hint Sign
|
||||
index: 21125
|
||||
type: message
|
||||
inf index: Lakebed Temple Hint Sign Text
|
||||
next flow index: 0xFFFF
|
||||
message: Lakebed Temple Hint Sign Text
|
||||
next: 0xFFFF
|
||||
|
||||
- name: Arbiters Grounds Hint Sign
|
||||
index: 21126
|
||||
type: message
|
||||
inf index: Arbiters Grounds Hint Sign Text
|
||||
next flow index: 0xFFFF
|
||||
message: Arbiters Grounds Hint Sign Text
|
||||
next: 0xFFFF
|
||||
|
||||
- name: Snowpeak Ruins Hint Sign
|
||||
index: 21127
|
||||
type: message
|
||||
inf index: Snowpeak Ruins Hint Sign Text
|
||||
next flow index: 0xFFFF
|
||||
message: Snowpeak Ruins Hint Sign Text
|
||||
next: 0xFFFF
|
||||
|
||||
- name: Temple of Time First Hint Sign
|
||||
index: 21128
|
||||
type: message
|
||||
inf index: Temple of Time First Hint Sign Text
|
||||
next flow index: 0xFFFF
|
||||
message: Temple of Time First Hint Sign Text
|
||||
next: 0xFFFF
|
||||
|
||||
- name: Temple of Time Second Hint Sign
|
||||
index: 21129
|
||||
type: message
|
||||
inf index: Temple of Time Second Hint Sign Text
|
||||
next flow index: 0xFFFF
|
||||
message: Temple of Time Second Hint Sign Text
|
||||
next: 0xFFFF
|
||||
|
||||
- name: City in the Sky Hint Sign
|
||||
index: 21130
|
||||
type: message
|
||||
inf index: City in the Sky Hint Sign Text
|
||||
next flow index: 0xFFFF
|
||||
message: City in the Sky Hint Sign Text
|
||||
next: 0xFFFF
|
||||
|
||||
- name: Palace of Twilight Hint Sign
|
||||
index: 21131
|
||||
type: message
|
||||
inf index: Palace of Twilight Hint Sign Text
|
||||
next flow index: 0xFFFF
|
||||
message: Palace of Twilight Hint Sign Text
|
||||
next: 0xFFFF
|
||||
|
||||
- name: Hyrule Castle Hint Sign
|
||||
index: 21132
|
||||
type: message
|
||||
inf index: Hyrule Castle Hint Sign Text
|
||||
next flow index: 0xFFFF
|
||||
message: Hyrule Castle Hint Sign Text
|
||||
next: 0xFFFF
|
||||
|
||||
- name: Cave of Ordeals Hint Sign
|
||||
index: 21133
|
||||
type: message
|
||||
inf index: Cave of Ordeals Hint Sign Text
|
||||
next flow index: 0xFFFF
|
||||
message: Cave of Ordeals Hint Sign Text
|
||||
next: 0xFFFF
|
||||
|
||||
@@ -12,8 +12,9 @@ D_MN01:
|
||||
x: -4765.38037
|
||||
y: -1.21
|
||||
z: 272.192261
|
||||
flow: Lakebed Temple Hint Sign
|
||||
angle:
|
||||
x: 21125 # Flow node id
|
||||
x: 0
|
||||
y: 0x46B9
|
||||
z: 0x0000
|
||||
set id: 0xFFFF
|
||||
@@ -32,8 +33,9 @@ D_MN04:
|
||||
x: 11394.1855
|
||||
y: 2878.65
|
||||
z: -17913.05
|
||||
flow: Goron Mines Hint Sign
|
||||
angle:
|
||||
x: 21124 # Flow node id
|
||||
x: 0
|
||||
y: 0xD556
|
||||
z: 0x0000
|
||||
set id: 0xFFFF
|
||||
@@ -52,8 +54,9 @@ D_MN05:
|
||||
x: -1972.11682
|
||||
y: 3150
|
||||
z: 7610.9751
|
||||
flow: Forest Temple Hint Sign
|
||||
angle:
|
||||
x: 21123 # Flow node id
|
||||
x: 0
|
||||
y: 0x6000
|
||||
z: 0x0000
|
||||
set id: 0xFFFF
|
||||
@@ -72,8 +75,9 @@ D_MN06:
|
||||
x: -618.9335
|
||||
y: 725.0
|
||||
z: 3112.2
|
||||
flow: Temple of Time First Hint Sign
|
||||
angle:
|
||||
x: 21128 # Flow node id
|
||||
x: 0
|
||||
y: 0x8000
|
||||
z: 0x0000
|
||||
set id: 0xFFFF
|
||||
@@ -90,8 +94,9 @@ D_MN06:
|
||||
x: -3885.2157
|
||||
y: 4450.0
|
||||
z: -6353.38135
|
||||
flow: Temple of Time Second Hint Sign
|
||||
angle:
|
||||
x: 21129 # Flow node id
|
||||
x: 0
|
||||
y: 0x2000
|
||||
z: 0x0000
|
||||
set id: 0xFFFF
|
||||
@@ -155,8 +160,9 @@ D_MN07:
|
||||
x: 3376.54
|
||||
y: 0.0
|
||||
z: -12709.0352
|
||||
flow: City in the Sky Hint Sign
|
||||
angle:
|
||||
x: 21130 # Flow node id
|
||||
x: 0
|
||||
y: 0xC846
|
||||
z: 0x0000
|
||||
set id: 0xFFFF
|
||||
@@ -237,8 +243,9 @@ D_MN08:
|
||||
x: 679.0
|
||||
y: -200.0
|
||||
z: 9311.0
|
||||
flow: Palace of Twilight Hint Sign
|
||||
angle:
|
||||
x: 21131 # Flow node id
|
||||
x: 0
|
||||
y: 0x0000
|
||||
z: 0x0000
|
||||
set id: 0xFFFF
|
||||
@@ -257,8 +264,9 @@ D_MN09:
|
||||
x: 0.0
|
||||
y: 25.0
|
||||
z: 11625.0
|
||||
flow: Hyrule Castle Hint Sign
|
||||
angle:
|
||||
x: 21132 # Flow node id
|
||||
x: 0
|
||||
y: 0x0000
|
||||
z: 0x0000
|
||||
set id: 0xFFFF
|
||||
@@ -277,8 +285,9 @@ D_MN10:
|
||||
x: -349.4044
|
||||
y: 450.0
|
||||
z: -2876.90771
|
||||
flow: Arbiters Grounds Hint Sign
|
||||
angle:
|
||||
x: 21126 # Flow node id
|
||||
x: 0
|
||||
y: 0x0000
|
||||
z: 0x0000
|
||||
set id: 0xFFFF
|
||||
@@ -297,8 +306,9 @@ D_MN11:
|
||||
x: -530.0
|
||||
y: 0.0
|
||||
z: -669.69
|
||||
flow: Snowpeak Ruins Hint Sign
|
||||
angle:
|
||||
x: 21127 # Flow node id
|
||||
x: 0
|
||||
y: 0x0000
|
||||
z: 0x0000
|
||||
set id: 0xFFFF
|
||||
@@ -317,8 +327,9 @@ D_SB01:
|
||||
x: -1191.42
|
||||
y: 1100.0
|
||||
z: -260.65
|
||||
flow: Cave of Ordeals Hint Sign
|
||||
angle:
|
||||
x: 21133 # Flow node id
|
||||
x: 0
|
||||
y: 0xE483
|
||||
z: 0x0000
|
||||
set id: 0xFFFF
|
||||
@@ -337,8 +348,9 @@ D_SB03:
|
||||
x: -2897.38672
|
||||
y: -1636.68994
|
||||
z: -17674.8691
|
||||
flow: Lake Lantern Cave Hint Sign
|
||||
angle:
|
||||
x: 21117 # Flow node id
|
||||
x: 0
|
||||
y: 0x3E21
|
||||
z: 0x0000
|
||||
set id: 0xFFFF
|
||||
@@ -533,8 +545,9 @@ F_SP103:
|
||||
x: 687.89
|
||||
y: 800.0
|
||||
z: -1424.16
|
||||
flow: Ordon Hint Sign
|
||||
angle:
|
||||
x: 21100 # Flow node id
|
||||
x: 0
|
||||
y: 0xA019
|
||||
z: 0x0000
|
||||
set id: 0xFFFF
|
||||
@@ -586,8 +599,9 @@ F_SP108:
|
||||
x: -12423.8467
|
||||
y: 273.277985
|
||||
z: -11518.958
|
||||
flow: South Faron Woods Hint Sign
|
||||
angle:
|
||||
x: 21101 # Flow node id
|
||||
x: 0
|
||||
y: 0x9228
|
||||
z: 0x0000
|
||||
set id: 0xFFFF
|
||||
@@ -721,8 +735,9 @@ F_SP109:
|
||||
x: -3347.52734
|
||||
y: 2999.16138
|
||||
z: -2865.99341
|
||||
flow: Kakariko Village Hint Sign
|
||||
angle:
|
||||
x: 21105 # Flow node id
|
||||
x: 0
|
||||
y: 0x0000
|
||||
z: 0x0000
|
||||
set id: 0xFFFF
|
||||
@@ -786,8 +801,9 @@ F_SP111:
|
||||
x: 21765.9863
|
||||
y: 500.0
|
||||
z: -62.247
|
||||
flow: Kakariko Graveyard Hint Sign
|
||||
angle:
|
||||
x: 21106 # Flow node id
|
||||
x: 0
|
||||
y: 0xC000
|
||||
z: 0x0000
|
||||
set id: 0xFFFF
|
||||
@@ -1217,8 +1233,9 @@ F_SP113:
|
||||
x: -2964.84839
|
||||
y: -2500.0
|
||||
z: 17146.2676
|
||||
flow: Zoras Domain Hint Sign
|
||||
angle:
|
||||
x: 21119 # Flow node id
|
||||
x: 0
|
||||
y: 0xAF09
|
||||
z: 0x0000
|
||||
set id: 0xFFFF
|
||||
@@ -1240,8 +1257,9 @@ F_SP114:
|
||||
x: 15931.9238
|
||||
y: -14389.4912
|
||||
z: -17388.1992
|
||||
flow: Snowpeak Hint Sign
|
||||
angle:
|
||||
x: 21120 # Flow node id
|
||||
x: 0
|
||||
y: 0x2C5A
|
||||
z: 0x0000
|
||||
set id: 0xFFFF
|
||||
@@ -1854,8 +1872,9 @@ F_SP115:
|
||||
x: -109203.461
|
||||
y: -7220.0
|
||||
z: 33083.7344
|
||||
flow: Lake Hylia Hint Sign
|
||||
angle:
|
||||
x: 21115 # Flow node id
|
||||
x: 0
|
||||
y: 0x64B5
|
||||
z: 0x0000
|
||||
set id: 0xFFFF
|
||||
@@ -2030,8 +2049,9 @@ F_SP115:
|
||||
x: -309.997833
|
||||
y: -1614.82178
|
||||
z: 157.970795
|
||||
flow: Lanayru Spring Hint Sign
|
||||
angle:
|
||||
x: 21116 # Flow node id
|
||||
x: 0
|
||||
y: 0x8000
|
||||
z: 0x0000
|
||||
set id: 0xFFFF
|
||||
@@ -2071,8 +2091,9 @@ F_SP116:
|
||||
x: 0.0
|
||||
y: -200.0
|
||||
z: 835.0
|
||||
flow: Castle Town Center Hint Sign
|
||||
angle:
|
||||
x: 21112 # Flow node id
|
||||
x: 0
|
||||
y: 0x0000
|
||||
z: 0x0000
|
||||
set id: 0xFFFF
|
||||
@@ -2229,8 +2250,9 @@ F_SP117:
|
||||
x: -1543.0
|
||||
y: 1725.0
|
||||
z: 7964.85498
|
||||
flow: Sacred Grove Hint Sign
|
||||
angle:
|
||||
x: 21102 # Flow node id
|
||||
x: 0
|
||||
y: 0x4000
|
||||
z: 0x0000
|
||||
set id: 0xFFFF
|
||||
@@ -2377,8 +2399,9 @@ F_SP118:
|
||||
x: -568.556152
|
||||
y: 260.0
|
||||
z: -3969.31
|
||||
flow: Bulblin Camp Hint Sign
|
||||
angle:
|
||||
x: 21122 # Flow node id
|
||||
x: 0
|
||||
y: 0x0000
|
||||
z: 0x0000
|
||||
set id: 0xFFFF
|
||||
@@ -2531,8 +2554,9 @@ F_SP121:
|
||||
x: -12433.2744
|
||||
y: -1075.2218
|
||||
z: 20885.7129
|
||||
flow: Eldin Field Hint Sign
|
||||
angle:
|
||||
x: 21107 # Flow node id
|
||||
x: 0
|
||||
y: 0x8000
|
||||
z: 0x0000
|
||||
set id: 0xFFFF
|
||||
@@ -2769,8 +2793,9 @@ F_SP121:
|
||||
x: -10116.0215
|
||||
y: -4923.46191
|
||||
z: 43064.4375
|
||||
flow: Kakariko Gorge Hint Sign
|
||||
angle:
|
||||
x: 21104 # Flow node id
|
||||
x: 0
|
||||
y: 0x0789
|
||||
z: 0x0000
|
||||
set id: 0xFFFF
|
||||
@@ -2862,8 +2887,9 @@ F_SP121:
|
||||
x: -46039.4922
|
||||
y: -9250.0
|
||||
z: 81859.2891
|
||||
flow: Faron Field Hint Sign
|
||||
angle:
|
||||
x: 21103 # Flow node id
|
||||
x: 0
|
||||
y: 0x8000
|
||||
z: 0x0000
|
||||
set id: 0xFFFF
|
||||
@@ -2930,8 +2956,9 @@ F_SP121:
|
||||
x: 18468.8418
|
||||
y: 1580.0
|
||||
z: -63560.9531
|
||||
flow: North Eldin Field Hint Sign
|
||||
angle:
|
||||
x: 21108 # Flow node id
|
||||
x: 0
|
||||
y: 0xCDC6
|
||||
z: 0x0000
|
||||
set id: 0xFFFF
|
||||
@@ -3177,8 +3204,9 @@ F_SP121:
|
||||
x: -64943.7422
|
||||
y: -1359.66711
|
||||
z: -31897.8828
|
||||
flow: Lanayru Field Hint Sign
|
||||
angle:
|
||||
x: 21110 # Flow node id
|
||||
x: 0
|
||||
y: 0xE223
|
||||
z: 0x0000
|
||||
set id: 0xFFFF
|
||||
@@ -3403,8 +3431,9 @@ F_SP121:
|
||||
x: -94678.8672
|
||||
y: -3900.0
|
||||
z: 18410.543
|
||||
flow: Lake Hylia Bridge Hint Sign
|
||||
angle:
|
||||
x: 21114 # Flow node id
|
||||
x: 0
|
||||
y: 0x0000
|
||||
z: 0x0000
|
||||
set id: 0xFFFF
|
||||
@@ -3727,8 +3756,9 @@ F_SP122:
|
||||
x: -80699.5
|
||||
y: -765.58374
|
||||
z: 1903.61462
|
||||
flow: Beside Castle Town Hint Sign
|
||||
angle:
|
||||
x: 21111 # Flow node id
|
||||
x: 0
|
||||
y: 0x25B7
|
||||
z: 0x0000
|
||||
set id: 0xFFFF
|
||||
@@ -4012,8 +4042,9 @@ F_SP122:
|
||||
x: -51500.0
|
||||
y: -5500.0
|
||||
z: 27368.3086
|
||||
flow: Outside South Castle Town Hint Sign
|
||||
angle:
|
||||
x: 21113 # Flow node id
|
||||
x: 0
|
||||
y: 0x0000
|
||||
z: 0x0000
|
||||
set id: 0xFFFF
|
||||
@@ -4096,8 +4127,9 @@ F_SP124:
|
||||
x: 20356.23
|
||||
y: 556.7
|
||||
z: 38694.8047
|
||||
flow: Gerudo Desert Hint Sign
|
||||
angle:
|
||||
x: 21121 # Flow node id
|
||||
x: 0
|
||||
y: 0x0099
|
||||
z: 0x0000
|
||||
set id: 0xFFFF
|
||||
@@ -4692,8 +4724,9 @@ F_SP127:
|
||||
x: -2924.74585
|
||||
y: 35.0
|
||||
z: 8386.28906
|
||||
flow: Fishing Hole Hint Sign
|
||||
angle:
|
||||
x: 21118 # Flow node id
|
||||
x: 0
|
||||
y: 0x8000
|
||||
z: 0x0000
|
||||
set id: 0xFFFF
|
||||
@@ -4716,8 +4749,9 @@ F_SP128:
|
||||
x: 5161.03
|
||||
y: 0.0
|
||||
z: -5264.33
|
||||
flow: Hidden Village Hint Sign
|
||||
angle:
|
||||
x: 21109 # Flow node id
|
||||
x: 0
|
||||
y: 0x0000
|
||||
z: 0x0000
|
||||
set id: 0xFFFF
|
||||
|
||||
@@ -401,25 +401,57 @@
|
||||
|
||||
# Completely Custom Text IDs
|
||||
- Name: Custom Midna Call Need Something Text
|
||||
Attributes: 0x07D30000150D0100FF000D001F1F0400
|
||||
Style:
|
||||
Speaker: 0x15
|
||||
Box Kind: 0x0D
|
||||
Draw Type: 1
|
||||
Speaker Mood: 0x0D
|
||||
Talk Animation: 0x1F
|
||||
Face Animation: 0x1F
|
||||
|
||||
- Name: Custom Midna Call 3 Choice Text
|
||||
Attributes: 0x07F8000015000000FF00000000000400
|
||||
Style:
|
||||
Speaker: 0x15
|
||||
Talk Animation: 0
|
||||
Face Animation: 0
|
||||
|
||||
- Name: Custom Midna Call 2 Choice Text
|
||||
Attributes: 0x07F8000015000000FF00000000000400
|
||||
Style:
|
||||
Speaker: 0x15
|
||||
Talk Animation: 0
|
||||
Face Animation: 0
|
||||
|
||||
- Name: Custom Midna Call Hints Text
|
||||
Attributes: 0x07D30000150D0100FF000D001F1F0400
|
||||
Style:
|
||||
Speaker: 0x15
|
||||
Box Kind: 0x0D
|
||||
Draw Type: 1
|
||||
Speaker Mood: 0x0D
|
||||
Talk Animation: 0x1F
|
||||
Face Animation: 0x1F
|
||||
|
||||
- Name: Return to Spawn Dungeon Intro Text
|
||||
Attributes: 0x07D30000150D0100FF000D001F1F0400
|
||||
Style:
|
||||
Speaker: 0x15
|
||||
Box Kind: 0x0D
|
||||
Draw Type: 1
|
||||
Speaker Mood: 0x0D
|
||||
Talk Animation: 0x1F
|
||||
Face Animation: 0x1F
|
||||
|
||||
- Name: Return to Spawn Dungeon Choice Text
|
||||
Attributes: 0x07F60000150D0000FF00000000000400
|
||||
Style:
|
||||
Speaker: 0x15
|
||||
Box Kind: 0x0D
|
||||
Talk Animation: 0
|
||||
Face Animation: 0
|
||||
|
||||
- Name: Return to Spawn Dungeon No Choice Text
|
||||
Attributes: 0x07F60000150D0000FF00000000000400
|
||||
Style:
|
||||
Speaker: 0x15
|
||||
Box Kind: 0x0D
|
||||
Talk Animation: 0
|
||||
Face Animation: 0
|
||||
|
||||
- Name: Ordon Hint Sign Text
|
||||
|
||||
@@ -487,4 +519,4 @@
|
||||
|
||||
- Name: Hyrule Castle Hint Sign Text
|
||||
|
||||
- Name: Cave of Ordeals Hint Sign Text
|
||||
- Name: Cave of Ordeals Hint Sign Text
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "dolphin/types.h"
|
||||
|
||||
inline constexpr u16 BASE_CUSTOM_MSG_AND_FLOW_ID = 21000;
|
||||
inline constexpr u16 CUSTOM_BMG_GROUP = 9;
|
||||
+301
-344
@@ -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
|
||||
|
||||
@@ -16,18 +16,98 @@
|
||||
#include "../generator/logic/entrance_shuffle.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <type_traits>
|
||||
#include <unordered_set>
|
||||
#include <mods/svc/log.hpp>
|
||||
|
||||
#include "custom_flow_ids.hpp"
|
||||
#include "d/actor/d_a_alink.h"
|
||||
#include "d/d_com_inf_game.h"
|
||||
#include "d/d_meter2.h"
|
||||
#include "d/d_meter2_draw.h"
|
||||
#include "d/d_meter2_info.h"
|
||||
#include "d/d_msg_class.h"
|
||||
#include "d/d_msg_flow.h"
|
||||
#include "m_Do/m_Do_audio.h"
|
||||
|
||||
namespace {
|
||||
|
||||
const char* flow_node_type_name(RandomizerContext::FlowNodeType type) {
|
||||
switch (type) {
|
||||
case RandomizerContext::FlowNodeType::MESSAGE:
|
||||
return "message";
|
||||
case RandomizerContext::FlowNodeType::BRANCH:
|
||||
return "branch";
|
||||
case RandomizerContext::FlowNodeType::EVENT:
|
||||
return "event";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
RandomizerContext::FlowNodeType parse_flow_node_type(const YAML::Node& node) {
|
||||
const auto type = node.as<std::string>();
|
||||
if (type == "message") {
|
||||
return RandomizerContext::FlowNodeType::MESSAGE;
|
||||
}
|
||||
if (type == "branch") {
|
||||
return RandomizerContext::FlowNodeType::BRANCH;
|
||||
}
|
||||
if (type == "event") {
|
||||
return RandomizerContext::FlowNodeType::EVENT;
|
||||
}
|
||||
throw std::runtime_error("Unknown flow node type: " + type);
|
||||
}
|
||||
|
||||
void write_flow_reference(
|
||||
YAML::Node node, const RandomizerContext::FlowReference& reference) {
|
||||
if (reference.nativeId.has_value()) {
|
||||
node = reference.nativeId.value();
|
||||
} else {
|
||||
node = reference.name;
|
||||
}
|
||||
}
|
||||
|
||||
RandomizerContext::FlowReference parse_flow_reference(const YAML::Node& node) {
|
||||
RandomizerContext::FlowReference reference{};
|
||||
try {
|
||||
reference.nativeId = node.as<u16>();
|
||||
} catch (const YAML::BadConversion&) {
|
||||
reference.name = node.as<std::string>();
|
||||
}
|
||||
return reference;
|
||||
}
|
||||
|
||||
void write_message_style(
|
||||
YAML::Node node, const RandomizerContext::MessageStyleData& style) {
|
||||
node["eventLabelId"] = style.eventLabelId;
|
||||
node["speaker"] = style.speaker;
|
||||
node["boxKind"] = style.boxKind;
|
||||
node["drawType"] = style.drawType;
|
||||
node["boxPosition"] = style.boxPosition;
|
||||
node["lineAlignment"] = style.lineAlignment;
|
||||
node["speakerMood"] = style.speakerMood;
|
||||
node["cameraAttr"] = style.cameraAttr;
|
||||
node["talkAnim"] = style.talkAnim;
|
||||
node["faceAnim"] = style.faceAnim;
|
||||
node["trailingData"] = style.trailingData;
|
||||
}
|
||||
|
||||
RandomizerContext::MessageStyleData parse_message_style(const YAML::Node& node) {
|
||||
RandomizerContext::MessageStyleData style{};
|
||||
style.eventLabelId = node["eventLabelId"].as<u16>();
|
||||
style.speaker = node["speaker"].as<u8>();
|
||||
style.boxKind = node["boxKind"].as<u8>();
|
||||
style.drawType = node["drawType"].as<u8>();
|
||||
style.boxPosition = node["boxPosition"].as<u8>();
|
||||
style.lineAlignment = node["lineAlignment"].as<u8>();
|
||||
style.speakerMood = node["speakerMood"].as<u8>();
|
||||
style.cameraAttr = node["cameraAttr"].as<u8>();
|
||||
style.talkAnim = node["talkAnim"].as<u8>();
|
||||
style.faceAnim = node["faceAnim"].as<u8>();
|
||||
style.trailingData = node["trailingData"].as<u16>();
|
||||
return style;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::optional<std::string> RandomizerContext::WriteToFile() {
|
||||
|
||||
std::ofstream seedData(this->GetSeedDataPath());
|
||||
@@ -36,6 +116,7 @@ std::optional<std::string> RandomizerContext::WriteToFile() {
|
||||
}
|
||||
|
||||
YAML::Node out{};
|
||||
out["formatVersion"] = FORMAT_VERSION;
|
||||
|
||||
for (const auto& [setting, option] : this->mSettings) {
|
||||
out["mSettings"][setting] = option;
|
||||
@@ -88,23 +169,64 @@ std::optional<std::string> RandomizerContext::WriteToFile() {
|
||||
|
||||
for (const auto& [stageRoomLayer, actorPatches] : this->mObjectPatches) {
|
||||
for (const auto& [actorCRC, actorPatch] : actorPatches) {
|
||||
out["mObjectPatches"][stageRoomLayer][actorCRC] = ContainerToHexString(actorPatch);
|
||||
auto node = out["mObjectPatches"][stageRoomLayer][actorCRC];
|
||||
node["data"] = ContainerToHexString(actorPatch.bytes);
|
||||
if (!actorPatch.flow.empty()) {
|
||||
node["flow"] = actorPatch.flow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [stageRoomLayer, newActors] : this->mObjectAdditions) {
|
||||
for (const auto& actor : newActors) {
|
||||
out["mObjectAdditions"][stageRoomLayer].push_back(ContainerToHexString(actor));
|
||||
YAML::Node node{};
|
||||
node["data"] = ContainerToHexString(actor.bytes);
|
||||
if (!actor.flow.empty()) {
|
||||
node["flow"] = actor.flow;
|
||||
}
|
||||
out["mObjectAdditions"][stageRoomLayer].push_back(node);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
out["mFlowPatches"] = this->mFlowPatches;
|
||||
|
||||
for (const auto& [key, branchOverrides]: this->mFlowPatchesBranchOverrides) {
|
||||
for (auto override : branchOverrides) {
|
||||
out["mFlowPatchesBranchOverrides"][key].push_back(override);
|
||||
for (const auto& flow : mFlowNodes) {
|
||||
YAML::Node node{};
|
||||
node["type"] = flow_node_type_name(flow.type);
|
||||
node["group"] = flow.group;
|
||||
if (flow.patchIndex.has_value()) {
|
||||
node["patchIndex"] = flow.patchIndex.value();
|
||||
} else {
|
||||
node["name"] = flow.name;
|
||||
}
|
||||
node["parameters"] = flow.parameters;
|
||||
if (!flow.operation.empty()) {
|
||||
node["operation"] = flow.operation;
|
||||
}
|
||||
if (flow.type == FlowNodeType::MESSAGE) {
|
||||
write_flow_reference(node["message"], flow.message);
|
||||
}
|
||||
if (flow.type != FlowNodeType::BRANCH) {
|
||||
write_flow_reference(node["next"], flow.next);
|
||||
}
|
||||
for (const auto& result : flow.results) {
|
||||
YAML::Node resultNode{};
|
||||
write_flow_reference(resultNode, result);
|
||||
node["results"].push_back(resultNode);
|
||||
}
|
||||
out["mFlowNodes"].push_back(node);
|
||||
}
|
||||
|
||||
for (const auto& message : mCustomMessages) {
|
||||
YAML::Node node{};
|
||||
node["group"] = message.group;
|
||||
node["name"] = message.name;
|
||||
write_message_style(node["style"], message.style);
|
||||
for (const auto& [language, text] : message.text) {
|
||||
const auto languageName = randomizer::languageToString(
|
||||
static_cast<randomizer::Text::Language>(language));
|
||||
node["text"][languageName] = YAML::Binary(
|
||||
reinterpret_cast<const unsigned char*>(text.data()), text.size());
|
||||
}
|
||||
out["mCustomMessages"].push_back(node);
|
||||
}
|
||||
|
||||
// Dump text overrides as binary to avoid losing intentional null characters
|
||||
@@ -125,10 +247,6 @@ std::optional<std::string> RandomizerContext::WriteToFile() {
|
||||
textData << YAML::EndMap;
|
||||
textData << YAML::EndMap;
|
||||
|
||||
for (const auto& [key, override] : mAttributeOverrides) {
|
||||
out["mAttributeOverrides"][key] = ContainerToHexString(override);
|
||||
}
|
||||
|
||||
for (const auto& [key, override] : mEntranceOverrides) {
|
||||
out["mEntranceOverrides"][std::bit_cast<uint64_t>(key)] = std::bit_cast<uint64_t>(override);
|
||||
}
|
||||
@@ -154,6 +272,11 @@ std::optional<std::string> RandomizerContext::LoadFromHash(const std::string& ha
|
||||
}
|
||||
|
||||
auto in = LoadYAML(this->GetSeedDataPath());
|
||||
if (!in["formatVersion"] || in["formatVersion"].as<u32>() != FORMAT_VERSION) {
|
||||
mods::log::error("Seed {} uses an obsolete data format and must be regenerated", hash);
|
||||
mHash.clear();
|
||||
return "Seed data format is obsolete; regenerate this seed";
|
||||
}
|
||||
|
||||
// Necessary settings
|
||||
for (const auto& settingNode : in["mSettings"] ) {
|
||||
@@ -257,7 +380,11 @@ std::optional<std::string> RandomizerContext::LoadFromHash(const std::string& ha
|
||||
u32 stageRoomLayer = stageRoomLayerNode.first.as<u32>();
|
||||
for (const auto& actorPatchNode : stageRoomLayerNode.second) {
|
||||
u32 actorCRC = actorPatchNode.first.as<u32>();
|
||||
this->mObjectPatches[stageRoomLayer][actorCRC] = HexToBytes(actorPatchNode.second.as<std::string>());
|
||||
auto& actor = this->mObjectPatches[stageRoomLayer][actorCRC];
|
||||
actor.bytes = HexToBytes(actorPatchNode.second["data"].as<std::string>());
|
||||
if (actorPatchNode.second["flow"]) {
|
||||
actor.flow = actorPatchNode.second["flow"].as<std::string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,24 +392,52 @@ std::optional<std::string> RandomizerContext::LoadFromHash(const std::string& ha
|
||||
for (const auto& stageNode: in["mObjectAdditions"]) {
|
||||
u32 stageRoomLayer = stageNode.first.as<u32>();
|
||||
for (const auto& objectData : stageNode.second) {
|
||||
this->mObjectAdditions[stageRoomLayer].emplace_back(HexToBytes(objectData.as<std::string>()));
|
||||
ActorData actor{};
|
||||
actor.bytes = HexToBytes(objectData["data"].as<std::string>());
|
||||
if (objectData["flow"]) {
|
||||
actor.flow = objectData["flow"].as<std::string>();
|
||||
}
|
||||
this->mObjectAdditions[stageRoomLayer].push_back(std::move(actor));
|
||||
}
|
||||
}
|
||||
|
||||
// Flow Patches
|
||||
for (const auto& flowNode: in["mFlowPatches"]) {
|
||||
auto key = flowNode.first.as<u32>();
|
||||
auto value = flowNode.second.as<u64>();
|
||||
this->mFlowPatches[key] = value;
|
||||
for (const auto& flowNode : in["mFlowNodes"]) {
|
||||
FlowNode flow{};
|
||||
flow.type = parse_flow_node_type(flowNode["type"]);
|
||||
flow.group = flowNode["group"].as<u8>();
|
||||
if (flowNode["patchIndex"]) {
|
||||
flow.patchIndex = flowNode["patchIndex"].as<u16>();
|
||||
} else {
|
||||
flow.name = flowNode["name"].as<std::string>();
|
||||
}
|
||||
flow.parameters = flowNode["parameters"].as<u32>();
|
||||
if (flowNode["operation"]) {
|
||||
flow.operation = flowNode["operation"].as<std::string>();
|
||||
}
|
||||
if (flowNode["message"]) {
|
||||
flow.message = parse_flow_reference(flowNode["message"]);
|
||||
}
|
||||
if (flowNode["next"]) {
|
||||
flow.next = parse_flow_reference(flowNode["next"]);
|
||||
}
|
||||
for (const auto& result : flowNode["results"]) {
|
||||
flow.results.push_back(parse_flow_reference(result));
|
||||
}
|
||||
mFlowNodes.push_back(std::move(flow));
|
||||
}
|
||||
|
||||
// Flow Patch Branch Overrides
|
||||
for (const auto& flowNode : in["mFlowPatchesBranchOverrides"]) {
|
||||
auto key = flowNode.first.as<u32>();
|
||||
for (const auto& branchNode : flowNode.second) {
|
||||
auto override = branchNode.as<u16>();
|
||||
this->mFlowPatchesBranchOverrides[key].push_back(override);
|
||||
for (const auto& messageNode : in["mCustomMessages"]) {
|
||||
CustomMessage message{};
|
||||
message.group = messageNode["group"].as<u8>();
|
||||
message.name = messageNode["name"].as<std::string>();
|
||||
message.style = parse_message_style(messageNode["style"]);
|
||||
for (const auto& textNode : messageNode["text"]) {
|
||||
const auto language = randomizer::stringToLanguage(textNode.first.as<std::string>());
|
||||
const auto binary = textNode.second.as<YAML::Binary>();
|
||||
message.text[language] =
|
||||
std::string(reinterpret_cast<const char*>(binary.data()), binary.size());
|
||||
}
|
||||
mCustomMessages.push_back(std::move(message));
|
||||
}
|
||||
|
||||
// Text Overrides
|
||||
@@ -297,15 +452,6 @@ std::optional<std::string> RandomizerContext::LoadFromHash(const std::string& ha
|
||||
}
|
||||
}
|
||||
|
||||
// Attribute Overrides
|
||||
for (const auto& attributeNode : in["mAttributeOverrides"]) {
|
||||
auto key = attributeNode.first.as<u32>();
|
||||
std::vector<u8> overrideVec = HexToBytes(attributeNode.second.as<std::string>());
|
||||
std::array<u8, 20> override{};
|
||||
std::copy(overrideVec.begin(), overrideVec.end(), override.begin());
|
||||
this->mAttributeOverrides[key] = override;
|
||||
}
|
||||
|
||||
// Entrance Overrides
|
||||
for (const auto& entranceNode : in["mEntranceOverrides"]) {
|
||||
const auto key = std::bit_cast<EntranceOverride>(entranceNode.first.as<uint64_t>());;
|
||||
@@ -997,68 +1143,9 @@ void parseObjPatchData(stage_tgsc_data_class& object, const YAML::Node& patchNod
|
||||
}
|
||||
}
|
||||
|
||||
static std::array<u8, 20> CreateAttributeData(const YAML::Node& node, const std::string& name) {
|
||||
auto attributesStr = node.as<std::string>();
|
||||
auto attributesVec = HexToBytes(attributesStr);
|
||||
if (attributesVec.size() != 16) {
|
||||
throw std::runtime_error(fmt::format("Attributes for Text Override {} "
|
||||
"are the wrong length. (Expected: 16, Actual: {}", name, attributesVec.size()));
|
||||
}
|
||||
|
||||
std::array<u8, 20> attributes{};
|
||||
for (size_t i = 0; i < attributesVec.size(); ++i) {
|
||||
attributes[i + 4] = attributesVec[i];
|
||||
}
|
||||
return attributes;
|
||||
}
|
||||
|
||||
RandomizerContext WriteSeedData(randomizer::logic::world::World* world) {
|
||||
RandomizerContext randoData{};
|
||||
|
||||
// Give custom flows and messages new indices as we read them in/create them
|
||||
std::unordered_map<std::string, u16> customMessageIDs{};
|
||||
std::unordered_map<std::string, u16> customFlowIDs{};
|
||||
std::unordered_set<u16> usedMessageIDs{};
|
||||
std::unordered_set<u16> usedFlowIDs{};
|
||||
u16 curCustomMessageID = BASE_CUSTOM_MSG_AND_FLOW_ID;
|
||||
u16 curCustomFlowID = BASE_CUSTOM_MSG_AND_FLOW_ID;
|
||||
|
||||
// Helper functions for assigning new custom flow IDs/message IDs
|
||||
auto handleCustomID = [](const std::string& name, auto& customIds, auto& usedIds, u16& curCustomID) {
|
||||
u16 resultIndex{};
|
||||
// Check to see if we're setting a custom index
|
||||
auto resultInt = randomizer::utility::str::toInt(name);
|
||||
// If we have a regular index, then use that directly
|
||||
if (resultInt.has_value()) {
|
||||
resultIndex = resultInt.value();
|
||||
} else {
|
||||
// If we don't, assume we're setting the index as custom
|
||||
if (customIds.contains(name)) {
|
||||
resultIndex = customIds[name];
|
||||
} else {
|
||||
while (usedIds.contains(curCustomID)) {
|
||||
++curCustomID;
|
||||
}
|
||||
auto newIndex = curCustomID++;
|
||||
resultIndex = newIndex;
|
||||
customIds[name] = newIndex;
|
||||
}
|
||||
}
|
||||
|
||||
usedIds.insert(resultIndex);
|
||||
return resultIndex;
|
||||
};
|
||||
|
||||
|
||||
auto handleCustomFlowID = [&](const std::string& name) {
|
||||
return handleCustomID(name, customFlowIDs, usedFlowIDs, curCustomFlowID);
|
||||
};
|
||||
|
||||
|
||||
auto handleCustomMessageID = [&](const std::string& name) {
|
||||
return handleCustomID(name, customMessageIDs, usedMessageIDs, curCustomMessageID);
|
||||
};
|
||||
|
||||
// Settings we need to check ingame
|
||||
for (const auto& [setting, info] : *randomizer::seedgen::settings::GetAllSettingsInfo()) {
|
||||
if (info->NeedInGame()) {
|
||||
@@ -1268,6 +1355,7 @@ RandomizerContext WriteSeedData(randomizer::logic::world::World* world) {
|
||||
randoData.mStartHour = 24;
|
||||
|
||||
// Actor Patches
|
||||
std::unordered_map<std::string, u8> objectFlowGroups{};
|
||||
auto actorPatches = LOAD_EMBED_YAML(RANDO_DATA_PATH "object_patches.yaml");
|
||||
for (const auto& stageNode : actorPatches) {
|
||||
const auto& stageName = stageNode.first.as<std::string>();
|
||||
@@ -1295,20 +1383,36 @@ RandomizerContext WriteSeedData(randomizer::logic::world::World* world) {
|
||||
u32 objectCRC32 = getStageObjCRC32(reinterpret_cast<u8*>(&object), objDataSize);
|
||||
|
||||
// Depending on the action, store data on this actor
|
||||
std::vector<u8> actorData(0);
|
||||
RandomizerContext::ActorData actorData{};
|
||||
if (objectNode["flow"]) {
|
||||
actorData.flow = objectNode["flow"].as<std::string>();
|
||||
const int stageId = getStageID(stageName.c_str());
|
||||
if (stageId < 0 ||
|
||||
static_cast<size_t>(stageId) >= std::size(allStageMessageGroups))
|
||||
{
|
||||
throw std::runtime_error("Unknown stage for flow-bearing actor: " + stageName);
|
||||
}
|
||||
const u8 group = allStageMessageGroups[stageId];
|
||||
const auto [found, inserted] = objectFlowGroups.emplace(actorData.flow, group);
|
||||
if (!inserted && found->second != group) {
|
||||
throw std::runtime_error(
|
||||
"Actor flow is referenced from multiple message groups: " + actorData.flow);
|
||||
}
|
||||
object.base.angle.x = 0;
|
||||
}
|
||||
// If we're patching this object, Then override the object with whatever parts are being patched
|
||||
// and add that patch data to our actorData
|
||||
if (action == "patch") {
|
||||
parseObjPatchData(object, objectNode["patch"]);
|
||||
actorData.resize(objDataSize);
|
||||
std::memcpy(actorData.data(), &object, objDataSize);
|
||||
actorData.bytes.resize(objDataSize);
|
||||
std::memcpy(actorData.bytes.data(), &object, objDataSize);
|
||||
} else if (action == "add") {
|
||||
// If we're adding the object, add it's regular data to the actorData
|
||||
actorData.resize(objDataSize);
|
||||
std::memcpy(actorData.data(), &object, objDataSize);
|
||||
actorData.bytes.resize(objDataSize);
|
||||
std::memcpy(actorData.bytes.data(), &object, objDataSize);
|
||||
} else if (action == "delete") {
|
||||
// If we're deleting this actor, give it a specific size to indicate we're deleting it
|
||||
actorData.resize(RandomizerContext::OBJ_DELETE_SIZE);
|
||||
actorData.bytes.resize(RandomizerContext::OBJ_DELETE_SIZE);
|
||||
} else {
|
||||
// Unknown action. Don't continue
|
||||
throw std::runtime_error("object patch action \"" + action + "\" not recognized");
|
||||
@@ -1333,228 +1437,224 @@ RandomizerContext WriteSeedData(randomizer::logic::world::World* world) {
|
||||
}
|
||||
}
|
||||
|
||||
// Flow Patches
|
||||
auto source_reference = [](const YAML::Node& node) {
|
||||
RandomizerContext::FlowReference reference{};
|
||||
const auto value = node.as<std::string>();
|
||||
if (const auto numeric = randomizer::utility::str::toInt(value); numeric.has_value()) {
|
||||
if (numeric.value() < 0 || numeric.value() > 0xffff) {
|
||||
throw std::runtime_error("Flow reference is outside the 16-bit range: " + value);
|
||||
}
|
||||
reference.nativeId = static_cast<u16>(numeric.value());
|
||||
} else {
|
||||
reference.name = value;
|
||||
}
|
||||
return reference;
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, std::unordered_set<u8>> customMessageGroups{};
|
||||
std::unordered_map<std::string, std::string> splitMessageStyles{};
|
||||
auto flowPatches = LOAD_EMBED_YAML(RANDO_DATA_PATH "flow_patches.yaml");
|
||||
for (const auto& groupNode : flowPatches) {
|
||||
u8 groupNo = groupNode.first.as<u8>();
|
||||
const auto groupName = groupNode.first.as<std::string>();
|
||||
const bool customSection = groupName == "custom";
|
||||
const u8 defaultGroup = customSection ? 0 : groupNode.first.as<u8>();
|
||||
for (const auto& flowNode : groupNode.second) {
|
||||
// Check to see if this patch is contingent on a specific setting
|
||||
if (flowNode["only if"]) {
|
||||
const auto& condition = flowNode["only if"].as<std::string>();
|
||||
// If the required condition isn't set, then skip this one
|
||||
if (!world->EvaluateSettingCondition(condition)) {
|
||||
continue;
|
||||
}
|
||||
if (flowNode["only if"] &&
|
||||
!world->EvaluateSettingCondition(flowNode["only if"].as<std::string>()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
std::string name{};
|
||||
std::list<u16> indices{};
|
||||
if (flowNode["index"]) {
|
||||
// If we're specifying a sequence of indices
|
||||
if (flowNode["index"].IsSequence()) {
|
||||
for (const auto& indexNode : flowNode["index"]) {
|
||||
auto index = indexNode.as<u16>();
|
||||
indices.push_back(index);
|
||||
usedFlowIDs.insert(index);
|
||||
}
|
||||
name = std::to_string(indices.front());
|
||||
}
|
||||
// If we have just a single index
|
||||
else if (flowNode["index"].IsScalar()) {
|
||||
auto index = flowNode["index"].as<u16>();
|
||||
indices.push_back(index);
|
||||
name = std::to_string(index);
|
||||
usedFlowIDs.insert(index);
|
||||
}
|
||||
|
||||
// If we're specifying an index as well as a name, add the index to the custom
|
||||
// ids
|
||||
if (flowNode["name"]) {
|
||||
name = flowNode["name"].as<std::string>();
|
||||
customFlowIDs[name] = indices.front();
|
||||
RandomizerContext::FlowNode flow{};
|
||||
flow.type = parse_flow_node_type(flowNode["type"]);
|
||||
flow.parameters = flowNode["parameters"].as<u32>();
|
||||
|
||||
std::vector<u16> patchIndices{};
|
||||
if (customSection) {
|
||||
flow.name = flowNode["name"].as<std::string>();
|
||||
if (flowNode["group"]) {
|
||||
flow.group = flowNode["group"].as<u8>();
|
||||
} else if (const auto found = objectFlowGroups.find(flow.name);
|
||||
found != objectFlowGroups.end())
|
||||
{
|
||||
flow.group = found->second;
|
||||
} else {
|
||||
throw std::runtime_error("Custom flow has no message group: " + flow.name);
|
||||
}
|
||||
} else {
|
||||
name = flowNode["name"].as<std::string>();
|
||||
indices.push_back(handleCustomFlowID(flowNode["name"].as<std::string>()));
|
||||
}
|
||||
|
||||
const auto& type = flowNode["type"].as<std::string>();
|
||||
u64 value{};
|
||||
if (type == "branch") {
|
||||
auto branch = reinterpret_cast<mesg_flow_node_branch*>(&value);
|
||||
branch->type = 2;
|
||||
branch->result_count = flowNode["num results"].as<u8>();
|
||||
branch->query_idx = flowNode["query"].as<u16>();
|
||||
branch->param = flowNode["parameters"].as<u16>();
|
||||
branch->next_node_idx = flowNode["next node index"].as<u16>();
|
||||
// If we're using custom result indices
|
||||
if (flowNode["results"]) {
|
||||
auto& results = flowNode["results"];
|
||||
if (results.size() != branch->result_count) {
|
||||
throw std::runtime_error(fmt::format("Flow results size for {} "
|
||||
"do not match num results. (expected: {}. size: {})", name, branch->result_count, results.size()));
|
||||
}
|
||||
for (const auto& resultNode : results) {
|
||||
auto resultIndex = handleCustomFlowID(resultNode.as<std::string>());
|
||||
for (auto index : indices) {
|
||||
u32 key = (groupNo << 16) | index;
|
||||
randoData.mFlowPatchesBranchOverrides[key].push_back(resultIndex);
|
||||
}
|
||||
flow.group = defaultGroup;
|
||||
if (flowNode["index"].IsSequence()) {
|
||||
for (const auto& index : flowNode["index"]) {
|
||||
patchIndices.push_back(index.as<u16>());
|
||||
}
|
||||
} else {
|
||||
patchIndices.push_back(flowNode["index"].as<u16>());
|
||||
}
|
||||
}
|
||||
else if (type == "event") {
|
||||
auto event = reinterpret_cast<mesg_flow_node_event*>(&value);
|
||||
event->type = 3;
|
||||
event->event_idx = flowNode["event"].as<u8>();
|
||||
event->next_node_idx = handleCustomFlowID(flowNode["next node index"].as<std::string>());
|
||||
u32 params = flowNode["parameters"].as<u32>();
|
||||
event->params[0] = (params >> 24) & 0xFF;
|
||||
event->params[1] = (params >> 16) & 0xFF;
|
||||
event->params[2] = (params >> 8) & 0xFF;
|
||||
event->params[3] = params & 0xFF;
|
||||
} else if (type == "message") {
|
||||
auto message = reinterpret_cast<mesg_flow_node*>(&value);
|
||||
message->type = 1;
|
||||
message->msg_index = handleCustomMessageID(flowNode["inf index"].as<std::string>());
|
||||
message->next_node_idx = handleCustomFlowID(flowNode["next flow index"].as<std::string>());
|
||||
|
||||
// If a custom message is too long, split it up among additional flow/message nodes
|
||||
if (message->msg_index >= BASE_CUSTOM_MSG_AND_FLOW_ID) {
|
||||
auto textName = flowNode["inf index"].as<std::string>();
|
||||
if (world->GetTextDatabase().contains(textName)) {
|
||||
auto& text = world->GetTextObject(textName);
|
||||
if (text.IsTooLong()) {
|
||||
// Get the attributes for the text at this ID. Pretty inefficient since we
|
||||
// have to loop through every element unfortunately
|
||||
std::optional<std::array<u8, 20>> customAttributes{};
|
||||
auto textOverrides = LOAD_EMBED_YAML(RANDO_DATA_PATH "text/text_overrides.yaml");
|
||||
for (const auto& overrideNode : textOverrides) {
|
||||
const auto& overrideName = overrideNode["Name"].as<std::string>();
|
||||
if (overrideName == textName && overrideNode["Attributes"]) {
|
||||
customAttributes = CreateAttributeData(overrideNode["Attributes"], textName);
|
||||
break;
|
||||
}
|
||||
if (flow.type == RandomizerContext::FlowNodeType::BRANCH) {
|
||||
flow.operation = flowNode["query"].as<std::string>();
|
||||
for (const auto& result : flowNode["results"]) {
|
||||
flow.results.push_back(source_reference(result));
|
||||
}
|
||||
if (flow.parameters > 0xffff || flow.results.empty() ||
|
||||
flow.results.size() > 0xff)
|
||||
{
|
||||
throw std::runtime_error("Flow branch has invalid parameters or results");
|
||||
}
|
||||
} else if (flow.type == RandomizerContext::FlowNodeType::EVENT) {
|
||||
flow.operation = flowNode["event"].as<std::string>();
|
||||
flow.next = source_reference(flowNode["next"]);
|
||||
} else {
|
||||
flow.message = source_reference(flowNode["message"]);
|
||||
flow.next = source_reference(flowNode["next"]);
|
||||
if (!flow.message.nativeId.has_value()) {
|
||||
customMessageGroups[flow.message.name].insert(flow.group);
|
||||
if (world->GetTextDatabase().contains(flow.message.name)) {
|
||||
auto& text = world->GetTextObject(flow.message.name);
|
||||
// The game still owns textbox pagination, so preserve the existing
|
||||
// per-message limit while representing overflow as ordinary flow nodes.
|
||||
const auto extraText = text.SplitToFitTextLimits();
|
||||
if (!extraText.empty()) {
|
||||
const auto originalNext = flow.next;
|
||||
std::vector<std::string> extraNames{};
|
||||
for (size_t i = 0; i < extraText.size(); ++i) {
|
||||
auto extraName = flow.message.name + std::to_string(i + 1);
|
||||
world->AddNewText(extraName) = extraText[i];
|
||||
splitMessageStyles[extraName] = flow.message.name;
|
||||
customMessageGroups[extraName].insert(flow.group);
|
||||
extraNames.push_back(std::move(extraName));
|
||||
}
|
||||
|
||||
|
||||
// Add each split text as a new custom message entry. The original entry
|
||||
// still exists but has been sliced down to fit properly.
|
||||
auto extraText = text.SplitToFitTextLimits();
|
||||
std::vector<mesg_flow_node> newMsgFlows{};
|
||||
for (size_t i = 0; i < extraText.size(); i++) {
|
||||
// Add this custom text to the world
|
||||
auto extraTextName = textName + std::to_string(i + 1);
|
||||
world->AddNewText(extraTextName) = extraText[i];
|
||||
|
||||
// Create new Flow and Message Ids for the split text object
|
||||
auto newCustomFlowIndex = handleCustomFlowID(extraTextName);
|
||||
auto newCustomMessageIndex = handleCustomMessageID(extraTextName);
|
||||
|
||||
// Create the new flow node. We're storing its own flow index with
|
||||
// itself for now, but we'll shift it back to the previous node later
|
||||
mesg_flow_node newMsgFlow{};
|
||||
newMsgFlow.type = 1;
|
||||
newMsgFlow.msg_index = newCustomMessageIndex;
|
||||
newMsgFlow.next_node_idx = newCustomFlowIndex;
|
||||
|
||||
newMsgFlows.push_back(newMsgFlow);
|
||||
|
||||
//Add the custom text to the rando data
|
||||
u32 key = (CUSTOM_BMG_GROUP << 16) | newCustomMessageIndex;
|
||||
for (auto language : randomizer::supportedLanguages) {
|
||||
std::string newText = extraText[i].mText[language];
|
||||
randomizer::applyMessageCodes(newText);
|
||||
randoData.mTextOverrides[language][key] = newText;
|
||||
}
|
||||
|
||||
// Add custom attribute data as well if it exists
|
||||
if (customAttributes.has_value()) {
|
||||
auto attributes = customAttributes.value();
|
||||
|
||||
// Set the message id in the attribute data
|
||||
attributes[4] = newCustomMessageIndex >> 8;
|
||||
attributes[5] = newCustomMessageIndex & 0xFF;
|
||||
|
||||
randoData.mAttributeOverrides[key] = attributes;
|
||||
}
|
||||
}
|
||||
|
||||
// Shift all the next_node_idx fields back a node and set the original
|
||||
// next node idx as the next node idx for the final of the new flows
|
||||
auto finalNodeIdx = message->next_node_idx;
|
||||
message->next_node_idx = newMsgFlows[0].next_node_idx;
|
||||
for (size_t i = 0; i < newMsgFlows.size(); i++) {
|
||||
auto& curFlow = newMsgFlows[i];
|
||||
auto curFlowIdx = curFlow.next_node_idx;
|
||||
if (i == newMsgFlows.size() - 1) {
|
||||
curFlow.next_node_idx = finalNodeIdx;
|
||||
} else {
|
||||
curFlow.next_node_idx = newMsgFlows[i + 1].next_node_idx;
|
||||
}
|
||||
|
||||
// Also Add the new custom flows to our rando data
|
||||
u32 key = (CUSTOM_BMG_GROUP << 16) | curFlowIdx;
|
||||
randoData.mFlowPatches[key] = std::bit_cast<u64>(curFlow);
|
||||
flow.next = {.name = extraNames.front()};
|
||||
for (size_t i = 0; i < extraNames.size(); ++i) {
|
||||
RandomizerContext::FlowNode extraFlow{
|
||||
.type = RandomizerContext::FlowNodeType::MESSAGE,
|
||||
.group = flow.group,
|
||||
.name = extraNames[i],
|
||||
.message = {.name = extraNames[i]},
|
||||
.next = i + 1 < extraNames.size() ?
|
||||
RandomizerContext::FlowReference{
|
||||
.name = extraNames[i + 1]} :
|
||||
originalNext,
|
||||
};
|
||||
randoData.mFlowNodes.push_back(std::move(extraFlow));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto index : indices) {
|
||||
u32 key = (groupNo << 16) | index;
|
||||
randoData.mFlowPatches[key] = value;
|
||||
|
||||
if (customSection) {
|
||||
randoData.mFlowNodes.push_back(std::move(flow));
|
||||
} else {
|
||||
for (const u16 patchIndex : patchIndices) {
|
||||
auto patch = flow;
|
||||
patch.patchIndex = patchIndex;
|
||||
randoData.mFlowNodes.push_back(std::move(patch));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Text Overrides
|
||||
std::array<std::unordered_set<std::string>, 9> flowNames{};
|
||||
for (const auto& flow : randoData.mFlowNodes) {
|
||||
if (flow.group >= flowNames.size()) {
|
||||
throw std::runtime_error("Flow node has an invalid message group");
|
||||
}
|
||||
if (!flow.patchIndex.has_value() &&
|
||||
(flow.name.empty() || !flowNames[flow.group].insert(flow.name).second))
|
||||
{
|
||||
throw std::runtime_error("Custom flow name is empty or duplicated: " + flow.name);
|
||||
}
|
||||
}
|
||||
auto validate_flow_reference = [&](u8 group, const RandomizerContext::FlowReference& reference) {
|
||||
if (!reference.nativeId.has_value() &&
|
||||
(reference.name.empty() || !flowNames[group].contains(reference.name)))
|
||||
{
|
||||
throw std::runtime_error(fmt::format(
|
||||
"Unresolved flow reference in group {}: {}", group, reference.name));
|
||||
}
|
||||
};
|
||||
for (const auto& [name, group] : objectFlowGroups) {
|
||||
validate_flow_reference(group, {.name = name});
|
||||
}
|
||||
for (const auto& flow : randoData.mFlowNodes) {
|
||||
if (flow.type == RandomizerContext::FlowNodeType::BRANCH) {
|
||||
for (const auto& result : flow.results) {
|
||||
validate_flow_reference(flow.group, result);
|
||||
}
|
||||
} else {
|
||||
validate_flow_reference(flow.group, flow.next);
|
||||
}
|
||||
}
|
||||
|
||||
auto parse_style = [](const YAML::Node& node) {
|
||||
RandomizerContext::MessageStyleData style{};
|
||||
if (!node) {
|
||||
return style;
|
||||
}
|
||||
auto set = [&](const char* key, auto& field) {
|
||||
if (node[key]) {
|
||||
field = node[key].as<std::remove_reference_t<decltype(field)>>();
|
||||
}
|
||||
};
|
||||
set("Event Label", style.eventLabelId);
|
||||
set("Speaker", style.speaker);
|
||||
set("Box Kind", style.boxKind);
|
||||
set("Draw Type", style.drawType);
|
||||
set("Box Position", style.boxPosition);
|
||||
set("Line Alignment", style.lineAlignment);
|
||||
set("Speaker Mood", style.speakerMood);
|
||||
set("Camera", style.cameraAttr);
|
||||
set("Talk Animation", style.talkAnim);
|
||||
set("Face Animation", style.faceAnim);
|
||||
set("Trailing Data", style.trailingData);
|
||||
return style;
|
||||
};
|
||||
|
||||
auto textOverrides = LOAD_EMBED_YAML(RANDO_DATA_PATH "text/text_overrides.yaml");
|
||||
std::unordered_map<std::string, YAML::Node> textDefinitions{};
|
||||
for (const auto& overrideNode : textOverrides) {
|
||||
// Check to see if this override is contingent on a specific setting
|
||||
if (overrideNode["Only If"]) {
|
||||
const auto& condition = overrideNode["Only If"].as<std::string>();
|
||||
// If the required condition isn't set, then skip this one
|
||||
if (!world->EvaluateSettingCondition(condition)) {
|
||||
continue;
|
||||
}
|
||||
const auto name = overrideNode["Name"].as<std::string>();
|
||||
textDefinitions.emplace(name, overrideNode);
|
||||
if (!overrideNode["Message Id"] ||
|
||||
(overrideNode["Only If"] &&
|
||||
!world->EvaluateSettingCondition(overrideNode["Only If"].as<std::string>())))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const auto& name = overrideNode["Name"].as<std::string>();
|
||||
u8 group;
|
||||
u16 messageId;
|
||||
if (overrideNode["Group"]) {
|
||||
group = overrideNode["Group"].as<u8>();
|
||||
} else {
|
||||
// If no group specified, assume custom bmg group
|
||||
group = CUSTOM_BMG_GROUP;
|
||||
}
|
||||
if (overrideNode["Message Id"]) {
|
||||
messageId = overrideNode["Message Id"].as<u16>();
|
||||
} else {
|
||||
// If no message id specified, assume a custom one
|
||||
messageId = handleCustomMessageID(overrideNode["Name"].as<std::string>());
|
||||
}
|
||||
u32 key = (group << 16) | messageId;
|
||||
for (auto language : randomizer::supportedLanguages) {
|
||||
std::string text;
|
||||
if (world->GetTextDatabase().contains(name)) {
|
||||
text = world->GetText(name, randomizer::Text::STANDARD, language);
|
||||
} else {
|
||||
text = randomizer::getTextStr(name, randomizer::Text::STANDARD, language);
|
||||
}
|
||||
|
||||
const u8 group = overrideNode["Group"].as<u8>();
|
||||
const u16 messageId = overrideNode["Message Id"].as<u16>();
|
||||
const u32 key = static_cast<u32>(group) << 16 | messageId;
|
||||
for (const auto language : randomizer::supportedLanguages) {
|
||||
std::string text = world->GetTextDatabase().contains(name) ?
|
||||
world->GetText(name, randomizer::Text::STANDARD, language) :
|
||||
randomizer::getTextStr(name, randomizer::Text::STANDARD, language);
|
||||
randomizer::applyMessageCodes(text);
|
||||
randoData.mTextOverrides[language][key] = text;
|
||||
randoData.mTextOverrides[language][key] = std::move(text);
|
||||
}
|
||||
}
|
||||
|
||||
// If we have custom attributes
|
||||
if (overrideNode["Attributes"]) {
|
||||
auto attributes = CreateAttributeData(overrideNode["Attributes"], name);
|
||||
|
||||
// Set the message id in the attribute data
|
||||
attributes[4] = messageId >> 8;
|
||||
attributes[5] = messageId & 0xFF;
|
||||
|
||||
randoData.mAttributeOverrides[key] = attributes;
|
||||
for (const auto& [name, groups] : customMessageGroups) {
|
||||
const auto styleName = splitMessageStyles.contains(name) ? splitMessageStyles[name] : name;
|
||||
const auto definition = textDefinitions.find(styleName);
|
||||
if (definition == textDefinitions.end()) {
|
||||
throw std::runtime_error("Custom flow message has no text definition: " + styleName);
|
||||
}
|
||||
for (const u8 group : groups) {
|
||||
RandomizerContext::CustomMessage message{
|
||||
.group = group,
|
||||
.name = name,
|
||||
.style = parse_style(definition->second["Style"]),
|
||||
};
|
||||
for (const auto language : randomizer::supportedLanguages) {
|
||||
std::string text = world->GetTextDatabase().contains(name) ?
|
||||
world->GetText(name, randomizer::Text::STANDARD, language) :
|
||||
randomizer::getTextStr(name, randomizer::Text::STANDARD, language);
|
||||
randomizer::applyMessageCodes(text);
|
||||
message.text[language] = std::move(text);
|
||||
}
|
||||
randoData.mCustomMessages.push_back(std::move(message));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
*/
|
||||
class RandomizerContext {
|
||||
public:
|
||||
static constexpr u32 FORMAT_VERSION = 2;
|
||||
static constexpr size_t ACTR_CRC_SIZE = 32;
|
||||
static constexpr size_t TGSC_CRC_SIZE = 35; // 3 extra bytes for scale x, y, z
|
||||
static constexpr size_t OBJ_DELETE_SIZE = 1;
|
||||
@@ -56,17 +57,62 @@ public:
|
||||
u8 mStartHour{0};
|
||||
u8 mMapBits{};
|
||||
|
||||
std::unordered_map<u32, std::unordered_map<u32, std::vector<u8>>> mObjectPatches{};
|
||||
std::unordered_map<u32, std::list<std::vector<u8>>> mObjectAdditions{};
|
||||
// std::unordered_map<u32, std::unordered_set<u32>> mTgscDeletions{};
|
||||
std::unordered_map<u32, u64> mFlowPatches{};
|
||||
std::unordered_map<u32, std::vector<u16>> mFlowPatchesBranchOverrides{};
|
||||
std::unordered_map<u32, std::array<u8, 20>> mAttributeOverrides{};
|
||||
struct ActorData {
|
||||
std::vector<u8> bytes{};
|
||||
std::string flow{};
|
||||
};
|
||||
|
||||
std::unordered_map<u32, std::unordered_map<u32, ActorData>> mObjectPatches{};
|
||||
std::unordered_map<u32, std::list<ActorData>> mObjectAdditions{};
|
||||
// std::unordered_map<u32, std::unordered_set<u32>> mTgscDeletions{};
|
||||
|
||||
struct FlowReference {
|
||||
std::optional<u16> nativeId{};
|
||||
std::string name{};
|
||||
};
|
||||
|
||||
enum class FlowNodeType : u8 {
|
||||
MESSAGE,
|
||||
BRANCH,
|
||||
EVENT,
|
||||
};
|
||||
|
||||
struct FlowNode {
|
||||
FlowNodeType type{};
|
||||
u8 group{};
|
||||
std::optional<u16> patchIndex{};
|
||||
std::string name{};
|
||||
std::string operation{};
|
||||
FlowReference message{};
|
||||
FlowReference next{};
|
||||
std::vector<FlowReference> results{};
|
||||
u32 parameters{};
|
||||
};
|
||||
|
||||
struct MessageStyleData {
|
||||
u16 eventLabelId{};
|
||||
u8 speaker{0x24};
|
||||
u8 boxKind{};
|
||||
u8 drawType{};
|
||||
u8 boxPosition{};
|
||||
u8 lineAlignment{};
|
||||
u8 speakerMood{};
|
||||
u8 cameraAttr{};
|
||||
u8 talkAnim{0x02};
|
||||
u8 faceAnim{0x03};
|
||||
u16 trailingData{0x0400};
|
||||
};
|
||||
|
||||
struct CustomMessage {
|
||||
u8 group{};
|
||||
std::string name{};
|
||||
MessageStyleData style{};
|
||||
std::unordered_map<int, std::string> text{};
|
||||
};
|
||||
|
||||
std::vector<FlowNode> mFlowNodes{};
|
||||
std::vector<CustomMessage> mCustomMessages{};
|
||||
|
||||
// struct TextOverride {
|
||||
// std::array<u8, 16> mAttributes{};
|
||||
// std::string mText{};
|
||||
// };
|
||||
// Map of language -> map of key -> string
|
||||
std::unordered_map<int, std::unordered_map<u32, std::string>> mTextOverrides{};
|
||||
|
||||
|
||||
@@ -292,15 +292,15 @@ void registerStageEdits() {
|
||||
|
||||
const u8 room = (key >> 8) & 0xFF;
|
||||
const s8 layer = static_cast<s8>(key & 0xFF);
|
||||
for (const auto& [crc, bytes] : patches) {
|
||||
for (const auto& [crc, actor] : patches) {
|
||||
StageActorHandle handle{};
|
||||
|
||||
ModResult res;
|
||||
if (bytes.size() == RandomizerContext::OBJ_DELETE_SIZE) {
|
||||
if (actor.bytes.size() == RandomizerContext::OBJ_DELETE_SIZE) {
|
||||
res = svc_mng.stage->delete_actor(mod_ctx, stage, room, layer, crc, &handle);
|
||||
} else {
|
||||
res = svc_mng.stage->patch_actor(
|
||||
mod_ctx, stage, room, layer, crc, bytes.data(), bytes.size(), &handle);
|
||||
res = svc_mng.stage->patch_actor(mod_ctx, stage, room, layer, crc,
|
||||
actor.bytes.data(), actor.bytes.size(), &handle);
|
||||
}
|
||||
|
||||
if (res == MOD_OK) {
|
||||
@@ -317,10 +317,11 @@ void registerStageEdits() {
|
||||
|
||||
const u8 room = (key >> 8) & 0xFF;
|
||||
const s8 layer = static_cast<s8>(key & 0xFF);
|
||||
for (const auto& bytes : additions) {
|
||||
for (const auto& actor : additions) {
|
||||
StageActorHandle handle{};
|
||||
ModResult rt;
|
||||
rt = svc_mng.stage->add_actor(mod_ctx, stage, room, layer, bytes.data(), bytes.size(), &handle);
|
||||
rt = svc_mng.stage->add_actor(mod_ctx, stage, room, layer, actor.bytes.data(),
|
||||
actor.bytes.size(), &handle);
|
||||
if (rt == MOD_OK) {
|
||||
s_stage_edits.push_back(handle);
|
||||
}
|
||||
|
||||
@@ -79,4 +79,12 @@ const char allStages[78][8] = {
|
||||
"R_SP209", // 75
|
||||
"R_SP300", // 76
|
||||
"R_SP301" // 77
|
||||
};
|
||||
};
|
||||
|
||||
// Message groups are the STAG metadata for the corresponding allStages entry.
|
||||
const unsigned char allStageMessageGroups[78] = {
|
||||
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
|
||||
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
|
||||
5, 1, 8, 1, 1, 6, 2, 3, 2, 8, 7, 6, 8, 4, 6, 6, 8, 8, 8, 6,
|
||||
6, 8, 7, 6, 6, 1, 4, 6, 2, 3, 4, 7, 6, 4, 4, 2, 5, 4,
|
||||
};
|
||||
|
||||
@@ -83,3 +83,4 @@ enum StageIDs
|
||||
};
|
||||
|
||||
extern const char allStages[78][8];
|
||||
extern const unsigned char allStageMessageGroups[78];
|
||||
|
||||
@@ -100,6 +100,7 @@ public:
|
||||
NodeRef next(uint16_t target) const;
|
||||
/* Set a branch node's result targets */
|
||||
NodeRef results(std::initializer_list<uint16_t> targets) const;
|
||||
NodeRef results(std::span<const uint16_t> targets) const;
|
||||
|
||||
uint16_t id() const { return mId; }
|
||||
operator uint16_t() const { return mId; }
|
||||
@@ -153,6 +154,35 @@ public:
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
GraphBuilder& patch_branch(uint16_t nodeIndex, FlowQueryId query, uint16_t parameter,
|
||||
std::span<const uint16_t> targets) {
|
||||
if (mResult != MOD_OK || targets.empty() || targets.size() > 0xff) {
|
||||
if (mResult == MOD_OK) {
|
||||
mResult = MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
uint16_t first = 0;
|
||||
mResult = svc_flow->add_edges(
|
||||
mod_ctx, mHandle, targets.data(), static_cast<uint16_t>(targets.size()), &first);
|
||||
if (mResult == MOD_OK) {
|
||||
patch_node(nodeIndex,
|
||||
branch(static_cast<uint8_t>(targets.size()), query, parameter, first));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
GraphBuilder& patch_event(uint16_t nodeIndex, FlowEventId eventId,
|
||||
std::array<uint8_t, 4> params, uint16_t target) {
|
||||
if (mResult != MOD_OK) {
|
||||
return *this;
|
||||
}
|
||||
uint16_t edge = 0;
|
||||
mResult = svc_flow->add_edges(mod_ctx, mHandle, &target, 1, &edge);
|
||||
if (mResult == MOD_OK) {
|
||||
patch_node(nodeIndex, event(eventId, edge, params));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Graph commit() {
|
||||
for (const auto& node : mNodes) {
|
||||
@@ -232,7 +262,7 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void set_results(uint16_t id, std::initializer_list<uint16_t> targets) {
|
||||
void set_results(uint16_t id, std::span<const uint16_t> targets) {
|
||||
if (mResult != MOD_OK) {
|
||||
return;
|
||||
}
|
||||
@@ -246,7 +276,7 @@ private:
|
||||
node->wired = true;
|
||||
uint16_t first = 0;
|
||||
mResult = svc_flow->add_edges(
|
||||
mod_ctx, mHandle, targets.begin(), static_cast<uint16_t>(targets.size()), &first);
|
||||
mod_ctx, mHandle, targets.data(), static_cast<uint16_t>(targets.size()), &first);
|
||||
if (mResult == MOD_OK) {
|
||||
node->data.bytes[1] = static_cast<uint8_t>(targets.size());
|
||||
write_bits(node->data.bytes + 6, first);
|
||||
@@ -266,6 +296,10 @@ inline NodeRef NodeRef::next(uint16_t target) const {
|
||||
}
|
||||
|
||||
inline NodeRef NodeRef::results(std::initializer_list<uint16_t> targets) const {
|
||||
return results(std::span{targets.begin(), targets.size()});
|
||||
}
|
||||
|
||||
inline NodeRef NodeRef::results(std::span<const uint16_t> targets) const {
|
||||
if (mBuilder != nullptr) {
|
||||
mBuilder->set_results(mId, targets);
|
||||
}
|
||||
@@ -368,6 +402,10 @@ public:
|
||||
[[nodiscard]] constexpr MessageStyle face_anim(uint8_t value) const {
|
||||
return set_u8(17, value);
|
||||
}
|
||||
/* INF1 bytes 18-19, normally 0x0400. */
|
||||
[[nodiscard]] constexpr MessageStyle trailing_data(uint16_t value) const {
|
||||
return set_u16(18, value);
|
||||
}
|
||||
[[nodiscard]] constexpr const MessageEntryData& data() const { return mData; }
|
||||
|
||||
private:
|
||||
@@ -432,6 +470,9 @@ public:
|
||||
MessageBuilder& camera_attr(uint8_t value) { return style(&MessageStyle::camera_attr, value); }
|
||||
MessageBuilder& talk_anim(uint8_t value) { return style(&MessageStyle::talk_anim, value); }
|
||||
MessageBuilder& face_anim(uint8_t value) { return style(&MessageStyle::face_anim, value); }
|
||||
MessageBuilder& trailing_data(uint16_t value) {
|
||||
return style(&MessageStyle::trailing_data, value);
|
||||
}
|
||||
|
||||
/* Content builder functions */
|
||||
MessageBuilder& text(std::string_view value) {
|
||||
|
||||
Reference in New Issue
Block a user