[Game Design] Dynamic Quests
I could just not stop myself from extending that idea that I roughly sketched in the previous article.
First - imagine that we have several classes that describe quest(s) "statically" - so how this is presented in Quest Editor.
Therefore we have some Scenario object that is being processed by responsible ScenarioManager class. During the game that manager class founds that some activation (like this is stored in scenario - dialog option, item picked, area entered or whatever) of a new quest happened. It then sends identification number of that quest and pointer to the activator to contained class of QuestsManager to take care of the rest.
Now QM checks the type of the quest and expected result from manager (In many cases it might be specific branch of dialogue, message to be displayed, or player's log entry or all at once. Anyway QM shall also check if quest that is not generic has not been already finished or started - internal integrity check that shall be found during QA process but also left in runtime - especially if game provides custom content. So far it goes normal. Generic quests is a matter for another entry so let us just consider that we just found a pure new dynamic quest started by picking cursed Sword of Slow Death...
In the script we have written that the curse cannot be taken away by normal methods or dispells but requires special treatment. In the quest the item is not dynamic, only the temple and person-to-talk are picked randomly. While there is no direct entry dialogue we must consider serious modification of game world for that quest. So there is no specific result returned to ScenarioManager.
QM orders QuestFactory class to build runtime data for given quest. QF loads static data and first of all creates array of variables that might be used during it initialization script. In our case there is going to happen quite much in that script - first of all it shall check if the activator is really that sword and if the curse flag remains in place. Therefore we are sure that default behaviors of the cursed possessions apply. We then check if expected special script is attached to the Sword's OnSpellCast() event. Last thing is attaching to the player curse script - that is also not a general one.
Curse script is gradually reducing player's endurance (just reducing, any curing spells or potions restore it) and each game hour it displays message - "You feel weaker..." when that happens. Much more interesting is script that is being executed each time the spell is being cast upon the sword. In just allows spells work normally until they have effects of dispell, uncurse, disintegrate, identification or similar (no exact spells, that might be created after quest script but particular effects that we are interested in).
When player tries to remove curse (dispell, uncurse) we return 100% resistance / failure and for FIRST such spell effect we register additional dialog branches for ANY priest or mage in the world (yep, game engine shall support that of course). For desintegrate spell there is 95% chance of failure and same registration of dialogs. If desegregation succeeds - quest and curse are just being removed, but not curse effects that shall be cured normally. For identification spells we display to the player some extra box with message "There is so strong curse on that Sword, that only highly qualified Priest or Mage can know more about it" and then allow standard identification dialog to appear with curse details (or not? SilentFail status? up to the Designer)
Then player have to visit any Mage or Priest and go that dialog branch for the cursed sword. He might be luck enough to ask person proficient enough at first try - then we just jump to the next stage. If not - there is first dynamic modification - we pick accordingly to the interlocutor 15 lvl Mage or Priest he might suggest to us (if our game has living world it may also be that he shall also KNOW that person exists - the he would only point us higher) to talk to. We shall have vast amount of options to define how this randomization shall happen - like localization, availability, if that location was already visited or do we have current entry permission there or just ignore all such obstacles and let the player find solution (by generic quests). We choose them all in Quest Editor and feed randomization engine. If there is NO location or person - engine shall be capable of creating one in game environment dynamically - and such change shall be therefore constant (well, until someone kills the mage...).
Anyway, quest factory then executes ScenarioManager.RegisterKeyElements() with the mage himself and his location. Therefore player will be warned when he tries to lock / kill / destroy object or person that he has quest(s) attached to. Of course player (or in living game world - other NPC or monster) could kill such person and not just being faced immortal character. If so - the quest shall generate a WORKAROUND branch - returning to last mage or other temple and ask again, or finding some scroll with the mage that informs about his master, or whatever. Such things shall be possible to hardcode in quest's script or generated by engine accordingly to similar rules used to generate dynamic branch. For example we shall decide if we just want replacement for current step or way that lead us back to previous one to try again. In our situation it shall be some cynical comment told by avatar - "Well, I just guess that I shall look for another mage..." and reset script few steps back.
For more details - we can then join such us dynamic quest with generic quests - for example our mage knows resolution for our problem and can do that for free but or requires some favor (service or possibly a "kiss" from female character) or component that is required to fulfill the spell. Anything - we can define strict steps in the quest or just define criteria for generic quest generation and wait with baseline until finished.
Let us then imagine that we bring that compound and find mage dead. In traditional scripting (and storyline) it would be intentional event with ready solution - player is supposed to find some track, person or anything that would lead him deeper into the story. This leads dynamic design much more complicated - we shall create workaround that will not interfere with already done parts and will not generate possible infinite loop (let us just imagine that some NPC just gone on his rampage and murders all high level mages...). Probably best solution would be to find yet another mage and continue from there. But how to prevent another generic quest from him? If previous quest was a simple service we might just pay him for casting spell and quest ends. But if we were supposed to find some compound required for spell this gets to be a little harder...
Game shall track therefore all such generic subquest progresses and have additional dialogues to make it smooth - like we just telling new wizard that we have required spell component. This also leads to another idea - that all generic subquests shall be prepared with special care for failures, additional dialogue lines - preferably also with randomization and variatization. A lot of work, carefull and complicated work. But final results might be astonishing!
We can also define maximum or default amount of generic subquests or default quest lines if generic fail... A lot things to consider by the designers.
In the end, after quest finishes all remaining scripts, dialog options that are still bounded to the quest are removed (so after curse is taken out of the sword, except taking out curse spell effect from itself and player, curse flag and scripts there also shall be removed quest id from the sword - all shall be considered by QuestEnds() script). This not touch log /diary entries - these are just moved to "Finished" branch. Therefore old data is no longer wasting place in save and memory.
All KeyElement flags are also removed from objects.
I hope that this article would help one build his own great questing system :)
First - imagine that we have several classes that describe quest(s) "statically" - so how this is presented in Quest Editor.
Therefore we have some Scenario object that is being processed by responsible ScenarioManager class. During the game that manager class founds that some activation (like this is stored in scenario - dialog option, item picked, area entered or whatever) of a new quest happened. It then sends identification number of that quest and pointer to the activator to contained class of QuestsManager to take care of the rest.
Now QM checks the type of the quest and expected result from manager (In many cases it might be specific branch of dialogue, message to be displayed, or player's log entry or all at once. Anyway QM shall also check if quest that is not generic has not been already finished or started - internal integrity check that shall be found during QA process but also left in runtime - especially if game provides custom content. So far it goes normal. Generic quests is a matter for another entry so let us just consider that we just found a pure new dynamic quest started by picking cursed Sword of Slow Death...
In the script we have written that the curse cannot be taken away by normal methods or dispells but requires special treatment. In the quest the item is not dynamic, only the temple and person-to-talk are picked randomly. While there is no direct entry dialogue we must consider serious modification of game world for that quest. So there is no specific result returned to ScenarioManager.
QM orders QuestFactory class to build runtime data for given quest. QF loads static data and first of all creates array of variables that might be used during it initialization script. In our case there is going to happen quite much in that script - first of all it shall check if the activator is really that sword and if the curse flag remains in place. Therefore we are sure that default behaviors of the cursed possessions apply. We then check if expected special script is attached to the Sword's OnSpellCast() event. Last thing is attaching to the player curse script - that is also not a general one.
Curse script is gradually reducing player's endurance (just reducing, any curing spells or potions restore it) and each game hour it displays message - "You feel weaker..." when that happens. Much more interesting is script that is being executed each time the spell is being cast upon the sword. In just allows spells work normally until they have effects of dispell, uncurse, disintegrate, identification or similar (no exact spells, that might be created after quest script but particular effects that we are interested in).
When player tries to remove curse (dispell, uncurse) we return 100% resistance / failure and for FIRST such spell effect we register additional dialog branches for ANY priest or mage in the world (yep, game engine shall support that of course). For desintegrate spell there is 95% chance of failure and same registration of dialogs. If desegregation succeeds - quest and curse are just being removed, but not curse effects that shall be cured normally. For identification spells we display to the player some extra box with message "There is so strong curse on that Sword, that only highly qualified Priest or Mage can know more about it" and then allow standard identification dialog to appear with curse details (or not? SilentFail status? up to the Designer)
Then player have to visit any Mage or Priest and go that dialog branch for the cursed sword. He might be luck enough to ask person proficient enough at first try - then we just jump to the next stage. If not - there is first dynamic modification - we pick accordingly to the interlocutor 15 lvl Mage or Priest he might suggest to us (if our game has living world it may also be that he shall also KNOW that person exists - the he would only point us higher) to talk to. We shall have vast amount of options to define how this randomization shall happen - like localization, availability, if that location was already visited or do we have current entry permission there or just ignore all such obstacles and let the player find solution (by generic quests). We choose them all in Quest Editor and feed randomization engine. If there is NO location or person - engine shall be capable of creating one in game environment dynamically - and such change shall be therefore constant (well, until someone kills the mage...).
Anyway, quest factory then executes ScenarioManager.RegisterKeyElements() with the mage himself and his location. Therefore player will be warned when he tries to lock / kill / destroy object or person that he has quest(s) attached to. Of course player (or in living game world - other NPC or monster) could kill such person and not just being faced immortal character. If so - the quest shall generate a WORKAROUND branch - returning to last mage or other temple and ask again, or finding some scroll with the mage that informs about his master, or whatever. Such things shall be possible to hardcode in quest's script or generated by engine accordingly to similar rules used to generate dynamic branch. For example we shall decide if we just want replacement for current step or way that lead us back to previous one to try again. In our situation it shall be some cynical comment told by avatar - "Well, I just guess that I shall look for another mage..." and reset script few steps back.
For more details - we can then join such us dynamic quest with generic quests - for example our mage knows resolution for our problem and can do that for free but or requires some favor (service or possibly a "kiss" from female character) or component that is required to fulfill the spell. Anything - we can define strict steps in the quest or just define criteria for generic quest generation and wait with baseline until finished.
Let us then imagine that we bring that compound and find mage dead. In traditional scripting (and storyline) it would be intentional event with ready solution - player is supposed to find some track, person or anything that would lead him deeper into the story. This leads dynamic design much more complicated - we shall create workaround that will not interfere with already done parts and will not generate possible infinite loop (let us just imagine that some NPC just gone on his rampage and murders all high level mages...). Probably best solution would be to find yet another mage and continue from there. But how to prevent another generic quest from him? If previous quest was a simple service we might just pay him for casting spell and quest ends. But if we were supposed to find some compound required for spell this gets to be a little harder...
Game shall track therefore all such generic subquest progresses and have additional dialogues to make it smooth - like we just telling new wizard that we have required spell component. This also leads to another idea - that all generic subquests shall be prepared with special care for failures, additional dialogue lines - preferably also with randomization and variatization. A lot of work, carefull and complicated work. But final results might be astonishing!
We can also define maximum or default amount of generic subquests or default quest lines if generic fail... A lot things to consider by the designers.
In the end, after quest finishes all remaining scripts, dialog options that are still bounded to the quest are removed (so after curse is taken out of the sword, except taking out curse spell effect from itself and player, curse flag and scripts there also shall be removed quest id from the sword - all shall be considered by QuestEnds() script). This not touch log /diary entries - these are just moved to "Finished" branch. Therefore old data is no longer wasting place in save and memory.
All KeyElement flags are also removed from objects.
I hope that this article would help one build his own great questing system :)
Komentarze (0):
Prześlij komentarz
Subskrybuj Komentarze do posta [Atom]
<< Strona główna