Quest Script readable by mere mortals?
-
- Posts: 555
- Joined: Fri Oct 23, 2015 8:19 pm
Quest Script readable by mere mortals?
So... making quests is hard, especially if you're new. Using N0B10Y03 as an example, I've mocked up a new scripting language that I think is a lot more readable and easier to design without mistakes, as it is closer to English syntax. Just a summary of what I did:
-Broke up the script into three separated parts: Definitions, Immediate Tasks, and Triggered Tasks
-Turned triggered tasks into if/then/& indented statements
-took out unnecessary abbreviations to be less cryptic for noobs (pc becomes Player)
-moved spawn intervals/quantities into definitions of enemy variables
-used ##d##h##m format for all duration
Task Objects
---------------------
Chance()
Enemy()
Item()
Message()
Person()
Place()
Affliction()
Player()
Reputation()
Time
Timer()
Object States (the "is" prefix is not required, but helps readability in "If" statements):
--------------------------
Chance can be isA, isB, isC, etc
Enemy can be isHit, isDead, isAlive or have #of in front of it to indicate quantity
Item can be isTaken or isDropped
Time can be is##:## for a time, is##:##to##:## for a timeframe, or is##:##or##:## for multiple periods of time
Timer can be isRunning, isStopped or isExpired
Person can be isClicked or isUnclicked
Player can be isHit, isInside, or isOutside
Actions
-----------
Calculate (Followed by Chance)
Create (Followed by Item)
Define (Followed by things that need preset before quest starts, can only appear under definitions)
Remove (Followed by Player Item or Player Affliction)
Drop (Followed by Player Item)
End (Followed by nothing, ends the quest)
Give (Followed by Player Item or Player Reputation or Player Affliction)
Hide (Followed by Person)
Log (Followed by Message)
Make (Followed by anything that has a changeable adjective status)
Reveal (followed by Place)
Say (Followed by Message)
Show (Followed by Person)
Spawn (followed by Enemy)
Start (Followed by Timer)
Stop (Followed by Timer)
Tag (followed by Item)
Unspawn (followed by Enemy)
Untag (followed by Item)
I'm sure I've made some oversights. I have no idea what the "create npc at mages guild" part does in the original, or the clearclick stuff, or the variables s.10 and s.13 that have no contents... thoughts?
edit: revising words
-Broke up the script into three separated parts: Definitions, Immediate Tasks, and Triggered Tasks
-Turned triggered tasks into if/then/& indented statements
-took out unnecessary abbreviations to be less cryptic for noobs (pc becomes Player)
-moved spawn intervals/quantities into definitions of enemy variables
-used ##d##h##m format for all duration
Task Objects
---------------------
Chance()
Enemy()
Item()
Message()
Person()
Place()
Affliction()
Player()
Reputation()
Time
Timer()
Object States (the "is" prefix is not required, but helps readability in "If" statements):
--------------------------
Chance can be isA, isB, isC, etc
Enemy can be isHit, isDead, isAlive or have #of in front of it to indicate quantity
Item can be isTaken or isDropped
Time can be is##:## for a time, is##:##to##:## for a timeframe, or is##:##or##:## for multiple periods of time
Timer can be isRunning, isStopped or isExpired
Person can be isClicked or isUnclicked
Player can be isHit, isInside, or isOutside
Actions
-----------
Calculate (Followed by Chance)
Create (Followed by Item)
Define (Followed by things that need preset before quest starts, can only appear under definitions)
Remove (Followed by Player Item or Player Affliction)
Drop (Followed by Player Item)
End (Followed by nothing, ends the quest)
Give (Followed by Player Item or Player Reputation or Player Affliction)
Hide (Followed by Person)
Log (Followed by Message)
Make (Followed by anything that has a changeable adjective status)
Reveal (followed by Place)
Say (Followed by Message)
Show (Followed by Person)
Spawn (followed by Enemy)
Start (Followed by Timer)
Stop (Followed by Timer)
Tag (followed by Item)
Unspawn (followed by Enemy)
Untag (followed by Item)
I'm sure I've made some oversights. I have no idea what the "create npc at mages guild" part does in the original, or the clearclick stuff, or the variables s.10 and s.13 that have no contents... thoughts?
edit: revising words
Last edited by BansheeXYZ on Fri Sep 20, 2019 11:14 am, edited 9 times in total.
- Jay_H
- Posts: 4072
- Joined: Tue Aug 25, 2015 1:54 am
- Contact:
Re: Quest Script readable by mere mortals?
If making quests were hard, I could never do it! I'm the least capable of this entire forum, I can almost guarantee it
"place" and "create" are two different concepts for the game. For items it's unnecessary; the game automatically creates all items upon quest start, but doesn't place them in the game world. For people creating was necessary in Daggerfall but unnecessary in DFU; DFU creates people at a chosen spawnpoint for them upon quest start.
"clear" is used to shut down a previously activated action. For example, if I hit an enemy, I can set task _spawn_. If I kill the enemy, I can clear task _spawn_. _spawn_ would create enemies while it's activated, until it's deactivated.
Part of the ease you have is that you've renamed all the variables. Working with endless "S.02" and so on while editing the classic quests was the most Medusa-like of all my tasks; overlooking that, in terms of order you're really just using preference here. I've gotten used to the Template structure so I can parse a quest in very little time. It's a matter of familiarity, is all.
JorisVanEijden has shown that the Template compiler wasn't perfect, but we're working to fix its errors. Some tasks will be blank just for no reason, partially due to things like that and partially because of unfinished content -- like the "love" variable in the Knightly Order witch artifact quest.
"place" and "create" are two different concepts for the game. For items it's unnecessary; the game automatically creates all items upon quest start, but doesn't place them in the game world. For people creating was necessary in Daggerfall but unnecessary in DFU; DFU creates people at a chosen spawnpoint for them upon quest start.
"clear" is used to shut down a previously activated action. For example, if I hit an enemy, I can set task _spawn_. If I kill the enemy, I can clear task _spawn_. _spawn_ would create enemies while it's activated, until it's deactivated.
Part of the ease you have is that you've renamed all the variables. Working with endless "S.02" and so on while editing the classic quests was the most Medusa-like of all my tasks; overlooking that, in terms of order you're really just using preference here. I've gotten used to the Template structure so I can parse a quest in very little time. It's a matter of familiarity, is all.
JorisVanEijden has shown that the Template compiler wasn't perfect, but we're working to fix its errors. Some tasks will be blank just for no reason, partially due to things like that and partially because of unfinished content -- like the "love" variable in the Knightly Order witch artifact quest.
-
- Posts: 555
- Joined: Fri Oct 23, 2015 8:19 pm
Re: Quest Script readable by mere mortals?
You've been at this for a long time and I fear we're not going to get many more people making quests due to how hard it is to decipher the old format. And I mean like REAL new quests, stuff that isn't just an edit or variant of existing ones. Seeing how many errors the original devs made with it and weeding out DFU bugs that have lingered for years makes me think it's just not readable enough for even seasoned programmers. Especially when the quest is somewhat complex and has all these forked paths and conditions.
I think I understood most of this, but I didn't want to use Place at Place. I thought it would be clearer to use Create at Place. Place is one of those weird words that can either be a noun or verb."place" and "create" are two different concepts for the game. For items it's unnecessary; the game automatically creates all items upon quest start, but doesn't place them in the game world. For people creating was necessary in Daggerfall but unnecessary in DFU; DFU creates people at a chosen spawnpoint for them upon quest start.
Yeah, I think this is an oversight on my end not having "Stop" for timers. Spawns in other quests can be infinite. If you hooked up these spawns to an immediate task Timer called _infinite_ that was the duration of the quest, you could just be like"clear" is used to shut down a previously activated action. For example, if I hit an enemy, I can set task _spawn_. If I kill the enemy, I can clear task _spawn_. _spawn_ would create enemies while it's activated, until it's deactivated.
Code: Select all
If Player isInside Place(_house1_) & Timer(_infinite_) isRunning
Then Spawn Enemy(_rats_)
If 7of Enemy(_rats_) isKilled
Then Stop Timer(_infinite_)
Yeah all the unnamed tasks and spawns are nasty. It makes the questfile read like a cipher, where I'm constantly scrolling back to see what S## means and how it's different from S##. Like, why did they call the timers _oneday_ and _S.10_ instead of _dueback_ and _guardshift_? Or _F.00_ instead of _thieves_? Not giving descriptive names makes it harder to reference them later, and you're going to make swap mistakes constantly.Part of the ease you have is that you've renamed all the variables. Working with endless "S.02" and so on while editing the classic quests was the most Medusa-like of all my tasks; overlooking that, in terms of order you're really just using preference here. I've gotten used to the Template structure so I can parse a quest in very little time. It's a matter of familiarity, is all.
- Jay_H
- Posts: 4072
- Joined: Tue Aug 25, 2015 1:54 am
- Contact:
Re: Quest Script readable by mere mortals?
I've been talking to a couple people on Discord who might be interested in writing quests. If they are, perhaps more will join too I was able to fix an error in the quest tutorial yesterday from someone following it strictly; I don't think others had followed the tutorial that far.
If someone were to add an alternate input methods for quests, it would have to be third party. I know for a fact Interkarma won't be doing it
If someone were to add an alternate input methods for quests, it would have to be third party. I know for a fact Interkarma won't be doing it
- JorisVanEijden
- Posts: 114
- Joined: Mon Aug 12, 2019 5:02 pm
Re: Quest Script readable by mere mortals?
See relevant comment in thread viewtopic.php?f=29&t=2628&start=10#p30818
And maybe https://stellargames.github.io/Quester/ for something very close to daggerfall classic.
At the moment any approach would need to be able to both compile to the internal DFU quest object format (see savegame json) and decompile from it to preserve the quests we have now.
The visual node based quest editor would be incredibly user friendly and eady to use but would require a lot of work to build and it would not produce copy & pastable text output to read.
A new language would have to be extremely easy to use and write quests in. As long as all the information is in it we can build a parser /compiler to get it in DFU.
The examples in this thread so far are not that much easier to read/use. They require quite a lot of boilerplate to achieve the things that are possible in the engine.
For a textual format I think we'd need a more coroutine-like syntax to represents the concurrency and the on /off states of "tasks", functions, variables or states.
The level of abstraction is also a concern. You need to find some middle ground between writing and
And maybe https://stellargames.github.io/Quester/ for something very close to daggerfall classic.
At the moment any approach would need to be able to both compile to the internal DFU quest object format (see savegame json) and decompile from it to preserve the quests we have now.
The visual node based quest editor would be incredibly user friendly and eady to use but would require a lot of work to build and it would not produce copy & pastable text output to read.
A new language would have to be extremely easy to use and write quests in. As long as all the information is in it we can build a parser /compiler to get it in DFU.
The examples in this thread so far are not that much easier to read/use. They require quite a lot of boilerplate to achieve the things that are possible in the engine.
For a textual format I think we'd need a more coroutine-like syntax to represents the concurrency and the on /off states of "tasks", functions, variables or states.
The level of abstraction is also a concern. You need to find some middle ground between writing
Code: Select all
if you have the treasure on you outside the guard period you will get arrested.
Code: Select all
12: >> IfItemPickedUp (i_treasure): set s_stoletreasure
13: >> IfItemDroppedAt (i_treasure, l_magesguild): set not s_stoletreasure
14: >> If (s_stoletreasure and not s_midnight3): set s_persecute
15: s_persecute >> StartTimer (t_000da513); When it expires: set s_000da513
16: s_persecute => CreateLogEntry (1011, 1)
17: s_persecute => AdjustReputationWithNpc (n_qgiver, -30)
18: s_persecute => CreateFoe(m_knights, 60, 10%, 5)
19: >> IfMobHurtByPlayer (m_knights): set s_001a98fb [Msg 1013]
-
- Posts: 555
- Joined: Fri Oct 23, 2015 8:19 pm
Re: Quest Script readable by mere mortals?
Thanks for the feedback. How hard would it be for DFU to support both the old and a new scripting method by using headers in the quest file to tell DFU which type it is and writing the new quests to its own "QuestData2.txt"? That would also prevent old quests from needing converted.At the moment any approach would need to be able to both compile to the internal DFU quest object format (see savegame json) and decompile from it to preserve the quests we have now.
Yeah, I'm not sure the interest is there to justify the work involved in something that fancy. Ultimately, the UI for such a program would end up looking like this, procedurally defining, then stringing conditions together to trigger events.The visual node based quest editor would be incredibly user friendly and eady to use but would require a lot of work to build and it would not produce copy & pastable text output to read.
If there was any interest in this, it would be useful to have input from people who don't already have intimate knowledge. It's really hard for programmers or DFU regulars like Jay to pretend to be new again and judge learning curves. So while I value your input, it would be better to throw both methods at some noobs with a bundled tutorial and get opinions on usability.
To clarify some earlier points:
Code: Select all
daily from 00:00 to 03:00
The second biggest struggle is that nothing is labelled. The definitions that come at the start aren't called definitions, they almost look at first like they could be part of the task portion. Clearer lines between what is done prior, at the start of, and during the quest are needed. That too is what I tried to solve by dividing things into those labelled categories.
Code: Select all
injured _F.00_ saying 1012
Also, why do things like "Start Timer X" and "Create Foe X", but then "Say X". That's not consistent, it shouldv'e been "Say Message X"
Code: Select all
create foe _F.01_ every 60 minutes 5 times with 10% success
start timer _S.10_
log 1011 step 1
change repute with _qgiver_ by -30
- JorisVanEijden
- Posts: 114
- Joined: Mon Aug 12, 2019 5:02 pm
Re: Quest Script readable by mere mortals?
Hah, you're telling me.
I stumbled upon this project only three weeks ago. I found the "Template" quest format so bewildering that I wrote a new QBN (daggerfall classic quest format) decompiler just to understand what the quests were trying to accomplish.
You can use any language or script that you can think of to write quests in, as long as they transpile to Template (Tipton's QBN decompiler format that DFU uses) or QBN (which can already be decompiled into Template)
Having multiple parsers in DFU is not going to happen because it makes little sense.
What we might do is decouple the parser from the questmachine a bit more so that other external parsers/compilers that produce serialized Quest objects can also be used.
So for a new/improved quest language your best bets are either writing a "newscript" to Template transpiler or building a "newscript" compiler that outputs serialized DaggerfallWorkshop.Game.Questing.Quest objects.
Once someone is committed to doing that we can discuss the syntax and features this new script language should have.
I stumbled upon this project only three weeks ago. I found the "Template" quest format so bewildering that I wrote a new QBN (daggerfall classic quest format) decompiler just to understand what the quests were trying to accomplish.
You can use any language or script that you can think of to write quests in, as long as they transpile to Template (Tipton's QBN decompiler format that DFU uses) or QBN (which can already be decompiled into Template)
Having multiple parsers in DFU is not going to happen because it makes little sense.
What we might do is decouple the parser from the questmachine a bit more so that other external parsers/compilers that produce serialized Quest objects can also be used.
So for a new/improved quest language your best bets are either writing a "newscript" to Template transpiler or building a "newscript" compiler that outputs serialized DaggerfallWorkshop.Game.Questing.Quest objects.
Once someone is committed to doing that we can discuss the syntax and features this new script language should have.
- TheLacus
- Posts: 1305
- Joined: Wed Sep 14, 2016 6:22 pm
Re: Quest Script readable by mere mortals?
Tasks are live objects; forget the name task if you think is confusing, but this is what they areBansheeXYZ wrote: ↑Sat Sep 07, 2019 7:35 amThis isn't a task per se, it's a condition posing as a task that an actual task references backwardly. Everything in classic is jumbled into the same visual hierarchy and called a "task". To me, this is what makes classic's method so hard to read. The word task connotes action, yet here are all these lines that have no action in them. I'd prefer clearer distinction between conditions and tasks, and what conditions belong to what tasks. That is what new people are going to struggle with the most, and what I believe my example overcomes.Code: Select all
daily from 00:00 to 03:00
A task is composed by one or multiple conditions (that are something like an event that trigger the task, not just an if that is checked once) and actions (the actual executive part). See this example from Jay
Code: Select all
_day_ task:
daily from 5:00 to 20:00
hide npc _friend_
_night_ task:
when not _day_
restore _friend_
Code: Select all
when (from 5:00 to 20:00)
hide npc _friend_
else
restore _friend_
Code: Select all
pick one of _dud_ _dud_ _dud_ _dud_ _alt_
pc at _mondung_ set _inside_
_daytime_ task:
daily from 8:00 to 18:00
_slain_ task:
killed 1 _enemy_ saying 1020
variable _dud_
_altspawn_ task:
when _inside_ and _alt_
start task _altinform_
_spawn_ task:
when _inside_ and _daytime_ and not _slain_ and not _alt_
create foe _wild1_ every 14 minutes indefinitely with 45% success
create foe _wild2_ every 15 minutes indefinitely with 36% success
create foe _wild3_ every 17 minutes indefinitely with 34% success
create foe _wild4_ every 22 minutes indefinitely with 22% success
There are many conditions that allow an optional saying id for convenience. If you don't like it you can split the condition and the action:BansheeXYZ wrote: ↑Sat Sep 07, 2019 7:35 amThis is pretty much a condition and task rolled into one, with awkward tensing (present participle) for the task (say) that doesn't match the tenses of other commands.Code: Select all
injured _F.00_ saying 1012
Also, why do things like "Start Timer X" and "Create Foe X", but then "Say X". That's not consistent, it shouldv'e been "Say Message X"
Code: Select all
injured _F.00_
say 1012
What exactly do you want to move to the definitions section? I don't think moving create foe is a good idea; what if a new action is introduced to deal with enemy spawn in a different way? Personally i see definitions as constructors for resources' symbols, i don't expect them to define data that is only useful for a specific action.BansheeXYZ wrote: ↑Sat Sep 07, 2019 7:35 amThese look much better, but we're getting lengthy definitions for spawns and penalties when we already had a place to do that at the start of the QBN. So why muddy up the task area with it when the task area is the hardest to read when looking for errors in your work?Code: Select all
create foe _F.01_ every 60 minutes 5 times with 10% success start timer _S.10_ log 1011 step 1 change repute with _qgiver_ by -30
-
- Posts: 555
- Joined: Fri Oct 23, 2015 8:19 pm
Re: Quest Script readable by mere mortals?
Sorry for the late reply, had stuff to do today.
It looks like I simply need to add Chance as a defined object and then use it in the conditions. Also, I used "??d??h??m as letting the engine set the timer (based on dungeon distance). Here's Jay's quest in my format. Please poke holes in it if you can:
Correct me if I'm wrong, but this seems to expose a shortcoming in classic, which is that making small chances (like a 1% chance to drop a rare item) is not feasible, because you'd have to write in 100 tasks. I mean, you could do that, but it would look ridiculous.
I see what you mean. The "pick one" command looks like it's mostly used as a mild randomizer to make quests less repetitive and fork you into different events. The sick cousin quest is another one I looked at doing this: you have a 50% chance of contracting swamp rot when talking to the cousin. Jay is using this to give a 20% chance to spawn no hermit minions and fire a message.The issue is that you lose the ability to reference tasks; How would you change something more advanced like this one?
It looks like I simply need to add Chance as a defined object and then use it in the conditions. Also, I used "??d??h??m as letting the engine set the timer (based on dungeon distance). Here's Jay's quest in my format. Please poke holes in it if you can:
Code: Select all
Definitions:
Define Item(_alch_) as small.plant
Define Person(_questgiver_) as male.questor
Define Place(_dungeon_) as remote.dungeon
Define Timer(_dueback_) as ??d??h??m
Define Enemy(_hermit_) as healer
Define Enemy(_grizzlybears_) as grizzly_bear every 00d00h14m indefinitely with 45% success
Define Enemy(_giantbats_) as giant_bat every 00d00h15m indefinitely with 36% success
Define Enemy(_giantrats_) as giant_rat every 00d00h17m indefinitely with 34% success
Define Enemy(_spriggans_) as spriggan every 00d00h22m indefinitely with 22% success
Define Chance(_minions_) as 20% A 80% B
Immediate Tasks:
Start Timer(_dueback_)
Log Message(1030)
Calculate Chance(_minions_)
Create Enemy(_hermit_) at Place(_dungeon_)
Reveal Place(_dungeon_)
Triggered Tasks:
If Player isInside Place(_dungeon_) & Chance(_minions_) isA
Then Say Message(1024)
If Player isInside Place(_dungeon_) & Chance(_minions_) isB & Enemy(_hermit_) isAlive & Time is08:00to18:00
Then Spawn Enemy(_grizzlybears_) & Spawn Enemy(_giantbats_) & Spawn Enemy(_giantrats_) & Spawn Enemy(_spriggans_)
If Enemy(_hermit_) isDead
Then Say Message(1020)
If Person(_questgiver_) isClicked & Enemy(_hermit_) isDead
Then Say Message(1004) & End
If Timer(_dueback_) isExpired
Then Say Message(1003) & End
Last edited by BansheeXYZ on Mon Sep 09, 2019 2:37 am, edited 1 time in total.
- Jay_H
- Posts: 4072
- Joined: Tue Aug 25, 2015 1:54 am
- Contact:
Re: Quest Script readable by mere mortals?
Eh, sort of. You can cascade it using multiple events:
10% * 10% should be 1%, if I have my statistics properly understood.
Code: Select all
_hundredpercent_ task:
pick one of _tenpercent_ _fail_ _fail_ _fail_ _fail_ _fail_ _fail_ _fail_ _fail_ _fail_
Code: Select all
_tenpercent_ task:
pick one of _onepercent_ _fail_ _fail_ _fail_ _fail_ _fail_ _fail_ _fail_ _fail_ _fail_
Code: Select all
_onepercent_ task:
give pc _reward_
Code: Select all
variable _fail_