Special NPCs - jjppof/goldensun_html5 GitHub Wiki

Special NPCs

In this tutorial, we'll be walking through how to add special types of NPC. There are some other types than regular NPCs, but some of them are just regular NPCs combined with specific types of Game Events, but since they're commonplace in the game, we'll be also talking about them.

Healer

In order to add a healer NPC, just setting the "npc_type" to "healer" is enough when adding it to a map. Example:

{
  "key_name": "healer_1",
  "x": 16,
  "y": 7,
  "movement_type": "idle",
  "npc_type": "healer",
  "avatar": "elder_healer",
  "base_collision_layer": 0,
  "back_interaction_message": "This a back interaction message!",
  "talk_range": 40
}

Inn

In order to add an inn NPC, you not only need to set "npc_type" to "inn" when adding it to a map but also register an inn in assets\dbs\inn_db.json db file. This file will hold information of all types of inns you'll have in your game, each inn has its own unique key name. Use this key name in the "inn_key" property when adding an NPC to the map.

Example of assets\dbs\inn_db.json:

[
  {
    "inn_id": "madra_inn",
    "cost": 6,
    "messages": {
      "welcome_message": "Welcome to our inn.",
      "price_message": "It's ${PRICE} coins for a room. Would you like to stay?",
      "cancel_message": "Please, come again.",
      "stay_message": "Please enjoy your stay.",
      "goodbye_message": "Have a nice trip!",
      "not_enough_coins": "You don't have enough coins."
    }
  }
]

Notice that you can use ${PRICE} placeholder in the inn messages in order to place the actual inn cost.

Example of NPC:

{
  "key_name": "village_1",
  "x": 69,
  "y": 11,
  "npc_type": "inn",
  "movement_type": "idle",
  "back_interaction_message": "This a back interaction message!",
  "avatar": "potion_shopkeeper",
  "inn_key": "madra_inn",
  "base_collision_layer": 0,
  "talk_range": 40
}

Shop

In order to add a shop NPC, you not only need to set "npc_type" to "shop" when adding it to a map but also register a shop in assets\dbs\shops_db.json db file. This file will hold information of all shops you'll have in your game, each shop has its own unique key name. Use this key name in the "shop_key" property when adding an NPC to the map.

Example of assets\dbs\shops_db.json:

[
  {
    "key_name": "madra_test_shop",
    "dialog_key": "female_medicine_shopkeep_00",
    "shop_type": "general_shop",
    "item_list": [
      {
        "key_name": "herb",
        "quantity": -1
      },
      {
        "key_name": "cookie",
        "quantity": 3
      },
      {
        "key_name": "cotton_shirt",
        "quantity": -1
      }
    ]
  }
]

Notice that before setting the shop db, you need to set the shopkeep dialogs db in assets\dbs\shopkeep_dialog_db.json. This file will contain the dialogs of the shopkeep, so you can just reference this set of dialogs in the "dialog_key" property. See details here.

Example of NPC:

{
  "key_name": "village_1",
  "x": 20,
  "y": 9,
  "avatar": "potion_shopkeeper",
  "npc_type": "shop",
  "movement_type": "idle",
  "back_interaction_message": "Please spend all your money here!",
  "shop_key": "madra_test_shop",
  "base_collision_layer": 1,
  "talk_range": 35
}

Djinn

Docile djinn

The below example is a configuration to get docile djinn, generally the djinn that are inside towns.

{
  "key_name": "mars_djinn",
  "label": "ember_djinn",
  "storage_keys": {
    "active": "ember_djinn"
  },
  "x": 7,
  "y": 15,
  "npc_type": "normal",
  "action": "idle",
  "movement_type": "idle",
  "base_collision_layer": 2,
  "events": [
    {
      "type": "djinn_get",
      "djinn_key": "ember",
      "finish_events": [
        {
          "type": "set_value",
          "event_value": {
            "type": "storage",
            "value": {
              "key_name": "ember_djinn",
              "value": false
            }
          }
        }
      ]
    }
  ]
}

This NPC is using two Game Events: "djinn_get" event and "set_value" event. Notice that you need to have a storage value associated with this djinn so it will get permanently deactivated after it gets caught. See about storage values here.

Aggressive djinn

This is the type of djinn that when you interact with it, it starts a fight before it gets caught. The setup for this NPC is very similar to the docile djinn, the only difference here is that you need to set "has_fight" property to true and also set the "enemy_party_key" property, both in "djinn_get" event. Example:

{
  "key_name": "venus_djinn",
  "storage_keys": {
    "active": "ground_djinn"
  },
  "x": 20,
  "y": 25,
  "npc_type": "normal",
  "action": "idle",
  "movement_type": "idle",
  "base_collision_layer": 0,
  "events": [
    {
      "type": "djinn_get",
      "djinn_key": "ground",
      "has_fight": true,
      "enemy_party_key": "venus_djinn_ground_party",
      "custom_battle_bg": "bg_cliffs",
      "finish_events": [
        {
          "type": "set_value",
          "event_value": {
            "type": "storage",
            "value": {
              "key_name": "ground_djinn",
              "value": false
            }
          }
        }
      ]
    }
  ]
}

World map djinn

Djinn that can be found on the world map are not set directly just like other NPCs. When creating an enemy party, there's a special option to handle this kind of djinn. In assets/dbs/enemies_parties_db.json, you can set a world map djinn like below:

[
  {
    "key_name": "jupiter_djinn_kite_party",
    "name": "Jupiter Djinn",
    "can_escape": true,
    "djinn": "kite",
    "active_storage_key": "kite_djinn",
    "weight": 2.0,
    "members": [
      {
        "key": "jupiter_djinn_kite",
        "min": 1,
        "max": 1
      }
    ]
  }
]

Set the "djinn" option with the djinni key name that will be received after the battle. Also sets a boolean storage value in "active_storage_key". After the battle victory, this variable will be set to false, then you won't find this djinn anymore.

After creating the enemy party for the djinni, add this party to an encounter zone on the map, see image below. Details about encounter zones, you can find here.

assets/special_npcs_tutorial/djinn_encounter_zone.png

Summon

The below example is a configuration to get a summon through a summon stone:

{
  "key_name": "summon",
  "storage_keys": {
    "active": "summon_megaera_stone"
  },
  "x": 101.5,
  "y": 6,
  "npc_type": "normal",
  "movement_type": "idle",
  "base_collision_layer": 0,
  "events": [
    {
      "type": "summon",
      "summon_key": "megaera",
      "finish_events": [
        {
          "type": "set_value",
          "event_value": {
            "type": "storage",
            "value": {
              "key_name": "summon_megaera_stone",
              "value": false
            }
          }
        }
      ]
    }
  ]
}

This NPC is using two Game Events: "summon" event and "set_value" event. Notice that you need to have a storage value associated with this summon stone so it will get permanently deactivated after you get it. See about storage values here.

Chest

Chests and objects like jars, boxes, etc are NPCs in the engine. They use the "chest" Game Event to work along with storage variables manipulation.

Standard chest

After registering the chest NPC with its sprites, you can add a chest to the map like the below example:

{
  "key_name": "chest",
  "storage_keys": {
    "animation": "pound_cube_chest"
  },
  "x": 28,
  "y": 10,
  "npc_type": "normal",
  "movement_type": "idle",
  "base_collision_layer": 2,
  "events": [
    {
      "type": "branch",
      "comparator_pairs": [{
        "condition": "=",
        "left_comparator_value": {
          "type": "storage",
          "value": {
            "key_name": "pound_cube_chest"
          }
        },
        "right_comparator_value": {
          "type": "value",
          "value": "closed"
        }
      }],
      "events": [
        {
          "type": "chest",
          "item": "pound_cube",
          "finish_events": [
            {
              "type": "set_value",
              "event_value": {
                "type": "storage",
                "value": {
                  "key_name": "pound_cube_chest",
                  "value": "empty"
                }
              }
            }
          ]
        }
      ],
      "else_events": [
        {
          "type": "dialog",
          "dialog_info": {
            "text": "${HERO} checked the chest...${BREAK}but the chest was empty."
          }
        }
      ]
    }
  ]
}

Notice that "pound_cube_chest" storage variable is the one that controls the state of this chest. Without it, every time you return to the map where this chest is, you'll find it open. So first we check with "branch" Game Event whether "pound_cube_chest" variable is equal to "closed". If yes, it will fire the "chest" Game Event, then set "pound_cube_chest" variable to "empty". So next time the hero interacts with this chest, "pound_cube_chest" will be "empty", then the "branch" event else condition will be fired which is just a "dialog" Game Event telling that this chest is empty.

Notice that this NPC has the "storage_keys" property set. Inside it, the "animation" property is set to "pound_cube_chest", which means that the "animation" property of this NPC will also be controlled by the "pound_cube_chest" variable value. See more info about it here.

Jars, boxes, etc

This category of NPCs is very similar to the chest ones. See below:

{
  "key_name": "foo",
  "x": 25,
  "y": 15,
  "base_collision_layer": 1,
  "storage_keys": {
    "affected_by_reveal": "herb_box_madra"
  },
  "npc_type": "sprite",
  "movement_type": "idle",
  "animation": "hidden_item",
  "sprite_misc_db_key": "psynergy_particle",
  "action": "psynergy_particle",
  "allow_interaction_when_inactive": "true",
  "active": false,
  "events": [
    {
      "type": "branch",
      "comparator_pairs": [{
        "condition": "=",
        "left_comparator_value": {
          "type": "storage",
          "value": {
            "key_name": "herb_box_madra"
          }
        },
        "right_comparator_value": {
          "type": "value",
          "value": true
        }
      }],
      "events": [
        {
          "type": "chest",
          "custom_init_text": "${HERO} checked the wooden box...",
          "item": "herb",
          "no_chest": true,
          "hide_on_finish": true,
          "finish_events": [
            {
              "type": "set_value",
              "check_npc_storage_values": true,
              "event_value": {
                "type": "storage",
                "value": {
                  "key_name": "herb_box_madra",
                  "value": false
                }
              }
            }
          ]
        }
      ],
      "else_events": [
        {
          "type": "dialog",
          "dialog_info": {
            "text": "${HERO} checked the wooden box...${BREAK}but didn't find anything."
          }
        }
      ]
    }
  ]
}

The above example is for an herb inside a wooden box. The behavior is very similar to the chest, but it has a little bit more details. First of all, the NPC for this wooden box was registered like this:

{
  "key_name": "foo",
  "interaction_pattern": "simple_interaction",
  "no_shadow": true,
  "ignore_physics": true
}

It's pretty much an empty NPC, no collision structures, no spritesheet. That's because the box image belongs to the map, and its collision structure also belongs to the map, so we just need something empty that we can interact with.

Notice that we can force this NPC to assume a different sritesheet with the "sprite_misc_db_key" property. For this example, it's that little shiny particle that appears over the boxes, jars, etc that has an item inside of it when you cast Reveal. When an NPC has the "affected_by_reveal" property set, it will have its active state toggled. "affected_by_reveal" is controlled by "herb_box_madra" variable in this example, so if this variable is set to true, the particle over the box will be shown because this NPC has its initial active state set to false as seen in "active" property.

For jars, pots, boxes, etc that don't have anything inside, just setting the below NPC is enough:

{
  "key_name": "foo",
  "x": 25,
  "y": 17,
  "base_collision_layer": 1,
  "npc_type": "sprite",
  "message": "${HERO} checked the jar...${BREAK}but didn't find anything."
}

Psynergy stone

In order to place a psynergy stone you just need to create an NPC like below:

{
  "key_name": "psynergy_stone",
  "storage_keys": {
    "active": "psy_stone_madra_active"
  },
  "x": 33,
  "y": 15,
  "npc_type": "normal",
  "movement_type": "idle",
  "animation": "normal",
  "base_collision_layer": 1,
  "events": [
    {
      "type": "psynergy_stone",
      "finish_events": [
        {
          "type": "set_value",
          "event_value": {
            "type": "storage",
            "value": {
              "key_name": "psy_stone_madra_active",
              "value": false
            }
          }
        }
      ]
    }
  ]
}

It's an NPC controlled by the "psy_stone_madra_active" storage variable and the event of grabbing the psynergy stone is handled by the "psynergy_stone" Game Event.