1 /***********************************************************************
2 Freeciv - Copyright (C) 2005 - The Freeciv Project
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; either version 2, or (at your option)
6 any later version.
7
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12 ***********************************************************************/
13
14 #ifdef HAVE_CONFIG_H
15 #include <fc_config.h>
16 #endif
17
18 #include <limits.h> /* USHRT_MAX */
19
20 /* utility */
21 #include "bitvector.h"
22 #include "capability.h"
23 #include "fcintl.h"
24 #include "log.h"
25 #include "shared.h"
26 #include "support.h"
27
28 /* common */
29 #include "events.h"
30 #include "game.h"
31 #include "government.h"
32 #include "map.h"
33 #include "movement.h"
34 #include "nation.h"
35 #include "terrain.h"
36 #include "research.h"
37 #include "unitlist.h"
38
39 /* generator */
40 #include "utilities.h"
41
42 /* server */
43 #include "aiiface.h"
44 #include "citytools.h"
45 #include "cityturn.h"
46 #include "connecthand.h"
47 #include "gamehand.h"
48 #include "hand_gen.h"
49 #include "maphand.h"
50 #include "plrhand.h"
51 #include "notify.h"
52 #include "sanitycheck.h"
53 #include "srv_main.h"
54 #include "stdinhand.h"
55 #include "techtools.h"
56 #include "unittools.h"
57
58 #include "edithand.h"
59
60 /* Set if anything in a sequence of edits triggers the expensive
61 * assign_continent_numbers() check, which will be done once when the
62 * sequence is complete. */
63 static bool need_continents_reassigned = FALSE;
64 /* Hold pointers to tiles which were changed during the edit sequence,
65 * so that they can be sanity-checked when the sequence is complete
66 * and final global fix-ups have been done. */
67 static struct tile_hash *modified_tile_table = NULL;
68
69 /* Array of size player_slot_count() indexed by player
70 * number to tell whether a given player has fog of war
71 * disabled in edit mode. */
72 static bool *unfogged_players;
73
74 /****************************************************************************
75 Initialize data structures required for edit mode.
76 ****************************************************************************/
edithand_init(void)77 void edithand_init(void)
78 {
79 if (NULL != modified_tile_table) {
80 tile_hash_destroy(modified_tile_table);
81 }
82 modified_tile_table = tile_hash_new();
83
84 need_continents_reassigned = FALSE;
85
86 if (unfogged_players != NULL) {
87 free(unfogged_players);
88 }
89 unfogged_players = fc_calloc(player_slot_count(), sizeof(bool));
90 }
91
92 /****************************************************************************
93 Free all memory used by data structures required for edit mode.
94 ****************************************************************************/
edithand_free(void)95 void edithand_free(void)
96 {
97 if (NULL != modified_tile_table) {
98 tile_hash_destroy(modified_tile_table);
99 modified_tile_table = NULL;
100 }
101
102 if (unfogged_players != NULL) {
103 free(unfogged_players);
104 unfogged_players = NULL;
105 }
106 }
107
108 /****************************************************************************
109 Send the needed packets for connections entering in the editing mode.
110 ****************************************************************************/
edithand_send_initial_packets(struct conn_list * dest)111 void edithand_send_initial_packets(struct conn_list *dest)
112 {
113 struct packet_edit_startpos startpos;
114 struct packet_edit_startpos_full startpos_full;
115
116 if (NULL == dest) {
117 dest = game.est_connections;
118 }
119
120 /* Send map start positions. */
121 map_startpos_iterate(psp) {
122 startpos.id = tile_index(startpos_tile(psp));
123 startpos.removal = FALSE;
124 startpos.tag = 0;
125
126 startpos_pack(psp, &startpos_full);
127
128 conn_list_iterate(dest, pconn) {
129 if (can_conn_edit(pconn)) {
130 send_packet_edit_startpos(pconn, &startpos);
131 send_packet_edit_startpos_full(pconn, &startpos_full);
132 }
133 } conn_list_iterate_end;
134 } map_startpos_iterate_end;
135 }
136
137 /****************************************************************************
138 Do the potentially slow checks required after one or several tiles'
139 terrain has change.
140 ****************************************************************************/
check_edited_tile_terrains(void)141 static void check_edited_tile_terrains(void)
142 {
143 if (need_continents_reassigned) {
144 assign_continent_numbers();
145 send_all_known_tiles(NULL);
146 need_continents_reassigned = FALSE;
147 }
148
149 #ifdef SANITY_CHECKING
150 tile_hash_iterate(modified_tile_table, ptile) {
151 sanity_check_tile(ptile);
152 } tile_hash_iterate_end;
153 #endif /* SANITY_CHECKING */
154 tile_hash_clear(modified_tile_table);
155 }
156
157 /****************************************************************************
158 Do any necessary checks after leaving edit mode to ensure that the game
159 is in a consistent state.
160 ****************************************************************************/
check_leaving_edit_mode(void)161 static void check_leaving_edit_mode(void)
162 {
163 bool unfogged;
164
165 conn_list_do_buffer(game.est_connections);
166 players_iterate(pplayer) {
167 unfogged = unfogged_players[player_number(pplayer)];
168 if (unfogged && game.info.fogofwar) {
169 enable_fog_of_war_player(pplayer);
170 } else if (!unfogged && !game.info.fogofwar) {
171 disable_fog_of_war_player(pplayer);
172 }
173 } players_iterate_end;
174
175 /* Clear the whole array. */
176 memset(unfogged_players, 0, player_slot_count() * sizeof(bool));
177
178 check_edited_tile_terrains();
179 conn_list_do_unbuffer(game.est_connections);
180 }
181
182 /****************************************************************************
183 Handles a request by the client to enter edit mode.
184 ****************************************************************************/
handle_edit_mode(struct connection * pc,bool is_edit_mode)185 void handle_edit_mode(struct connection *pc, bool is_edit_mode)
186 {
187 if (!can_conn_enable_editing(pc)) {
188 return;
189 }
190
191 if (!game.info.is_edit_mode && is_edit_mode) {
192 /* Someone could be cheating! Warn people. */
193 notify_conn(NULL, NULL, E_SETTING, ftc_editor,
194 _(" *** Server set to edit mode by %s! *** "),
195 conn_description(pc));
196 }
197
198 if (game.info.is_edit_mode && !is_edit_mode) {
199 notify_conn(NULL, NULL, E_SETTING, ftc_editor,
200 _(" *** Edit mode canceled by %s. *** "),
201 conn_description(pc));
202
203 check_leaving_edit_mode();
204 }
205
206 if (game.info.is_edit_mode != is_edit_mode) {
207 game.info.is_edit_mode = is_edit_mode;
208
209 send_game_info(NULL);
210 edithand_send_initial_packets(NULL);
211 }
212 }
213
214 /****************************************************************************
215 Base function to edit the terrain property of a tile. Returns TRUE if
216 the terrain has changed.
217 ****************************************************************************/
edit_tile_terrain_handling(struct tile * ptile,struct terrain * pterrain,bool send_info)218 static bool edit_tile_terrain_handling(struct tile *ptile,
219 struct terrain *pterrain,
220 bool send_info)
221 {
222 struct terrain *old_terrain = tile_terrain(ptile);
223
224 if (old_terrain == pterrain
225 || (terrain_has_flag(pterrain, TER_NO_CITIES)
226 && NULL != tile_city(ptile))) {
227 return FALSE;
228 }
229
230 tile_change_terrain(ptile, pterrain);
231 fix_tile_on_terrain_change(ptile, old_terrain, FALSE);
232 tile_hash_insert(modified_tile_table, ptile, NULL);
233 if (need_to_reassign_continents(old_terrain, pterrain)) {
234 need_continents_reassigned = TRUE;
235 }
236
237 if (send_info) {
238 update_tile_knowledge(ptile);
239 }
240
241 return TRUE;
242 }
243
244 /****************************************************************************
245 Base function to edit the resource property of a tile. Returns TRUE if
246 the resource has changed.
247 ****************************************************************************/
edit_tile_resource_handling(struct tile * ptile,struct resource * presource,bool send_info)248 static bool edit_tile_resource_handling(struct tile *ptile,
249 struct resource *presource,
250 bool send_info)
251 {
252 if (presource == tile_resource(ptile)) {
253 return FALSE;
254 }
255
256 if (NULL != presource
257 && !terrain_has_resource(tile_terrain(ptile), presource)) {
258 return FALSE;
259 }
260
261 tile_set_resource(ptile, presource);
262
263 if (send_info) {
264 update_tile_knowledge(ptile);
265 }
266
267 return TRUE;
268 }
269
270 /****************************************************************************
271 Base function to edit the extras property of a tile. Returns TRUE if
272 the extra state has changed.
273 ****************************************************************************/
edit_tile_extra_handling(struct tile * ptile,struct extra_type * pextra,bool remove_mode,bool send_info)274 static bool edit_tile_extra_handling(struct tile *ptile,
275 struct extra_type *pextra,
276 bool remove_mode, bool send_info)
277 {
278 if (remove_mode) {
279 if (!tile_has_extra(ptile, pextra)) {
280 return FALSE;
281 }
282
283 if (!tile_extra_rm_apply(ptile, pextra)) {
284 return FALSE;
285 }
286
287 terrain_changed(ptile);
288
289 } else {
290 if (tile_has_extra(ptile, pextra)) {
291 return FALSE;
292 }
293
294 if (!tile_extra_apply(ptile, pextra)) {
295 return FALSE;
296 }
297 }
298
299 if (send_info) {
300 update_tile_knowledge(ptile);
301 }
302
303 return TRUE;
304 }
305
306 /****************************************************************************
307 Handles a client request to change the terrain of the tile at the given
308 x, y coordinates. The 'size' parameter indicates that all tiles in a
309 square of "radius" 'size' should be affected. So size=1 corresponds to
310 the single tile case.
311 ****************************************************************************/
handle_edit_tile_terrain(struct connection * pc,int tile,Terrain_type_id terrain,int size)312 void handle_edit_tile_terrain(struct connection *pc, int tile,
313 Terrain_type_id terrain, int size)
314 {
315 struct terrain *pterrain;
316 struct tile *ptile_center;
317
318 ptile_center = index_to_tile(tile);
319 if (!ptile_center) {
320 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
321 _("Cannot edit the tile because %d is not a valid "
322 "tile index on this map!"), tile);
323 return;
324 }
325
326 pterrain = terrain_by_number(terrain);
327 if (!pterrain) {
328 notify_conn(pc->self, ptile_center, E_BAD_COMMAND, ftc_editor,
329 /* TRANS: ..." the tile <tile-coordinates> because"... */
330 _("Cannot modify terrain for the tile %s because "
331 "%d is not a valid terrain id."),
332 tile_link(ptile_center), terrain);
333 return;
334 }
335
336 conn_list_do_buffer(game.est_connections);
337 /* This iterates outward, which gives any units that can't survive on
338 * changed terrain the best chance of survival. */
339 square_iterate(ptile_center, size - 1, ptile) {
340 edit_tile_terrain_handling(ptile, pterrain, TRUE);
341 } square_iterate_end;
342 conn_list_do_unbuffer(game.est_connections);
343 }
344
345 /****************************************************************************
346 Handle a request to change one or more tiles' resources.
347 ****************************************************************************/
handle_edit_tile_resource(struct connection * pc,int tile,Resource_type_id resource,int size)348 void handle_edit_tile_resource(struct connection *pc, int tile,
349 Resource_type_id resource, int size)
350 {
351 struct resource *presource;
352 struct tile *ptile_center;
353
354 ptile_center = index_to_tile(tile);
355 if (!ptile_center) {
356 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
357 _("Cannot edit the tile because %d is not a valid "
358 "tile index on this map!"), tile);
359 return;
360 }
361 presource = resource_by_number(resource); /* May be NULL. */
362
363 conn_list_do_buffer(game.est_connections);
364 square_iterate(ptile_center, size - 1, ptile) {
365 edit_tile_resource_handling(ptile, presource, TRUE);
366 } square_iterate_end;
367 conn_list_do_unbuffer(game.est_connections);
368 }
369
370 /****************************************************************************
371 Handle a request to change one or more tiles' extras. The 'remove'
372 argument controls whether to remove or add the given extra from the tile.
373 ****************************************************************************/
handle_edit_tile_extra(struct connection * pc,int tile,int id,bool removal,int size)374 void handle_edit_tile_extra(struct connection *pc, int tile,
375 int id, bool removal, int size)
376 {
377 struct tile *ptile_center;
378
379 ptile_center = index_to_tile(tile);
380 if (!ptile_center) {
381 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
382 _("Cannot edit the tile because %d is not a valid "
383 "tile index on this map!"), tile);
384 return;
385 }
386
387 if (id < 0 || id >= game.control.num_extra_types) {
388 notify_conn(pc->self, ptile_center, E_BAD_COMMAND, ftc_editor,
389 /* TRANS: ..." the tile <tile-coordinates> because"... */
390 _("Cannot modify extras for the tile %s because "
391 "%d is not a valid extra id."),
392 tile_link(ptile_center), id);
393 return;
394 }
395
396 conn_list_do_buffer(game.est_connections);
397 square_iterate(ptile_center, size - 1, ptile) {
398 edit_tile_extra_handling(ptile, extra_by_number(id), removal, TRUE);
399 } square_iterate_end;
400 conn_list_do_unbuffer(game.est_connections);
401 }
402
403 /****************************************************************************
404 Handles tile information from the client, to make edits to tiles.
405 ****************************************************************************/
handle_edit_tile(struct connection * pc,const struct packet_edit_tile * packet)406 void handle_edit_tile(struct connection *pc,
407 const struct packet_edit_tile *packet)
408 {
409 struct tile *ptile;
410 bool changed = FALSE;
411
412 ptile = index_to_tile(packet->tile);
413 if (!ptile) {
414 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
415 _("Cannot edit the tile because %d is not a valid "
416 "tile index on this map!"), packet->tile);
417 return;
418 }
419
420 /* Handle changes in extras. */
421 if (!BV_ARE_EQUAL(packet->extras, ptile->extras)) {
422 extra_type_iterate(pextra) {
423 if (edit_tile_extra_handling(ptile, pextra,
424 !BV_ISSET(packet->extras, extra_number(pextra)),
425 FALSE)) {
426 changed = TRUE;
427 }
428 } extra_type_iterate_end;
429 }
430
431 /* Handle changes in label */
432 if (tile_set_label(ptile, packet->label)) {
433 changed = TRUE;
434 }
435
436 /* TODO: Handle more property edits. */
437
438
439 /* Send the new state to all affected. */
440 if (changed) {
441 update_tile_knowledge(ptile);
442 send_tile_info(NULL, ptile, FALSE);
443 }
444 }
445
446 /****************************************************************************
447 Handle a request to create 'count' units of type 'utid' at the tile given
448 by the x, y coordinates and owned by player with number 'owner'.
449 ****************************************************************************/
handle_edit_unit_create(struct connection * pc,int owner,int tile,Unit_type_id utid,int count,int tag)450 void handle_edit_unit_create(struct connection *pc, int owner, int tile,
451 Unit_type_id utid, int count, int tag)
452 {
453 struct tile *ptile;
454 struct unit_type *punittype;
455 struct player *pplayer;
456 struct city *homecity;
457 struct unit *punit;
458 int id, i;
459
460 ptile = index_to_tile(tile);
461 if (!ptile) {
462 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
463 _("Cannot create units because %d is not a valid "
464 "tile index on this map!"), tile);
465 return;
466 }
467
468 punittype = utype_by_number(utid);
469 if (!punittype) {
470 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
471 /* TRANS: ..." at <tile-coordinates> because"... */
472 _("Cannot create a unit at %s because the "
473 "given unit type id %d is invalid."),
474 tile_link(ptile), utid);
475 return;
476 }
477
478 pplayer = player_by_number(owner);
479 if (!pplayer) {
480 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
481 /* TRANS: ..." type <unit-type> at <tile-coordinates>"... */
482 _("Cannot create a unit of type %s at %s "
483 "because the given owner's player id %d is "
484 "invalid."), utype_name_translation(punittype),
485 tile_link(ptile), owner);
486 return;
487 }
488
489 if (is_non_allied_unit_tile(ptile, pplayer)
490 || (tile_city(ptile)
491 && !pplayers_allied(pplayer, city_owner(tile_city(ptile))))) {
492 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
493 /* TRANS: ..." type <unit-type> on enemy tile
494 * <tile-coordinates>"... */
495 _("Cannot create unit of type %s on enemy tile "
496 "%s."), utype_name_translation(punittype),
497 tile_link(ptile));
498 return;
499 }
500
501 if (!can_exist_at_tile(punittype, ptile)) {
502 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
503 /* TRANS: ..." type <unit-type> on the terrain at
504 * <tile-coordinates>"... */
505 _("Cannot create a unit of type %s on the terrain "
506 "at %s."),
507 utype_name_translation(punittype), tile_link(ptile));
508 return;
509 }
510
511 if (count > 0 && !pplayer->is_alive) {
512 pplayer->is_alive = TRUE;
513 send_player_info_c(pplayer, NULL);
514 }
515
516 homecity = find_closest_city(ptile, NULL, pplayer, FALSE, FALSE, FALSE,
517 TRUE, FALSE, utype_class(punittype));
518 id = homecity ? homecity->id : 0;
519
520 conn_list_do_buffer(game.est_connections);
521 map_show_circle(pplayer, ptile, punittype->vision_radius_sq);
522 for (i = 0; i < count; i++) {
523 /* As far as I can see create_unit is guaranteed to
524 * never return NULL. */
525 punit = create_unit(pplayer, ptile, punittype, 0, id, -1);
526 if (tag > 0) {
527 dsend_packet_edit_object_created(pc, tag, punit->id);
528 }
529 }
530 conn_list_do_unbuffer(game.est_connections);
531 }
532
533 /****************************************************************************
534 Remove 'count' units of type 'utid' owned by player number 'owner' at
535 tile (x, y).
536 ****************************************************************************/
handle_edit_unit_remove(struct connection * pc,int owner,int tile,Unit_type_id utid,int count)537 void handle_edit_unit_remove(struct connection *pc, int owner,
538 int tile, Unit_type_id utid, int count)
539 {
540 struct tile *ptile;
541 struct unit_type *punittype;
542 struct player *pplayer;
543 int i;
544
545 ptile = index_to_tile(tile);
546 if (!ptile) {
547 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
548 _("Cannot remove units because %d is not a valid "
549 "tile index on this map!"), tile);
550 return;
551 }
552
553 punittype = utype_by_number(utid);
554 if (!punittype) {
555 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
556 /* TRANS: ..." at <tile-coordinates> because"... */
557 _("Cannot remove a unit at %s because the "
558 "given unit type id %d is invalid."),
559 tile_link(ptile), utid);
560 return;
561 }
562
563 pplayer = player_by_number(owner);
564 if (!pplayer) {
565 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
566 /* TRANS: ..." type <unit-type> at <tile-coordinates>
567 * because"... */
568 _("Cannot remove a unit of type %s at %s "
569 "because the given owner's player id %d is "
570 "invalid."), utype_name_translation(punittype),
571 tile_link(ptile), owner);
572 return;
573 }
574
575 i = 0;
576 unit_list_iterate_safe(ptile->units, punit) {
577 if (i >= count) {
578 break;
579 }
580 if (unit_type_get(punit) != punittype
581 || unit_owner(punit) != pplayer) {
582 continue;
583 }
584 wipe_unit(punit, ULR_EDITOR, NULL);
585 i++;
586 } unit_list_iterate_safe_end;
587 }
588
589 /****************************************************************************
590 Handle a request to remove a unit given by its id.
591 ****************************************************************************/
handle_edit_unit_remove_by_id(struct connection * pc,Unit_type_id id)592 void handle_edit_unit_remove_by_id(struct connection *pc, Unit_type_id id)
593 {
594 struct unit *punit;
595
596 punit = game_unit_by_number(id);
597 if (!punit) {
598 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
599 _("No such unit (ID %d)."), id);
600 return;
601 }
602
603 wipe_unit(punit, ULR_EDITOR, NULL);
604 }
605
606 /****************************************************************************
607 Handles unit information from the client, to make edits to units.
608 ****************************************************************************/
handle_edit_unit(struct connection * pc,const struct packet_edit_unit * packet)609 void handle_edit_unit(struct connection *pc,
610 const struct packet_edit_unit *packet)
611 {
612 struct unit_type *putype;
613 struct unit *punit;
614 int id;
615 bool changed = FALSE;
616 int fuel, hp;
617
618 id = packet->id;
619 punit = game_unit_by_number(id);
620 if (!punit) {
621 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
622 _("No such unit (ID %d)."), id);
623 return;
624 }
625
626 putype = unit_type_get(punit);
627
628 if (packet->moves_left != punit->moves_left) {
629 punit->moves_left = packet->moves_left;
630 changed = TRUE;
631 }
632
633 fuel = CLIP(0, packet->fuel, utype_fuel(putype));
634 if (fuel != punit->fuel) {
635 punit->fuel = fuel;
636 changed = TRUE;
637 }
638
639 if (packet->moved != punit->moved) {
640 punit->moved = packet->moved;
641 changed = TRUE;
642 }
643
644 if (packet->done_moving != punit->done_moving) {
645 punit->done_moving = packet->done_moving;
646 changed = TRUE;
647 }
648
649 hp = CLIP(1, packet->hp, putype->hp);
650 if (hp != punit->hp) {
651 punit->hp = hp;
652 changed = TRUE;
653 }
654
655 if (packet->veteran != punit->veteran) {
656 int v = packet->veteran;
657 if (!utype_veteran_level(putype, v)) {
658 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
659 _("Invalid veteran level %d for unit %d (%s)."),
660 v, id, unit_link(punit));
661 } else {
662 punit->veteran = v;
663 changed = TRUE;
664 }
665 }
666
667 /* TODO: Handle more property edits. */
668
669
670 /* Send the new state to all affected. */
671 if (changed) {
672 send_unit_info(NULL, punit);
673 }
674 }
675
676 /****************************************************************************
677 Allows the editing client to create a city at the given position and
678 of size 'size'.
679 ****************************************************************************/
handle_edit_city_create(struct connection * pc,int owner,int tile,int size,int tag)680 void handle_edit_city_create(struct connection *pc, int owner, int tile,
681 int size, int tag)
682 {
683 struct tile *ptile;
684 struct city *pcity;
685 struct player *pplayer;
686
687 ptile = index_to_tile(tile);
688 if (!ptile) {
689 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
690 _("Cannot create a city because %d is not a valid "
691 "tile index on this map!"), tile);
692 return;
693 }
694
695 pplayer = player_by_number(owner);
696 if (!pplayer) {
697 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
698 /* TRANS: ..." at <tile-coordinates> because"... */
699 _("Cannot create a city at %s because the "
700 "given owner's player id %d is invalid"),
701 tile_link(ptile), owner);
702 return;
703
704 }
705
706
707 if (is_enemy_unit_tile(ptile, pplayer) != NULL
708 || !city_can_be_built_here(ptile, NULL)) {
709 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
710 /* TRANS: ..." at <tile-coordinates>." */
711 _("A city may not be built at %s."), tile_link(ptile));
712 return;
713 }
714
715 if (!pplayer->is_alive) {
716 pplayer->is_alive = TRUE;
717 send_player_info_c(pplayer, NULL);
718 }
719
720 conn_list_do_buffer(game.est_connections);
721
722 map_show_tile(pplayer, ptile);
723 create_city(pplayer, ptile, city_name_suggestion(pplayer, ptile),
724 pplayer);
725 pcity = tile_city(ptile);
726
727 if (size > 1) {
728 /* FIXME: Slow and inefficient for large size changes. */
729 city_change_size(pcity, CLIP(1, size, MAX_CITY_SIZE), pplayer, NULL);
730 send_city_info(NULL, pcity);
731 }
732
733 if (tag > 0) {
734 dsend_packet_edit_object_created(pc, tag, pcity->id);
735 }
736
737 conn_list_do_unbuffer(game.est_connections);
738 }
739
740
741 /****************************************************************************
742 Handle a request to change the internal state of a city.
743 ****************************************************************************/
handle_edit_city(struct connection * pc,const struct packet_edit_city * packet)744 void handle_edit_city(struct connection *pc,
745 const struct packet_edit_city *packet)
746 {
747 struct tile *ptile;
748 struct city *pcity, *oldcity;
749 struct player *pplayer;
750 char buf[1024];
751 int id;
752 bool changed = FALSE;
753 bool need_game_info = FALSE;
754 bv_player need_player_info;
755
756 pcity = game_city_by_number(packet->id);
757 if (!pcity) {
758 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
759 _("Cannot edit city with invalid city ID %d."),
760 packet->id);
761 return;
762 }
763
764 pplayer = city_owner(pcity);
765 ptile = city_tile(pcity);
766 BV_CLR_ALL(need_player_info);
767
768 /* Handle name change. */
769 if (0 != strcmp(pcity->name, packet->name)) {
770 if (!is_allowed_city_name(pplayer, packet->name, buf, sizeof(buf))) {
771 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
772 _("Cannot edit city name: %s"), buf);
773 } else {
774 sz_strlcpy(pcity->name, packet->name);
775 changed = TRUE;
776 }
777 }
778
779 /* Handle size change. */
780 if (packet->size != city_size_get(pcity)) {
781 if (!(0 < packet->size && packet->size <= MAX_CITY_SIZE)) {
782 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
783 _("Invalid city size %d for city %s."),
784 packet->size, city_link(pcity));
785 } else {
786 /* FIXME: Slow and inefficient for large size changes. */
787 city_change_size(pcity, packet->size, NULL, NULL);
788 changed = TRUE;
789 }
790 }
791
792 if (packet->history != pcity->history) {
793 pcity->history = packet->history;
794 changed = TRUE;
795 }
796
797 /* Handle city improvement changes. */
798 improvement_iterate(pimprove) {
799 oldcity = NULL;
800 id = improvement_number(pimprove);
801
802 if (is_special_improvement(pimprove)) {
803 if (packet->built[id] >= 0) {
804 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
805 _("It is impossible for a city to have %s!"),
806 improvement_name_translation(pimprove));
807 }
808 continue;
809 }
810
811 /* FIXME: game.info.great_wonder_owners and pplayer->wonders
812 * logic duplication with city_build_building. */
813
814 if (city_has_building(pcity, pimprove) && packet->built[id] < 0) {
815
816 city_remove_improvement(pcity, pimprove);
817 changed = TRUE;
818
819 } else if (!city_has_building(pcity, pimprove)
820 && packet->built[id] >= 0) {
821
822 if (is_great_wonder(pimprove)) {
823 oldcity = city_from_great_wonder(pimprove);
824 if (oldcity != pcity) {
825 BV_SET(need_player_info, player_index(pplayer));
826 }
827 if (NULL != oldcity && city_owner(oldcity) != pplayer) {
828 /* Great wonders make more changes. */
829 need_game_info = TRUE;
830 BV_SET(need_player_info, player_index(city_owner(oldcity)));
831 }
832 } else if (is_small_wonder(pimprove)) {
833 oldcity = city_from_small_wonder(pplayer, pimprove);
834 if (oldcity != pcity) {
835 BV_SET(need_player_info, player_index(pplayer));
836 }
837 }
838
839 if (oldcity) {
840 city_remove_improvement(oldcity, pimprove);
841 city_refresh_queue_add(oldcity);
842 }
843
844 city_add_improvement(pcity, pimprove);
845 changed = TRUE;
846 }
847 } improvement_iterate_end;
848
849 /* Handle food stock change. */
850 if (packet->food_stock != pcity->food_stock) {
851 int max = city_granary_size(city_size_get(pcity));
852 if (!(0 <= packet->food_stock && packet->food_stock <= max)) {
853 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
854 _("Invalid city food stock amount %d for city %s "
855 "(allowed range is %d to %d)."),
856 packet->food_stock, city_link(pcity), 0, max);
857 } else {
858 pcity->food_stock = packet->food_stock;
859 changed = TRUE;
860 }
861 }
862
863 /* Handle shield stock change. */
864 if (packet->shield_stock != pcity->shield_stock) {
865 int max = USHRT_MAX; /* Limited to uint16 by city info packet. */
866
867 if (!(0 <= packet->shield_stock && packet->shield_stock <= max)) {
868 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
869 _("Invalid city shield stock amount %d for city %s "
870 "(allowed range is %d to %d)."),
871 packet->shield_stock, city_link(pcity), 0, max);
872 } else {
873 pcity->shield_stock = packet->shield_stock;
874 /* Make sure the shields stay if changing production back and forth */
875 pcity->before_change_shields = packet->shield_stock;
876 changed = TRUE;
877 }
878 }
879
880 /* TODO: Handle more property edits. */
881
882
883 if (changed) {
884 city_refresh_queue_add(pcity);
885 conn_list_do_buffer(game.est_connections);
886 city_refresh_queue_processing();
887
888 /* FIXME: city_refresh_queue_processing only sends to city owner? */
889 send_city_info(NULL, pcity);
890
891 conn_list_do_unbuffer(game.est_connections);
892 }
893
894 /* Update wonder infos. */
895 if (need_game_info) {
896 send_game_info(NULL);
897 }
898 if (BV_ISSET_ANY(need_player_info)) {
899 players_iterate(aplayer) {
900 if (BV_ISSET(need_player_info, player_index(aplayer))) {
901 /* No need to send to detached connections. */
902 send_player_info_c(aplayer, NULL);
903 }
904 } players_iterate_end;
905 }
906 }
907
908 /****************************************************************************
909 Handle a request to create a new player.
910 ****************************************************************************/
handle_edit_player_create(struct connection * pc,int tag)911 void handle_edit_player_create(struct connection *pc, int tag)
912 {
913 struct player *pplayer;
914 struct nation_type *pnation;
915 struct research *presearch;
916
917 if (player_count() >= player_slot_count()) {
918 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
919 _("No more players can be added because the maximum "
920 "number of players (%d) has been reached."),
921 player_slot_count());
922 return;
923 }
924
925 if (player_count() >= nation_count() ) {
926 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
927 _("No more players can be added because there are "
928 "no available nations (%d used)."),
929 nation_count());
930 return;
931 }
932
933 pnation = pick_a_nation(NULL, TRUE, TRUE, NOT_A_BARBARIAN);
934 if (!pnation) {
935 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
936 _("Player cannot be created because random nation "
937 "selection failed."));
938 return;
939 }
940
941 pplayer = server_create_player(-1, default_ai_type_name(),
942 NULL, FALSE);
943 if (!pplayer) {
944 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
945 _("Player creation failed."));
946 return;
947 }
948 server_player_init(pplayer, TRUE, TRUE);
949
950 player_nation_defaults(pplayer, pnation, TRUE);
951 if (game_was_started()) {
952 /* Find a color for the new player. */
953 assign_player_colors();
954 }
955 sz_strlcpy(pplayer->username, _(ANON_USER_NAME));
956 pplayer->unassigned_user = TRUE;
957 pplayer->is_connected = FALSE;
958 pplayer->government = init_government_of_nation(pnation);
959 pplayer->server.got_first_city = FALSE;
960
961 pplayer->economic.gold = 0;
962 pplayer->economic = player_limit_to_max_rates(pplayer);
963
964 presearch = research_get(pplayer);
965 init_tech(presearch, TRUE);
966 give_initial_techs(presearch, 0);
967
968 send_player_all_c(pplayer, NULL);
969 /* Send research info after player info, else the client will complain
970 * about invalid team. */
971 send_research_info(presearch, NULL);
972 if (tag > 0) {
973 dsend_packet_edit_object_created(pc, tag, player_number(pplayer));
974 }
975 }
976
977 /****************************************************************************
978 Handle a request to remove a player.
979 ****************************************************************************/
handle_edit_player_remove(struct connection * pc,int id)980 void handle_edit_player_remove(struct connection *pc, int id)
981 {
982 struct player *pplayer;
983
984 pplayer = player_by_number(id);
985 if (pplayer == NULL) {
986 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
987 _("No such player (ID %d)."), id);
988 return;
989 }
990
991 /* Don't use conn_list_iterate here because connection_detach() can be
992 * recursive and free the next connection pointer. */
993 while (conn_list_size(pplayer->connections) > 0) {
994 connection_detach(conn_list_get(pplayer->connections, 0), FALSE);
995 }
996
997 kill_player(pplayer);
998 server_remove_player(pplayer);
999 }
1000
1001 /**************************************************************************
1002 Handle editing of any or all player properties.
1003 ***************************************************************************/
handle_edit_player(struct connection * pc,const struct packet_edit_player * packet)1004 void handle_edit_player(struct connection *pc,
1005 const struct packet_edit_player *packet)
1006 {
1007 struct player *pplayer;
1008 bool changed = FALSE, update_research = FALSE;
1009 struct nation_type *pnation;
1010 struct research *research;
1011 enum tech_state known;
1012 struct government *gov;
1013
1014 pplayer = player_by_number(packet->id);
1015 if (!pplayer) {
1016 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1017 _("Cannot edit player with invalid player ID %d."),
1018 packet->id);
1019 return;
1020 }
1021
1022 research = research_get(pplayer);
1023
1024
1025 /* Handle player name change. */
1026 if (0 != strcmp(packet->name, player_name(pplayer))) {
1027 char error_buf[256];
1028
1029 if (server_player_set_name_full(pc, pplayer, NULL, packet->name,
1030 error_buf, sizeof(error_buf))) {
1031 changed = TRUE;
1032 } else {
1033 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1034 _("Cannot change name of player (%d) '%s' to '%s': %s"),
1035 player_number(pplayer), player_name(pplayer),
1036 packet->name, error_buf);
1037 }
1038 }
1039
1040 /* Handle nation change. */
1041 pnation = nation_by_number(packet->nation);
1042 if (nation_of_player(pplayer) != pnation) {
1043 if (pnation == NULL) {
1044 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1045 _("Cannot change nation for player %d (%s) "
1046 "because the given nation ID %d is invalid."),
1047 player_number(pplayer), player_name(pplayer),
1048 packet->nation);
1049 } else if (pnation->player != NULL) {
1050 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1051 _("Cannot change nation for player %d (%s) "
1052 "to nation %d (%s) because that nation is "
1053 "already assigned to player %d (%s)."),
1054 player_number(pplayer), player_name(pplayer),
1055 packet->nation, nation_plural_translation(pnation),
1056 player_number(pnation->player),
1057 player_name(pnation->player));
1058 } else if (!nation_is_in_current_set(pnation)) {
1059 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1060 _("Cannot change nation for player %d (%s) "
1061 "to nation %d (%s) because that nation is "
1062 "not in the current nation set."),
1063 player_number(pplayer), player_name(pplayer),
1064 packet->nation, nation_plural_translation(pnation));
1065 } else if (pplayer->ai_common.barbarian_type
1066 != nation_barbarian_type(pnation)
1067 || (!is_barbarian(pplayer) && !is_nation_playable(pnation))) {
1068 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1069 _("Cannot change nation for player %d (%s) "
1070 "to nation %d (%s) because that nation is "
1071 "unsuitable for this player."),
1072 player_number(pplayer), player_name(pplayer),
1073 packet->nation, nation_plural_translation(pnation));
1074 } else {
1075 changed = player_set_nation(pplayer, pnation);
1076 }
1077 }
1078
1079 /* Handle a change in research progress. */
1080 if (packet->bulbs_researched != research->bulbs_researched) {
1081 research->bulbs_researched = packet->bulbs_researched;
1082 changed = TRUE;
1083 update_research = TRUE;
1084 }
1085
1086 /* Handle a change in known inventions. */
1087 advance_index_iterate(A_FIRST, tech) {
1088 known = research_invention_state(research, tech);
1089 if ((packet->inventions[tech] && known == TECH_KNOWN)
1090 || (!packet->inventions[tech] && known != TECH_KNOWN)) {
1091 continue;
1092 }
1093 if (packet->inventions[tech]) {
1094 /* FIXME: Side-effect modifies game.info.global_advances. */
1095 research_invention_set(research, tech, TECH_KNOWN);
1096 research->techs_researched++;
1097 } else {
1098 research_invention_set(research, tech, TECH_UNKNOWN);
1099 research->techs_researched--;
1100 }
1101 changed = TRUE;
1102 update_research = TRUE;
1103 } advance_index_iterate_end;
1104
1105 /* Handle a change in the player's gold. */
1106 if (packet->gold != pplayer->economic.gold) {
1107 if (!(0 <= packet->gold && packet->gold <= 1000000)) {
1108 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1109 _("Cannot set gold for player %d (%s) because "
1110 "the value %d is outside the allowed range."),
1111 player_number(pplayer), player_name(pplayer),
1112 packet->gold);
1113 } else {
1114 pplayer->economic.gold = packet->gold;
1115 changed = TRUE;
1116 }
1117 }
1118
1119 /* Handle player government change */
1120 gov = government_by_number(packet->government);
1121 if (gov != pplayer->government) {
1122 if (gov != game.government_during_revolution) {
1123 government_change(pplayer, gov, FALSE);
1124 } else {
1125 int turns = revolution_length(gov, pplayer);
1126
1127 if (turns >= 0) {
1128 pplayer->government = gov;
1129 pplayer->revolution_finishes = game.info.turn + turns;
1130 }
1131 }
1132
1133 changed = TRUE;
1134 }
1135
1136 /* TODO: Handle more property edits. */
1137
1138 if (update_research) {
1139 Tech_type_id current, goal;
1140
1141 research_update(research);
1142
1143 /* FIXME: Modifies struct research directly. */
1144
1145 current = research->researching;
1146 goal = research->tech_goal;
1147
1148 if (current != A_UNSET) {
1149 if (current != A_FUTURE) {
1150 known = research_invention_state(research, current);
1151 if (known != TECH_PREREQS_KNOWN) {
1152 research->researching = A_UNSET;
1153 }
1154 } else {
1155 /* Future Tech is legal only if all techs are known */
1156 advance_index_iterate(A_FIRST, tech_i) {
1157 known = research_invention_state(research, tech_i);
1158 if (known != TECH_KNOWN) {
1159 research->researching = A_UNSET;
1160 break;
1161 }
1162 } advance_index_iterate_end;
1163 }
1164 }
1165 if (goal != A_UNSET) {
1166 if (goal != A_FUTURE) {
1167 known = research_invention_state(research, goal);
1168 if (known == TECH_KNOWN) {
1169 research->tech_goal = A_UNSET;
1170 }
1171 }
1172 }
1173 changed = TRUE;
1174
1175 /* Inform everybody about global advances */
1176 send_game_info(NULL);
1177 send_research_info(research, NULL);
1178 }
1179
1180 if (changed) {
1181 send_player_all_c(pplayer, NULL);
1182 }
1183 }
1184
1185 /****************************************************************************
1186 Handles vision editing requests from client.
1187 ****************************************************************************/
handle_edit_player_vision(struct connection * pc,int plr_no,int tile,bool known,int size)1188 void handle_edit_player_vision(struct connection *pc, int plr_no,
1189 int tile, bool known, int size)
1190 {
1191 struct player *pplayer;
1192 struct tile *ptile_center;
1193
1194 ptile_center = index_to_tile(tile);
1195 if (!ptile_center) {
1196 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1197 _("Cannot edit vision because %d is not a valid "
1198 "tile index on this map!"), tile);
1199 return;
1200 }
1201
1202 pplayer = player_by_number(plr_no);
1203 if (!pplayer) {
1204 notify_conn(pc->self, ptile_center, E_BAD_COMMAND, ftc_editor,
1205 /* TRANS: ..." at <tile-coordinates> because"... */
1206 _("Cannot edit vision for the tile at %s because "
1207 "given player id %d is invalid."),
1208 tile_link(ptile_center), plr_no);
1209 return;
1210 }
1211
1212 conn_list_do_buffer(game.est_connections);
1213 square_iterate(ptile_center, size - 1, ptile) {
1214
1215 if (!known) {
1216 struct city *pcity = tile_city(ptile);
1217 bool cannot_make_unknown = FALSE;
1218
1219 if (pcity && city_owner(pcity) == pplayer) {
1220 continue;
1221 }
1222
1223 unit_list_iterate(ptile->units, punit) {
1224 if (unit_owner(punit) == pplayer
1225 || really_gives_vision(pplayer, unit_owner(punit))) {
1226 cannot_make_unknown = TRUE;
1227 break;
1228 }
1229 } unit_list_iterate_end;
1230
1231 if (cannot_make_unknown) {
1232 continue;
1233 }
1234
1235 /* The client expects tiles which become unseen to
1236 * contain no units (client/packhand.c +2368).
1237 * So here we tell it to remove units that do
1238 * not give it vision. */
1239 unit_list_iterate(ptile->units, punit) {
1240 conn_list_iterate(pplayer->connections, pconn) {
1241 dsend_packet_unit_remove(pconn, punit->id);
1242 } conn_list_iterate_end;
1243 } unit_list_iterate_end;
1244 }
1245
1246 if (known) {
1247 map_show_tile(pplayer, ptile);
1248 } else {
1249 map_hide_tile(pplayer, ptile);
1250 }
1251 } square_iterate_end;
1252 conn_list_do_unbuffer(game.est_connections);
1253 }
1254
1255 /****************************************************************************
1256 Client editor requests us to recalculate borders. Note that this does
1257 not necessarily extend borders to their maximum due to the way the
1258 borders code is written. This may be considered a feature or limitation.
1259 ****************************************************************************/
handle_edit_recalculate_borders(struct connection * pc)1260 void handle_edit_recalculate_borders(struct connection *pc)
1261 {
1262 map_calculate_borders();
1263 }
1264
1265 /****************************************************************************
1266 Remove any city at the given location.
1267 ****************************************************************************/
handle_edit_city_remove(struct connection * pc,int id)1268 void handle_edit_city_remove(struct connection *pc, int id)
1269 {
1270 struct city *pcity;
1271
1272 pcity = game_city_by_number(id);
1273 if (pcity == NULL) {
1274 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1275 _("No such city (ID %d)."), id);
1276 return;
1277 }
1278
1279 remove_city(pcity);
1280 }
1281
1282 /****************************************************************************
1283 Run any pending tile checks.
1284 ****************************************************************************/
handle_edit_check_tiles(struct connection * pc)1285 void handle_edit_check_tiles(struct connection *pc)
1286 {
1287 check_edited_tile_terrains();
1288 }
1289
1290 /****************************************************************************
1291 Temporarily remove fog-of-war for the player with player number 'plr_no'.
1292 This will only stay in effect while the server is in edit mode and the
1293 connection is editing. Has no effect if fog-of-war is disabled globally.
1294 ****************************************************************************/
handle_edit_toggle_fogofwar(struct connection * pc,int plr_no)1295 void handle_edit_toggle_fogofwar(struct connection *pc, int plr_no)
1296 {
1297 struct player *pplayer;
1298
1299 if (!game.info.fogofwar) {
1300 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1301 _("Cannot toggle fog-of-war when it is already "
1302 "disabled."));
1303 return;
1304 }
1305
1306 pplayer = player_by_number(plr_no);
1307 if (!pplayer) {
1308 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1309 _("Cannot toggle fog-of-war for invalid player ID %d."),
1310 plr_no);
1311 return;
1312 }
1313
1314 conn_list_do_buffer(game.est_connections);
1315 if (unfogged_players[player_number(pplayer)]) {
1316 enable_fog_of_war_player(pplayer);
1317 unfogged_players[player_number(pplayer)] = FALSE;
1318 } else {
1319 disable_fog_of_war_player(pplayer);
1320 unfogged_players[player_number(pplayer)] = TRUE;
1321 }
1322 conn_list_do_unbuffer(game.est_connections);
1323 }
1324
1325 /****************************************************************************
1326 Create or remove a start position at a tile.
1327 ****************************************************************************/
handle_edit_startpos(struct connection * pconn,const struct packet_edit_startpos * packet)1328 void handle_edit_startpos(struct connection *pconn,
1329 const struct packet_edit_startpos *packet)
1330 {
1331 struct tile *ptile = index_to_tile(packet->id);
1332 bool changed;
1333
1334 /* Check. */
1335 if (NULL == ptile) {
1336 notify_conn(pconn->self, NULL, E_BAD_COMMAND, ftc_editor,
1337 _("Invalid tile index %d for start position."), packet->id);
1338 return;
1339 }
1340
1341 /* Handle. */
1342 if (packet->removal) {
1343 changed = map_startpos_remove(ptile);
1344 } else {
1345 if (NULL != map_startpos_get(ptile)) {
1346 changed = FALSE;
1347 } else {
1348 map_startpos_new(ptile);
1349 changed = TRUE;
1350 }
1351 }
1352
1353 /* Notify. */
1354 if (changed) {
1355 conn_list_iterate(game.est_connections, aconn) {
1356 if (can_conn_edit(aconn)) {
1357 send_packet_edit_startpos(aconn, packet);
1358 }
1359 } conn_list_iterate_end;
1360 }
1361 }
1362
1363 /****************************************************************************
1364 Setup which nations can start at a start position.
1365 ****************************************************************************/
handle_edit_startpos_full(struct connection * pconn,const struct packet_edit_startpos_full * packet)1366 void handle_edit_startpos_full(struct connection *pconn,
1367 const struct packet_edit_startpos_full *
1368 packet)
1369 {
1370 struct tile *ptile = index_to_tile(packet->id);
1371 struct startpos *psp;
1372
1373 /* Check. */
1374 if (NULL == ptile) {
1375 notify_conn(pconn->self, NULL, E_BAD_COMMAND, ftc_editor,
1376 _("Invalid tile index %d for start position."),
1377 packet->id);
1378 return;
1379 }
1380
1381 psp = map_startpos_get(ptile);
1382 if (NULL == psp) {
1383 notify_conn(pconn->self, ptile, E_BAD_COMMAND, ftc_editor,
1384 _("Cannot edit start position nations at (%d, %d) "
1385 "because there is no start position there."),
1386 TILE_XY(ptile));
1387 return;
1388 }
1389
1390 /* Handle. */
1391 if (startpos_unpack(psp, packet)) {
1392 /* Notify. */
1393 conn_list_iterate(game.est_connections, aconn) {
1394 if (can_conn_edit(aconn)) {
1395 send_packet_edit_startpos_full(aconn, packet);
1396 }
1397 } conn_list_iterate_end;
1398 }
1399 }
1400
1401 /****************************************************************************
1402 Handle edit requests to the main game data structure.
1403 ****************************************************************************/
handle_edit_game(struct connection * pc,const struct packet_edit_game * packet)1404 void handle_edit_game(struct connection *pc,
1405 const struct packet_edit_game *packet)
1406 {
1407 bool changed = FALSE;
1408 int year;
1409
1410 if (has_capability("year32", pc->capability)) {
1411 year = packet->year32;
1412 } else {
1413 year = packet->year16;
1414 }
1415 if (year != game.info.year32) {
1416 const int min_year = -30000, max_year = 30000;
1417
1418 if (!(min_year <= year && year <= max_year)) {
1419 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1420 _("Cannot set invalid game year %d. Valid year range "
1421 "is from %d to %d."),
1422 year, min_year, max_year);
1423 } else {
1424 game.info.year32 = year;
1425 game.info.year16 = year;
1426 changed = TRUE;
1427 }
1428 }
1429
1430 if (packet->scenario != game.scenario.is_scenario) {
1431 game.scenario.is_scenario = packet->scenario;
1432 changed = TRUE;
1433 }
1434
1435 if (0 != strncmp(packet->scenario_name, game.scenario.name, 256)) {
1436 sz_strlcpy(game.scenario.name, packet->scenario_name);
1437 changed = TRUE;
1438 }
1439
1440 FC_STATIC_ASSERT(sizeof(packet->scenario_authors) == sizeof(game.scenario.authors),
1441 scen_authors_field_size_mismatch);
1442
1443 if (0 != strncmp(packet->scenario_authors, game.scenario.authors,
1444 sizeof(game.scenario.authors))) {
1445 sz_strlcpy(game.scenario.authors, packet->scenario_authors);
1446 changed = TRUE;
1447 }
1448
1449 if (packet->scenario_random != game.scenario.save_random) {
1450 game.scenario.save_random = packet->scenario_random;
1451 changed = TRUE;
1452 }
1453
1454 if (packet->scenario_players != game.scenario.players) {
1455 game.scenario.players = packet->scenario_players;
1456 changed = TRUE;
1457 }
1458
1459 if (packet->startpos_nations != game.scenario.startpos_nations) {
1460 game.scenario.startpos_nations = packet->startpos_nations;
1461 changed = TRUE;
1462 }
1463
1464 if (packet->prevent_new_cities != game.scenario.prevent_new_cities) {
1465 game.scenario.prevent_new_cities = packet->prevent_new_cities;
1466 changed = TRUE;
1467 }
1468
1469 if (packet->lake_flooding != game.scenario.lake_flooding) {
1470 game.scenario.lake_flooding = packet->lake_flooding;
1471 changed = TRUE;
1472 }
1473
1474 if (changed) {
1475 send_scenario_info(NULL);
1476 send_game_info(NULL);
1477 }
1478 }
1479
1480 /****************************************************************************
1481 Handle edit requests to scenario description
1482 ****************************************************************************/
handle_edit_scenario_desc(struct connection * pc,const char * scenario_desc)1483 void handle_edit_scenario_desc(struct connection *pc, const char *scenario_desc)
1484 {
1485 if (0 != strncmp(scenario_desc, game.scenario_desc.description,
1486 MAX_LEN_PACKET)) {
1487 sz_strlcpy(game.scenario_desc.description, scenario_desc);
1488 send_scenario_description(NULL);
1489 }
1490 }
1491
1492 /****************************************************************************
1493 Make scenario file out of current game.
1494 ****************************************************************************/
handle_save_scenario(struct connection * pc,const char * name)1495 void handle_save_scenario(struct connection *pc, const char *name)
1496 {
1497 if (pc->access_level != ALLOW_HACK) {
1498 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1499 _("No permissions to remotely save scenario."));
1500 return;
1501 }
1502
1503 if (!game.scenario.is_scenario) {
1504 /* Scenario information not available */
1505 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1506 _("Scenario information not set. Cannot save scenario."));
1507 return;
1508 }
1509
1510 /* Client initiated scenario saving is not handmade */
1511 game.scenario.handmade = FALSE;
1512
1513 save_game(name, "Scenario", TRUE);
1514 }
1515