1 /*
2 * See Licensing and Copyright notice in naev.h
3 */
4
5 /**
6 * @file mission.c
7 *
8 * @brief Handles missions.
9 */
10
11
12 #include "mission.h"
13
14 #include "naev.h"
15
16 #include <stdint.h>
17 #include "nstring.h"
18 #include <stdlib.h>
19
20 #include "nlua.h"
21 #include "nluadef.h"
22 #include "nlua_faction.h"
23 #include "nlua_ship.h"
24 #include "nlua_misn.h"
25 #include "rng.h"
26 #include "log.h"
27 #include "hook.h"
28 #include "ndata.h"
29 #include "nxml.h"
30 #include "nxml_lua.h"
31 #include "faction.h"
32 #include "player.h"
33 #include "space.h"
34 #include "cond.h"
35 #include "gui_osd.h"
36 #include "npc.h"
37 #include "array.h"
38 #include "land.h"
39
40
41 #define XML_MISSION_ID "Missions" /**< XML document identifier */
42 #define XML_MISSION_TAG "mission" /**< XML mission tag. */
43
44 #define MISSION_CHUNK 32 /**< Chunk allocation. */
45
46
47 /*
48 * current player missions
49 */
50 static unsigned int mission_id = 0; /**< Mission ID generator. */
51 Mission *player_missions[MISSION_MAX]; /**< Player's active missions. */
52
53
54 /*
55 * mission stack
56 */
57 static MissionData *mission_stack = NULL; /**< Unmutable after creation */
58 static int mission_nstack = 0; /**< Missions in stack. */
59
60
61 /*
62 * prototypes
63 */
64 /* static */
65 /* Generation. */
66 static unsigned int mission_genID (void);
67 static int mission_init( Mission* mission, MissionData* misn, int genid, int create, unsigned int *id );
68 static void mission_freeData( MissionData* mission );
69 /* Matching. */
70 static int mission_compare( const void* arg1, const void* arg2 );
71 static int mission_meetReq( int mission, int faction,
72 const char* planet, const char* sysname );
73 static int mission_matchFaction( MissionData* misn, int faction );
74 static int mission_location( const char* loc );
75 /* Loading. */
76 static int mission_parse( MissionData* temp, const xmlNodePtr parent );
77 static int missions_parseActive( xmlNodePtr parent );
78 /* externed */
79 int missions_saveActive( xmlTextWriterPtr writer );
80 int missions_loadActive( xmlNodePtr parent );
81
82
83 /**
84 * @brief Generates a new id for the mission.
85 *
86 * @return New id for the mission.
87 */
mission_genID(void)88 static unsigned int mission_genID (void)
89 {
90 unsigned int id;
91 int i;
92 id = ++mission_id; /* default id, not safe if loading */
93
94 /* we save mission ids, so check for collisions with player's missions */
95 for (i=0; i<MISSION_MAX; i++)
96 if (id == player_missions[i]->id) /* mission id was loaded from save */
97 return mission_genID(); /* recursively try again */
98 return id;
99 }
100
101 /**
102 * @brief Gets id from mission name.
103 *
104 * @param name Name to match.
105 * @return id of the matching mission.
106 */
mission_getID(const char * name)107 int mission_getID( const char* name )
108 {
109 int i;
110
111 for (i=0; i<mission_nstack; i++)
112 if (strcmp(name,mission_stack[i].name)==0)
113 return i;
114
115 DEBUG("Mission '%s' not found in stack", name);
116 return -1;
117 }
118
119
120 /**
121 * @brief Gets a MissionData based on ID.
122 *
123 * @param id ID to match.
124 * @return MissonData matching ID.
125 */
mission_get(int id)126 MissionData* mission_get( int id )
127 {
128 if ((id < 0) || (id >= mission_nstack)) return NULL;
129 return &mission_stack[id];
130 }
131
132
133 /**
134 * @brief Gets mission data from a name.
135 */
mission_getFromName(const char * name)136 MissionData* mission_getFromName( const char* name )
137 {
138 int id;
139
140 id = mission_getID( name );
141 if (id < 0)
142 return NULL;
143
144 return mission_get( id );
145 }
146
147
148 /**
149 * @brief Initializes a mission.
150 *
151 * @param mission Mission to initialize.
152 * @param misn Data to use.
153 * @param genid 1 if should generate id, 0 otherwise.
154 * @param create 1 if should run create function, 0 otherwise.
155 * @param[out] id ID of the newly created mission.
156 * @return 0 on success.
157 */
mission_init(Mission * mission,MissionData * misn,int genid,int create,unsigned int * id)158 static int mission_init( Mission* mission, MissionData* misn, int genid, int create, unsigned int *id )
159 {
160 char *buf;
161 uint32_t bufsize;
162 int ret;
163
164 /* clear the mission */
165 memset( mission, 0, sizeof(Mission) );
166
167 /* Create id if needed. */
168 mission->id = (genid) ? mission_genID() : 0;
169 if (id != NULL)
170 *id = mission->id;
171 mission->data = misn;
172 if (create) {
173 mission->title = strdup(misn->name);
174 mission->desc = strdup("No description.");
175 }
176
177 /* init Lua */
178 mission->env = nlua_newEnv(1);
179
180 misn_loadLibs( mission->env ); /* load our custom libraries */
181
182 /* load the file */
183 buf = ndata_read( misn->lua, &bufsize );
184 if (buf == NULL) {
185 WARN("Mission '%s' Lua script not found.", misn->lua );
186 return -1;
187 }
188 if (nlua_dobufenv(mission->env, buf, bufsize, misn->lua) != 0) {
189 WARN("Error loading mission file: %s\n"
190 "%s\n"
191 "Most likely Lua file has improper syntax, please check",
192 misn->lua, lua_tostring(naevL, -1));
193 free(buf);
194 return -1;
195 }
196 free(buf);
197
198 /* run create function */
199 if (create) {
200 /* Failed to create. */
201 ret = misn_run( mission, "create");
202 if (ret) {
203 mission_cleanup(mission);
204 return ret;
205 }
206 }
207
208 return 0;
209 }
210
211
212 /**
213 * @brief Small wrapper for misn_run.
214 *
215 * @param mission Mission to accept.
216 * @return -1 on error, 1 on misn.finish() call, 2 if mission got deleted
217 * and 0 normally.
218 *
219 * @sa misn_run
220 */
mission_accept(Mission * mission)221 int mission_accept( Mission* mission )
222 {
223 return misn_run( mission, "accept" );
224 }
225
226
227 /**
228 * @brief Checks to see if mission is already running.
229 *
230 * @param misn Mission to check if is already running.
231 * @return 1 if already running, 0 if isn't.
232 */
mission_alreadyRunning(MissionData * misn)233 int mission_alreadyRunning( MissionData* misn )
234 {
235 int i;
236 for (i=0; i<MISSION_MAX; i++)
237 if (player_missions[i]->data == misn)
238 return 1;
239 return 0;
240 }
241
242
243 /**
244 * @brief Checks to see if a mission meets the requirements.
245 *
246 * @param mission ID of the mission to check.
247 * @param faction Faction of the current planet.
248 * @param planet Name of the current planet.
249 * @param sysname Name of the current system.
250 * @return 1 if requirements are met, 0 if they aren't.
251 */
mission_meetReq(int mission,int faction,const char * planet,const char * sysname)252 static int mission_meetReq( int mission, int faction,
253 const char* planet, const char* sysname )
254 {
255 MissionData* misn;
256 int c;
257
258 misn = mission_get( mission );
259 if (misn == NULL) /* In case it doesn't exist */
260 return 0;
261
262 /* If planet, must match planet. */
263 if ((misn->avail.planet != NULL) && (strcmp(misn->avail.planet,planet)!=0))
264 return 0;
265
266 /* If system, must match system. */
267 if ((misn->avail.system != NULL) && (strcmp(misn->avail.system,sysname)!=0))
268 return 0;
269
270 /* Match faction. */
271 if ((faction >= 0) && !mission_matchFaction(misn,faction))
272 return 0;
273
274 /* Must not be already done or running if unique. */
275 if (mis_isFlag(misn,MISSION_UNIQUE) &&
276 (player_missionAlreadyDone(mission) ||
277 mission_alreadyRunning(misn)))
278 return 0;
279
280 /* Must meet Lua condition. */
281 if (misn->avail.cond != NULL) {
282 c = cond_check(misn->avail.cond);
283 if (c < 0) {
284 WARN("Conditional for mission '%s' failed to run", misn->name);
285 return 0;
286 }
287 else if (!c)
288 return 0;
289 }
290
291 /* Must meet previous mission requirements. */
292 if ((misn->avail.done != NULL) &&
293 (player_missionAlreadyDone( mission_getID(misn->avail.done) ) == 0))
294 return 0;
295
296 return 1;
297 }
298
299
300 /**
301 * @brief Runs missions matching location, all Lua side and one-shot.
302 *
303 * @param loc Location to match.
304 * @param faction Faction of the planet.
305 * @param planet Name of the current planet.
306 * @param sysname Name of the current system.
307 */
missions_run(int loc,int faction,const char * planet,const char * sysname)308 void missions_run( int loc, int faction, const char* planet, const char* sysname )
309 {
310 MissionData* misn;
311 Mission mission;
312 int i;
313 double chance;
314
315 for (i=0; i<mission_nstack; i++) {
316 misn = &mission_stack[i];
317 if (misn->avail.loc != loc)
318 continue;
319
320 if (!mission_meetReq(i, faction, planet, sysname))
321 continue;
322
323 chance = (double)(misn->avail.chance % 100)/100.;
324 if (chance == 0.) /* We want to consider 100 -> 100% not 0% */
325 chance = 1.;
326
327 if (RNGF() < chance) {
328 mission_init( &mission, misn, 1, 1, NULL );
329 mission_cleanup(&mission); /* it better clean up for itself or we do it */
330 }
331 }
332 }
333
334
335 /**
336 * @brief Starts a mission.
337 *
338 * Mission must still call misn.accept() to actually get added to the player's
339 * active missions.
340 *
341 * @param name Name of the mission to start.
342 * @param[out] id ID of the newly created mission.
343 * @return 0 on success, >0 on forced exit (misn.finish), <0 on error.
344 */
mission_start(const char * name,unsigned int * id)345 int mission_start( const char *name, unsigned int *id )
346 {
347 Mission mission;
348 MissionData *mdat;
349 int ret;
350
351 /* Try to get the mission. */
352 mdat = mission_get( mission_getID(name) );
353 if (mdat == NULL)
354 return -1;
355
356 /* Try to run the mission. */
357 ret = mission_init( &mission, mdat, 1, 1, id );
358 /* Add to mission giver if necessary. */
359 if (landed && (ret==0) && (mdat->avail.loc==MIS_AVAIL_BAR))
360 npc_patchMission( &mission );
361 else
362 mission_cleanup( &mission ); /* Clean up in case not accepted. */
363
364 return ret;
365 }
366
367
368 /**
369 * @brief Adds a system marker to a mission.
370 */
mission_addMarker(Mission * misn,int id,int sys,SysMarker type)371 int mission_addMarker( Mission *misn, int id, int sys, SysMarker type )
372 {
373 MissionMarker *marker;
374 int i, n, m;
375
376 /* Create array. */
377 if (misn->markers == NULL)
378 misn->markers = array_create( MissionMarker );
379
380 /* Avoid ID collisions. */
381 if (id < 0) {
382 m = -1;
383 n = array_size( misn->markers );
384 for (i=0; i<n; i++)
385 if (misn->markers[i].id > m)
386 m = misn->markers[i].id;
387 id = m+1;
388 }
389
390 /* Create the marker. */
391 marker = &array_grow( &misn->markers );
392 marker->id = id;
393 marker->sys = sys;
394 marker->type = type;
395
396 return marker->id;
397 }
398
399
400 /**
401 * @brief Marks all active systems that need marking.
402 */
mission_sysMark(void)403 void mission_sysMark (void)
404 {
405 int i, j, n;
406 MissionMarker *m;
407
408 /* Clear markers. */
409 space_clearMarkers();
410
411 for (i=0; i<MISSION_MAX; i++) {
412 /* Must be a valid player mission. */
413 if (player_missions[i]->id == 0)
414 continue;
415 /* Must have markers. */
416 if (player_missions[i]->markers == NULL)
417 continue;
418
419 n = array_size( player_missions[i]->markers );
420 for (j=0; j<n; j++) {
421 m = &player_missions[i]->markers[j];
422
423 /* Add the individual markers. */
424 space_addMarker( m->sys, m->type );
425 }
426 }
427 }
428
429
430 /**
431 * @brief Marks the system of the computer mission to reflect where it will head to.
432 *
433 * Does not modify other markers.
434 *
435 * @param misn Mission to mark.
436 */
mission_sysComputerMark(Mission * misn)437 void mission_sysComputerMark( Mission* misn )
438 {
439 StarSystem *sys;
440 int i, n;
441
442 /* Clear markers. */
443 space_clearComputerMarkers();
444
445 /* Set all the markers. */
446 if (misn->markers != NULL) {
447 n = array_size(misn->markers);
448 for (i=0; i<n; i++) {
449 sys = system_getIndex( misn->markers[i].sys );
450 sys_setFlag( sys,SYSTEM_CMARKED );
451 }
452 }
453 }
454
455
456 /**
457 * @brief Links cargo to the mission for posterior cleanup.
458 *
459 * @param misn Mission to link cargo to.
460 * @param cargo_id ID of cargo to link.
461 * @return 0 on success.
462 */
mission_linkCargo(Mission * misn,unsigned int cargo_id)463 int mission_linkCargo( Mission* misn, unsigned int cargo_id )
464 {
465 misn->ncargo++;
466 misn->cargo = realloc( misn->cargo, sizeof(unsigned int) * misn->ncargo);
467 misn->cargo[ misn->ncargo-1 ] = cargo_id;
468
469 return 0;
470 }
471
472
473 /**
474 * @brief Unlinks cargo from the mission, removes it from the player.
475 *
476 * @param misn Mission to unlink cargo from.
477 * @param cargo_id ID of cargo to unlink.
478 * @return returns 0 on success.
479 */
mission_unlinkCargo(Mission * misn,unsigned int cargo_id)480 int mission_unlinkCargo( Mission* misn, unsigned int cargo_id )
481 {
482 int i;
483 for (i=0; i<misn->ncargo; i++)
484 if (misn->cargo[i] == cargo_id)
485 break;
486
487 if (i>=misn->ncargo) { /* not found */
488 DEBUG("Mission '%s' attempting to unlink inexistant cargo %d.",
489 misn->title, cargo_id);
490 return 1;
491 }
492
493 /* shrink cargo size - no need to realloc */
494 memmove( &misn->cargo[i], &misn->cargo[i+1],
495 sizeof(unsigned int) * (misn->ncargo-i-1) );
496 misn->ncargo--;
497
498 return 0;
499 }
500
501
502 /**
503 * @brief Cleans up a mission.
504 *
505 * @param misn Mission to clean up.
506 */
mission_cleanup(Mission * misn)507 void mission_cleanup( Mission* misn )
508 {
509 int i, ret;
510
511 /* Hooks and missions. */
512 if (misn->id != 0) {
513 hook_rmMisnParent( misn->id ); /* remove existing hooks */
514 npc_rm_parentMission( misn ); /* remove existing npc */
515 }
516
517 /* Cargo. */
518 if (misn->cargo != NULL) {
519 for (i=0; i<misn->ncargo; i++) { /* must unlink all the cargo */
520 if (player.p != NULL) { /* Only remove if player exists. */
521 ret = pilot_rmMissionCargo( player.p, misn->cargo[i], 0 );
522 if (ret)
523 WARN("Failed to remove mission cargo '%d' for mission '%s'.", misn->cargo[i], misn->title);
524 }
525 }
526 free(misn->cargo);
527 }
528 if (misn->osd > 0)
529 osd_destroy(misn->osd);
530 /*
531 * XXX With the way the mission code works, this function is called on a
532 * Mission struct of all zeros. Looking at the implementation, luaL_ref()
533 * never returns 0, but this is probably undefined behavior.
534 */
535 if (misn->env != LUA_NOREF && misn->env != 0)
536 nlua_freeEnv(misn->env);
537
538 /* Data. */
539 if (misn->title != NULL)
540 free(misn->title);
541 if (misn->desc != NULL)
542 free(misn->desc);
543 if (misn->reward != NULL)
544 free(misn->reward);
545 if (misn->portrait != NULL)
546 gl_freeTexture(misn->portrait);
547 if (misn->npc != NULL)
548 free(misn->npc);
549
550 /* Markers. */
551 if (misn->markers != NULL)
552 array_free( misn->markers );
553
554 /* Claims. */
555 if (misn->claims != NULL)
556 claim_destroy( misn->claims );
557
558 /* Clear the memory. */
559 memset( misn, 0, sizeof(Mission) );
560 }
561
562
563 /**
564 * @brief Puts the specified mission at the end of the player_missions array.
565 *
566 * @param pos Mission's position within player_missions
567 */
mission_shift(int pos)568 void mission_shift( int pos )
569 {
570 Mission *misn;
571
572 if (pos >= (MISSION_MAX-1))
573 return;
574
575 /* Store specified mission. */
576 misn = player_missions[pos];
577
578 /* Move other missions down. */
579 memmove( &player_missions[pos], &player_missions[pos+1],
580 sizeof(Mission*) * (MISSION_MAX - pos - 1) );
581
582 /* Put the specified mission at the end of the array. */
583 player_missions[MISSION_MAX - 1] = misn;
584 }
585
586
587 /**
588 * @brief Frees MissionData.
589 *
590 * @param mission MissionData to free.
591 */
mission_freeData(MissionData * mission)592 static void mission_freeData( MissionData* mission )
593 {
594 if (mission->name)
595 free(mission->name);
596 if (mission->lua)
597 free(mission->lua);
598 if (mission->avail.planet)
599 free(mission->avail.planet);
600 if (mission->avail.system)
601 free(mission->avail.system);
602 if (mission->avail.factions)
603 free(mission->avail.factions);
604 if (mission->avail.cond)
605 free(mission->avail.cond);
606 if (mission->avail.done)
607 free(mission->avail.done);
608
609 /* Clear the memory. */
610 #ifdef DEBUGGING
611 memset( mission, 0, sizeof(MissionData) );
612 #endif /* DEBUGGING */
613 }
614
615
616 /**
617 * @brief Checks to see if a mission matches the faction requirements.
618 *
619 * @param misn Mission to check.
620 * @param faction Faction to check against.
621 * @return 1 if it meets the faction requirement, 0 if it doesn't.
622 */
mission_matchFaction(MissionData * misn,int faction)623 static int mission_matchFaction( MissionData* misn, int faction )
624 {
625 int i;
626
627 /* No faction always accepted. */
628 if (misn->avail.nfactions <= 0)
629 return 1;
630
631 /* Check factions. */
632 for (i=0; i<misn->avail.nfactions; i++)
633 if (faction == misn->avail.factions[i])
634 return 1;
635
636 return 0;
637 }
638
639
640 /**
641 * @brief Activates mission claims.
642 */
missions_activateClaims(void)643 void missions_activateClaims (void)
644 {
645 int i;
646
647 for (i=0; i<MISSION_MAX; i++)
648 if (player_missions[i]->claims != NULL)
649 claim_activate( player_missions[i]->claims );
650 }
651
652
653 /**
654 * @brief Compares to missions to see which has more priority.
655 */
mission_compare(const void * arg1,const void * arg2)656 static int mission_compare( const void* arg1, const void* arg2 )
657 {
658 Mission *m1, *m2;
659
660 /* Get arguments. */
661 m1 = (Mission*) arg1;
662 m2 = (Mission*) arg2;
663
664 /* Check priority - lower is more important. */
665 if (m1->data->avail.priority < m2->data->avail.priority)
666 return +1;
667 else if (m1->data->avail.priority > m2->data->avail.priority)
668 return -1;
669
670 /* Compare NPC. */
671 if ((m1->npc != NULL) && (m2->npc != NULL))
672 return strcmp( m1->npc, m2->npc );
673
674 /* Compare title. */
675 if ((m1->title != NULL) && (m2->title != NULL))
676 return strcmp( m1->title, m2->title );
677
678 /* Tied. */
679 return 0.;
680 }
681
682
683 /**
684 * @brief Generates a mission list. This runs create() so won't work with all
685 * missions.
686 *
687 * @param[out] n Missions created.
688 * @param faction Faction of the planet.
689 * @param planet Name of the planet.
690 * @param sysname Name of the current system.
691 * @param loc Location
692 * @return The stack of Missions created with n members.
693 */
missions_genList(int * n,int faction,const char * planet,const char * sysname,int loc)694 Mission* missions_genList( int *n, int faction,
695 const char* planet, const char* sysname, int loc )
696 {
697 int i,j, m, alloced;
698 double chance;
699 int rep;
700 Mission* tmp;
701 MissionData* misn;
702
703 /* Missions can't be generated by tutorial. */
704 if (player_isTut()) {
705 *n = 0;
706 return NULL;
707 }
708
709 /* Find available missions. */
710 tmp = NULL;
711 m = 0;
712 alloced = 0;
713 for (i=0; i<mission_nstack; i++) {
714 misn = &mission_stack[i];
715 if (misn->avail.loc == loc) {
716
717 /* Must meet requirements. */
718 if (!mission_meetReq(i, faction, planet, sysname))
719 continue;
720
721 /* Must hit chance. */
722 chance = (double)(misn->avail.chance % 100)/100.;
723 if (chance == 0.) /* We want to consider 100 -> 100% not 0% */
724 chance = 1.;
725 rep = MAX(1, misn->avail.chance / 100);
726
727 for (j=0; j<rep; j++) /* random chance of rep appearances */
728 if (RNGF() < chance) {
729 m++;
730 /* Extra allocation. */
731 if (m > alloced) {
732 if (alloced == 0)
733 alloced = 32;
734 else
735 alloced *= 2;
736 tmp = realloc( tmp, sizeof(Mission) * alloced );
737 }
738 /* Initialize the mission. */
739 if (mission_init( &tmp[m-1], misn, 1, 1, NULL ))
740 m--;
741 }
742 }
743 }
744
745 /* Sort. */
746 if (tmp != NULL) {
747 qsort( tmp, m, sizeof(Mission), mission_compare );
748 (*n) = m;
749 }
750 else
751 (*n) = 0;
752
753 return tmp;
754 }
755
756
757 /**
758 * @brief Gets location based on a human readable string.
759 *
760 * @param loc String to get the location of.
761 * @return Location matching loc.
762 */
mission_location(const char * loc)763 static int mission_location( const char* loc )
764 {
765 if (strcmp(loc,"None")==0) return MIS_AVAIL_NONE;
766 else if (strcmp(loc,"Computer")==0) return MIS_AVAIL_COMPUTER;
767 else if (strcmp(loc,"Bar")==0) return MIS_AVAIL_BAR;
768 else if (strcmp(loc,"Outfit")==0) return MIS_AVAIL_OUTFIT;
769 else if (strcmp(loc,"Shipyard")==0) return MIS_AVAIL_SHIPYARD;
770 else if (strcmp(loc,"Land")==0) return MIS_AVAIL_LAND;
771 else if (strcmp(loc,"Commodity")==0) return MIS_AVAIL_COMMODITY;
772 else if (strcmp(loc,"Space")==0) return MIS_AVAIL_SPACE;
773 return -1;
774 }
775
776
777 /**
778 * @brief Parses a node of a mission.
779 *
780 * @param temp Data to load into.
781 * @param parent Node containing the mission.
782 * @return 0 on success.
783 */
mission_parse(MissionData * temp,const xmlNodePtr parent)784 static int mission_parse( MissionData* temp, const xmlNodePtr parent )
785 {
786 xmlNodePtr cur, node;
787
788 #ifdef DEBUGGING
789 /* To check if mission is valid. */
790 int ret;
791 char *buf;
792 uint32_t len;
793 #endif /* DEBUGGING */
794
795 /* Clear memory. */
796 memset( temp, 0, sizeof(MissionData) );
797
798 /* Defaults. */
799 temp->avail.priority = 5;
800
801 /* get the name */
802 temp->name = xml_nodeProp(parent,"name");
803 if (temp->name == NULL)
804 WARN("Mission in "MISSION_DATA_PATH" has invalid or no name");
805
806 node = parent->xmlChildrenNode;
807
808 char str[PATH_MAX] = "\0";
809
810 do { /* load all the data */
811
812 /* Only handle nodes. */
813 xml_onlyNodes(node);
814
815 if (xml_isNode(node,"lua")) {
816 nsnprintf( str, PATH_MAX, MISSION_LUA_PATH"%s.lua", xml_get(node) );
817 temp->lua = strdup( str );
818 str[0] = '\0';
819
820 #ifdef DEBUGGING
821 /* Check to see if syntax is valid. */
822 buf = ndata_read( temp->lua, &len );
823 ret = luaL_loadbuffer(naevL, buf, len, temp->name );
824 if (ret == LUA_ERRSYNTAX) {
825 WARN("Mission Lua '%s' of mission '%s' syntax error: %s",
826 temp->name, temp->lua, lua_tostring(naevL,-1) );
827 } else {
828 lua_pop(naevL, 1);
829 }
830 free(buf);
831 #endif /* DEBUGGING */
832
833 continue;
834 }
835 else if (xml_isNode(node,"flags")) { /* set the various flags */
836 cur = node->children;
837 do {
838 xml_onlyNodes(cur);
839 if (xml_isNode(cur,"unique")) {
840 mis_setFlag(temp,MISSION_UNIQUE);
841 continue;
842 }
843 WARN("Mission '%s' has unknown flag node '%s'.", temp->name, cur->name);
844 } while (xml_nextNode(cur));
845 continue;
846 }
847 else if (xml_isNode(node,"avail")) { /* mission availability */
848 cur = node->children;
849 do {
850 xml_onlyNodes(cur);
851 if (xml_isNode(cur,"location")) {
852 temp->avail.loc = mission_location( xml_get(cur) );
853 continue;
854 }
855 xmlr_int(cur,"chance",temp->avail.chance);
856 xmlr_strd(cur,"planet",temp->avail.planet);
857 xmlr_strd(cur,"system",temp->avail.system);
858 if (xml_isNode(cur,"faction")) {
859 temp->avail.factions = realloc( temp->avail.factions,
860 sizeof(int) * ++temp->avail.nfactions );
861 temp->avail.factions[temp->avail.nfactions-1] =
862 faction_get( xml_get(cur) );
863 continue;
864 }
865 xmlr_strd(cur,"cond",temp->avail.cond);
866 xmlr_strd(cur,"done",temp->avail.done);
867 xmlr_int(cur,"priority",temp->avail.priority);
868 WARN("Mission '%s' has unknown avail node '%s'.", temp->name, cur->name);
869 } while (xml_nextNode(cur));
870 continue;
871 }
872
873 DEBUG("Unknown node '%s' in mission '%s'",node->name,temp->name);
874 } while (xml_nextNode(node));
875
876 #define MELEMENT(o,s) \
877 if (o) WARN("Mission '%s' missing/invalid '"s"' element", temp->name)
878 MELEMENT(temp->lua==NULL,"lua");
879 MELEMENT(temp->avail.loc==-1,"location");
880 MELEMENT((temp->avail.loc!=MIS_AVAIL_NONE) && (temp->avail.chance==0),"chance");
881 #undef MELEMENT
882
883 return 0;
884 }
885
886
887 /**
888 * @brief Loads all the mission data.
889 *
890 * @return 0 on success.
891 */
missions_load(void)892 int missions_load (void)
893 {
894 int i, m;
895 uint32_t bufsize;
896 char *buf;
897
898 for (i=0; i<MISSION_MAX; i++)
899 player_missions[i] = calloc(1, sizeof(Mission));
900
901 buf = ndata_read( MISSION_DATA_PATH, &bufsize );
902
903 xmlNodePtr node;
904 xmlDocPtr doc = xmlParseMemory( buf, bufsize );
905
906 node = doc->xmlChildrenNode;
907 if (!xml_isNode(node,XML_MISSION_ID)) {
908 ERR("Malformed '"MISSION_DATA_PATH"' file: missing root element '"XML_MISSION_ID"'");
909 return -1;
910 }
911
912 node = node->xmlChildrenNode; /* first mission node */
913 if (node == NULL) {
914 ERR("Malformed '"MISSION_DATA_PATH"' file: does not contain elements");
915 return -1;
916 }
917
918 m = 0;
919 do {
920 if (xml_isNode(node,XML_MISSION_TAG)) {
921
922 /* See if must grow. */
923 mission_nstack++;
924 if (mission_nstack > m) {
925 m += MISSION_CHUNK;
926 mission_stack = realloc(mission_stack, sizeof(MissionData)*m);
927 }
928
929 /* Load it. */
930 mission_parse( &mission_stack[mission_nstack-1], node );
931 }
932 } while (xml_nextNode(node));
933
934 /* Shrink to minimum. */
935 mission_stack = realloc(mission_stack, sizeof(MissionData)*mission_nstack);
936
937 /* Clean up. */
938 xmlFreeDoc(doc);
939 free(buf);
940
941 DEBUG("Loaded %d Mission%s", mission_nstack, (mission_nstack==1) ? "" : "s" );
942
943 return 0;
944 }
945
946
947 /**
948 * @brief Frees all the mission data.
949 */
missions_free(void)950 void missions_free (void)
951 {
952 int i;
953
954 /* Free all the player missions. */
955 missions_cleanup();
956
957 /* Free the mission data. */
958 for (i=0; i<mission_nstack; i++)
959 mission_freeData( &mission_stack[i] );
960 free( mission_stack );
961 mission_stack = NULL;
962 mission_nstack = 0;
963
964 /* Free the player mission stack. */
965 for (i=0; i<MISSION_MAX; i++)
966 free(player_missions[i]);
967 }
968
969
970 /**
971 * @brief Cleans up all the player's active missions.
972 */
missions_cleanup(void)973 void missions_cleanup (void)
974 {
975 int i;
976
977 for (i=0; i<MISSION_MAX; i++)
978 mission_cleanup( player_missions[i] );
979 }
980
981
982 /**
983 * @brief Saves the player's active missions.
984 *
985 * @param writer XML Write to use to save missions.
986 * @return 0 on success.
987 */
missions_saveActive(xmlTextWriterPtr writer)988 int missions_saveActive( xmlTextWriterPtr writer )
989 {
990 int i,j,n;
991 int nitems;
992 char **items;
993
994 xmlw_startElem(writer,"missions");
995
996 for (i=0; i<MISSION_MAX; i++) {
997 if (player_missions[i]->id != 0) {
998 xmlw_startElem(writer,"mission");
999
1000 /* data and id are attributes because they must be loaded first */
1001 xmlw_attr(writer,"data","%s",player_missions[i]->data->name);
1002 xmlw_attr(writer,"id","%u",player_missions[i]->id);
1003
1004 xmlw_elem(writer,"title","%s",player_missions[i]->title);
1005 xmlw_elem(writer,"desc","%s",player_missions[i]->desc);
1006 xmlw_elem(writer,"reward","%s",player_missions[i]->reward);
1007
1008 /* Markers. */
1009 xmlw_startElem( writer, "markers" );
1010 if (player_missions[i]->markers != NULL) {
1011 n = array_size( player_missions[i]->markers );
1012 for (j=0; j<n; j++) {
1013 xmlw_startElem(writer,"marker");
1014 xmlw_attr(writer,"id","%d",player_missions[i]->markers[j].id);
1015 xmlw_attr(writer,"type","%d",player_missions[i]->markers[j].type);
1016 xmlw_str(writer,"%s", system_getIndex(player_missions[i]->markers[j].sys)->name);
1017 xmlw_endElem(writer); /* "marker" */
1018 }
1019 }
1020 xmlw_endElem( writer ); /* "markers" */
1021
1022 /* Cargo */
1023 xmlw_startElem(writer,"cargos");
1024 for (j=0; j<player_missions[i]->ncargo; j++)
1025 xmlw_elem(writer,"cargo","%u", player_missions[i]->cargo[j]);
1026 xmlw_endElem(writer); /* "cargos" */
1027
1028 /* OSD. */
1029 if (player_missions[i]->osd > 0) {
1030 xmlw_startElem(writer,"osd");
1031
1032 /* Save attributes. */
1033 items = osd_getItems(player_missions[i]->osd, &nitems);
1034 xmlw_attr(writer,"title","%s",osd_getTitle(player_missions[i]->osd));
1035 xmlw_attr(writer,"nitems","%d",nitems);
1036 xmlw_attr(writer,"active","%d",osd_getActive(player_missions[i]->osd));
1037
1038 /* Save messages. */
1039 for (j=0; j<nitems; j++)
1040 xmlw_elem(writer,"msg","%s",items[j]);
1041
1042 xmlw_endElem(writer); /* "osd" */
1043 }
1044
1045 /* Claims. */
1046 xmlw_startElem(writer,"claims");
1047 claim_xmlSave( writer, player_missions[i]->claims );
1048 xmlw_endElem(writer); /* "claims" */
1049
1050 /* Write Lua magic */
1051 xmlw_startElem(writer,"lua");
1052 nxml_persistLua( player_missions[i]->env, writer );
1053 xmlw_endElem(writer); /* "lua" */
1054
1055 xmlw_endElem(writer); /* "mission" */
1056 }
1057 }
1058
1059 xmlw_endElem(writer); /* "missions" */
1060
1061 return 0;
1062 }
1063
1064
1065 /**
1066 * @brief Loads the player's active missions from a save.
1067 *
1068 * @param parent Node containing the player's active missions.
1069 * @return 0 on success.
1070 */
missions_loadActive(xmlNodePtr parent)1071 int missions_loadActive( xmlNodePtr parent )
1072 {
1073 xmlNodePtr node;
1074
1075 /* cleanup old missions */
1076 missions_cleanup();
1077
1078 node = parent->xmlChildrenNode;
1079 do {
1080 if (xml_isNode(node,"missions"))
1081 if (missions_parseActive( node ) < 0) return -1;
1082 } while (xml_nextNode(node));
1083
1084 return 0;
1085 }
1086
1087
1088 /**
1089 * @brief Parses the actual individual mission nodes.
1090 *
1091 * @param parent Parent node to parse.
1092 * @return 0 on success.
1093 */
missions_parseActive(xmlNodePtr parent)1094 static int missions_parseActive( xmlNodePtr parent )
1095 {
1096 Mission *misn;
1097 MissionData *data;
1098 int m, i;
1099 char *buf;
1100 char *title;
1101 const char **items;
1102 int nitems;
1103 int id, sys, type;
1104 StarSystem *ssys;
1105
1106 xmlNodePtr node, cur, nest;
1107
1108 m = 0; /* start with mission 0 */
1109 node = parent->xmlChildrenNode;
1110 do {
1111 if (xml_isNode(node,"mission")) {
1112 misn = player_missions[m];
1113
1114 /* process the attributes to create the mission */
1115 xmlr_attr(node,"data",buf);
1116 data = mission_get(mission_getID(buf));
1117 if (data == NULL) {
1118 WARN("Mission '%s' from savegame not found in game - ignoring.", buf);
1119 free(buf);
1120 continue;
1121 }
1122 else {
1123 if (mission_init( misn, data, 0, 0, NULL )) {
1124 WARN("Mission '%s' from savegame failed to load properly - ignoring.", buf);
1125 free(buf);
1126 continue;
1127 }
1128 misn->accepted = 1;
1129 }
1130 free(buf);
1131
1132 /* this will orphan an identifier */
1133 xmlr_attr(node,"id",buf);
1134 misn->id = atol(buf);
1135 free(buf);
1136
1137 cur = node->xmlChildrenNode;
1138 do {
1139
1140 xmlr_strd(cur,"title",misn->title);
1141 xmlr_strd(cur,"desc",misn->desc);
1142 xmlr_strd(cur,"reward",misn->reward);
1143
1144 /* Get the markers. */
1145 if (xml_isNode(cur,"markers")) {
1146 nest = cur->xmlChildrenNode;
1147 do {
1148 if (xml_isNode(nest,"marker")) {
1149 /* Get ID. */
1150 xmlr_attr(nest,"id",buf);
1151 id = (buf != NULL) ? atoi(buf) : -1;
1152 if (buf != NULL)
1153 free(buf);
1154 /* Get type. */
1155 xmlr_attr(nest,"type",buf);
1156 type = (buf != NULL) ? atoi(buf) : -1;
1157 if (buf != NULL)
1158 free(buf);
1159 /* Get system. */
1160 ssys = system_get( xml_get( nest ));
1161 if (ssys == NULL) {
1162 WARN( "System Marker to '%s' does not exist", xml_get( nest ) );
1163 continue;
1164 }
1165 sys = system_index( ssys );
1166 mission_addMarker( misn, id, sys, type );
1167 }
1168 } while (xml_nextNode(nest));
1169 }
1170
1171 /* Cargo. */
1172 if (xml_isNode(cur,"cargos")) {
1173 nest = cur->xmlChildrenNode;
1174 do {
1175 if (xml_isNode(nest,"cargo"))
1176 mission_linkCargo( misn, xml_getLong(nest) );
1177 } while (xml_nextNode(nest));
1178 }
1179
1180 /* OSD. */
1181 if (xml_isNode(cur,"osd")) {
1182 xmlr_attr(cur,"nitems",buf);
1183 if (buf != NULL) {
1184 nitems = atoi(buf);
1185 free(buf);
1186 }
1187 else
1188 continue;
1189 xmlr_attr(cur,"title",title);
1190 items = malloc( nitems * sizeof(char*) );
1191 i = 0;
1192 nest = cur->xmlChildrenNode;
1193 do {
1194 if (xml_isNode(nest,"msg")) {
1195 if (i > nitems) {
1196 WARN("Inconsistency with 'nitems' in savefile.");
1197 break;
1198 }
1199 items[i] = xml_get(nest);
1200 i++;
1201 }
1202 } while (xml_nextNode(nest));
1203
1204 /* Create the osd. */
1205 misn->osd = osd_create( title, nitems, items, data->avail.priority );
1206 free(items);
1207 free(title);
1208
1209 /* Set active. */
1210 xmlr_attr(cur,"active",buf);
1211 if (buf != NULL) {
1212 osd_active( misn->osd, atoi(buf) );
1213 free(buf);
1214 }
1215 }
1216
1217 /* Claims. */
1218 if (xml_isNode(cur,"claims"))
1219 misn->claims = claim_xmlLoad( cur );
1220
1221 if (xml_isNode(cur,"lua"))
1222 /* start the unpersist routine */
1223 nxml_unpersistLua( misn->env, cur );
1224
1225 } while (xml_nextNode(cur));
1226
1227
1228
1229 m++; /* next mission */
1230 if (m >= MISSION_MAX) break; /* full of missions, must be an error */
1231 }
1232 } while (xml_nextNode(node));
1233
1234 return 0;
1235 }
1236
1237
1238