LunaDLL Tutorial

From Moondust Wiki
Jump to navigation Jump to search


Using lunadll.txt scripts

The most important things to think about are the action you want to do and the point at which the action should happen. The scripting engine only deals with those two things: A time, and an action.

Example times:

  • While loading a level
  • Always
  • In section 1
  • In section 2
  • When a timer runs down
  • When touching a block of a certain type
  • When typing a custom cheat code
  • When a condition is met such as NPC with ID 138 no longer exists

Example actions:

  • Change the player character
  • Change the powerup
  • Change the reserve powerup
  • Play a sound
  • Change the music
  • Show text
  • Trigger an event
  • Manipulate game memory somehow

The basics

The first step is creating a file named lunadll.txt in your level folder. That's it.

Structure

There's only 4 things you'll be writing in your lunadll.txt.

Time designator - #

# designates a time of an action. They mostly correspond to sections of the level #-1 means during level load #0 means "always" #1 means section 1 #2 means section 2 ... #21 means section 21

#1000 and up is an advanced feature for designating custom events / custom blocks of commands

Once you write say #-1, then all commands you write from then on will be run during the level load time of your level, until you write #1 or some other designator.

Comments - // comment

Any line that contains a // will be understood as a comment. Comments are only for humans to read, and the scripting engine ignores them. They're actually important for remembering just what the hell your code is supposed to do, and for others to understand what your code does.

Commands - Kill,0,0,0,0,0,0

Commands are the most important part of lunadll scripts, and the most complicated. There's no way to remember them all or what all of the parts of one do usually, so you have to check the reference.

They mostly have the same parts, separated by commas

"Kill" - This first part is the command name. It's the easiest part to remember and explains what the command does, usually. This one kills something. "Infinite flying" activates infinite flying for the player, and so on.

After each command name, there are 6 parameters separated by commas. What each one does is specific to each command, and they're quite hard to remember, which is why you'll have to refer to the reference. But there is some underlying pattern.

First - The target parameter

The first number is usually the "target" of the command. If you want the command to target the player, 0 is usually the player. If you want to target the "key" NPC (the thing you pick up that opens lockd doors), well you need to target the key's NPC ID (the key is NPC ID 38)

Second, Third, Fourth - Options

Parameter 2, 3, 4 are extra parameters that can mean just about anything, but for a lot of commands that aren't complicated, they usually aren't used and are just 0. Check the command reference.

Fifth - Active time

Parameter 5 is virtually always the "active time" specifier. Basically it's how long the command will "run" before finally deleting itself. 0 means it never runs out. 1 means it lasts 1 frame. 60 means it lasts 60 frames (1 second), and so on.

Keep in mind that commands don't run at all unless they're in the section you're in. A command in #21 with 1 frame active time will sit there forever until the player actually gets to section 21, and then it will run once and then die.

Sixth - Text

Unlike the others, the 6th parameter can also be entered as text, but is oftentimes just left as 0 anyway. It's usually where you type messages, decimal numbers, and options.

That's all you need to know about commands.

Script footer - #END

#END is a special string you should put at the end of your .txt file. It's pretty simple - just put #END after everything else.

Examples

Here's a bunch of examples in ascending level of complexity with lots of comments

Basic filter script

#-1
FilterToBig,0,0,0,0,0,0
#END

That's the whole thing. First we have the level load designator, which is where you normally want a filter. FilterToBig lowers Demo's powerup down to mushroom level if she has anything higher than a mushroom, and does nothing if she's small.

FilterToBig's 6 parameters do nothing except the 5th, the active time. It's set to 0 which means "always", but since it's in the level load section, it only works for that 1 frame when the level is loading. If this were in section #0, Demo would never be able to get a powerup higher than a mushroom because she would constantly be filtered to bigness.

Basic filter script 2

#-1
FilterMount,0,0,0,0,0,0
#END

Same thing as filter 1, except it deletes your mount/yoshi/shoe if you have one.

Basic filter script 3

// Section 2
#2
FilterReservePowerup,0,0,0,0,180,0
#END

Let's change things up. The designator is now section 2, and the active time is 180. That means when you enter section 2, the player will constantly have his reserve powerup removed over the span of 3 seconds. Why you would actually do this is another story.

Bad Example

#-1
InfiniteFlying,0,0,0,0,0,0
#END

This is a bad example because the command exists in the level load section. This command in particular doesn't work unless it's continuously active, and in the level load section it won't actually do anything. It should be under #0

Multi character filter

#-1

// Filter Raocow to Demo
FilterPlayer,0,4,1,0,0,0

// Filter Princess to Demo
FilterPlayer,0,3,1,0,0,0

#END

This example removes the possibility of Raocow or Princess being in your level. FilterPlayer only has 3 relevent parameters. #2 is the character you want to filter out, and #3 is the character you want it to filter to. 1 is Demo, 2 is Iris, etc. #5 is the active time as usual, but since it's the load level section, any amount is fine.

Fairness (lunadll version 7+)

#0
ClearInputString,0,0,0,0,0,0
#END

What might that do? #0 is the always section, and ClearInputString... clears the keyboard input buffer. With an active time of 0, it never stops. The keyboard input buffer is filled with keyboard strokes and used to identify when a cheat code is entered. Clearing it constantly means no one will be able to enter a cheat code.

Full level

#-1

// Filter Demo to Sheath
FilterPlayer,0,1,5,0,0,0

#0
// Print the word "HI" at x:300 y:300 on the screen, with font 3
ShowText,0,300,300,3,0,Hi

#1

ShowText,0,200,400,3,0,ISN'T IT HARD TO SEE WITH ALL THIS TEXT IN THE WAY?

#2
// Filter a bunch of stuff and play sound effect ID 10 for no reason
SFX,10,0,0,0,0,0
FilterToSmall,0,0,0,0,1,0
FilterReservePowerup,0,0,0,0,1,0
FilterMount,0,0,0,0,1,0

#END

This is mainly here to illustrate the syntax of having a bunch of different things in one script (basically there aren't many rules in the way of syntax)

Modify boss life

#1
// Set the hit counter of NPCs of type 209 (mother brains) in section 1 to 9 hits
AT_SetHits,209,1,9,0,1,0
#END

NPCs in this game don't have health. They have a hit counter. So a mother brain with 9 hits has 1 hit left. A birdo with 1 hit has 2 hits left. A mother brain with -10 hits has 20 hits left. Only NPCs that can normally get hit more than once can have their hit counts manipulated. To find the NPC ID of an NPC, check the graphics folder or a list of NPC IDs. Currently there's no way to specify one single NPC among many that may be in your level. The command runs for every NPC of the type you specify. This goes for all NPC and block commands actually.

Trigger an event (lunadll version 7+)

#1
TriggerSMBXEvent,0,0,0,0,1,MyEvent
#END

This will start the event named "MyEvent" (assuming you put such an event in your level). Pretty simple.

Is this working?

#0
// Show script info on the screen
DebugPrint,0,0,0,0,0,0
#END

DebugPrint is a command that prints some basic info about how the scripts are running. It's useful for figuring out whether lunadll is even working at all. If you see it reporting that thousands of events are being spawned for no good reason, you should probably recheck your script.

Double the player's lives

#1

// Multiply the decimal amount at memory location 0x00B2C5AC by 2
MemAssign,0x00B2C5AC,2,3,0,1,f

#END

This is a memory manipulation command, and it's the most complicated thing you're going to find in here. You can skip this part and just not use direct memory manipulation commands and do just fine.

MemAssign performs an operation on a selected memory location in a global variable section. The game remembers all sorts of things in this space. Lives, coins, the state of switches, timers for everything, score, pointers to level names, directory names, state info like whether or not you're in the editor, in battle mode, how many players there are, how many npcs there are...

Unfortunately, there's no way to be sure which memory location contains what information, unless you poke around in a debugger or scan memory. 0x00B2C5AC just happens to be where the lives counter is always located.

If you know the address of something and the format, you can do basic operations on it with MemAssign. The 3rd parameter in 0x00B2C5AC,2,3 is the 3, which is the operation to perform, here being multiply. The 2nd is 2, meaning multiply by 2.

  • 0 = Assign
  • 1 = Add
  • 2 = Subtract
  • 3 = Multiply <<
  • 4 = Divide
  • 5 = XOR

MemAssign,0x00B2C5AC,2,3 means multiply the value at 0x00B2C5AC by 2.

MemAssign,0x00B2C5AC,2,2 would be subtract lives by 2.

MemAssign,0x00B2C5AC,2,1 would be add 2 extra lives.

MemAssign,0x00B2C5AC,2,0 would be set player's lives to 2.

MemAssign,0x00B2C5AC,2,3,0 - The 4th param is 0 since it's unused

MemAssign,0x00B2C5AC,2,3,0,1 - An active time of 1. More would keep multiplying the value by 2 and it would quickly max at 99.

MemAssign,0x00B2C5AC,2,3,0,1,f - Unfortunately memory is not packed evenly. You need to know the size of the data you're trying to manipulate. For lives, it just so happens that they're a decimal / floating point value (which is odd considering lives are one thing you'd always expect to be a whole number). So the last param specifies what kind of data is operated upon. f means floating point / decimal operation.

  • b - 1 byte
  • w - 2 byte (word)
  • dw- 4 byte (double word)
  • f - 4 byte decimal / floating point
  • df- 8 byte decimal / double precision floating point

1 byte memory is rarely used in this game. 2 byte words are used for many things. 4 byte double words aren't used that often and when they are it's usually something you don't WANT to manipulate this way. The decimal types are very often used for spatial positions of things on the game map.