Practice makes perfect; once you have a system, you can improve it and you can refine it, but the hardest part is getting the system up in the first place. As a wise man once said:
Case in point: I’ve spent the period since the release of Revision 39 and today rewriting a good part of the jobs system.
The goal of the most recent jobs system refactoring is to kill a number of birds with one stone. First, we wanted to deal with the auto save crashes in Revision 39, which were caused (primarily) by having a reference, somewhere in Lua space, to a job which had been deleted in C++ space. In normal gameplay, this is fine; when saving the game, however, the game may attempt to serialize out the contents of the pointer in which case the game crashes. To fix this, we replaced all direct pointers to job instances in the engine with a new handle class, which contains an ID. The job ID is then used to dereference the actual job any time we need it, and only the handle class is allowed in Lua space. In this manner, it doesn’t matter if we save a dead handle; the actual data is guaranteed to exist for any valid job, and we can always determine if a job instance has been deleted. There’s a slight overhead cost for doing this, both in terms of debugging (you have to get your handle object in the watch window, then fish out the thing that it’s pointing to) and in terms of runtime (you have a second pointer lookup) but it’s not so bad all in all, and was easy enough to rewrite the code to support. We shipped this in Revision 39A.The second issue we encountered was that workshops are just all over the damned place. A workshop job in Revision 39A works by creating an assignment, and then cancelling it and replacing it with another assignment if nobody picks up the workshop assignment in time. This has a number of poor consequences:
1. The job ordering doesn’t really matter, because people will just get whatever thing they’re currently trying to evaluate.
2. Standing orders, in the current build, are totally hooped; you may end up queueing up more jobs than required in order to try and address a standing order for planks because of the new “one log can produce multiple planks” stack/container system introduced for Revision 39.
3. It’s very hard to determine what a workshop is actually working on, which means it’s hard to communicate through the UI effectively.
4. Multiple bugs, including the “all my workshop stuff disappears and stops working” bug.
To address this, I am in the process of moving all the workshop code to C++. If a character is assigned to a workshop, searches for workshop jobs now will happen in the main “I need a job!” loop, before assignment jobs are processed. If a workshop job is not being done in the workshop, we start from the top, find the first one (in priority order) that we both can, and need, to fill, then fill it. If a workshop job IS being done in the workshop, we see if anybody else can do it and should do it, and if so we add another job to the assignment. This is in the process of being debugged, and I intend to add a few more little bits of UI niceness here while I’m doing it (having a “Current Task” being put on the workshop window when somebody’s currently doing something, for instance, better workshop tooltips, and having all the jobs to be done being displayed in the jobs UI and not simply the ones that are currently being done.)
I’m hoping to sneak in better office building management here as well; if a character requires a service, such as “Being Vicared At”, “Diplomacy and Trade”, “Lodging a Complaint”, “Healthcare”, or “Psychoanalysis”, they can go to the appropriate office and then wait for somebody to see them (or get upset if they’re waiting for too long.) A third wing of the jobs-in-buildings code is reviving the ancient part of the codebase that supports people throwing parties in appropriate venues, but this is probably not going in for Revision 40.
The third thing that we’re doing this month is sorting out the decision tree mechanisms that we started putting in a couple of months ago. Right now, they’re big ad hoc, messy Lua scripts that just sort of run and try to do things. This was good enough for a first draft, but this makes it easy to introduce bugs (for instance, we currently abort an eating raw food job if there is cooked food to be eaten; however, we do not then pick up the cooked food in the next loop because there is a range threshold at which people eat cooked food which is SMALLER than the range at which we abort the raw food job in order to eat cooked food. Therefore, people find the raw food, go up to eat it, freeze up because there’s cooked food, abort, find raw food, and starve in a stockpile full of lingonberries.) Unifying how decision trees work will make the game safer and stabler, as well as providing better tools to debug problems and make sure our logic is safe and sound and working correctly with the rest of the job system; in turn, this should help us deal with military problems.
Obviously, I’m working on other stuff as well this month which is new features, but these little pauses to clean up are important. I’m super happy with how Revision 39 went out, and I’m going to be happier once Revision 40 is done because, you know, game development!