1 /*
2 *
3 * Copyright (c) 1994, 2002, 2003 Johannes Prix
4 * Copyright (c) 1994, 2002 Reinhard Prix
5 * Copyright (c) 2004-2010 Arthur Huillet
6 *
7 *
8 * This file is part of Freedroid
9 *
10 * Freedroid is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * Freedroid is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with Freedroid; see the file COPYING. If not, write to the
22 * Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
23 * MA 02111-1307 USA
24 *
25 */
26
27 /**
28 * This file contains (all?) map-related functions, which also includes
29 * loading of decks and whole ships, starting the lifts and consoles if
30 * close to the paradroid, refreshes as well as determining the map brick
31 * that contains specified coordinates are done in this file.
32 */
33
34 #define _map_c
35
36 #include "system.h"
37
38 #include "defs.h"
39 #include "struct.h"
40 #include "proto.h"
41 #include "global.h"
42
43 #include "lvledit/lvledit_actions.h"
44 #include "lvledit/lvledit_display.h"
45 #include "map.h"
46
47 #define TELEPORT_PAIR_STRING "teleport pair:"
48
49 void GetThisLevelsDroids(char *section_pointer);
50
51 /*
52 * Initialize a map tile with default values.
53 */
init_map_tile(struct map_tile * tile)54 void init_map_tile(struct map_tile* tile)
55 {
56 int i;
57 tile->floor_values[0] = ISO_FLOOR_SAND;
58 for (i = 1; i < MAX_FLOOR_LAYERS; i++)
59 tile->floor_values[i] = ISO_FLOOR_EMPTY;
60 dynarray_init(&tile->glued_obstacles, 0, sizeof(int));
61 INIT_LIST_HEAD(&tile->volatile_obstacles);
62 tile->timestamp = 0;
63 }
64
65 /**
66 * This function will make all blood obstacles vanish, all dead bots come
67 * back to life, and get all bots return to a wandering state.
68 */
respawn_level(int level_num)69 void respawn_level(int level_num)
70 {
71 enemy *erot, *nerot;
72
73 int wp_num = curShip.AllLevels[level_num]->waypoints.size;
74 char wp_used[wp_num]; // is a waypoint already used ?
75 memset(wp_used, 0, wp_num);
76
77 // First we remove all the volatile obstacles...
78 //
79 remove_volatile_obstacles(level_num);
80
81 // Now we can give new life to dead bots...
82 //
83 BROWSE_DEAD_BOTS_SAFE(erot, nerot) {
84 if (erot->pos.z != level_num || Droidmap[erot->type].is_human || !erot->will_respawn)
85 continue;
86 /* Move the bot to the alive list */
87 list_move(&(erot->global_list), &alive_bots_head);
88 /* Reinsert it into the current level list */
89 list_add(&(erot->level_list), &level_bots_head[level_num]);
90 }
91
92 // Finally, we reset the runtime attributes of the bots, place them
93 // on a waypoint, and ask them to start wandering...
94 //
95 BROWSE_LEVEL_BOTS(erot, level_num) {
96
97 // Unconditional reset of the 'transient state' attributes
98 enemy_reset(erot);
99
100 // Conditional reset of some 'global state' attributes
101 if (erot->has_been_taken_over) {
102 erot->faction = FACTION_BOTS;
103 erot->has_been_taken_over = FALSE;
104 erot->CompletelyFixed = FALSE;
105 erot->follow_tux = FALSE;
106 }
107
108 erot->has_greeted_influencer = FALSE;
109
110 if (wp_num) {
111 // Re-place the bots onto the waypoint system
112 if (!erot->SpecialForce) {
113 // Standard bots are randomly placed on one waypoint
114 int wp = teleport_to_random_waypoint(erot, curShip.AllLevels[level_num], wp_used);
115 wp_used[wp] = 1;
116 erot->homewaypoint = erot->lastwaypoint;
117 erot->combat_state = SELECT_NEW_WAYPOINT;
118 erot->state_timeout = 0.0;
119 } else {
120 if (erot->homewaypoint == -1) {
121 // If a special force droid has not yet been integrated onto
122 // the waypoint system, place it near its current position.
123 int wp = teleport_to_closest_waypoint(erot);
124 wp_used[wp] = 1;
125 erot->homewaypoint = erot->lastwaypoint;
126 erot->combat_state = SELECT_NEW_WAYPOINT;
127 erot->state_timeout = 0.0;
128 } else {
129 // Consider that the nextwaypoint of a special force droid
130 // is occupied, so that a standard bot will not be placed here
131 if (erot->nextwaypoint != -1)
132 wp_used[erot->nextwaypoint] = 1;
133 }
134 }
135 } else {
136 error_message(__FUNCTION__, "There is no waypoint on level %d - unable to place random bots.",
137 PLEASE_INFORM, level_num);
138 }
139 }
140 }
141
142 /**
143 * \brief Get the center coordinates of a given map label.
144 * In case of fail, a fatal error is thrown.
145 * \param map_label The name of map label to resolve.
146 * \return The gps center of the map label.
147 */
get_map_label_center(const char * label)148 gps get_map_label_center(const char *label)
149 {
150 struct map_label *m;
151 gps position = {0., 0., -1};
152 int i;
153
154 for (i = 0; i < curShip.num_levels; i++) {
155 if (!level_exists(i))
156 continue;
157
158 m = get_map_label(curShip.AllLevels[i], label);
159 if (m) {
160 position.x = m->pos.x + 0.5;
161 position.y = m->pos.y + 0.5;
162 position.z = i;
163 return position;
164 }
165 }
166
167 error_message(__FUNCTION__, "\
168 Resolving map label \"%s\" failed on the entire world!\n\
169 This is a severe error in the game data of FreedroidRPG.", PLEASE_INFORM, label);
170
171 return position;
172 };
173
174 /**
175 * Next we extract the level interface data from the human-readable data
176 * into the level struct, but WITHOUT destroying or damaging the
177 * human-readable data in the process!
178 */
decode_interfaces(level * loadlevel,char * DataPointer)179 static void decode_interfaces(level *loadlevel, char *DataPointer)
180 {
181 char *TempSectionPointer;
182 char PreservedLetter;
183
184 // We look for the beginning and end of the map statement section
185 TempSectionPointer = LocateStringInData(DataPointer, MAP_BEGIN_STRING);
186
187 // We add a terminator at the end, but ONLY TEMPORARY. The damage will be restored later!
188 PreservedLetter = TempSectionPointer[0];
189 TempSectionPointer[0] = 0;
190
191 ReadValueFromString(DataPointer, "jump target north: ", "%d", &(loadlevel->jump_target_north), TempSectionPointer);
192 ReadValueFromString(DataPointer, "jump target south: ", "%d", &(loadlevel->jump_target_south), TempSectionPointer);
193 ReadValueFromString(DataPointer, "jump target east: ", "%d", &(loadlevel->jump_target_east), TempSectionPointer);
194 ReadValueFromString(DataPointer, "jump target west: ", "%d", &(loadlevel->jump_target_west), TempSectionPointer);
195
196 TempSectionPointer[0] = PreservedLetter;
197
198 }
199
decode_dimensions(level * loadlevel,char * DataPointer)200 static void decode_dimensions(level *loadlevel, char *DataPointer)
201 {
202
203 int off = 0;
204
205 /* Read levelnumber */
206 char *fp = DataPointer;
207 fp += strlen(LEVEL_HEADER_LEVELNUMBER);
208 while (*(fp + off) != '\n')
209 off++;
210 fp[off] = 0;
211 loadlevel->levelnum = atoi(fp);
212 fp[off] = '\n';
213 fp += off + 1;
214 off = 0;
215
216 /* Read xlen */
217 fp += strlen("xlen of this level:");
218 while (*(fp + off) != '\n')
219 off++;
220 fp[off] = 0;
221 loadlevel->xlen = atoi(fp);
222 fp[off] = '\n';
223 fp += off + 1;
224 off = 0;
225
226 /* Read ylen */
227 fp += strlen("ylen of this level:");
228 while (*(fp + off) != '\n')
229 off++;
230 fp[off] = 0;
231 loadlevel->ylen = atoi(fp);
232 fp[off] = '\n';
233 fp += off + 1;
234 off = 0;
235
236 /* Read floor_layers */
237 fp += strlen("floor layers:");
238 while (*(fp + off) != '\n')
239 off++;
240 fp[off] = 0;
241 loadlevel->floor_layers = atoi(fp);
242 fp[off] = '\n';
243 fp += off + 1;
244 off = 0;
245
246 /* Read lrb */
247 fp += strlen("light radius bonus of this level:");
248 while (*(fp + off) != '\n')
249 off++;
250 fp[off] = 0;
251 loadlevel->light_bonus = atoi(fp);
252 fp[off] = '\n';
253 fp += off + 1;
254 off = 0;
255
256 fp += strlen("minimal light on this level:");
257 while (*(fp + off) != '\n')
258 off++;
259 fp[off] = 0;
260 loadlevel->minimum_light_value = atoi(fp);
261 fp[off] = '\n';
262 fp += off + 1;
263 off = 0;
264
265 fp += strlen("infinite_running_on_this_level:");
266 while (*(fp + off) != '\n')
267 off++;
268 fp[off] = 0;
269 loadlevel->infinite_running_on_this_level = atoi(fp);
270 fp[off] = '\n';
271 fp += off + 1;
272 off = 0;
273
274 fp += strlen("random dungeon:");
275 while (*(fp + off) != '\n')
276 off++;
277 fp[off] = 0;
278 loadlevel->random_dungeon = atoi(fp);
279 fp[off] = '\n';
280 fp += off + 1;
281 off = 0;
282
283 if (!strncmp(fp, TELEPORT_PAIR_STRING, strlen(TELEPORT_PAIR_STRING))) {
284 fp += strlen(TELEPORT_PAIR_STRING);
285 while (*(fp + off) != '\n')
286 off++;
287 fp[off] = 0;
288 loadlevel->teleport_pair = atoi(fp);
289 fp[off] = '\n';
290 fp += off + 1;
291 off = 0;
292 } else {
293 loadlevel->teleport_pair = 0;
294 }
295
296 if (!strncmp(fp, "dungeon generated:", 18)) {
297 fp += strlen("dungeon generated:");
298 while (*(fp + off) != '\n')
299 off++;
300 fp[off] = 0;
301 loadlevel->dungeon_generated = atoi(fp);
302 fp[off] = '\n';
303 fp += off + 1;
304 off = 0;
305 } else {
306 loadlevel->dungeon_generated = 0;
307 }
308
309 if (!strncmp(fp, "environmental flags:", 20)) {
310 fp += strlen("environmental flags:");
311 while (*(fp + off) != '\n')
312 off++;
313 fp[off] = 0;
314 loadlevel->flags = atoi(fp);
315 fp[off] = '\n';
316 fp += off + 1;
317 off = 0;
318 } else {
319 loadlevel->flags = 0;
320 }
321
322 if (!strncmp(fp, "item drop class:", 16)) {
323 fp += strlen("item drop class:");
324 while (*(fp + off) != '\n')
325 off++;
326 fp[off] = 0;
327 loadlevel->drop_class = atoi(fp);
328 fp[off] = '\n';
329 // Note: to be commented out if an other decoding part is added after this one
330 // fp += off + 1;
331 // off = 0;
332 } else {
333 loadlevel->drop_class = -1;
334 }
335
336 // Note: if an other decoding part is added, do not forget to comment out the 2 lines
337 // above...
338
339 if (loadlevel->ylen >= MAX_MAP_LINES) {
340 error_message(__FUNCTION__, "\
341 A map/level in FreedroidRPG which was supposed to load has more map lines than allowed\n\
342 for a map/level as by the constant MAX_MAP_LINES in defs.h.\n\
343 Sorry, but unless this constant is raised, FreedroidRPG will refuse to load this map.", PLEASE_INFORM | IS_FATAL);
344 }
345 }
346
decode_random_droids(level * loadlevel,char * data)347 static void decode_random_droids(level *loadlevel, char *data)
348 {
349 char *search_ptr;
350 char *end_ptr;
351
352 #define DROIDS_NUMBER_INDICATION_STRING "number of random droids: "
353 #define ALLOWED_TYPE_INDICATION_STRING "random droid types: "
354
355 // Read the number of random droids for this level
356 end_ptr = strstr(data, ALLOWED_TYPE_INDICATION_STRING);
357 ReadValueFromString(data, DROIDS_NUMBER_INDICATION_STRING, "%d", &loadlevel->random_droids.nr, end_ptr);
358
359 if (loadlevel->random_droids.nr <= 0)
360 return;
361
362 // Now we read in the type(s) of random droids for this level
363 data = strstr(data, ALLOWED_TYPE_INDICATION_STRING);
364
365 search_ptr = ReadAndMallocStringFromDataOptional(data, ALLOWED_TYPE_INDICATION_STRING, "\n");
366 if (search_ptr) {
367 char *droid_type_ptr = search_ptr;
368 while (*droid_type_ptr) {
369 while (*droid_type_ptr && isspace(*droid_type_ptr)) {
370 droid_type_ptr++;
371 }
372 int droid_type_length = 0;
373 char *ptr = droid_type_ptr;
374 while (isalnum(*ptr)) {
375 ptr++;
376 droid_type_length++;
377 }
378 if (!droid_type_length)
379 break;
380
381 char type_indication_string[droid_type_length + 1];
382 strncpy(type_indication_string, droid_type_ptr, droid_type_length);
383 type_indication_string[droid_type_length] = 0;
384
385 int droid_type = get_droid_type(type_indication_string);
386
387 loadlevel->random_droids.types[loadlevel->random_droids.types_size++] = droid_type;
388
389 droid_type_ptr += droid_type_length;
390 if (*droid_type_ptr)
391 droid_type_ptr++; //skip the comma
392 }
393 free(search_ptr);
394 }
395 }
396
decode_header(level * loadlevel,char * data)397 static int decode_header(level *loadlevel, char *data)
398 {
399 data = strstr(data, LEVEL_HEADER_LEVELNUMBER);
400 if (!data)
401 return 1;
402
403 decode_interfaces(loadlevel, data);
404 decode_dimensions(loadlevel, data);
405 decode_random_droids(loadlevel, data);
406
407 // Read the levelname.
408 loadlevel->Levelname = ReadAndMallocStringFromData(data, LEVEL_NAME_STRING, "\"");
409
410 loadlevel->Background_Song_Name = ReadAndMallocStringFromData(data, BACKGROUND_SONG_NAME_STRING, "\n");
411
412 return 0;
413 }
414
415 /**
416 * Next we extract the human readable obstacle data into the level struct
417 * WITHOUT destroying or damaging the human-readable data in the process!
418 * This is an improved parser that is not quite readable but very performant.
419 */
decode_obstacles(level * loadlevel,char * DataPointer)420 static char *decode_obstacles(level *loadlevel, char *DataPointer)
421 {
422 int i;
423 char *curfield = NULL;
424 char *curfieldend = NULL;
425 char *obstacle_SectionBegin;
426 float x, y;
427 int type;
428
429 // First we initialize the obstacles with 'empty' information
430 //
431 for (i = 0; i < MAX_OBSTACLES_ON_MAP; i++) {
432 loadlevel->obstacle_list[i].type = -1;
433 loadlevel->obstacle_list[i].pos.x = -1;
434 loadlevel->obstacle_list[i].pos.y = -1;
435 loadlevel->obstacle_list[i].pos.z = loadlevel->levelnum;
436 loadlevel->obstacle_list[i].timestamp = 0;
437 loadlevel->obstacle_list[i].frame_index = 0;
438 }
439
440 if (loadlevel->random_dungeon && !loadlevel->dungeon_generated)
441 return DataPointer;
442
443 // Now we look for the beginning and end of the obstacle section
444 //
445 obstacle_SectionBegin = LocateStringInData(DataPointer, OBSTACLE_DATA_BEGIN_STRING) + strlen(OBSTACLE_DATA_BEGIN_STRING) + 1;
446
447 // Now we decode all the obstacle information
448 //
449 curfield = obstacle_SectionBegin;
450 while (*curfield != '/') {
451 //structure of obstacle entry is : // t59 x2.50 y63.50 l-1 d-1
452 //we read the type
453 curfield++;
454 curfieldend = curfield;
455 while ((*curfieldend) != ' ')
456 curfieldend++;
457 (*curfieldend) = 0;
458 type = atoi(curfield);
459 (*curfieldend) = ' ';
460
461 //we read the X position
462 curfield = curfieldend + 2;
463 curfieldend += 2;
464 while ((*curfieldend) != ' ')
465 curfieldend++;
466 (*curfieldend) = 0;
467 x = atof(curfield);
468 (*curfieldend) = ' ';
469
470 //Y position
471 curfield = curfieldend + 2;
472 curfieldend += 2;
473 while ((*curfieldend) != ' ')
474 curfieldend++;
475 (*curfieldend) = 0;
476 y = atof(curfield);
477 (*curfieldend) = ' ';
478
479 while ((*curfield) != '\n')
480 curfield++;
481 curfield++;
482
483 // Even invalid obstacles are loaded. They can not be removed at this
484 // point, or else obstacle extensions will point to the wrong obstacles.
485 // decode_level(), our callee, will take care of them.
486 add_obstacle_nocheck(loadlevel, x, y, type);
487 }
488
489 return curfield;
490 }
491
492 /**
493 * Next we extract the map labels of this level WITHOUT destroying
494 * or damaging the data in the process!
495 */
decode_map_labels(level * loadlevel,char * data)496 static char *decode_map_labels(level *loadlevel, char *data)
497 {
498 char *label_name;
499 int i, x, y;
500
501 // Initialize map labels
502 dynarray_init(&loadlevel->map_labels, 10, sizeof(struct map_label));
503
504 if (loadlevel->random_dungeon && !loadlevel->dungeon_generated)
505 return data;
506
507 // Now we look for the beginning and end of the map labels section
508 char *map_label_begin = LocateStringInData(data, MAP_LABEL_BEGIN_STRING) + strlen(MAP_LABEL_BEGIN_STRING) + 1;
509 char *map_label_end = LocateStringInData(map_label_begin, MAP_LABEL_END_STRING);
510 *map_label_end = '\0';
511
512 // Get the number of map labels in this level
513 int nb_map_labels_in_level = CountStringOccurences(map_label_begin, LABEL_ITSELF_ANNOUNCE_STRING);
514 DebugPrintf(1, "\nNumber of map labels found in this level : %d.", nb_map_labels_in_level);
515
516 // Now we decode all the map label information
517 for (i = 0; i < nb_map_labels_in_level ; i++) {
518 if (i)
519 map_label_begin = strstr(map_label_begin + 1, X_POSITION_OF_LABEL_STRING);
520
521 // Get the position of the map label
522 ReadValueFromString(map_label_begin, X_POSITION_OF_LABEL_STRING, "%d", &x, map_label_end);
523 ReadValueFromString(map_label_begin, Y_POSITION_OF_LABEL_STRING, "%d", &y, map_label_end);
524
525 // Get the name of the map label
526 label_name = ReadAndMallocStringFromData(map_label_begin, LABEL_ITSELF_ANNOUNCE_STRING, "\"");
527
528 // Add the map label on the level
529 add_map_label(loadlevel, x, y, label_name);
530
531 DebugPrintf(1, "\npos.x=%d pos.y=%d label_name=\"%s\"", x, y, label_name);
532 }
533
534 *map_label_end = MAP_LABEL_END_STRING[0];
535 return map_label_end;
536 }
537
ReadInOneItem(char * ItemPointer,char * ItemsSectionEnd,item * TargetItem)538 static void ReadInOneItem(char *ItemPointer, char *ItemsSectionEnd, item *TargetItem)
539 {
540 init_item(TargetItem);
541
542 char *item_id = ReadAndMallocStringFromData(ItemPointer, ITEM_ID_STRING, "\"");
543 TargetItem->type = get_item_type_by_id(item_id);
544 free(item_id);
545
546 ReadValueFromString(ItemPointer, ITEM_POS_X_STRING, "%f", &(TargetItem->pos.x), ItemsSectionEnd);
547 ReadValueFromString(ItemPointer, ITEM_POS_Y_STRING, "%f", &(TargetItem->pos.y), ItemsSectionEnd);
548 ReadValueFromStringWithDefault(ItemPointer, ITEM_ARMOR_CLASS_BASE_STRING, "%d", "0", &(TargetItem->armor_class), ItemsSectionEnd);
549 ReadValueFromString(ItemPointer, ITEM_MAX_DURABILITY_STRING, "%d", &(TargetItem->max_durability), ItemsSectionEnd);
550 ReadValueFromString(ItemPointer, ITEM_CUR_DURABILITY_STRING, "%f", &(TargetItem->current_durability), ItemsSectionEnd);
551 ReadValueFromString(ItemPointer, ITEM_AMMO_CLIP_STRING, "%d", &(TargetItem->ammo_clip), ItemsSectionEnd);
552 ReadValueFromString(ItemPointer, ITEM_MULTIPLICITY_STRING, "%d", &(TargetItem->multiplicity), ItemsSectionEnd);
553
554 // Read the socket data of the item and calculate bonuses using it.
555 int i;
556 int socket_count;
557 ReadValueFromStringWithDefault(ItemPointer, ITEM_SOCKETS_SIZE_STRING, "%d", "0", &socket_count, ItemsSectionEnd);
558 for (i = 0; i < socket_count; i++) {
559 char type_string[32];
560 char addon_string[32];
561 struct upgrade_socket socket;
562 sprintf(type_string, "%s%d=", ITEM_SOCKET_TYPE_STRING, i);
563 sprintf(addon_string, "%s%d=", ITEM_SOCKET_ADDON_STRING, i);
564 ReadValueFromString(ItemPointer, type_string, "%d", &socket.type, ItemsSectionEnd);
565 socket.addon = ReadAndMallocStringFromDataOptional(ItemPointer, addon_string, "\"");
566 create_upgrade_socket(TargetItem, socket.type, socket.addon);
567 free(socket.addon);
568 }
569 calculate_item_bonuses(TargetItem);
570
571 DebugPrintf(1, "\nPosX=%f PosY=%f Item=%d", TargetItem->pos.x, TargetItem->pos.y, TargetItem->type);
572
573 }
574
decode_extension_chest(char * ext,void ** data)575 static char *decode_extension_chest(char *ext, void **data)
576 {
577 struct dynarray *chest = dynarray_alloc(1, sizeof(item));
578 char *item_str, *item_end;
579
580 item_str = ext;
581
582 while (*item_str != '}') {
583 // Find end of this item (beginning of next item)
584 item_end = item_str;
585 while (*item_end != '\n')
586 item_end++;
587 while (isspace(*item_end))
588 item_end++;
589
590 // Read the item on this line
591 item new_item;
592 ReadInOneItem(item_str, item_end, &new_item);
593
594 // Add the item to the dynarray
595 dynarray_add(chest, &new_item, sizeof(item));
596
597 // Move to the next item definition
598 item_str = item_end;
599 }
600
601
602 *data = chest;
603 return item_str;
604 }
605
decode_extension_label(char * ext,void ** data)606 static char *decode_extension_label(char *ext, void **data)
607 {
608 char *begin = NULL;
609 char *end = NULL;
610
611 if (*ext != '\"') {
612 // For compatibility with old levels.dat and savegames, parse an
613 // unquoted string.
614 // TODO: to be removed in the future, when unquoted strings are no
615 // more used by anybody...
616 begin = ext;
617 end = begin;
618 while (*end != '\n')
619 end++;
620 } else {
621 begin = ext + 1;
622 end = begin;
623 while (*end != '\"' && *end != '\n')
624 end++;
625 }
626
627 *end = '\0';
628 *data = strdup(begin);
629 *end = '\n';
630
631 while (*end != '}')
632 end++;
633
634 return end;
635 }
636
decode_extension_dialog(char * ext,void ** data)637 static char *decode_extension_dialog(char *ext, void **data)
638 {
639 // Same format than label extension.
640 return decode_extension_label(ext, data);
641 }
642
decode_extension_signmessage(char * ext,void ** data)643 static char *decode_extension_signmessage(char *ext, void **data)
644 {
645 char *begin = NULL;
646 char *end = NULL;
647
648 if (*ext != '_' && *ext != '\"') {
649 // For compatibility with old levels.dat and savegames, parse an
650 // unquoted string.
651 // TODO: to be removed in the future, when unquoted strings are no
652 // more used by anybody...
653 begin = ext;
654 end = begin;
655 while (*end != '\n')
656 end++;
657 } else {
658 if (*ext == '\"')
659 begin = ext + 1;
660 else
661 begin = ext + 2;
662 end = begin;
663 while (*end != '\"' && *end != '\n')
664 end++;
665 }
666
667 *end = '\0';
668 *data = strdup(begin);
669 *end = '\n';
670
671 while (*end != '}')
672 end++;
673
674 return end;
675 }
676
decode_obstacle_extensions(level * loadlevel,char * data)677 static char *decode_obstacle_extensions(level *loadlevel, char *data)
678 {
679 dynarray_init(&loadlevel->obstacle_extensions, 10, sizeof(struct obstacle_extension));
680
681 if (loadlevel->random_dungeon && !loadlevel->dungeon_generated)
682 return data;
683
684 char *ext_begin = LocateStringInData(data, OBSTACLE_EXTENSIONS_BEGIN_STRING);
685 char *ext_end = LocateStringInData(ext_begin, OBSTACLE_EXTENSIONS_END_STRING);
686 *ext_end = '\0';
687
688 while (1) {
689 // Look for the next extension
690 ext_begin = strstr(ext_begin, "idx=");
691 if (!ext_begin)
692 break;
693
694 // Read extension information
695 int index;
696 int type;
697 void *ext_data = NULL;
698 sscanf(ext_begin, "idx=%d type=%d", &index, &type);
699
700 // Move to the extension data definition
701 ext_begin = strstr(ext_begin, "data={\n");
702 while (*ext_begin != '\n')
703 ext_begin++;
704 while (isspace(*ext_begin))
705 ext_begin++;
706
707 // Read the extension data
708 switch (type) {
709 case OBSTACLE_EXTENSION_CHEST_ITEMS:
710 ext_begin = decode_extension_chest(ext_begin, &ext_data);
711 break;
712 case OBSTACLE_EXTENSION_LABEL:
713 ext_begin = decode_extension_label(ext_begin, &ext_data);
714 break;
715 case OBSTACLE_EXTENSION_DIALOGFILE:
716 {
717 // Old levels.dat and savegames use this type for
718 // dialog extensions as well as for sign messages extensions.
719 // New 'format' uses two different values.
720 // For compatibility, at least during some time, we accept
721 // to read sign message extension here, but we convert it to
722 // its actual type.
723 // The actual use of an obstacle extension, and thus its actual type,
724 // is defined by the 'action' set to the obstacle (see action.c).
725 // TODO: To be removed in the future
726 obstacle *obs = &(loadlevel->obstacle_list[index]);
727 obstacle_spec *spec = get_obstacle_spec(obs->type);
728
729 if (spec->action && strcmp(spec->action, "sign")) {
730 // This is really a dialog extension
731 ext_begin = decode_extension_dialog(ext_begin, &ext_data);
732 break;
733 }
734 // This is a sign message extension
735 // We change the extension's type, and continue within the next
736 // switch case
737 type = OBSTACLE_EXTENSION_SIGNMESSAGE;
738 }
739 /* no break */
740 case OBSTACLE_EXTENSION_SIGNMESSAGE:
741 ext_begin = decode_extension_signmessage(ext_begin, &ext_data);
742 break;
743 }
744
745 // Add the obstacle extension on the level
746 add_obstacle_extension(loadlevel, &(loadlevel->obstacle_list[index]), type, ext_data);
747 }
748
749 *ext_end = OBSTACLE_EXTENSIONS_END_STRING[0];
750 return ext_end;
751 }
752
decode_item_section(level * loadlevel,char * data)753 static char *decode_item_section(level *loadlevel, char *data)
754 {
755 int i;
756 char Preserved_Letter;
757 int NumberOfItemsInThisLevel;
758 char *ItemPointer;
759 char *ItemsSectionBegin;
760 char *ItemsSectionEnd;
761
762 // First we initialize the items arrays with 'empty' information
763 //
764 for (i = 0; i < MAX_ITEMS_PER_LEVEL; i++) {
765 init_item(&loadlevel->ItemList[i]);
766 }
767
768 if (loadlevel->random_dungeon && !loadlevel->dungeon_generated)
769 return data;
770
771 // We look for the beginning and end of the items section
772 ItemsSectionBegin = LocateStringInData(data, ITEMS_SECTION_BEGIN_STRING);
773 ItemsSectionEnd = LocateStringInData(ItemsSectionBegin, ITEMS_SECTION_END_STRING);
774
775 // We add a terminator at the end of the items section, but ONLY TEMPORARY.
776 // The damage will be restored later!
777 Preserved_Letter = ItemsSectionEnd[0];
778 ItemsSectionEnd[0] = 0;
779 NumberOfItemsInThisLevel = CountStringOccurences(ItemsSectionBegin, ITEM_ID_STRING);
780 DebugPrintf(1, "\nNumber of items found in this level : %d.", NumberOfItemsInThisLevel);
781
782 // Now we decode all the item information
783 ItemPointer = ItemsSectionBegin;
784 char *NextItemPointer;
785 for (i = 0; i < NumberOfItemsInThisLevel; i++) {
786 if ((ItemPointer = strstr(ItemPointer + 1, ITEM_ID_STRING))) {
787 NextItemPointer = strstr(ItemPointer + 1, ITEM_ID_STRING);
788 if (NextItemPointer)
789 NextItemPointer[0] = 0;
790 ReadInOneItem(ItemPointer, ItemsSectionEnd, &(loadlevel->ItemList[i]));
791 loadlevel->ItemList[i].pos.z = loadlevel->levelnum;
792 if (NextItemPointer)
793 NextItemPointer[0] = ITEM_ID_STRING[0];
794 }
795 }
796
797 // Now we repair the damage done to the loaded level data
798 ItemsSectionEnd[0] = Preserved_Letter;
799 return ItemsSectionEnd;
800 }
801
decode_map(level * loadlevel,char * data)802 static char *decode_map(level *loadlevel, char *data)
803 {
804 char *map_begin, *map_end;
805 char *this_line;
806 int i;
807
808 if ((map_begin = strstr(data, MAP_BEGIN_STRING)) == NULL)
809 return NULL;
810 map_begin += strlen(MAP_BEGIN_STRING) + 1;
811
812 if ((map_end = strstr(data, MAP_END_STRING)) == NULL)
813 return NULL;
814
815 /* now scan the map */
816 unsigned int curlinepos = 0;
817 this_line = (char *)MyMalloc(4096);
818
819 /* read MapData */
820 for (i = 0; i < loadlevel->ylen; i++) {
821 int col;
822 int layer;
823 map_tile *Buffer;
824 int tmp;
825
826 /* Select the next line */
827 unsigned int nlpos = 0;
828 memset(this_line, 0, 4096);
829 while (map_begin[curlinepos + nlpos] != '\n')
830 nlpos++;
831 memcpy(this_line, map_begin + curlinepos, nlpos);
832 this_line[nlpos] = '\0';
833 nlpos++;
834
835 /* Decode it */
836 Buffer = MyMalloc((loadlevel->xlen + 10) * sizeof(map_tile));
837 for (col = 0; col < loadlevel->xlen; col++) {
838 // Make sure that all floor layers are always initialized properly.
839 init_map_tile(&Buffer[col]);
840
841 for (layer = 0; layer < loadlevel->floor_layers; layer++) {
842 tmp = strtol(this_line + 4 * (loadlevel->floor_layers * col + layer), NULL, 10);
843 Buffer[col].floor_values[layer] = (Uint16) tmp;
844 }
845 }
846
847 // Now the old text pointer can be replaced with a pointer to the
848 // correctly assembled struct...
849 //
850 loadlevel->map[i] = Buffer;
851
852 curlinepos += nlpos;
853 }
854
855 free(this_line);
856 return map_end;
857 }
858
decode_waypoints(level * loadlevel,char * data)859 static char *decode_waypoints(level *loadlevel, char *data)
860 {
861 char *wp_begin, *wp_end;
862 char *this_line;
863 int nr, x, y, wp_rnd;
864 char *pos;
865
866 // Initialize waypoints
867 dynarray_init(&loadlevel->waypoints, 2, sizeof(struct waypoint));
868
869 if (loadlevel->random_dungeon && !loadlevel->dungeon_generated)
870 return data;
871
872 // Find the beginning and end of the waypoint list
873 if ((wp_begin = strstr(data, WP_BEGIN_STRING)) == NULL)
874 return NULL;
875 wp_begin += strlen(WP_BEGIN_STRING) + 1;
876
877 if ((wp_end = strstr(data, WP_END_STRING)) == NULL)
878 return NULL;
879
880 int curlinepos = 0;
881 this_line = (char *)MyMalloc(4096);
882
883 while (1) {
884 /* Select the next line */
885 short int nlpos = 0;
886 memset(this_line, 0, 4096);
887 while (wp_begin[curlinepos + nlpos] != '\n')
888 nlpos++;
889 memcpy(this_line, wp_begin + curlinepos, nlpos);
890 this_line[nlpos] = '\0';
891 nlpos++;
892
893 curlinepos += nlpos;
894
895 if (!strncmp(this_line, wp_end, strlen(WP_END_STRING))) {
896 break;
897 }
898 wp_rnd = 0;
899 sscanf(this_line, "Nr.=%d \t x=%d \t y=%d rnd=%d", &nr, &x, &y, &wp_rnd);
900
901 // Create a new waypoint
902 waypoint new_wp;
903 new_wp.x = x;
904 new_wp.y = y;
905 new_wp.suppress_random_spawn = wp_rnd;
906
907 // Initalize the connections of the new waypoint
908 dynarray_init(&new_wp.connections, 2, sizeof(int));
909
910 pos = strstr(this_line, CONNECTION_STRING);
911 if (pos == NULL) {
912 fprintf(stderr, "Unable to find connection string. line is %s, level %i\n", this_line,
913 loadlevel->levelnum);
914 }
915 pos += strlen(CONNECTION_STRING); // skip connection-string
916 pos += strspn(pos, WHITE_SPACE); // skip initial whitespace
917
918 while (1) {
919 if (*pos == '\0')
920 break;
921 int connection;
922 int res = sscanf(pos, "%d", &connection);
923 if ((connection == -1) || (res == 0) || (res == EOF))
924 break;
925
926 // Add the connection on this waypoint
927 dynarray_add(&new_wp.connections, &connection, sizeof(int));
928
929 pos += strcspn(pos, WHITE_SPACE); // skip last token
930 pos += strspn(pos, WHITE_SPACE); // skip initial whitespace for next one
931 }
932
933 // Add the waypoint on the level
934 dynarray_add(&loadlevel->waypoints, &new_wp, sizeof(struct waypoint));
935 }
936
937 free(this_line);
938 return wp_end;
939 }
940
941 /**
942 * The smash_obstacle function uses this function as a subfunction to
943 * check for exploding obstacles glued to one specific map square. Of
944 * course also the player number (or -1 in case of no check/bullet hit)
945 * must be supplied so as to be able to suppress hits through walls or
946 * the like.
947 */
smash_obstacles_only_on_tile(float x,float y,int lvl,int map_x,int map_y)948 static int smash_obstacles_only_on_tile(float x, float y, int lvl, int map_x, int map_y)
949 {
950 Level BoxLevel = curShip.AllLevels[lvl];
951 int i;
952 int target_idx;
953 obstacle *target_obstacle;
954 int smashed_something = FALSE;
955 moderately_finepoint blast_start_pos;
956
957 // First some security checks against touching the outsides of the map...
958 //
959 if (!pos_inside_level(map_x, map_y, BoxLevel))
960 return (FALSE);
961
962 // We check all the obstacles on this square if they are maybe destructible
963 // and if they are, we destruct them, haha
964 //
965 for (i = 0; i < BoxLevel->map[map_y][map_x].glued_obstacles.size; i++) {
966 // First we see if there is something glued to this map tile at all.
967 //
968 target_idx = ((int *)(BoxLevel->map[map_y][map_x].glued_obstacles.arr))[i];
969
970 target_obstacle = &(BoxLevel->obstacle_list[target_idx]);
971
972 obstacle_spec *obstacle_spec = get_obstacle_spec(target_obstacle->type);
973 if (!(obstacle_spec->flags & IS_SMASHABLE))
974 continue;
975
976 // Now we check if the item really was close enough to the strike target.
977 // A range of 0.5 should do.
978 //
979 if (fabsf(x - target_obstacle->pos.x) > 0.4)
980 continue;
981 if (fabsf(y - target_obstacle->pos.y) > 0.4)
982 continue;
983
984 colldet_filter filter = FlyableExceptIdPassFilter;
985 filter.data = &target_idx;
986 gps smash_pos = { x, y, lvl };
987 gps vsmash_pos;
988 update_virtual_position(&vsmash_pos, &smash_pos, Me.pos.z);
989 if (vsmash_pos.x == -1)
990 continue;
991 if (!DirectLineColldet(vsmash_pos.x, vsmash_pos.y, Me.pos.x, Me.pos.y, Me.pos.z, &filter)) {
992 continue;
993 }
994
995 DebugPrintf(1, "\nObject smashed at: (%f/%f) by hit/explosion at (%f/%f).",
996 target_obstacle->pos.x, target_obstacle->pos.y, x, y);
997
998 smashed_something = TRUE;
999
1000 // Since the obstacle is destroyed, we start a blast at it's position.
1001 // But here a WARNING WARNING WARNING! is due! We must not start the
1002 // blast before the obstacle is removed, because the blast will again
1003 // cause this very obstacle removal function, so we need to be careful
1004 // so as not to incite endless recursion. We memorize the position for
1005 // later, then delete the obstacle, then we start the blast.
1006 //
1007 blast_start_pos.x = target_obstacle->pos.x;
1008 blast_start_pos.y = target_obstacle->pos.y;
1009
1010 int obstacle_drops_treasure
1011 = obstacle_spec->flags & DROPS_RANDOM_TREASURE;
1012
1013 // Let the automap know that we've updated things
1014 update_obstacle_automap(lvl, target_obstacle);
1015
1016 // Now we really smash the obstacle, i.e. we can set it's type to the debris that has
1017 // been configured for this obstacle type. In if there is nothing configured (i.e. -1 set)
1018 // then we'll just delete the obstacle in question entirely. For this we got a standard function to
1019 // safely do it and not make some errors into the glue structure or obstacles lists...
1020 //
1021 if (obstacle_spec->result_type_after_smashing_once == (-1)) {
1022 del_obstacle(target_obstacle);
1023 } else {
1024 target_obstacle->type = obstacle_spec->result_type_after_smashing_once;
1025 }
1026
1027 // Drop items after destroying the obstacle, in order to avoid collisions
1028 if (obstacle_drops_treasure)
1029 DropRandomItem(lvl, target_obstacle->pos.x, target_obstacle->pos.y, BoxLevel->drop_class, FALSE);
1030
1031 // Now that the obstacle is removed AND ONLY NOW that the obstacle is
1032 // removed, we may start a blast at this position. Otherwise we would
1033 // run into trouble, see the warning further above.
1034 StartBlast(blast_start_pos.x, blast_start_pos.y,
1035 lvl, obstacle_spec->blast_type, 0.0, FACTION_SELF, obstacle_spec->smashed_sound);
1036 }
1037
1038 return smashed_something;
1039 }
1040
1041 /**
1042 * When a destructible type of obstacle gets hit, e.g. by a blast
1043 * exploding on the tile or a melee hit on the same floor tile, then some
1044 * of the obstacles (like barrel or crates) might explode, sometimes
1045 * leaving some treasure behind.
1046 *
1047 */
smash_obstacle(float x,float y,int lvl)1048 int smash_obstacle(float x, float y, int lvl)
1049 {
1050 int map_x, map_y;
1051 int smash_x, smash_y;
1052 int smashed_something = FALSE;
1053
1054 map_x = (int)rintf(x);
1055 map_y = (int)rintf(y);
1056
1057 for (smash_y = map_y - 1; smash_y < map_y + 2; smash_y++) {
1058 for (smash_x = map_x - 1; smash_x < map_x + 2; smash_x++) {
1059 if (smash_obstacles_only_on_tile(x, y, lvl, smash_x, smash_y))
1060 smashed_something = TRUE;
1061 }
1062 }
1063
1064 return (smashed_something);
1065
1066 } // int smash_obstacle ( float x , float y );
1067
1068 /**
1069 * This function returns the map brick code of the tile that occupies the
1070 * given position in the given layer.
1071 * Floor layers are indexed from 0 to lvl->floor_layers - 1. The lowest
1072 * floor layer is #0. Every map has at least one layer.
1073 */
get_map_brick(level * lvl,float x,float y,int layer)1074 Uint16 get_map_brick(level *lvl, float x, float y, int layer)
1075 {
1076 Uint16 BrickWanted;
1077 int RoundX, RoundY;
1078
1079 gps vpos = { x, y, lvl->levelnum };
1080 gps rpos;
1081 if (!resolve_virtual_position(&rpos, &vpos)) {
1082 return ISO_FLOOR_EMPTY;
1083 }
1084 RoundX = (int)rintf(rpos.x);
1085 RoundY = (int)rintf(rpos.y);
1086
1087 BrickWanted = curShip.AllLevels[rpos.z]->map[RoundY][RoundX].floor_values[layer];
1088
1089 if (BrickWanted >= underlay_floor_tiles.size) {
1090 if (BrickWanted < MAX_UNDERLAY_FLOOR_TILES || (BrickWanted - MAX_UNDERLAY_FLOOR_TILES) >= overlay_floor_tiles.size) {
1091 error_message(__FUNCTION__, "Level %d at %d %d in %d layer uses an unknown floor tile: %d.", PLEASE_INFORM,
1092 lvl->levelnum, RoundX, RoundY, layer, BrickWanted);
1093 return ISO_FLOOR_EMPTY;
1094 }
1095 }
1096
1097 return BrickWanted;
1098 }
1099
1100 /**
1101 * This functions reads the specification for a level
1102 * taken from the ship file.
1103 *
1104 * @return pointer to the level
1105 * @param text buffer containing level description
1106 */
decode_level(char ** buffer)1107 static level *decode_level(char **buffer)
1108 {
1109 level *loadlevel;
1110 char *data = *buffer;
1111
1112 loadlevel = (level *)MyMalloc(sizeof(level));
1113
1114 if (decode_header(loadlevel, data)) {
1115 error_message(__FUNCTION__, "Unable to decode level header!", PLEASE_INFORM | IS_FATAL);
1116 }
1117
1118 // The order of sections in the file has to match this.
1119 data = decode_map(loadlevel, data);
1120 if (!data) {
1121 error_message(__FUNCTION__, "Unable to decode the map for level %d", PLEASE_INFORM | IS_FATAL, loadlevel->levelnum);
1122 }
1123 data = decode_obstacles(loadlevel, data);
1124 data = decode_map_labels(loadlevel, data);
1125 data = decode_item_section(loadlevel, data);
1126 data = decode_obstacle_extensions(loadlevel, data);
1127 data = decode_waypoints(loadlevel, data);
1128
1129 // Due to currently unknown bugs, some obstacles can be located on invalid
1130 // positions. If we are starting the lvleditor, we will warn the user but
1131 // keep those obstacles to help investigating the bugs. Otherwise, those
1132 // obstacles and their associated extension are silently removed.
1133 int i;
1134 int need_defrag = FALSE;
1135 struct auto_string *error_msg = alloc_autostr(256);
1136
1137 for (i = 0; i < MAX_OBSTACLES_ON_MAP; i++) {
1138 struct obstacle *obs = &loadlevel->obstacle_list[i];
1139 if (obs->type == -1)
1140 continue;
1141 if (!pos_inside_level(obs->pos.x, obs->pos.y, loadlevel)) {
1142 if (game_root_mode == ROOT_IS_LVLEDIT && game_status == INSIDE_LVLEDITOR) {
1143 autostr_append(error_msg, "Invalid obstacle (%s) position on level %d: t%d x%3.2f y%3.2f\n",
1144 ((char **)get_obstacle_spec(obs->type)->filenames.arr)[0],
1145 loadlevel->levelnum, obs->type, obs->pos.x, obs->pos.y);
1146 alert_once_window(ONCE_PER_GAME,
1147 _("-- WARNING --\n"
1148 "An obstacle with invalid coords was loaded onto a map.\n"
1149 "We keep it to help finding the bug.\n"
1150 "See the report in your terminal console."));
1151 } else if (game_root_mode != ROOT_IS_LVLEDIT) {
1152 unglue_obstacle(loadlevel, obs);
1153 obs->type = -1;
1154 del_obstacle_extensions(loadlevel, obs);
1155 need_defrag = TRUE;
1156 }
1157 }
1158 }
1159 if (error_msg->length != 0) {
1160 // Remove the last carriage return
1161 error_msg->value[error_msg->length - 1] = '\0';
1162 error_message(__FUNCTION__, "%s", NO_REPORT, error_msg->value);
1163 }
1164 free_autostr(error_msg);
1165 if (need_defrag)
1166 defrag_obstacle_array(loadlevel);
1167
1168 // Point the buffer to the end of this level, so the next level can be read
1169 *buffer = data;
1170 return loadlevel;
1171 }
1172
1173
1174 /**
1175 * Call the random dungeon generator on this level if this level is marked
1176 * as being randomly generated and if we are not in the "leveleditor" mode
1177 * in which case random dungeons must not be considered as generated (so that
1178 * they will be exported as being non-generated random levels).
1179 */
generate_dungeon_if_needed(level * l)1180 static void generate_dungeon_if_needed(level *l)
1181 {
1182 if (!l->random_dungeon || l->dungeon_generated) {
1183 return;
1184 }
1185
1186 // Generate random dungeon now
1187 set_dungeon_output(l);
1188 generate_dungeon(l->xlen, l->ylen, l->random_dungeon, l->teleport_pair);
1189 l->dungeon_generated = 1;
1190 }
1191
free_ship_level(level * lvl)1192 void free_ship_level(level *lvl)
1193 {
1194 int row = 0;
1195 int col = 0;
1196
1197 // Map tiles
1198 for (row = 0; row < lvl->ylen; row++) {
1199 if (lvl->map[row]) {
1200 for (col = 0; col < lvl->xlen; col++) {
1201 dynarray_free(&lvl->map[row][col].glued_obstacles);
1202 }
1203
1204 free(lvl->map[row]);
1205 lvl->map[row] = NULL;
1206 }
1207 }
1208
1209 // Level strings
1210 if (lvl->Levelname) {
1211 free(lvl->Levelname);
1212 lvl->Levelname = NULL;
1213 }
1214
1215 if (lvl->Background_Song_Name) {
1216 free(lvl->Background_Song_Name);
1217 lvl->Background_Song_Name = NULL;
1218 }
1219
1220 // Waypoints
1221 int w;
1222 for (w = 0; w < lvl->waypoints.size; w++) {
1223 struct waypoint *wpts = lvl->waypoints.arr;
1224 dynarray_free(&wpts[w].connections);
1225 }
1226
1227 dynarray_free(&lvl->waypoints);
1228
1229 // Obstacle extensions
1230 free_obstacle_extensions(lvl);
1231
1232 // Map labels
1233 free_map_labels(lvl);
1234
1235 // Random droids
1236 lvl->random_droids.types_size = 0;
1237
1238 // Items
1239 for (w = 0; w < MAX_ITEMS_PER_LEVEL; w++) {
1240 if (lvl->ItemList[w].type != -1) {
1241 delete_upgrade_sockets(&(lvl->ItemList[w]));
1242 }
1243 }
1244
1245 free(lvl);
1246 }
1247
free_current_ship()1248 void free_current_ship()
1249 {
1250 struct level *lvl;
1251 BROWSE_LEVELS(lvl) {
1252 int lvlnum = lvl->levelnum;
1253 free_ship_level(lvl);
1254 curShip.AllLevels[lvlnum] = NULL;
1255 }
1256 curShip.num_levels = 0;
1257 }
1258
1259 /**
1260 * This function loads the data for a whole ship
1261 * Possible return values are : OK and ERR
1262 */
LoadShip(char * filename,int compressed)1263 int LoadShip(char *filename, int compressed)
1264 {
1265 char *ShipData = NULL;
1266 FILE *ShipFile;
1267
1268 #define END_OF_SHIP_DATA_STRING "*** End of Ship Data ***"
1269
1270 // Free existing level data
1271 free_current_ship();
1272
1273 // Read the whole ship-data to memory
1274 //
1275 ShipFile = fopen(filename, "rb");
1276 if (!ShipFile) {
1277 error_message(__FUNCTION__, "Unable to open ship file %s: %s.", PLEASE_INFORM | IS_FATAL, filename, strerror(errno));
1278 }
1279
1280 if (compressed) {
1281 if (inflate_stream(ShipFile, (unsigned char **)&ShipData, NULL)) {
1282 error_message(__FUNCTION__, "Unable to decompress ship file %s.", PLEASE_INFORM | IS_FATAL, filename);
1283 }
1284 } else {
1285 int length = FS_filelength(ShipFile);
1286 ShipData = malloc(length + 1);
1287 if (!fread(ShipData, length, 1, ShipFile))
1288 error_message(__FUNCTION__, "Reading ship file %s failed with fread().", PLEASE_INFORM | IS_FATAL, filename);
1289 ShipData[length] = 0;
1290 }
1291
1292 fclose(ShipFile);
1293
1294 // Read each level
1295 int done = 0;
1296 char *pos = ShipData;
1297 while (!done) {
1298 level *this_level = decode_level(&pos);
1299 int this_levelnum = this_level->levelnum;
1300
1301 if (this_levelnum >= MAX_LEVELS)
1302 error_message(__FUNCTION__, "One levelnumber in savegame (%d) is bigger than the maximum allowed (%d).",
1303 PLEASE_INFORM | IS_FATAL, this_levelnum, MAX_LEVELS - 1);
1304 if (level_exists(this_levelnum))
1305 error_message(__FUNCTION__, "Two levels with same levelnumber (%d) found in the savegame.",
1306 PLEASE_INFORM | IS_FATAL, this_levelnum);
1307
1308 curShip.AllLevels[this_levelnum] = this_level;
1309 curShip.num_levels = this_levelnum + 1;
1310
1311 generate_dungeon_if_needed(this_level);
1312
1313 // Move to the level termination marker
1314 pos = strstr(pos, LEVEL_END_STRING);
1315 pos += strlen(LEVEL_END_STRING) + 1;
1316
1317 // Check if there is another level
1318 if (!strstr(pos, LEVEL_HEADER_LEVELNUMBER)) {
1319 done = 1;
1320 }
1321 }
1322
1323 // Now that all the information has been copied, we can free the loaded data
1324 // again.
1325 //
1326 free(ShipData);
1327
1328 // Compute the gps transform acceleration data
1329 gps_transform_map_dirty_flag = TRUE;
1330 gps_transform_map_init();
1331
1332 return OK;
1333
1334 }; // int LoadShip ( ... )
1335
1336 /**
1337 * This should write the obstacle information in human-readable form into
1338 * a buffer.
1339 */
encode_obstacles_of_this_level(struct auto_string * shipstr,struct level * lvl)1340 static void encode_obstacles_of_this_level(struct auto_string *shipstr, struct level *lvl)
1341 {
1342 int i;
1343 struct auto_string *error_msg = alloc_autostr(256);
1344
1345 defrag_obstacle_array(lvl);
1346
1347 autostr_append(shipstr, "%s\n", OBSTACLE_DATA_BEGIN_STRING);
1348
1349 for (i = 0; i < MAX_OBSTACLES_ON_MAP; i++) {
1350 if (lvl->obstacle_list[i].type == (-1))
1351 continue;
1352
1353 // Invalid obstacles are saved, but with a warning message unless we
1354 // are playtesting, to enable further inspection of the bug.
1355 // Anyhow, invalid obstacles are not loaded.
1356 if (!pos_inside_level(lvl->obstacle_list[i].pos.x, lvl->obstacle_list[i].pos.y, lvl)) {
1357 if (game_root_mode != ROOT_IS_LVLEDIT) {
1358 error_once_message(ONCE_PER_GAME, __FUNCTION__,
1359 "Some obstacles with an invalid position were found"
1360 " while saving them. See the individual reports below.", PLEASE_INFORM);
1361 }
1362 if (game_root_mode != ROOT_IS_LVLEDIT || game_status == INSIDE_LVLEDITOR) {
1363 autostr_append(error_msg, "Invalid obstacle (%s) position on level %d: t%d x%3.2f y%3.2f\n",
1364 ((char **)get_obstacle_spec(lvl->obstacle_list[i].type)->filenames.arr)[0],
1365 lvl->levelnum, lvl->obstacle_list[i].type, lvl->obstacle_list[i].pos.x, lvl->obstacle_list[i].pos.y);
1366 }
1367 // If we are inside the lvleditor, also display an alert
1368 if (game_root_mode == ROOT_IS_LVLEDIT && game_status == INSIDE_LVLEDITOR) {
1369 alert_once_window(ONCE_PER_GAME,
1370 _("-- WARNING --\n"
1371 "An obstacle with invalid coords is saved to a map.\n"
1372 "We accept to save it, for further inspection of the bug.\n"
1373 "See the report in your terminal console."));
1374 }
1375 }
1376 autostr_append(shipstr, "%s%d %s%3.2f %s%3.2f\n", OBSTACLE_TYPE_STRING, lvl->obstacle_list[i].type,
1377 OBSTACLE_X_POSITION_STRING, lvl->obstacle_list[i].pos.x, OBSTACLE_Y_POSITION_STRING,
1378 lvl->obstacle_list[i].pos.y);
1379 }
1380
1381 autostr_append(shipstr, "%s\n", OBSTACLE_DATA_END_STRING);
1382
1383 if (error_msg->length != 0) {
1384 // Remove the last carriage return
1385 error_msg->value[error_msg->length - 1] = '\0';
1386 error_message(__FUNCTION__, "%s", NO_REPORT, error_msg->value);
1387 }
1388 free_autostr(error_msg);
1389 }
1390
encode_map_labels(struct auto_string * shipstr,level * lvl)1391 static void encode_map_labels(struct auto_string *shipstr, level *lvl)
1392 {
1393 int i;
1394 struct map_label *map_label;
1395
1396 autostr_append(shipstr, "%s\n", MAP_LABEL_BEGIN_STRING);
1397
1398 for (i = 0; i < lvl->map_labels.size; i++) {
1399 // Get the map label
1400 map_label = &ACCESS_MAP_LABEL(lvl->map_labels, i);
1401
1402 // Encode map label
1403 autostr_append(shipstr, "%s%d %s%d %s%s\"\n", X_POSITION_OF_LABEL_STRING, map_label->pos.x, Y_POSITION_OF_LABEL_STRING,
1404 map_label->pos.y, LABEL_ITSELF_ANNOUNCE_STRING, map_label->label_name);
1405 }
1406
1407 autostr_append(shipstr, "%s\n", MAP_LABEL_END_STRING);
1408 }
1409
1410 /**
1411 *
1412 *
1413 */
WriteOutOneItem(struct auto_string * shipstr,item * ItemToWriteOut)1414 static void WriteOutOneItem(struct auto_string *shipstr, item *ItemToWriteOut)
1415 {
1416
1417 autostr_append(shipstr, "%s%s\" %s%f %s%f ", ITEM_ID_STRING, ItemMap[ItemToWriteOut->type].id,
1418 ITEM_POS_X_STRING, ItemToWriteOut->pos.x, ITEM_POS_Y_STRING, ItemToWriteOut->pos.y);
1419
1420 if (ItemToWriteOut->armor_class) {
1421 autostr_append(shipstr, "%s%d ", ITEM_ARMOR_CLASS_BASE_STRING, ItemToWriteOut->armor_class);
1422 }
1423
1424 autostr_append(shipstr, "%s%d %s%f %s%d %s%d",
1425 ITEM_MAX_DURABILITY_STRING, ItemToWriteOut->max_durability,
1426 ITEM_CUR_DURABILITY_STRING, ItemToWriteOut->current_durability,
1427 ITEM_AMMO_CLIP_STRING, ItemToWriteOut->ammo_clip,
1428 ITEM_MULTIPLICITY_STRING, ItemToWriteOut->multiplicity);
1429
1430 // Write the sockets of the item. The bonuses can be reconstructed from
1431 // these easily so we don't need to write them at all.
1432 if (ItemToWriteOut->upgrade_sockets.size) {
1433 int i;
1434 autostr_append(shipstr, "%s%d ", ITEM_SOCKETS_SIZE_STRING, ItemToWriteOut->upgrade_sockets.size);
1435 for (i = 0; i < ItemToWriteOut->upgrade_sockets.size; i++) {
1436 struct upgrade_socket *socket = &ItemToWriteOut->upgrade_sockets.arr[i];
1437 autostr_append(shipstr, "%s%d=%d ", ITEM_SOCKET_TYPE_STRING, i, socket->type);
1438 if (socket->addon) {
1439 autostr_append(shipstr, "%s%d=%s\" ", ITEM_SOCKET_ADDON_STRING, i, socket->addon);
1440 }
1441 }
1442 }
1443
1444 autostr_append(shipstr, "\n");
1445 }
1446
EncodeItemSectionOfThisLevel(struct auto_string * shipstr,level * Lev)1447 static void EncodeItemSectionOfThisLevel(struct auto_string *shipstr, level *Lev)
1448 {
1449 int i;
1450
1451 autostr_append(shipstr, "%s\n", ITEMS_SECTION_BEGIN_STRING);
1452
1453 // Now we write out the bulk of items infos
1454 //
1455 for (i = 0; i < MAX_ITEMS_PER_LEVEL; i++) {
1456 if (Lev->ItemList[i].type == (-1))
1457 continue;
1458
1459 WriteOutOneItem(shipstr, &(Lev->ItemList[i]));
1460
1461 }
1462
1463 autostr_append(shipstr, "%s\n", ITEMS_SECTION_END_STRING);
1464 }
1465
encode_extension_chest(struct auto_string * shipstr,struct obstacle_extension * ext)1466 static void encode_extension_chest(struct auto_string *shipstr, struct obstacle_extension *ext)
1467 {
1468 int i;
1469 struct dynarray *da = ext->data;
1470
1471 for (i = 0; i < da->size; i++) {
1472 item *it = &((item *)da->arr)[i];
1473 if (it->type == -1)
1474 continue;
1475
1476 autostr_append(shipstr, "\t");
1477 WriteOutOneItem(shipstr, it);
1478 }
1479 }
1480
encode_extension_label(struct auto_string * shipstr,struct obstacle_extension * ext)1481 static void encode_extension_label(struct auto_string *shipstr, struct obstacle_extension *ext)
1482 {
1483 const char *label = ext->data;
1484
1485 autostr_append(shipstr, "\t\"%s\"\n", label);
1486 }
1487
encode_extension_dialog(struct auto_string * shipstr,struct obstacle_extension * ext)1488 static void encode_extension_dialog(struct auto_string *shipstr, struct obstacle_extension *ext)
1489 {
1490 const char *label = ext->data;
1491
1492 autostr_append(shipstr, "\t\"%s\"\n", label);
1493 }
1494
encode_extension_signmessage(struct auto_string * shipstr,struct obstacle_extension * ext)1495 static void encode_extension_signmessage(struct auto_string *shipstr, struct obstacle_extension *ext)
1496 {
1497 const char *label = ext->data;
1498
1499 // Sign messages can be localized. Use gettext marker.
1500 autostr_append(shipstr, "\t_\"%s\"\n", label);
1501 }
1502
encode_obstacle_extensions(struct auto_string * shipstr,level * l)1503 static void encode_obstacle_extensions(struct auto_string *shipstr, level *l)
1504 {
1505 int i;
1506 autostr_append(shipstr, "%s\n", OBSTACLE_EXTENSIONS_BEGIN_STRING);
1507 for (i = 0; i < l->obstacle_extensions.size; i++) {
1508 struct obstacle_extension *ext = &ACCESS_OBSTACLE_EXTENSION(l->obstacle_extensions, i);
1509
1510 if (ext->type == 0)
1511 continue;
1512
1513 autostr_append(shipstr, "idx=%d type=%d data={\n", get_obstacle_index(l, ext->obs), ext->type);
1514
1515 switch ((enum obstacle_extension_type)(ext->type)) {
1516 case OBSTACLE_EXTENSION_CHEST_ITEMS:
1517 encode_extension_chest(shipstr, ext);
1518 break;
1519 case OBSTACLE_EXTENSION_LABEL:
1520 encode_extension_label(shipstr, ext);
1521 break;
1522 case OBSTACLE_EXTENSION_DIALOGFILE:
1523 encode_extension_dialog(shipstr, ext);
1524 break;
1525 case OBSTACLE_EXTENSION_SIGNMESSAGE:
1526 encode_extension_signmessage(shipstr, ext);
1527 break;
1528 }
1529
1530 autostr_append(shipstr, "}\n");
1531 }
1532 autostr_append(shipstr, "%s\n", OBSTACLE_EXTENSIONS_END_STRING);
1533 }
1534
encode_waypoints(struct auto_string * shipstr,level * lvl)1535 static void encode_waypoints(struct auto_string *shipstr, level *lvl)
1536 {
1537 waypoint *wpts = lvl->waypoints.arr;
1538 int *connections;
1539 int i, j;
1540
1541 autostr_append(shipstr, "%s\n", WP_BEGIN_STRING);
1542
1543 for (i = 0; i < lvl->waypoints.size; i++) {
1544 // Encode the waypoint
1545 autostr_append(shipstr, "Nr.=%3d x=%4d y=%4d rnd=%1d\t %s", i, wpts[i].x, wpts[i].y, wpts[i].suppress_random_spawn, CONNECTION_STRING);
1546
1547 // Get the connections of the waypoint
1548 connections = wpts[i].connections.arr;
1549
1550 for (j = 0; j < wpts[i].connections.size; j++) {
1551 // Check connected waypoint validity
1552 int connected_waypoint = connections[j];
1553 if (connected_waypoint < 0 || connected_waypoint >= lvl->waypoints.size) {
1554 error_message(__FUNCTION__, "A connection to an invalid waypoint (#%d) was found while encoding level #%d\n."
1555 "We discard it.", PLEASE_INFORM, connected_waypoint, lvl->levelnum);
1556 continue;
1557 }
1558 // Encode the connection of the waypoint
1559 autostr_append(shipstr, " %3d", connected_waypoint);
1560 }
1561
1562 autostr_append(shipstr, "\n");
1563 }
1564 }
1565
1566 /**
1567 * This function translates map data into human readable map code, that
1568 * can later be written to the map file on disk.
1569 */
TranslateToHumanReadable(struct auto_string * str,map_tile * MapInfo,int LineLength,int layers)1570 static void TranslateToHumanReadable(struct auto_string *str, map_tile * MapInfo, int LineLength, int layers)
1571 {
1572 int col;
1573 int layer;
1574
1575 for (col = 0; col < LineLength; col++) {
1576 for (layer = 0; layer < layers; layer++)
1577 autostr_append(str, "%3d ", MapInfo[col].floor_values[layer]);
1578 }
1579
1580 autostr_append(str, "\n");
1581 }
1582
1583 /**
1584 * This function generates savable text out of the current level data
1585 *
1586 * If reset_random_levels is TRUE, then the random levels are saved
1587 * "un-generated" (typical usage: levels.dat).
1588 *
1589 */
encode_level_for_saving(struct auto_string * shipstr,level * lvl,int reset_random_levels)1590 static void encode_level_for_saving(struct auto_string *shipstr, level *lvl, int reset_random_levels)
1591 {
1592 int i;
1593 int xlen = lvl->xlen, ylen = lvl->ylen;
1594
1595 // Write level header
1596 autostr_append(shipstr, "%s %d\n\
1597 xlen of this level: %d\n\
1598 ylen of this level: %d\n\
1599 floor layers: %d\n\
1600 light radius bonus of this level: %d\n\
1601 minimal light on this level: %d\n\
1602 infinite_running_on_this_level: %d\n\
1603 random dungeon: %d\n\
1604 teleport pair: %d\n\
1605 dungeon generated: %d\n\
1606 environmental flags: %d\n\
1607 item drop class: %d\n\
1608 jump target north: %d\n\
1609 jump target south: %d\n\
1610 jump target east: %d\n\
1611 jump target west: %d\n", LEVEL_HEADER_LEVELNUMBER, lvl->levelnum,
1612 lvl->xlen, lvl->ylen, lvl->floor_layers,
1613 lvl->light_bonus, lvl->minimum_light_value,
1614 lvl->infinite_running_on_this_level,
1615 lvl->random_dungeon,
1616 lvl->teleport_pair,
1617 (reset_random_levels && lvl->random_dungeon) ? 0 : lvl->dungeon_generated,
1618 lvl->flags,
1619 lvl->drop_class,
1620 lvl->jump_target_north,
1621 lvl->jump_target_south,
1622 lvl->jump_target_east,
1623 lvl->jump_target_west);
1624
1625 autostr_append(shipstr, "number of random droids: %d\n", lvl->random_droids.nr);
1626 autostr_append(shipstr, "random droid types: ");
1627
1628 for (i = 0; i < lvl->random_droids.types_size; i++) {
1629 if (i)
1630 autostr_append(shipstr, ", ");
1631 autostr_append(shipstr, "%s", Droidmap[lvl->random_droids.types[i]].droidname);
1632 }
1633
1634 autostr_append(shipstr, "\n%s%s\"\n%s%s\n", LEVEL_NAME_STRING, lvl->Levelname,
1635 BACKGROUND_SONG_NAME_STRING, lvl->Background_Song_Name);
1636
1637 autostr_append(shipstr, "%s\n", MAP_BEGIN_STRING);
1638
1639 // Now in the loop each line of map data should be saved as a whole
1640 for (i = 0; i < ylen; i++) {
1641 if (!(reset_random_levels && lvl->random_dungeon)) {
1642 TranslateToHumanReadable(shipstr, lvl->map[i], xlen, lvl->floor_layers);
1643 } else {
1644 int j = xlen;
1645 while (j--) {
1646 autostr_append(shipstr, " 0 ");
1647 }
1648 autostr_append(shipstr, "\n");
1649 }
1650 }
1651
1652 autostr_append(shipstr, "%s\n", MAP_END_STRING);
1653
1654 if (!(reset_random_levels && lvl->random_dungeon)) {
1655 encode_obstacles_of_this_level(shipstr, lvl);
1656
1657 encode_map_labels(shipstr, lvl);
1658
1659 EncodeItemSectionOfThisLevel(shipstr, lvl);
1660
1661 encode_obstacle_extensions(shipstr, lvl);
1662
1663 encode_waypoints(shipstr, lvl);
1664 }
1665
1666 autostr_append(shipstr, "%s\n----------------------------------------------------------------------\n",
1667 LEVEL_END_STRING);
1668 }
1669
1670 /**
1671 * This function should save a whole ship to disk to the given filename.
1672 * It is not only used by the level editor, but also by the function that
1673 * saves whole games.
1674 *
1675 * If reset_random_levels is TRUE, then the random levels are saved
1676 * "un-generated" (typical usage: levels.dat).
1677 * @return 0 if OK, 1 on error
1678 */
SaveShip(const char * filename,int reset_random_levels,int compress)1679 int SaveShip(const char *filename, int reset_random_levels, int compress)
1680 {
1681 int i;
1682 FILE *ShipFile = NULL;
1683 struct auto_string *shipstr;
1684
1685 // Open the ship file
1686 if ((ShipFile = fopen(filename, "wb")) == NULL) {
1687 error_message(__FUNCTION__, "Error opening ship file %s for writing.", NO_REPORT, filename);
1688 return ERR;
1689 }
1690
1691 shipstr = alloc_autostr(1048576);
1692 autostr_printf(shipstr, "\n");
1693
1694 // Save all the levels
1695 for (i = 0; i < curShip.num_levels; i++) {
1696 if (level_exists(i)) {
1697 encode_level_for_saving(shipstr, curShip.AllLevels[i], reset_random_levels);
1698 }
1699 }
1700
1701 autostr_append(shipstr, "%s\n\n", END_OF_SHIP_DATA_STRING);
1702
1703 if (compress) {
1704 deflate_to_stream((unsigned char *)shipstr->value, shipstr->length, ShipFile);
1705 } else {
1706 if (fwrite((unsigned char *)shipstr->value, shipstr->length, 1, ShipFile) != 1) {
1707 error_message(__FUNCTION__, "Error writing ship file %s.", PLEASE_INFORM, filename);
1708 fclose(ShipFile);
1709 free_autostr(shipstr);
1710 return ERR;
1711 }
1712 }
1713
1714 if (fclose(ShipFile) == EOF) {
1715 error_message(__FUNCTION__, "Closing of ship file failed!", PLEASE_INFORM);
1716 free_autostr(shipstr);
1717 return ERR;
1718 }
1719
1720 free_autostr(shipstr);
1721 return OK;
1722 }
1723
save_special_forces(const char * filename)1724 int save_special_forces(const char *filename)
1725 {
1726 FILE *s_forces_file = NULL;
1727 struct auto_string *s_forces_str;
1728 level *lvl;
1729 int i;
1730
1731 if ((s_forces_file = fopen(filename, "wb")) == NULL) {
1732 error_message(__FUNCTION__, "Error opening Special Forces file %s for writing.", NO_REPORT, filename);
1733 return ERR;
1734 }
1735
1736 s_forces_str = alloc_autostr(64);
1737
1738 for (i = 0; i < curShip.num_levels; i++) {
1739 if (!level_exists(i))
1740 continue;
1741
1742 lvl = curShip.AllLevels[i];
1743 autostr_append(s_forces_str, "** Beginning of new Level **\n");
1744 autostr_append(s_forces_str, "Level=%d\n\n", lvl->levelnum);
1745
1746 enemy *en;
1747
1748 list_for_each_entry_reverse(en, &level_bots_head[lvl->levelnum], level_list) {
1749 if (!en->SpecialForce)
1750 continue;
1751
1752 autostr_append(s_forces_str, "T=%s: ", Droidmap[en->type].droidname);
1753 autostr_append(s_forces_str, "PosX=%d PosY=%d ", (int)en->pos.x, (int)en->pos.y);
1754 autostr_append(s_forces_str, "Faction=\"%s\" ", get_faction_from_id(en->faction));
1755 autostr_append(s_forces_str, "UseDialog=\"%s\" ", en->dialog_section_name);
1756
1757 autostr_append(s_forces_str, "ShortLabel=_\"%s\" ", en->short_description_text);
1758 autostr_append(s_forces_str, "Marker=%d ", en->marker);
1759 autostr_append(s_forces_str, "RushTux=%d ", en->will_rush_tux);
1760
1761 autostr_append(s_forces_str, "Fixed=%hi ", en->CompletelyFixed);
1762 autostr_append(s_forces_str, "DropItemId=\"%s\" ",
1763 (en->on_death_drop_item_code == -1) ? "none" : ItemMap[en->on_death_drop_item_code].id);
1764 autostr_append(s_forces_str, "MaxDistanceToHome=%hd", en->max_distance_to_home);
1765 if (en->sensor_id != Droidmap[en->type].sensor_id) // Only save sensor if it's not the default.
1766 autostr_append(s_forces_str, " UseSensor=\"%s\"", get_sensor_name_by_id(en->sensor_id));
1767 autostr_append(s_forces_str, "\n");
1768 }
1769
1770 autostr_append(s_forces_str, "** End of this levels Special Forces data **\n");
1771 autostr_append(s_forces_str, "---------------------------------------------------------\n");
1772 }
1773
1774 autostr_append(s_forces_str, "*** End of Droid Data ***\n");
1775
1776 if (fwrite((unsigned char *)s_forces_str->value, s_forces_str->length, 1, s_forces_file) != 1) {
1777 error_message(__FUNCTION__, "Error writing SpecialForces file %s.", PLEASE_INFORM, filename);
1778 fclose(s_forces_file);
1779 goto out;
1780 }
1781
1782 if (fclose(s_forces_file) == EOF) {
1783 error_message(__FUNCTION__, "Closing of Special Forces file failed!", PLEASE_INFORM);
1784 goto out;
1785 }
1786
1787 out: free_autostr(s_forces_str);
1788 return OK;
1789 }
1790
1791 /**
1792 * This function is used to calculate the number of the droids on the
1793 * ship, which is a global variable.
1794 */
CountNumberOfDroidsOnShip(void)1795 void CountNumberOfDroidsOnShip(void)
1796 {
1797 Number_Of_Droids_On_Ship = 0;
1798
1799 enemy *erot;
1800 BROWSE_ALIVE_BOTS(erot) {
1801 Number_Of_Droids_On_Ship++;
1802 }
1803
1804 BROWSE_DEAD_BOTS(erot) {
1805 Number_Of_Droids_On_Ship++;
1806 }
1807
1808 }; // void CountNumberOfDroidsOnShip ( void )
1809
1810 /* -----------------------------------------------------------------
1811 * This function initializes all enemies, which means that enemies are
1812 * filled in into the enemy list according to the enemies types that
1813 * are to be found on each deck.
1814 * ----------------------------------------------------------------- */
GetCrew(char * filename)1815 int GetCrew(char *filename)
1816 {
1817 char fpath[PATH_MAX];
1818 char *MainDroidsFilePointer;
1819 char *DroidSectionPointer;
1820 char *EndOfThisDroidSectionPointer;
1821
1822 #define START_OF_DROID_DATA_STRING "*** Beginning of Droid Data ***"
1823 #define END_OF_DROID_DATA_STRING "*** End of Droid Data ***"
1824 #define DROIDS_LEVEL_DESCRIPTION_START_STRING "** Beginning of new Level **"
1825 #define DROIDS_LEVEL_DESCRIPTION_END_STRING "** End of this levels Special Forces data **"
1826
1827 //Now its time to start decoding the droids file.
1828 //For that, we must get it into memory first.
1829 //The procedure is the same as with LoadShip
1830 //
1831 find_file(filename, MAP_DIR, fpath, PLEASE_INFORM | IS_FATAL);
1832
1833 MainDroidsFilePointer = ReadAndMallocAndTerminateFile(fpath, END_OF_DROID_DATA_STRING);
1834
1835 // The Droid crew file for this map is now completely read into memory
1836 // It's now time to decode the file and to fill the array of enemys with
1837 // new droids of the given types.
1838 //
1839 enemy_reset_fabric();
1840
1841 DroidSectionPointer = MainDroidsFilePointer;
1842 while ((DroidSectionPointer = strstr(DroidSectionPointer, DROIDS_LEVEL_DESCRIPTION_START_STRING)) != NULL) {
1843 DroidSectionPointer += strlen(DROIDS_LEVEL_DESCRIPTION_START_STRING);
1844 DebugPrintf(1, "\nFound another levels droids description starting point entry!");
1845 EndOfThisDroidSectionPointer = strstr(DroidSectionPointer, DROIDS_LEVEL_DESCRIPTION_END_STRING);
1846 if (EndOfThisDroidSectionPointer == NULL) {
1847 error_message(__FUNCTION__, "Unterminated droid section encountered!", PLEASE_INFORM | IS_FATAL);
1848 }
1849 // EndOfThisDroidSectionPointer[0]=0;
1850 GetThisLevelsDroids(DroidSectionPointer);
1851 DroidSectionPointer = EndOfThisDroidSectionPointer + 2; // Move past the inserted String terminator
1852 }
1853
1854 free(MainDroidsFilePointer);
1855 return (OK);
1856
1857 }; // int GetCrew ( ... )
1858
1859 /**
1860 *
1861 *
1862 */
GetThisLevelsSpecialForces(char * search_pointer,int our_level_number,char * lvl_end_location)1863 static void GetThisLevelsSpecialForces(char *search_pointer, int our_level_number, char *lvl_end_location)
1864 {
1865 int droid_type;
1866 #define SPECIAL_FORCE_INDICATION_STRING "T="
1867
1868 while ((search_pointer = strstr(search_pointer, SPECIAL_FORCE_INDICATION_STRING)) != NULL) {
1869 char *special_droid = ReadAndMallocStringFromData(search_pointer, SPECIAL_FORCE_INDICATION_STRING, "\n");
1870 char *special_droid_end = special_droid + strlen(special_droid);
1871 search_pointer += strlen(SPECIAL_FORCE_INDICATION_STRING);
1872 //identify what model of droid to display:
1873 char *ptr = special_droid;
1874 int droid_type_length = 0;
1875 while (isalnum(*ptr)) {
1876 ptr++;
1877 droid_type_length++;
1878 }
1879 char type_indication_string[droid_type_length + 1];
1880 strncpy(type_indication_string, special_droid, droid_type_length);
1881 type_indication_string[droid_type_length] = 0;
1882
1883 droid_type = get_droid_type(type_indication_string);
1884
1885 // Create a new enemy, and initialize its 'identity' and 'global state'
1886 // (the enemy will be fully initialized by respawn_level())
1887
1888
1889 enemy *newen = enemy_new(droid_type);
1890
1891
1892 newen->SpecialForce = TRUE;
1893
1894
1895 char *tmp_sensor_ID = ReadAndMallocStringFromDataOptional(special_droid, "UseSensor=\"","\"");
1896 if (!tmp_sensor_ID)
1897 newen->sensor_id=Droidmap[newen->type].sensor_id; //Not declared? In this case, use default.
1898 else
1899 newen->sensor_id=get_sensor_id_by_name(tmp_sensor_ID); // Otherwise, use the specified sensor!
1900 free(tmp_sensor_ID);
1901
1902 ReadValueFromStringWithDefault(special_droid, "Fixed=", "%hd", "0", &(newen->CompletelyFixed), special_droid_end);
1903 ReadValueFromStringWithDefault(special_droid, "Marker=", "%d", "0000", &(newen->marker), special_droid_end);
1904 ReadValueFromStringWithDefault(special_droid, "MaxDistanceToHome=", "%hd", "0", &(newen->max_distance_to_home),
1905 special_droid_end);
1906
1907 char *faction = ReadAndMallocStringFromData(special_droid, "Faction=\"", "\"");
1908 newen->faction = get_faction_id(faction);
1909 free(faction);
1910
1911 char *x, *y;
1912 x = ReadAndMallocStringFromData(special_droid, "PosX=", " ");
1913 y = ReadAndMallocStringFromData(special_droid, "PosY=", " ");
1914
1915 newen->pos.x = strtof(x, NULL);
1916 newen->pos.y = strtof(y, NULL);
1917 newen->pos.z = our_level_number;
1918 free(x);
1919 free(y);
1920
1921 ReadValueFromStringWithDefault(special_droid, "RushTux=", "%hu", "0", &(newen->will_rush_tux), special_droid_end);
1922
1923 newen->dialog_section_name = ReadAndMallocStringFromData(special_droid, "UseDialog=\"", "\"");
1924 npc_get(newen->dialog_section_name); // Check that we have a valid dialog.
1925
1926 if (newen->short_description_text)
1927 free(newen->short_description_text);
1928
1929 newen->short_description_text = ReadAndMallocStringFromDataOptional(special_droid, "ShortLabel=_\"", "\"");
1930 if (!newen->short_description_text) {
1931 newen->short_description_text = ReadAndMallocStringFromData(special_droid, "ShortLabel=\"", "\"");
1932 }
1933
1934 char *death_drop;
1935 death_drop = ReadAndMallocStringFromData(special_droid, "DropItemId=\"", "\"");
1936 if (strcmp(death_drop, "none")) {
1937 newen->on_death_drop_item_code = get_item_type_by_id(death_drop);
1938 } else {
1939 newen->on_death_drop_item_code = -1;
1940 }
1941 free(death_drop);
1942 free(special_droid);
1943 enemy_insert_into_lists(newen, TRUE);
1944 }
1945
1946 };
1947
1948 /**
1949 * This function receives a pointer to the already read in crew section
1950 * in a already read in droids file and decodes all the contents of that
1951 * droid section to fill the AllEnemys array with droid types according
1952 * to the specifications made in the file.
1953 */
GetThisLevelsDroids(char * section_pointer)1954 void GetThisLevelsDroids(char *section_pointer)
1955 {
1956 int our_level_number;
1957 char *search_ptr;
1958 char *lvl_end_location;
1959 int random_droids;
1960 int *allowed_type_list;
1961 level *lvl;
1962
1963 #define DROIDS_LEVEL_INDICATION_STRING "Level="
1964 #define DROIDS_LEVEL_END_INDICATION_STRING "** End of this levels Special Forces data **"
1965
1966 lvl_end_location = LocateStringInData(section_pointer, DROIDS_LEVEL_END_INDICATION_STRING);
1967 lvl_end_location[0] = 0;
1968
1969 // Now we read in the level number for this level
1970 ReadValueFromString(section_pointer, DROIDS_LEVEL_INDICATION_STRING, "%d", &our_level_number, lvl_end_location);
1971
1972 lvl = curShip.AllLevels[our_level_number];
1973
1974 // At this point, the List "allowed_type_list" has been filled with the NUMBERS of
1975 // the allowed types. The number of different allowed types found is also available.
1976 // That means that now we can add the appropriate droid types into the list of existing
1977 // droids in that mission.
1978
1979 random_droids = lvl->random_droids.nr;
1980 allowed_type_list = lvl->random_droids.types;
1981
1982 while (random_droids--) {
1983 // Create a new enemy, and initialize its 'identity' and 'global state'
1984 // (the enemy will be fully initialized by respawn_level())
1985 enemy *newen = enemy_new(allowed_type_list[MyRandom(lvl->random_droids.types_size - 1)]);
1986 newen->pos.x = newen->pos.y = -1;
1987 newen->pos.z = our_level_number;
1988 newen->on_death_drop_item_code = -1;
1989 newen->dialog_section_name = strdup("AfterTakeover");
1990 newen->faction = FACTION_BOTS;
1991
1992 enemy_insert_into_lists(newen, TRUE);
1993 } // while (enemy-limit of this level not reached)
1994
1995 search_ptr = section_pointer;
1996 GetThisLevelsSpecialForces(search_ptr, our_level_number, lvl_end_location);
1997
1998 // End bot's initialization, and put them onto a waypoint.
1999 respawn_level(our_level_number);
2000 };
2001
2002 /**
2003 * This function determines whether a given object on x/y is visible to
2004 * the 001 or not (due to some walls or something in between
2005 *
2006 * Return values are TRUE or FALSE accordingly
2007 *
2008 */
IsVisible(gps * objpos)2009 int IsVisible(gps *objpos)
2010 {
2011
2012 // For the purpose of visibility checking, we might as well exclude objects
2013 // that are too far away to ever be visible and thereby save some checks of
2014 // longer lines on the map, that wouldn't be necessary or helpful anyway.
2015 //
2016 if ((fabsf(Me.pos.x - objpos->x) > FLOOR_TILES_VISIBLE_AROUND_TUX) ||
2017 (fabsf(Me.pos.y - objpos->y) > FLOOR_TILES_VISIBLE_AROUND_TUX))
2018 return (FALSE);
2019
2020 // So if the object in question is close enough to be visible, we'll do the
2021 // actual check and see if the line of sight is free or blocked, a rather
2022 // time-consuming and often re-iterated process. (Maybe some do-it-every-
2023 // -10th-frame-only code could be added here later... and in the meantime
2024 // old values could be used from a stored flag?!
2025 //
2026 return (DirectLineColldet(objpos->x, objpos->y, Me.pos.x, Me.pos.y, objpos->z, &VisiblePassFilter));
2027
2028 }; // int IsVisible( Point objpos )
2029
2030 /**
2031 *
2032 *
2033 */
translate_pixel_to_map_location(float axis_x,float axis_y,int give_x)2034 inline float translate_pixel_to_map_location(float axis_x, float axis_y, int give_x)
2035 {
2036
2037 // NOTE: This function does not expect absolute screen coordinates but rather coordinates relative
2038 // to the center of the screen.
2039 //
2040 // That's also why it's 'axis' rather than 'pos' or 'point'.
2041 //
2042 // That is because mouse clicks can best be analyzed this way.
2043 //
2044
2045 if (give_x) {
2046 return (Me.pos.x + (axis_x / ((float)iso_floor_tile_width)) + (axis_y / ((float)iso_floor_tile_height)));
2047 } else {
2048 return (Me.pos.y - (axis_x / ((float)iso_floor_tile_width)) + (axis_y / ((float)iso_floor_tile_height)));
2049 }
2050
2051 }; // int translate_pixel_to_map_location ( int axis_x , int axis_y , int give_x )
2052
2053 /**
2054 *
2055 *
2056 */
translate_pixel_to_zoomed_map_location(float axis_x,float axis_y,int give_x)2057 float translate_pixel_to_zoomed_map_location(float axis_x, float axis_y, int give_x)
2058 {
2059 float zf = lvledit_zoomfact();
2060 if (give_x) {
2061 return (Me.pos.x + (zf * axis_x / ((float)iso_floor_tile_width)) + (zf * axis_y / ((float)iso_floor_tile_height)));
2062 // return ( ( axis_x / ISO_WIDTH ) + ( axis_y / ISO_HEIGHT ) ) ;
2063 } else {
2064 return (Me.pos.y - (zf * axis_x / ((float)iso_floor_tile_width)) + (zf * axis_y / ((float)iso_floor_tile_height)));
2065 // return ( - ( axis_x / ISO_WIDTH ) + ( axis_y / ISO_HEIGHT ) ) ;
2066 }
2067
2068 }; // int translate_pixel_to_zoomed_map_location ( int axis_x , int axis_y , int give_x )
2069
2070 /**
2071 *
2072 *
2073 */
translate_point_to_map_location(float axis_x,float axis_y,int zoom_is_on)2074 moderately_finepoint translate_point_to_map_location(float axis_x, float axis_y, int zoom_is_on)
2075 {
2076 moderately_finepoint position;
2077 if (zoom_is_on) {
2078 position.x = translate_pixel_to_zoomed_map_location(axis_x, axis_y, TRUE);
2079 position.y = translate_pixel_to_zoomed_map_location(axis_x, axis_y, FALSE);
2080 } else {
2081 position.x = translate_pixel_to_map_location(axis_x, axis_y, TRUE);
2082 position.y = translate_pixel_to_map_location(axis_x, axis_y, FALSE);
2083 }
2084 return position;
2085 }
2086
2087 /**
2088 * This function translates a given map point to screen coordinates.
2089 *
2090 * @param x_map_pos X position on map
2091 * @param y_map_pos Y position on map
2092 * @param x_res pointer to the int that will hold the x position on screen
2093 * @param y_res pointer to the y position on screen
2094 * @param zoom_factor zoom factor in use
2095 *
2096 */
translate_map_point_to_screen_pixel_func(float x_map_pos,float y_map_pos,int * x_res,int * y_res)2097 void translate_map_point_to_screen_pixel_func(float x_map_pos, float y_map_pos, int *x_res, int *y_res)
2098 {
2099 float zoom_factor = 1.0;
2100
2101 /* XXX should not check for leveleditor here! */
2102 if (game_status == INSIDE_LVLEDITOR && GameConfig.zoom_is_on) {
2103 zoom_factor = lvledit_zoomfact_inv();
2104 }
2105 #define R ceilf
2106 #define factX iso_floor_tile_width*0.5*zoom_factor
2107 #define factY iso_floor_tile_height*0.5*zoom_factor
2108 if (x_res != NULL) {
2109 //obstacles oscillent *x_res = UserCenter_x + R( (x_map_pos - Me.pos.x) * factX) + R((Me . pos . y - y_map_pos) * factX);
2110 //murs tilent pas -- en fait si
2111 *x_res = UserCenter_x + R(x_map_pos * factX) - R(y_map_pos * factX) + R(Me.pos.y * factX) - R(factX * Me.pos.x);
2112 //murs tilent pas ET tux oscille *x_res = UserCenter_x + R( x_map_pos * factX) - R(y_map_pos * factX) + R((Me.pos.y - Me.pos.x) * factX);
2113 //original "devtrack" - murs tilent pas *x_res = ( UserCenter_x + R ( ( x_map_pos - y_map_pos ) * factX ) + R ( ( Me . pos . y - Me . pos . x ) * factX ) );
2114
2115 }
2116 if (y_res != NULL) {
2117 //*y_res = UserCenter_y + R( (x_map_pos - Me.pos.x)* factY ) + R((y_map_pos - Me . pos . y)* factY);
2118 *y_res = UserCenter_y + R(x_map_pos * factY) + R(y_map_pos * factY) - R(Me.pos.x * factY) - R(factY * Me.pos.y);
2119 //*y_res = UserCenter_y + R( x_map_pos * factY ) + R(y_map_pos * factY) - R((Me.pos.x + Me.pos.y) * factY);
2120 //*y_res=( UserCenter_y + R ( ( x_map_pos + y_map_pos ) * factY ) - R( ( Me . pos . x + Me . pos . y ) * factY ));
2121 }
2122 #undef R
2123 #undef factX
2124 #undef factY
2125 }
2126
2127 #undef _map_c
2128