Sequoia

Sequoia - MapleStory server emulator

Overview

Note that this may not be completely accurate, as this page is intended to describe the intended features of Sequoia, not what is currently implemented. Sequoia is still under heavy development and is not available publicly yet.

Sequoia is a custom MapleStory server emulator written from the ground up in pure Kotlin (excluding external libraries). To be precise, Sequoia is the API layer of the server. In order to facilitate custom server features, the API and implementation are kept separate, allowing for plugins to be written against the API without having to modify the core server code. For those familiar with Minecraft servers, Sequoia is basically intended to be the "Bukkit" of MapleStory servers.

Unlike nearly every public Java source out there, Sequoia is not Odin-based, and was written to get away from the instability and bugs that Odin-based sources typically have in common. As a result, Sequoia is rock solid and performant.

Since Sequoia is intended to support multiple versions of MapleStory, the API is separated into multiple "editions":


Technologies

Administration

Sequoia includes numerous features that make the lives of server administrators easier. Some of these include:

Development

Some notable internal features:

Random Stuff

Database and Storage

Sequoia's storage layer is abstracted in order to support multiple backends easily. The primary database implementation is PostgreSQL. MySQL support exists, but is outdated/unmaintained until further stabilization of the source. SQLite support is also planned for easy server setup, although it probably wouldn't be ideal for real world use.

By default, the server autosaves online players every five minutes. When data is saved, only changed data is actually saved to the database, which significantly reduces the amount of work the database has to do compared to doing a full save like other sources do. From live use with 1 to 30 players, the autosaver takes less than 500ms to run.

Scripting

The scripts provided with Sequoia are entirely Lua-based. However, Sequoia itself is not tied to any specific scripting language. As long as a loader for a language is implemented and registered, it can be used for scripts. Due to the fact that all of the scripts were newly written for Sequoia, code duplication is minimal compared to other sources. Common NPC logic is split off into separate files, which are then included as necessary. This significantly reduces the amount of code for certain types of NPCs, namely stylists, item crafters, party quest starters, and boss expedition starters.

In order to simplify the process of writing scripts for NPCs, portals, reactors, and party quests, there are a few core features provided in Sequoia:

Conversation API

The conversation API is simply a built in Lua library that makes it significantly easier to model the flow of NPC conversations. Most Odin-based sources have scripts with a monstrous amount of magic numbers for checking the current state of a conversation, such as this:

From HeavenMS

var status = -1;

function start() {
    cm.sendNext("Bowmen are blessed with dexterity and power, taking charge of long-distance attacks, providing support for those at the front line of the battle. Very adept at using landscape as part of the arsenal.");
}

function action(mode, type, selection) {
    status++;
    if (mode != 1){
        if(mode == 0)
           cm.sendNext("If you wish to experience what it's like to be a Bowman, come see me again.");
        cm.dispose();
        return;
    }
    if (status == 0) {
        cm.sendYesNo("Would you like to experience what it's like to be a Bowman?");
    } else if (status == 1){
	cm.lockUI();
        cm.warp(1020300, 0);
        cm.dispose();
  }
}

The same script looks like this with Sequoia's Lua coroutine-based conversation API:

require("core.coconversation")

function conversation.onStart(ctx, conv)
    conv:monologue(
        "Bowmen are blessed with dexterity and power, taking charge of long-distance attacks, "..
            "providing support for those at the front line of the battle. Very adept at using "..
            "landscape as part of the arsenal."
    )

    if conv:askYesNo("Would you like to experience what it's like to be a Bowman?") then
        ctx:warp(1020300)
    else
        conv:monologue("If you wish to experience what it's like to be a Bowman, come see me again.")
    end
end

Shindig API

The Shindig API is intended for basically any type of "event script", as other sources refer to it. This includes GM events, party quests, boss expeditions, etc. The primary motivation behind this system is to reduce redundant code, making it easier not only to write new shindigs, but also maintain them.

Most of a shindig's logic is actually implemented via a simple configuration file, although scripts can easily be added as necessary. These scripts will only ever be executed if the player is actually in an instance of the shindig.

In shindig instance Not in shindig instance

As an example, here's the shindig.conf file for Kerning PQ (note that it may be changed before a public release of Sequoia).

# Shindig configuration
# Since this shindig is purely script-based, this is where the Kotlin wrapper will grab custom
# configuration for the Shindig, along with the descriptor.

# REQUIRED
# Shindig descriptor
descriptor {
  name = "kerning"
  category = "party_quest"
  description = "Basic Kerning PQ implementation"
  version = "1.0.0"
}

# REQUIRED
# The map to warp players to upon being removed from the shindig.
# Hidden Street - 1st Accompaniment <Exit>
exit map = 103000890

# OPTIONAL
# Max duration of the shindig
timer = 30m

# TODO: Not sure I like this name
# How other game behavior should interact with an instance of this shindig
internal config {
  # Popup message configuration.
  popup {
    # TODO: I don't really like the name of "cash item"
    # Type can be:
    # - simple [default]: Send message in chat
    # - cash item: Send mesage using cash item (for PQs)
    #     - Requires "item ID" to be set
    type = "cash item"
    item ID = 5120017
  }
}

# REQUIRED
# How players participate in this shindig.
players {
  # REQUIRED
  # The grouping mode for players. Can be one of the following:
  # - party (max 6): Typically used for party quests
  # - expedition (max 30, not supported in v62): Typically used for bosses
  # - signup (no max): Includes whoever signs up via the NPC once the leader initiates sign up.
  #   Typically used as an alternative for bosses since v62 doesn't support expeditions.
  # - in map (no max): Includes whoever is in the starting map when the shindig starts.
  mode = "party"

  # REQUIRED
  # The available slots for this shindig.
  slots {
    # Hard minimum = absolute minimum number of players required for the shindig.
    # KPQ CAN be completed with 3 players, but it requires 4 to start.
    # Defaults to min.
    hard min = 3
    # OPTIONAL [default: 1]
    # Must be >= 1
    min = 4
    # REQUIRED
    # Must be <= the max for the grouping mode specified.
    max = 6
  }

  # OPTIONAL
  # The required level range for participants.
  level range {
    # OPTIONAL [default: 1]
    min = 21
    # OPTIONAL [default: 200]
    max = 200
  }
}

npc aliases {
  # Cloto
  9020001 = "stageAdvance"
  # Nella
  9020002 = "exit"
}

# OPTIONAL
# Shared stage definitions
global stage {
  end actions = [
    { type = "play effect", name = "quest/party/clear" }
    { type = "play sound", name = "Party1/Clear" }
    { type = "show map object", name = "gate" }
  ]

  # If true, overrides drops with this shindig's definitions, on a per-stage basis.
  override drops = true
}

# REQUIRED
# Stage definitions
stages = [
  # Stage 1
  {
    initial map = 103000800

    # Popup message to show when entering the initial map
    popup = "Please collect the same number of coupons as the number of Cloto's 1:1 questions!"

    # Condition(s) required for the stage to be completed.
    # These are essentially end actions, but they must all pass a check before actually being applied.
    end conditions = [
      {
        type = "take item"
        item = ${custom.items.pass}
        quantity = "P - 1"
        exact = true
        from leader = true
        take all = true
      }
    ]

    # Actions to perform on stage completion.
    end actions = [
      { type = "experience", amount = 100 }
    ]

    loot table {
      mob {
        # Ligator
        9300001 {
          mode = "all"
          entries = [
            { name = ${custom.items.coupon}, quantity = 1, weight = -1 }
          ]
        }
      }
    }
  }
  # Stage 2
  {
    initial map = 103000801

    popup = "Please find the 3 ropes that can open the door to the next stage and cling on to them!"

    end conditions = [
      # If no regions are defined, they are taken from the Map.wz
      { type = "map combo", slots = 3 }
    ]

    end actions = [
      { type = "experience", amount = 200 }
    ]
  }
  # Stage 3
  {
    initial map = 103000802

    popup = "Please find the 3 platforms that can open the door to the next stage!"

    end conditions = [
      { type = "map combo", slots = 3 }
    ]

    end actions = [
      { type = "experience", amount = 400 }
    ]
  }
  # Stage 4
  {
    initial map = 103000803

    popup = "Please find the 3 containers that can open the door to the next stage!"

    end conditions = [
      { type = "map combo", slots = 3 }
    ]

    end actions = [
      { type = "experience", amount = 800 }
    ]
  }
  # Stage 5 (boss)
  {
    initial map = 103000804

    popup = "Please defeat all the monsters and collect 10 passes!"

    end conditions = [
      { type = "take item", item = ${custom.items.pass}, quantity = 10 }
    ]

    end actions = [
      { type = "experience", amount = 1500 }
    ]

    loot table {
      # Items that aren't tagged as part of the shindig, and therefore can be taken out of it.
      persistent {
        mode = "whitelist"
        entries = [
          ${custom.items.squishy shoes}
        ]
      }

      overrides {
        mob {
          # Slime - use normal loot table and don't mark as shindig items
          210100 = "skip"
        }
      }

      mob {
        # Jr. Necki
        9300000 {
          mode = "all"
          entries = [
            { name = ${custom.items.pass}, quantity = 1, weight = -1 }
          ]
        }

        # Curse Eye
        9300002 {
          mode = "all"
          entries = [
            { name = ${custom.items.pass}, quantity = 1, weight = -1 }
          ]
        }

        # King Slime
        9300003 {
          mode = "all"
          entries = [
            {
              group = "special"
              mode = "one"
              weight = -1
              entries = [
                # Squishy Shoes rate = weight / 100 (25 + 75 from null entry)
                { name = ${custom.items.squishy shoes}, quantity = 1, weight = 25 }
                { name = "null", weight = 75 }
              ]
            }
            {
              group = "normal"
              mode = "all"
              weight = -1
              entries = [
                { name = ${custom.items.pass}, quantity = 1, weight = -1 }
              ]
            }
          ]
        }
      }
    }
  }
  # Stage 6 (bonus)
  {
    initial map = 103000805

    is extra stage = true

    override drops = false
  }
]

Migrating from Other Sources

Not implemented yet

In order to further encourage server owners to use Sequoia over their old sources, Sequoia includes an easy way to convert from other common sources such as:

Account Conversion

To make the account conversion process simple for users (and owners!), password hashes can automatically be converted to BCrypt (Sequoia's password hash) upon login.

Setup Process

  1. First, ensure that account.enable auto migration is set to true in your login.conf configuration file.
  2. Next, you need to add entries in the account_migration table under the global database for each account to migrate from. Each row must have the following: (This would normally be done automatically by the migration helper)
    • id: The ID of the account in the account table.
    • password: The old password hash.
    • type: The type of hash used for the old password hash (see below for supported formats).
  3. Assuming you did the previous two steps correctly; upon login, an account will check for a migration entry in the database. If it exists, it will verify the inputted password against it. If the password is correct, the migration entry will be deleted and it will then be converted to BCrypt and stored in the account table.

Supported Hash Types

These are the valid hash types for type:

Commands

Sequoia features a wide variety of commands to make life easier for both game masters and players. By default, commands can be executed with the prefixes !, @, and /. These prefixes can be changed via the configuration.

If a command is used without all of the required arguments, a help page will be shown in-game. Additionally, a command can be suffixed with "help" to show the description as well.

Numerous commands split their output into multiple "pages" in order to avoid spamming a player's chat box. In order to specify a page, simply run the command with a number at the end, such as !whatdrops "glove att 60%" 2.

Without further ado, here's the list of commands available in Sequoia. Since commands can be granted to players by assigning permissions, these categories aren't completely relevant, and are mostly just for organizational purposes.

General

Info

Party


Game Master

Management

TODO: Finalized commands below (polished functionality)

Punishment + Anticheat

Information

Messages

Stats

Appearance

Inventory

Spawning

Map

Teleportation

Utilities


Administrative

General

Debug

Configuration

Information about configuring Sequoia.

Configuration

Drops

Sequoia's drop tables are highly configurable and easy to manage. This is accomplished by supporting multiple drop table files which can be enabled or disabled even while the server is running.

Drop Entries

A single drop entry contains the following:

Related Commands

Load Priority

A typical Sequoia server looks as follows:

Drop table files are placed in a directory named drops within either the game server root or a world root, such as:

Upon loading the final drop table for a world, world files take priority over the game server files in order to allow for worlds to override entries easily. Drops are unique on a monster/reactor ID, item ID basis (for global drops, item ID is the only key). If a lower priority file contains a conflicting key, then it will not be used.

The load priority of world files is determined by alphabetically sorting based on the file name. Because of this, it is recommended that you name your files with a numerical prefix if you're likely to have lots of overridden entries (e.g. 00-base.conf). When searching for drop files, Sequoia looks for files suffixed with .conf. If you want to disable a file without removing it, you can simply suffix it with extra characters, such as base.conf.disabled.

This system allows for easy management of drop tables, especially for limited time drop tables such as for events. For example, for a Halloween event, you could place a file named halloween.conf in the drops directory and it will be used upon reloading the drop table. Once the event ends, the file can simply be removed and the entries will no longer be used.

Drop Table Format

Drop table files are HOCON configuration files with the following format:

# Optional section that defines some file-wide properties.
META {
  # Overridden default values for values loaded from this file. These will be used as the default values for missing entry properties.
  # Useful for things such as quest-specific files.
  defaults {
    quest = 123
  }

  # Conditions that must be met for this drop table to be used.
  conditions {
    # Whitelisted/blacklisted maps that the player must be in.
    # Example: This example enables this file for all maps outside of Maple Island.
    maps {
      # Mode:
      # - whitelist: Drop for maps in entries below.
      # - blacklist: Drop for maps NOT in entries below.
      mode = "blacklist"
      
      # Configuration-defined entries (from world.conf)
      areas = [
        "maple_island"
      ]
      
      # Ranges of map IDs.
      ranges = [
        0 99999999
      ]
      
      specific = [
        0
        100
        10000
      ]
    }
  }
}

mob {
  # Mob ID
  1000 {
    # Item ID
    2340000 {
      # The quantity to drop.
      # If a single number, will drop that amount.
      # If a range (i.e. "1 5"), will drop a random value between min (inclusive) and max (exclusive).
      quantity = 1
      # The percent chance for the item to drop.
      chance = 50.0
      # If set, the quest required for the player to be able to see the item.
      quest = 1234
      # If true, will drop the items as a single stack. Otherwise, will drop each item individually. Only has an effect for items that can actually stack (i.e. equipment items will always drop individually, such as Zakum Helmets).
      is stack = true
    }
  }
}

reactor {
  # Reactor ID
  1234 {
    # Item ID
    2340000 {
      ...
    }
  }
}

Special IDs

Mob/Reactor:

Item:


Default Drop Tables

Sequoia has a few pre-made drop tables included. These can be found in the resources/drops directory. Simply copy the contents of any directory into your server's drops folder to use that particular configuration.

Configuration

Configurable Scripts

Some NPCs and portals have simple configurable behavior, which can be configured via the script_config-npc.conf and script_config-portal.conf files.

To modify an NPC or portal's configuration options, simply make an entry in the file with the ID of the NPC/portal, like so:

# Kenta
2060005 {
  hog purchase price = 20000000
}

Configurable NPCs

This is a list of configurable NPCs available in Sequoia's provided NPC scripts.

Configurable Portals

This is a list of configurable portals available in Sequoia's provided portal scripts.

Configuration

MTS Button

The MTS button can be configured to operate in a few modes. The name of the configuration section for a mode corresponds to the name of the mode itself. For example, if the mode is set to map_warp, then configuration will be read from the map_warp section.

MTS configuration is defined in world.conf under the mts section.

# (world.conf)
mts {
  # Whether the MTS button is enabled. False will do nothing, regardless of what the rest of the configuration is.
  enabled = true

  # Which mode the MTS button should operate as.
  button mode = "map_warp"
  ...
}

mts (NYI)

The actual MTS. Not implemented yet, and likely won't be for a while, as it's unused by nearly every server out there.

map_warp

Warp the player to a map. Useful for an "FM button" or "hub button".

Configuration

# The map to warp the player to.
map id = 910000000

# Blacklisted maps where the button will not function from.
# This automatically includes the map ID defined above, which prevents getting stuck.
blacklist {
  # Areas to blacklist, corresponding to defined areas in the configuration.
  areas = ["maple_island", "free_market"]
  # Specific map IDs to blacklist.
  specific = [100000000, 100000001]
  # Inclusive ranges of map IDs to blacklist
  # A single entry should be a quoted string, separated by a space.
  ranges = ["200000000 299999999", "300000000 399999999"]
}

npc

Run an NPC script.

Configuration

# The ID of the NPC to show when running the script.
npc id = 9010000
# The name of the NPC script to run.
script = "special/mtsButton"
Configuration

Logging

Sequoia has a number of built in loggers, which can each be disabled if desired via a world's logging configuration section.

Loggers

Log Files

Log files are saved on a per-world basis within a world's directory (i.e. world_scania). Logs roll over to new files on a daily basis or after reaching 10mb in size. The currently active log will be found under logs/<name>/latest.log (e.g. logs/chat/latest.log) and archived in folders named <year>-<month> (e.g. logs/chat/2019-10).

Logs are currently not automatically deleted, so for spammy log files, it is recommended to clear them manually if you notice large disk usage. Alternatively, the logger can be disabled entirely if it's not needed.

Configuration

Server Setup

This page will cover how to set up a basic Sequoia instance. Sequoia is intended to be easy to setup, but highly flexible to configure for those who want detailed control over server features. With the default configuration, Sequoia should have no problem running out of the box, assuming you meet the minimum requirements.

Terminology

There are a few terms that are important to understand before reading through the rest of this page. These are used commonly throughout the various wiki pages for Seqouia, and are good to know as a result.

Configuration

While Sequoia has many configuration files, you don't actually have to modify most of them to get a server up and running. At the bare minimum, you just have to configure database connection settings for the most part (and maybe the path to your assets and the server's IP/ports).

This is how a basic Sequoia server directory would look:

Database

Sequoia currently supports three different storage backends:

Worlds

Worlds are defined in game.conf under the worlds section. The recommended convention for world directories is <server root>/world_<name>, although it shouldn't cause any issues if you use other names.

A world directory must contain a file called world.conf for it to be recognized as a world.

Requirements

This page describes the requirements for running Sequoia, along with other various internal information.

Server Requirements

Ports

Sequoia uses a number of ports for both public and private purposes. Private ports run on 127.0.0.1 in Sequoia's default configuration. If exposed on a public IP, then they should be firewalled for higher security.

Port Description Usage
8484 Default login server port public
8485 REST API public
8500 Cash shop public
8501 Base game server port public
6210 Login server IPC server private
6211 Game server IPC server private