Jump to content

Recommended Posts

Posted (edited)

Introduction:

Scripting is what coordinates and executes in-game events to create a story for the player. Gen III hacks have been able to tell new stories with custom scripts written from scratch; future hacks in more current generations are key for good hacks. This thread is to provide information in regards to how to script, what script commands do, and some example scripts. I'll try to go more in depth than pichu2001 did in his basic tutorial thread; if you are just starting off scripting,
you should definitely read his thread first
.

Starting Information:

First you need to extract the Script .narc with NitroExplorer, and unpack it into the individual files with editor.exe (kiwi.ds).

  • Program & Lua starter kit:
    download here

  • The location of the scripts is /a/0/5/6

There is currently no XSE equivalent of symbolic scripting for B2W2. We're doing this with hex. So have a hex editor ready (HxD or Hex Workshop), and get PPNFR to replace the individual scripts you edit.

How do you know what script is used where? You currently need to use a Lua script while emulating. The
script depicted below
is in the starter kit.

uSbDT.png

At the bottom right corner of the touch-screen, it tells you which Script is being used. Correlate this with the file numbers you extracted from the narc.

Script Structure:

, the script contains two parts. The pointers and the scripts that are pointed to.

iZNWB.jpg

Pointers are 4 bytes long (32 bits); the first script in this example image points to 00 00 00 38, which is equivalent to 00000038 = 0x38. 0x38 bytes after this pointer is the start of script 1. Use this formula to find the script's location in the file:

Current Read Offset + Pointer Value = Script Offset

In this example image, 0x04+0x38 = 0x3C. The first script is located at 0x3C... unfortunately it starts off reading as "End", so the script ends there. Try finding the offset of Script 2; you should get 0x3E, which just so happens to start right after Script 1 ends.

To tell the game to stop reading pointers, a 16 byte "STOP" = 13 FD is used (0xFD13). After the STOP, there are no more pointers and the raw script command data starts. Pointers are an important part of the file; if you insert anything between existing data, the pointers will point to improper locations. I'll cover this later.

Hello World: Interpreting Existing Scripts

Knowing how to script requires you to observe how commands are normally used by others. I'll show you an example script that has an NPC says a text line and ends the script when dismissed.

Jillv.jpg

Above is the example script file. As you can see, it contains one script, which starts at 0x06.

[color="purple"]02 00 00 00[/color] [u][i]13 FD[/i][/u] [color="blue"]2E 00 A6 00 47 05 74 00 3D 00 00 04 0A 00 00 00 00 00 32 00 3F 00 30 00 2F 00 02 00 00 00[/color]

First we must separate out our script data. Remove everything before the start of our script (everything before 0x06)

[color="blue"]2E 00 A6 00[/color] [color="green"]47 05[/color] [color="blue"]74 00 3D 00[/color] [color="green"]00 04 0A 00 00 00 00 00[/color] [color="blue"]32 00 3F 00 30 00 2F 00 02 00[/color] 00 00

Looks decent, let's make this more easily to see:

[color="blue"]2E 00[/color] 				-- [color="red"]LockAll[/color]
[color="blue"]A6 00[/color] [color="green"]47 05[/color] 			-- [color="red"]PlaySound Clink[/color]
[color="blue"]74 00[/color] 				-- [color="red"]FacePlayer[/color]
[color="blue"]3D 00[/color] [color="green"]00 04 0A 00 00 00 00 00[/color] 	-- [color="red"]Message2, text line 0xA, default settings[/color]
[color="blue"]32 00[/color] 				-- [color="red"]WaitKeypress[/color]
[color="blue"]3F 00[/color] 				-- [color="red"]CloseMessage2[/color]
[color="blue"]30 00[/color] 				-- [color="red"]WaitDelay[/color]
[color="blue"]2F 00[/color] 				-- [color="red"]UnlockAll[/color]
[color="blue"]02 00[/color] 				-- [color="red"]End[/color]

00 00				-- Padding to make the file's size divisible by 0x4

This is gibberish to the untrained eye, so I'll explain what each line does in 'English'.

[color="red"]LockAll[/color] 	[color="blue"]0x002E[/color]		-- Stop all movements of NPCs and Player. LOCK EVERYTHING. 
[color="red"]PlaySound Clink[/color]	[color="blue"]0x00A6[/color] [color="green"]0x0547[/color] 	-- Play a sound, sound number 0x0547 - "clink"
[color="red"]FacePlayer[/color]	[color="blue"]0x0074[/color]		-- Make the NPC that was interacted with face the player

[color="red"]Message2[/color]	[color="blue"]0x003D[/color]		-- Pops up a speech bubble from the interacted with NPC
	[color="green"]0x0400[/color]		-- Tells the game to use the current text file
	[color="green"]0x000A[/color]		-- Tells the game to use text line 0xA
	[color="green"]0x0000[/color]		-- Tells the game to place the bubble away from interaction
	[color="green"]0x0000[/color]		-- Tells the game to use the default speech bubble

[color="red"]WaitKeypress[/color]	[color="blue"]0x0032[/color]		-- Wait until a button is pressed before proceeding
[color="red"]CloseMessage2[/color]	[color="blue"]0x003F[/color]		-- Close the popped up speech bubble

[color="red"]WaitDelay[/color]	[color="blue"]0x0030[/color]		-- Waits a frame; cools down the game
[color="red"]UnlockAll[/color]	[color="blue"]0x002F[/color]		-- Allow all NPCs and player to move
[color="red"]End[/color]		[color="blue"]0x0002[/color]		-- Ends the script; read no further!

Pretty easy to interpret when you know what each command is...

Value Storage: Flags, Containers (const) and Variables

The game likes to keep track of what has happened in game, and your choices that you have made.

  • If you get an item from an NPC, the game sets a flag so that the NPC doesn't give it out again.

    • The game also sets flags to prevent NPCs from spawning (so that progress based NPCs appear/disappear)

    [*]If you choose a certain starter, the game sets a Const container to know what Rival teams to face you against.

    • Constants are stored in the save file along with flags.

    [*]Variables are temporary values that are used to contain values for a short time.

    • Variables do the majority of the work in scripts. They are defined by some command, then later used as dynamic parameters. Essentially symbolic math.

    • Variables are not saved in the save file; however if they are not cleared/set properly their values can persist into other custom scripts. When you are done with them, clear them!

Conclusion: Part 1

This introduction to scripting is just touching the surface on what you can do with B2W2 scripting. With over 800 commands at your disposal (more than 3x that of Gen III), there's so much more you can do!

Edited by Kaphotics
Posted

.

Quick Script Interpretation

The best way to figure out how to script an event is to observe one in game. I created a subversion of NPRE that parses B2W2 scripts.
.
How to use NPRE:
  1. Open the program
  2. File->Open your ROM
  3. Navigate to /a/0/5/6, right click open as Narc (script).
  4. Select Target game as B2W2
  5. A new window will pop up.
  6. In the top left corner, enter the script number you want to open. It will then parse the script!
Open spoiler for visual illustration of ^ above process ^
 

http://i.snag.gy/tQXj2.jpg

To exhibit the parsing, here's an image of the first example script file I gave in part 1. Open script file 0 with NPRE via the dropdown in the top left corner.
Open spoiler for visual illustration of ^ above process ^
 

http://i.snag.gy/gBahe.jpg

As you can see, Script 1 (displayed as Script 0) starts at Offset 60. In hex, that's 0x3C. The next script starts at 0x3E; this is exactly the same as we mathematically solved in part one of this tutorial. Now that you can quickly see how the game does certain events in game, you can easily open up that script file to see the raw hex instructions and what they do! But... you want to script your own stuff too!

-----

List of Common Commands

 

 

. Should be fairly easy to understand; although most don't have comments, it should be obvious as to how the parameters of each command are used (and how many).
Here's a reduced list with some description:
Logic & Meta commands:
 

0x0000	NOP		Do nothing
0x0001	NOP		Do nothing
0x0002	End		End Script
0x0003	Wait		Wait a given amount of time
--16bit parameter : Frames to wait

0x0004	CallRoutine	Point to a sub-script within the current script 
--32bit pointer parameter

0x0005	EndRoutine	End subscript, pop back to main script

--

0x0008	CompareTo	Compares the current internal number to parameter
--16bit value

0x0009	StoreVar	Stores Variable/Container as an internal number
--16bit value of Var/Cont to store.

0x000A	ClearVar	Clears the variable from internal memory.
--16bit value of Var/Cont to clear.

--

0x0010	StoreFlag	Stores the current status of an event flag.
--16bit flag number - 0~unset 1~set

0x0011	Condition	Comparison Condition
--16bit comparison condition =>< etc

0x0012	BitAND		Bitwise AND of two parameters
--16bit container
--16bit value to AND

0x0013	BitXOR		Bitwise XOR of two parameters
--16bit container
--16bit value to XOR

0x0019	CompareVar	Compares Container to a parameter
--16bit container
--16bit compareto

--

0x001C	CallStd		Calls a standard function (main->external script)
--16bit external script reference number

0x001D	ReturnStd	EndStd, pop back to main script (external->main)

0x001E	Jump / Goto	Goto another part of the script (pointer)
--32bit pointer

0x001F	If		If-Then jump compare with conditional, if satisfied->jump
--8bit result operator
--32bit pointer

0x0023	SetFlag		Toggle flag to set (1)
--16bit flag number to set

0x0024	ClearFlag	Toggle flag to unset (0)
--16bit flag to unset


0x0025	SetVarFlagStatus	Get status of flag
--16bit var
--16bit flag

0x0026	Add		Add value to Variable/Constant
--16bit container
--16bit add

0x0027	Subtract	Subtract value from Variable/Constant

0x0028	SetVarEqualVal	Sets Variable/Constant equal to Value
--16bit container
--16bit value

0x0029	-

0x002A	CopyVar		Copy variable to new, keeping qualities (text type etc)
--16bit destination var
--16bit source variable
 
NPC Interaction
 

               case 0x2E:
                   com.Name = "LockAll";
                   break;
               case 0x2F:
                   com.Name = "UnlockAll";
                   break;
               case 0x30:
                   com.Name = "WaitMoment";
                   break;
               case 0x32:
                   com.Name = "WaitButton";
                   break;


               case 0x74:
                   com.Name = "FacePlayer";
                   break;


               case 0x3C:
                   com.Name = "Message";
                   com.parameters.Add(reader.ReadByte()); //Costant
                   com.parameters.Add(reader.ReadByte()); //Costant
                   com.parameters.Add(reader.ReadUInt16()); //Message Id
                   com.parameters.Add(reader.ReadUInt16()); //NPC Id 
                   com.parameters.Add(reader.ReadUInt16()); //Bottom/Top View.
                   com.parameters.Add(reader.ReadUInt16()); //Message Type
                   break;
               case 0x3D:
                   com.Name = "Message2";
                   com.parameters.Add(reader.ReadByte()); //Costant
                   com.parameters.Add(reader.ReadByte()); //Costant
                   com.parameters.Add(reader.ReadUInt16()); //Message Id
                   com.parameters.Add(reader.ReadUInt16()); //Bottom/Top View.
                   com.parameters.Add(reader.ReadUInt16()); //Message Type
                   break;
               case 0x3E:
                   com.Name = "CloseMessageKeyPress";
                   break;
               case 0x3F:
                   com.Name = "CloseMessageKeyPress2";
                   break;

               case 0x33:
                   com.Name = "MusicalMessage";
                   com.parameters.Add(reader.ReadUInt16()); //Message Id
                   break;
               case 0x34:
                   com.Name = "EventGreyMessage";
                   com.parameters.Add(reader.ReadUInt16()); //Message Id
                   com.parameters.Add(reader.ReadUInt16()); //Bottom/Top View.
                   break;
               case 0x35:
                   com.Name = "CloseMusicalMessage";
                   break;
               case 0x36:
                   com.Name = "CloseEventGreyMessage";
                   break;
               case 0x38:
                   com.Name = "BubbleMessage";
                   com.parameters.Add(reader.ReadUInt16()); //Message Id
                   com.parameters.Add(reader.ReadByte()); //Bottom/Top View.
                   break;
               case 0x39:
                   com.Name = "CloseBubbleMessage";
                   break;
 
Teleportation
 

               case 0xBE:
                   com.Name = "Warp";
                   com.parameters.Add(reader.ReadUInt16()); //Map Id
                   com.parameters.Add(reader.ReadUInt16()); // X coordinate
                   com.parameters.Add(reader.ReadUInt16()); // Y coordinate
                   break;
               case 0xBF:
                   com.Name = "TeleportWarp";
                   com.parameters.Add(reader.ReadUInt16()); //Map Id
                   com.parameters.Add(reader.ReadUInt16()); // X coordinate
                   com.parameters.Add(reader.ReadUInt16()); // Y coordinate
                   com.parameters.Add(reader.ReadUInt16()); // Z coordinate
                   break;
               case 0xC1:
                   com.Name = "FallWarp";
                   com.parameters.Add(reader.ReadUInt16()); //Map Id
                   com.parameters.Add(reader.ReadUInt16()); // X coordinate
                   com.parameters.Add(reader.ReadUInt16()); // Y coordinate
                   break;
               case 0xC2:
                   com.Name = "FastWarp";
                   com.parameters.Add(reader.ReadUInt16()); //Map Id
                   com.parameters.Add(reader.ReadUInt16()); // X coordinate
                   com.parameters.Add(reader.ReadUInt16()); // Y coordinate
                   com.parameters.Add(reader.ReadUInt16()); // Hero's Facing
                   break;
               case 0xC3:
                   com.Name = "UnionWarp"; // warp to union room
                   break;
               case 0xC4:
                   com.Name = "TeleportWarp";
                   com.parameters.Add(reader.ReadUInt16()); //Map Id
                   com.parameters.Add(reader.ReadUInt16()); // X coordinate
                   com.parameters.Add(reader.ReadUInt16()); // Y coordinate
                   com.parameters.Add(reader.ReadUInt16()); // Z coordinate
                   com.parameters.Add(reader.ReadUInt16()); // Hero's Facing
                   break;
 
All this is pretty useless unless you know what you are doing. More advanced scripting is covered in the next post.

 

 

again,
. Biggest database of known commands.
Posted (edited)

.

Common Use Scripts & Snippets of Large Ones

Simple NPC Interaction

[color="blue"]2E 00[/color] 				-- [color="red"]LockAll[/color]
[color="blue"]A6 00[/color] [color="green"]47 05[/color] 			-- [color="red"]PlaySound Clink[/color]
[color="blue"]74 00[/color] 				-- [color="red"]FacePlayer[/color]
[color="blue"]3D 00[/color] [color="green"]00 04 0A 00 00 00 00 00[/color] 	-- [color="red"]Message2[/color][color="orange"], current text file, text line 0xA, default settings[/color]
[color="blue"]32 00[/color] 				-- [color="red"]WaitKeypress[/color]
[color="blue"]3F 00[/color] 				-- [color="red"]CloseMessage2[/color]
[color="blue"]30 00[/color] 				-- [color="red"]WaitDelay[/color]
[color="blue"]2F 00[/color] 				-- [color="red"]UnlockAll[/color]
[color="blue"]02 00[/color] 				-- [color="red"]End[/color]

NPC that you didn't interact with says a message:

... You can't use FacePlayer. 
[color="blue"]3C 00[/color] [color="green"]00 04 02 00 01 00 00 00 00 00[/color] 	[color="red"]Message[/color] [color="orange"]current text file, line 0x0002, NPC 0x0001 (from overworld #), default view/type[/color]
[color="blue"]32 00[/color] 					[color="red"]WaitKeypress[/color]
[color="blue"]3E 00[/color] 					[color="red"]CloseMessage[/color]
...

YesNo Box - Saying No ends the conversation

...
[color="blue"]47 00[/color] [color="green"]10 80[/color]				[color="red"]PopYesNo[/color] [color="orange"]Store Result@Var10[/color]

[color="blue"]09 00[/color] [color="green"]10 80[/color] 				[color="orange"]Internalize Var10[/color]
[color="blue"]08 00[/color] [color="green"]00 00[/color] 				[color="orange"]Compare to 0 (yes=true=0)[/color]
[color="blue"]11 00[/color] [color="green"]01 00[/color] 				[color="orange"]Condition Equal[/color]
[color="blue"]1F 00[/color] [color="green"][b][u]FF[/u][/b][/color] [color="purple"]06 00 00 00[/color]			[color="orange"]If False, Jump 0x00000006 to #no[/color]

[color="blue"]1E 00[/color] [color="purple"]14 00 00 00[/color]			[color="orange"]Jump 0x00000014 to #body[/color]

[color="blue"]3D 00[/color] [color="green"]00 04 02 00 00 00 00 00[/color] 		[color="orange"]##no Message (line 2)[/color]
[color="blue"]32 00[/color] 					[color="red"]WaitKeypress[/color]
[color="blue"]3F 00[/color] 					[color="red"]CloseMessage2[/color]
[color="blue"]30 00[/color] 					[color="red"]WaitMoment[/color]
[color="blue"]2F 00[/color] 					[color="red"]ReleaseAll[/color]
[color="blue"]02 00[/color] 					[color="Red"]End[/color]

[color="blue"]A6 00[/color][color="green"] 47 05[/color] 				[color="orange"]##body Sound Clink[/color]
[color="blue"]3D 00[/color][color="green"] 00 04 03 00 00 00 00 00[/color] 		[color="orange"]Message (line 3)[/color]
[color="blue"]32 00[/color] 					[color="Red"]WaitKeypress[/color]
[color="blue"]3F 00[/color] 					[color="Red"]CloseMessage2[/color]
... proceed...

YesNo box script in action:

[video=youtube;8tBDVstp1hM]

GiveItem

...
[color="blue"]09 00[/color] [color="green"]00 80[/color]		[color="red"]StoreVar[/color] [color="orange"]0x8000[/color]
[color="blue"]09 00[/color] [color="green"]01 80[/color]		[color="red"]StoreVar[/color] [color="orange"]0x8001[/color]
[color="blue"]2A 00[/color] [color="green"]00 80 32 00[/color]	[color="orange"]Item # to give[/color]
[color="blue"]2A 00[/color] [color="green"]01 80 01 00[/color]	[color="orange"]Amount to give[/color]
[color="blue"]1C 00[/color] [color="green"]F5 0A[/color]		[color="red"]CallStd[/color] [color="orange"]'give item'[/color]
[color="blue"]0A 00[/color] [color="green"]01 80[/color]		[color="red"]ClearVar[/color] [color="orange"]0x8000[/color]
[color="blue"]0A 00[/color] [color="green"]00 80[/color]		[color="red"]ClearVar[/color] [color="orange"]0x8000[/color]
...

Another one which says the quantity but no colored item text
...
[color="blue"]09 00[/color] [color="green"]00 80 [/color]		[color="red"]StoreVar[/color] [color="orange"]0x8000[/color]
[color="blue"]09 00[/color] [color="green"]01 80 [/color]		[color="red"]StoreVar[/color] [color="orange"]0x8000[/color]
[color="blue"]2A 00[/color] [color="green"]01 80 02 00 [/color]	[color="orange"]Item # to give[/color]
[color="blue"]2A 00[/color] [color="green"]00 80 0F 00 [/color]	[color="orange"]Amount to give[/color]
[color="blue"]1C 00[/color] [color="green"]01 0B[/color]		[color="red"]CallStd[/color] [color="orange"]'give item2'[/color]
[color="blue"]0A 00[/color] [color="green"]01 80[/color]		[color="red"]ClearVar[/color] [color="orange"]0x8001[/color]
[color="blue"]0A 00[/color] [color="green"]00 80[/color]		[color="red"]ClearVar[/color] [color="orange"]0x8000[/color]

Edited by Kaphotics
Posted (edited)

.

Useful Large Scripts (Trainer / Wild Battles)

Trainer Battle

[color="blue"]85 00[/color] [color="green"]2D 03 00 00 00 00[/color] 		[color="red"]SingleTrainerBattle[/color] [color="orange"](813 - Colress), trainer2 = none, params=none[/color]

				---Battle Ends, script continues---

[color="blue"]8D 00[/color] [color="green"]10 80[/color] 				[color="orange"]StoreBattleResult to var 0x8010[/color]
[color="blue"]09 00[/color] [color="green"]10 80[/color] 				[color="orange"]StoreVar 0x8010[/color]
[color="blue"]08 00[/color] [color="green"]01 00[/color] 				[color="orange"]CompareTo 0x01[/color]
[color="blue"]11 00[/color] [color="green"]01 00[/color] 				[color="orange"]Condition 0x01 (notEQ)[/color]
[color="blue"]1F 00[/color] [color="green"]FF[/color] [color="purple"]08 00 00 00 [/color]			[color="orange"]Incapacitation logic (return to pokecenter, etc)[/color]
[color="blue"]8E 00[/color] 						[color="orange"]and eventually restore to Overworld if won[/color]
[color="blue"]1E 00[/color] [color="purple"]02 00 00 00[/color]
[color="blue"]8C 00[/color] 

PKM Battle

[color="blue"]74 01[/color] [color="green"]7D 02 41 00 01 00 [/color]		[color="red"]StartWildBattle[/color] [color="orange"]dex=0x027D @ level 0x41, parameters=0x0001[/color]

				---Battle Ends, script continues---

[color="blue"]77 01[/color] [color="green"]10 80[/color] 				[color="orange"]Check if KO'd[/color]
[color="blue"]09 00[/color] [color="green"]10 80[/color] 
[color="blue"]08 00[/color] [color="green"]01 00[/color] 
[color="blue"]11 00[/color] [color="green"]01 00[/color] 
[color="blue"]1F 00[/color] [color="green"]FF[/color] [color="purple"]14 00 00 00[/color] 			[color="orange"]Incapacitation Logic[/color]

[color="blue"]23 00[/color] [color="green"]A0 03[/color] 				[color="orange"]Set disablespawn flag[/color]
[color="blue"]23 00[/color] [color="green"]94 01[/color] 				[color="orange"]Set encountered at least once flag[/color]
[color="blue"]6C 00[/color] [color="green"]00 00[/color] 				[color="orange"]Remove NPC[/color]
[color="blue"]75 01[/color] 					[color="red"]EndWildBattle[/color]
[color="blue"]1E 00[/color] [color="purple"]02 00 00 00[/color] 
[color="blue"]76 01[/color] 					[color="red"]EndWildBattle1[/color]

[color="blue"]78 01[/color] [color="green"]10 80[/color] 
[color="blue"]19 00[/color] [color="green"]10 80 00 00[/color] 			[color="orange"]Check if captured[/color]
[color="blue"]1F 00[/color] [color="green"]01[/color] [color="purple"]06 00 00 00[/color] 

[color="blue"]1E 00[/color] [color="purple"]0A 00 00 00[/color] 

[color="blue"]23 00[/color] [color="green"]93 01[/color] 				[color="orange"]Set captured flag, goto end[/color]
[color="blue"]1E 00[/color] [color="purple"]30 00 00 00[/color] 

[color="blue"]19 00[/color] [color="green"]10 80 01 00[/color] 			[color="orange"]Check if player was defeated?[/color]
[color="blue"]1F 00[/color] [color="green"]01[/color] [color="purple"]13 00 00 00 

[color="blue"]19 00[/color] [color="green"]10 80 02 00[/color]			[color="orange"]Check if defeated[/color]
[color="blue"]1F 00[/color] [color="green"]01 [color="purple"]06 00 00 00[/color] 			[color="orange"]Goto #defeated[/color]

[color="blue"]1E 00[/color] [color="purple"]10 00 00 00 [/color]			[color="orange"]Goto end[/color]

[color="blue"]34 00[/color] [color="green"]01 00 02 00[/color] 			[color="orange"]#defeated pkm, message flew away.[/color]
[color="blue"]32 00[/color] [/color]
[color="blue"]36 00[/color] [/color]
[color="blue"]1E 00[/color] [color="purple"]00 00 00 00[/color] 			[color="orange"]Goto end[/color]

[color="blue"]30 00 2F 00 02 00[/color] 00 00			end

---

Other Useful Scripts (Trades / PokeMart)

.. coming soon ..

Edited by Kaphotics
Posted (edited)

.

NPC Manipulation (Add / Remove / MOVE)

NPCs are referred to with an 8 bit number, based off of their overworld data. The hero is always 0xFF (entry 255), and the map's overworld NPCs start at 0x00+

Adding and Removing an NPC:

Overworld Add/Remove: You can add and remove an NPC based on its overworld ID.

[color="blue"]006B[/color] [color="red"]AddNPC[/color][color="green"]
- u16: NPC ID[/color]

[color="blue"]6B 00[/color] [color="green"]0A 00[/color]
Add NPC ID=0x0A

=====

[color="blue"]006C[/color] [color="red"]RemoveNPC[/color][color="green"]
- u16: NPC ID[/color]

[color="blue"]6C 00[/color] [color="green"]0A 00[/color]
Remove NPC ID=0x0A

Adding only works for NPCs that already exist in the overworld file, but aren't on the map (previously removed, or flag prevented spawn). In order to add a new NPC from scratch, use CreateNPC

[color="blue"]0069[/color] [color="red"]CreateNPC[/color][color="green"]
- u16: X coordinate
- u16: Y coordinate
- u16: Face direction
- u16: Overworld ID
- u16: Sprite Number
- u16: Movement Permission[/color]

[color="blue"]69 00[/color] [color="green"]01 00 02 00 03 00 41 00 53 00 02 00[/color]
make NPC @ (X,Y)=(1,2) facing Right, NPC ID=0x41, sprite=SuitMan, looks around randomly 

Moving an NPC:

You have three methods to move NPCs.

  • 0064
    -
    Applymovement
    - You can tell them what movements to perform.

  • 006D
    -
    RelocateNPC
    - You can instantly relocate them to new coordinates.

  • 024F
    -
    MoveNPC
    - You can tell them to move coordinates (over a time period) from their current coordinates.

==========

0064
-
Applymovement

Applymovement has many movement types. Give the NPC a movement and how many times it does that movement.

...time to move an NPC.
[color="blue"]64 00[/color] [color="green"]FF 00[/color] [color="purple"]08 00 00 00[/color] 		ApplyMovement 0xFF~hero (jump 0x00000008, start those movements, continue script)
[color="blue"]65 00[/color] 					WaitMovement
[color="blue"]1E 00[/color] [color="purple"]16 00 00 00[/color]			Jump (0x00000016) past the movement instructions
[color="gold"]01 00 00 00[/color] 				Face Down for 0 iterations.
[color="gold"]4B 00 00 00[/color] 				Exclamation for 0 iterations (0 or 1 is ok)
[color="gold"]17 00 01 00[/color] 				Shuffle right 1 step (one can always have StoreHeroPosition -> different moves)
[color="gold"]14 00 01 00[/color] 				Shuffle up 1 step
[color="gold"]01 00 00 00[/color] 				Face Down for 0 iterations
[u]FE 00 00 00[/u]				End Movement Instructions
... continue script

==========

006D
-
RelocateNPC
- Used mainly for off-screen relocation of NPCs or between fadeouts.

[color="blue"]006D[/color] [color="red"]RelocateNPC[/color][color="green"]
- u16: Overworld ID
- u16: X coordinate
- u16: Y coordinate
- u16: Z coordinate
- u16: Face direction[/color]

[color="blue"]6D 00[/color] [color="green"]00 41 01 00 02 00 00 00 02 00[/color]
relocate NPC=0x41 to (X,Y,Z)=(1,2,0), facing left 

==========

024F
-
MoveNPC

[color="blue"]024F[/color] [color="red"]MoveNPC[/color][color="green"]
- u16: Overworld ID
- u16: X coordinate
- u16: Y coordinate
- u16: Z coordinate
- u16: Rapidity (how fast)
- u16: Face Direction (not used?)[/color]

[color="blue"]4F 02 [/color] [color="green"]FF 00 19 00 0F 00 02 00 0C 00 02 00[/color]
relocate NPC=0xFF to (X,Y,Z)=(0x19,0x15,0x02), at speed 0x0C 

Edited by Kaphotics
Posted

.

Testing custom scripts in real time

Using a Lua script, you can write a script into the game in real time. Using
Event Script Writer (scrins.Lua)
, the script writes a text string (of hex copypasted from a hex editor) directly into the memory. If an NPC were to initiate script 0, the game would read the inserted script rather than the one from the ROM.

To use:

  1. Save state.

  2. Load the script.

  3. Stop it for now.

  4. Talk to the NPC once so that the default script data is loaded.

  5. Use the Overworld Editor script to force all NPCs to use Script 1 (offset 0xA = 0x1).

  6. Run the insertion script.

  7. Talk to the NPC, custom script runs.

Dump of scripting related tutorial resources:

My Video Tutorials

============

Feel free to ask questions related to scripting, whether it be how they work or how to script a certain event.

Posted
That's not a question. Don't understand something?

No, I replied to Kaphotics he had written "reserve" and I thought he was talking to me since I was only following the discussion, that's all.

  • 4 weeks later...
Posted (edited)

scrcmd.txt from my info dump in the r&d forum:

http://pastebin.com/yhVVej8A

the script commands are at the end, starting with cmd 0, s0000. the array at the top is super-important if you're adding commands. it's a 0x2f3 u8 array(matching the number of script commands) of bitflags for each script command that each one is verified against before executing. the verifier works because it changes for every type of script command- moves, AI, effects, and events. it's part of the virtual machine built before execution starts. see here: http://img.thundaga.com/verify.png green is the verifier that runs first then hands off to the command execution in red.

7 is for a normal script command

1 is an assembly command

0 is for non-existant script commands- there are about 90 or 95 non-existent script command numbers, more than enough to add your own.

the other flags are for fine-grained stuff and aren't relevant for basically anyone besides me.

ex. s0002 ends a script without jumping to the asm handler, so it's a normal script command and is a 7. s0003 pauses the entire script system for a given amount of time- a spinwait kinda thing in C#. it does it via the asm handler in the script virtual machine, so it's a 1 in the verifier array.

the main script environment struct virtual machine is @ 224A140 and looks like this:

struct ScriptEnvironment
{
 u16 maxStackDepth;
 u16 maxVars;
 ScriptCall *cmdArray;
 u32 cmdMax;
 u32 unk3;
 u32 unk4;
 u32 unk5;
 u8 stackPointer;
 u8 executionState;
 u8 scriptCmpResult;
 u8 padding;
 AsmCall funcptr;
 u8 *script_pos_ptr;
 u8 *script_stack_ptr;
 u32 *vars;
 ScrcmdEnvironment *scrcmd_ptr;
 ScriptCall script_callback_verifier;
 void *sys_info;
 Arc_Tool *script_arc_file_ptr;
};

or this if you like it in excel: (see info dump download here for the spreadsheet)

Offset	Member	Size	Use	224A140	2257248	224738C	224A004
0x0	Stack size	2	Max size of the script stack- 4 bytes each				
0x2	Global/Environment var size	2	Max number of global/script environment vars- 4 bytes each				
0x4	Command ptr array	4	Ptr to command array to fetch script functions from				
0x8	Command max 	4	Total number of commands				
0xC							
0x10							
0x14							
0x18	Stack position	1	sp++ every time a value is added to the stack, sp-- when removed				
0x19	Environment state	1	Script environment current running state- 0/1/2 end/script/asm				
0x1A	Compare result	1	Used for loading from compare mode array for conditionals				
0x1B	Padding	1	Empty, 00				
0x1C	Asm ptr	4	Ptr to asm routine for script state asm				
0x20	Script pos	4	Ptr to current script position to read from				
0x24	Stack	4	ptr to stack				
0x28	Global/Environment var	4	Global/Environment var ptr				
0x2C	Scrcmd environment	4	Ptr to info container				
0x30	Script callback	4	Ptr to script callback verifier function				
0x34	Gamesys	4	Ptr to main gamesystem				
0x38	Script file	4	Ptr to beginning of loaded script file via Arc_Tool*				

the entire core of the script system: (see scriptHandler_main for the actual script handler with notes)

http://pastebin.com/zk31sJYg

and last, there are up to 4 virtual machines running at once in a struct that looks like this:

struct ScriptSystem
{
 u16 vmMax;
 u16 vmCount;
 ScriptEnvironment *vmachines[4];
};

vmMax is always 4 and the first element of vmachine struct ptrs will always be 224A140. there's a script command that adds to this and creates submachines off the main one: s001B.

(for anyone who hasn't realized it by now, the entire(literally entire) game hinges on a series of structs within structs within structs with structs that are nothing but pointers to other structs. gamefreak is obsessive about it. it also happens to be good practice for an embedded ARM system- reducing literal pools via relative offsetting, etc.)

Edited by Bond697
  • 2 weeks later...
Posted (edited)

it turns out that the 3 unknowns in my script environment:

struct ScriptEnvironment
{
 u16 maxStackDepth;
 u16 maxVars;
 ScriptCall *cmdArray;
 u32 cmdMax;
 [b]u32 unk3;
 u32 unk4;
 u32 unk5;[/b]
 u8 stackPointer;
 u8 executionState;
 u8 scriptCmpResult;
 u8 padding;
 AsmCall funcptr;
 u8 *script_pos_ptr;
 u8 *script_stack_ptr;
 u32 *vars;
 ScrcmdEnvironment *scrcmd_ptr;
 ScriptCall script_callback_verifier;
 void *sys_info;
 Arc_Tool *script_arc_file_ptr;
};

are actually the components of a scripting plugin system that gamefreak uses for commands that are very specific to certain contexts in the game. they're plugins that can be switched in when new sub-script environments are started and are "unloaded" when the environment memory is freed. the environment actually looks like this:

struct ScriptEnvironment
{
 u16 maxStackDepth;
 u16 maxVars;
 ScriptCall *cmdArray;
 u32 cmdMax;
 ScriptPlugin plugin;
 u8 stackPointer;
 u8 executionState;
 u8 scriptCmpResult;
 u8 padding;
 AsmCall funcptr;
 u8 *script_pos_ptr;
 u8 *script_stack_ptr;
 u32 *vars;
 ScrcmdEnvironment *scrcmd_ptr;
 ScriptCall script_callback_verifier;
 void *sys_info;
 Arc_Tool *script_arc_file_ptr;
};

ScriptPlugin being this:

struct ScriptPlugin
{
 ScriptCall *plugin_lut;
 int plugin_cmd_count;
 int plugin_min_cmd;    // seems to always be 0x3E8(1000d)
};

at 0x216C1EC there's a set of 17 registered script plugins. each info set is 0x14 bytes. basically, there's a word in the player data(0x223B370 + 0x2F8) that gets updated when a plugin is needed. that byte is read while the script environment is being built and the right plugin pointer is loaded from the array. that plugin ptr is written to the script virtual machine. then the plugin ptr is run through a verifier function that verifies the plugin and counts the number of commands in it. that number of commands is written to the virtual machine and so is 0x3E8, which is always the minimum script command value. after it's loaded, the script handler and virtual machine run as usual. when they hit a special command from a plugin, it activates an exception handler kind of thing that handles the very high script number and points the handler to the plugin to pull data from. after that command runs, it returns to the main script handler and continues execution commands back and forth between the main script command set and the plugin.

Edited by Bond697
Posted

figuring out which script file a callstd call will use:

-take the argument supplied with the callstd and look here: http://pastebin.com/q98R6YfN.

-this is a 60 element array of 5 member 16-bit structs.

-go down the array of structs looking at the first member of each struct until you find the one where the callstd argument is finally bigger than the first member of the struct.

-take the third member of that struct and turn it to decimal. there's your script file number.

Posted (edited)

Can anyone help me with this script ? In the end of the event, NPCs are not unlocked but if i use bicycle then they are unlocked. I've tried to fix it a lot but it doesn't work :(

Here is the whole script( it is based on Scripted event Kaphotics - Thanks)

2E 00 74 00 A6 00 47 05 4C 00 00 3D 00 00 04 00 00 00 00 00 00 47 00 10 80 09 00 10 80 08 00 00 00 11 00 01 00 1F 00 FF 23 04 00 00 03 00 06 00 98 00 B4 04 3D 00 00 04 01 00 00 00 00 00 32 00 3F 00 1E 00 00 00 00 00 A6 00 47 05 3D 00 00 04 03 00 00 00 00 00 32 00 3F 00 30 00 09 00 00 80 09 00 01 80 2A 00 00 80 01 00 2A 00 01 80 02 00 1C 00 F5 0A 0A 00 01 80 0A 00 00 80 03 00 0F 00 30 00 A6 00 47 05 3D 00 00 04 04 00 00 00 00 00 32 00 3F 00 03 00 04 00 98 00 F3 04 9B 01 6D 00 6C 00 02 00 6C 00 03 00 6C 00 05 00 6C 00 06 00 6C 00 08 00 6D 00 0A 00 74 00 01 00 A8 02 00 00 6D 00 0B 00 76 00 01 00 A8 02 00 00 6D 00 0C 00 78 00 01 00 A8 02 00 00 6D 00 0D 00 7A 00 01 00 A8 02 00 00 AD 01 03 00 0A 00 64 00 FF 00 08 00 00 00 65 00 1E 00 18 00 00 00 01 00 00 00 4B 00 00 00 17 00 01 00 14 00 01 00 01 00 00 00 FE 00 00 00 30 00 3D 00 00 04 06 00 00 00 00 00 32 00 3F 00 64 00 09 00 06 00 00 00 1E 00 10 00 00 00 02 00 00 00 16 00 06 00 15 00 0C 00 FE 00 00 00 03 00 10 00 6C 00 09 00 64 00 0B 00 06 00 00 00 1E 00 0C 00 00 00 9F 00 01 00 4C 00 02 00 FE 00 00 00 03 00 24 00 64 00 0C 00 06 00 00 00 1E 00 08 00 00 00 4C 00 02 00 FE 00 00 00 A6 00 47 05 3C 00 00 04 07 00 0C 00 00 00 00 00 32 00 3F 00 AB 00 7E 01 00 00 64 00 0D 00 06 00 00 00 1E 00 10 00 00 00 4C 00 02 00 4E 00 01 00 00 00 01 00 FE 00 00 00 64 00 0C 00 06 00 00 00 1E 00 10 00 00 00 4D 00 02 00 4F 00 01 00 00 00 01 00 FE 00 00 00 A6 00 47 05 3C 00 00 04 08 00 0B 00 00 00 00 00 32 00 3F 00 AB 00 7F 01 00 00 64 00 0A 00 06 00 00 00 1E 00 10 00 00 00 4C 00 02 00 4F 00 03 00 00 00 01 00 FE 00 00 00 64 00 0B 00 06 00 00 00 1E 00 10 00 00 00 4D 00 02 00 4F 00 01 00 00 00 01 00 FE 00 00 00 6D 00 0E 00 72 00 01 00 9A 02 00 00 6D 00 0F 00 70 00 01 00 9A 02 00 00 03 00 08 00 3C 00 00 04 09 00 0E 00 00 00 01 00 32 00 3F 00 98 00 4D 04 64 00 0E 00 06 00 00 00 1E 00 14 00 00 00 4D 00 0B 00 4F 00 07 00 4C 00 01 00 01 00 01 00 FE 00 00 00 64 00 0F 00 06 00 00 00 1E 00 14 00 00 00 4D 00 0B 00 4F 00 07 00 4C 00 01 00 01 00 01 00 FE 00 00 00 03 00 4C 00 64 00 0A 00 06 00 00 00 1E 00 0C 00 00 00 4D 00 01 00 00 00 01 00 FE 00 00 00 03 00 08 00 64 00 0D 00 06 00 00 00 1E 00 0C 00 00 00 4D 00 01 00 00 00 01 00 FE 00 00 00 03 00 1A 00 64 00 0E 00 06 00 00 00 1E 00 08 00 00 00 02 00 01 00 FE 00 00 00 A6 00 47 05 3C 00 00 04 0A 00 0E 00 00 00 00 00 32 00 3F 00 03 00 0C 00 64 00 0E 00 06 00 00 00 1E 00 0C 00 00 00 01 00 00 00 9C 00 01 00 FE 00 00 00 64 00 0F 00 06 00 00 00 1E 00 08 00 00 00 9C 00 01 00 FE 00 00 00 AC 01 03 00 24 00 A6 00 00 08 98 00 00 00 03 00 4C 00 A9 00 76 05 A7 00 03 00 08 00 AB 00 80 01 00 00 03 00 10 00 AC 00 03 00 2C 00 A6 00 91 05 03 00 1F 00 AB 01 03 00 0C 00 64 00 0E 00 06 00 00 00 1E 00 0C 00 00 00 9B 00 01 00 01 00 00 00 FE 00 00 00 64 00 0F 00 06 00 00 00 1E 00 0C 00 00 00 9B 00 01 00 01 00 00 00 FE 00 00 00 3C 00 00 04 0B 00 0E 00 00 00 00 00 32 00 3F 00 3C 00 00 04 0C 00 0C 00 00 00 00 00 32 00 3F 00 85 00 01 00 00 00 00 00 28 00 20 80 00 00 8D 00 10 80 09 00 10 80 08 00 01 00 11 00 01 00 1F 00 FF 08 00 00 00 8E 00 1E 00 02 00 00 00 8C 00 3C 00 00 04 0E 00 0C 00 00 00 00 00 32 00 3F 00 03 00 0F 00 AC 01 6C 00 0A 00 6C 00 0B 00 6C 00 0C 00 6C 00 0D 00 6B 00 02 00 6B 00 03 00 6B 00 05 00 6B 00 06 00 6B 00 08 00 6C 00 0E 00 6C 00 0F 00 AB 01 03 00 0A 00 34 00 0F 00 02 00 32 00 36 00 1E 00 10 00 00 00 3D 00 00 04 02 00 00 00 00 00 32 00 3F 00 30 00 2F 00 02 00 00 00 

BTW, i don't know how to use "Wait command" - 0x03.

Sometimes it's 03 00 2A 00 and

sometimes it's 03 00 0F 00

What is the diference? I'm confused.

Edited by na81096
Posted

the parameter for 'wait' is essentially like in-game frames. the larger the number, the longer the wait

you need to make sure that the script finishes properly. if something isn't done right than the game error handles -> premature quit or bad termination.

Posted

I don't know, but there's probably 30 frames per second in this game. No more than 60. Possibly slightly less than 30. Unless Kaphotics or someone saves the day with a specific answer, pick a number and see if that's long enough for you.

Posted (edited)

Oh thks now I know how to use it. But still having trouble with the script ... I use NPRE to check it and it's OK but in the game at the end it still not unlock everything :( Can you help me plz ?

Edited by na81096
  • 4 weeks later...
  • 1 month later...
Posted

Kaphotics, how to I make the pokemon change the pokemon form with the script that u given me. I edited ur script and I have a keldeo but i dont know how to change it into resolute form... Here is the script

74 01 87 02 32 00 01 10 77 01 00 80 09 00 10 80 08 00 01 00 11 00 01 00 1F 00 FF 14 00 00 00 23 00 A0 03 23 00 94 01 6C 00 00 00 75 01 1E 00 02 00 00 00 76 01 78 01 10 80 19 00 10 80 00 00 1F 00 01 06 00 00 00 1E 00 0A 00 00 00 23 00 93 01 1E 00 30 00 00 00 19 00 10 80 01 00 1F 00 01 13 00 00 00 19 00 10 80 02 00 1F 00 01 06 00 00 00 1E 00 10 00 00 00 32 00 36 00 1E 00 00 00 00 00 30 00 2F 00 02 00 00 00

.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...