Tutorial:Basic Zone Scripts: Difference between revisions

From PMDOWiki
Imbion (talk | contribs)
Imbion (talk | contribs)
This is very similar to the dungeon scripts guide, I feel like I’m going insane lmao
 
Line 1: Line 1:
PMDO has some base functions for its zones that have to be user implemented. In order to make a functioning dungeon, these functions must be filled out. From the root folder of a mod, you can find them at <code>Data/Script/[modNamespace]/zone/[zoneName]</code>. All of these functions are found in the <code>init.lua</code> file.
This article is a guide on how to initalize the basic scripts for a zone (aka dungeon) script. Most of the complexity lies in the <code>ExitSegment</code> function.
== Init ==
 
This function is run every time the zone is initialized, such as when we enter it, exit a quicksave, or load a replay. All base dungeons in the game display a debug message with some information about them:
== About the File ==
When creating a zone, PMDO automatically creates a corresponding script file for the zone. These files can be found by going into the mod's root folder, and then following the path <code>Data/Script/[namespace]/zone</code>. Then, to find the script file for a dungeon, select the folder with the corresponding name. Inside this will be a file called <code>init.lua</code>, where all of the data for the zone is contained.
 
There are four functions present in this file:
* <code>Init</code>, which runs always when initializing the zone, aka whenever it is loaded
* <code>Rescued</code>, which runs when being rescued by another team
* <code>EnterSegment</code>, which runs when entering a segment
* <code>ExitSegment</code>, which runs when exiting a segment
 
All the functions are indexed into a table named after the zone's internal name. For example, with a zone named <code>cave</code>, the init function is defined as <code>cave.Init(zone)</code>.
 
== Initializing ==
This gives the game instructions on what to do when initalizing the dungeon.
 
Most zones do not require any unique steps to initialize them, unless custom scripting is implemented. Instead some debug lines are executed:
<pre>
<pre>
function [zone].Init(zone)
DEBUG.EnableDbgCoro() --Enable debugging this coroutine
  DEBUG.EnableDbgCoro() --Enable debugging this coroutine
PrintInfo("=>> Init_[zoneID]")
  PrintInfo("=>> Init_[zone]")
end
</pre>
</pre>
These are just to make debugging easier in case something does go wrong with the scripting.


== Rescued ==
== Rescues ==
To enable rescues, we need to fill out the <code>Rescued</code> function. This is rather simple, as most base dungeons just have one line of code:
This gives the game instructions on what to do when the player has been rescued.


function [zone].Rescued(zone, name, mail)
Rescues are simply handled with a single line in <code>Rescued</code>:
  COMMON.Rescued(zone, name, mail)
<pre>
end
COMMON.Rescued(zone, name, mail)
</pre>


This just runs the common function for rescues found in the base game.
This enables the game to execute the function that completes the rescue.
== EnterSegment ==
 
This function runs every time a segment is entered. Generally we just want to begin the dungeon in the standard way. In that case, the function looks like as follows:
== Entering a Segment ==
This gives the game instructions for what to do when entering a segment.
 
<code>EnterSegment</code> typically has no extra scripting, other than cases where the player is rescuing another team:


<pre>
<pre>
function [zone].EnterSegment(zone, rescuing, segmentID, mapID)
   if rescuing ~= true then
   if rescuing ~= true then
     COMMON.BeginDungeon(zone.ID, segmentID, mapID)
     COMMON.BeginDungeon(zone.ID, segmentID, mapID)
  end
end
</pre>
</pre>
== ExitSegment==
This function runs when exiting a segment. This is what most zones must fill out in order to function, as otherwise the game will simply stop execution rather than doing anything. We need to tell the game what to do upon exiting a segment through lua scripting.


This function receives the following arguments:
== Exiting Dungeon Segments ==
This gives the game instructions for what to do when exiting a segment. This is the primary function that requires scripting, because otherwise the game will simply stop execution and softlock if not given instructions.
 
This function receives the following information when exiting a segment, storing them in variables:


* <code>zone</code>: The zone we currently exited.
* <code>zone</code>: The zone we currently exited.
Line 36: Line 51:
* <code>rescue</code>: Tells us whenever we're rescuing someone or not. This is true if we are rescuing, and false if we are not.
* <code>rescue</code>: Tells us whenever we're rescuing someone or not. This is true if we are rescuing, and false if we are not.
* <code>segmentID</code>: A number that tells us what segment of the dungeon we're exiting from, based on the ordering in the mod editor. It starts at 0 and counts up by one per next segment.  
* <code>segmentID</code>: A number that tells us what segment of the dungeon we're exiting from, based on the ordering in the mod editor. It starts at 0 and counts up by one per next segment.  
* <code>mapID</code>: Says the map ID that we just exited from. Can be used for things like secret exits.
* <code>mapID</code>: Says the map ID that we just exited from. Isn't referenced most of the time.  


In order to tell the game what to do, we need to check if these variables have a certain value.
In order to tell the game what to do, we need to check if these variables have a certain value.
=== An Example ===
=== An Example ===


Line 84: Line 100:
     end
     end
</pre>
</pre>
=== Blueprint ===
=== Blueprint ===
Putting it together, we get this psuedocode blueprint of what occurs while running <code>ExitSegment</code>:
Putting it together, we get this psuedocode blueprint of what occurs while running <code>ExitSegment</code>:


<pre>
<pre>
function [zone].ExitSegment(zone, result, rescue, segmentID, mapID)
  DEBUG.EnableDbgCoro() --Enable debugging this coroutine
  PrintInfo("=>> ExitSegment result "..tostring(result).." segment "..tostring(segmentID))
   --first check for rescue flag; if we're in rescue mode then take a different path
   --first check for rescue flag; if we're in rescue mode then take a different path
   local exited = COMMON.ExitDungeonMissionCheck(result, rescue, zone.ID, segmentID) --check if we cleared while doing a rescue
   local exited = COMMON.ExitDungeonMissionCheck(result, rescue, zone.ID, segmentID) --check if we cleared while doing a rescue
Line 112: Line 125:
         end
         end
   end
   end
end
</pre>
</pre>
[[Category:Modding Data]]
[[Category:Dungeon Generation]][[Category:Scripting]]
[[Category:Tutorial]]

Latest revision as of 16:42, 20 October 2025

This article is a guide on how to initalize the basic scripts for a zone (aka dungeon) script. Most of the complexity lies in the ExitSegment function.

About the File

When creating a zone, PMDO automatically creates a corresponding script file for the zone. These files can be found by going into the mod's root folder, and then following the path Data/Script/[namespace]/zone. Then, to find the script file for a dungeon, select the folder with the corresponding name. Inside this will be a file called init.lua, where all of the data for the zone is contained.

There are four functions present in this file:

  • Init, which runs always when initializing the zone, aka whenever it is loaded
  • Rescued, which runs when being rescued by another team
  • EnterSegment, which runs when entering a segment
  • ExitSegment, which runs when exiting a segment

All the functions are indexed into a table named after the zone's internal name. For example, with a zone named cave, the init function is defined as cave.Init(zone).

Initializing

This gives the game instructions on what to do when initalizing the dungeon.

Most zones do not require any unique steps to initialize them, unless custom scripting is implemented. Instead some debug lines are executed:

DEBUG.EnableDbgCoro() --Enable debugging this coroutine
PrintInfo("=>> Init_[zoneID]")

These are just to make debugging easier in case something does go wrong with the scripting.

Rescues

This gives the game instructions on what to do when the player has been rescued.

Rescues are simply handled with a single line in Rescued:

COMMON.Rescued(zone, name, mail)

This enables the game to execute the function that completes the rescue.

Entering a Segment

This gives the game instructions for what to do when entering a segment.

EnterSegment typically has no extra scripting, other than cases where the player is rescuing another team:

  if rescuing ~= true then
    COMMON.BeginDungeon(zone.ID, segmentID, mapID)

Exiting Dungeon Segments

This gives the game instructions for what to do when exiting a segment. This is the primary function that requires scripting, because otherwise the game will simply stop execution and softlock if not given instructions.

This function receives the following information when exiting a segment, storing them in variables:

  • zone: The zone we currently exited.
  • result: The outcome of the dungeon exploration - Most relevant, this lets us check whenever the segment was cleared or if the player was defeated.
  • rescue: Tells us whenever we're rescuing someone or not. This is true if we are rescuing, and false if we are not.
  • segmentID: A number that tells us what segment of the dungeon we're exiting from, based on the ordering in the mod editor. It starts at 0 and counts up by one per next segment.
  • mapID: Says the map ID that we just exited from. Isn't referenced most of the time.

In order to tell the game what to do, we need to check if these variables have a certain value.

An Example

Let's take a look at how the ExitSegment function of Faded Trail works. We use a if...elseif...else block in order to branch through our different choices.

First, we want to check if we are in rescue mode, and succeeded on the rescue:

local exited = COMMON.ExitDungeonMissionCheck(result, rescue, zone.ID, segmentID)
  if exited == true then
    --do nothing

You only require this part of the block if your dungeon should support rescues.

If exited is false, then we continue along the statement. This branch is taken if the dungeon was not cleared.:

  elseif result ~= RogueEssence.Data.GameProgress.ResultType.Cleared then
    COMMON.EndDungeonDay(result, SV.checkpoint.Zone, SV.checkpoint.Segment, SV.checkpoint.Map, SV.checkpoint.Entry)

The dungeon day is ended, and the player is sent back to their last checkpoint with punishments applied.

If the above is false, then we enter into a branch that holds the results if we cleared the dungeon. So, all other results under this one else assume the player has cleared the dungeon.

Faded Trail is a dungeon with an alternative dungeon segment that the player can take. We would like to do something different if the player clears the alternative segment. The default segment, Faded Trail, is segment 0, while the alternative segment, Hidden Trail, is segment 1.

For segment 0, we just want to send the player to the forest camp:

    if segmentID == 0 then
      COMMON.EndDungeonDay(result, 'guildmaster_island', -1, 4, 0)

For segment 1, we want the player to go back to the island entrance and unlock a new dungeon:

    elseif segmentID == 1 then
      COMMON.UnlockWithFanfare('faultline_ridge', true)
      COMMON.EndDungeonDay(result, 'guildmaster_island', -1, 1, 3)

Finally, we make our last else be a fallback function that sends the player to their last checkpoint:

    else
      PrintInfo("No exit procedure found!")
	  COMMON.EndDungeonDay(result, SV.checkpoint.Zone, SV.checkpoint.Segment, SV.checkpoint.Map, SV.checkpoint.Entry)
    end

Blueprint

Putting it together, we get this psuedocode blueprint of what occurs while running ExitSegment:

  --first check for rescue flag; if we're in rescue mode then take a different path
  local exited = COMMON.ExitDungeonMissionCheck(result, rescue, zone.ID, segmentID) --check if we cleared while doing a rescue
  if exited == true then
        --do nothing; this is a dummy branch to stop us from going through the other branches
   elseif result ~= RogueEssence.Data.GameProgress.ResultType.Cleared then -- resolves true if the dungeon was not cleared
        --end the dungeon day by sending the player back to their last checkpoint, since they didn't clear the dungeon
         COMMON.EndDungeonDay(result, SV.checkpoint.Zone, SV.checkpoint.Segment, SV.checkpoint.Map, SV.checkpoint.Entry)
   else -- we didn't resolve true for being in rescue or losing the dungeon, so we must have cleared the dungeon
         -- now we will check what segment we took, so we can make the game do what we want to depending on the segment
         if segmentID == 0 then
            -- do things for exiting on segment 0
         elseif segmentID == 1 then
            -- do things for exiting on segment 1
         elseif .... -- continue checking likewise for all the segments we have
         else -- this is a fallback branch, in case we went through all of the branches and hit nothing (which should not happen!)
                 PrintInfo("No exit procedure found!")
                 -- just send the player to their last checkpoint
	             COMMON.EndDungeonDay(result, SV.checkpoint.Zone, SV.checkpoint.Segment, SV.checkpoint.Map, SV.checkpoint.Entry)
         end
   end