webperimental: killstack decides stack protects.
[freeciv.git] / server / edithand.c
blob131772117d29460e52d646621b3f11f19cc93e37
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 /* server */
39 #include "aiiface.h"
40 #include "citytools.h"
41 #include "cityturn.h"
42 #include "connecthand.h"
43 #include "gamehand.h"
44 #include "hand_gen.h"
45 #include "maphand.h"
46 #include "plrhand.h"
47 #include "notify.h"
48 #include "sanitycheck.h"
49 #include "savegame.h"
50 #include "stdinhand.h"
51 #include "techtools.h"
52 #include "unittools.h"
54 /* server/generator */
55 #include "mapgen_utils.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 extras property of a tile. Returns TRUE if
245 the extra state has changed.
246 ****************************************************************************/
247 static bool edit_tile_extra_handling(struct tile *ptile,
248 struct extra_type *pextra,
249 bool remove_mode, bool send_info)
251 if (remove_mode) {
252 if (!tile_has_extra(ptile, pextra)) {
253 return FALSE;
256 if (!tile_extra_rm_apply(ptile, pextra)) {
257 return FALSE;
260 terrain_changed(ptile);
262 } else {
263 if (tile_has_extra(ptile, pextra)) {
264 return FALSE;
267 if (!tile_extra_apply(ptile, pextra)) {
268 return FALSE;
272 if (send_info) {
273 update_tile_knowledge(ptile);
276 return TRUE;
279 /****************************************************************************
280 Handles a client request to change the terrain of the tile at the given
281 x, y coordinates. The 'size' parameter indicates that all tiles in a
282 square of "radius" 'size' should be affected. So size=1 corresponds to
283 the single tile case.
284 ****************************************************************************/
285 void handle_edit_tile_terrain(struct connection *pc, int tile,
286 Terrain_type_id terrain, int size)
288 struct terrain *pterrain;
289 struct tile *ptile_center;
291 ptile_center = index_to_tile(&(wld.map), tile);
292 if (!ptile_center) {
293 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
294 _("Cannot edit the tile because %d is not a valid "
295 "tile index on this map!"), tile);
296 return;
299 pterrain = terrain_by_number(terrain);
300 if (!pterrain) {
301 notify_conn(pc->self, ptile_center, E_BAD_COMMAND, ftc_editor,
302 /* TRANS: ..." the tile <tile-coordinates> because"... */
303 _("Cannot modify terrain for the tile %s because "
304 "%d is not a valid terrain id."),
305 tile_link(ptile_center), terrain);
306 return;
309 conn_list_do_buffer(game.est_connections);
310 /* This iterates outward, which gives any units that can't survive on
311 * changed terrain the best chance of survival. */
312 square_iterate(&(wld.map), ptile_center, size - 1, ptile) {
313 edit_tile_terrain_handling(ptile, pterrain, TRUE);
314 } square_iterate_end;
315 conn_list_do_unbuffer(game.est_connections);
318 /****************************************************************************
319 Handle a request to change one or more tiles' extras. The 'remove'
320 argument controls whether to remove or add the given extra from the tile.
321 ****************************************************************************/
322 void handle_edit_tile_extra(struct connection *pc, int tile,
323 int id, bool removal, int size)
325 struct tile *ptile_center;
327 ptile_center = index_to_tile(&(wld.map), tile);
328 if (!ptile_center) {
329 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
330 _("Cannot edit the tile because %d is not a valid "
331 "tile index on this map!"), tile);
332 return;
335 if (id < 0 || id >= game.control.num_extra_types) {
336 notify_conn(pc->self, ptile_center, E_BAD_COMMAND, ftc_editor,
337 /* TRANS: ..." the tile <tile-coordinates> because"... */
338 _("Cannot modify extras for the tile %s because "
339 "%d is not a valid extra id."),
340 tile_link(ptile_center), id);
341 return;
344 conn_list_do_buffer(game.est_connections);
345 square_iterate(&(wld.map), ptile_center, size - 1, ptile) {
346 edit_tile_extra_handling(ptile, extra_by_number(id), removal, TRUE);
347 } square_iterate_end;
348 conn_list_do_unbuffer(game.est_connections);
351 /****************************************************************************
352 Handles tile information from the client, to make edits to tiles.
353 ****************************************************************************/
354 void handle_edit_tile(struct connection *pc,
355 const struct packet_edit_tile *packet)
357 struct tile *ptile;
358 bool changed = FALSE;
360 ptile = index_to_tile(&(wld.map), packet->tile);
361 if (!ptile) {
362 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
363 _("Cannot edit the tile because %d is not a valid "
364 "tile index on this map!"), packet->tile);
365 return;
368 /* Handle changes in extras. */
369 if (!BV_ARE_EQUAL(packet->extras, ptile->extras)) {
370 extra_type_iterate(pextra) {
371 if (edit_tile_extra_handling(ptile, pextra,
372 !BV_ISSET(packet->extras, extra_number(pextra)),
373 FALSE)) {
374 changed = TRUE;
376 } extra_type_iterate_end;
379 /* Handle changes in label */
380 if (tile_set_label(ptile, packet->label)) {
381 changed = TRUE;
384 /* TODO: Handle more property edits. */
387 /* Send the new state to all affected. */
388 if (changed) {
389 update_tile_knowledge(ptile);
390 send_tile_info(NULL, ptile, FALSE);
394 /****************************************************************************
395 Handle a request to create 'count' units of type 'utid' at the tile given
396 by the x, y coordinates and owned by player with number 'owner'.
397 ****************************************************************************/
398 void handle_edit_unit_create(struct connection *pc, int owner, int tile,
399 Unit_type_id utid, int count, int tag)
401 struct tile *ptile;
402 struct unit_type *punittype;
403 struct player *pplayer;
404 struct city *homecity;
405 struct unit *punit;
406 int id, i;
408 ptile = index_to_tile(&(wld.map), tile);
409 if (!ptile) {
410 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
411 _("Cannot create units because %d is not a valid "
412 "tile index on this map!"), tile);
413 return;
416 punittype = utype_by_number(utid);
417 if (!punittype) {
418 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
419 /* TRANS: ..." at <tile-coordinates> because"... */
420 _("Cannot create a unit at %s because the "
421 "given unit type id %d is invalid."),
422 tile_link(ptile), utid);
423 return;
426 pplayer = player_by_number(owner);
427 if (!pplayer) {
428 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
429 /* TRANS: ..." type <unit-type> at <tile-coordinates>"... */
430 _("Cannot create a unit of type %s at %s "
431 "because the given owner's player id %d is "
432 "invalid."), utype_name_translation(punittype),
433 tile_link(ptile), owner);
434 return;
437 if (is_non_allied_unit_tile(ptile, pplayer)
438 || (tile_city(ptile)
439 && !pplayers_allied(pplayer, city_owner(tile_city(ptile))))) {
440 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
441 /* TRANS: ..." type <unit-type> on enemy tile
442 * <tile-coordinates>"... */
443 _("Cannot create unit of type %s on enemy tile "
444 "%s."), utype_name_translation(punittype),
445 tile_link(ptile));
446 return;
449 if (!can_exist_at_tile(&(wld.map), punittype, ptile)) {
450 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
451 /* TRANS: ..." type <unit-type> on the terrain at
452 * <tile-coordinates>"... */
453 _("Cannot create a unit of type %s on the terrain "
454 "at %s."),
455 utype_name_translation(punittype), tile_link(ptile));
456 return;
459 if (count > 0 && !pplayer->is_alive) {
460 pplayer->is_alive = TRUE;
461 send_player_info_c(pplayer, NULL);
464 homecity = find_closest_city(ptile, NULL, pplayer, FALSE, FALSE, FALSE,
465 TRUE, FALSE, utype_class(punittype));
466 id = homecity ? homecity->id : 0;
468 conn_list_do_buffer(game.est_connections);
469 map_show_circle(pplayer, ptile, punittype->vision_radius_sq);
470 for (i = 0; i < count; i++) {
471 /* As far as I can see create_unit is guaranteed to
472 * never return NULL. */
473 punit = create_unit(pplayer, ptile, punittype, 0, id, -1);
474 if (tag > 0) {
475 dsend_packet_edit_object_created(pc, tag, punit->id);
478 conn_list_do_unbuffer(game.est_connections);
481 /****************************************************************************
482 Remove 'count' units of type 'utid' owned by player number 'owner' at
483 tile (x, y).
484 ****************************************************************************/
485 void handle_edit_unit_remove(struct connection *pc, int owner,
486 int tile, Unit_type_id utid, int count)
488 struct tile *ptile;
489 struct unit_type *punittype;
490 struct player *pplayer;
491 int i;
493 ptile = index_to_tile(&(wld.map), tile);
494 if (!ptile) {
495 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
496 _("Cannot remove units because %d is not a valid "
497 "tile index on this map!"), tile);
498 return;
501 punittype = utype_by_number(utid);
502 if (!punittype) {
503 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
504 /* TRANS: ..." at <tile-coordinates> because"... */
505 _("Cannot remove a unit at %s because the "
506 "given unit type id %d is invalid."),
507 tile_link(ptile), utid);
508 return;
511 pplayer = player_by_number(owner);
512 if (!pplayer) {
513 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
514 /* TRANS: ..." type <unit-type> at <tile-coordinates>
515 * because"... */
516 _("Cannot remove a unit of type %s at %s "
517 "because the given owner's player id %d is "
518 "invalid."), utype_name_translation(punittype),
519 tile_link(ptile), owner);
520 return;
523 i = 0;
524 unit_list_iterate_safe(ptile->units, punit) {
525 if (i >= count) {
526 break;
528 if (unit_type_get(punit) != punittype
529 || unit_owner(punit) != pplayer) {
530 continue;
532 wipe_unit(punit, ULR_EDITOR, NULL);
533 i++;
534 } unit_list_iterate_safe_end;
537 /****************************************************************************
538 Handle a request to remove a unit given by its id.
539 ****************************************************************************/
540 void handle_edit_unit_remove_by_id(struct connection *pc, Unit_type_id id)
542 struct unit *punit;
544 punit = game_unit_by_number(id);
545 if (!punit) {
546 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
547 _("No such unit (ID %d)."), id);
548 return;
551 wipe_unit(punit, ULR_EDITOR, NULL);
554 /****************************************************************************
555 Handles unit information from the client, to make edits to units.
556 ****************************************************************************/
557 void handle_edit_unit(struct connection *pc,
558 const struct packet_edit_unit *packet)
560 struct unit_type *putype;
561 struct unit *punit;
562 int id;
563 bool changed = FALSE;
564 int fuel, hp;
566 id = packet->id;
567 punit = game_unit_by_number(id);
568 if (!punit) {
569 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
570 _("No such unit (ID %d)."), id);
571 return;
574 putype = unit_type_get(punit);
576 if (packet->moves_left != punit->moves_left) {
577 punit->moves_left = packet->moves_left;
578 changed = TRUE;
581 fuel = CLIP(0, packet->fuel, utype_fuel(putype));
582 if (fuel != punit->fuel) {
583 punit->fuel = fuel;
584 changed = TRUE;
587 if (packet->moved != punit->moved) {
588 punit->moved = packet->moved;
589 changed = TRUE;
592 if (packet->done_moving != punit->done_moving) {
593 punit->done_moving = packet->done_moving;
594 changed = TRUE;
597 hp = CLIP(1, packet->hp, putype->hp);
598 if (hp != punit->hp) {
599 punit->hp = hp;
600 changed = TRUE;
603 if (packet->veteran != punit->veteran) {
604 int v = packet->veteran;
605 if (!utype_veteran_level(putype, v)) {
606 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
607 _("Invalid veteran level %d for unit %d (%s)."),
608 v, id, unit_link(punit));
609 } else {
610 punit->veteran = v;
611 changed = TRUE;
615 /* TODO: Handle more property edits. */
618 /* Send the new state to all affected. */
619 if (changed) {
620 send_unit_info(NULL, punit);
624 /****************************************************************************
625 Allows the editing client to create a city at the given position and
626 of size 'size'.
627 ****************************************************************************/
628 void handle_edit_city_create(struct connection *pc, int owner, int tile,
629 int size, int tag)
631 struct tile *ptile;
632 struct city *pcity;
633 struct player *pplayer;
635 ptile = index_to_tile(&(wld.map), tile);
636 if (!ptile) {
637 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
638 _("Cannot create a city because %d is not a valid "
639 "tile index on this map!"), tile);
640 return;
643 pplayer = player_by_number(owner);
644 if (!pplayer) {
645 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
646 /* TRANS: ..." at <tile-coordinates> because"... */
647 _("Cannot create a city at %s because the "
648 "given owner's player id %d is invalid"),
649 tile_link(ptile), owner);
650 return;
655 if (is_enemy_unit_tile(ptile, pplayer) != NULL
656 || !city_can_be_built_here(ptile, NULL)) {
657 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
658 /* TRANS: ..." at <tile-coordinates>." */
659 _("A city may not be built at %s."), tile_link(ptile));
660 return;
663 if (!pplayer->is_alive) {
664 pplayer->is_alive = TRUE;
665 send_player_info_c(pplayer, NULL);
668 conn_list_do_buffer(game.est_connections);
670 map_show_tile(pplayer, ptile);
671 create_city(pplayer, ptile, city_name_suggestion(pplayer, ptile),
672 pplayer);
673 pcity = tile_city(ptile);
675 if (size > 1) {
676 /* FIXME: Slow and inefficient for large size changes. */
677 city_change_size(pcity, CLIP(1, size, MAX_CITY_SIZE), pplayer, NULL);
678 send_city_info(NULL, pcity);
681 if (tag > 0) {
682 dsend_packet_edit_object_created(pc, tag, pcity->id);
685 conn_list_do_unbuffer(game.est_connections);
689 /****************************************************************************
690 Handle a request to change the internal state of a city.
691 ****************************************************************************/
692 void handle_edit_city(struct connection *pc,
693 const struct packet_edit_city *packet)
695 struct tile *ptile;
696 struct city *pcity, *oldcity;
697 struct player *pplayer;
698 char buf[1024];
699 int id;
700 bool changed = FALSE;
701 bool need_game_info = FALSE;
702 bv_player need_player_info;
704 pcity = game_city_by_number(packet->id);
705 if (!pcity) {
706 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
707 _("Cannot edit city with invalid city ID %d."),
708 packet->id);
709 return;
712 pplayer = city_owner(pcity);
713 ptile = city_tile(pcity);
714 BV_CLR_ALL(need_player_info);
716 /* Handle name change. */
717 if (0 != strcmp(pcity->name, packet->name)) {
718 if (!is_allowed_city_name(pplayer, packet->name, buf, sizeof(buf))) {
719 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
720 _("Cannot edit city name: %s"), buf);
721 } else {
722 sz_strlcpy(pcity->name, packet->name);
723 changed = TRUE;
727 /* Handle size change. */
728 if (packet->size != city_size_get(pcity)) {
729 if (!(0 < packet->size && packet->size <= MAX_CITY_SIZE)) {
730 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
731 _("Invalid city size %d for city %s."),
732 packet->size, city_link(pcity));
733 } else {
734 /* FIXME: Slow and inefficient for large size changes. */
735 city_change_size(pcity, packet->size, NULL, NULL);
736 changed = TRUE;
740 if (packet->history != pcity->history) {
741 pcity->history = packet->history;
742 changed = TRUE;
745 /* Handle city improvement changes. */
746 improvement_iterate(pimprove) {
747 oldcity = NULL;
748 id = improvement_number(pimprove);
750 if (is_special_improvement(pimprove)) {
751 if (packet->built[id] >= 0) {
752 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
753 _("It is impossible for a city to have %s!"),
754 improvement_name_translation(pimprove));
756 continue;
759 /* FIXME: game.info.great_wonder_owners and pplayer->wonders
760 * logic duplication with city_build_building. */
762 if (city_has_building(pcity, pimprove) && packet->built[id] < 0) {
764 city_remove_improvement(pcity, pimprove);
765 changed = TRUE;
767 } else if (!city_has_building(pcity, pimprove)
768 && packet->built[id] >= 0) {
770 if (is_great_wonder(pimprove)) {
771 oldcity = city_from_great_wonder(pimprove);
772 if (oldcity != pcity) {
773 BV_SET(need_player_info, player_index(pplayer));
775 if (NULL != oldcity && city_owner(oldcity) != pplayer) {
776 /* Great wonders make more changes. */
777 need_game_info = TRUE;
778 BV_SET(need_player_info, player_index(city_owner(oldcity)));
780 } else if (is_small_wonder(pimprove)) {
781 oldcity = city_from_small_wonder(pplayer, pimprove);
782 if (oldcity != pcity) {
783 BV_SET(need_player_info, player_index(pplayer));
787 if (oldcity) {
788 city_remove_improvement(oldcity, pimprove);
789 city_refresh_queue_add(oldcity);
792 city_add_improvement(pcity, pimprove);
793 changed = TRUE;
795 } improvement_iterate_end;
797 /* Handle food stock change. */
798 if (packet->food_stock != pcity->food_stock) {
799 int max = city_granary_size(city_size_get(pcity));
800 if (!(0 <= packet->food_stock && packet->food_stock <= max)) {
801 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
802 _("Invalid city food stock amount %d for city %s "
803 "(allowed range is %d to %d)."),
804 packet->food_stock, city_link(pcity), 0, max);
805 } else {
806 pcity->food_stock = packet->food_stock;
807 changed = TRUE;
811 /* Handle shield stock change. */
812 if (packet->shield_stock != pcity->shield_stock) {
813 int max = USHRT_MAX; /* Limited to uint16 by city info packet. */
814 if (!(0 <= packet->shield_stock && packet->shield_stock <= max)) {
815 notify_conn(pc->self, ptile, E_BAD_COMMAND, ftc_editor,
816 _("Invalid city shield stock amount %d for city %s "
817 "(allowed range is %d to %d)."),
818 packet->shield_stock, city_link(pcity), 0, max);
819 } else {
820 pcity->shield_stock = packet->shield_stock;
821 changed = TRUE;
825 /* TODO: Handle more property edits. */
828 if (changed) {
829 city_refresh_queue_add(pcity);
830 conn_list_do_buffer(game.est_connections);
831 city_refresh_queue_processing();
833 /* FIXME: city_refresh_queue_processing only sends to city owner? */
834 send_city_info(NULL, pcity);
836 conn_list_do_unbuffer(game.est_connections);
839 /* Update wonder infos. */
840 if (need_game_info) {
841 send_game_info(NULL);
843 if (BV_ISSET_ANY(need_player_info)) {
844 players_iterate(aplayer) {
845 if (BV_ISSET(need_player_info, player_index(aplayer))) {
846 /* No need to send to detached connections. */
847 send_player_info_c(aplayer, NULL);
849 } players_iterate_end;
853 /****************************************************************************
854 Handle a request to create a new player.
855 ****************************************************************************/
856 void handle_edit_player_create(struct connection *pc, int tag)
858 struct player *pplayer;
859 struct nation_type *pnation;
860 struct research *presearch;
862 if (player_count() >= player_slot_count()) {
863 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
864 _("No more players can be added because the maximum "
865 "number of players (%d) has been reached."),
866 player_slot_count());
867 return;
870 if (player_count() >= nation_count() ) {
871 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
872 _("No more players can be added because there are "
873 "no available nations (%d used)."),
874 nation_count());
875 return;
878 pnation = pick_a_nation(NULL, TRUE, TRUE, NOT_A_BARBARIAN);
879 if (!pnation) {
880 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
881 _("Player cannot be created because random nation "
882 "selection failed."));
883 return;
886 pplayer = server_create_player(-1, default_ai_type_name(),
887 NULL, FALSE);
888 if (!pplayer) {
889 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
890 _("Player creation failed."));
891 return;
893 server_player_init(pplayer, TRUE, TRUE);
895 player_nation_defaults(pplayer, pnation, TRUE);
896 if (game_was_started()) {
897 /* Find a color for the new player. */
898 assign_player_colors();
900 sz_strlcpy(pplayer->username, _(ANON_USER_NAME));
901 pplayer->unassigned_user = TRUE;
902 pplayer->is_connected = FALSE;
903 pplayer->government = init_government_of_nation(pnation);
904 pplayer->server.got_first_city = FALSE;
906 pplayer->economic.gold = 0;
907 pplayer->economic = player_limit_to_max_rates(pplayer);
909 presearch = research_get(pplayer);
910 init_tech(presearch, TRUE);
911 give_initial_techs(presearch, 0);
913 send_player_all_c(pplayer, NULL);
914 /* Send research info after player info, else the client will complain
915 * about invalid team. */
916 send_research_info(presearch, NULL);
917 if (tag > 0) {
918 dsend_packet_edit_object_created(pc, tag, player_number(pplayer));
922 /****************************************************************************
923 Handle a request to remove a player.
924 ****************************************************************************/
925 void handle_edit_player_remove(struct connection *pc, int id)
927 struct player *pplayer;
929 pplayer = player_by_number(id);
930 if (pplayer == NULL) {
931 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
932 _("No such player (ID %d)."), id);
933 return;
936 /* Don't use conn_list_iterate here because connection_detach() can be
937 * recursive and free the next connection pointer. */
938 while (conn_list_size(pplayer->connections) > 0) {
939 connection_detach(conn_list_get(pplayer->connections, 0), FALSE);
942 kill_player(pplayer);
943 server_remove_player(pplayer);
946 /**************************************************************************
947 Handle editing of any or all player properties.
948 ***************************************************************************/
949 void handle_edit_player(struct connection *pc,
950 const struct packet_edit_player *packet)
952 struct player *pplayer;
953 bool changed = FALSE, update_research = FALSE;
954 struct nation_type *pnation;
955 struct research *research;
956 enum tech_state known;
957 struct government *gov;
959 pplayer = player_by_number(packet->id);
960 if (!pplayer) {
961 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
962 _("Cannot edit player with invalid player ID %d."),
963 packet->id);
964 return;
967 research = research_get(pplayer);
970 /* Handle player name change. */
971 if (0 != strcmp(packet->name, player_name(pplayer))) {
972 char error_buf[256];
974 if (server_player_set_name_full(pc, pplayer, NULL, packet->name,
975 error_buf, sizeof(error_buf))) {
976 changed = TRUE;
977 } else {
978 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
979 _("Cannot change name of player (%d) '%s' to '%s': %s"),
980 player_number(pplayer), player_name(pplayer),
981 packet->name, error_buf);
985 /* Handle nation change. */
986 pnation = nation_by_number(packet->nation);
987 if (nation_of_player(pplayer) != pnation) {
988 if (pnation == NULL) {
989 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
990 _("Cannot change nation for player %d (%s) "
991 "because the given nation ID %d is invalid."),
992 player_number(pplayer), player_name(pplayer),
993 packet->nation);
994 } else if (pnation->player != NULL) {
995 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
996 _("Cannot change nation for player %d (%s) "
997 "to nation %d (%s) because that nation is "
998 "already assigned to player %d (%s)."),
999 player_number(pplayer), player_name(pplayer),
1000 packet->nation, nation_plural_translation(pnation),
1001 player_number(pnation->player),
1002 player_name(pnation->player));
1003 } else if (!nation_is_in_current_set(pnation)) {
1004 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1005 _("Cannot change nation for player %d (%s) "
1006 "to nation %d (%s) because that nation is "
1007 "not in the current nation set."),
1008 player_number(pplayer), player_name(pplayer),
1009 packet->nation, nation_plural_translation(pnation));
1010 } else if (pplayer->ai_common.barbarian_type
1011 != nation_barbarian_type(pnation)
1012 || (!is_barbarian(pplayer) && !is_nation_playable(pnation))) {
1013 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1014 _("Cannot change nation for player %d (%s) "
1015 "to nation %d (%s) because that nation is "
1016 "unsuitable for this player."),
1017 player_number(pplayer), player_name(pplayer),
1018 packet->nation, nation_plural_translation(pnation));
1019 } else {
1020 changed = player_set_nation(pplayer, pnation);
1024 /* Handle a change in research progress. */
1025 if (packet->bulbs_researched != research->bulbs_researched) {
1026 research->bulbs_researched = packet->bulbs_researched;
1027 changed = TRUE;
1028 update_research = TRUE;
1031 /* Handle a change in known inventions. */
1032 advance_index_iterate(A_FIRST, tech) {
1033 known = research_invention_state(research, tech);
1034 if ((packet->inventions[tech] && known == TECH_KNOWN)
1035 || (!packet->inventions[tech] && known != TECH_KNOWN)) {
1036 continue;
1038 if (packet->inventions[tech]) {
1039 /* FIXME: Side-effect modifies game.info.global_advances. */
1040 research_invention_set(research, tech, TECH_KNOWN);
1041 research->techs_researched++;
1042 } else {
1043 research_invention_set(research, tech, TECH_UNKNOWN);
1044 research->techs_researched--;
1046 changed = TRUE;
1047 update_research = TRUE;
1048 } advance_index_iterate_end;
1050 /* Handle a change in the player's gold. */
1051 if (packet->gold != pplayer->economic.gold) {
1052 if (!(0 <= packet->gold && packet->gold <= 1000000)) {
1053 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1054 _("Cannot set gold for player %d (%s) because "
1055 "the value %d is outside the allowed range."),
1056 player_number(pplayer), player_name(pplayer),
1057 packet->gold);
1058 } else {
1059 pplayer->economic.gold = packet->gold;
1060 changed = TRUE;
1064 /* Handle player government change */
1065 gov = government_by_number(packet->government);
1066 if (gov != pplayer->government) {
1067 if (gov != game.government_during_revolution) {
1068 government_change(pplayer, gov, FALSE);
1069 } else {
1070 int turns = revolution_length(gov, pplayer);
1072 if (turns >= 0) {
1073 pplayer->government = gov;
1074 pplayer->revolution_finishes = game.info.turn + turns;
1078 changed = TRUE;
1081 if (packet->scenario_reserved) {
1082 if (!player_has_flag(pplayer, PLRF_SCENARIO_RESERVED)) {
1083 changed = TRUE;
1084 BV_SET(pplayer->flags, PLRF_SCENARIO_RESERVED);
1086 } else {
1087 if (player_has_flag(pplayer, PLRF_SCENARIO_RESERVED)) {
1088 changed = TRUE;
1089 BV_CLR(pplayer->flags, PLRF_SCENARIO_RESERVED);
1093 /* TODO: Handle more property edits. */
1095 if (update_research) {
1096 Tech_type_id current, goal;
1098 research_update(research);
1100 /* FIXME: Modifies struct research directly. */
1102 current = research->researching;
1103 goal = research->tech_goal;
1105 if (current != A_UNSET) {
1106 known = research_invention_state(research, current);
1107 if (known != TECH_PREREQS_KNOWN) {
1108 research->researching = A_UNSET;
1111 if (goal != A_UNSET) {
1112 known = research_invention_state(research, goal);
1113 if (known == TECH_KNOWN) {
1114 research->tech_goal = A_UNSET;
1117 changed = TRUE;
1119 /* Inform everybody about global advances */
1120 send_game_info(NULL);
1121 send_research_info(research, NULL);
1124 if (changed) {
1125 send_player_all_c(pplayer, NULL);
1129 /****************************************************************************
1130 Handles vision editing requests from client.
1131 ****************************************************************************/
1132 void handle_edit_player_vision(struct connection *pc, int plr_no,
1133 int tile, bool known, int size)
1135 struct player *pplayer;
1136 struct tile *ptile_center;
1138 ptile_center = index_to_tile(&(wld.map), tile);
1139 if (!ptile_center) {
1140 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1141 _("Cannot edit vision because %d is not a valid "
1142 "tile index on this map!"), tile);
1143 return;
1146 pplayer = player_by_number(plr_no);
1147 if (!pplayer) {
1148 notify_conn(pc->self, ptile_center, E_BAD_COMMAND, ftc_editor,
1149 /* TRANS: ..." at <tile-coordinates> because"... */
1150 _("Cannot edit vision for the tile at %s because "
1151 "given player id %d is invalid."),
1152 tile_link(ptile_center), plr_no);
1153 return;
1156 conn_list_do_buffer(game.est_connections);
1157 square_iterate(&(wld.map), ptile_center, size - 1, ptile) {
1159 if (!known) {
1160 struct city *pcity = tile_city(ptile);
1161 bool cannot_make_unknown = FALSE;
1163 if (pcity && city_owner(pcity) == pplayer) {
1164 continue;
1167 unit_list_iterate(ptile->units, punit) {
1168 if (unit_owner(punit) == pplayer
1169 || really_gives_vision(pplayer, unit_owner(punit))) {
1170 cannot_make_unknown = TRUE;
1171 break;
1173 } unit_list_iterate_end;
1175 if (cannot_make_unknown) {
1176 continue;
1179 /* The client expects tiles which become unseen to
1180 * contain no units (client/packhand.c +2368).
1181 * So here we tell it to remove units that do
1182 * not give it vision. */
1183 unit_list_iterate(ptile->units, punit) {
1184 conn_list_iterate(pplayer->connections, pconn) {
1185 dsend_packet_unit_remove(pconn, punit->id);
1186 } conn_list_iterate_end;
1187 } unit_list_iterate_end;
1190 if (known) {
1191 map_show_tile(pplayer, ptile);
1192 } else {
1193 map_hide_tile(pplayer, ptile);
1195 } square_iterate_end;
1196 conn_list_do_unbuffer(game.est_connections);
1199 /****************************************************************************
1200 Client editor requests us to recalculate borders. Note that this does
1201 not necessarily extend borders to their maximum due to the way the
1202 borders code is written. This may be considered a feature or limitation.
1203 ****************************************************************************/
1204 void handle_edit_recalculate_borders(struct connection *pc)
1206 map_calculate_borders();
1209 /****************************************************************************
1210 Remove any city at the given location.
1211 ****************************************************************************/
1212 void handle_edit_city_remove(struct connection *pc, int id)
1214 struct city *pcity;
1216 pcity = game_city_by_number(id);
1217 if (pcity == NULL) {
1218 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1219 _("No such city (ID %d)."), id);
1220 return;
1223 remove_city(pcity);
1226 /****************************************************************************
1227 Run any pending tile checks.
1228 ****************************************************************************/
1229 void handle_edit_check_tiles(struct connection *pc)
1231 check_edited_tile_terrains();
1234 /****************************************************************************
1235 Temporarily remove fog-of-war for the player with player number 'plr_no'.
1236 This will only stay in effect while the server is in edit mode and the
1237 connection is editing. Has no effect if fog-of-war is disabled globally.
1238 ****************************************************************************/
1239 void handle_edit_toggle_fogofwar(struct connection *pc, int plr_no)
1241 struct player *pplayer;
1243 if (!game.info.fogofwar) {
1244 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1245 _("Cannot toggle fog-of-war when it is already "
1246 "disabled."));
1247 return;
1250 pplayer = player_by_number(plr_no);
1251 if (!pplayer) {
1252 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1253 _("Cannot toggle fog-of-war for invalid player ID %d."),
1254 plr_no);
1255 return;
1258 conn_list_do_buffer(game.est_connections);
1259 if (unfogged_players[player_number(pplayer)]) {
1260 enable_fog_of_war_player(pplayer);
1261 unfogged_players[player_number(pplayer)] = FALSE;
1262 } else {
1263 disable_fog_of_war_player(pplayer);
1264 unfogged_players[player_number(pplayer)] = TRUE;
1266 conn_list_do_unbuffer(game.est_connections);
1269 /****************************************************************************
1270 Create or remove a start position at a tile.
1271 ****************************************************************************/
1272 void handle_edit_startpos(struct connection *pconn,
1273 const struct packet_edit_startpos *packet)
1275 struct tile *ptile = index_to_tile(&(wld.map), packet->id);
1276 bool changed;
1278 /* Check. */
1279 if (NULL == ptile) {
1280 notify_conn(pconn->self, NULL, E_BAD_COMMAND, ftc_editor,
1281 _("Invalid tile index %d for start position."), packet->id);
1282 return;
1285 /* Handle. */
1286 if (packet->removal) {
1287 changed = map_startpos_remove(ptile);
1288 } else {
1289 if (NULL != map_startpos_get(ptile)) {
1290 changed = FALSE;
1291 } else {
1292 map_startpos_new(ptile);
1293 changed = TRUE;
1297 /* Notify. */
1298 if (changed) {
1299 conn_list_iterate(game.est_connections, aconn) {
1300 if (can_conn_edit(aconn)) {
1301 send_packet_edit_startpos(aconn, packet);
1303 } conn_list_iterate_end;
1307 /****************************************************************************
1308 Setup which nations can start at a start position.
1309 ****************************************************************************/
1310 void handle_edit_startpos_full(struct connection *pconn,
1311 const struct packet_edit_startpos_full *
1312 packet)
1314 struct tile *ptile = index_to_tile(&(wld.map), packet->id);
1315 struct startpos *psp;
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."),
1321 packet->id);
1322 return;
1325 psp = map_startpos_get(ptile);
1326 if (NULL == psp) {
1327 notify_conn(pconn->self, ptile, E_BAD_COMMAND, ftc_editor,
1328 _("Cannot edit start position nations at (%d, %d) "
1329 "because there is no start position there."),
1330 TILE_XY(ptile));
1331 return;
1334 /* Handle. */
1335 if (startpos_unpack(psp, packet)) {
1336 /* Notify. */
1337 conn_list_iterate(game.est_connections, aconn) {
1338 if (can_conn_edit(aconn)) {
1339 send_packet_edit_startpos_full(aconn, packet);
1341 } conn_list_iterate_end;
1345 /****************************************************************************
1346 Handle edit requests to the main game data structure.
1347 ****************************************************************************/
1348 void handle_edit_game(struct connection *pc,
1349 const struct packet_edit_game *packet)
1351 bool changed = FALSE;
1353 if (packet->year != game.info.year) {
1355 /* 'year' is stored in a signed short. */
1356 const short min_year = -30000, max_year = 30000;
1358 if (!(min_year <= packet->year && packet->year <= max_year)) {
1359 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1360 _("Cannot set invalid game year %d. Valid year range "
1361 "is from %d to %d."),
1362 packet->year, min_year, max_year);
1363 } else {
1364 game.info.year = packet->year;
1365 changed = TRUE;
1369 if (packet->scenario != game.scenario.is_scenario) {
1370 game.scenario.is_scenario = packet->scenario;
1371 changed = TRUE;
1374 if (0 != strncmp(packet->scenario_name, game.scenario.name, 256)) {
1375 sz_strlcpy(game.scenario.name, packet->scenario_name);
1376 changed = TRUE;
1379 if (0 != strncmp(packet->scenario_authors, game.scenario.authors,
1380 MAX_LEN_PACKET)) {
1381 sz_strlcpy(game.scenario.authors, packet->scenario_authors);
1382 changed = TRUE;
1385 if (packet->scenario_random != game.scenario.save_random) {
1386 game.scenario.save_random = packet->scenario_random;
1387 changed = TRUE;
1390 if (packet->scenario_players != game.scenario.players) {
1391 game.scenario.players = packet->scenario_players;
1392 changed = TRUE;
1395 if (packet->startpos_nations != game.scenario.startpos_nations) {
1396 game.scenario.startpos_nations = packet->startpos_nations;
1397 changed = TRUE;
1400 if (packet->prevent_new_cities != game.scenario.prevent_new_cities) {
1401 game.scenario.prevent_new_cities = packet->prevent_new_cities;
1402 changed = TRUE;
1405 if (packet->lake_flooding != game.scenario.lake_flooding) {
1406 game.scenario.lake_flooding = packet->lake_flooding;
1407 changed = TRUE;
1410 if (packet->ruleset_locked != game.scenario.ruleset_locked) {
1411 game.scenario.ruleset_locked = packet->ruleset_locked;
1412 changed = TRUE;
1415 if (changed) {
1416 send_scenario_info(NULL);
1417 send_game_info(NULL);
1421 /****************************************************************************
1422 Handle edit requests to scenario description
1423 ****************************************************************************/
1424 void handle_edit_scenario_desc(struct connection *pc, const char *scenario_desc)
1426 if (0 != strncmp(scenario_desc, game.scenario_desc.description,
1427 MAX_LEN_PACKET)) {
1428 sz_strlcpy(game.scenario_desc.description, scenario_desc);
1429 send_scenario_description(NULL);
1433 /****************************************************************************
1434 Make scenario file out of current game.
1435 ****************************************************************************/
1436 void handle_save_scenario(struct connection *pc, const char *name)
1438 if (pc->access_level != ALLOW_HACK) {
1439 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1440 _("No permissions to remotely save scenario."));
1441 return;
1444 if (!game.scenario.is_scenario) {
1445 /* Scenario information not available */
1446 notify_conn(pc->self, NULL, E_BAD_COMMAND, ftc_editor,
1447 _("Scenario information not set. Cannot save scenario."));
1448 return;
1451 /* Client initiated scenario saving is not handmade */
1452 game.scenario.handmade = FALSE;
1454 save_game(name, "Scenario", TRUE);