1 /*
2 *
3 * Copyright (c) 2004-2010 Arthur Huillet
4 *
5 *
6 * This file is part of Freedroid
7 *
8 * Freedroid is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * Freedroid is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with Freedroid; see the file COPYING. If not, write to the
20 * Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
21 * MA 02111-1307 USA
22 *
23 */
24
25 /**
26 * This file contains all the actions performed by the level editor, ie. the functions that act on the level.
27 */
28
29 #define _leveleditor_actions_c
30
31 #include "system.h"
32
33 #include "defs.h"
34 #include "struct.h"
35 #include "global.h"
36 #include "proto.h"
37
38
39 #include "lvledit/lvledit.h"
40 #include "lvledit/lvledit_widgets.h"
41 #include "lvledit/lvledit_tool_select.h"
42 #include "lvledit/lvledit_tool_place.h"
43
44 /* Undo/redo action lists */
45 LIST_HEAD(to_undo);
46 LIST_HEAD(to_redo);
47
48 static int push_mode = NORMAL;
49
50 /**
51 * @fn void clear_action(action * pos)
52 *
53 * @brief clears an action from its list, and pointers held within the action
54 */
clear_action(action * action)55 static void clear_action(action * action)
56 {
57 if (action->type == ACT_SET_OBSTACLE_LABEL && action->d.change_obstacle_name.new_name != NULL)
58 free(action->d.change_obstacle_name.new_name);
59 else if (action->type == ACT_SET_MAP_LABEL && action->d.change_label_name.new_name != NULL)
60 free(action->d.change_label_name.new_name);
61 else if (action->type == ACT_CREATE_MAP_LABEL && action->d.create_map_label.label_name != NULL)
62 free(action->d.create_map_label.label_name);
63 else if (action->type == ACT_CREATE_ENEMY && action->d.create_enemy != NULL)
64 enemy_free(action->d.create_enemy);
65
66 list_del(&action->node); //< removes an action from a list
67 free(action); //< frees the action
68 }
69
70 /**
71 * @fn void clear_action_list(struct list_head *list)
72 *
73 * @brief clears an action list, and all its data
74 */
clear_action_list(struct list_head * list)75 static void clear_action_list(struct list_head *list)
76 {
77 // free actions individually
78 action *pos, *next;
79 list_for_each_entry_safe(pos, next, list, node)
80 clear_action(pos);
81 }
82
83 /**
84 * @fn static void action_freestack(void)
85 *
86 * @brief clears to_undo and to_redo when LevelEditor() exits
87 */
action_freestack(void)88 void action_freestack(void)
89 {
90 clear_action_list(&to_redo);
91 clear_action_list(&to_undo);
92 }
93
action_create(int type,va_list args)94 static action *action_create(int type, va_list args)
95 {
96 action *act = malloc(sizeof(action));
97 act->type = type;
98 switch (type) {
99 case ACT_CREATE_OBSTACLE:
100 act->d.create_obstacle.x = va_arg(args, double);
101 act->d.create_obstacle.y = va_arg(args, double);
102 act->d.create_obstacle.new_obstacle_type = va_arg(args, int);
103 break;
104 case ACT_REMOVE_OBSTACLE:
105 act->d.delete_obstacle = va_arg(args, obstacle *);
106 break;
107 case ACT_MOVE_OBSTACLE:
108 act->d.move_obstacle.obstacle = va_arg(args, obstacle *);
109 act->d.move_obstacle.newx = va_arg(args, double);
110 act->d.move_obstacle.newy = va_arg(args, double);
111 break;
112 case ACT_CREATE_ITEM:
113 act->d.create_item.x = (float)va_arg(args, double);
114 act->d.create_item.y = (float)va_arg(args, double);
115 act->d.create_item.type = va_arg(args, int);
116 break;
117 case ACT_REMOVE_ITEM:
118 act->d.delete_item = va_arg(args, item *);
119 break;
120 case ACT_MOVE_ITEM:
121 act->d.move_item.item = va_arg(args, item *);
122 act->d.move_item.newx = (float)va_arg(args, double);
123 act->d.move_item.newy = (float)va_arg(args, double);
124 break;
125 case ACT_CREATE_WAYPOINT:
126 act->d.create_waypoint.x = va_arg(args, int);
127 act->d.create_waypoint.y = va_arg(args, int);
128 act->d.create_waypoint.suppress_random_spawn = va_arg(args, int);
129 break;
130 case ACT_REMOVE_WAYPOINT:
131 act->d.delete_waypoint.x = va_arg(args, int);
132 act->d.delete_waypoint.y = va_arg(args, int);
133 break;
134 case ACT_MOVE_WAYPOINT:
135 act->d.move_waypoint.w = va_arg(args, waypoint *);
136 act->d.move_waypoint.newx = va_arg(args, int);
137 act->d.move_waypoint.newy = va_arg(args, int);
138 break;
139 case ACT_TOGGLE_WAYPOINT_RSPAWN:
140 act->d.toggle_waypoint_rspawn.x = va_arg(args, int);
141 act->d.toggle_waypoint_rspawn.y = va_arg(args, int);
142 break;
143 case ACT_TOGGLE_WAYPOINT_CONNECTION:
144 act->d.toggle_waypoint_connection.x = va_arg(args, int);
145 act->d.toggle_waypoint_connection.y = va_arg(args, int);
146 break;
147 case ACT_TILE_FLOOR_SET:
148 act->d.change_floor.x = va_arg(args, int);
149 act->d.change_floor.y = va_arg(args, int);
150 act->d.change_floor.layer = va_arg(args, int);
151 act->d.change_floor.type = va_arg(args, int);
152 break;
153 case ACT_MULTIPLE_ACTIONS:
154 act->d.number_actions = va_arg(args, int);
155 break;
156 case ACT_SET_OBSTACLE_LABEL:
157 act->d.change_obstacle_name.obstacle = va_arg(args, obstacle *);
158 act->d.change_obstacle_name.new_name = va_arg(args, char *);
159 break;
160 case ACT_SET_MAP_LABEL:
161 act->d.change_label_name.id = va_arg(args, int);
162 act->d.change_label_name.new_name = va_arg(args, char *);
163 act->d.change_label_name.x = va_arg(args, int);
164 act->d.change_label_name.y = va_arg(args, int);
165 break;
166 case ACT_JUMP_TO_LEVEL:
167 act->d.jump_to_level.target_level = va_arg(args, int);
168 act->d.jump_to_level.x = (float)va_arg(args, double);
169 act->d.jump_to_level.y = (float)va_arg(args, double);
170 break;
171 case ACT_CHANGE_FLOOR_LAYER:
172 act->d.target_layer = va_arg(args, int);
173 break;
174 case ACT_CREATE_MAP_LABEL:
175 act->d.create_map_label.x = va_arg(args, int);
176 act->d.create_map_label.y = va_arg(args, int);
177 act->d.create_map_label.label_name = va_arg(args, char *);
178 break;
179 case ACT_REMOVE_MAP_LABEL:
180 act->d.delete_map_label.x = va_arg(args, int);
181 act->d.delete_map_label.y = va_arg(args, int);
182 break;
183 case ACT_CREATE_ENEMY:
184 act->d.create_enemy = va_arg(args, enemy *);
185 break;
186 case ACT_REMOVE_ENEMY:
187 act->d.delete_enemy = va_arg(args, enemy *);
188 break;
189 default:
190 error_message(__FUNCTION__, "Unknown action type %d", PLEASE_INFORM | IS_FATAL, type);
191 }
192
193 return act;
194 }
195
action_push(int type,...)196 void action_push(int type, ...)
197 {
198 va_list args;
199 va_start(args, type);
200
201 action *act = action_create(type, args);
202
203 switch (push_mode) {
204 case UNDO:
205 list_add(&act->node, &to_redo);
206 break;
207 case REDO:
208 list_add(&act->node, &to_undo);
209 break;
210 case NORMAL:
211 list_add(&act->node, &to_undo);
212 clear_action_list(&to_redo);
213 break;
214 }
215
216 va_end(args);
217 }
218
action_move_obstacle(level * EditLevel,obstacle * obs,float newx,float newy)219 void action_move_obstacle(level * EditLevel, obstacle * obs, float newx, float newy)
220 {
221 action_push(ACT_MOVE_OBSTACLE, obs, obs->pos.x, obs->pos.y);
222
223 move_obstacle(obs, newx, newy);
224 }
225
action_create_obstacle_user(Level EditLevel,double x,double y,int new_obstacle)226 obstacle *action_create_obstacle_user(Level EditLevel, double x, double y, int new_obstacle)
227 {
228 obstacle *o = add_obstacle(EditLevel, x, y, new_obstacle);
229 if (o) {
230 action_push(ACT_REMOVE_OBSTACLE, o);
231 }
232 return o;
233 }
234
235 /**
236 * Change an obstacle label, possibly removing it.
237 * @return the number of actions that were pushed on the stack.
238 */
action_change_obstacle_label(level * EditLevel,obstacle * obstacle,char * name,int undoable)239 static int action_change_obstacle_label(level *EditLevel, obstacle *obstacle, char *name, int undoable)
240 {
241 // If the obstacle already has a label, remove it
242 char *old_label = get_obstacle_extension(EditLevel, obstacle, OBSTACLE_EXTENSION_LABEL);
243 if (old_label) {
244 old_label = strdup(old_label);
245 del_obstacle_extension(EditLevel, obstacle, OBSTACLE_EXTENSION_LABEL);
246 }
247
248 // Create the undo action if appropriate
249 if (undoable) {
250 action_push(ACT_SET_OBSTACLE_LABEL, obstacle, old_label);
251 } else {
252 if (old_label) {
253 free(old_label);
254 old_label = NULL;
255 }
256 }
257
258 // If the new label is empty, we are done
259 if (!name || !strlen(name))
260 return 0;
261
262 // Assign the new label
263 add_obstacle_extension(EditLevel, obstacle, OBSTACLE_EXTENSION_LABEL, strdup(name));
264
265 return undoable;
266 }
267
action_change_obstacle_label_user(level * EditLevel,obstacle * our_obstacle)268 void action_change_obstacle_label_user(level *EditLevel, obstacle *our_obstacle)
269 {
270 char *name = NULL;
271
272 if (!our_obstacle)
273 return;
274
275 char *old_label = get_obstacle_extension(EditLevel, our_obstacle, OBSTACLE_EXTENSION_LABEL);
276 name = GetEditableStringInPopupWindow(1000, _("\nPlease enter obstacle label: \n\n"), old_label ? old_label : "");
277
278 if (name) {
279 action_change_obstacle_label(EditLevel, our_obstacle, name, 1);
280 free(name);
281 }
282 }
283
action_remove_obstacle_user(Level EditLevel,obstacle * our_obstacle)284 void action_remove_obstacle_user(Level EditLevel, obstacle * our_obstacle)
285 {
286 /* Save obstacle information for the undo action */
287 double posx, posy;
288 int type;
289
290 type = our_obstacle->type;
291 posx = our_obstacle->pos.x;
292 posy = our_obstacle->pos.y;
293
294 // The obstacle can be in the selection list
295 remove_element_from_selection(our_obstacle);
296
297 // make an undoable removal
298 del_obstacle(our_obstacle);
299 action_push(ACT_CREATE_OBSTACLE, posx, posy, type);
300 }
301
302 /**
303 * Create an item on the map, add this action in the stack undo/redo
304 * \param EditLevel Pointer towards the editing level where create the item
305 * \param x The x position of the item
306 * \param y The y position of the item
307 * \param type The type of the item
308 * \return The new item created
309 */
action_create_item(level * EditLevel,float x,float y,int type)310 item *action_create_item(level *EditLevel, float x, float y, int type)
311 {
312 int multiplicity = 1;
313
314 if (ItemMap[type].item_group_together_in_inventory) {
315 // Display a popup window with a number selector in order to choose the
316 // multiplicity of the item
317 multiplicity =
318 do_graphical_number_selection_in_range(1, (!item_spec_eq_id(type, "Valuable Circuits")) ? 100 : 1000, 1, 0);
319
320 if (multiplicity == 0) {
321 // Do not drop an item on the floor when the user cancelled the action.
322 return NULL;
323 }
324 }
325
326 // Create an item on the map
327 item *it = DropItemAt(type, EditLevel->levelnum, x, y, multiplicity);
328 if (it) {
329 action_push(ACT_REMOVE_ITEM, it);
330 }
331 return it;
332 }
333
334 /**
335 * Remove an item on the map and add this action in the stack undo/redo
336 * \param EditLevel Pointer towards the editing level where delete the item
337 * \param it The item which we want deleted
338 */
action_remove_item(level * EditLevel,item * it)339 void action_remove_item(level *EditLevel, item *it)
340 {
341 float x, y;
342 int type;
343
344 // Save item information for the undo action
345 type = it->type;
346 x = it->pos.x;
347 y = it->pos.y;
348
349 // The item can be in the selection list
350 remove_element_from_selection(it);
351
352 // Remove the item on the map
353 DeleteItem(it);
354
355 // Make an undoable removal
356 action_push(ACT_CREATE_ITEM, x, y, type);
357 }
358
359 /**
360 * Move an item on the map and add this action in stack undo/redo
361 * \param EditLevel Pointer towards the editing level where move the item
362 * \param it The item which we want moved
363 * \param x The new x position of the item
364 * \param y The new y position of the item
365 */
action_move_item(level * EditLevel,item * it,float x,float y)366 void action_move_item(level *EditLevel, item *it, float x, float y)
367 {
368 float oldx, oldy;
369
370 // Save item position for the undo action
371 oldx = it->pos.x;
372 oldy = it->pos.y;
373
374 // Define the new position of the item
375 it->pos.x = x;
376 it->pos.y = y;
377
378 action_push(ACT_MOVE_ITEM, it, oldx, oldy);
379 }
380
381 /**
382 * Create a waypoint on the map, add this action in the undo/redo stack
383 * \param EditLevel Pointer towards the currently edited level where create the waypoint
384 * \param x The x position of the waypoint
385 * \param y The y position of the waypoint
386 * \param random_spawn TRUE if the waypoint can be used to place a random bot
387 * \return The new waypoint created
388 */
action_create_waypoint(level * EditLevel,int x,int y,int random_spawn)389 waypoint *action_create_waypoint(level *EditLevel, int x, int y, int random_spawn)
390 {
391 waypoint *wpts = EditLevel->waypoints.arr;
392 int wpnum;
393
394 wpnum = get_waypoint(EditLevel, x, y);
395 if (wpnum < 0) {
396 // When the waypoint doesn't exist on the map, create it
397 wpnum = add_waypoint(EditLevel, x, y, random_spawn);
398
399 // The waypoints array may have been moved by the add_waypoint call
400 wpts = EditLevel->waypoints.arr;
401
402 // Make an undoable action
403 action_push(ACT_REMOVE_WAYPOINT, wpts[wpnum].x, wpts[wpnum].y);
404 }
405
406 return &wpts[wpnum];
407 }
408
409 /**
410 * Remove a waypoint on the map and add this action in the undo/redo stack
411 * \param EditLevel Pointer towards the currently edited level where delete the waypoint
412 * \param x The x position of the waypoint
413 * \param y The y position of the waypoint
414 */
action_remove_waypoint(level * EditLevel,int x,int y)415 void action_remove_waypoint(level *EditLevel, int x, int y)
416 {
417 waypoint *wpts = EditLevel->waypoints.arr;
418
419 int wpnum = get_waypoint(EditLevel, x, y);
420 if (wpnum < 0) {
421 // When the waypoint doesn't exist on the map, we are done
422 return;
423 }
424
425 // Save waypoint information for the undo action
426 int old_random_spawn = wpts[wpnum].suppress_random_spawn;
427
428 // The waypoint can be in the selection list
429 remove_element_from_selection(&wpts[wpnum]);
430
431 // Remove the waypoint on the map
432 del_waypoint(EditLevel, x, y);
433
434 // If a route was being traced from this waypoint, remove it
435 leveleditor_place_reset_waypoint_route(wpnum);
436
437 // Make an undoable action
438 action_push(ACT_CREATE_WAYPOINT, x, y, old_random_spawn);
439 }
440
action_move_waypoint(level * lvl,waypoint * w,int x,int y)441 void action_move_waypoint(level *lvl, waypoint *w, int x, int y)
442 {
443 int oldx, oldy;
444
445 oldx = w->x;
446 oldy = w->y;
447
448 move_waypoint(lvl, w, x, y);
449
450 action_push(ACT_MOVE_WAYPOINT, w, oldx, oldy);
451 }
452
453 /**
454 * Set/unset the flag for random bots and add this action in the undo/redo stack
455 * \param EditLevel Pointer towards the currently edited level where toggle the waypoint
456 * \param x The x position of the waypoint
457 * \param y The y position of the waypoint
458 */
action_toggle_waypoint_randomspawn(level * EditLevel,int x,int y)459 void action_toggle_waypoint_randomspawn(level *EditLevel, int x, int y)
460 {
461 waypoint *wpts = EditLevel->waypoints.arr;
462
463 int wpnum = get_waypoint(EditLevel, x, y);
464 if (wpnum < 0) {
465 return;
466 }
467
468 // Toggle the flag for random bots
469 wpts[wpnum].suppress_random_spawn = !wpts[wpnum].suppress_random_spawn;
470
471 // Make an undoable action
472 action_push(ACT_TOGGLE_WAYPOINT_RSPAWN, x, y);
473 }
474
action_toggle_waypoint_connection(level * EditLevel,int id_origin,int id_target,int removeifpresent,int undoable)475 int action_toggle_waypoint_connection(level *EditLevel, int id_origin, int id_target, int removeifpresent, int undoable)
476 {
477 waypoint *wpts = EditLevel->waypoints.arr;
478 int *connections;
479 int i;
480
481 // Get the connections of the waypoint;
482 connections = wpts[id_origin].connections.arr;
483
484 for (i = 0; i < wpts[id_origin].connections.size; i++) {
485 if (connections[i] == id_target) {
486 if (removeifpresent) {
487 // Delete the connection of the waypoint
488 dynarray_del(&wpts[id_origin].connections, i, sizeof(int));
489 }
490 if (undoable)
491 action_push(ACT_TOGGLE_WAYPOINT_CONNECTION, id_origin, id_target);
492 return -1;
493 }
494 }
495
496 // Add the target connection of the waypoint
497 dynarray_add(&wpts[id_origin].connections, &id_target, sizeof(int));
498
499 if (undoable)
500 action_push(ACT_TOGGLE_WAYPOINT_CONNECTION, id_origin, id_target);
501
502 return 1;
503 }
504
level_editor_action_toggle_waypoint_connection_user(level * EditLevel,int xpos,int ypos)505 void level_editor_action_toggle_waypoint_connection_user(level * EditLevel, int xpos, int ypos)
506 {
507 int wpnum;
508
509 wpnum = get_waypoint(EditLevel, xpos, ypos);
510 if (wpnum < 0) {
511 // No waypoint is currently targeted.
512 return;
513 }
514
515 if (OriginWaypoint == -1) {
516 // Set the origin waypoint for the next connection.
517 OriginWaypoint = wpnum;
518 return;
519 }
520
521 if (OriginWaypoint != wpnum) {
522 // Toggle a connection between the origin waypoint and the currently targeted.
523 action_toggle_waypoint_connection(EditLevel, OriginWaypoint, wpnum, 1, 1);
524 }
525
526 OriginWaypoint = -1;
527 }
528
lvledit_action_toggle_waypoint(int randomspawn)529 void lvledit_action_toggle_waypoint(int randomspawn)
530 {
531 int wpnum = get_waypoint(EditLevel(), EditX(), EditY());
532 if (wpnum < 0) {
533 // If the waypoint doesn't exist at the map position, create it
534 action_create_waypoint(EditLevel(), EditX(), EditY(), randomspawn);
535 } else {
536 // An existing waypoint will be removed or have its
537 // randomspawn flag toggled
538 waypoint *wpts = EditLevel()->waypoints.arr;
539 if (randomspawn) {
540 action_toggle_waypoint_randomspawn(EditLevel(), wpts[wpnum].x, wpts[wpnum].y);
541 } else {
542 action_remove_waypoint(EditLevel(), wpts[wpnum].x, wpts[wpnum].y);
543 }
544 }
545 }
546
action_set_floor_layer(Level EditLevel,int x,int y,int layer,int type)547 void action_set_floor_layer(Level EditLevel, int x, int y, int layer, int type)
548 {
549 if (layer >= EditLevel->floor_layers)
550 EditLevel->floor_layers++;
551
552 int old = EditLevel->map[y][x].floor_values[layer];
553 EditLevel->map[y][x].floor_values[layer] = type;
554 action_push(ACT_TILE_FLOOR_SET, x, y, layer, old);
555 }
556
action_set_floor(Level EditLevel,int x,int y,int type)557 void action_set_floor(Level EditLevel, int x, int y, int type)
558 {
559 // Underlay floor tiles are placed in layer #0.
560 // Overlay floor tiles are placed in layer #1.
561 int layer = type < MAX_UNDERLAY_FLOOR_TILES ? 0 : 1;
562 action_set_floor_layer(EditLevel, x, y, layer, type);
563 }
564
action_change_floor_layer(level * lvl,int layer)565 void action_change_floor_layer(level *lvl, int layer)
566 {
567 int old_floor_layer = current_floor_layer;
568 current_floor_layer = layer;
569 action_push(ACT_CHANGE_FLOOR_LAYER, old_floor_layer);
570 }
571
action_change_map_label(level * EditLevel,int i,char * name,int x,int y)572 static void action_change_map_label(level *EditLevel, int i, char *name, int x, int y)
573 {
574 struct map_label *map_label;
575 char *old_label = NULL;
576
577 // If the map label exist, remove it
578 if (i < EditLevel->map_labels.size) {
579 // Get the map label
580 map_label = &ACCESS_MAP_LABEL(EditLevel->map_labels, i);
581
582 // Get the old label for undoable actions
583 old_label = strdup(map_label->label_name);
584
585 // Delete the map label
586 del_map_label(EditLevel, map_label->label_name);
587 }
588
589 action_push(ACT_SET_MAP_LABEL, i, old_label, x, y);
590
591 // If the new label is empty, we are done
592 if (!name || !strlen(name))
593 return;
594
595 // Create a new map label at the position of cursor
596 add_map_label(EditLevel, x, y, strdup(name));
597 }
598
level_editor_action_change_map_label_user(level * EditLevel,float x,float y)599 void level_editor_action_change_map_label_user(level *EditLevel, float x, float y)
600 {
601 struct map_label *map_label = NULL;
602 char *name;
603 char *old_name = NULL;
604 char suggested_label[200];
605 int i;
606
607 suggested_label[0] = '\0';
608
609 // We check if a map label already exists for this spot
610 for (i = 0; i < EditLevel->map_labels.size; i++) {
611 map_label = &ACCESS_MAP_LABEL(EditLevel->map_labels, i);
612
613 if ((fabs(map_label->pos.x + 0.5 - x) < 0.5) &&
614 (fabs(map_label->pos.y + 0.5 - y) < 0.5)) {
615
616 // Use the old label as a suggestion
617 old_name = map_label->label_name;
618 strncpy(suggested_label, old_name, sizeof(suggested_label) - 1);
619 suggested_label[sizeof(suggested_label) - 1] = '\0';
620 break;
621 }
622 }
623
624 // Check if the name entered already exists
625
626 while (1) {
627 // Show popup window to enter a new map label
628 name = GetEditableStringInPopupWindow(sizeof(suggested_label) - 1, _("\nPlease enter map label: \n\n"), suggested_label);
629 if (!name || (old_name && !strcmp(name, old_name))) {
630 // Do not change label
631 free(name);
632 return;
633 }
634
635 map_label = map_label_exists(name);
636 if (!map_label) {
637 // When the new name of the map label does not exist, we are done
638 break;
639 }
640
641 // When the new name already exists, we must not create an other map
642 // label with the same name, but we want to display an alert window,
643 // and then go back to input box with the name still present
644 alert_window("%s", _("The new name of the map label already exists, please choose an other name."));
645
646 // Copy the name in order to have it in the input box
647 strncpy(suggested_label, name, sizeof(suggested_label) - 1);
648 suggested_label[sizeof(suggested_label) - 1] = '\0';
649 free(name);
650
651 // Restore the menu background in order to correctly draw the next popup window
652 RestoreMenuBackground(1);
653 }
654
655 // Change a map label when the name enter by the user is valid
656 action_change_map_label(EditLevel, i, name, rintf(x - 0.5), rintf(y - 0.5));
657 free(name);
658 }
659
660 /**
661 * @brief Create a map label on the map and push this action on the undo/redo stack.
662 *
663 * @param lvl Pointer towards the level where the map label is created.
664 * @param x The x position of the map label.
665 * @param y The y position of the map label.
666 * @param label_name The name of the map label.
667 */
action_create_map_label(level * lvl,int x,int y,char * label_name)668 void action_create_map_label(level *lvl, int x, int y, char *label_name)
669 {
670 add_map_label(lvl, x, y, strdup(label_name));
671
672 action_push(ACT_REMOVE_MAP_LABEL, x, y);
673 }
674
675 /**
676 * @brief Remove a map label on the map and push this action on the undo/redo stack.
677 *
678 * @param lvl Pointer towards the level where the map label is removed.
679 * @param x The x position of the map label.
680 * @param y The y position of the map label.
681 */
action_remove_map_label(level * lvl,int x,int y)682 void action_remove_map_label(level *lvl, int x, int y)
683 {
684 char *old_label_name;
685 map_label *m;
686
687 m = get_map_label_from_coords(lvl, x, y);
688 if (m) {
689 old_label_name = strdup(m->label_name);
690
691 remove_element_from_selection(m);
692
693 del_map_label(lvl, m->label_name);
694
695 action_push(ACT_CREATE_MAP_LABEL, x, y, old_label_name);
696 }
697 }
698
action_create_enemy(level * lvl,enemy * en)699 enemy *action_create_enemy(level *lvl, enemy *en)
700 {
701 enemy_insert_into_lists(en, TRUE);
702 action_push(ACT_REMOVE_ENEMY, en);
703 return en;
704 }
705
action_remove_enemy(level * lvl,enemy * en)706 void action_remove_enemy(level *lvl, enemy *en)
707 {
708 enemy *current_enemy, *nerot;
709
710 remove_element_from_selection(en);
711
712 BROWSE_ALIVE_BOTS_SAFE(current_enemy, nerot) {
713 if (current_enemy == en)
714 list_del(¤t_enemy->global_list);
715 }
716
717 BROWSE_DEAD_BOTS_SAFE(current_enemy, nerot) {
718 if (current_enemy == en)
719 list_del(¤t_enemy->global_list);
720 }
721
722 BROWSE_LEVEL_BOTS_SAFE(current_enemy, nerot, lvl->levelnum) {
723 if (current_enemy == en)
724 list_del(¤t_enemy->level_list);
725 }
726
727 action_push(ACT_CREATE_ENEMY, en);
728 }
729
730 /**
731 * @fn void jump_to_level( int target_map, float x, float y)
732 *
733 * @brief jumps to a target level, saving this level on the undo/redo stack
734 */
action_jump_to_level(int target_level,double x,double y)735 void action_jump_to_level(int target_level, double x, double y)
736 {
737 // When the user wants to change the current edited level, reset tools
738 lvledit_reset_tools();
739
740 action_push(ACT_JUMP_TO_LEVEL, EditLevel()->levelnum, Me.pos.x, Me.pos.y); //< sets undo or redo stack, depending on push_mode state
741 reset_visible_levels();
742 Teleport(target_level, (float)x, (float)y, FALSE, TRUE);
743 }
744
745 /**
746 * Jump to the center of a level.
747 *
748 * @param level_num The level id.
749 */
action_jump_to_level_center(int level_num)750 void action_jump_to_level_center(int level_num)
751 {
752 // Calculate the center of the level.
753 float x = curShip.AllLevels[level_num]->xlen / 2;
754 float y = curShip.AllLevels[level_num]->ylen / 2;
755
756 action_jump_to_level(level_num, x, y);
757 }
758
action_do(level * level,action * a)759 static void action_do(level * level, action * a)
760 {
761 switch (a->type) {
762 case ACT_CREATE_OBSTACLE:
763 action_create_obstacle_user(level, a->d.create_obstacle.x, a->d.create_obstacle.y, a->d.create_obstacle.new_obstacle_type);
764 break;
765 case ACT_REMOVE_OBSTACLE:
766 action_remove_obstacle_user(level, a->d.delete_obstacle);
767 break;
768 case ACT_MOVE_OBSTACLE:
769 action_move_obstacle(level, a->d.move_obstacle.obstacle, a->d.move_obstacle.newx, a->d.move_obstacle.newy);
770 break;
771 case ACT_CREATE_ITEM:
772 action_create_item(level, a->d.create_item.x, a->d.create_item.y, a->d.create_item.type);
773 break;
774 case ACT_REMOVE_ITEM:
775 action_remove_item(level, a->d.delete_item);
776 break;
777 case ACT_MOVE_ITEM:
778 action_move_item(level, a->d.move_item.item, a->d.move_item.newx, a->d.move_item.newy);
779 break;
780 case ACT_CREATE_WAYPOINT:
781 action_create_waypoint(level, a->d.create_waypoint.x, a->d.create_waypoint.y, a->d.create_waypoint.suppress_random_spawn);
782 break;
783 case ACT_REMOVE_WAYPOINT:
784 action_remove_waypoint(level, a->d.delete_waypoint.x, a->d.delete_waypoint.y);
785 break;
786 case ACT_MOVE_WAYPOINT:
787 action_move_waypoint(level, a->d.move_waypoint.w, a->d.move_waypoint.newx, a->d.move_waypoint.newy);
788 break;
789 case ACT_TOGGLE_WAYPOINT_RSPAWN:
790 action_toggle_waypoint_randomspawn(level, a->d.toggle_waypoint_rspawn.x, a->d.toggle_waypoint_rspawn.y);
791 break;
792 case ACT_TOGGLE_WAYPOINT_CONNECTION:
793 action_toggle_waypoint_connection(level, a->d.toggle_waypoint_connection.x, a->d.toggle_waypoint_connection.y, 1, 1);
794 break;
795 case ACT_TILE_FLOOR_SET:
796 action_set_floor_layer(level, a->d.change_floor.x, a->d.change_floor.y, a->d.change_floor.layer, a->d.change_floor.type);
797 break;
798 case ACT_MULTIPLE_ACTIONS:
799 error_message(__FUNCTION__, "Passed a multiple actions meta-action as parameter. A real action is needed.", PLEASE_INFORM);
800 break;
801 case ACT_SET_OBSTACLE_LABEL:
802 action_change_obstacle_label(level, a->d.change_obstacle_name.obstacle, a->d.change_obstacle_name.new_name, 1);
803 break;
804 case ACT_SET_MAP_LABEL:
805 action_change_map_label(level, a->d.change_label_name.id, a->d.change_label_name.new_name, a->d.change_label_name.x, a->d.change_label_name.y);
806 break;
807 case ACT_JUMP_TO_LEVEL:
808 action_jump_to_level(a->d.jump_to_level.target_level, a->d.jump_to_level.x, a->d.jump_to_level.y);
809 break;
810 case ACT_CHANGE_FLOOR_LAYER:
811 action_change_floor_layer(level, a->d.target_layer);
812 break;
813 case ACT_CREATE_MAP_LABEL:
814 action_create_map_label(level, a->d.create_map_label.x, a->d.create_map_label.y, a->d.create_map_label.label_name);
815 break;
816 case ACT_REMOVE_MAP_LABEL:
817 action_remove_map_label(level, a->d.delete_map_label.x, a->d.delete_map_label.y);
818 break;
819 case ACT_CREATE_ENEMY:
820 action_create_enemy(level, a->d.create_enemy);
821 a->d.create_enemy = NULL;
822 break;
823 case ACT_REMOVE_ENEMY:
824 action_remove_enemy(level, a->d.delete_enemy);
825 break;
826 }
827 }
828
__level_editor_do_action_from_stack(struct list_head * stack)829 static void __level_editor_do_action_from_stack(struct list_head *stack)
830 {
831 action *a;
832
833 if (list_empty(stack))
834 return;
835
836 // Get the top action from the undo/redo stack
837 a = list_entry(stack->next, action, node);
838
839 if (a->type == ACT_MULTIPLE_ACTIONS) {
840 // When the action is multiple, we must execute each action,
841 // and push all actions in the stack in order to undo
842 int i, max;
843
844 max = a->d.number_actions;
845 clear_action(a);
846
847 // Execute all actions
848 for (i = 0; i < max; i++) {
849 __level_editor_do_action_from_stack(stack);
850 }
851
852 // Push all actions in the stack in order to undo
853 action_push(ACT_MULTIPLE_ACTIONS, max);
854 } else {
855 action_do(EditLevel(), a);
856 clear_action(a);
857 }
858 }
859
level_editor_action_undo()860 void level_editor_action_undo()
861 {
862 push_mode = UNDO;
863 __level_editor_do_action_from_stack(&to_undo);
864 push_mode = NORMAL;
865 }
866
level_editor_action_redo()867 void level_editor_action_redo()
868 {
869 push_mode = REDO;
870 __level_editor_do_action_from_stack(&to_redo);
871 push_mode = NORMAL;
872 }
873
874 /**
875 * Place an aligned object on the map with the keypad
876 * \param positionid The position of the object on the purple grid
877 */
level_editor_place_aligned_object(int positionid)878 void level_editor_place_aligned_object(int positionid)
879 {
880 float position_offset_x[9] = { 0, 0.5, 1.0, 0, 0.5, 1.0, 0, 0.5, 1.0 };
881 float position_offset_y[9] = { 1.0, 1.0, 1.0, 0.5, 0.5, 0.5, 0, 0, 0 };
882 struct widget_lvledit_categoryselect *cs = get_current_object_type();
883 int type = cs->indices[cs->selected_tile_nb];
884 moderately_finepoint pos;
885
886 positionid--;
887
888 // Calculate the position of the object
889 pos.x = (int)Me.pos.x + position_offset_x[positionid];
890 pos.y = (int)Me.pos.y + position_offset_y[positionid];
891
892 if (!pos_inside_level(pos.x, pos.y, EditLevel()))
893 {
894 // Do not place an aligned object outside the current level.
895 return;
896 }
897
898 switch (cs->type) {
899 case OBJECT_OBSTACLE:
900 action_create_obstacle_user(EditLevel(), pos.x, pos.y, type);
901 break;
902 case OBJECT_FLOOR:
903 action_set_floor(EditLevel(), (int)Me.pos.x, (int)Me.pos.y, type);
904 break;
905 case OBJECT_ITEM:
906 action_create_item(EditLevel(), pos.x, pos.y, type);
907 break;
908 default:
909 break;
910 }
911 }
912
913 /**
914 * This function should create a completely new level into the existing
915 * ship structure that we already have. The new level will be rather
916 * small and simple.
917 */
CreateNewMapLevel(int level_num)918 void CreateNewMapLevel(int level_num)
919 {
920 level *NewLevel;
921 int i, k;
922
923 // Get the memory for one level
924 //
925 NewLevel = (Level) MyMalloc(sizeof(level));
926
927 DebugPrintf(0, "\n-----------------------------------------------------------------");
928 DebugPrintf(0, "\nStarting to create and add a completely new level to the ship.");
929
930 // Now we proceed in the order of the struct 'level' in the
931 // struct.h file so that we can easily verify if we've handled
932 // all the data structure or left something out which could
933 // be terrible!
934 //
935 NewLevel->levelnum = level_num;
936 NewLevel->xlen = 90;
937 NewLevel->ylen = 90;
938 NewLevel->floor_layers = 1;
939 NewLevel->light_bonus = 19;
940 NewLevel->minimum_light_value = 19;
941 NewLevel->infinite_running_on_this_level = FALSE;
942 NewLevel->random_dungeon = 0;
943 NewLevel->dungeon_generated = FALSE;
944 NewLevel->Levelname = strdup("New level just created");
945 NewLevel->Background_Song_Name = strdup("TheBeginning.ogg");
946
947 // First we initialize the floor with 'empty' values
948 //
949 for (i = 0; i < NewLevel->ylen; i++) {
950 NewLevel->map[i] = MyMalloc(NewLevel->xlen * sizeof(map_tile));
951 for (k = 0; k < NewLevel->xlen; k++) {
952 init_map_tile(&NewLevel->map[i][k]);
953 }
954 }
955 // Now we initialize the level jump interface variables with 'empty' values
956 //
957 NewLevel->jump_target_north = (-1);
958 NewLevel->jump_target_south = (-1);
959 NewLevel->jump_target_east = (-1);
960 NewLevel->jump_target_west = (-1);
961
962 // Now we initialize the map obstacles with 'empty' information
963 //
964 for (i = 0; i < MAX_OBSTACLES_ON_MAP; i++) {
965 NewLevel->obstacle_list[i].type = (-1);
966 NewLevel->obstacle_list[i].pos.x = (-1);
967 NewLevel->obstacle_list[i].pos.y = (-1);
968 NewLevel->obstacle_list[i].pos.z = level_num;
969 }
970
971 // First we initialize the items arrays with 'empty' information
972 //
973 for (i = 0; i < MAX_ITEMS_PER_LEVEL; i++) {
974 NewLevel->ItemList[i].pos.x = (-1);
975 NewLevel->ItemList[i].pos.y = (-1);
976 NewLevel->ItemList[i].pos.z = (-1);
977 NewLevel->ItemList[i].type = (-1);
978
979 }
980
981 // Initialize obstacle extensions
982 dynarray_init(&NewLevel->obstacle_extensions, 10, sizeof(struct obstacle_extension));
983
984 // Initialize map labels
985 dynarray_init(&NewLevel->map_labels, 10, sizeof(struct map_label));
986
987 // Initialize waypoints
988 dynarray_init(&NewLevel->waypoints, 10, sizeof(struct waypoint));
989
990 curShip.AllLevels[level_num] = NewLevel;
991 }
992
delete_map_level(int lnum)993 void delete_map_level(int lnum)
994 {
995 // Remove references to this level from others
996 level *l = NULL;
997
998 BROWSE_LEVELS(l) {
999 if (l->jump_target_north == lnum)
1000 l->jump_target_north = -1;
1001 if (l->jump_target_south == lnum)
1002 l->jump_target_south = -1;
1003 if (l->jump_target_west == lnum)
1004 l->jump_target_west = -1;
1005 if (l->jump_target_east == lnum)
1006 l->jump_target_east = -1;
1007 }
1008
1009 // Free memory
1010 free_ship_level(curShip.AllLevels[lnum]);
1011 curShip.AllLevels[lnum] = NULL;
1012
1013 if (lnum == curShip.num_levels - 1)
1014 curShip.num_levels--;
1015
1016 // Removing a level cannot be undo, so we need to clear everything that could have
1017 // a reference to an object of the deleted level.
1018 action_freestack();
1019 clear_selection(-1);
1020 clear_clipboard(-1);
1021 }
1022
get_chest_contents(level * l,obstacle * o,item * items[MAX_ITEMS_IN_INVENTORY])1023 static int get_chest_contents(level *l, obstacle *o, item *items[MAX_ITEMS_IN_INVENTORY])
1024 {
1025 struct dynarray *itemlist = get_obstacle_extension(l, o, OBSTACLE_EXTENSION_CHEST_ITEMS);
1026
1027 memset(items, 0, MAX_ITEMS_IN_INVENTORY*sizeof(item *));
1028
1029 if (!itemlist) {
1030 return 0;
1031 }
1032
1033 int i;
1034 int curitem = 0;
1035 for (i = 0; i < itemlist->size && i < MAX_ITEMS_IN_INVENTORY; i++) {
1036 items[curitem++] = &((item *)itemlist->arr)[i];
1037 }
1038
1039 return curitem;
1040 }
1041
level_editor_edit_chest(obstacle * o)1042 void level_editor_edit_chest(obstacle *o)
1043 {
1044 item *chest_items[MAX_ITEMS_IN_INVENTORY];
1045 item *user_items[2];
1046 int chest_nb_items;
1047 int done = 0;
1048 shop_decision shop_order;
1049 item *tmp;
1050
1051 item dummy_addtochest;
1052 init_item(&dummy_addtochest);
1053 dummy_addtochest.type = 1;
1054 FillInItemProperties(&dummy_addtochest, 2, 1);
1055
1056 user_items[0] = &dummy_addtochest;
1057 user_items[1] = NULL;
1058
1059 struct dynarray *itemlist = get_obstacle_extension(CURLEVEL(), o, OBSTACLE_EXTENSION_CHEST_ITEMS);
1060
1061 // Safety check
1062 struct obstacle_spec *obs_spec = get_obstacle_spec(o->type);
1063 if (!obs_spec->action || strncmp(obs_spec->action, "chest", 5)) {
1064 error_message(__FUNCTION__, "Tried to edit the contents of a chest, but the obstacle is not a chest.",
1065 PLEASE_INFORM | IS_FATAL);
1066 }
1067
1068 while (!done) {
1069
1070 // Build the list of items in the chest
1071 chest_nb_items = get_chest_contents(CURLEVEL(), o, chest_items);
1072
1073 // Display the shop interface
1074 done = GreatShopInterface(chest_nb_items, chest_items, 1, user_items, &shop_order);
1075
1076 // BUY removes an item from the chest
1077 // SELL spawns the drop item interface
1078 switch (shop_order.shop_command) {
1079 case BUY_1_ITEM:
1080 DeleteItem(chest_items[shop_order.item_selected]);
1081 dynarray_del(itemlist, shop_order.item_selected, sizeof(item));
1082 break;
1083 case SELL_1_ITEM:
1084 tmp = ItemDropFromLevelEditor();
1085 if (tmp) {
1086
1087 if (!itemlist) {
1088 itemlist = dynarray_alloc(10, sizeof(item));
1089 add_obstacle_extension(CURLEVEL(), o, OBSTACLE_EXTENSION_CHEST_ITEMS, itemlist);
1090 }
1091
1092 dynarray_add(itemlist, tmp, sizeof(item));
1093
1094 // delete the ground copy
1095 DeleteItem(tmp);
1096 }
1097 break;
1098 default:
1099 ;
1100 }
1101 }
1102 }
1103
1104 #undef _leveleditor_actions_c
1105