**************************************************************************** M A Y 2 0 0 8 **************************************************************************** 30 May 2008 23:39 Kernel changes: o Conversation keyword highlighting. That really wasn't so hard after all. Thanks to kaypy for making me realize it was doable. ---------------------------------------------------------------------------- 29 May 2008 23:40 Decided today that I'd add manually add a QUES keyword to all the conversations and point them at a procedure chosed to suit the character. I expect characters will be able to share procedures, and that the procedures will allow new quest types to be added in a scalable way. The exact form of this remains to be worked out, but the best way to work it out is to just start adding real ones and refactor as necessary while I go. Since all the characters will need to be revisited, and since I wanted to revisit all their gobs anyway, I'm going to just start working my way through them one at a time, changing them and testing as I go. While I'm at it, I think I might as well add portrait art. Yep. I expect this will take me a while. And since it will, and since I'll be retesting all the conversations anyway, I've decided to take a crack at keyword highlighting. I've got the matching algorithm in some test code already. ---------------------------------------------------------------------------- 27 May 2008 22:00 Been thinking about random quests. I thought an easy one to toss in would be the fedex quest. You know, "Deliver this package of SOMETHING to SOMEBODY SOMEWHERE." Then I got to thinking about how I could generate the SOMETHING, SOMEBODY and SOMEWHERE in a way that makes sense. It's actually harder than you'd think. For instance, the SOMETHING. Is it a letter; then what's it say? Is it some kind of raw material; then what is it, why is the quest-giver sending it, why does the receiver need it, and why not just do that kind of thing as a trading subgame? So I was thinking I could break fedex quests into categories so that merchants would offer certain types, town officials another, and everybody might offer certain common types of ones (like the letter that doesn't say anything). But there's six ways to do that kind of thing and picking the right one gets to be a lot of trouble just for fedex quests. So what other types of random quests make sense? Are they all going to require a bunch of work like this? o Go kill SOME-NUMBER of npc's of SOME-TYPE. Requires body count stats (or at least a hook when a character dies); would be best with some notion of agro (so spell kills like In Nox Por count). o SOME-TOWN is under attack, go help them fight off the ATTACKERS. Opens up the possibility of important npc's getting killed, so need some kind of solution there. o Assassination: the VILLIAN is hiding near SOMEWHERE, go kill him. Also needs an on-death hook. o Capture: the VILLIAN is hiding near SOMEWHERE, go capture him. o A party of SOMETHING is harrassing the countryside. ---------------------------------------------------------------------------- 26 May 2008 09:18 Kernel changes: o Added kern-define o Moved the scm macros from kern.c to scheme-private.h for everyone to enjoy o Removed some duplicate macros from closure.c o Changed kern-add-hook to expect a symbol for the handler o Added session_hook_id_to_str and session_str_to_hook_id o session now saves the registered hook handlers (except for new_game_start_hook, which needs to be a special case for now at least) Haxima changes: o Added tbl-rm! and tbl-for-each-val o Had to update all the kern-add-hook calls to match the kernel changes o Finally got my prototype quest offer to be persistent across sessions, remove itself when accepted, and in general work the way I want it to. Now I'm experimenting with having Scheme register one end-of-conv handler with the kernel that will handle all end-of-conv quest offers. The advantage of this -- as opposed to having each handler register with the kernel -- is removal. I have a kern-rm-hook but it won't do the right thing now that I have multiple handlers per hook, and I'm thinking of just removing it. Instead I currently have a single table for all end-of-conv quest offers, and it is persistent across sessions (that was the final justification for kern-define, which I've been thinking about adding for some time). It is also mutable, so offers can be added and removed (specifically, they can remove themselves when accepted, expired or otherwise no longer valid). Here's how I create a new quest offer, any time I want to: (tbl-set! end-of-conv-handlers 'gregors-quest '((basic-quest-offer (ch_gregor "Want a quest?" "You got it." "Fine. Loser." gregors-quest)))) This is the part I really like, here's the offer: basic-quest-offer (ch_gregor "Want a quest?" "You got it." "Fine. Loser." gregors-quest) What does that look like? If you said, "A function call" then you might be a programmer. And guess what? That's what it is. The things in the end-of-conv table are function calls, complete with arguments. The end-of-conv handler rips through the table, invoking all these functions. It just so happens that some of them could be quest offers. And, because everything in that table is a symbol, it can be saved and reloaded across sessions. So I've stumbled on a way to work around my inability to save closures by exploiting scheme's ability to treat code as data. This is the first time I've encountered a practical application of that kind of thing. There's a bit more to cleanup and test with other types of quests, in particular random ones. This is taking longer than I'd expected to iron out. ---------------------------------------------------------------------------- 25 May 2008 10:39 Kernel changes: o Split session hooks and queries up into separate tables. o Hooks can have multiple handlers. Running the hooks does not return a value. o Queries only have one handler and return an int. Haxima changes: o Fixed the query registration as per the kernel changes. o Random experimental stuff in minimal-*.scm Taking a slightly different tack than I outlined yesterday. Session hook handlers can now have an optional arg list registered with them. ---------------------------------------------------------------------------- 24 May 2008 08:05 Yesterday I talked about quests and how I might represent them in the object that is going to do the quest. But what about the object that is going to offer the quest? I'm back to what I was thinking of two days ago, after making an initial effort, which I'm not going to talk about. I'd like to retry in light of what I came up with yesterday. In the case of Gregor, what I want to do is apply a hook that runs when the player ends his conversation. This hook will make the offer. Iff the player accepts it will attach Gregor's quest to the player object. This is very simple to do, I was making too big a deal out of it with premature generalization: (kern-add-hook 'conv_end_hook (lambda (kpc knpc) (cond ((equal? knpc ch_gregor) (say knpc "Want a quest?") (cond ((yes? kpc) (say knpc "You got it.") (quest-assign gregors-quest (gob (kern-get-player)))) (else (say knpc "Fine. Loser.") (kern-conv-end)) ))))) What does this do? At the end of every conversation, it checks if the npc is Gregor, and if so, offers the quest. It works, but there are several problems. 1. It doesn't check if the quest was already assigned, but that's an easy fix. 2. It is very specific. For every quest that gets offered I'll have to write something very similar, duplicating the same pattern over and over manually. 3. It does not remove itself when the quest is accepted, so it continues to needlessly run at the end of every conversation. 4. It runs at the end of *every* conversation with *anybody*, even though Gregor is the only npc we're interested in (hence the equal? check). Finally, could I generate a random quest offer in this style? No, because this exact call to kern-add-hook must run every time the session is reloaded. However, it's not clear that I need quest *offers* for random quests, since the assignment mechanism may be more straightforward. How can I solve these problems? One way to solve problems 1, 3 & 4 is by using an effect (which removes itself) on ch_gregor instead of a session hook. I'm pretty sure I can get that working, so I'll focus on problem 2 instead. I'll need a few more examples to confirm which parts of the pattern are fixed and which parts I'll want to change. First, if I use an effect instead of a session hook, I can drop the equal? check, since only the npc I'm interested in will have the effect attached. What remains looks an awful lot like the response procedure in a conversation. In fact, it is exactly the same. For simple quests that don't need a lot of introduction or explanation, the pattern is like this: (lambda (kpc knpc) (say knpc (cond ((yes?) (say knpc ) (attach kpc ) (else (say knpc )))))) We can roll that into a procedure for attaching simple quests: (define (simple-quest-offer knpc offer on-accept on-reject quest) (hook-end-of-conv ch_gregor (lambda (kpc knpc) (say knpc offer) (cond ((yes? kpc) (say knpc on-accept) (quest-assign quest (gob (kern-get-player)))) (else (say knpc on-reject) ))))) (simple-quest-offer ch_gregor "Want a quest?" "You got it." "Fine. Loser." gregors-quest) The hook-end-of-conv proc represents my (unwritten) solution to problems 3&4 above. It ensure that our lamba proc runs at the end of any conversation with Gregor (and only Gregor). This attempt at a solution still has a couple of problems because of the use of lambda. The lambda is being passed to the effect code, where presumably it is putting it into a gob for future reference, and that means we can't save the game. The solution is not as simple as pulling the lambda out into a named function, we also need to bundle up the args and put them into the gob. ---------------------------------------------------------------------------- 23 May 2008 07:02 If I attach the label of a quest to a gob, I can have anonymous closures in the quest. But since the label must be in the top-level env does that really buy me anything? Other than less pollution of the top-level env? Can I still do instances of generic quests? Yes, I think I can, if the generic quests look for parameters stored in the gob under a different entry. But could I have multiple instances of the same generic quest, each with its own separate entry? I don't think so. Not that way. But I could store the generic quest tag *in* the entry, along with the parms for it: ;; object gob: (*tbl* ;; all quests filed under this entry: ('quest ;; one instance of a generic talk-to-for-xp quest ('talk-to-quest ('ch_gregor)) ;; another instance of a generic talk-to-for-xp quest ('talk-to-quest ('ch_deric)) )) Where the general format of a gob is like this: (*tbl* ...) And all quests are listed in the gob as a single key-value: (quests ()) Each quest appears in the list as a tag for the quest type followed by its parameters: (talk-to-quest (ch_deric)) (harvest-quest (t_royal cape 10 ch_shroom)) Where the parameters depend on the quest type. All the symbols are just that: symbols. And they're all defined in the top-level environment. That's necessary so that the gob can be saved and reloaded. I'm warming up to this. Quests can even have subtypes, where the subtype is just another parameter (with it's own list of args). You could also do subquests this way, where a quest's parameter is a list of sub-quests: ;; type of quest (chained-quest ;; list of parameters for chained quest: ("Bandits of the Great Wood" ; title (go-to-quest (p_green_tower)) ; first subquest (with parms) (ask-quest (ch_deric band)) ; second subquest (with parms) (go-to-quest (p_bandits_hideout)) ; etc (recruit-quest (ch_nate)) ; etc (talk-to-quest (ch_deric)) ; etc ;; and now a subquest with a subtype: (escort-quest (escort-to-contact ch_nate ch_jailor)) ;; and now two subquests that may be completed in parallel: (parallel-quest (talk-to-quest (ch_deric)) (talk-to-quest (ch_gregor)) ))) That's almost-but-not-quite-right because some of the talk-to quests have different results. The first two can just reward some xp on conclusion and do nothing else, leaving it to the chained quest procedures to follow through with the next quest. The last two will offer different rewards. Also, there should be unique dialogue associated with them. This dialogue should be coded as part of the quest, not in the character's original conversation, so that quests are truly additive. This can be done as another parameter to the talk-to quest. The dialogue might be yet another template-with-parms or it might be completely custom: (talk-to-quest (ch_deric (prisoner-delivered ch_nate "that pesky bandit" ch_jailor))) Where a talk-to-quest is formatted like this: (talk-to-quest ( )) And the prisoner-deliverd proc emits some dialogue that looks like this: Deric: "Oh ho! I see you've captured . Deliver him to and " return to collect the bounty." In fact, capturing a known outlaw might be a generic quest in itself, with a convenient constructor, but it could be built from these smaller generic quest pieces by the constructor. ;; format: (mk-capture-quest outlaw sheriff jailor outlaw-description-text reward ) ;; example call (mk-capture-quest ch_deric ch_nate ch_fildo "the pesky bandit of the Great Wood" ((gold 100) (xp 100)) ) Capture quests could even be randomly generated by using a standard procedure that takes randomized parameters. Even the outlaws and their hideouts could be part of what is generated. ---------------------------------------------------------------------------- 22 May 2008 09:30 Kernel changes: o Added conv_end_hook Haxima changes: o Re-implemented player gob in minimal game as a table. o Changed quest stuff accordingly ---------------------------------------------------------------------------- 21 May 2008 07:13 Dyanamic quest assignment. Suppose I add a new standard conversation keyword: QUES(T). The standard response to this keyword is to run a procedure which decides whether or not to give the player a new quest, and if so, which one to give. Easy enough. Now consider something a little bit different. One way to think of an RPG which has a "story" is to imagine it as a graph of quests. I mean graph in the computer science sense, something you draw with circles to represent quests and arrows to represent connections to follow-up quests, with a big circle at the end for the final quest that ends the plot. To make this happen in the game I need to have a bunch of quests that are all waiting for the player, but he has to work his way up to them, through other quests, gaining xp, skills, whatever. The circumstances have to be right before these quests are offered to the player. As a game designer I'd like to be able to set these all up like dominoes. And Sam's idea was, instead of going through and hand-crafting all the conversations and object types to offer quests, have some kind of an object which encapsulates all that, and attach that to the thing which will make the offer. Furthermore, most quests fall into one of several categories, so make a template for each category, with an easy way to make an instance of a template, and attach the instance as the quest offer. A quest offer needs a couple of things, either in itself or the environment: 1. A check-ready procedure to run to decide if the quest is ready to be offered. 2. A make-offer procedure which offers the quest and attaches it to the player. 3. A hook to determine when to check if the quest is ready 4. A hook to determine when to run the make-offer procedure. 5. Some notion of the quest it is attaching, both so it can attach it and so the make-offer proc can emit verbage consistent with the quest's title and description. An example. Consider Gregor's quest, which is to get rid of the bandits in the forest. This is done via a chain of lesser quests: talk to the rangers, capture Nate, bring Nate back to Green Tower, deliver him to the dungeons (or not -- the player may choose to keep Nate as a party member rather than complete the quest). The trick I want to think about here is how to get the first quest offered. So let's suppose I have Gregor and his conversation all written, and I decide I want to add this quest. Ideally, I won't have to change his conversation. How could I do that? I could hook the start of the conversation, the end of the conversation, or a keyword in the conversation. Let's say I hook the end. The handler will get the player and Gregor objects as parms. It could actually run a partial conversation: "Wait! Before you go, I was wondering, would you help me? I can't pay you much... (Y/N)?", etc. This means that when I attach the quest-offer object to Gregor, it needs to register with the conv_end_hook. That's part of the quest-offer-attach procedure. From this it appears that a quest-offer also needs a custom attach procedure which the generic quest-offer-attach can call to handle the details of attaching a particular type of quest offer. So far a quest-offer consists of a quest-attach proc and a make-offer proc. Let's try it: ... Ok, after thinking about it some more I think the best approach will be to implement a few of these for real, using whatever method seems most straightforward, and then step back and look at what can be made generally applicable. ---------------------------------------------------------------------------- 20 May 2008 19:15 Kernel changes: o Consolidated all the misc. "hooks" we've accumulated so far into a single hook table. o Added a conv_start_hook. Haxima changes: o Changed the hook registrations to match the kernel changes. o Had to change the quest ctor to require symbols instead of raw procedures. This was necessary to support saving quests as part of the object's gob (closures can't be saved). o Added a proc for the conv_start_hook o Talk-to quests can now be completed. o Added a simplified talk-to quest which just awards xp when complete. Still a few things left to do with the talk-to quests. First, need a quest-done proc to do things like reward the player, assign follow-up quests, etc. Second, need a way to generate and assign this type of quest dynamically based on some type of rule. Finally, probably need an optional deadline just to work out how that can be done. *** The inability to save and reload closures really cramps the style of programming I can employ in the scheme script. Lexical closures are the best thing about scheme that I know of. I'm almost tempted to take a crack at trying to save/reload them. I can probably generate the lambda code from the parse tree, but what about the (lexical) environment that's associated with the closure? Can I properly resurrect that? I'm skeptical. Must study. Hm... looking over some old notes I have on the interpreter it looks like it is, in general, not really feasible to get the environment right on reload. Even if I try to save the environment chain it won't be quite right, different closures might share an ancestral environment (ie, they may share and even "communicate" via common variables in that environment), and it's not at all clear how to properly save and reload that type of relationship. No, I think the right answer is to just write the interpreter's entire memory space to disk, along with its virtual register settings, and reload it again. That would work slick if not for the fact that... (a) the addresses, being native machine virtual addresses, might not be reloadable to the same location (mmap has the right to refuse, and I have no idea if it will even really work on Windows). (b) some of the cells will be pointing to kernel data structures outside the interpreter's memory space, and those absolutely will change on reload. (c) opposite of (b), some of the kernel data structures are pointing into the interpreter memory space, so this is another variation of problem (a) but the referring pointers are outside the interpreter space. Nasty! The only solution I can think of requires two things. First, all pointers have to be offsets from a common base address, so they can be fixed up on reload... hey, I just realized, I could *save* them as offsets, and fix them up on reload. That would save all the overhead of doing the offset computation at runtime for every pointer dereference. I just do it once on reload. Second, the kernel and interpreter need to allocate from a common base address, which suggests a common allocator. *Or* they could have their own, as long as I knew which type of pointer I was saving and loading. Third, I'd need to freeze all the kernel data structures in the same way. That could be a problem. I suppose. Maybe it wouldn't be such a big deal. If I used my own allocator for everything it would be no sweat, but SDL allocates some things via libc. Now, if SDL is the only thing that uses libc, and I know when I'm dealing with a pointer to something allocated from libc, I could possibly make a deep copy from my own allocator and free the libc version right at the point where it's created. Now, I've still got a problem with needing to initialize SDL on boot, and I'm not sure I can make a deep enough copy to really set things up right. I might have to make SDL objects a special case and use constructors for them similar to what I do now, and then I'd have to somehow patch the referring pointers. Fourth, I'd need to fix up the entry point. It's not feasible to try and save the CPU stack and registers, since that varies with the OS and CPU. But that may not be such a big deal. I just need to resurrect all the data structures, correctly establishing their relationships, and make the session start running in a standard place at the top of the event loop. But one definite result of this is that the save files would be binary images. The good news: they'd load really fast compared to what happens now. Yum. The bad news: you can't just open them up in a text editor and go find out what's wrong when they crash; or edit things for test; or, yeah, to cheat and give yourself an eldritch sword and an L20 balron for a party member. So I'd need to write a more sophisticated editor to compensate for the loss. But it would be really cool. And lexical closures would work. ---------------------------------------------------------------------------- 19 May 2008 19:24 Time to finish the prototype talk-to quest. Need to hook conv_enter. Hook closure should be stored in the session struct. When it runs, pass the pc and npc. It will grab the pc's party gob, get the quest list, and scan for talk to quests. If the target of a talk-to quest is the npc, mark the quest as complete, emit a message and run the quest completion proc (need to add that to the quest gob). That should do it. Here goes. ---------------------------------------------------------------------------- 18 May 2008 10:09 Kernel changes: o Changed kern-event-push/pop-keyhandler to kern-event-run-keyhandler o Removed the unused "stop" operation from the applet struct o Added kern-screen-erase and kern-screen-update Haxima changes: o Added quest viewer applet for individual quest details And that's that. The quest UI implemented in the script. I was getting pretty sick of the long load times every time I wanted to test a change. Although I think the right way to fix this is with a built-in scheme terminal, I didn't want to wait until I had that working, so I created a stripped-down version of the game. This loads the minimal sprite sets and auxiliary files. This effort turned out to be something of an eye-opener. Can we say "Dependency hell", kids? I need to figure out some way to make the system even more modular. One problem is the way the parts of complete things (eg, npc's or terrain) are grouped into resource files, so each thing is split across multiple files. Part of the reason for this gets back to the fact that it's faster to load a bunch of images in one file than a bunch of files with a single image in each, so right from the start that drove me to organize things into resource files. Another thing is that it's easier to add new "resources" when you have a bunch of other examples to look at right there in the same file. One of my fantasies is to have an editor that would let me treat things as separate modules, while on the back-end generating whatever organization is most efficient for loading. That would eliminate some of the problem, but even then separate modules would have dependencies. For instance, I try to make each npc type have some unique ability, and it makes sense for the ability to be defined as its own sort of thing, so there's a linkage right there between two separate things. I guess that means the editor would need to automatically track dependencies where they were unavoidable. A lifetime of work. All for a cheesy little rip-off game. But I guess we don't get to choose our obsessions. ---------------------------------------------------------------------------- 17 May 2008 17:59 Kernel changes: o Added an applet struct o Added statusRunApplet and statusGetCurrentApplet o Added a "select" operation to the ztats panes o Converted the ztats UI to run as an applet Haxima changes: o Modified quest log ui to implement a nop "select" proc Implementing the applet had a few minor surprises. I don't know if I'm happy with the way repainting is done (particularly the title repaint). It seems clumsy to make each applet deal with that. A bigger concern, however, is if I've got the right model for converting all the status panes into applets. In particular, the default party mode view is niggling at me. This mode does not need to "run", it just needs to sit there and reflect changes to the party. It's a passive observer. Currently, statusRunApplet() will push the applet, run it, then pop it on the way out. Won't work for a passive observer. A separate statusPushApplet() might be the answer. Must study. Another nettling thought is the way the status window resizes itself based on the mode it's in. The right thing is to size it when an applet is pushed, and restore it to whatever it was when the applet is popped. I can think of two approaches: 1. The applet tells the status code how bit it wants to be, maybe just in one of its struct fields. 2. Let the applet size the window in their "enter" op, and re-enter applets (or have a separate "resume" op) when they become top-of-stack again. There's going to be a similar issue with the title bar contents. In fact, there may be any number of little things I'm forgetting about that need to be reset when an applet becomes top-of-stack again, so I'm leaning toward the "resume" op. The big difference between "resume" and "enter" is that active applets - ie, ones that have a "run" op - should not start their keyhandler loops in "resume" like they do in "enter", because they're still in their keyhandling loop when they are resumed. Resume is just a ui restore. That might do it. Something's still bothering me but this is worth a try. Completely different topic. I read a speech or something by Stallman where he mentioned that one of the things he'd done while developing emacs was gradually re-implement all the original ad-hoc C structures as native Lisp structures. I've toyed with that idea myself, or something similar, being somewhat envious of the garbage collection enjoyed by the script as compared to all the C code I have to write to deal with allocation and making sure things get freed properly. I was just thinking of trying to do a complete, as-automated-as-possible reference counting system in the kernel. But Stallman's idea has a lot of merit. Not only would the kernel get the automatic ref-counting for free if I converted the data structures to what scheme.c uses internally, but all of the code that crosses the script/kernel boundary would become radically simpler. No more packing/unpacking (and all the boilerplate error checking that goes with it): no conversion would be necessary. It's an intriguing idea. However, there would have to be at least one major change: I would need to be able to support structs. There is no way in hell I'd ever consider converting all the kernel structs to lists. Nor do I want to do something clever-but-stupid with implementing them as Scheme vectors (unless it was all automatic and the C code still used . and -> normally). On the other hand, Common Lisp has structs... why can't TinyScheme? Something to think about. ---------------------------------------------------------------------------- 16 May 2008 16:40 Ok, keyhandlers. Looks pretty simple: Add kern-push/pop-key-handler, which will wrap the scheme handler with a C struct similar to kern-ztats-add-pane: (kern-push-keyhandler keyh) (kern-pop-keyhandler) Where keyh is a procedure like this: (keyh key keymod) Where key and keymod are constants in common with the kernel. Kernel changes: o eventPopKeyHandler now returns the keyhandler that was popped o added kern-event-push-keyhandler o added kern-event-pop-keyhandler o added a 'select' operation to the ztats_pane o added eventRunKeyHandler to manage all the boilerplate of pushing/running/popping key handlers I think I'll go ahead and add a statusPush/PopPane. I'll convert ztats to use it, then exploit it with the quest list to push a quest view pane. I might want to do something similar with the console (so I can put the scheme terminal in the console window). ---------------------------------------------------------------------------- 15 May 2008 07:40 Kernel changes: o kern-screen-print expects the rect arg to be a list now o kernel passes ztats ui dimension rect as a list back to scheme o added (kern-screen-shade ...) Script changes: o ztats-quest-ui.scm ...updated to match kern-screen-print ...modified to print all the quests attached to the party ...scrolls properly ...shades all but the currently 'selected' line o quest-sys.scm prints a console msg when a quest is assigned The quest list pane is much slower to repaint than I anticipated, which is discouraging. Of course, this is a pretty slow machine by today's standards (600MHz), but still... Now I need to figure out how to make selecting the current entry flip to a quest display pane, and back again to the quest list. This will probably require the ability to register a key handler from the script, and that will open up all kinds of possibilities. I find development frustratingly slow because of the time it takes to load a game so I can test my changes. I've stripped down the main game somewhat, but more can be done if I want to put the effort into it. The other idea which I find very intriguing is the notion of adding a scheme terminal inside the game. From there I could reload just the files I changed w/o having to restart the game. Of course, this assumes that bugs in the development code won't constantly crash the game and require a restart anyway. On the other hand, ideally the game should never crash from a script bug. ---------------------------------------------------------------------------- 14 May 2008 19:24 kernel changes: o Added kern-ztats-add-pane, kern-ztats-set-title and kern-screen-print. haxima changes: o Added ztats-quest-ui.scm, which implements a stub for a quest log ztats pane. o player-member-loc checks for a NIL location o music-on-combat-change checks for a null location o Added quest-sys.scm which defines the basic quest type o Added quest-talk-to.scm which defines a talk-to quest type o Modified ztats-quest-ui.scm to print the title of the current quest ---------------------------------------------------------------------------- 13 May 2008 05:28 Maybe I gave up too soon on doing the UI in the script. I realize now that scrolling the quest pane would require custom code anyway, and probably isn't necessary on a first cut. A status pane must support the following operations: 1. Setting up the pane. This needs to switch the status view to tall mode, set the title, and setup two function pointers: scroll and paint. 2. The scroll function gets the scroll direction arg and changes whatever variables it needs to change so that the next repaint looks like the UI is responding to the scroll direction. 3. The paint function blits stuff to the status window, making heavy use of screenPrint(). Plugging into ztats is a small problem. I'll need to reorganize the ztats code a bit. I could have each ztats pane implement something like the above as a stand-alone thing, and puth them in a doubly-linked list instead of an array. The horizontal scroller would then just move a pointer in the list and call the new pane's 'enter' function, which would set the title, init vars, etc. The ztat paint function would just call the active pane's paint function, and so would the vertical scroller (the pane might have a NULL vertical scroll function, which just means it won't support that operation). ---------------------------------------------------------------------------- 12 May 2008 07:23 Showing "quest available" icons on NPC sprites. When an NPC sprite is painted, there needs to be some way for the paint routine to tell that the NPC has a quest available. It either has to be able to check a flag or run a procedure that does the check. Which is better? If it's a flag, then what triggers it to become set? When does this happen? This depends on how the quest came to be "offerable" (terrible word) by the NPC. I'm going to refer to this as a quest offer becoming attached to an NPC. A quest offer is going to be different from (but related to) a quest. The three ways a quest offer can become attached are: 1. The game designer may design the NPC with it attached. 2. Some event in the game (eg, completion of another quest) may attach it. 3. An algorithm which assigns random quests may attach it. Once an offer is attached the paint routine can check if it is available, either via a flag on the offer or a closure which runs. More on that later. For now, we're starting to get a picture of what a quest offer might look like. It should at least have a "check-if-available" closure. Once available, how does the quest actually get assigned from the offer? It depends on the offer. In most cases quests will be offered by NPC's, and the simplest thing would be to establish a convention where asking an NPC about QUESTS triggers the offer, as described yesterday. So long as the kernel supports an "ask-about" hook, and a method like (kern-obj-get-quest-offers ...), the particular keyword will be up to the game script. If a game designer wants a quest to be "hidden" and triggered via a different keyword then a custom ask-about hook may be added via an effect. I coud design a quest offer to be a kernel object with a simple api like: (kern-mk-quest-offer ...) (kern-quest-offer-attach kobj koffer) (kern-quest-offer-detach kobj koffer) (kern-obj-get-quest-offers kobj) Or I might be able to design them entirely via hooks and closures in the script. What if I add a paint-done hook to the kernel, which invokes a closure that takes the object as a parm? The closure could check the object's gob for attached quest offers, run their "check-if-available" closures, and paint an icon over the object sprite. Attaching and removing offers would modify the object's gob. The kernel would be none the wiser. Something to think about. If I do this, I need to seriously think about standardizing gob layouts. Quests can also be offered via other means, like using an item (eg, a letter) or perhaps even by stepping on a tile. As long as the hooks are in the kernel for these events, I don't see an issue with supporting this kind of thing. Even the painting hook could work. *** Now for a slightly different topic: the UI. As a thought experiment I'd like to consider implementing as much as possible of the UI for quests in the script. What's the minimal API I'll need to make this possible? First, what will the UI look like? I'd like to add a panel to the Ztats UI that shows a list of quests, with uncompleted ones shown first and then completed ones below that. I think there are already some examples of creating such a thing in the script (but not of making it part of the Ztats). To get a detailed view of a quest the user selects it from the list. There it looks like the examples below. Hitting ESC takes the player back to the list. For painting the UI, it looks like screenPrint is the workhorse, and maybe sprite_paint would be useful. Something would also need to handle keystrokes for things like scrolling, etc. Yuck. Can't say I want to re-write the scrolling code again. I guess I'll save this for a UI redesign down the road. For now the kernel will render the UI, which means it's probably best for all the static info related to quests to be in a kernel quest structure, with the dynamic info fetched via closures stored in the struct. ---------------------------------------------------------------------------- 11 May 2008 09:04 Quests. Take two. UI mockup of Enchanter's Quest: ###########> Quest <############# # Find the Enchanter # Find and talk to the Enchanter. # Status: Incomplete ################################# I can think of lots of features to add to this, but it's a step in the right direction. UI mockup of Shroom's Quest: ###########> Quest <############# # Shroom's Mushroom Quest # Bring 3 Purple Cape mushrooms to # Shroom. # Status: 2/3 collected ################################# This suggests that the text displayed under "Status" should be fetched via a closure. UI mockup of Gertie's Quest: ###########> Quest <############# # Avenge Gertie # Bring the Skull Rings of her # crew back to Gertie. # Status: 2/3 collected ################################# Same thing. A "collection" quest, so the Shroom and Gertie quest could share the same type of closure for reporting status: +---------------------------------------------------------------------------- | (define (collection-quest-get-status kquest kchar) | (let ((qgob (gob kquest)) | (ktype (collection-quest-gob-type qgob)) | (total (collection-quest-gob-quantity qgob)) | (current (count-in-inventory kchar ktype)) | ) | (cond ((< current total) | (string-append! (string current) | "/" | (string total) | " collected")) | (else "Ready for turn-in")))) +---------------------------------------------------------------------------- That's not quite right, because it needs to report "Completed" if the quest was turned in, but that could just be another condition that checks the gob. Let's do one more example. The Enchanter asks the player to speak to all the Wise. This could be one quest: ###########> Quest <############# # Speak to All the Wise # Visit all the Wise and ask about # the Rune, then report back to # the Enchanter. # Status: 3/7 visited (Alchemist, # Necromancer, Engineer) ################################# Or there could be a quest for each of the Wise: ###########> Quest <############# # Visit the Necromancer # Ask the Necromancer about the # Rune, then report back to the # Enchanter. # Status: unvisited ################################# How does a quest get assigned? The "Find the enchanter" quest is assigned when the player opens the starter chest and gets the letter from the enchanter. The 'get' method of the letter needs to assign the quest. Currently it doesn't have a 'get' method one, so I'd have to change the way its type is defined. Not too hard. Below is a fairly complete look at how a talk-to quest could be implemented. +---------------------------------------------------------------------------- | ;; A generic (useless) quest gob looks like: | ;; ('quest (...)) | (define (mk-quest-gob . parms) '( 'quest parms)) | (define (quest-gob-ext gob) (cdr gob)) | | ;; A talk-to-quest gob looks like: | ;; ('quest ('talk 'ch_bilbo (...))) | (define (mk-talk-to-quest-gob target-tag) (quest-gob-mk 'talk target-tag)) | (define (talk-to-quest-gob-target-tag gob) (cdr (quest-gob-ext gob))) | (define (talk-to-quest-gob-ext gob) (cadr (quest-gob-ext gob))) | | ;; Convenience proc for getting the target of a talk quest. Returns a pointer | ;; to the kernel object which is the target character, or nil if that object is | ;; not defined. | (define (talk-quest-target kquest) | (safe-eval (talk-quest-gob-target-tag (gob kquest)))) | | ;; The effect gob (the first parm) is the kernel quest object. | (define (talk-to-quest-check kquest kobj kchar) | (if (equal? (safe-eval (talk-quest-get-targ (gob kquest))) kchar) | (kern-quest-set-done kquest))) | | (mk-effect 'ef_talk_to_quest_check ; tag | nil ; name | nil ; sprite | 'talk-to-quest-check ; exec closure | nil ; apply closure | nil ; remove closure | nil ; restart closure | talk-start-hook ; hook | "" ; symbol (obsolete) | 0 ; difficulty detection class | #f ; cumulative | -1 ; duration | ) | | ;; The talk-to-quest assignment closure attaches the quest object as the | ;; effect's gob. | (define (talk-to-quest-assign kquest kobj) | (kern-obj-add-effect kobj ef_talk_to_quest_check (gob kquest))) | | (define (mk-talk-to-quest targ-tag) | (bind (kern-mk-quest "Find and talk to the Enchanter." ; title | talk-to-quest-assign ; assignment closure | nil ; status query closure | ) | (mk-talk-to-quest-gob #f ; complete? | targ-tag ; tag of character to talk to | ))) | | (define (assign-talk-to-quest kobj targ-tag) | (kern-quest-assign kobj (mk-talk-to-quest targ-tag))) +---------------------------------------------------------------------------- That should do it, with some kernel changes to support the API and the talk-start hook. But another goal is to be able to add quest assignments at runtime. For example, every so often a townsfolk may randomly assign the player a quest. Is there some way to define a set of template quests, and then randomly choose one for a townsperson to offer? Let's say that every so often a townsperson will offer the player a quest to deliver something to somebody in another town, with the quest completing upon delivery. The first questions are, what will trigger the quest assignment, and how will it be presented to the player? The first thing that comes to mind is a permanent "talk-start" hook which runs a procedure to check various criteria to decide if the npc will offer a generic quest. If the answer is "yes", how will this be done? The second idea that comes to mind is that there will be a permanent "ask-about" hook which could be tied to the keyword QUES. The procedure that runs under this hook will check the criteria, select a quest template, and then run a piece of conversation which is defined in the template. For example: Wanderer: QUES George: You seem a trustworthy fellow. I need someone to deliver these stuffed bunnies to Janice in Stinkwater, who will pay 50 gold on delivery. Do you think you can manage it? Wanderer: YES George: Here are the bunnies. You get 20 stuffed bunnies. George: Be careful, highwaymen love these bunnies. You have a new quest! Straightforward enough, I think. I'll prototype some scheme later. But another question is this: wouldn't it be nice if George had a little icon over his head that shows he was willing to give a new quest? How could that be done? **************************************************************************** J A N U A R Y 2 0 0 8 **************************************************************************** 15 January 2008 08:45 While messing around with adding the developer mode option to the settings menu I realized I have a conflict between the command-line args and the settings menu. Some values can be set from both places. I think the command-line should override the saved options, but that's not the way things are currently implemented, probably because I need to parse the command-line args to figure out which directory contains the saved options file. One way to fix this would be to store the command-line args in a temporary cfg table, and then "push" this one into the main one after loading the cfg scripts. It also seems wrong to have one cfg table for the program, since some settings should really be per-session. It might be better to have a config for each session as well as a program config. And instead of bandying about some of these globals, I really ought to have a program struct and a session struct that gets passed on the call stack. Too bad Scheme's scoping rules don't apply to C; it would be nice to have an environment that didn't have to be either a) explicitly passed into each function via an arg or b) set at global scope as the ONLY possible environment. Add that to my wish-list for the theoretical super-C. Do I want a C that's more Scheme-like, or a Scheme that's more C-like? How would I change Scheme? First, it has to be fast, so that probably means a compiler is involved. Second, it must have a debugger of equal or greater utility than gdb. Third, it must support C-style structs natively and efficiently without verbose accessors/mutators, and I'd prefer them to be actual memory chunks, not hash tables of some kind (actually, I just want to be able to dump them in gdb, so if that property plus fast access is preserved I might relent on this requirement). Also, I want faster, more mutable lists (ie doubly-linked). And I need a fast 2d array type (yes, vectors would work, but I want direct access, kind of like the structs). I guess I'm thinking of a Scheme that uses vectors more heavily, with direct access (ie no procedure call overhead). Built-in hash table support would be extra icing. What else? All the C libraries I like to use must be available, of course, without me having to write wrapper functions. Actually, there aren't that many that I need, so maybe that wouldn't be such a killer. I'd have to wrap SDL but that doesn't seem like an enormous task. The real killers are performance and the debugger. I want a NICE debugger, that shows the source tree while stepping, etc. It goes without saying that adopting this new dream language would isolate every other developer in the world. Especially if it maintained the parenthesis syndrome. I have a pet theory about why some people hate Scheme and some people love it. I think that people who hate it hate the regularity. Now pretty much any developer except Larry Wall and his disciples will claim that they like regularity in a language, but I think they only like the IDEA of regularity. On the contrary, I think people like a certain amount of irregularity in languages. Consider the fact that every natural language contains a fair amount of irregularity, and people are extending this all the time via idioms and dialects that break the "rules" of their language. I think these irregularities are analogous to landmarks, helping people orientate themselves within a statement or conversation stream. By way of comparison, a statement in a completely regular language is like an open plane tiled with a repeating pattern. It's much more difficult for human brains to jump into the middle of this pattern and figure out what it's about, and it's tedious to start from the beginning and build up the necessary mental context so that when you reach some part in the middle you understand its role. On the other hand, machines love regularity. They can process regular statements with very small amounts of code. Anyway, nothing solved, but I feel a little bit better. 14 January 2008 19:45 Got my first bare-bones quest declaration to load into a C structure. I guess that's progress. It leaks right now, so I need to fix that. Then I need to tie it to something so it triggers, write the UI widget to show it, write the check-for-completion code, etc. Yuck. This reminds me of churning out spell code. I've heard that Smalltalk had this great development environment where you could browse your objects and stuff while running your program or developing your code (it almost sounds like the two were interleaved concurrent processes). Wouldn't it be neat if I could just write the quest structure and click a button and all the parsing and UI browsing code would be generated? What I've done so far is pretty mechanical, I think I could write something that would do all that. For the UI, you probably want some markup or you'll end up with something so horrid and ugly that people will think MIT made it. But the tricky part is generating the code that actually groks the structure and makes decisions based on its value. For that you might as well do it by hand. The other tricky part is getting all the hooks in place in the existing code to support the new feature. That also requires a brain. I have this niggling feeling that there is a much better, easier way to do all this. Vote for your favorite high-level language here. But they all have some achilles heel. Perl is too big. Python is too big and too slow. Don't even talk to me about java or C++ (too verbose on both counts). Scheme is nice and terse but slow and just too loosey-goosey (how's that for a technical adjective?). C is still the champ in my book. Awesome tools and library support, ported to every platform known to man, a robust standard spec that hasn't changed much for years, still well-known among programmers, and speed is hardly ever a problem. But it's missing some of those things that I've come to really appreciate from Scheme: the ability to just declare an instance of a structure, no matter how recursive or dynamic it is; garbage collection of course; inherent polymorphism; easy closures. I can sum up everything I love in 3 symbols: map, lambda and '. If C had native support for some equivalent of these... wow. But of course it's not that simple. There's a lot of machinery hidden beneath the stage floor to make those things work. That's stuff that typically must be built on top of C, not into it. Which leads me into a circular thought process: what if there were a library that did just that? In a way, that's what the tinyscheme interpreter is. Would making the library calls really be easier to use? Wouldn't it be better to just do that in a language designed for that? And isn't that language just Scheme itself? And around and around I go thinking about that. 13 January 2008 21:49 I decided to start a branch and take a stab at implementing a new-style quest. While writing the code to implement kern-mk-quest I realized how horrid kern.c is. Ideally, the whole thing would either be machine-generated or interpreted, because it is miles and miles of boilerplate repeated over and over. I've postponed doing something about it for all this time and so of course it just keeps growing. My unhappiness with that situation has infected my perspective on the quest api. At this point I'm discouraged enough that I just want to punt on the quest api until after 1.0. I'm still strongly sold on the idea behind the new quest system, but a couple of niggling issues continue to haunt me, which is one reason I wanted to prototype something. Maybe I'll feel better about it tomorrow. It's never a good idea to quit when you're discouraged. You need to do it when you're rational. A lot of good ideas come out of the depths of discouragement. 10 January 2008 18:36 I see where I left off that I had a bunch of Scheme in my sample quest declaration. Looking back at it, at first I didn't like that, because I've been thinking about how to automate more of the game creation with less hand-coding. I don't want every little quest to require someone to code up a bunch of support procedures. But, on reflection, I could write a wrapper procedure that would generate a fairly generic "gather" quest, with some parametrization if necessary. **************************************************************************** D E C E M B E R 2 0 0 7 **************************************************************************** 19 December 2007 09:52 Rather than jump right into the middle of a discussion, I'm going to dump all my current notes on the new quest API and pick up the chronology tomorrow: Examples -------- Name: Shroom's Mushroom Quest Given By: Shroom of Green Tower Turn in to: Shroom of Green Tower Quest Text: Bring 5 Royal Cape mushrooms to Shroom Reward: She will tell you the syllables and mixture for the Panic spell. Objective: Bring 5 Royal Cape mushrooms to Shroom Completion Progress: Timer: Is Repeatable: No Pre-requisites: nil Name: Bill's Axe Quest Given By: Bill of Bole Turn In To: Bill of Bole Quest Text: "Bill encountered a haunted tree west of Bole and lost his axe. Go investigate." Reward: XP + torches Objective: Kill the haunted tree and return Bill's axe Completion Progress: Timer: Is Repeatable: No Pre-requisites: nil Name: Ask the Engineer about the Rune Given By: Enchanter Turn In To: Enchanter Quest Text: Enchanters wants you to ask the Engineer about the Rune. Reward: XP Objective: Ask the Engineer about the Rune Completion Progress: Timer: Is Repeatable: no Pre-Requisites: Complete the Stolen Rune Quest General notes: 1. All quests are refuse/accept 2. Quests may be abandoned once started. (Some may be taken up again, maybe not). 3. Items that offer quests should have UI hints in inventory 4. Conflicting quests (eg, Enchanter and Silas both want you to collect Runes for them) can both be pending, but only one can be satisfied. This implies that quests might need to be able to refer to one another. *** Add quest_new()/del() Add kern-mk-quest Add a list of quests to the Object class. Also add Object::addQuest Add kern-bind-quest. *** I don't think the trigger support should go in the kernel. This should be done in the script. I don't want to add loops to check all the quests in every call to hitLocation, etc. Besides, is the quest offered before or after processing the closure? Better to have it right in the closure, I think. Well. Hmm. Maybe not. I can do it in one function, assume post-processing. Most objects won't have quests so it will go quick. *** Creation, attachment and triggering of quest assignment seems straightforward. Even checking prereqs should be ok. What about progress and turn-in... In general, for turn-in, I need to hook conv_enter at the start of conversation. talk-to: add a check in conv_enter. But talk-to will almost certainly be extended to 'ask-about'. This could also be done in the main loop of conv_enter. kill: easy enough to add a check in ctrl_do_attack, but what about spells? Or simple connivery? We could check in Character::kill(), but it would be a hideous hack: check all party members for a quest to kill things that match this one's attributes. But what if assigned quests were kept in the session struct? Or player_party struct? Then it would be no worse than some of the other hacks we've already got... yeah... then this would be easy. gather: hook PlayerParty::addToInventory deliver: hook Character::addToInventory for the non-player-party-member case. This assumes delivery to a character, not a place. Or, rather, the conv entry can check for the quest and auto-initiate delivery. escort: I think I'll limit my ambition the first time around to "on entry to a place". I just need to find where a party member is added to a new place and hook there (all of them). Escort quest progress should probably be broken into phases: assigned, acquired member, reached place, done. It seems like the usual case will be the person you escorted will be the turn-in person, and he should talk to you when the destination is reached. The alternative is you're supposed to bring them to someone else. The last quest phase could just be "talk to". For phases quests like this, progress could be broken into sub-quests. The normal phase of a quest is: offered accepted fulfilled turned-in But an escort quest is like: offered accepted joined fulfilled turned-in Written out like that, it doesn't look like sub-quests make sense. The two progress steps for escort are "joined" and "arrived". Once arrived, the player still has to do turn-in. In the special case where the escortee is the turn-in person, the engine should start a conv automatically. In fact, one way to get in-place destinations would be for the start of conversation to check if the escortee is in the target region. So it looks like this: the player gets the escortee in the party (JOINED), goes to the right place (ARRIVED, or if a region of a place is specified, NEAR). The on-entry-to-place hook will check for an escort quest, find this one, and... Change progress to ARRIVED Print a console message to that effect If no in-place region is specified Print a console message that the quest is ready for turn-in If the escortee is the turn-in person Initiate conv with the escortee The conv_entry will find the quest and complete it Else Else Print a console message about where in the place to go No, wait, what if the quest is to deliver the person, and return for the turn-in? No, if we want that, the return should be a separate quest, where the escort quest is a prerequisite. craft: requires crafting gainrep: hook the diplomacy code explore: probably requires FOW use-ability-on-subject: Ouch. Abilities (and their targeting) are handled entirely in the script. I could add a new kern-call specifically to notify the engine when this happens. I suppose the yusage has to succeed... This puts the burden on the script-write to always invoke this notifier. use-item-on-subject: Same kind of thing as use-ability-on-subject. use-travel-to-goto: I think I'd have to hook the vehicle object's relocate method to check if the object has arrived at a quest destination. special: To support special quests I think the quest object must be bindable to a gob. The get_progress closure would have to return a string describing current progress. The check closure would return #t if the objective was done. time-limit: hook the internal clock. What happens if the time expires? Ok, instead of making the TIME_LIMIT an objective I vote we move it back to the main quest structure, because it complicates the evaluation of the "is-complete?" expression. Every other quest objective starts out false and potentially becomes true in the future. The time limit starts out true, and when it changes to false it will never go back to being true. When the time limit expires the quest is not just incomplete it is uncompletable. So, when the time limit expires I need to mark the quest is uncompletable (or "failed") and print a console message. *** Failed, re-doable quest. If a failed quest is re-assigned the failed one should be removed from the quest journal. Rather, a quest should keep a "times failed" count as well as a "times completed" count. Any time a quest is re-assigned it should be reset. *** Alternative: do it in the script with kernel support for UI API. This alternate approach has been in the back of my mind as I've gone through the above thought process, and my conclusion is that it would not buy us much more in flexibility. All of the hacks required in the kernel would require some kind of equally horrid hack in the script, and then it would still require more hooks to be added to the kernel specifically to support the script quests. So I'm not tempted at this point to rethink that part of the decision. *** Objects will have a new attribute for showing their quest assigner status. This status is updated lazily and is not saved. There will be a new place_check_quest_status() function which will hit every object, telling them to checkQuestStatus(). An object will walk its list of quest assignments. let P be the player party object for each object O # update the 'offer' status O.resetQuestOfferStatus() for each quest_assignment A assigned = false for each quest_progress object R if R.assignee == P assigned = true break if ! assigned O.accumQuestOfferStatus(quest_evaluate_prerequisites(A.quest, P)) # update the 'turn-in' status O.resetQuestTurnInStatus() for each quest_turn_in T O.accumQuestTurnInStatus(quest_progress_evaluate_progress(T.assignee_progress)) Since only one quest status can be shown at a time, Object::accumQuestOfferStatus() respects an ordering: QUEST_OFFER_OK > QUEST_OFFER_ALMOST > QUEST_OFFER_NYET. Similarly for the turn-in case of QUEST_TURNIN_OK > QUEST_TURNIN_ALMOST > QUEST_TURNIN_NYET. How will quest_evaluate_prerequisites work? QUEST_EVALUATE_PREREQUISITES(Quest Q, Object A) result = QUEST_OFFER_OK for each prerequisite P r2 = quest_evaluate_prerequisite(P, A) if r2 < result result = r2 return result QUEST_EVALUATE_PREREQUISITE(P, A) switch (P.type) case EXPERIENCE: ... ... ... I think the rest is straightforward. *** If (kern-bind-quest ...) works on objects, and not object types, then it won't be useful for things in inventories or containers (like the Enchanter's letter). I think I'll need two: (kern-bind-quest-to-object ...) and (kern-bind-quest-to-object-type ...). This means object types will need a quest list. ObjectType::createInstance() may need to propogate the quest to the object. Or, quests on objects can work like sprites: usually the object type's quest list is used, but characters can override the accessor methods. So really what I want is (kern-bind-quest-to-being ...) instead of to object. *** Auto-offer flag. Rather than bind to a keyword, a quest write may want an npc to offer a quest automatically when the player talks to them. We can have a flag for that, which conv_enter can check at the same time it checks for completed quests. The introduction text (by the way, instead of 'hail printing the "You meet a ..." greeting, this should be done automatically by conv_enter, using the description) should be printed, then any completed quest UI's, then any auto-offer quest UI's, then on into the main conversation. Note that when processing a completed quest, it may enable an auto-offer quest, which should then be processed before proceeding to the main conversation. In this way it looks like one quest is "auto-launching" another one. PHASE II: Walk-throughs TALK_TO The Enchanter's letter in the starter chest will give the player a TALK_TO quest with the Enchanter. See quest.scm for sample code. (kern-bind-quest-to-object-type ...) will add the quest to the list of quests for the object. o Player o)pens chest, putting the letter on the map. Item creation is a good time to run the checkQuestStatus() method on the new item. o The next time objects are painted, the offer icon will show above the tile with the letter. o Player gets the letter. During the g)et, the verbage should print parenthetically that the letter has a quest associated with it. cmdGet uses the object's questOfferStatus method to do this. o When the player checks z)tats, on the inventory page the letter should show the offer icon next to it or over it. The status page uses the ObjectType's questOfferStatus method to do this. o Player u)ses the letter. In cmdUse, after the item is used, the code checks if the item has any quests ready to offer. It does, so cmdUse automatically invokes the quest offer UI. o The quest UI sees that the quest is not accepted and is not refusable, so it automatically causes the player to accept the quest. The text shown is the "reminder" text. o When the player browses z)tats again he will find the quest pane, which lists all completed and pending quests. Pending quests will be at the top. Completed quests will be purple, pending quests will be white, failed quests will be gray. Selecting a quest will jump to the quest pane, which shows all the details and progress. o When the player t)alks to the Enchanter, the conv_enter() code checks for any pending quests in the speaker's party. Perhaps it just calls a hook and the object method checks the quest. Actually two things need to happen here, in order. First, conv_enter must notify the speaker object that conversation is about to begin. The speaker object walks the list of pending quests, checking for any TALK_TO quests; when it finds one it checks if it has just been fulfilled, and marks it. Second, when this process completes, the speaker object should re-check all pending quests to see if this character is the turn-in object for them. If it is, and the quest is not turned-in, then the quest UI should pop up. If the quest is completed then the completion process should run. If is not then the reminder process should run. And third (I miscounted above), the speaker should AGAIN re-check all pending quests to see if any are now ready to offer and auto-offer, and show the UI and run the offer process for them. After all that, the main conversation may be entered. o When the method sees that the quest is fulfilled, it should play the quest complete fanfare and pop up the quest UI to show it as done. The quest completion text shows. When the player exits the quest UI conversation with the Enchanter begins normally. GATHER The second quest in the main line is to find the stolen Rune. This is automatically offered when the player talks to the Enchanter and all the prereqs are met. For GATHER quests, when the quest is accepted the offer process should immediately check for completion, because the player may have accidentally found the stuff before getting the quest. Note: we may want some quests to create the object(s) in question when the quest is accepted. Care must be taken if such quests are abortable or failable that we don't open a door for cheats to create lot of rare objects. Currently I have no support for auto-creation in my examples. To complete a GATHER quest the place to hook is addToInventory. For the player we need to hook the player party's method, since everything currently winds up there, regardless of which party member picks it up. The hook should check not just what is currently added, but that plus whatever is already in inventory. removeFromInventory also needs to be hooked, so the progress struct can be decremented properly and the "completed" flag cleared if need be. Note: we need a separate sound for cases where a completed quest relapses into incompleteness, as well as a sound for failure. And it goes without saying that the console should also print messages for these events. This brings up a sticky point... I had intended that gathering the Rune be one quest, and delivering it back to the Enchanter be another. What if the Rune is gathered, the delivery quest accepted, and then the player drops the rune somewhere?