Authors: JeNoVaViRuS
This tutorial will use esdtools python talk structure. Since this is python you NEED to pay attention to indents (otherwise it won't save). Copy and paste the existing indents if needed.
You can write directly into the editor but it doesn't have some functionality like marking a word marks the same words everywhere (to locate where vars arer used) Notepad++.
This tutorial will show how to add a custom menu in the talk menu and trade items.
All talk scripts are assigned to a character. Talk scripts can't be assigned to objects. This means that every bonfire and covenant place without a leader has an invisible character standing there.
You can use Smithbox to search a map for the invisible character (hemisphere shape and red mesh structure).
Important to note is that talk scripts are active as soon as you step into a certain radius.
Talk scripts are checking for distance or angle with “ActionButtonParam”. Only then will the talk prompt appear.
The text ID for the talk prompt is in there as well and refers to an “Event” text entry.
Use “Smithbox”, load a map and find the character (red half orb) you want to edit or you just duplicate an existing one. They have a “TalkID” field which directly refers to the ID.
Open “ESDStudio” and select the ID from the map or click on an existing one, copy it and then click on the map ID line and click “paste” - you will be asked to enter a new ID.
Stick to the existing format and just change the last 1 or 2 numbers of the ID and enter it into the “TalkID” field of the map object and save.
Every talk script has a headline where you can either see the bonfire location name, the covenants object name or the characters name.
For actual characters not bonfires the NPC has an “NPCParam” ID with a “Name ID” field which refers to an entry in “Characters” (Text). To show their names in the talk menu: UNKNOWN
If there is no match then the talk menu name is matched against: “BonfireWarpParam” by the area and the last digit of the Event Flag ID (from the bonfire ID).
Bonfire entity ID (this is the ID of the enemy object +1000): e.g. enemy ID: 3200954 then there needs to be an entry with ID 3201954
If both don't exist it shows no name at the top.
Every talk script has an entry point which is the first state. This one is named with <talk script ID>_1 e.g. “320003_1”.
def t320004_1(): """State 0,1""" t320004_x4() Quit()
All other states have consecutive IDs like this: “320003_x0”, “320003_x1”, “320003_x2”… A talk script is not moving from state to state but instead stays in one state if it is allowed and also enters other states. This means that some states continiously check for e.g. cancelling if the player takes damage, talks to someone else, has some menu open etc. All of that happens while the character is in another state that presents the talk menu.
Here is the default example of a bonfire:
def t320000_x9(): """State 0,10""" assert GetCurrentStateElapsedTime() > 2 """State 21""" # goods:9019:Rest assert t320000_x1(gesture1=17, goods1=9019, flag3=6067) """State 17""" MainBonfireMenuFlag() while True: """State 1""" ClearTalkListData() """State 2""" # action:15000150:"Travel" AddTalkListDataIf(GetEventFlag(14000101) or GetEventFlag(2050), 1, 15000150, -1) # action:15000130:"Attune Spell" AddTalkListDataIf(GetEventFlag(14000101) or GetEventFlag(2050), 2, 15000130, -1) # action:15000220:"Organize Storage Box" AddTalkListDataIf(GetEventFlag(14000101) or GetEventFlag(2050), 3, 15000220, -1) # action:15000005:"Leave" AddTalkListData(99, 15000005, -1) """State 4""" ShowShopMessage(TalkOptionsType.Regular) if GetTalkListEntryResult() == 1: """State 3""" if GetEventFlag(2030): """State 18,8""" StartWarpMenuInit(-1) assert GetCurrentStateElapsedFrames() > 1 """State 12""" if WasWarpMenuDestinationSelected(): break elif not (CheckSpecificPersonMenuIsOpen(-1, 0) and not CheckSpecificPersonGenericDialogIsOpen(0)): """State 13""" pass else: """State 16,20""" # action:10010713:"Game installation incomplete.\nCannot travel between bonfires." assert t320000_x3(action1=10010713) elif GetTalkListEntryResult() == 2: """State 6,7""" OpenMagicEquip(1000, 1000) assert not (CheckSpecificPersonMenuIsOpen(11, 0) and not CheckSpecificPersonGenericDialogIsOpen(0)) elif GetTalkListEntryResult() == 3: """State 14,15""" OpenRepository() assert not (CheckSpecificPersonMenuIsOpen(3, 0) and not CheckSpecificPersonGenericDialogIsOpen(0)) elif GetTalkListEntryResult() == 99 or not (CheckSpecificPersonMenuIsOpen(1, 0) and not CheckSpecificPersonGenericDialogIsOpen(0)): """State 5,22""" return 0 """State 11,19""" SetEventFlag(74000013, FlagState.On) """State 9""" Quit()
Here is the breakdown of every single element:
“assert” is nothing else than a “if”-statement. When used for a state like this:
assert t320000_x1(gesture1=17, goods1=9019, flag3=6067)
it means that it checks the state and only if it returns it will continue with the current state. This is the event that checks for a flag and if not set it will reward the player with the “Rest” gesture.
This sets the talk menu flag for the game. This is also done in normal NPC talk when the menu appears:
MainBonfireMenuFlag()
This ensures that the state constantly checks for input instead of breaking out:
while True:
This clears all existing talk menu entries:
ClearTalkListData()
This adds a talk menu option depending on a flag (15000150 is the text ID from “Event”; 1 is the option number):
AddTalkListDataIf(GetEventFlag(14000101) or GetEventFlag(2050), 1, 15000150, -1)
This adds a talk menu option without any flag (99 is the option number):
AddTalkListData(99, 15000005, -1)
This shows the actual talk menu after it is filled:
ShowShopMessage(TalkOptionsType.Regular)
This checks if the Option “1” was picked:
if GetTalkListEntryResult() == 1:
This checks if the Option “99” was picked OR if the player has closed the menu with the cancel button:
elif GetTalkListEntryResult() == 99 or not (CheckSpecificPersonMenuIsOpen(1, 0) and not CheckSpecificPersonGenericDialogIsOpen(0)):
If you are looking at a talk script you need to imagine as if another state doesn't “go” the another state below or above but instead “opens” sideways to the right.
So you can have some kind of “queue” or “line” that is made of states that open the next and the next state.
Some states just check for something with another state. It then returns a value which the original state can check and trigger something else based on that value.
Add 2 itemLots:
3200920 (20x Throwing knife)
3200930 (1x Ember)
DO NOT SET FLAGS FOR THEM! They need to be given repeatedly.
Add the following “Event” text entries:
15000610: “Trade Firebomb for 20x Throwing Knife”
15000620: “Trade 3x Large Titanite Shard for Ember”
15001610: “No Firebomb in inventory”
15001620: “3x Large Titanite Shards not in inventory”
We will add:
For the menu we add the new talk option:
...
# action:15000220:"Organize Storage Box"
AddTalkListDataIf(GetEventFlag(14000101) or GetEventFlag(2050), 3, 15000220, -1)
# OUR CUSTOM TALK OPTION
# action:15000600:"Trade"
AddTalkListData(4, 15000600, -1)
# action:15000005:"Leave"
AddTalkListData(99, 15000005, -1)
...
then we add our check after the “elif GetTalkListEntryResult() == 3”:
elif GetTalkListEntryResult() == 4: assert t320000_x11() assert not (CheckSpecificPersonMenuIsOpen(3, 0) and not CheckSpecificPersonGenericDialogIsOpen(0))
and here we add the actual state where we want to go:
def t320000_x11(lot1=3200920, lot2=3200930, goods2=297, goods3=1001, z1=0): """State 0""" while True: ClearTalkListData() # goods1 = Firebomb # goods2 = Large Titanie Shard # action:15000610:"Trade Firebomb for 20x Throwing Knife" AddTalkListData(1, 15000610, -1) # action:15000620:"Trade 3x Large Titanite Shard for Ember" AddTalkListData(2, 15000620, -1) AddTalkListData(99, 15000005, -1) ShowShopMessage(TalkOptionsType.Regular) # Trade Firebomb for 20x Throwing Knife if GetTalkListEntryResult() == 1: assert (t320000_x12(lot1=lot1, goods2=goods2, goods3=goods3)) # Trade 3x Large Titanite Shard for Ember elif GetTalkListEntryResult() == 2: assert (t320000_x12(lot1=lot2, goods2=goods2, goods3=goods3)) elif GetTalkListEntryResult() == 99 or not (CheckSpecificPersonMenuIsOpen(1, 0) and not CheckSpecificPersonGenericDialogIsOpen(0)): return 0
As you can see we change the value of “lot1” to
IMPORTANT: You need to add these vars “lot1=3200920, lot2=3200930, goods2=297, goods3=1001, z1=0” to the call of the state as well.
So in state “t320000_x5” there is the line:
call = t320000_x9()
and you need to add it in the brackets so it gets passed to the state.
call = t320000_x9(lot1=3200920, lot2=3200930, goods2=297, goods3=1001, z1=0)
As you can see in the other states you can use the underscore so it uses the values that were passed from the call.
I recommend atleast adding it in the main state “t320000_x9” that starts making use of them.
Here we add another event that runs when one of the trade options was selected.
We check for the lot1 var and then call state “t320000_x13” to first check the players inventory for the item.
If it returns 0 (the player has the items) we take the items and call state “t320000_x14” to reward the player with items.
If it returns 1 (the player doesn't have them items) we show a text line that they don't have it in their inventory.
def t320000_x12(lot1=_, goods2=_, goods3=_, z1=_): if lot1 == 3200920: call = t320000_x13(goods2=goods2, goods3=goods3, z1=goods2) if call.Get() == 0: PlayerEquipmentQuantityChange(ItemType.Goods, goods2, -1) assert t320000_x14(lot1=lot1) elif call.Get() == 1: assert t320000_x15(action4=15001610) elif lot1 == 3200930: call = t320000_x13(goods2=goods2, goods3=goods3, z1=goods3) if call.Get() == 0: PlayerEquipmentQuantityChange(ItemType.Goods, goods3, -3) assert t320000_x14(lot1=lot2) elif call.Get() == 1: assert t320000_x15(action4=15001620) else: pass return 0
Here we pass the var “z1” to check which option was selected. We could include these checks in the state above instead and enter all vars manually there.
However if your script would do the same thing for other talk options it is better to outsource it to another state and keep it clean. With 5 trade options it would be 5x times the lines.
Check the players inventory for the items:
def t320000_x13(goods2=_, goods3=_, z1=_): if z1 == goods2: if ComparePlayerInventoryNumber(ItemType.Goods, goods2, CompareType.Greater, 0, False): return 0 else: return 1 elif z1 == goods3: if ComparePlayerInventoryNumber(ItemType.Goods, goods3, CompareType.GreaterOrEqual, 3, False): return 0 else: return 1
Reward the player with an item:
def t320000_x14(lot1=_): GetItemFromItemLot(lot1) assert not IsMenuOpen(63) and GetCurrentStateElapsedFrames() > 1 return 0
Show a text line to the player (if the player doesn't have the item in the inventory):
def t320000_x15(action4=_): OpenGenericDialog(7, action4, 1, 0, 1) assert not CheckSpecificPersonGenericDialogIsOpen(0) return 0