Interaction Framework Migration & Verification Guide
1. Overview
The project is migrating from "Old-Style" NPC scripts (hardcoded logic in onTrigger) to the Interaction Framework (IF). This centralizes quest/mission logic, improves readability, and reduces NPC scripts to minimal stubs.
2. Information Discovery
Retail Captures
Retail packet captures are essential for accurate migrations. They provide the exact sequence of events, NPC positions, and parameters needed for the Interaction Framework. (See the Retail Packet Captures Format Guide for detailed folder structures).
caplog(Chat Logs): Start here to understand the quest flow. The capturer's notes and NPC dialogue will help you identify which events correspond to which quest steps.eventview/simple(Simplified Events): Use these logs to map raw packets directly to Interaction Framework calls:CEventPacket(0x032/0x034): TheEventParavalue is the Event ID. This directly maps to your IFquest:progressEvent(id)orquest:event(id)calls.CMessageSpecialPacket(0x02A): TheMessageNumbervalue is the Text ID. This directly maps to your IFquest:messageSpecial(id)calls (often used for non-standard dialogues or system messages like checking doors/sarcophagi).CMessageNamePacket(0x027): TheMesNumvalue (sometimes requiring a bitwise& 0xFFFFdepending on the logger output) is the standard chat dialogue ID. This maps to IFquest:messageName(id)or standard text lookups.
npclogger/database(NPC Data): Use the.luafiles here to populate or verify NPC coordinates and properties insql/npc_list.sqlor to format your NPC script headers correctly.packetviewer(Raw Packets): For complex interactions thateventviewdoesn't fully capture, you can dive into the raw packets here to understand what the client is sending and receiving.
Database (SQL)
- NPC IDs & Positions: Check
sql/npc_list.sql. Use this to find the 8-digit NPC ID and their!poscoordinates. - Item IDs: Check
sql/item_basic.sql. If an item constant is missing from Lua, find the ID here and use it directly or add it to the enum.
Global Registries (Lua Enums)
- Quest IDs:
scripts/globals/quests.lua. - Mission IDs:
scripts/globals/missions.lua. - Item Constants:
scripts/enum/item.lua. - Key Item Constants:
scripts/enum/key_item.lua. - Zone Constants:
scripts/enum/zone.lua.
Retail Event Dumps
The sruon/FFXI-EventsDump repository is the source of truth for retail event IDs and dialogue.
- Nearby Repo: If the repo is cloned next to this one, access it via
../FFXI-EventsDump/dumps/<Zone_Name>. - Remote Access: Use
web_fetchorgoogle_web_searchto find raw markdown files on GitHub if the local path is unavailable. - Strings: Each zone folder has a
strings.txtfile. Map the decimal/hex IDs in the.mdfiles to these strings to verify dialogue.
3. The Migration Workflow
Step 1: Research (The "Wiki Triangulation")
- Check BOTH BG-Wiki and FFXI Wikia.
- Compare steps, item requirements, and NPC dialogue descriptions.
- Note any "Wait until Japanese Midnight" or "Zone out/in" requirements.
Step 2: Implementation (The IF File)
Create the appropriate file in scripts/quests/, scripts/missions/, or scripts/quests/hiddenQuests/.
-
Choosing the Container Class:
- Quests:
local quest = Quest:new(xi.questLog.LOG_NAME, xi.quest.id.area.QUEST_NAME) - Missions:
local mission = Mission:new(xi.mission.log_id.LOG_NAME, xi.mission.id.area.MISSION_NAME) - Hidden Quests:
local quest = HiddenQuest:new('UniqueStringName')(Used for non-logged content like Trust acquisitions or Mog House expansions).
- Quests:
-
Why they are different:
- Variable Scoping: Each class automatically prefixes variables (e.g.,
Quest[1][2]Prog,Mission[4][10]Prog, orUniqueStringNameProg). This prevents collisions across different types of content. - Check Arguments:
QuestandMissionobjects automatically check their respective logs/statuses when determining if a section'scheckfunction should run.HiddenQuestrelies entirely on custom variables.
- Variable Scoping: Each class automatically prefixes variables (e.g.,
-
Basic Structure:
local quest = Quest:new(xi.questLog.LOG_NAME, xi.quest.id.area.QUEST_NAME)
quest.reward = { fame = 30, gil = 1000 }
quest.sections = {
{
check = function(player, status, vars) return status == xi.questStatus.QUEST_AVAILABLE end,
[xi.zone.ZONE_ID] = {
['NPC_Name'] = quest:progressEvent(100),
},
},
}
- Trading: Use
onTradewithin the NPC block. Always usenpcUtil.tradeHasExactlyornpcUtil.tradeHas. - Zoning: Use
quest:setMustZone(player)andquest:getMustZone(player). - Trigger Areas: Handle approach-based cutscenes via
onTriggerAreaEnter.
Step 3: NPC Script Cleanup
- Header: Ensure the header includes the
!posfromnpc_list.sql. - Logic Removal: Strip all
if/elsequest blocks. - Minimal Stub: If no non-quest logic remains, convert to:
---@type TNpcEntity
local entity = {}
entity.onTrigger = function(player, npc) end
entity.onTrade = function(player, npc, trade) end
return entity
- Preservation: Keep patrol logic (
onSpawn), Trust logic, or complex non-migrated quests in the script.
Step 4: Default Actions
If an NPC has a single default interaction (e.g., player:startEvent(200)), move it to scripts/zones/<Zone>/DefaultActions.lua:
['NPC_Name'] = { event = 200 },
Step 5: Global Marking
Mark the quest in scripts/globals/quests.lua with the specific partial conversion tag:
QUEST_NAME = 123, -- + Partial conversion. TODO: This needs completing with retail caps
4. Advanced Interaction Techniques
- Container Action Helpers (
eventvsprogressEvent): Under the hood (scripts/globals/interaction/container.lua), actions likequest:progressEvent(id)are simply syntactic sugar for creating a base Event and applying a priority modifier (e.g.Event:new(id):progress()). The same applies toquest:cutscene(id)orquest:replaceEvent(id). UseprogressEventfor critical quest progression to ensure it overrides default NPC behaviors. - Event Updates: Handle multi-choice menus via
onEventUpdate. - Bitmasks: Use
quest:setVarBit(player, 'Prog', bit)andquest:isVarBitsSet(player, 'Prog', bit). - Variables: Prefer
quest:getVaroverplayer:getCharVarfor framework-managed persistence. - Timers: Use
quest:setTimedVar(player, 'Timer', JstMidnight())for daily repeats.
5. Verification Golden Rule
Never assume existing logic is correct. Existing scripts often skip "Reminder" dialogue or "Post-Quest" flavor text. Always cross-reference the sruon/FFXI-EventsDump for every NPC involved in the quest to ensure 100% dialogue coverage.