Tutorial Intro to Generated Quests

Discussion in 'Starbound Modding' started by magewish4, Mar 14, 2017.

  1. magewish4

    magewish4 Pangalactic Porcupine

    Build a generated quest!

    This tutorial is meant to help you get started on adding a generated quest. I hope to write a series on this, but this is the bare bones. You should have a good grasp of lua and JSON.
    I use a custom npctype called 'questy'. I highly recommend using it for testing generated quests - it generates a new quest more often and will generate while there are a lot of other quests nearby.

    Part I. NPC Setup
    What type of npc will generate your quest? I will be attaching my quest to the questy npctype. If you are attaching this quest to an existing npctype, you need to add the questpool it to '/scriptConfig/questGenerator/pools' on the npctype. This defines the questpool the npc can pull quests from.
    Example Patch:
    Code:
    [ { 
        "op" : "add", 
        "path" : "/scriptConfig/questGenerator/pools/-", 
        "value" : "test" } ]
    


    The important bits for the quest generator from the Questy npctype:
    Code:
        "questGenerator" : {
          "pools" : ["test"],
          // Override enableParticipation in npctypes to enable the quest generator
          // and participation in other NPC's quests (if the behavior tree includes
          // the 'overrides' behavior module).
          "enableParticipation" : true,
         
          "queryCooldown" : 60,
          "maxPlanCost" : 5,
          // The probability that, in any given 30 second chunk of time where the
          // NPC is not offering a quest, this NPC will attempt to generate one.
          "chance" : 0.8,
          "maxBoostedChance" : 0.5,
    
          // Every 30 seconds, decide whether to generate a quest and if so, spend
          // the rest of the 30s window trying to generate it.
          "timeLimit" : 10,
    
          // Don't generate new quests if there are already more than <nearbyQuestLimit>
          // questmanager stagehands nearby, within a <nearbyQuestRange> tile range.
          "nearbyQuestLimit" : 10,
          "nearbyQuestRange" : 5,
          "questCountRange" : [1, 11]
        }
    


    Part II: QuestPool
    Now to create the questpool. Questpools define what quests an NPC offers. An NPC can have multiple questpools. Create this file at /quests/generated/questpools/test.config

    Code:
    {
      "ends" : [
        [8.0, "testQuest1"],
        [1.0, "testQuest2"]
      ],
    
      "quests" : {
        "testQuest1" : {
          "templateId" : "test.generated",
          "difficulty" : 0.1,
          "participants" : {
            "questGiver" : { "turnInQuest" : true }
          },
          "preconditions" : [
          ],
          "postconditions" : [
          ]
        },
        "testQuest2" : {
          "templateId" : "test.generated",
          "difficulty" : 0.1,
          "participants" : {
            "questGiver" : { "turnInQuest" : true }
          },
          "preconditions" : [
          ],
          "postconditions" : [
          ]
        }
      }
    }
    


    "ends": The quests listed here are final quests in a chain or don't form a chain. Make sure your quest is listed here. It also gives the chance of each quest being generated - similar to treasurepools.
    "quests": The list of quests.
    "testQuest1" + "testQuest2": These are the practice quest. The same template can be used multiple times in a questpool, with different parameters.
    "templateId": The id of the quest template. A json file.
    "difficulty": ??
    "participants": Entities that are participating in one quest cannot be used in another. In this example, the questgiver cannot be part of another quest. This also designates the questgiver as the turn in.
    "preconditions": Used to create quest chains.
    "postconditions": Used to create quest chains.

    Part III. The QuestTemplate
    Create this file at /quests/generated/templates/test.questtemplate:

    Code:
    {
      "id" : "test.generated",
      "moneyRange" : [10, 400],
      "canBeAbandoned" : true,
      "ephemeral" : true,
      "rewards" : [ ],
      "rewardParameters" : [ "rewards" ],
      "parameters" : {
        "questGiver" : {
          "type" : "entity"
        }
      },
    
      "updateDelta" : 10,
      "script" : "/quests/scripts/generated/test.lua",
      "scriptConfig" : {
        "portraits" : {
          "default" : "questGiver"
        },
       
        "generatedText" : {
          "title" : {
            "default" : [
              "Practice Quest",
              "A New Quest"
            ]
          },
         "goalText" : {
            "default" : [
              "^cyan;Do Stuff!"
            ],
            "floran" : [
              "^cyan;Do Ssstuff!"
            ]
          },
         "text" : {
            "default" : {
              "default" : [
                "You should do the thing!"
              ],
              "floran" : [
                "You ssshould do the thing!"
              ],
              "glitch" : [
                "Demanding. You should do the thing!"
              ]
            }
          },
         "completionText" : {
            "default" : {
              "default" : [
                "You survived! Knew you'd make it. Good job, champ!"
              ],
              "floran" : [
                "You ssurvived! Knew you'd make it. Good job, champ!"
              ],
              "glitch" : [
                "Glad. You survived! Knew you'd make it. Good job, champ!"
              ]
            }
          },
         "failureText" : {
            "default" : [
              "I guess I wasn't able to help <questGiver>. I'm sure someone else will help out."
            ]
          }
        },
        "requireTurnIn" : true,
        "objectives" : [
          {
            "id" : "action",
            "text" : "Do the thing"
          },
          {
            "id" : "return",
            "text" : "Return to <questGiver>"
          }
        ]
      }
    }
    

    "id" : The unique id for the quest
    "moneyRange": Finishing the quest will reward the player with an amount of pixels in this range
    "rewards" and "rewardParameters" : both accept treasurepools. not sure what the difference is.
    "parameters": This describes parts of the quest generated by the questpool. The only part used in this quest is the questgiver.
    "generatedText": The generated quest text has a title, goalText, text, completionText, and failureText. You can define different text for different races and you can give multiple versions that the engine will choose from - a lot like npc dialog!
    "objectives": These are displayed when the quest is active. The id is used by the lua file and the text is displayed. The "return" id is ignored by the engine when checking that all objectives are complete.

    Create a blank file at /quests/scripts/generated/test.lua

    If you start up starbound, the quest will be generated by your npc. But it won't do anything! The quest doesn't have anything to tell it that you have finished all the objectives.

    Part IV: The Quest Script

    The two objectives for this quest are "action" and "return"; the generator will ignore "return" objectives- it will just display when everything else is done. So we need to check if the "action" is done.

    Put the following code into test.lua


    Code:
    require("/quests/scripts/generated/common.lua")
    
    function onInit()
      message.setHandler("actionDone", actionDone)
    end
    
    function actionDone()
      objective("action"):complete()
    end
    
    function conditionsMet()
      return allObjectivesComplete()
    end
    


    All this script does is wait for the message "actionDone" and then it marks the "action" objective as complete. The common.lua file is a boilerplate that handles most of the work. conditionsMet returns true or false and tells the engine if the quest can be turned in. allObjectivesComplete returns true when each objective is marked complete - except for the return objective.
    common.lua creates the callbacks onInit, onUninit, onQuestOffer, onQuestDecline, onQuestStart, onQuestComplete, onQuestFail, onUpdate, and conditionsMet. Don't use init, update, or uninit.

    So what is our action? What is our quest actually asking us to do? Well, we can create an object that sends the actionDone message when interacted with. Or we could make a stagehand at the end of a dungeon that sends the message to nearby players. Or an item that sends the message if it is used in the outpost. Go crazy.

    For this example, I'm creating a script that completes the quest when I interact with an object and attaching it to the Traveller's Meter.

    Code:
    function init()
      object.setInteractive(true)
    end
    
    function onInteraction(args)
        world.sendEntityMessage(args.sourceId,"actionDone")
    end
    


    That's it! Let me know if you have any questions. If people are interested, I'll get another tutorial done.
     

    Attached Files:

Share This Page