Skip to main content

News

Topic: Easier quest creation? (Read 10575 times) previous topic - next topic

0 Members and 1 Guest are viewing this topic.
Easier quest creation?
Hey everyone, as I've been working on creating quests and miniquests in my game, I've come across what may be an issue, especially on the really large, and complicated quests I have in mind to create.

Currently, my quest creation system is basically the same as what was on the old Sphere tutorial for 'Creating RPG Events', or however it was titled. It uses a case system to progress through different quests. In example, this is just a snippet from the small side quest (an investigation quest) that I'm testing in the game:

Code: (javascript) [Select]

function bradleyTalk()
{
  switch (monkeyingAround)
  {
    case 0:
      Text("Chef: \n\n\tWelcome to Bradley's Bakery! I'm Bradley. Can I help you? \n\nYou: \n\n\tMmm, what are you cooking? It smells excellent. \n\nBradley: \n\n\tI'm baking a special recipe of mine - ring cake. See, it's basically just dough and honey, and I make it in a circular shape, like a ring! They are excellent with milk. \n\nYou: \n\n\tSounds great, I'll have to remember that! Good-bye. \n\nBradley: \n\n\tCome again soon!");
    break;

    case 1:
      Text("Bradley: \n\n\tHello, sir! How may I help you today? \n\nYou: \n\n\tHello, I'm investigating a series of thefts from the marketplace. Is there any information you can provide to me about the issue? \n\nBradley: \n\n\tHmm, come to think of it, I HAVE been missing several sacks of flour recently. Not sure what thief would take something as cheap as flour. \n\nYou: \n\n\tThanks, I'll work from that.");
    ++monkeyingAround;
    break;

... and so forth


So basically, I was wondering if there is a method of creating quests with Sphere that would be less jumbled, less confusing, less time-consuming, and less easy to mess up. Would it be possible to create a basic quest creation tool for Sphere, perhaps for future updates, that uses a case-based system?
  • Last Edit: July 06, 2013, 07:54:10 am by DaVince
"If you don't build your dreams, someone else will hire you to build theirs."

Challenging Apathy | Follow me on Twitter!

  • DaVince
  • [*][*][*][*][*]
  • Administrator
  • Used Sphere for, like, half my life
Re: Easier quest creation?
Reply #1
That would be nice. In script, something like this could do the trick:

Code: (javascript) [Select]
var monkeyingAround = new Quest("Monkeying around");

monkeyingAround.add(1, "Bradley", function()
{
  Text("Bradley: \n\n\tHello, sir! How may I help you today? \n\nYou: \n\n\tHello, I'm investigating a series of thefts from the marketplace. Is there any information you can provide to me about the issue? \n\nBradley: \n\n\tHmm, come to think of it, I HAVE been missing several sacks of flour recently. Not sure what thief would take something as cheap as flour. \n\nYou: \n\n\tThanks, I'll work from that.");
  this.update();  //Update quest progress
});

monkeyingAround.add(1, "Steve", function()
{
  Text("Steve: \n\n\tHuh? I know nothing...");
});

And then bind these conversation bits to characters on map entry, or something. Well, it's just a quickly thought up idea.
  • Last Edit: July 06, 2013, 07:53:52 am by DaVince

Re: Easier quest creation?
Reply #2
Basically you're doing some sort of ad-hoc state machine. I suggest switching to a real FSM (such as this, although you might have to modify that to get it to work in Sphere). Writing your own wouldn't be too hard either. It could be very simple, such as DaVince's example above (which is effectively an FSM in disguise) or more complicated to handle interleaved quests and stuff.
The switch way gets complicated very fast (I've worked on a project that uses a 200+ line switch statement as a sort of ad-hoc FSM...), not to mention is harder to read. A real state machine will be both more flexible and easier to manage.

  • DaVince
  • [*][*][*][*][*]
  • Administrator
  • Used Sphere for, like, half my life
Re: Easier quest creation?
Reply #3
alpha123, that Github project you linked to seems pretty useful. I'm new to state machines, so I'm wondering if I'm understanding it properly. Is this is how a snippet of the example works?

Code: (javascript) [Select]

fsm.addTransition(
  "Press Power Button", //Name of this transition
  "Power Off", //Condition: if it's in the "Power Off" state
  function() { this.sendEvent("Press Reset Button"); },  //Transition to "Reset" state
  "Power On"  //Set state to "Power On"
);


Now I'm trying to imagine how that would work with quests and in-game events... This seems pretty powerful, in any case.

Re: Easier quest creation?
Reply #4
That's correct DaVince (although I don't know this particular library very well, the source is clear enough on this point). Here's how I think it would look for quests:

Code: (javascript) [Select]
var quest = new FSM("start");
quest.addTransition("1", "start", function () { Text("stuff...."); }, "2");
quest.addTransition("2", "1", function () { Text("more stuff..."); }, "3");
// and so on
// Then in someone's talk script
if (quest.currentState == "start") // if we haven't started this quest yet
    quest.sendEvent("1");
else
    quest.sendEvent(quest.currentState); // I'm not 100% sure this will do what we want, but I think so

  • DaVince
  • [*][*][*][*][*]
  • Administrator
  • Used Sphere for, like, half my life
Re: Easier quest creation?
Reply #5
Code: (javascript) [Select]
else
    quest.sendEvent(quest.currentState); // I'm not 100% sure this will do what we want, but I think so

It seems to me like it would then run the state "start", which isn't defined to have behaviour. Think the else would even be needed if the quest wasn't in the right state to begin with? Or are you thinking along the lines of defining that to invoke some default non-quest character line?

Re: Easier quest creation?
Reply #6

It seems to me like it would then run the state "start", which isn't defined to have behaviour. Think the else would even be needed if the quest wasn't in the right state to begin with? Or are you thinking along the lines of defining that to invoke some default non-quest character line?

I don't think so, since we transition into "1" if we're at "start". But maybe you're right; the library turned out to be more complicated than it first looked. :(

  • DaVince
  • [*][*][*][*][*]
  • Administrator
  • Used Sphere for, like, half my life
Re: Easier quest creation?
Reply #7
Oops, no, you're right. I was thinking in the perspective of another game character who might not say anything about the quest when it hasn't started, but contributes to it when it's in a specific state. But your code was intended for the quest giver, I suppose.

  • Radnen
  • [*][*][*][*][*]
  • Senior Staff
  • Wise Warrior
Re: Easier quest creation?
Reply #8
In blockman I've used an approach similar to persist/analogue:

Here's the data file,
Code: (javascript) [Select]

// Jomba Seeds quest-line

({
count: 0,
amount: 6,
name: "Jomba Seeds",

/* start: function() {}, */

desc: function() {
switch (this.state) {
                        // initial state:
case 0: return "My friend needs me for something.";

                        // progress states:
case 1: return "He wants me to collect jomba seeds, but I am still undecided.";
case 2: return "I have collected " + this.count + " seeds.";
case 3: return "I have collected enough seeds for my friend.";

                        // termination states:
case 4: return "I have finished this task.";
case 5: return "I turned down my friends offer.";
}
},

finish: function() {
Game.player.addExp(75);
}
})


And here's an entity interacting with said datafile:
Code: (javascript) [Select]

switch (questsys.quest("jomba").state) {
case 1:
Textbox.showText("Granfurt", "Great! What are friends for?");
Textbox.showText("Granfurt", "So, this morning I was cleaning the basement for a party I'll be having and...");
Textbox.showText("Granfurt", "I realized I ran out of *Jomba Seeds* for the feast...");
Textbox.showText("Granfurt", "I need you to find some for me.");
Textbox.showText("player", "I'm busy, why don't you do it yourself?");
Textbox.showText("Granfurt", "But you have clearance to leave, right?");
Textbox.showText("player", "Yes I do, though there's a reason as to why this place is walled in!");
Textbox.showText("Granfurt", "That was only because of the Mutagen Wars. I'm sure you'll be fine.");
switch (Textbox.showChoices("Fine. I'll do it.", "Go do it yourself.")) {
case 0:
Textbox.showText("Granfurt", "Thanks!");
questsys.quest("jomba").state = 2;
break;
case 1:
Textbox.showText("Granfurt", "Arg! I guess I'll have to ask someone else.");
questsys.quest("jomba").state = 5;
questsys.finish("jomba");
break;
}
break;
case 2: Textbox.showText("Granfurt", "Hmm, I need more seeds than that."); break;
case 3:
Textbox.showText("Granfurt", "It looks like you have all the seeds!");
Textbox.showText("player", "Yeah well it was a pain. Here you go.");
Textbox.showText("Granfurt", "Thanks for doing that for me!");
questsys.quest("jomba").state = 4;
questsys.finish("jomba");
break;
case 4: Textbox.showText("Granfurt", "Thanks for doing that for me!"); break;
case 5: Textbox.showText("Granfurt", "Well, I guess I don't get my seeds then."); break;
}


I wanted it to be lightweight, and have it so that all entities who interact with the data file basically check the state of the quest and react to that state. I wish there wasn't something so cryptic: what does state 3 mean? You can read the description you wrote for each state in the 'desc' callback, but they must all have a definition there. Anyways, Skyrim does something very similar to this in the background and some of their bugs do show it.

The only last solution is to put the entity dialogue right inside of the quest itself, but that just moves the code from one place to another.

My goal with this was to also create a framework that was subsequently easy to save and load, took less typing to update and receive quests with a lightweight api, and made sure to do things like stumbling upon a quest you hadn't started and keep those variables around until you do find the quest giver (or you put it into a state that just says, for example: "Somebody in this town lost their doll").

As with all state machines you must be thorough for each entity that interacts with the quest : How does it start? End? What's it like in progress? What if they stumble upon it without starting it? Can they do that?
  • Last Edit: July 06, 2013, 03:34:26 pm by Radnen
If you use code to help you code you can use less code to code. Also, I have approximate knowledge of many things.

Sphere-sfml here
Sphere Studio editor here

Re: Easier quest creation?
Reply #9
Sorry it took me so long to respond, I've been busy in my life outside of my game development, I nearly forgot about this topic. :P

Anyways, this quest idea is an experiment in a more complex case-based system. A simpler, straight-forward (mini)quest in my game is like this:

Code: (javascript) [Select]
 var fireLily = 0;

function priestFL()
{
   switch (fireLily)
   {
      case 0:
         Text("Priest: \n\n\tOh, Ilxder! I was looking for you earlier. You see, my first anniversary with my wife is tomorrow, and I'd like to get her something nice. Do you have some time to spare? \n\nYou: \n\n\tSure, what do you need? \n\nPriest: \n\n\tCould you get a Fire Lily for me? They're a rare flower that only grows on this island. You can find some by walking east along the North Shore. \n\nYou: \n\n\tAlright.");
      ++fireLily;
      break;

      case 1:
         Text("Priest: \n\n\tHave you acquired a fire lily yet? \n\nYou: \n\n\tNo, not yet. \n\nPriest: \n\n\tPlease, do hurry! I wish to have that flower for tomorrow.");
      break;
 
      case 2:
         Text("Priest: \n\n\tHave you acquired a fire lily yet? \n\nYou: \n\n\tYes, I did. Here it is! \n\n*You hand the flower to the priest* \n\nPriest: \n\n\tThank you so much, Ilxder! Now I can give my wife something really nice - the priestly salary isn't very large, as you can well imagine. \n\n*You have completed the quest: 'Fire Lillies'*");
      ++fireLily;
      break;

      case 3:
         Text("Priest: \n\n\tThank you for obtaining that flower for me! Can I do anything else for you? \n\nYou: \n\n\tNo, thank you. \n\nPriest: \n\n\tAlright. Let me know if you need anything more. \n\nYou: \n\n\tI will. Good day! \n\nPriest: \n\n\tTylbur's blessings. \n\n\t* You have completed the Fire Lily side quest *");
      break;
   }
}

function fireLilyGather()
{
   switch (fireLily)
   {
      case 0:
         Text("~ These flowers are beautiful, but I can't think of anything I might need them for ~");
      break;
 
      case 1:
         Text("*You pick the flower*");
      ++fireLily;
      break;

      case 2:
         Text("~ I already picked the flower for the priest. I have no other need for them. ~");
      break;

      case 3:
         Text("~ I already picked the flower for the priest. I have no other need for them. ~");
      break;
   }
}


Whereas, the full quest script for this 'Monkeying Around' is a more complex system, where I have to lay out the plan and which character has new information and during which specific case they HAVE the new info. This is easy to lay out in my head, but it can be easily messed up (and has been) while I've been designing this little quest that appears during the tutorial. Here's the full script that I have so far:

Code: (javascript) [Select]
var monkeyingAround = 0;


function captainTalk()
{
   switch (monkeyingAround)
   {
      case 0:
         Text("Captain:\n\n\tPlease address all of your concerns to the office of the.. oh, Ilxder sir! I've been getting too many complaints from the merchants in the market about some sort of thief taking all of their products. So what can I do for you, sir? \n\nYou: \n\n\tI could look into that issue for you. \n\nCaptain: \n\n\tWould you? I'd definitely be grateful. \n\nYou: \n\n\tSure! \n\n* This is an investigation-style quest. You will encounter many of these quests throughout your time on Steorth. *");
      ++monkeyingAround;
      break;
     
      case 1:
         Text("Captain:\n\n\tDid you look into the thefts from the marketplace yet? \n\nYou: \n\n\tNo, not yet. \n\nCaptain: \n\n\tPlease try to look into the issue as soon as possible.");
      break;
     
      case 2:
         Text("Captain:\n\n\tDid you look into the thefts yet? \n\nYou: \n\n\tI'm currently gathering info from the merchants on the thefts. \n\nCaptain: \n\n\tVery good. Please report back to me if you find anything out.");
      break;
     
      case 3:
         Text("Captain:\n\n\tAnything new? \n\nYou: \n\n\tYes. I spoke to some of the merchants, and from what I gather, it's not a human thief, but an animal. \n\nCaptain: \n\n\tAn animal, you say? Hmm, strange. Anything else? \n\nYou: \n\n\tI'm afraid not, yet.");
     
      case 4:  
         Text("Captain: \n\n\tFind anything more about the thefts yet? \n\nYou: \n\n\tYes, I have. It turns out that a little monkey, presumably from the eastern end of the Island, has been sneaking into the buildings around the square and apparently stealing the merchandise. \n\nCaptain: \n\n\tA monkey? That's odd, they usually keep to themselves. Well done, thanks for the help. We'll keep an eye out for the little critters in the future. \n\n* You've completed 'Monkeying Around'! *");
      ++monkeyingAround;
      break;
     
      case 5:
         Text("Captain: \n\n\tThank you for investigating that issue for me. \n\nYou: \n\n\tNo trouble at all, Captain. I always like to see things going well on Halmuna. \n\nCaptain: \n\n\tIndeed. I'll be sure to call for you if I need help in the future!");
   }
}
     
function bradleyTalk()
{
   switch (monkeyingAround)
   {
      case 0:
         Text("Chef: \n\n\tWelcome to Bradley's Bakery! I'm Bradley. Can I help you? \n\nYou: \n\n\tMmm, what are you cooking? It smells excellent. \n\nBradley: \n\n\tI'm baking a special recipe of mine - ring cake. See, it's basically just dough and honey, and I make it in a circular shape, like a ring! They are excellent with milk. \n\nYou: \n\n\tSounds great, I'll have to remember that! Good-bye. \n\nBradley: \n\n\tCome again soon!");
      break;
        
      case 1:
         Text("Bradley: \n\n\tHello, sir! How may I help you today? \n\nYou: \n\n\tHello, I'm investigating a series of thefts from the marketplace. Is there any information you can provide to me about the issue? \n\nBradley: \n\n\tHmm, come to think of it, I HAVE been missing several sacks of flour recently. Not sure what thief would take something as cheap as flour. \n\nYou: \n\n\tThanks, I'll work from that.");
      ++monkeyingAround;
      break;
        
      case 2:
         Text("Bradley: \n\n\tDid you find any more evidence concerning the thefts yet? \n\nYou: \n\n\tNo, not yet. If you find any new evidence, please let either myself or the Captain of the Guard know. \n\nBradley: \n\n\tCan-do, sir!");
      break;
     
      case 3:
         Text("Bradley: \n\n\tDid you find out any more about the thefts? \n\nYou: \n\n\tYes, but not much. It's not a human that's stealing the merchandise, but a creature, reportedly with a small tail. \n\nBradley: \n\n\tThat is really strange. I'll be sure to keep an eye out for anything suspicious! \n\nYou: \n\n\tThank you, Bradley.");
        
      case 4:  
         Text("Bradley: \n\n\tDid you find any more evidence concerning the thefts yet? \n\nYou: \n\n\tYes, actually, I did. It turns out a monkey has been stealing things from the merchants in the marketplace. \n\nBradley: \n\n\tA monkey? I thought they didn't come this far west. \n\nYou: \n\n\tI thought the same, but apparently that's not the case. Perhaps one of the local residents owns one as a pet.");
      break;
        
      case 5:
         Text("Bradley: \n\n\tWelcome back, sir! \n\nYou: \n\n\tHello, Bradley. Had any more run-ins with monkeys recently? \n\nBradley: \n\n\tNo, everything has been really quiet recently. Thanks for asking though!");
      break;
   }
}

function richardTalk()
{
   switch (monkeyingAround)
   {
      case 0:
         Text("Richard: \n\n\tHello, good sir, and welcome to Richard's Research Lab! Is there anything I can help you with? \n\nYou: \n\n\tNot at the minute, but thank you very much, Richard! \n\nRichard: \n\n\tAlright, feel free to speak to me if you change your mind.");
      break;
     
      case 1:
         Text("You: \n\n\tRichard, have you seen any suspicious activity recently? I'm investigating a theft issue that has been plaguing the marketplace. \n\nRichard: \n\n\tNo, I can't say I have, sir. \n\nYou: \n\n\tVery well. If you see anything suspicious, please report it to either myself, or the Captain of the Guard.");
      break;
     
      case 2:
         Text("You: \n\n\tHave you seen anything suspicious recently, Richard? \n\nRichard: \n\n\tYes, sir, I caught only a glimpse of what appeared to be a small tail on a creature as it left my store. \n\nYou: \n\n\tA tail... hmm, I'll speak to the other people around here and see if they have any possible ideas of what this could be about.");
      ++monkeyingAround;
      break;
     
      case 3:
         Text("Richard: \n\n\tDid you happen to find anything yet? \n\nYou: \n\n\tNo, not yet. I'm still asking the others around here. \n\nRichard: \n\n\tPlease hurry and solve this problem. I don't think I can afford my medicine being stolen constantly like this!");
      break;
     
      case 4:
         Text("Richard: \n\n\tDid you happen to find anything yet? \n\nYou: \n\n\tYes, it turns out this creature is just a monkey! \n\nRichard: \n\n\tA monkey?? Here? Why? \n\nYou: \n\n\tI know about as much as you do, on that aspect. I'm going to report it to the captain.");
      break;
     
      case 5:
         Text("You: \n\n\tHave you had any more issues with theft recently? \n\nRichard: \n\n\tNo, sir! Thank you for sorting this all out!");
      break;
   }
}


Of course, this is still a work-in-progress.

The thing I often have a problem with, in implementing others' scripts and libraries in my game, is that certain functions and variables may not be properly defined, or it may be just a different coding style, so I'd have a harder time implementing it myself. I suppose, since I'm not that great at programming, that my coding style is probably not very good, but I'd say it's probably too late in the game's progress to redo it all. Perhaps for my next Sphere project, things will be programmed better.






@DaVince - Your example makes sense to me, but I think I'd essentially be doing the same thing as before, in just a different style - like, I'd still be writing 'case 0', 'case 1', 'case 2' and so forth, right? I'm not sure, as I said, I still have a hard time programming with things, even after experimenting with it and reading books for nearly the past four years. :-\


EDIT: Honestly, I can work out most of the logic of programming, particularly in JS, but it's the actual coding process that I have problems with. Just an FYI.
  • Last Edit: July 07, 2013, 08:24:28 pm by Jacob
"If you don't build your dreams, someone else will hire you to build theirs."

Challenging Apathy | Follow me on Twitter!

  • DaVince
  • [*][*][*][*][*]
  • Administrator
  • Used Sphere for, like, half my life
Re: Easier quest creation?
Reply #10
You could make it more readable for yourself by identifying 0, 1, etc. as "quest start", "picked flower", whatever. That could expose it to possible issues of making typos, but it would be much clearer what a certain quest point is about.

Re: Easier quest creation?
Reply #11
Oh, so you mean put just comment lines by each one, or is there an actual code definition I could use?
"If you don't build your dreams, someone else will hire you to build theirs."

Challenging Apathy | Follow me on Twitter!

  • Fat Cerberus
  • [*][*][*][*][*]
  • Global Moderator
  • Sphere Developer
Re: Easier quest creation?
Reply #12
Code: (javascript) [Select]
if (monkeyingAround == "pig fed") {
  // dialogue
  monkeyingAround = "done";
} else if (monkeyingAround == "done") {
  // thanks for feeding my pig!
}


That kind of thing.  You can do the same with switch as well.  You just compare against a string instead of a number.
neoSphere 5.9.2 - neoSphere engine - Cell compiler - SSj debugger
forum thread | on GitHub

Re: Easier quest creation?
Reply #13
Yeah, but wouldn't that end up being a bit more complicated in the end? I mean, it's more or less the same thing I'm running right now, except your example involves if statements, and mine just runs on case numbers. It would probably be more time-consuming to do if statements, possibly more confusing, since I'd be writing all of these different lines, when I could essentially do the same thing with the current system. Plus, I'd have to define what terms like 'pig fed' and 'done' mean. Wouldn't that take more time to do in the long run?
"If you don't build your dreams, someone else will hire you to build theirs."

Challenging Apathy | Follow me on Twitter!

  • Fat Cerberus
  • [*][*][*][*][*]
  • Global Moderator
  • Sphere Developer
Re: Easier quest creation?
Reply #14
You can use switch/case with strings as well as numbers, you don't have to use the if/elseIf if you don't want to.  But not sure what you mean on defining what the terms mean: the point is that you use a string that describes the current state of the the quest, rather than using numbers that you'll have to document somewhere else what they mean.
neoSphere 5.9.2 - neoSphere engine - Cell compiler - SSj debugger
forum thread | on GitHub