2 // nazghul - an old-school RPG engine
3 // Copyright (C) 2002, 2003 Gordon McNutt
5 // This program is free software; you can redistribute it and/or modify it
6 // under the terms of the GNU General Public License as published by the Free
7 // Software Foundation; either version 2 of the License, or (at your option)
10 // This program is distributed in the hope that it will be useful, but WITHOUT
11 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 // You should have received a copy of the GNU General Public License along with
16 // this program; if not, write to the Free Foundation, Inc., 59 Temple Place,
17 // Suite 330, Boston, MA 02111-1307 USA
20 // gmcnutt@users.sourceforge.net
25 #include "../config.h"
26 #include "character.h"
47 #include "formation.h"
49 #include "Container.h"
53 #include "terrain_map.h" // dbg
58 #include "skill_set.h"
61 #include <ctype.h> // isspace()
67 #include <SDL_image.h>
70 //#include <sys/mman.h>
72 #include <SDL.h> // for SDL_GetTicks()
75 #include "scheme-private.h"
77 struct data_obj_entry {
82 void (*save)(save_t *save, void *obj);
83 void (*start)(void *obj);
86 struct session_hook_entry {
92 struct session *Session = 0;
96 static void session_init_hooks(struct session *session);
97 static void session_cleanup_hooks(struct session *session);
98 static void session_cleanup_queries(struct session *session);
100 /* Redefine the session hook macro to turn its arg into a string, then #include
101 * the list of hooks directly into an array of string pointers. */
102 #undef SESSION_DECL_HOOK
103 #define SESSION_DECL_HOOK(id) #id
104 static const char * session_hook_str[] = {
105 # include "session_hooks.h"
108 static char session_last_error[128] = { 0 };
110 void load_err(const char *fmt, ...)
120 /* duplicate it to the global string error */
122 vsnprintf(session_last_error, sizeof(session_last_error),
128 void rt_err(const char *fmt, ...)
131 warn("runtime error: ");
138 consolePrint("\n*** script error! ***\n");
143 void save_err(const char *fmt, ...)
154 static void data_obj_entry_unref(struct data_obj_entry *entry)
156 assert(entry->refcount >= 1);
158 if (! entry->refcount) {
163 void *session_add(struct session *session, void *obj,
164 void (*dtor)(void *),
165 void (*save)(save_t *, void *),
166 void (*start)(void *)
169 struct data_obj_entry *entry;
171 entry = (struct data_obj_entry *)malloc(sizeof(*entry));
173 list_init(&entry->list);
178 entry->start = start;
180 /* Note: it's important to always add to the tail of the list. This
181 * ensures that the objects in a session will be saved in the same
182 * order they are loaded. The order can matter in certain cases (e.g.,
183 * include files loaded at the front of the session). */
184 list_add_tail(&session->data_objects, &entry->list);
188 void session_rm(struct session *session, void *handle)
190 struct data_obj_entry *entry;
191 entry = (struct data_obj_entry *)handle;
192 list_remove(&entry->list);
193 data_obj_entry_unref(entry);
196 static void session_save_crosshair(save_t *save, struct session *session)
198 save->write(save, "(kern-set-crosshair %s)\n",
199 session->crosshair_type->getTag());
202 static void session_save_damage_sprite(save_t *save, struct session *session)
204 save->write(save, "(kern-set-damage-sprite %s)\n",
205 sprite_get_tag(session->damage_sprite));
208 static void session_save_clock(save_t *save, struct session *session)
210 save->write(save, "(kern-set-clock %d %d %d %d %d %d)\n",
212 session->clock.month,
219 static void session_save_time_accel(save_t *save, struct session *session)
221 save->write(save, "(kern-set-time-accel %f)\n",
222 session->time_accel);
225 static void session_save_turn_count(save_t *save, struct session *session)
227 save->write(save, "(kern-set-turn-count %d)\n",
228 session->turn_count);
231 struct session *session_new(void *interp)
233 struct session *session = (struct session*)calloc(1, sizeof(*session));
235 list_init(&session->data_objects);
236 list_init(&session->terrains);
237 session->interp = interp;
238 session->status_mode = ShowParty;
239 session->los = "angband";
240 sky_init(&session->sky);
241 magic_init(&session->magic);
242 list_init(&session->tickq);
243 list_init(&session->turnq);
244 node_init(&session->sched_chars);
245 list_init(&session->blenders);
246 list_init(&session->skills);
247 list_init(&session->skill_sets);
248 session->time_accel = 1;
249 session_init_hooks(session);
253 void session_del(struct session *session)
256 struct data_obj_entry *entry;
258 /* ref the player party so we can control when it gets destroyed, so we
259 * know when to zero out the global */
260 if (session->player) {
261 obj_inc_ref(session->player);
266 /* stop sound so sound entries will be purged */
269 /* Cleanup the data objects */
270 elem = session->data_objects.next;
272 while (elem != &session->data_objects) {
273 entry = list_entry(elem, struct data_obj_entry, list);
275 entry->refcount++; /* keep a ref while dtor runs */
276 entry->dtor(entry->obj);
277 data_obj_entry_unref(entry); /* now release ref */
281 /* Cleanup the terrains */
282 elem = session->terrains.next;
283 while (elem != &session->terrains) {
284 struct terrain *terrain = list_entry(elem, struct terrain,
287 list_remove(&terrain->session_list);
288 terrain_del(terrain);
291 if (session->crosshair)
292 obj_dec_ref(session->crosshair);
293 sky_end_session(&session->sky);
294 magic_end_session(&session->magic);
296 ptable_del(session->ptable);
298 dtable_del(session->dtable);
300 /* Clean up the turn work queue */
301 elem = session->turnq.next;
302 while (elem != &session->turnq) {
303 struct wq_job *job = list_entry(elem, struct wq_job, list);
308 /* Clean up the tick work queue */
309 elem = session->tickq.next;
310 while (elem != &session->tickq) {
311 struct wq_job *job = list_entry(elem, struct wq_job, list);
316 session_cleanup_hooks(session);
317 session_cleanup_queries(session);
319 /* Ensure that nothing is referencing the player party (except perhaps
320 * its vehicle, which will be cleaned up with the party). Note: if
321 * we're aborting a failed load then the player may not exist. */
322 if (session->player) {
323 assert((1==session->player->refcount)
324 || ((2==session->player->refcount)
325 && session->player->getVehicle()));
327 /* Now zilch the global player party */
328 obj_dec_ref(session->player);
332 /* clean up the list of skills */
333 elem = session->skills.next;
334 while (elem != &session->skills) {
335 struct skill *skill = list_entry(elem, struct skill, list);
337 list_remove(&skill->list);
341 /* clean up the list of skill sets */
342 elem = session->skill_sets.next;
343 while (elem != &session->skill_sets) {
344 struct skill_set *skset
345 = list_entry(elem, struct skill_set, list);
347 list_remove(&skset->list);
348 skill_set_unref(skset);
351 /* cleanup the interpreter */
352 if (session->interp) {
353 scheme_deinit((scheme*)session->interp);
354 free(session->interp);
357 /* Check for memory leaks. Character dtors should have removed
358 * themselves from the sched_chars list if they were on it. */
359 if (! node_list_empty(&session->sched_chars)) {
361 "warn: session sched_chars list non-empty\n");
367 int session_load(char *filename)
371 struct session *old_session;
375 /* Remember the old session in case we have to bail out. */
376 old_session = Session;
378 /* Clear any leftover load errors. */
381 /* Open the load file (this might come from the include dir or the save
382 * dir, so the caller must pass in the complete pathname). */
383 file = file_open(filename, "r");
385 load_err("could not open script file '%s' for reading: %s",
386 filename, strerror(errno));
390 /* Create a new interpreter. */
391 if (! (sc = kern_init())) {
392 load_err("could not create interpreter");
397 /* Create a new current sesssion. */
398 Session = session_new(sc);
401 /* Load the file and close it. */
403 scheme_load_named_file(sc, file, filename);
405 info("%d ms to load\n", (t2 - t1));
409 /* Check for any errors during loading. */
410 if (load_err_any()) {
412 /* Check if the problem is version obsolescence. */
413 if (Session->major < MIN_SCRIPT_MAJOR
414 || Session->minor < MIN_SCRIPT_MINOR
415 || Session->release < MIN_SCRIPT_RELEASE) {
416 snprintf(session_last_error,
417 sizeof(session_last_error),
418 "The save file format is version %u.%u.%u, "
419 "but this release of the engine requires "
420 "version %u.%u.%u or better. You might try "
421 "an older version of the engine with this "
423 Session->major, Session->minor,
424 Session->release, MIN_SCRIPT_MAJOR,
425 MIN_SCRIPT_MINOR, MIN_SCRIPT_RELEASE);
428 session_del(Session);
429 Session = old_session;
433 /* Check for stuff that needs to be there for the new session to
434 * work. Start with some globals. */
435 if (! Session->crosshair_type) {
436 load_err("no crosshair object (use kern-set-crosshair)");
438 Session->crosshair = new Cursor();
439 Session->crosshair->init(Session->crosshair_type);
440 obj_inc_ref(Session->crosshair);
442 if (! Session->damage_sprite) {
443 load_err("no damage sprite (use kern-set-cursor)");
445 if (! Session->clock.set) {
446 load_err("clock not set (use kern-set-clock)");
448 if (! Session->ptable) {
449 load_err("passability table not set (use kern-set-ptable)");
451 if (! Session->dtable) {
452 load_err("diplomacy table not set (use kern-set-dtable)");
455 if (! Session->player) {
456 load_err("no player party");
458 if (Session->player->getSize() == 0) {
459 load_err("player party empty");
463 /* Check for any errors. If there are any then destroy the new session
464 * and return the old one. Otherwise destroy the old session and return
466 if (load_err_any()) {
467 session_del(Session);
470 Session = old_session;
474 /* No errors, so it's safe to delete the old session. */
476 session_del(old_session);
479 combat_reset_state();
481 Session->player->startSession();
483 /* Now setup stuff that with known defaults. */
484 statusSetMode(Session->status_mode);
485 mapSetLosStyle(Session->los);
487 /* Run through all the objects in the world, initializing their
489 list_for_each(&Session->data_objects, elem) {
490 struct data_obj_entry *entry;
491 entry = list_entry(elem, struct data_obj_entry, list);
493 entry->start(entry->obj);
497 /* need to start all the objects in the freezer too */
498 freezer_start_contents();
500 sky_start_session(&Session->sky,
501 NULL != Place && ! Place->underground);
504 // This is also called from place_enter(), which is called when the
505 // game first starts up but apparently not necessarily on a
506 // reload. Should be safe to call more than once, I think.
507 //session_synch_sched_chars(Session);
509 /* Paint all the windows for the first time in the new session. */
511 screen_repaint_frame();
516 /* show the sun, moon and wind status */
517 sky_advance(&Session->sky,
518 NULL != Place && ! Place->underground);
522 session_run_hook(Session, session_start_hook, "p", Session->player);
527 #define SAVE_INDENT_WIDTH 2
528 static void save_vwrite(struct save *save, const char *fmt, va_list args)
531 fprintf(save->file, "%*c", save->indent, ' ');
532 vfprintf(save->file, fmt, args);
535 static void save_write(save_t *save, const char *fmt, ...)
539 save_vwrite(save, fmt, args);
543 static void save_append(save_t *save, const char *fmt, ...)
547 vfprintf(save->file, fmt, args);
551 static void save_enter(save_t *save, const char *fmt, ...)
555 save_vwrite(save, fmt, args);
557 save->indent += SAVE_INDENT_WIDTH;
560 static void save_exit(save_t *save, const char *fmt, ...)
564 save->indent -= SAVE_INDENT_WIDTH;
565 assert(save->indent >= 0);
566 save_vwrite(save, fmt, args);
570 save_t *save_new(FILE *file)
574 save = (save_t*)calloc(1, sizeof(*save));
578 // SAM: Where is indent ever initialized???
579 save->write = save_write;
580 save->enter = save_enter;
581 save->exit = save_exit;
582 save->append = save_append;
587 void save_del(save_t *save)
590 warn("save file indentation left at %d; possible "\
591 "unterminated block in save file", save->indent);
597 int session_save(char *fname)
602 int object_saves = 0;
604 file = file_open_in_save_dir(fname, "w");
606 warn("session_save: could not open %s: %s\n", fname,
611 save = save_new(file);
613 /* Advance the session ID each time we save so that objects can know
614 * that they need to save themselves. */
615 Session->session_id++;
616 save->session_id = Session->session_id;
618 /* Save the header. */
619 save->write(save, ";; %s -- a nazghul session file\n", fname);
620 /* Note: below does not work, and causes false negs with regression
622 /* save->write(save, ";; Created %s\n", ctime(&timep)); */
624 /* Save the standard file. */
625 save->write(save, ";; Load the standard definitions file\n");
626 save->write(save, "(load \"naz.scm\")\n");
627 save->write(save, "\n");
629 /* Write the (new) version */
630 save->write(save, "(kern-script-version \"%s\")\n", PACKAGE_VERSION);
632 /* Generate the first part of the progress bar code. Use the number of
633 * data objects which will save themselves, plus the number of load
634 * files, as the limit. */
635 list_for_each(&Session->data_objects, elem) {
636 struct data_obj_entry *entry;
637 entry = list_entry(elem, struct data_obj_entry, list);
643 save->write(save, ";; Progress bar\n");
644 save->write(save, "(kern-progress-bar-start \"Æɤ߹þ¤ßÃæ\" %d)\n",
645 Session->num_kern_includes + object_saves);
647 /* Generate code to advance the progress bar as each file is loaded. */
648 save->write(save, "(define original-load load) "
649 "(define (load file) "
650 "(kern-progress-bar-advance 1) "
651 "(original-load file))\n");
653 /* Save all the saveable objects. */
654 list_for_each(&Session->data_objects, elem) {
655 struct data_obj_entry *entry;
656 entry = list_entry(elem, struct data_obj_entry, list);
658 entry->save(save, entry->obj);
659 if (save_err_any()) {
660 warn("Aborting save to %s due to errors\n",
665 /* Generate code to advance the progress bar as each
666 * object finishes loading. */
667 save->write(save, "(kern-progress-bar-advance 1)\n");
672 save->write(save, ";;--------------\n");
673 save->write(save, ";; ObjectFreezer\n");
674 save->write(save, ";;--------------\n");
677 /* Save all the special-case stuff... */
678 save->write(save, ";;--------------\n");
679 save->write(save, ";; Miscellaneous\n");
680 save->write(save, ";;--------------\n");
681 session_save_damage_sprite(save, Session);
682 session_save_crosshair(save, Session);
683 session_save_clock(save, Session);
684 session_save_time_accel(save, Session);
685 session_save_turn_count(save, Session);
686 dtable_save(Session->dtable, save);
687 sky_save(&Session->sky, save);
691 save->write(save, "(kern-add-reveal %d)\n", Session->reveal.duration);
692 save->write(save, "(kern-add-quicken %d)\n",
693 Session->quicken.duration);
694 save->write(save, "(kern-add-time-stop %d)\n",
695 Session->time_stop.duration);
696 save->write(save, "(kern-add-magic-negated %d)\n",
697 Session->magic_negated.duration);
698 save->write(save, "(kern-add-xray-vision %d)\n",
699 Session->xray.duration);
701 /* save the work queues */
702 /* NOTE: don't see how we can, since work queue jobs use a void
703 * pointer. We could also require a save callback pointer in the job,
704 * but usually the data is a C pointer which won't be valid on reload,
705 * so I don't know what the save callback can do to help. */
706 /* session_save_wq(&session->turnq); */
707 /* session_save_wq(&session->tickq); */
709 /* Finish progress bar code */
710 save->write(save, "(kern-progress-bar-finish)\n");
717 struct node *session_add_sched_char(struct session *session,
718 class Character *npc)
720 struct node *node = node_new(npc);
721 node_add(&session->sched_chars, node);
725 void session_rm_sched_char(struct node *node)
727 /* beware of doing this while iterationg over the sched_chars list */
732 void session_synch_sched_chars(struct session *session)
734 struct node *node = node_next(&session->sched_chars);
735 while (node != &session->sched_chars) {
736 class Character *npc = (class Character*)node->ptr;
737 node = node_next(node);
739 if (npc->isCharmed())
744 void session_intro_sched_chars(struct session *session)
746 struct node *node = node_next(&session->sched_chars);
747 while (node != &session->sched_chars) {
748 class Character *npc = (class Character*)node->ptr;
750 node = node_next(node);
754 static void session_init_hooks(struct session *session)
758 for (id = 0; id < NUM_HOOKS; id++) {
759 list_init(&session->hook_table[id]);
763 void session_run_hook(struct session *session, session_hook_id_t id, const char *fmt, ...)
765 struct list *lptr, *head;
767 assert(id < NUM_HOOKS);
768 head = &session->hook_table[id];
770 list_for_each(head, lptr) {
771 struct session_hook_entry *entry = list_entry(lptr, struct session_hook_entry, list);
774 closure_execvl(entry->proc, fmt, args, entry->args);
781 void *session_add_hook(struct session *session, session_hook_id_t id, struct closure *proc, pointer args)
784 struct session_hook_entry *entry;
786 assert(id < NUM_HOOKS);
787 head = &session->hook_table[id];
789 if (!(entry = (struct session_hook_entry*)calloc(1, sizeof(*entry)))) {
796 proc->sc->vptr->protect(proc->sc, args);
798 list_add_tail(&session->hook_table[id], &entry->list);
802 void session_rm_hook(struct session *session, session_hook_id_t id, pointer code)
804 struct list *head, *lptr;
805 struct session_hook_entry *entry;
807 assert(id < NUM_HOOKS);
808 head = &session->hook_table[id];
810 while (lptr != head) {
811 entry = list_entry(lptr, struct session_hook_entry, list);
812 struct closure *proc = entry->proc;
813 if (proc->code == code) {
815 scheme *sc = proc->sc;
816 sc->vptr->unprotect(sc, entry->args);
825 static void session_cleanup_hooks(struct session *session)
828 for (i = 0; i < NUM_HOOKS; i++) {
829 struct list *head = &session->hook_table[i];
830 struct list *lptr = head->next;
831 while (lptr != head) {
832 struct session_hook_entry *entry = list_entry(lptr, struct session_hook_entry, list);
834 list_remove(&entry->list);
835 entry->proc->sc->vptr->unprotect(entry->proc->sc, entry->args);
836 closure_unref(entry->proc);
842 int session_run_query(struct session *session, session_query_id_t id, const char *fmt, ...)
844 assert(id < NUM_QUERIES);
845 struct closure *proc = session->query_table[id];
851 result = closure_execv(proc, fmt, args);
853 return closure_translate_result(proc->sc, result);
859 void session_add_query(struct session *session, session_query_id_t id, struct closure *proc)
861 assert(id < NUM_QUERIES);
863 /* out with the old */
864 if (session->query_table[id]) {
865 closure_unref(session->query_table[id]);
866 session->query_table[id] = NULL;
869 /* in with the new */
872 session->query_table[id] = proc;
876 static void session_cleanup_queries(struct session *session)
879 for (i = 0; i < NUM_QUERIES; i++) {
880 if (session->query_table[i]) {
881 closure_unref(session->query_table[i]);
882 session->query_table[i] = NULL;
887 const char *session_hook_id_to_str(session_hook_id_t id)
889 assert(id < NUM_HOOKS);
890 return session_hook_str[id];
893 session_hook_id_t session_str_to_hook_id(char *str)
896 for (id = 0; id < NUM_HOOKS; id++) {
897 if (! strcmp(session_hook_str[id], str)) {
901 return (session_hook_id_t)id;
904 char *session_get_last_error(void)
906 return session_last_error;
909 void session_eval(struct session *session, char *buf)
911 scheme_load_string((scheme *)(session->interp), (const char*)buf);