                 /----------------------------------------\
                <            Adding a new quest            >
                 \----------------------------------------/

    === Introduction ===

    Adding a quest involves a bit more work, and there is, in some ways, rather
    more potential for things to go wrong! Bit it's a great way of showing just
    WHAT can be done with lua scripting. It proves just how much a lua patch can
    change the overall feel of the game. And it will give you a much better idea of
    how lua interfaces with the game source. You should have read the
    scripting introduction, racial power tutorial
    and adding new skills tutorial before going much
    further. All of the above files contain some fairly fundamental information
    which you will find necessary for full understanding of this file.

    The script we're looking at is going to create a quest entrance in the middle
    of Bree. Entering the quest you see a little girl who has had her necklace
    stolen. Your job is to travel down a corridor, killing some monsters on the
    way, pick up the amulet and return it to the girl. Once done, she'll reveal the
    stairs back to Bree, and give you a (randomly generated) ring. If you feel the
    monsters are too hard, the only thing to do is talk to the little girl who will
    reveal the stairs again, failing the quest for you, and also block off the
    entrance to the amulet so that you can't cheat and make off with the amulet!

    === Getting started ===

    Open amulet.lua (you have downloaded the example scripts from
    http://www.moppy.co.uk/angband.htm, haven't you?). The first thing you
    should see is that yet again we're calling a function that takes it's arguments
    from a table, making it easy to read what's going on in the script.

    This time our function is add_quest and we have the following keys and values:

    ["global"] =    "AMULET_QUEST",
    ["name"] =      "Hannahs lost amulet",
    ["desc"] =      {
                      "Retrieve an amulet for Hannah Cooke. It's guarded!"
                   },
    ["level"] =     5,
    ["hooks"] =     {

    "global" =       is a constant that we set when we refer to this quest in
    various places...
    "name" =         Obviously a long name for the quest. This will appear in the
    quest screen (Ctrl-Q) and we may use in some map files too.
    "desc" =         This is a long description for the quest. Again this is what
    will appear in the quest screen, and each line should be not more than 80
    characters.
    "level" =        This is a rough indicator of how hard the quest is, and again,
    appears in the quest screen
    "hooks" =        This is the real 'meat' of the quest. Like the "spell_list" key
    in the add_magic function, this is another sub-table.

    To understand fully the structure of the "hooks" key it's worth taking a bit of
    a detour at this point and discussing  the scripting interface works  in
    general.

    === How the scripts work (ish) ===

    Essentially there's a list of events that happen in the game. As each of these
    events happen they run a check to see if there's any functions that they need
    to run at that event. When we ran the add_mkey part of adding a new skill
    power, we essentially said 'when the 'm' key is pressed in the game, perform
    the execute_magic(constructor_powers) function'. Likewise we did a similar
    thing with adding the racial power, only we hooked onto the pressing of the
    'U' key.

    All of this was partly hidden because of the way that the add_magic and
    add_power functions work. But here in the add_quest function it's a bit more
    specific. We are going to specify what events we're going to hook onto, and
    what functions we want to trigger at that event.

    A full list of hooks can be found in the source-file util.pkg.

    === The hooks ===

    [HOOK_BIRTH_OBJECTS] = function()
      quest(AMULET_QUEST).status = QUEST_STATUS_TAKEN
    end,

    So here we are with our first hook. We've declared that we're adding it to the
    birth of your character. That is, the function will be called when you create
    your character. And what we're doing here is automatically declaring the quest
    as being taken, like the Dol Guldur quest is. Each quest has 7 different
    statuses:

    QUEST_STATUS_IGNORED         -1          This is unused, but the quest is
    ignored (will not be taken and has not been taken).
    QUEST_STATUS_UNTAKEN         0           The quest has not been accepted yet
    QUEST_STATUS_TAKEN           1           You have accepted the quest
    QUEST_STATUS_COMPLETED       2           You have completed the quest
    successfully but not been rewarded for it
    QUEST_STATUS_REWARDED        3           You've completed and rewarded the quest
    QUEST_STATUS_FAILED          4           You've failed the quest
    QUEST_STATUS_FINISHED        5           The quest is completely finished
    successfully.
    QUEST_STATUS_FAILED_DONE     6           The quest is completely finished
    unsuccessfully.

    You see that we've used the constant we defined in the "global" section is
    passed as an argument to quest.status.

    Next hook then:

    [HOOK_GEN_QUEST] = function()
      if (player.inside_quest ~= AMULET_QUEST) then
              return FALSE
      else
              load_map("amulet.map", 2, 2)
              return TRUE
      end
    end,

    Ok we're hooking onto the generation of the quest here. This is specifically
    triggered in this instance by the going down the quest entrance stairs in Bree.
    Once you've gone down the stairs, you are technically inside the quest, which
    means we can say if the person is not inside the amulet quest, then ignore this
    function, otherwise load the file 'amulet.map' at co-ordinates x=2 y=2. You'll
    find the amulet.map file in the edit directory, make sure you check it out. The
    syntax for map files is fairly simple, though I might get round to writing a
    tutorial on them some day! In the mean time holler for me at the usual email
    address if you're unsure.

    [HOOK_FEELING] = function()
      if (player.inside_quest ~= AMULET_QUEST) then
              return FALSE
      else
              cmsg_print(TERM_L_BLUE, "Hannah speaks to you:")
              cmsg_print(TERM_YELLOW, "'Some nasty monsters stole my
                                                      favourite necklace.'")
              cmsg_print(TERM_YELLOW, "'It's hidden at the back of that
                                              corridor! Please fetch it for me'")
              return TRUE
      end
    end,

    We're moving into some rather more obvious territory here, and getting into the
    meat of the quest. The HOOK_FEELING is triggered at the point when the level
    feeling appears. It's important that this is run only if the player is inside
    the amulet quest, as otherwise it will trigger EVERY time a level feeling
    occurs, when you go down a level in the barrow-downs, whenever! Returning TRUE
    will replace the level feeling with what's above, returning FALSE will still
    perform the function but will amend the normal level feeling - so here if we'd
    returned false we'd still get out custom messages, but they'd follow with
    'looks like a typical quest level'. Of course returning false may cause you
    other problems (see end of this file!) depending on what else you have in your
    function.

    [HOOK_GIVE] = function(m_idx, item)

      m_ptr = monster(m_idx)
      o_ptr = get_object(item)

      if (m_ptr.r_idx == test_monster_name("Hannah Cooke, a little girl"))
                      and (o_ptr.tval == TV_AMULET) and (o_ptr.sval == 2) then

              cmsg_print(TERM_YELLOW, "'Thank-you!'")

              inven_item_increase(item, -1)
              inven_item_optimize(item)

              quest(AMULET_QUEST).status = QUEST_STATUS_COMPLETED

              cave_set_feat(7, 6, 6)

              cmsg_print(TERM_YELLOW, "'Here, take this pretty ring I found
                                                      as a token of gratitude!'")
              random_type = randint(57)
              reward = create_object(TV_RING, random_type)
              drop_near(reward, -1, py, px)
              quest(AMULET_QUEST).status = QUEST_STATUS_REWARDED
              return TRUE
      else
              return FALSE
      end
    end,

    this is a fairly long function, but don't be intimidated. It's not really
    difficult to understand. As you can see we're hooking into the giving of an
    object to a monster( the 'y' key). Because of this, the function takes two
    arguments - m_idx (the monster that you're giving to) and item (the item that
    you're giving).

    We then make it possible to work with the monster and item variables by
    referencing them to two functions which identify them from the edit files:
    monster() and get_object(). This enables us to now say, 'if the name of the
    monster is "Hannah Cooke, a little girl" and the type of item is an amulet and
    that amulet is an amulet of adornment, then carry out the following commands'.

    We then say call the function inven_item_increase() which places an object in
    the inventory. It takes two arguments, the first being what object to put in
    the inventory and the second being how many of that type of objects to put in
    the inventory. You can see that by placing -1 as the second argument it fairly
    obviously subtracts that item from the inventory. The inven_item_optimize()
    function checks that there are no empty inventory slots, and if there are,
    erases them.

    The quest is then completed, and the stairs are revealed using the
    cave_set_feat() function. This function takes three arguments, the first is the
    x co-ordinate of the cave square you wish to change (counted from top left) the
    second is the y co-ordinate, and the third is the index number of the feature
    you wish the square to become as defined in f_info.txt.

    We then set about rewarding the player. As you can see we call create_object()
    which takes two variables: the first is the type of object (these are all
    listed in object.pkg) and the second is the sub-type of that object. I searched
    k_info.txt to see how many different types of ring there were (57) and used a
    randomly selected number with a maximum value of 57 as that specific sub-type.

    We then drop the object (although it's been created, it has only been created
    in the game's memory, it's nowhere that the player can interact with it until
    we drop it). The drop_near() function takes 3 variables, the first being the
    object that you wish to drop, the second being the chance that it disappears
    (like an arrow, or mimicked creature) on drop. If you set it to -1, it wont
    ever disappear. the last two are the co-ordinates at which the object will be
    dropped. py and px are the global variables defined by where the player is
    standing, so in this case it will drop under the player. You could do
    inven_item_increase(reward, 1) if you wanted, but I wanted to show a variety of
    ways of handling objects.

    OK lets take a look at the next hook:

    [HOOK_CHAT] = function(m_idx)
      m_ptr = monster(m_idx)
      if (m_ptr.r_idx == test_monster_name("Hannah Cooke, a little girl")) then
              if (quest(AMULET_QUEST).status == QUEST_STATUS_REWARDED) then
                      cmsg_print(TERM_YELLOW, "'Bye!'")
              else
                      cmsg_print(TERM_YELLOW, "'Are the monsters too tough?
                                                      Do you want to leave?'")
                      if (get_check("Really leave and fail the quest?") ==
                                                                      FALSE)
                              then
                                      cmsg_print(TERM_YELLOW, "'Go and get my
                                                              amulet then!'")
                              else
                                      cmsg_print(TERM_YELLOW, "'Awww. Never
                                      mind. It was only a bit of rabbits foot'")
                                      quest(AMULET_QUEST).status =
                                                              QUEST_STATUS_FAILED
                                      cave_set_feat(7, 6, 6)
                                      cave_set_feat(12, 5, 60)
                      end
              end
              return TRUE
      end
      return FALSE
    end,

    This only looks complicated because of the nested 'if' statements. It's easy to
    lose your way when doing this kind of thing, always make sure you close all the
    statements and put the returns in the right place. HOOK_CHAT functions have one
    argument - the monster you are chatting to. As you can see, we perform a check
    to make sure it's the right monster and then away we go.... If the player wants
    to  leave the quest without completion they talk to Hannah, who gives them a
    chance to change their mind! If the player asks to leave the entrance to the
    corridor is blocked off (the second cave_set_feat()) so that the user can't
    then go and get the amulet. Gumband or Zangband players may at this point think
    they've lost out on the rabbits foot of burglary! (they haven't though as it
    doesn't exist in ToME).

    [HOOK_CHAR_DUMP] = function()
      if (quest(AMULET_QUEST).status == QUEST_STATUS_FAILED) then
              print_hook("\n You chickened out of rescuing a necklace and
                                                      made a little girl sad. ")
      elseif (quest(AMULET_QUEST).status == QUEST_STATUS_COMPLETED) or
              (quest(AMULET_QUEST).status == QUEST_STATUS_REWARDED) or
              (quest(AMULET_QUEST).status == QUEST_STATUS_FINISHED) then
                      print_hook("\n You rescued little Hannah Cooke's necklace from
                                                      the nasty monsters ")
      end
      return FALSE
    end,

    This quite simply and obviously prints an appropriate line in the character
    dump based on the status of the quest. the n bit ensures the text goes on a
    new line, so make sure you include it! Also you should return FALSE as
    returning TRUE will stop executing all the other character dump lines (and you
    may get other quests not having their lines printed.

    === A word about returning TRUE and FALSE ===

    As I mentioned above, you need to be careful what you return when dealing with
    HOOKS as you can mess up the game a bit. Bear in mind that if you add a
    function to HOOK_GEN_QUEST, every time a quest is generated, that function will
    run. If you return TRUE, then no further functions attached to that hook will
    run. If you return FALSE, it continues processing functions on that hook.

    That is pretty much it. Do take a look at the other included scripts that I
    haven't gone into any detail about in the files, as you'll pick up some useful
    techniques there too. Especially worthy of note is the hina.lua file which uses
    hooks outside of the quest structure and also global variables and variables in
    a table. If you have any questions, let me know at the email addy below.

    Back to the lua help index .

                                 This file by fearoffours (fearoffours@moppy.co.uk)