Replaced deprecated gtk_menu_popup() calls with modern constructs in gtk3.22-client
[freeciv.git] / server / edithand.c
blobc40a15e8c444691f90db39f2493fa56413f66f3c
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.
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 ***********************************************************************/
14 #ifdef HAVE_CONFIG_H
15 #include <fc_config.h>
16 #endif
18 #include <limits.h> /* USHRT_MAX */
20 /* utility */
21 #include "bitvector.h"
22 #include "fcintl.h"
23 #include "log.h"
24 #include "shared.h"
25 #include "support.h"
27 /* common */
28 #include "events.h"
29 #include "game.h"
30 #include "government.h"
31 #include "map.h"
32 #include "movement.h"
33 #include "nation.h"
34 #include "terrain.h"
35 #include "research.h"
36 #include "unitlist.h"
38 /* generator */
39 #include "utilities.h"
41 /* server */
42 #include "aiiface.h"
43 #include "citytools.h"
44 #include "cityturn.h"
45 #include "connecthand.h"
46 #include "gamehand.h"
47 #include "hand_gen.h"
48 #include "maphand.h"
49 #include "plrhand.h"
50 #include "notify.h"
51 #include "sanitycheck.h"
52 #include "srv_main.h"
53 #include "stdinhand.h"
54 #include "techtools.h"
55 #include "unittools.h"
57 #include "edithand.h"
59 /* Set if anything in a sequence of edits triggers the expensive
60 * assign_continent_numbers() check, which will be done once when the
61 * sequence is complete. */
62 static bool need_continents_reassigned = FALSE;
63 /* Hold pointers to tiles which were changed during the edit sequence,
64 * so that they can be sanity-checked when the sequence is complete
65 * and final global fix-ups have been done. */
66 static struct tile_hash *modified_tile_table = NULL;
68 /* Array of size player_slot_count() indexed by player
69 * number to tell whether a given player has fog of war
70 * disabled in edit mode. */
71 static bool *unfogged_players;
73 /****************************************************************************
74 Initialize data structures required for edit mode.
75 ****************************************************************************/
76 void edithand_init(void)
78 if (NULL != modified_tile_table) {
79 tile_hash_destroy(modified_tile_table);
81 modified_tile_table = tile_hash_new();
83 need_continents_reassigned = FALSE;
85 if (unfogged_players != NULL) {
86 free(unfogged_players);
88 unfogged_players = fc_calloc(player_slot_count(), sizeof(bool));
91 /****************************************************************************
92 Free all memory used by data structures required for edit mode.
93 ****************************************************************************/
94 void edithand_free(void)
96 if (NULL != modified_tile_table) {
97 tile_hash_destroy(modified_tile_table);
98 modified_tile_table = NULL;
101 if (unfogged_players != NULL) {
102 free(unfogged_players);
103 unfogged_players = NULL;
107 /****************************************************************************
108 Send the needed packets for connections entering in the editing mode.
109 ****************************************************************************/
110 void edithand_send_initial_packets(struct conn_list *dest)
112 struct packet_edit_startpos startpos;
113 struct packet_edit_startpos_full startpos_full;
115 if (NULL == dest) {
116 dest = game.est_connections;
119 /* Send map start positions. */
120 map_startpos_iterate(psp) {
121 startpos.id = tile_index(startpos_tile(psp));
122 startpos.removal = FALSE;
123 startpos.tag = 0;
125 startpos_pack(psp, &startpos_full);
127 conn_list_iterate(dest, pconn) {
128 if (can_conn_edit(pconn)) {
129 send_packet_edit_startpos(pconn, &startpos);
130 send_packet_edit_startpos_full(pconn, &startpos_full);
132 } conn_list_iterate_end;
133 } map_startpos_iterate_end;
136 /****************************************************************************
137 Do the potentially slow checks required after one or several tiles'
138 terrain has change.
139 ****************************************************************************/
140 static void check_edited_tile_terrains(void)
142 if (need_continents_reassigned) {
143 assign_continent_numbers();
144 send_all_known_tiles(NULL);
145 need_continents_reassigned = FALSE;
148 #ifdef SANITY_CHECKING
149 tile_hash_iterate(modified_tile_table, ptile) {
150 sanity_check_tile(ptile);
151 } tile_hash_iterate_end;
152 #endif /* SANITY_CHECKING */
153 tile_hash_clear(modified_tile_table);
156 /****************************************************************************
157 Do any necessary checks after leaving edit mode to ensure that the game
158 is in a consistent state.
159 ****************************************************************************/
160 static void check_leaving_edit_mode(void)
162 bool unfogged;
164 conn_list_do_buffer(game.est_connections);
165 players_iterate(pplayer) {
166 unfogged = unfogged_players[player_number(pplayer)];
167 if (unfogged && game.info.fogofwar) {
168 enable_fog_of_war_player(pplayer);
169 } else if (!unfogged && !game.info.fogofwar) {
170 disable_fog_of_war_player(pplayer);
172 } players_iterate_end;
174 /* Clear the whole array. */
175 memset(unfogged_players, 0, player_slot_count() * sizeof(bool));
177 check_edited_tile_terrains();
178 conn_list_do_unbuffer(game.est_connections);
181 /****************************************************************************
182 Handles a request by the client to enter edit mode.
183 ****************************************************************************/
184 void handle_edit_mode(struct connection *pc, bool is_edit_mode)
186 if (!can_conn_enable_editing(pc)) {
187 return;
190 if (!game.info.is_edit_mode && is_edit_mode) {
191 /* Someone could be cheating! Warn people. */
192 notify_conn(NULL, NULL, E_SETTING, ftc_editor,
193 _(" *** Server set to edit mode by %s! *** "),
194 conn_description(pc));
197 if (game.info.is_edit_mode && !is_edit_mode) {
198 notify_conn(NULL, NULL, E_SETTING, ftc_editor,
199 _(" *** Edit mode canceled by %s. *** "),
200 conn_description(pc));
202 check_leaving_edit_mode();
205 if (game.info.is_edit_mode != is_edit_mode) {
206 game.info.is_edit_mode = is_edit_mode;
208 send_game_info(NULL);
209 edithand_send_initial_packets(NULL);
213 /****************************************************************************
214 Base function to edit the terrain property of a tile. Returns TRUE if
215 the terrain has changed.
216 ****************************************************************************/
217 static bool edit_tile_terrain_handling(struct tile *ptile,
218 struct terrain *pterrain,
219 bool send_info)
221 struct terrain *old_terrain = tile_terrain(ptile);
223 if (old_terrain == pterrain
224 || (terrain_has_flag(pterrain, TER_NO_CITIES)
225 && NULL != tile_city(ptile))) {
226 return FALSE;
229 tile_change_terrain(ptile, pterrain);
230 fix_tile_on_terrain_change(ptile, old_terrain, FALSE);
231 tile_hash_insert(modified_tile_table, ptile, NULL);
232 if (need_to_reassign_continents(old_terrain, pterrain)) {
233 need_continents_reassigned = TRUE;
236 if (send_info) {
237 update_tile_knowledge(ptile);
240 return TRUE;
243 /****************************************************************************
244 Base function to edit the resource property of a tile. Returns TRUE if
245 the resource has changed.
246 ****************************************************************************/
247 static bool edit_tile_resource_handling(struct tile *ptile,
248 struct resource *presource,
249 bool send_info)
251 if (presource == tile_resource(ptile)) {
252 return FALSE;
255 if (NULL != presource
256 && !terrain_has_resource(tile_terrain(ptile), presource)) {
257 return FALSE;
260 tile_set_resource(ptile, presource);
262 if (send_info) {
263 update_tile_knowledge(ptile);
266 return TRUE;
269 /****************************************************************************
270 Base function to edit the extras property of a tile. Returns TRUE if
271 the extra state has changed.
272 ****************************************************************************/
273 static bool edit_tile_extra_handling(struct tile *ptile,
274 struct extra_type *pextra,
275 bool remove_mode, bool send_info)
277 if (remove_mode) {
278 if (!tile_has_extra(ptile, pextra)) {
279 return FALSE;
282 if (!tile_extra_rm_apply(ptile, pextra)) {
283 return FALSE;
286 terrain_changed(ptile);
288 } else {
289 if (tile_has_extra(ptile, pextra)) {
290 return FALSE;
293 if (!tile_extra_apply(ptile, pextra)) {
294 return FALSE;
298 if (send_info) {
299 update_tile_knowledge(ptile);
302 return TRUE;
305 /****************************************************************************
306 Handles a client request to change the terrain of the tile at the given
307 x, y coordinates. The 'size' parameter indicates that all tiles in a
308 square of "radius" 'size' should be affected. So size=1 corresponds to
309 the single tile case.
310 ****************************************************************************/
311 void handle_edit_tile_terrain(struct connection *pc, int tile,
312 Terrain_type_id terrain, int size)
314 struct terrain *pterrain;
315 struct tile *ptile_center;
317 ptile_center = index_to_tile(tile);
318 if (!ptile_center) {
319 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
320 _("Cannot edit the tile because %d is not a valid "
321 "tile index on this map!"), tile);
322 return;
325 pterrain = terrain_by_number(terrain);
326 if (!pterrain) {
327 notify_conn(pc->self, ptile_center, E_BAD_COMMAND, ftc_editor,
328 /* TRANS: ..." the tile <tile-coordinates> because"... */
329 _("Cannot modify terrain for the tile %s because "
330 "%d is not a valid terrain id."),
331 tile_link(ptile_center), terrain);
332 return;
335 conn_list_do_buffer(game.est_connections);
336 /* This iterates outward, which gives any units that can't survive on
337 * changed terrain the best chance of survival. */
338 square_iterate(ptile_center, size - 1, ptile) {
339 edit_tile_terrain_handling(ptile, pterrain, TRUE);
340 } square_iterate_end;
341 conn_list_do_unbuffer(game.est_connections);
344 /****************************************************************************
345 Handle a request to change one or more tiles' resources.
346 ****************************************************************************/
347 void handle_edit_tile_resource(struct connection *pc, int tile,
348 Resource_type_id resource, int size)
350 struct resource *presource;
351 struct tile *ptile_center;
353 ptile_center = index_to_tile(tile);
354 if (!ptile_center) {
355 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
356 _("Cannot edit the tile because %d is not a valid "
357 "tile index on this map!"), tile);
358 return;
360 presource = resource_by_number(resource); /* May be NULL. */
362 conn_list_do_buffer(game.est_connections);
363 square_iterate(ptile_center, size - 1, ptile) {
364 edit_tile_resource_handling(ptile, presource, TRUE);
365 } square_iterate_end;
366 conn_list_do_unbuffer(game.est_connections);
369 /****************************************************************************
370 Handle a request to change one or more tiles' extras. The 'remove'
371 argument controls whether to remove or add the given extra from the tile.
372 ****************************************************************************/
373 void handle_edit_tile_extra(struct connection *pc, int tile,
374 int id, bool removal, int size)
376 struct tile *ptile_center;
378 ptile_center = index_to_tile(tile);
379 if (!ptile_center) {
380 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
381 _("Cannot edit the tile because %d is not a valid "
382 "tile index on this map!"), tile);
383 return;
386 if (id < 0 || id >= game.control.num_extra_types) {
387 notify_conn(pc->self, ptile_center, E_BAD_COMMAND, ftc_editor,
388 /* TRANS: ..." the tile <tile-coordinates> because"... */
389 _("Cannot modify extras for the tile %s because "
390 "%d is not a valid extra id."),
391 tile_link(ptile_center), id);
392 return;
395 conn_list_do_buffer(game.est_connections);
396 square_iterate(ptile_center, size - 1, ptile) {
397 edit_tile_extra_handling(ptile, extra_by_number(id), removal, TRUE);
398 } square_iterate_end;
399 conn_list_do_unbuffer(game.est_connections);
402 /****************************************************************************
403 Handles tile information from the client, to make edits to tiles.
404 ****************************************************************************/
405 void handle_edit_tile(struct connection *pc,
406 const struct packet_edit_tile *packet)
408 struct tile *ptile;
409 bool changed = FALSE;
411 ptile = index_to_tile(packet->tile);
412 if (!ptile) {
413 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
414 _("Cannot edit the tile because %d is not a valid "
415 "tile index on this map!"), packet->tile);
416 return;
419 /* Handle changes in extras. */
420 if (!BV_ARE_EQUAL(packet->extras, ptile->extras)) {
421 extra_type_iterate(pextra) {
422 if (edit_tile_extra_handling(ptile, pextra,
423 !BV_ISSET(packet->extras, extra_number(pextra)),
424 FALSE)) {
425 changed = TRUE;
427 } extra_type_iterate_end;
430 /* Handle changes in label */
431 if (tile_set_label(ptile, packet->label)) {
432 changed = TRUE;
435 /* TODO: Handle more property edits. */
438 /* Send the new state to all affected. */
439 if (changed) {
440 update_tile_knowledge(ptile);
441 send_tile_info(NULL, ptile, FALSE);
445 /****************************************************************************
446 Handle a request to create 'count' units of type 'utid' at the tile given
447 by the x, y coordinates and owned by player with number 'owner'.
448 ****************************************************************************/
449 void handle_edit_unit_create(struct connection *pc, int owner, int tile,
450 Unit_type_id utid, int count, int tag)
452 struct tile *ptile;
453 struct unit_type *punittype;
454 struct player *pplayer;
455 struct city *homecity;
456 struct unit *punit;
457 int id, i;
459 ptile = index_to_tile(tile);
460 if (!ptile) {
461 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
462 _("Cannot create units because %d is not a valid "
463 "tile index on this map!"), tile);
464 return;
467 punittype = utype_by_number(utid);
468 if (!punittype) {
469 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
470 /* TRANS: ..." at <tile-coordinates> because"... */
471 _("Cannot create a unit at %s because the "
472 "given unit type id %d is invalid."),
473 tile_link(ptile), utid);
474 return;
477 pplayer = player_by_number(owner);
478 if (!pplayer) {
479 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
480 /* TRANS: ..." type <unit-type> at <tile-coordinates>"... */
481 _("Cannot create a unit of type %s at %s "
482 "because the given owner's player id %d is "
483 "invalid."), utype_name_translation(punittype),
484 tile_link(ptile), owner);
485 return;
488 if (is_non_allied_unit_tile(ptile, pplayer)
489 || (tile_city(ptile)
490 && !pplayers_allied(pplayer, city_owner(tile_city(ptile))))) {
491 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
492 /* TRANS: ..." type <unit-type> on enemy tile
493 * <tile-coordinates>"... */
494 _("Cannot create unit of type %s on enemy tile "
495 "%s."), utype_name_translation(punittype),
496 tile_link(ptile));
497 return;
500 if (!can_exist_at_tile(punittype, ptile)) {
501 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
502 /* TRANS: ..." type <unit-type> on the terrain at
503 * <tile-coordinates>"... */
504 _("Cannot create a unit of type %s on the terrain "
505 "at %s."),
506 utype_name_translation(punittype), tile_link(ptile));
507 return;
510 if (count > 0 && !pplayer->is_alive) {
511 pplayer->is_alive = TRUE;
512 send_player_info_c(pplayer, NULL);
515 homecity = find_closest_city(ptile, NULL, pplayer, FALSE, FALSE, FALSE,
516 TRUE, FALSE, utype_class(punittype));
517 id = homecity ? homecity->id : 0;
519 conn_list_do_buffer(game.est_connections);
520 map_show_circle(pplayer, ptile, punittype->vision_radius_sq);
521 for (i = 0; i < count; i++) {
522 /* As far as I can see create_unit is guaranteed to
523 * never return NULL. */
524 punit = create_unit(pplayer, ptile, punittype, 0, id, -1);
525 if (tag > 0) {
526 dsend_packet_edit_object_created(pc, tag, punit->id);
529 conn_list_do_unbuffer(game.est_connections);
532 /****************************************************************************
533 Remove 'count' units of type 'utid' owned by player number 'owner' at
534 tile (x, y).
535 ****************************************************************************/
536 void handle_edit_unit_remove(struct connection *pc, int owner,
537 int tile, Unit_type_id utid, int count)
539 struct tile *ptile;
540 struct unit_type *punittype;
541 struct player *pplayer;
542 int i;
544 ptile = index_to_tile(tile);
545 if (!ptile) {
546 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
547 _("Cannot remove units because %d is not a valid "
548 "tile index on this map!"), tile);
549 return;
552 punittype = utype_by_number(utid);
553 if (!punittype) {
554 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
555 /* TRANS: ..." at <tile-coordinates> because"... */
556 _("Cannot remove a unit at %s because the "
557 "given unit type id %d is invalid."),
558 tile_link(ptile), utid);
559 return;
562 pplayer = player_by_number(owner);
563 if (!pplayer) {
564 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
565 /* TRANS: ..." type <unit-type> at <tile-coordinates>
566 * because"... */
567 _("Cannot remove a unit of type %s at %s "
568 "because the given owner's player id %d is "
569 "invalid."), utype_name_translation(punittype),
570 tile_link(ptile), owner);
571 return;
574 i = 0;
575 unit_list_iterate_safe(ptile->units, punit) {
576 if (i >= count) {
577 break;
579 if (unit_type_get(punit) != punittype
580 || unit_owner(punit) != pplayer) {
581 continue;
583 wipe_unit(punit, ULR_EDITOR, NULL);
584 i++;
585 } unit_list_iterate_safe_end;
588 /****************************************************************************
589 Handle a request to remove a unit given by its id.
590 ****************************************************************************/
591 void handle_edit_unit_remove_by_id(struct connection *pc, Unit_type_id id)
593 struct unit *punit;
595 punit = game_unit_by_number(id);
596 if (!punit) {
597 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
598 _("No such unit (ID %d)."), id);
599 return;
602 wipe_unit(punit, ULR_EDITOR, NULL);
605 /****************************************************************************
606 Handles unit information from the client, to make edits to units.
607 ****************************************************************************/
608 void handle_edit_unit(struct connection *pc,
609 const struct packet_edit_unit *packet)
611 struct unit_type *putype;
612 struct unit *punit;
613 int id;
614 bool changed = FALSE;
615 int fuel, hp;
617 id = packet->id;
618 punit = game_unit_by_number(id);
619 if (!punit) {
620 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
621 _("No such unit (ID %d)."), id);
622 return;
625 putype = unit_type_get(punit);
627 if (packet->moves_left != punit->moves_left) {
628 punit->moves_left = packet->moves_left;
629 changed = TRUE;
632 fuel = CLIP(0, packet->fuel, utype_fuel(putype));
633 if (fuel != punit->fuel) {
634 punit->fuel = fuel;
635 changed = TRUE;
638 if (packet->moved != punit->moved) {
639 punit->moved = packet->moved;
640 changed = TRUE;
643 if (packet->done_moving != punit->done_moving) {
644 punit->done_moving = packet->done_moving;
645 changed = TRUE;
648 hp = CLIP(1, packet->hp, putype->hp);
649 if (hp != punit->hp) {
650 punit->hp = hp;
651 changed = TRUE;
654 if (packet->veteran != punit->veteran) {
655 int v = packet->veteran;
656 if (!utype_veteran_level(putype, v)) {
657 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
658 _("Invalid veteran level %d for unit %d (%s)."),
659 v, id, unit_link(punit));
660 } else {
661 punit->veteran = v;
662 changed = TRUE;
666 /* TODO: Handle more property edits. */
669 /* Send the new state to all affected. */
670 if (changed) {
671 send_unit_info(NULL, punit);
675 /****************************************************************************
676 Allows the editing client to create a city at the given position and
677 of size 'size'.
678 ****************************************************************************/
679 void handle_edit_city_create(struct connection *pc, int owner, int tile,
680 int size, int tag)
682 struct tile *ptile;
683 struct city *pcity;
684 struct player *pplayer;
686 ptile = index_to_tile(tile);
687 if (!ptile) {
688 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
689 _("Cannot create a city because %d is not a valid "
690 "tile index on this map!"), tile);
691 return;
694 pplayer = player_by_number(owner);
695 if (!pplayer) {
696 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
697 /* TRANS: ..." at <tile-coordinates> because"... */
698 _("Cannot create a city at %s because the "
699 "given owner's player id %d is invalid"),
700 tile_link(ptile), owner);
701 return;
706 if (is_enemy_unit_tile(ptile, pplayer) != NULL
707 || !city_can_be_built_here(ptile, NULL)) {
708 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
709 /* TRANS: ..." at <tile-coordinates>." */
710 _("A city may not be built at %s."), tile_link(ptile));
711 return;
714 if (!pplayer->is_alive) {
715 pplayer->is_alive = TRUE;
716 send_player_info_c(pplayer, NULL);
719 conn_list_do_buffer(game.est_connections);
721 map_show_tile(pplayer, ptile);
722 create_city(pplayer, ptile, city_name_suggestion(pplayer, ptile),
723 pplayer);
724 pcity = tile_city(ptile);
726 if (size > 1) {
727 /* FIXME: Slow and inefficient for large size changes. */
728 city_change_size(pcity, CLIP(1, size, MAX_CITY_SIZE), pplayer, NULL);
729 send_city_info(NULL, pcity);
732 if (tag > 0) {
733 dsend_packet_edit_object_created(pc, tag, pcity->id);
736 conn_list_do_unbuffer(game.est_connections);
740 /****************************************************************************
741 Handle a request to change the internal state of a city.
742 ****************************************************************************/
743 void handle_edit_city(struct connection *pc,
744 const struct packet_edit_city *packet)
746 struct tile *ptile;
747 struct city *pcity, *oldcity;
748 struct player *pplayer;
749 char buf[1024];
750 int id;
751 bool changed = FALSE;
752 bool need_game_info = FALSE;
753 bv_player need_player_info;
755 pcity = game_city_by_number(packet->id);
756 if (!pcity) {
757 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
758 _("Cannot edit city with invalid city ID %d."),
759 packet->id);
760 return;
763 pplayer = city_owner(pcity);
764 ptile = city_tile(pcity);
765 BV_CLR_ALL(need_player_info);
767 /* Handle name change. */
768 if (0 != strcmp(pcity->name, packet->name)) {
769 if (!is_allowed_city_name(pplayer, packet->name, buf, sizeof(buf))) {
770 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
771 _("Cannot edit city name: %s"), buf);
772 } else {
773 sz_strlcpy(pcity->name, packet->name);
774 changed = TRUE;
778 /* Handle size change. */
779 if (packet->size != city_size_get(pcity)) {
780 if (!(0 < packet->size && packet->size <= MAX_CITY_SIZE)) {
781 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
782 _("Invalid city size %d for city %s."),
783 packet->size, city_link(pcity));
784 } else {
785 /* FIXME: Slow and inefficient for large size changes. */
786 city_change_size(pcity, packet->size, NULL, NULL);
787 changed = TRUE;
791 if (packet->history != pcity->history) {
792 pcity->history = packet->history;
793 changed = TRUE;
796 /* Handle city improvement changes. */
797 improvement_iterate(pimprove) {
798 oldcity = NULL;
799 id = improvement_number(pimprove);
801 if (is_special_improvement(pimprove)) {
802 if (packet->built[id] >= 0) {
803 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
804 _("It is impossible for a city to have %s!"),
805 improvement_name_translation(pimprove));
807 continue;
810 /* FIXME: game.info.great_wonder_owners and pplayer->wonders
811 * logic duplication with city_build_building. */
813 if (city_has_building(pcity, pimprove) && packet->built[id] < 0) {
815 city_remove_improvement(pcity, pimprove);
816 changed = TRUE;
818 } else if (!city_has_building(pcity, pimprove)
819 && packet->built[id] >= 0) {
821 if (is_great_wonder(pimprove)) {
822 oldcity = city_from_great_wonder(pimprove);
823 if (oldcity != pcity) {
824 BV_SET(need_player_info, player_index(pplayer));
826 if (NULL != oldcity && city_owner(oldcity) != pplayer) {
827 /* Great wonders make more changes. */
828 need_game_info = TRUE;
829 BV_SET(need_player_info, player_index(city_owner(oldcity)));
831 } else if (is_small_wonder(pimprove)) {
832 oldcity = city_from_small_wonder(pplayer, pimprove);
833 if (oldcity != pcity) {
834 BV_SET(need_player_info, player_index(pplayer));
838 if (oldcity) {
839 city_remove_improvement(oldcity, pimprove);
840 city_refresh_queue_add(oldcity);
843 city_add_improvement(pcity, pimprove);
844 changed = TRUE;
846 } improvement_iterate_end;
848 /* Handle food stock change. */
849 if (packet->food_stock != pcity->food_stock) {
850 int max = city_granary_size(city_size_get(pcity));
851 if (!(0 <= packet->food_stock && packet->food_stock <= max)) {
852 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
853 _("Invalid city food stock amount %d for city %s "
854 "(allowed range is %d to %d)."),
855 packet->food_stock, city_link(pcity), 0, max);
856 } else {
857 pcity->food_stock = packet->food_stock;
858 changed = TRUE;
862 /* Handle shield stock change. */
863 if (packet->shield_stock != pcity->shield_stock) {
864 int max = USHRT_MAX; /* Limited to uint16 by city info packet. */
865 if (!(0 <= packet->shield_stock && packet->shield_stock <= max)) {
866 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
867 _("Invalid city shield stock amount %d for city %s "
868 "(allowed range is %d to %d)."),
869 packet->shield_stock, city_link(pcity), 0, max);
870 } else {
871 pcity->shield_stock = packet->shield_stock;
872 changed = TRUE;
876 /* TODO: Handle more property edits. */
879 if (changed) {
880 city_refresh_queue_add(pcity);
881 conn_list_do_buffer(game.est_connections);
882 city_refresh_queue_processing();
884 /* FIXME: city_refresh_queue_processing only sends to city owner? */
885 send_city_info(NULL, pcity);
887 conn_list_do_unbuffer(game.est_connections);
890 /* Update wonder infos. */
891 if (need_game_info) {
892 send_game_info(NULL);
894 if (BV_ISSET_ANY(need_player_info)) {
895 players_iterate(aplayer) {
896 if (BV_ISSET(need_player_info, player_index(aplayer))) {
897 /* No need to send to detached connections. */
898 send_player_info_c(aplayer, NULL);
900 } players_iterate_end;
904 /****************************************************************************
905 Handle a request to create a new player.
906 ****************************************************************************/
907 void handle_edit_player_create(struct connection *pc, int tag)
909 struct player *pplayer;
910 struct nation_type *pnation;
911 struct research *presearch;
913 if (player_count() >= player_slot_count()) {
914 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
915 _("No more players can be added because the maximum "
916 "number of players (%d) has been reached."),
917 player_slot_count());
918 return;
921 if (player_count() >= nation_count() ) {
922 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
923 _("No more players can be added because there are "
924 "no available nations (%d used)."),
925 nation_count());
926 return;
929 pnation = pick_a_nation(NULL, TRUE, TRUE, NOT_A_BARBARIAN);
930 if (!pnation) {
931 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
932 _("Player cannot be created because random nation "
933 "selection failed."));
934 return;
937 pplayer = server_create_player(-1, default_ai_type_name(),
938 NULL, FALSE);
939 if (!pplayer) {
940 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
941 _("Player creation failed."));
942 return;
944 server_player_init(pplayer, TRUE, TRUE);
946 player_nation_defaults(pplayer, pnation, TRUE);
947 if (game_was_started()) {
948 /* Find a color for the new player. */
949 assign_player_colors();
951 sz_strlcpy(pplayer->username, _(ANON_USER_NAME));
952 pplayer->unassigned_user = TRUE;
953 pplayer->is_connected = FALSE;
954 pplayer->government = init_government_of_nation(pnation);
955 pplayer->server.got_first_city = FALSE;
957 pplayer->economic.gold = 0;
958 pplayer->economic = player_limit_to_max_rates(pplayer);
960 presearch = research_get(pplayer);
961 init_tech(presearch, TRUE);
962 give_initial_techs(presearch, 0);
964 send_player_all_c(pplayer, NULL);
965 /* Send research info after player info, else the client will complain
966 * about invalid team. */
967 send_research_info(presearch, NULL);
968 if (tag > 0) {
969 dsend_packet_edit_object_created(pc, tag, player_number(pplayer));
973 /****************************************************************************
974 Handle a request to remove a player.
975 ****************************************************************************/
976 void handle_edit_player_remove(struct connection *pc, int id)
978 struct player *pplayer;
980 pplayer = player_by_number(id);
981 if (pplayer == NULL) {
982 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
983 _("No such player (ID %d)."), id);
984 return;
987 /* Don't use conn_list_iterate here because connection_detach() can be
988 * recursive and free the next connection pointer. */
989 while (conn_list_size(pplayer->connections) > 0) {
990 connection_detach(conn_list_get(pplayer->connections, 0), FALSE);
993 kill_player(pplayer);
994 server_remove_player(pplayer);
997 /**************************************************************************
998 Handle editing of any or all player properties.
999 ***************************************************************************/
1000 void handle_edit_player(struct connection *pc,
1001 const struct packet_edit_player *packet)
1003 struct player *pplayer;
1004 bool changed = FALSE, update_research = FALSE;
1005 struct nation_type *pnation;
1006 struct research *research;
1007 enum tech_state known;
1008 struct government *gov;
1010 pplayer = player_by_number(packet->id);
1011 if (!pplayer) {
1012 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1013 _("Cannot edit player with invalid player ID %d."),
1014 packet->id);
1015 return;
1018 research = research_get(pplayer);
1021 /* Handle player name change. */
1022 if (0 != strcmp(packet->name, player_name(pplayer))) {
1023 char error_buf[256];
1025 if (server_player_set_name_full(pc, pplayer, NULL, packet->name,
1026 error_buf, sizeof(error_buf))) {
1027 changed = TRUE;
1028 } else {
1029 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1030 _("Cannot change name of player (%d) '%s' to '%s': %s"),
1031 player_number(pplayer), player_name(pplayer),
1032 packet->name, error_buf);
1036 /* Handle nation change. */
1037 pnation = nation_by_number(packet->nation);
1038 if (nation_of_player(pplayer) != pnation) {
1039 if (pnation == NULL) {
1040 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1041 _("Cannot change nation for player %d (%s) "
1042 "because the given nation ID %d is invalid."),
1043 player_number(pplayer), player_name(pplayer),
1044 packet->nation);
1045 } else if (pnation->player != NULL) {
1046 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1047 _("Cannot change nation for player %d (%s) "
1048 "to nation %d (%s) because that nation is "
1049 "already assigned to player %d (%s)."),
1050 player_number(pplayer), player_name(pplayer),
1051 packet->nation, nation_plural_translation(pnation),
1052 player_number(pnation->player),
1053 player_name(pnation->player));
1054 } else if (!nation_is_in_current_set(pnation)) {
1055 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1056 _("Cannot change nation for player %d (%s) "
1057 "to nation %d (%s) because that nation is "
1058 "not in the current nation set."),
1059 player_number(pplayer), player_name(pplayer),
1060 packet->nation, nation_plural_translation(pnation));
1061 } else if (pplayer->ai_common.barbarian_type
1062 != nation_barbarian_type(pnation)
1063 || (!is_barbarian(pplayer) && !is_nation_playable(pnation))) {
1064 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1065 _("Cannot change nation for player %d (%s) "
1066 "to nation %d (%s) because that nation is "
1067 "unsuitable for this player."),
1068 player_number(pplayer), player_name(pplayer),
1069 packet->nation, nation_plural_translation(pnation));
1070 } else {
1071 changed = player_set_nation(pplayer, pnation);
1075 /* Handle a change in research progress. */
1076 if (packet->bulbs_researched != research->bulbs_researched) {
1077 research->bulbs_researched = packet->bulbs_researched;
1078 changed = TRUE;
1079 update_research = TRUE;
1082 /* Handle a change in known inventions. */
1083 advance_index_iterate(A_FIRST, tech) {
1084 known = research_invention_state(research, tech);
1085 if ((packet->inventions[tech] && known == TECH_KNOWN)
1086 || (!packet->inventions[tech] && known != TECH_KNOWN)) {
1087 continue;
1089 if (packet->inventions[tech]) {
1090 /* FIXME: Side-effect modifies game.info.global_advances. */
1091 research_invention_set(research, tech, TECH_KNOWN);
1092 research->techs_researched++;
1093 } else {
1094 research_invention_set(research, tech, TECH_UNKNOWN);
1095 research->techs_researched--;
1097 changed = TRUE;
1098 update_research = TRUE;
1099 } advance_index_iterate_end;
1101 /* Handle a change in the player's gold. */
1102 if (packet->gold != pplayer->economic.gold) {
1103 if (!(0 <= packet->gold && packet->gold <= 1000000)) {
1104 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1105 _("Cannot set gold for player %d (%s) because "
1106 "the value %d is outside the allowed range."),
1107 player_number(pplayer), player_name(pplayer),
1108 packet->gold);
1109 } else {
1110 pplayer->economic.gold = packet->gold;
1111 changed = TRUE;
1115 /* Handle player government change */
1116 gov = government_by_number(packet->government);
1117 if (gov != pplayer->government) {
1118 if (gov != game.government_during_revolution) {
1119 government_change(pplayer, gov, FALSE);
1120 } else {
1121 int turns = revolution_length(gov, pplayer);
1123 if (turns >= 0) {
1124 pplayer->government = gov;
1125 pplayer->revolution_finishes = game.info.turn + turns;
1129 changed = TRUE;
1132 /* TODO: Handle more property edits. */
1134 if (update_research) {
1135 Tech_type_id current, goal;
1137 research_update(research);
1139 /* FIXME: Modifies struct research directly. */
1141 current = research->researching;
1142 goal = research->tech_goal;
1144 if (current != A_UNSET) {
1145 known = research_invention_state(research, current);
1146 if (known != TECH_PREREQS_KNOWN) {
1147 research->researching = A_UNSET;
1150 if (goal != A_UNSET) {
1151 known = research_invention_state(research, goal);
1152 if (known == TECH_KNOWN) {
1153 research->tech_goal = A_UNSET;
1156 changed = TRUE;
1158 /* Inform everybody about global advances */
1159 send_game_info(NULL);
1160 send_research_info(research, NULL);
1163 if (changed) {
1164 send_player_all_c(pplayer, NULL);
1168 /****************************************************************************
1169 Handles vision editing requests from client.
1170 ****************************************************************************/
1171 void handle_edit_player_vision(struct connection *pc, int plr_no,
1172 int tile, bool known, int size)
1174 struct player *pplayer;
1175 struct tile *ptile_center;
1177 ptile_center = index_to_tile(tile);
1178 if (!ptile_center) {
1179 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1180 _("Cannot edit vision because %d is not a valid "
1181 "tile index on this map!"), tile);
1182 return;
1185 pplayer = player_by_number(plr_no);
1186 if (!pplayer) {
1187 notify_conn(pc->self, ptile_center, E_BAD_COMMAND, ftc_editor,
1188 /* TRANS: ..." at <tile-coordinates> because"... */
1189 _("Cannot edit vision for the tile at %s because "
1190 "given player id %d is invalid."),
1191 tile_link(ptile_center), plr_no);
1192 return;
1195 conn_list_do_buffer(game.est_connections);
1196 square_iterate(ptile_center, size - 1, ptile) {
1198 if (!known) {
1199 struct city *pcity = tile_city(ptile);
1200 bool cannot_make_unknown = FALSE;
1202 if (pcity && city_owner(pcity) == pplayer) {
1203 continue;
1206 unit_list_iterate(ptile->units, punit) {
1207 if (unit_owner(punit) == pplayer
1208 || really_gives_vision(pplayer, unit_owner(punit))) {
1209 cannot_make_unknown = TRUE;
1210 break;
1212 } unit_list_iterate_end;
1214 if (cannot_make_unknown) {
1215 continue;
1218 /* The client expects tiles which become unseen to
1219 * contain no units (client/packhand.c +2368).
1220 * So here we tell it to remove units that do
1221 * not give it vision. */
1222 unit_list_iterate(ptile->units, punit) {
1223 conn_list_iterate(pplayer->connections, pconn) {
1224 dsend_packet_unit_remove(pconn, punit->id);
1225 } conn_list_iterate_end;
1226 } unit_list_iterate_end;
1229 if (known) {
1230 map_show_tile(pplayer, ptile);
1231 } else {
1232 map_hide_tile(pplayer, ptile);
1234 } square_iterate_end;
1235 conn_list_do_unbuffer(game.est_connections);
1238 /****************************************************************************
1239 Client editor requests us to recalculate borders. Note that this does
1240 not necessarily extend borders to their maximum due to the way the
1241 borders code is written. This may be considered a feature or limitation.
1242 ****************************************************************************/
1243 void handle_edit_recalculate_borders(struct connection *pc)
1245 map_calculate_borders();
1248 /****************************************************************************
1249 Remove any city at the given location.
1250 ****************************************************************************/
1251 void handle_edit_city_remove(struct connection *pc, int id)
1253 struct city *pcity;
1255 pcity = game_city_by_number(id);
1256 if (pcity == NULL) {
1257 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1258 _("No such city (ID %d)."), id);
1259 return;
1262 remove_city(pcity);
1265 /****************************************************************************
1266 Run any pending tile checks.
1267 ****************************************************************************/
1268 void handle_edit_check_tiles(struct connection *pc)
1270 check_edited_tile_terrains();
1273 /****************************************************************************
1274 Temporarily remove fog-of-war for the player with player number 'plr_no'.
1275 This will only stay in effect while the server is in edit mode and the
1276 connection is editing. Has no effect if fog-of-war is disabled globally.
1277 ****************************************************************************/
1278 void handle_edit_toggle_fogofwar(struct connection *pc, int plr_no)
1280 struct player *pplayer;
1282 if (!game.info.fogofwar) {
1283 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1284 _("Cannot toggle fog-of-war when it is already "
1285 "disabled."));
1286 return;
1289 pplayer = player_by_number(plr_no);
1290 if (!pplayer) {
1291 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1292 _("Cannot toggle fog-of-war for invalid player ID %d."),
1293 plr_no);
1294 return;
1297 conn_list_do_buffer(game.est_connections);
1298 if (unfogged_players[player_number(pplayer)]) {
1299 enable_fog_of_war_player(pplayer);
1300 unfogged_players[player_number(pplayer)] = FALSE;
1301 } else {
1302 disable_fog_of_war_player(pplayer);
1303 unfogged_players[player_number(pplayer)] = TRUE;
1305 conn_list_do_unbuffer(game.est_connections);
1308 /****************************************************************************
1309 Create or remove a start position at a tile.
1310 ****************************************************************************/
1311 void handle_edit_startpos(struct connection *pconn,
1312 const struct packet_edit_startpos *packet)
1314 struct tile *ptile = index_to_tile(packet->id);
1315 bool changed;
1317 /* Check. */
1318 if (NULL == ptile) {
1319 notify_conn(pconn->self, NULL, E_BAD_COMMAND, ftc_editor,
1320 _("Invalid tile index %d for start position."), packet->id);
1321 return;
1324 /* Handle. */
1325 if (packet->removal) {
1326 changed = map_startpos_remove(ptile);
1327 } else {
1328 if (NULL != map_startpos_get(ptile)) {
1329 changed = FALSE;
1330 } else {
1331 map_startpos_new(ptile);
1332 changed = TRUE;
1336 /* Notify. */
1337 if (changed) {
1338 conn_list_iterate(game.est_connections, aconn) {
1339 if (can_conn_edit(aconn)) {
1340 send_packet_edit_startpos(aconn, packet);
1342 } conn_list_iterate_end;
1346 /****************************************************************************
1347 Setup which nations can start at a start position.
1348 ****************************************************************************/
1349 void handle_edit_startpos_full(struct connection *pconn,
1350 const struct packet_edit_startpos_full *
1351 packet)
1353 struct tile *ptile = index_to_tile(packet->id);
1354 struct startpos *psp;
1356 /* Check. */
1357 if (NULL == ptile) {
1358 notify_conn(pconn->self, NULL, E_BAD_COMMAND, ftc_editor,
1359 _("Invalid tile index %d for start position."),
1360 packet->id);
1361 return;
1364 psp = map_startpos_get(ptile);
1365 if (NULL == psp) {
1366 notify_conn(pconn->self, ptile, E_BAD_COMMAND, ftc_editor,
1367 _("Cannot edit start position nations at (%d, %d) "
1368 "because there is no start position there."),
1369 TILE_XY(ptile));
1370 return;
1373 /* Handle. */
1374 if (startpos_unpack(psp, packet)) {
1375 /* Notify. */
1376 conn_list_iterate(game.est_connections, aconn) {
1377 if (can_conn_edit(aconn)) {
1378 send_packet_edit_startpos_full(aconn, packet);
1380 } conn_list_iterate_end;
1384 /****************************************************************************
1385 Handle edit requests to the main game data structure.
1386 ****************************************************************************/
1387 void handle_edit_game(struct connection *pc,
1388 const struct packet_edit_game *packet)
1390 bool changed = FALSE;
1392 if (packet->year != game.info.year) {
1394 /* 'year' is stored in a signed short. */
1395 const short min_year = -30000, max_year = 30000;
1397 if (!(min_year <= packet->year && packet->year <= max_year)) {
1398 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1399 _("Cannot set invalid game year %d. Valid year range "
1400 "is from %d to %d."),
1401 packet->year, min_year, max_year);
1402 } else {
1403 game.info.year = packet->year;
1404 changed = TRUE;
1408 if (packet->scenario != game.scenario.is_scenario) {
1409 game.scenario.is_scenario = packet->scenario;
1410 changed = TRUE;
1413 if (0 != strncmp(packet->scenario_name, game.scenario.name, 256)) {
1414 sz_strlcpy(game.scenario.name, packet->scenario_name);
1415 changed = TRUE;
1418 if (0 != strncmp(packet->scenario_authors, game.scenario.authors,
1419 MAX_LEN_PACKET)) {
1420 sz_strlcpy(game.scenario.authors, packet->scenario_authors);
1421 changed = TRUE;
1424 if (packet->scenario_random != game.scenario.save_random) {
1425 game.scenario.save_random = packet->scenario_random;
1426 changed = TRUE;
1429 if (packet->scenario_players != game.scenario.players) {
1430 game.scenario.players = packet->scenario_players;
1431 changed = TRUE;
1434 if (packet->startpos_nations != game.scenario.startpos_nations) {
1435 game.scenario.startpos_nations = packet->startpos_nations;
1436 changed = TRUE;
1439 if (packet->prevent_new_cities != game.scenario.prevent_new_cities) {
1440 game.scenario.prevent_new_cities = packet->prevent_new_cities;
1441 changed = TRUE;
1444 if (packet->lake_flooding != game.scenario.lake_flooding) {
1445 game.scenario.lake_flooding = packet->lake_flooding;
1446 changed = TRUE;
1449 if (changed) {
1450 send_scenario_info(NULL);
1451 send_game_info(NULL);
1455 /****************************************************************************
1456 Handle edit requests to scenario description
1457 ****************************************************************************/
1458 void handle_edit_scenario_desc(struct connection *pc, const char *scenario_desc)
1460 if (0 != strncmp(scenario_desc, game.scenario_desc.description,
1461 MAX_LEN_PACKET)) {
1462 sz_strlcpy(game.scenario_desc.description, scenario_desc);
1463 send_scenario_description(NULL);
1467 /****************************************************************************
1468 Make scenario file out of current game.
1469 ****************************************************************************/
1470 void handle_save_scenario(struct connection *pc, const char *name)
1472 if (pc->access_level != ALLOW_HACK) {
1473 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1474 _("No permissions to remotely save scenario."));
1475 return;
1478 if (!game.scenario.is_scenario) {
1479 /* Scenario information not available */
1480 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1481 _("Scenario information not set. Cannot save scenario."));
1482 return;
1485 /* Client initiated scenario saving is not handmade */
1486 game.scenario.handmade = FALSE;
1488 save_game(name, "Scenario", TRUE);