Jay_H's quest writing tutorial

For all talk about quest development - creation, testing, and quest system.
Post Reply
User avatar
Jay_H
Posts: 4059
Joined: Tue Aug 25, 2015 1:54 am
Contact:

Jay_H's quest writing tutorial

Post by Jay_H »

Part One: Reading the Quest File
Part Two: Writing a Simple Quest
Part Three: create foe; "pc at" condition; when x and y; journal
Part Four: Dungeons, hours of the day, multiple "killed" conditions
Part Five: Placing and Delivering Items
Part Six: hide npc; add/drop npc face; yes/no prompt; gold range x to y

Requirements: Text editor, Daggerfall Unity

Part One: Reading the Quest File

We will begin with the basic points, so if the reader is already acquainted with programming principles, you are free to scroll down until you see new things :)

In spite of being composed by .txt files, Daggerfall quests use a scripting language. This means it must begin at the beginning of the file and logically connect one element to another to produce a meaningful effect or series of effects. The language Daggerfall uses to put together a quest is simple but efficient.

There are three primary elements to a Daggerfall quest: definitions, conditions, and actions.

The Preamble consists of the first major section of the quest file. Take one sample Fighters Guild quest: (download it here if you prefer seeing it in your text editor)

Code: Select all

-- Fighters Guild
-- Min Rep: 35
Quest: FGDFU001
DisplayName: Much at Stake
-- Message panels
QRC:

QuestorOffer:  [1000]
<ce>          We've got a job for someone experienced like you,
<ce>           %pcf, but I'm not gonna blame you for turning
<ce>           it down. It involves one vampire and no sunlight.
<ce>              You think you can fix that kind of problem?
<ce>                  There's _reward_ gold in it for you.

RefuseQuest:  [1001]
<ce>                       I'll look for someone else then.

AcceptQuest:  [1002]
<ce>                   So here's the deal. You're headed to 
<ce>          __vampres_. We don't know how the thing got into
<ce>               _vampres_ or how long it's planning on
<ce>           staying, but we've gotta kill it before we find out.
<ce>             I'm giving you =timer_ days before I send for backup 
<ce>                from a temple or something. Good luck, %pcf.

QuestFail:  [1003]
<ce>               Scared of vampires, %pcf? Get a spine.

QuestComplete:  [1004]
<ce>           Excellent work, %pcf. This is why
<ce>            we count on you. Have your gold.

RumorsDuringQuest:  [1005]
Did you hear about the vampire that showed up in __vampres_? I can't sleep anymore.


Message:  1020
<ce>                   "No!" The vampire shrieks
<ce>                 before crumpling to the floor.


Message:  1030
%qdt:
 The Fighters Guild of
 ___questgiver_ has sent me to
 __vampres_ to slay a vampire
 in _vampres_.  I've got to
 to be back in =timer_ days
 or they'll call for the temple.


QBN:
Item _reward_ gold

Person _questgiver_ group Questor male

Place _vampres_ remote house1

Clock _timer_ 00:00 0 flag 17 range 0 2

Foe _vamp_ is Vampire

--	Quest start-up:
	start timer _timer_ 
	log 1030 step 0 
	place foe _vamp_ at _vampres_ 

_pcgetsgold_ task:
	when _qgclicked_ and _slain_ 
	give pc _reward_ 
	end quest 

_slain_ task:
	killed 1 _vamp_ saying 1020 

_qgclicked_ task:
	clicked npc _questgiver_ 

_timer_ task:
	end quest 

_clearclick_ task:
	when _qgclicked_ and not _slain_ 
	clear _qgclicked_ _clearclick_ 
Everything from the beginning to the section named "-- Quest start-up:" is the preamble. This sets forth definitions for what the quest will be using, such as text messages, people, enemies, cities, and dungeons. As you can tell, a vast majority of a quest file's content is its dialogue. If you're quick at writing dialogue and know your way around the quest file, a new quest can take fifteen minutes to write and test.

Without definitions, the quest will not function. Let's see first the definitions for the quest named above.

Code: Select all

-- Fighters Guild
-- Min Rep: 35
These first two lines are meaningless to Daggerfall, since any line that begins with a hyphen is ignored. Be it one hyphen or twenty, the effect is the same. I included these two lines as useful notes for humans.

Code: Select all

Quest: FGDFU001
DisplayName: Much at Stake
These are useful for the quest debugger but do not serve a major technical purpose. You can practically put any text to these two fields and I've found no issue.

Code: Select all

-- Message panels
QRC:
In classic Daggerfall, the QRC file was where the dialogue came from. Leave it in this section each time.

Code: Select all

QuestorOffer:  [1000]
When you click the "Get Quest" button, Daggerfall will randomly choose a quest from its pool and produce the message number 1000, with a "yes or no" prompt.

Code: Select all

RefuseQuest:  [1001]
If you say "no," you will always get message number 1001, and the quest will end.

Code: Select all

AcceptQuest:  [1002]
If you say "yes," you will always get message number 1002, and the quest will begin applying quest definitions, as found further below.

Code: Select all

QuestFail:  [1003]
1003 is called by some quests, and others never use it.

Code: Select all

QuestComplete:  [1004]
Message 1004 is shown when you have successfully completed the quest objectives.

Code: Select all

RumorsDuringQuest:  [1005]
The Rumors section belongs to townspeople dialogue. This comes in several varieties:

RumorsDuringQuest: [1005]
RumorsPostfailure: [1006]
RumorsPostsuccess: [1007]
QuestorPostsuccess: [1008]
QuestorPostfailure: [1009]

Code: Select all

Message:  1020
I've added here a custom message which I'll be using during the quest. You can write as many messages as you want, and they do not have to be in any order at all. Therefore, you can write up a series like:

Message: 1020
Message: 1048
Message: 1055
Message: 1071
Message: 1090

For order's sake I always make the number rise as I go down, but I do not know whether a mixed order would have any impact on quest execution.

Code: Select all

Message:  1030
%qdt:
This is the message that will be written into your quest log when the quest begins. This could be any number at all, and many custom quests use 1010 instead. The %qdt is converted into the full date, such as "Tirdas, 2nd of First Seed." automatically by Daggerfall.

Code: Select all

QBN:
Now the text is over with and we're getting into object definitions. QBN was the Daggerfall file that defined quest objects, conditions, and actions. Leave it in this section each time.

Code: Select all

Item _reward_ gold
The "item" command defines one variable (usually called "symbol" in Daggerfall terminology) as some non-moving object in the game world, including inventory and overworld items. In this case, we have told the game to create an object called _reward_, which in this case is some gold.

The "gold" definition, when given no other parameters, automatically calculates to gold coins of approximately "your level x 100" value in the game. In this case we're just going to let that be.

Code: Select all

Person _questgiver_ group Questor male
The "person" command defines one variable as something considered a person. These are the flat people inside Daggerfall's buildings and dungeons, who you can ordinarily click on for conversation of some kind.

The "group" parameter places the person into one of Daggerfall's set groups. The "group" trait itself is not very relevant yet, nor is "male," but having a "questor" is vital to end this quest.

Always include this definition for the questgiver if you're going to return to interact with him/her in some way.

Code: Select all

Place _vampres_ remote house1
The "place" command defines one variable as a location to be used in the quest. This has many, many options with different effects. I have named this place _vampres_, because it's a residence where a vampire will show up.

"Remote" means it's some location not in the same town but in the same region, like Daenia or Abibon-Gora. Use "local" instead to look for that location type in the same geographical location you're now in. There's also a "permanent" option, which refers to certain fixed places in Daggerfall like Shedungent, Scourg Barrow, and Wayrest Castle, to name a few.

"house1" means that Daggerfall is told to look for some house within the subset "house1" (I don't know what the house1 set is but it looks for houses), and then define it as the place known as _vampres_. You can consult the quest element topic to see what other options exist beyond house1.

Not all regions or cities have all quest locations; for example, Betony has no witch coven, so attempting to define a quest in Betony using a remote witch coven would prevent the quest from setting up. Likewise, defining a "local bank" in a town that doesn't have a bank will just mean the quest refuses to run in that city.

Code: Select all

Clock _timer_ 00:00 0 flag 17 range 0 2
The "clock" command creates one timer, called _timer_. A quest can use multiple timers for multiple purposes.

The whole "00:00 0 flag 17 range 0 2" mess is the ordinary way of calculating a time limit automatically, based on travel time to the quest location and back. Do not use this method if your quest doesn't require any fast travel.

Instead of "00:00 0 flag 17 range 0 2," you can replace it with "1.00:00," which will give you 1 day, zero hours, and zero minutes. Or "0.11:37," which will give you eleven hours and thirty-seven minutes to complete the quest.

If a timer runs out, that alone doesn't accomplish anything. You'll need to define a task further below that says what happens when time is up.

All times refer to in-game days, hours, and minutes.

Code: Select all

Foe _vamp_ is Vampire
The "foe" command creates an enemy or group of enemies, but does not place it anywhere in the game world. You will do that later on. You can vary this using the table found in the quest element topic. You can also define multiple enemies in one variable. Some examples are:

Foe _vamp_ is 2 Vampire
Foe _killer_ is Barbarian
Foe _jayH_ is 3 Imp

When defining a foe as multiple enemies, never use the plural form of the enemy's name.

Code: Select all

--	Quest start-up:
Since this line is hyphenated, it means nothing to Daggerfall. This just means definitions are over and we're now getting into the quest set-up.

Everything that happens in this paragraph is executed immediately after you accept a quest and move beyond the acceptance text.

Code: Select all

	start timer _timer_ 
Each section from here uses a normal first line and a tabbed content section. This is for easy navigation.

The "start timer" command tells that timer we defined above to begin. Most quests have a timer, and pairing the "Clock _timer_ 00:00 0 flag 17 range 0 2" and "start timer _timer_" commands is a quick way to resolve this in most cases.

You may think some quests don't need a timer, but it's a good idea to put one in all ordinary quests, even if it's like three months long. If something goes wrong, such as the quest enemy or item disappearing, and the quest becomes impossible to complete, there needs to be some way for the quest to terminate on its own. Otherwise it'll stay around in the journal until the sun goes cold, which is a bother.

Code: Select all

	log 1030 step 0
This command writes into your character's journal the message number 1030. If you go back up, you'll see that's the one that begins with %qdt. All journal entries in Daggerfall begin with %qdt, since that's just your character's way of keeping track of time with these things.

The "step 0" doesn't matter for this quest. I don't know what happens if you remove it, as I've always kept it on. The step system determines where in the journal to put each entry in a single quest. Thus, if I for some reason order,

log 1014 step 3
log 1030 step 0
log 1035 step 2
log 1021 step 1

In the journal the quest messages will appear in this order:

1030
1021
1035
1014

If you for some reason assign the same step number to two messages, the first will be overwritten.

You can't use more than 10 steps in a single quest, which are 0 to 9.

Steps among different quests do not interfere with each other.

Code: Select all

	place foe _vamp_ at _vampres_ 
We previously told Daggerfall we wanted an enemy called _vamp_ who would be a Vampire. We also asked for a house somewhere in our region called _vampres_.

The "place foe" command puts the defined enemy into the defined location at some random quest marker within it. Once you enter the defined location, the defined enemy will be there, waiting for you.

If you attempt to use the "place foe" command in some location with no valid quest markers, the quest will refuse to set up.

Code: Select all

_pcgetsgold_ task:
Now we're getting into specific defined tasks. These use conditions and actions.

Code: Select all

	when _qgclicked_ and _slain_ 
This is a condition. Specifically, it says, "when the _gqclicked_ task and the _slain_ task are simultaneously activated,"

Code: Select all

	give pc _reward_ 
This is an action. Once the condition on the line above is met, this action executes: "give pc the gold we previously defined as _reward_."

"give pc" is an action that opens the inventory screen and offers you the chance to accept a defined item.

Code: Select all

	end quest 
Any time the "end quest" action is run, the quest immediately cuts off there. Spawned enemies will stay present, at least until you fast travel, and defined items will remain in your inventory after the quest ends. This will change at some point, when items will need to be made permanent specifically as a separate command in order to persist, but that is not yet relevant.

As you can tell the "pcgetsgold" task is out of place, but this was the very first quest I wrote.

Code: Select all

_slain_ task:
This defines what the task "slain" is going to mean.

Code: Select all

	killed 1 _vamp_ saying 1020 
Now this is curious. There's no action in this task, just a condition. What this task means is that once you have killed one enemy defined as _vamp_, the _slain_ task is activated.

Tasks activated in this manner remain activated during the entire quest. If you go up, you'll see what significance this has for the quest overall:

_pcgetsgold_ task:
when _qgclicked_ and _slain_
give pc _reward_
end quest

So once _slain_ is activated, we just need to activate _qgclicked_, and then _pcgetsgold_ will execute its action: giving a reward and ending the quest.

Coming back to the "killed" command, attaching a "saying 1020" to the end of it means the game will display message 1020 once the _vamp_ is killed. Not all actions support the "saying ####" parameter, but this one does.

When using the "killed" condition, define the number of enemies that must be slain, no matter what. Even if you define _vamp_ to mean 10 vampires, you must still define the "killed" action as 10 _vamp_ if you want every one to be killed, even though the game should presume you meant 10 to begin with.

Code: Select all

_qgclicked_ task:
	clicked npc _questgiver_ 
Well, this is simple. We defined a person named _questgiver_ at the very start. Once we click on _questgiver_, the _qgclicked_ task is activated.

"clicked npc" can function on any defined person as a condition. This works similarly to how "killed" is a condition, not an action.

Code: Select all

_timer_ task:
You remember how we defined a timer up above, called _timer_? That on its own does not decide a consequence when the timer runs out. When _timer_ hits zero, it will want to execute some action. This is where you put the actions that will happen when the timer runs out.

Code: Select all

	end quest 
In this case, once the timer runs out, the quest will get cut off.

All timers inherently create a task of their own name. This convention must be followed for any timer to have any effect.

Code: Select all

_clearclick_ task:
	when _qgclicked_ and not _slain_ 
	clear _qgclicked_ _clearclick_ 
This clever piece of work by Interkarma resolves some background problems with clicking on the questgiver. Just include it somewhere in all your quests, along with the _questgiver_ definition and the _qgclicked_ task.

That is our quest file dissection completed! In the upcoming posts, we'll create a quest file step by step, and then run it in Daggerfall Unity.
Last edited by Jay_H on Sun Feb 17, 2019 12:21 am, edited 24 times in total.

User avatar
Jay_H
Posts: 4059
Joined: Tue Aug 25, 2015 1:54 am
Contact:

Re: Jay_H's quest writing tutorial

Post by Jay_H »

Part Two: Writing a Simple Quest

Boot up Daggerfall Unity and your text editor. You can choose any from Notepad up to any code writer, but I highly recommend TheLacus' quest-writing utilities. They will drastically reduce the work for each quest.

In Daggerfall Unity, move your character to the region of Ilessan Hills, to the town called Aldingdale. It'll provide a perfect testing environment to begin with. Go into the Fighters Guild there and save your game in front of the quest giver.

In your text editor, if you want to save yourself some time, use a template containing all the major categories a DFU quest will require. Here's one in case you want to use it:

Code: Select all

Quest: 
DisplayName:
-- Message panels
QRC:

QuestorOffer:  [1000]
<ce>          

RefuseQuest:  [1001]
<ce>              

AcceptQuest:  [1002]
<ce>             

QuestFail:  [1003]
<ce>               

QuestComplete:  [1004]
<ce>                

QBN:
Item _reward_ gold

Person _questgiver_ group Questor male

Clock _timer_ 00:00 0 flag 17 range 0 2


--	Quest start-up:
	start timer _timer_
	log 1030 step 0
	place _mondung_

variable _slain_
_qgclicked_ task:
    clicked npc _questgiver_
 
_timer_ task:
    end quest
 
_clearclick_ task:
    when _qgclicked_ and not _slain_
    clear _qgclicked_ _clearclick_
You can edit your text file and save it, and Daggerfall Unity will incorporate the changes each time it attempts to begin that quest. Testing can be very quick if you know the variable you're trying to single out.

Placing the Quest File

First, name your quest file something. Right now we're going to name it the following:

TESTING1.txt

Save it in the folder /DaggerfallUnity_Data/StreamingAssets/QuestPacks.

In a separate text editor, create a new file called 'QuestList-TESTING.txt' in the same folder and enter the following:

Code: Select all

schema: *name, group, membership, minReq, flag, notes

TESTING1, FightersGuild, N, 0, 0, Test quest
The commas must be there, and you mustn't use any commas in the last field (notes) or you'll get an index error from DFU when you try to run the quest. This quest will be offered to non-members by fighters guild quest npc as long as rep is >= 0 with the guild. Change the N to an M to have the quest offered to members instead. You can close this text editor now and return to just DFU and your custom quest. You'll need to do this every time you add a new quest. When you come to creating quests for real, they should be placed in a subfolder named after the author (you) and in a subdirectory if you wish to release multiple packs. See http://forums.dfworkshop.net/viewtopic. ... 901#p11005 for more about quest packs.

Defining Variables

Let's go filling out our template step by step, or you can write them in if you prefer not to use a template.

Code: Select all

Quest: TESTING1
DisplayName: JayH Is Helping Me
This is the first thing we put in.

Code: Select all

QRC:
This will follow on the next line. It needs no further parameters.

Code: Select all

QuestorOffer:  [1000]
<ce>          We need to kill one _enemy_.
When you fill message boxes, it's generally done with a <ce> tag at the start of each line. This will center the text in the middle of the row.

Code: Select all

RefuseQuest:  [1001]
<ce>              Don't be like that, %pcn.
This text will show if you click "no."

Code: Select all

AcceptQuest:  [1002]
<ce>                   Kill it then.
This text will show if you click "yes."

Code: Select all

QuestFail:  [1003]
<ce>               Just ridiculous.
This text might never show, but you might as well put something there for training purposes.

Code: Select all

QuestComplete:  [1004]
<ce>                Good job.
This will show up when you've fulfilled our victory requirement.

Code: Select all

QBN:
This follows next, since we're already on to defining objects. Pretty quick, right?

Defining Quest Objects

Code: Select all

Item _reward_ gold

Person _questgiver_ group Questor male

Clock _timer_ 00:20

Foe _enemy_ is Orc

Place _guild_ local fighters
Set the clock thus for this specific quest since we're not fast traveling anywhere. You could forego the clock if you want, but it's good to see multiple effects at work.

What we've done here is:
we've told Daggerfall to make an item called _reward_, which will be an amount of gold about one hundred times your character's level;

we've identified the guy in the green tunic as the questgiver; (this one's not mechanically obvious but trust me, it'll work fine)

we've set a timer which will last twenty minutes;

we've created an enemy we're calling _enemy_ who is an Orc;

and we've designated a local "fighters" as the location _guild_.

Defining Quest Actions:

Code: Select all

--	Quest start-up:
	start timer _timer_
	place foe _enemy_ at _guild_
Note that "quest start-up" begins with hyphens, so it isn't strictly needed. It helps organize, though.

Code: Select all

_mondead_ task:
	killed 1 _enemy_
	give pc _reward_
	end quest
This is our victory task.

Code: Select all

_timer_ task:
	end quest
This is our failure task. Besides dying, of course, but I don't want your character to die.

Our first quest is now finished! It's that simple. Let's get into Daggerfall Unity and test it!

Testing Time
Load up that character in the Fighters Guild in Aldingdale in Ilessan Hills, which you did exactly as I told you at the start of this post (excellent work!), and talk to the questgiver, the guy in the green tunic. Preferably from the angle shown below.
Image
Accept the quest.
Image
Deal with tunic guy's smart attitude.
Image
Here it is! Our first quest enemy!
Image
More attitude from tunic guy.
Image
And our clever, pre-packaged reward:
Image
(If your experience at all varies from what you see in these screens, check your quest file against the steps above, or ask here for help.)

Pretty clever! How did this all happen?

We told Daggerfall to find a Fighters Guild in the current town. Aldingdale only has one Fighters Guild. We told the game to choose one of those one Fighters Guilds at random and put _enemy_ in it, which is Orc. Once we killed Orc, the _mondead_ task activated, since its condition was:

killed 1 _enemy_

_mondead_'s action was:

give pc _reward_
end quest

When you use the command "give pc _variable-item_", the game automatically states message 1004, the victory statement. If you want to end the quest in success but have no reward (like in a knightly order), you'll have to use the "give pc nothing" command (yep).

Now, let's take a bit of alternate historyism here. Let's let time run down and not get killed by the Orc. You can step outside or use the "tgm" command in the console, which will make you invulnerable. Once you've let time run out, take a look at the top-left of the Daggerfall Unity screen.

We'll compare the "success" screen to the "failure" screen.
Image
Image
The top left of the screen provides your quest diagnostics. There isn't a full-fledged system in Daggerfall Unity to say things like "missing parameter on line 68" or things like that, so this is what we use.

In both versions, "startup" began. That's this section:

Code: Select all

--	Quest start-up:
	start timer _timer_
	place foe _enemy_ at _guild_
Then the paths diverge. The success screen has the task _mondead_ highlighted, since _mondead_ is activated by killing one _enemy_.

The failure screen has the task _timer_ highlighted, since _timer_ is activated once that timer runs out. Also notice the very unfriendly red "0:00" on the left side of the screen.

In both cases, the quest will say "Finished" on the top left. That's to let you know the quest is no longer running, for whatever reason; something executed an "end quest" action somewhere in the process. In our case, _mondead_ and _timer_ both had "end quest" actions attached, for different reasons.

Before we're done, why not vary things a little? It's very easy in Daggerfall.

Go back up to the definitions section. Delete this line:

Code: Select all

Foe _enemy_ is Orc
and replace it with this line:

Code: Select all

Foe _enemy_ is Centaur
Now save the text file and try the quest again.

Then try deleting this line:

Code: Select all

place _guild_ local fighters
and replacing it with this line:

Code: Select all

place _guild_ local temple
See if you can find it before time runs out! (use the "set_runspeed 25" command in the console if you can't make it there in time.)

This ends our first quest, where we see some basic variables and how to start and end a quest. Post if you have questions or if things go horribly, horribly wrong.
Last edited by Jay_H on Sun Feb 17, 2019 12:33 am, edited 21 times in total.

User avatar
Jay_H
Posts: 4059
Joined: Tue Aug 25, 2015 1:54 am
Contact:

Re: Jay_H's quest writing tutorial

Post by Jay_H »

Part Three: create foe; "pc at" condition; when x and y; journal

Here's our second tutorial quest. We'll name it TESTING2.txt. Once again, you'll need to modify the quest tables as described previously.

We'll be using a few of the things we saw last time, and adding a lot more. Now our conditions are going to get more complex, which means more fun.

Code: Select all

Quest: TESTING2
DisplayName: A Second Helping
-- Message panels
QRC:
Giving it a unique quest name and display name.

Code: Select all

QuestorOffer:  [1000]
<ce>          Go to ___questplace_. There's a
<ce>           trap in _questplace_.
This time we're going to be going outside Aldingdale, and we need to know both the city and the building we're headed to. The underscores before each of these variables alters what it's going to show. ___questplace_ (when defined as a building) becomes the city name and _questplace_ becomes the target building.

Code: Select all

RefuseQuest:  [1001]
<ce>              Go away.
He's tunic guy, let him be.

Code: Select all

AcceptQuest:  [1002]
<ce>              Be back in =timer_ days.
When included in dialogue, the timer must be written with an equation sign before it instead of an underscore. Otherwise it won't express properly.

Code: Select all

QuestFail:  [1003]
<ce>               I'm kicking you out of the guild.
Again, 1003 is likely irrelevant.

Code: Select all

QuestComplete:  [1004]
<ce>                You win this time.
We'll see, tunic guy.

Code: Select all

Message: 1020
<ce>                Your supremacy is clear.
This is a particular message we'll be using midway through the quest.

Code: Select all

Message: 1030
%qdt
 I'll go to _questplace_
 in ___questplace_ and
 totally own these critters.
 I'm going back to
 ___questgiver_ before =timer_ days
 are up.
This will be written in our journal, providing us directions on where we're going and where the questgiver is. There should be a journal entry for every quest, but especially so if you're going to another city. It needs to show the destination, the home point, and the time limit.

Code: Select all

QBN:
Item _reward_ gem

Person _questgiver_ group Questor male

Foe _hum_ is 2 Assassin

Place _questplace_ remote apothecary

Clock _timer_ 00:00 0 flag 17 range 0 2
Here we are again!

We're changing the reward from gold to a randomly chosen gem.

We're defining a new foe, and setting two of them to appear at a time.

We're both using a "remote" location (outside the questgiver's city) and an apothecary. Daggerfall will look for some herb store outside of your current city to plant the quest location, as we'll see soon.

Lastly, we returned to the automatic clock. It'll work well since we only have one destination and it's outside our current city.

Code: Select all

--	Quest start-up:
	start timer _timer_
	log 1030 step 0	
	pc at _questplace_ set _inside_
The timer is the same as before.

the "log" command will write message 1030 into your journal. "Step 0" just means it'll be the first message to appear in your journal from this quest. A single quest can include up to 10 steps, 0 to 9.

The "pc at" is by far the biggest change in this quest. It's a quest condition that says, "When the player character is inside _questplace_, the task _inside_ is going to be activated. If the player character leaves _questplace_, the task _inside_ is going to be deactivated."

Code: Select all

_inside_ task:
This task will be completely empty. Its only purpose is to be a light switch that the "pc at" condition turns on. Another task is going to check whether _inside_ is turned on, and based on that is going to act:

Code: Select all

_spawn_ task:
	when _inside_ and not _slain_
	create foe _hum_ every 2 minutes 100 times with 100% success
While _inside_ is activated and _slain_ is not activated, this task is going to create enemies around you. This is the "create foe" action, which is different from "place foe." "place foe" puts a stationary enemy at a quest marker. "create foe" makes the enemy appear around you.

"Create foe" has some conditions that have to be forcibly answered:

"every x minutes," referring to in-game time. The game will wait x minutes before each spawn. So you can make that 1 minute, 15 minutes, 2000 minutes, and so on. If you want it to occur every minute, you must still say "every 1 minutes." Every 0 minutes is also a valid variable if you want to flood with enemies.

"y times," which means how many times the enemy could possibly be created. 100 sounds like a lot but you'll see they won't all spawn because of something else we do. You can substitute "y times" with "indefinitely" if you want.

"with k% success" means the game will run a probability check on whether, every x minutes, the enemy will actually spawn or not. However, given enough time and unchanging conditions, the enemy will spawn y times, no matter what. It's just a question of how many times the game will fail to spawn it in the meantime.

Code: Select all

_slain_ task:
	killed 6 _hum_ saying 1020
And now here we have the aforementioned _slain_ task. As long as this is not lit up and _inside_ is lit up, enemies will keep spawning around you up to 100 times. Killing six individual Assassins will cause _slain_ to be activated, thus ending the invasion.

"saying 1020" will produce the message we defined as "Message: 1020" above when this task is activated. It's good to have a cue of some kind that shows the player that their work is done.

Code: Select all

_qgclicked_ task:
	clicked npc _questgiver_
This task must be added or the questgiver will refuse to address you even after succeeding against the enemies.

Code: Select all

_pcgetsgold_ task:
	when _qgclicked_ and _slain_
	give pc _reward_ 
	end quest
This task requires two conditions: _slain_ (in other words, that you kill six assassins) and _qgclicked_ (in other words, that you click on the questgiver). Once both of these conditions are met, you will meet the successful resolution of this quest.

Code: Select all

_timer_ task:
	end quest

_clearclick_ task:
	when _qgclicked_ and not _slain_ 
	clear _qgclicked_ _clearclick_ 
These are staple parts of the quest, as explained in the first post.

So then, where will this quest send me?
Image
Knightwall, in my case.
Image
And this will show up...
Image
In the journal. People need their biggest reminders in their journal.
Image
Note that the only task activated so far is "startup."
Image
Now that I've stepped inside, both "inside" and "spawn" are activated. Enemies will start showing up.
Image
Six enemies are slain, which produces the "slain" task. Now no enemies will spawn, since it conflicts with "spawn"'s requirements.
Image
Indeed I have, tunic guy.
Image
And as promised, a different quest reward.

Now we have to deal with dungeons, hours of the day, and quest variations chosen by the game. That'll be in the next post, and then we'll focus on substitution among variables and work on people's ideas for quests.
Last edited by Jay_H on Sun Feb 17, 2019 12:38 am, edited 6 times in total.

User avatar
Jay_H
Posts: 4059
Joined: Tue Aug 25, 2015 1:54 am
Contact:

Re: Jay_H's quest writing tutorial

Post by Jay_H »

Part Four: Dungeons, hours of the day, multiple "killed" conditions

This is our third quest tutorial.

Name your new text file "TESTING3.txt", and do the same for the quest tables as previously described.

Code: Select all

Quest: TESTING3
DisplayName: We Crawl Dungeons Now
-- Message panels
QRC:
Standard intro.

Code: Select all

QuestorOffer:  [1000]
<ce>          Head to __mondung_.
_mondung_ is a fairly customary variable used to mean a dungeon where a monster's going to appear. You can re-use the same variable names across quests, which will save you some quest execution problems along the road, as well as some effort. It's completely legitimate to just cut and paste elements from old quests when you're making new ones.

Take note that to properly display the dungeon in dialogue, it must be __mondung_, with two underscores in front. Each variable (place, person, dungeon, building, city) has its own rules on how many underscores you have to use.

Code: Select all

RefuseQuest:  [1001]
<ce>              Fine then.
He's been here since the early morning.

Code: Select all

AcceptQuest:  [1002]
<ce>              So get going then.

Code: Select all

QuestFail:  [1003]
<ce>               Failed.

Code: Select all

QuestComplete:  [1004]
<ce>              You survived.
<ce>            Have this _reward_.
All standard text.

Code: Select all

Message: 1020
<ce>               You killed the enemy.
As I mentioned before, it's good to have some cue to denote every step of quest progress. In classic it was maddening to have actually killed the quest enemy (or worse, picked up the quest object in earlier versions of Daggerfall) and never know it. Then you're running back to the questgiver every so often to see whether it was done or not.

Code: Select all

Message: 1030
%qdt:
 In __mondung_
 there's a powerful enemy
 I need to slay to prove
 to _questgiver_ that
 I belong in the Fighters Guild.
As you can tell by the %qdt, this will go in our log. This is customary for message 1030.

By writing _questgiver_, which is the person's defined name, I've told Daggerfall to display the person's name in this message when it appears in the game. DFU still has limited abilities in terms of people for now, but this display will work.

Code: Select all

QBN:
Item _reward_ weapon

Person _questgiver_ group Questor male

Foe _enemy1_ is Giant_rat
Foe _enemy2_ is Giant_bat
Foe _enemy3_ is Grizzley_bear
Foe _enemy4_ is Lich

Place _mondung_ remote dungeon

Clock _timer_ 00:00 0 flag 17 range 0 2
Now then, let's get to our definitions:

The quest reward this time will be a random weapon Daggerfall chooses from its pool of weapons. Right now that'll just mean something made of iron.

The questgiver is defined, which we'll always want to do.

We have defined four types of enemies, but we won't be using all of them at a time. Note that the "grizzley" thing is not my typo, as that's how it has to be spelled for Daggerfall to accept it. Don't blame Julian Lefay; his work with Daggerfall was spectacular.

We have defined a place, _mondung_ as some dungeon somewhere in the region. This will work similarly to how we defined a remote location last time.

We've defined a standard automatic clock for this quest.

Code: Select all

--	Quest start-up:
	start timer _timer_
	log 1030 step 0	
	pc at _mondung_ set _inside_
	reveal _mondung_
	pick one of _mission1_ _mission2_ _mission3_
We've told Daggerfall to start the timer we mentioned above as soon as the quest begins.

We've told Daggerfall to write message 1030 in the character's log.

We're using the "pc at" condition again, which will turn on the _inside_ task once we're in the dungeon.

"pick one of" is a new quest action which tells Daggerfall to choose, in equal proportion, one of the tasks of the three I've given it. Once the quest begins, we'll see on the left side of the screen which one it chose for me.

Code: Select all

_inside_ task:

Once again, _inside_ will be empty. Its only purpose is to be a signal for other tasks.

Code: Select all

_day_ task:
	daily from 5:30 to 18:30
"daily from" allows you to denote specific times of the day, during which the _day_ task will be activated. Outside of those hours, _day_ will not be activated.

This quest condition does not tolerate things like "daily from 23:00 to 4:00," where you move from 23:59 to 0:00. However, that's not an impediment.

Code: Select all

_spawn_ task:
	when _inside_ and not _day_
	create foe _enemy4_ every 1 minutes indefinitely with 50% success
We're using a _spawn_ task like in quest 2. We're mixing it with the _day_ task. To break it down, _spawn_ will be activated when two things are true:

1. The PC is inside _mondung_,

2. The time in-game is not between 5:30 AM to 6:30 PM.

While _spawn_ is activated, _enemy4_ will appear in merciless waves.

Code: Select all

_mission1_ task:
	place foe _enemy1_ at _mondung_

_mission2_ task:
	place foe _enemy2_ at _mondung_

_mission3_ task:
	place foe _enemy3_ at _mondung_
Now then, this is what we told Daggerfall to choose one of at the beginning. One of these three tasks will be executed. Each one tells Daggerfall to place a different enemy kind in a random quest marker in the dungeon, according to the definitions we made up above.

Code: Select all

_slain_ task:
	killed 1 _enemy1_ saying 1020
	killed 1 _enemy2_ saying 1020
	killed 1 _enemy3_ saying 1020
We have included three "killed" conditions here, but only one enemy will spawn, as we noted above. The _slain_ task will be activated when any of these three "killed" conditions happens, not all of them. Once we kill one _enemy1_, one _enemy2_, or one _enemy3_, the _slain_ task will be activated.

As a side note, if you want you could take the "saying 1020" off the end of each line and instead do the following:

killed 1 _enemy1_
killed 1 _enemy2_
killed 1 _enemy3_
say 1020

The effect is the same. The first three lines are conditions, and the fourth is an action.

Code: Select all

_qgclicked_ task:
	clicked npc _questgiver_

_pcgetsgold_ task:
	when _qgclicked_ and _slain_
	give pc _reward_ 
	end quest 

_timer_ task:
	end quest

_clearclick_ task:
	when _qgclicked_ and not _slain_ 
	clear _qgclicked_ _clearclick_ 
All very standard parts which will make things work well.

So then, how will this quest go for me?
Image
Before we get started, we're going to talk a little about the console. It will make quest testing far easier for you.

While in gameplay, press the ` button, the one to the left of 1 if you're using an English keyboard. That'll open a little text field at the bottom of the screen. You can write "help" if you want to see all of its commands.

First put in the command "tgm" and press enter. That should answer, "Godmode enabled: True." This keeps your health and stamina at full all the time.

Then put in the command "set_runspeed 25" and press enter. That should answer, "Run speed set to: 25." This will make navigation a bit more convenient.
Image
You'll notice, as part of quest set-up, the game chose for me "mission2" on the left side. If none of those three "mission#" things lights up, something's wrong with the quest file.

Now head to the dungeon.
Image
As you can tell from Lypyl's wonderful enhanced sky, we're still in daytime. This means the _spawn_ mechanic we previously defined will remain inactive. But what happens if I go inside and it's no longer daytime?
Image
_spawn_ has two conditions, which are _inside_ on and _day_ off. This is how we tell Daggerfall to give us night-based quest tasks: by defining a day period and telling it to exclude that period.

If _inside_ is not lighting up, either it's programmed wrong or you're not in the right dungeon.

Daggerfall will not stop spawning liches so long as we are in this dungeon and it's night time. You could go outside and wait around until it's day again to make it easier, but we have more dev tools to use.

Open your console again with ` and type the command "tele2qspawn" and press enter. It should answer, "finished," and your surroundings should look different. Step back and you should see:
Image
A bat, in my case.
Image
It's dead, so take note that the _slain_ task on the left is now activated. Now to complete this quest I just have to get back to _questgiver_.

In the console, type in the command "tele2exit" and press enter. This should send you back to the entrance of the dungeon. Here's a list of the console commands we used today:
Image
Here are our activated quest tasks:
Image
And the reward this time:
Image
And everything finished, including the "Finished" status on the top left.
Image

If you're testing several quests and saving your game, you'll start to see the quest number on the top left start to rise, like [1 of 14]. That doesn't do any harm, but your personal preference might be to clear it all out and start from zero. In the console you can use the command "purgeallquests" to wipe away every previous and current quest to start over.

One command we didn't explicitly explain was "say ####," but that's covered slightly here in this lesson. Experiment with it, such as "say 1020" or "say 1030" at any time you can designate a quest action, and you'll see it's a simple action to perform.

There's also the "prompt" command, which is completely functional but more oriented toward people. If you want to attempt to use it, you can see how I used it in a quiz quest. It's really just "say ####" with a yes/no prompt and two logic choices, at least for now.

Wasn't that a fun idea, though? We've made a dungeon that's an absolute nightmare during the night and is safer during the day. Now it's your turn. Mix and match quest variables, one with another, and start to form ideas for making quests. Here are a few ideas:

Can you make a quest fail by killing the wrong enemy?
Can you make enemies appear in more than one location?
Can you make more enemies appear after killing one enemy?
Can you find a way to use place variables such as "templehome" and "farmhome?"
Can you find a use for the place variable, "permanent Shedungent1?"
Can you find a way to broadcast a message in a specific building at a specific time of day?
Can you give more than one reward per quest?
Can you make an enemy speak upon dying?
Can you use one "remote" location and one "local" location in the same quest?
Can you spawn enemies as long as you're not in a given safe location?

Be certain to consult the quest element topic to see what's enabled so far. This will let you see all the kinds of enemies, places, and items available, as well as any notes if they have problematic functionality. Beyond that, feel free to get elaborate! :)
Last edited by Jay_H on Thu Sep 06, 2018 5:55 am, edited 18 times in total.

User avatar
Interkarma
Posts: 7234
Joined: Sun Mar 22, 2015 1:51 am

Re: Jay_H's quest writing tutorial

Post by Interkarma »

Jumping in just say to say AWESOME! and thank you for doing this, you're a legend. :)

User avatar
Jay_H
Posts: 4059
Joined: Tue Aug 25, 2015 1:54 am
Contact:

Re: Jay_H's quest writing tutorial

Post by Jay_H »

Aw, you rock Interkarma :) I'm just happy I could finally do something. I've been here for a long time just cheering and reporting sometimes obsolete bugs.

By the way, is Tipton's questing.html hosted somewhere in the slush pool? It would be pretty convenient to link to later on.

User avatar
Interkarma
Posts: 7234
Joined: Sun Mar 22, 2015 1:51 am

Re: Jay_H's quest writing tutorial

Post by Interkarma »

I have an online copy here. Link away. :)

User avatar
Jay_H
Posts: 4059
Joined: Tue Aug 25, 2015 1:54 am
Contact:

Re: Jay_H's quest writing tutorial

Post by Jay_H »

Perfect, thank you!

User avatar
Interkarma
Posts: 7234
Joined: Sun Mar 22, 2015 1:51 am

Re: Jay_H's quest writing tutorial

Post by Interkarma »

Stickied.

I can also confirm Daggerfall Unity doesn't need the "Messages:" tag in the header. You can basically just structure file like so:

Code: Select all

Quest: FILENAME
DisplayName: Display Name

QRC:
-Quest messages

QBN:
-Resources and Tasks

User avatar
Ziune Wolf
Posts: 52
Joined: Wed Aug 09, 2017 2:55 am

Re: Jay_H's quest writing tutorial

Post by Ziune Wolf »

I might say, thank you for holding to your word and making this beginner's guide to quest-making. While Tipton's documentation is great, I think it better serves as a reference if you forget how to write something, forget the exact meaning of certain lines in quests, or want to see if functionality is available for the thing you are trying to do. I will likely be referring to this several times when making new quests sometime soon!

What I must say though, is that I must've worded some of my previous questions rather strangely and ambiguously. In previous discussions, I mentioned making what I called "Custom Tasks". Seeing the guide above, I now see how my wording could have been confused.

When I referred to "Custom Tasks" I referred to making tasks that the original, classical Daggerfall system couldn't do. For example, creating a task that makes a variable that is saved even after the quest is completed, or similar such tasks. These are more where I was getting at when asking questions. For example, one thing I would like to do with some quests I intend to write is this:

Player finishes quest A, and after doing so, a flag is set to say that the player has accomplished quest A, and stored for later reference.

When asking the same questgiver (or another quest-giver of the same faction) after, the player has a chance of being offered quest B, which is only available if the flag for finishing quest A is set.

So on and so forth for quests C,D,E,and beyond. This would be a way of implementing questlines, which Daggerfall lacked save for the Main Quest, of course. Also, if anything, I can use some of the Main Quests's background quests as a way of storing variables, since they run always in the background.

I'm in no rush to learn or know these more complicated tasks, as I will likely begin with simpler quests anyway just to get my feet wet. I have read through a blog post by Interkarma which discussed how to create custom tasks, and unfortunately, while I understand basic code, it overwhelmed me somewhat, and I'm a slow (but thorough) learner. If there comes a time when references for constructing these kinds of tasks through "pseudo-code", I would be grateful to see and read them.

Don't rush though, again. Making a reference for something like that is quite time-consuming. But either way, thank you for writing this! It's already very helpful in its current state, and it serves as a great starting point for me and others who are starting to make their own quests. :)

Post Reply