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