1 /***********************************************************************
2 Freeciv - Copyright (C) 1996 - A Kjeldberg, L Gregersen, P Unold
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)
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 ***********************************************************************/
15 #include <fc_config.h>
18 #include <stdio.h> /* for remove() */
21 #include "capability.h"
28 #include "string_vector.h"
36 #include "improvement.h"
41 #include "citytools.h"
42 #include "connecthand.h"
47 #include "unittools.h"
54 #define CHALLENGE_ROOT "challenge"
56 #define SPECLIST_TAG startpos
57 #define SPECLIST_TYPE struct startpos
59 #define startpos_list_iterate(list, plink, psp) \
60 TYPED_LIST_BOTH_ITERATE(struct startpos_list_link, struct startpos, \
62 #define startpos_list_iterate_end LIST_BOTH_ITERATE_END
64 struct team_placement_config
{
65 struct tile
**startpos
;
66 int flexible_startpos_num
;
67 int usable_startpos_num
;
68 int total_startpos_num
;
71 struct team_placement_state
{
76 #define SPECPQ_TAG team_placement
77 #define SPECPQ_DATA_TYPE struct team_placement_state *
78 #define SPECPQ_PRIORITY_TYPE long
81 static struct strvec
*ruleset_choices
= NULL
;
83 /****************************************************************************
84 Get role_id for given role character
85 ****************************************************************************/
86 enum unit_role_id
crole_to_role_id(char crole
)
90 return L_START_CITIES
;
92 return L_START_WORKER
;
94 return L_START_EXPLORER
;
98 return L_START_DIPLOMAT
;
100 return L_START_FERRY
;
102 return L_START_DEFEND_OK
;
104 return L_START_DEFEND_GOOD
;
106 return L_START_ATTACK_FAST
;
108 return L_START_ATTACK_STRONG
;
114 /****************************************************************************
115 Get unit_type for given role character
116 ****************************************************************************/
117 struct unit_type
*crole_to_unit_type(char crole
, struct player
*pplayer
)
119 struct unit_type
*utype
= NULL
;
120 enum unit_role_id role
= crole_to_role_id(crole
);
123 fc_assert_ret_val(FALSE
, NULL
);
127 /* Create the unit of an appropriate type, if it exists */
128 if (num_role_units(role
) > 0) {
129 if (pplayer
!= NULL
) {
130 utype
= first_role_unit_for_player(pplayer
, role
);
133 utype
= get_role_unit(role
, 0);
140 /****************************************************************************
141 Place a starting unit for the player. Returns tile where unit was really
143 ****************************************************************************/
144 static struct tile
*place_starting_unit(struct tile
*starttile
,
145 struct player
*pplayer
,
148 struct tile
*ptile
= NULL
;
149 struct unit_type
*utype
= crole_to_unit_type(crole
, pplayer
);
150 bool hut_present
= FALSE
;
153 iterate_outward(&(wld
.map
), starttile
,
154 wld
.map
.xsize
+ wld
.map
.ysize
, itertile
) {
155 if (!is_non_allied_unit_tile(itertile
, pplayer
)
156 && is_native_tile(utype
, itertile
)) {
160 } iterate_outward_end
;
164 /* No place where unit may exist. */
168 fc_assert_ret_val(!is_non_allied_unit_tile(ptile
, pplayer
), NULL
);
170 /* For scenarios or dispersion, huts may coincide with player starts (in
171 * other cases, huts are avoided as start positions). Remove any such hut,
172 * and make sure to tell the client, since we may have already sent this
173 * tile (with the hut) earlier: */
174 extra_type_by_cause_iterate(EC_HUT
, pextra
) {
175 if (tile_has_extra(ptile
, pextra
)) {
176 tile_extra_rm_apply(ptile
, pextra
);
179 } extra_type_by_cause_iterate_end
;
182 update_tile_knowledge(ptile
);
183 log_verbose("Removed hut on start position for %s",
184 player_name(pplayer
));
187 /* Expose visible area. */
188 map_show_circle(pplayer
, ptile
, game
.server
.init_vis_radius_sq
);
191 (void) create_unit(pplayer
, ptile
, utype
, FALSE
, 0, 0);
198 /****************************************************************************
199 Find a valid position not far from our starting position.
200 ****************************************************************************/
201 static struct tile
*find_dispersed_position(struct player
*pplayer
,
202 struct tile
*pcenter
)
208 index_to_map_pos(&x
, &y
, tile_index(pcenter
));
209 x
+= fc_rand(2 * game
.server
.dispersion
+ 1) - game
.server
.dispersion
;
210 y
+= fc_rand(2 * game
.server
.dispersion
+ 1) - game
.server
.dispersion
;
211 } while (!((ptile
= map_pos_to_tile(&(wld
.map
), x
, y
))
212 && tile_continent(pcenter
) == tile_continent(ptile
)
213 && !is_ocean_tile(ptile
)
214 && !is_non_allied_unit_tile(ptile
, pplayer
)));
219 /* Calculate the distance between tiles, according to the 'teamplacement'
220 * setting set to 'CLOSEST'. */
221 #define team_placement_closest sq_map_distance
223 /****************************************************************************
224 Calculate the distance between tiles, according to the 'teamplacement'
225 setting set to 'CONTINENT'.
226 ****************************************************************************/
227 static int team_placement_continent(const struct tile
*ptile1
,
228 const struct tile
*ptile2
)
230 return (ptile1
->continent
== ptile2
->continent
231 ? sq_map_distance(ptile1
, ptile2
)
232 : sq_map_distance(ptile1
, ptile2
) + MAP_INDEX_SIZE
);
235 /****************************************************************************
236 Calculate the distance between tiles, according to the 'teamplacement'
237 setting set to 'HORIZONTAL'.
238 ****************************************************************************/
239 static int team_placement_horizontal(const struct tile
*ptile1
,
240 const struct tile
*ptile2
)
244 map_distance_vector(&dx
, &dy
, ptile1
, ptile2
);
245 /* Map vector to natural vector (Y axis). */
246 return abs(MAP_IS_ISOMETRIC
? dx
+ dy
: dy
);
249 /****************************************************************************
250 Calculate the distance between tiles, according to the 'teamplacement'
251 setting set to 'VERTICAL'.
252 ****************************************************************************/
253 static int team_placement_vertical(const struct tile
*ptile1
,
254 const struct tile
*ptile2
)
258 map_distance_vector(&dx
, &dy
, ptile1
, ptile2
);
259 /* Map vector to natural vector (X axis). */
260 return abs(MAP_IS_ISOMETRIC
? dx
- dy
: dy
);
263 /****************************************************************************
264 Destroys a team_placement_state structure.
265 ****************************************************************************/
266 static void team_placement_state_destroy(struct team_placement_state
*pstate
)
268 free(pstate
->startpos
);
272 /****************************************************************************
273 Find the best team placement, according to the 'team_placement' setting.
274 ****************************************************************************/
275 static void do_team_placement(const struct team_placement_config
*pconfig
,
276 struct team_placement_state
*pbest_state
,
279 const size_t state_array_size
= (sizeof(*pbest_state
->startpos
)
280 * pconfig
->total_startpos_num
);
281 struct team_placement_pq
*pqueue
=
282 team_placement_pq_new(pconfig
->total_startpos_num
* 4);
283 int (*distance
)(const struct tile
*, const struct tile
*) = NULL
;
284 struct team_placement_state
*pstate
, *pnew
;
285 const struct tile
*ptile1
, *ptile2
;
286 long base_delta
, delta
;
287 bool base_delta_calculated
;
292 switch (wld
.map
.server
.team_placement
) {
293 case TEAM_PLACEMENT_CLOSEST
:
294 distance
= team_placement_closest
;
296 case TEAM_PLACEMENT_CONTINENT
:
297 distance
= team_placement_continent
;
299 case TEAM_PLACEMENT_HORIZONTAL
:
300 distance
= team_placement_horizontal
;
302 case TEAM_PLACEMENT_VERTICAL
:
303 distance
= team_placement_vertical
;
305 case TEAM_PLACEMENT_DISABLED
:
308 fc_assert_ret_msg(distance
!= NULL
, "Wrong team_placement variant (%d)",
309 wld
.map
.server
.team_placement
);
311 /* Initialize starting state. */
312 pstate
= fc_malloc(sizeof(*pstate
));
313 pstate
->startpos
= fc_malloc(state_array_size
);
314 memcpy(pstate
->startpos
, pbest_state
->startpos
, state_array_size
);
315 pstate
->score
= pbest_state
->score
;
319 for (i
= 0; i
< pconfig
->usable_startpos_num
; i
++) {
320 t1
= pstate
->startpos
[i
];
322 continue; /* Not used. */
324 ptile1
= pconfig
->startpos
[i
];
325 base_delta_calculated
= FALSE
;
326 for (j
= i
+ 1; j
< (i
>= pconfig
->flexible_startpos_num
327 ? pconfig
->usable_startpos_num
328 : pconfig
->flexible_startpos_num
); j
++) {
329 t2
= pstate
->startpos
[j
];
331 /* Not assigned yet. */
332 ptile2
= pconfig
->startpos
[j
];
333 if (base_delta_calculated
) {
335 for (k
= 0; k
< pconfig
->total_startpos_num
; k
++) {
336 if (k
!= i
&& t1
== pstate
->startpos
[k
]) {
337 delta
+= distance(ptile2
, pconfig
->startpos
[k
]);
343 for (k
= 0; k
< pconfig
->total_startpos_num
; k
++) {
344 if (k
!= i
&& t1
== pstate
->startpos
[k
]) {
345 base_delta
-= distance(ptile1
, pconfig
->startpos
[k
]);
346 delta
+= distance(ptile2
, pconfig
->startpos
[k
]);
350 base_delta_calculated
= TRUE
;
352 } else if (t1
< t2
) {
353 ptile2
= pconfig
->startpos
[j
];
354 if (base_delta_calculated
) {
356 for (k
= 0; k
< pconfig
->total_startpos_num
; k
++) {
357 if (k
!= i
&& t1
== pstate
->startpos
[k
]) {
358 delta
+= distance(ptile2
, pconfig
->startpos
[k
]);
359 } else if (k
!= j
&& t2
== pstate
->startpos
[k
]) {
360 delta
-= distance(ptile2
, pconfig
->startpos
[k
]);
361 delta
+= distance(ptile1
, pconfig
->startpos
[k
]);
367 for (k
= 0; k
< pconfig
->total_startpos_num
; k
++) {
368 if (k
!= i
&& t1
== pstate
->startpos
[k
]) {
369 base_delta
-= distance(ptile1
, pconfig
->startpos
[k
]);
370 delta
+= distance(ptile2
, pconfig
->startpos
[k
]);
371 } else if (k
!= j
&& t2
== pstate
->startpos
[k
]) {
372 delta
-= distance(ptile2
, pconfig
->startpos
[k
]);
373 delta
+= distance(ptile1
, pconfig
->startpos
[k
]);
377 base_delta_calculated
= TRUE
;
385 pnew
= fc_malloc(sizeof(*pnew
));
386 pnew
->startpos
= fc_malloc(state_array_size
);
387 memcpy(pnew
->startpos
, pstate
->startpos
, state_array_size
);
388 pnew
->startpos
[i
] = t2
;
389 pnew
->startpos
[j
] = t1
;
390 pnew
->score
= pstate
->score
+ delta
;
391 team_placement_pq_insert(pqueue
, pnew
, -pnew
->score
);
393 if (pnew
->score
< pbest_state
->score
) {
394 memcpy(pbest_state
->startpos
, pnew
->startpos
, state_array_size
);
395 pbest_state
->score
= pnew
->score
;
401 team_placement_state_destroy(pstate
);
402 if (iter
++ >= iter_max
) {
403 log_normal(_("Didn't find optimal solution for team placement "
404 "in %d iterations."), iter
);
408 } while (repeat
&& team_placement_pq_remove(pqueue
, &pstate
));
410 team_placement_pq_destroy_full(pqueue
, team_placement_state_destroy
);
413 /****************************************************************************
414 Initialize a new game: place the players' units onto the map, etc.
415 ****************************************************************************/
416 void init_new_game(void)
418 struct startpos_list
*impossible_list
, *targeted_list
, *flexible_list
;
419 struct tile
*player_startpos
[player_slot_count()];
420 int placed_units
[player_slot_count()];
421 int players_to_place
= player_count();
424 randomize_base64url_string(server
.game_identifier
,
425 sizeof(server
.game_identifier
));
427 /* Assign players to starting positions on the map.
428 * (In scenarios with restrictions on which nations can use which predefined
429 * start positions, this process tries to satisfy those restrictions, but
430 * does not guarantee to. Even if there is a solution to the matching
431 * problem, this algorithm may not find it.) */
433 fc_assert(player_count() <= map_startpos_count());
435 /* Convert the startposition hash table in a linked lists, as we mostly
436 * need now to iterate it now. And then, we will be able to remove the
437 * assigned start postions one by one. */
438 impossible_list
= startpos_list_new();
439 targeted_list
= startpos_list_new();
440 flexible_list
= startpos_list_new();
442 map_startpos_iterate(psp
) {
443 if (startpos_allows_all(psp
)) {
444 startpos_list_append(flexible_list
, psp
);
446 startpos_list_append(targeted_list
, psp
);
448 } map_startpos_iterate_end
;
450 fc_assert(startpos_list_size(targeted_list
)
451 + startpos_list_size(flexible_list
) == map_startpos_count());
453 memset(player_startpos
, 0, sizeof(player_startpos
));
454 log_verbose("Placing players at start positions.");
456 /* First assign start positions which have restrictions on which nations
458 if (0 < startpos_list_size(targeted_list
)) {
459 log_verbose("Assigning matching nations.");
461 startpos_list_shuffle(targeted_list
); /* Randomize. */
463 struct nation_type
*pnation
;
464 struct startpos_list_link
*choice
;
465 bool removed
= FALSE
;
467 /* Assign first players which can pick only one start position. */
468 players_iterate(pplayer
) {
469 if (NULL
!= player_startpos
[player_index(pplayer
)]) {
470 /* Already assigned. */
474 pnation
= nation_of_player(pplayer
);
476 startpos_list_iterate(targeted_list
, plink
, psp
) {
477 if (startpos_nation_allowed(psp
, pnation
)) {
478 if (NULL
!= choice
) {
480 break; /* Many choices. */
485 } startpos_list_iterate_end
;
487 if (NULL
!= choice
) {
488 /* Assign this start position to this player and remove
489 * both from consideration. */
491 startpos_tile(startpos_list_link_data(choice
));
493 player_startpos
[player_index(pplayer
)] = ptile
;
494 startpos_list_erase(targeted_list
, choice
);
497 log_verbose("Start position (%d, %d) exactly matches player %s (%s).",
498 TILE_XY(ptile
), player_name(pplayer
),
499 nation_rule_name(pnation
));
501 } players_iterate_end
;
504 /* Didn't find any 1:1 matches. For the next restricted start
505 * position, assign a random matching player. (This may create
506 * restrictions such that more 1:1 matches are possible.) */
507 struct startpos
*psp
= startpos_list_back(targeted_list
);
508 struct tile
*ptile
= startpos_tile(psp
);
509 struct player
*rand_plr
= NULL
;
512 startpos_list_pop_back(targeted_list
); /* Detach 'psp'. */
513 players_iterate(pplayer
) {
514 if (NULL
!= player_startpos
[player_index(pplayer
)]) {
515 /* Already assigned. */
519 pnation
= nation_of_player(pplayer
);
520 if (startpos_nation_allowed(psp
, pnation
) && 0 == fc_rand(++i
)) {
523 } players_iterate_end
;
525 if (NULL
!= rand_plr
) {
526 player_startpos
[player_index(rand_plr
)] = ptile
;
528 log_verbose("Start position (%d, %d) matches player %s (%s).",
529 TILE_XY(ptile
), player_name(rand_plr
),
530 nation_rule_name(nation_of_player(rand_plr
)));
532 /* This start position cannot be assigned, given the assignments
533 * made so far. We may have to fall back to mismatched
535 log_verbose("Start position (%d, %d) cannot be assigned for "
536 "any player, keeping for the moment...",
538 /* Keep it for later, we may need it. */
539 startpos_list_append(impossible_list
, psp
);
542 } while (0 < players_to_place
&& 0 < startpos_list_size(targeted_list
));
545 /* Now try to assign with regard to the 'teamplacement' setting. */
546 if (players_to_place
> 0
547 && wld
.map
.server
.team_placement
!= TEAM_PLACEMENT_DISABLED
548 && player_count() > team_count()) {
549 const struct player_list
*members
;
550 int team_placement_players_to_place
= 0;
551 int real_team_count
= 0;
553 teams_iterate(pteam
) {
554 members
= team_members(pteam
);
555 fc_assert(0 < player_list_size(members
));
557 if (player_list_size(members
) == 1) {
558 /* Single player teams, doesn't count for team placement. */
561 player_list_iterate(members
, pplayer
) {
562 if (player_startpos
[player_index(pplayer
)] == NULL
) {
563 team_placement_players_to_place
++;
565 } player_list_iterate_end
;
568 if (real_team_count
> 1 && team_placement_players_to_place
> 0) {
569 /* We really can do something to improve team placement. */
570 struct team_placement_config config
;
571 struct team_placement_state state
;
574 log_verbose("Do team placement for %d players, using %s variant.",
575 team_placement_players_to_place
,
576 team_placement_name(wld
.map
.server
.team_placement
));
578 /* Initialize configuration. */
579 config
.flexible_startpos_num
= startpos_list_size(flexible_list
);
580 config
.usable_startpos_num
= config
.flexible_startpos_num
;
581 if (config
.flexible_startpos_num
< team_placement_players_to_place
) {
582 config
.usable_startpos_num
+= startpos_list_size(impossible_list
);
584 config
.total_startpos_num
= (config
.usable_startpos_num
585 + player_count() - players_to_place
);
586 config
.startpos
= fc_malloc(sizeof(*config
.startpos
)
587 * config
.total_startpos_num
);
589 startpos_list_iterate(flexible_list
, plink
, psp
) {
590 config
.startpos
[i
++] = startpos_tile(psp
);
591 } startpos_list_iterate_end
;
592 fc_assert(i
== config
.flexible_startpos_num
);
593 if (i
< config
.usable_startpos_num
) {
594 startpos_list_iterate(impossible_list
, plink
, psp
) {
595 config
.startpos
[i
++] = startpos_tile(psp
);
596 } startpos_list_iterate_end
;
598 fc_assert(i
== config
.usable_startpos_num
);
599 while (i
< config
.total_startpos_num
) {
600 config
.startpos
[i
++] = NULL
;
602 fc_assert(i
== config
.total_startpos_num
);
604 /* Initialize state. */
605 state
.startpos
= fc_malloc(sizeof(*state
.startpos
)
606 * config
.total_startpos_num
);
609 j
= config
.usable_startpos_num
;
610 teams_iterate(pteam
) {
611 members
= team_members(pteam
);
612 if (player_list_size(members
) <= 1) {
613 /* Single player teams, doesn't count for team placement. */
616 t
= team_number(pteam
);
617 player_list_iterate(members
, pplayer
) {
618 struct tile
*ptile
= player_startpos
[player_index(pplayer
)];
621 state
.startpos
[i
++] = t
;
623 state
.startpos
[j
] = t
;
624 config
.startpos
[j
] = ptile
;
627 } player_list_iterate_end
;
629 while (i
< config
.usable_startpos_num
) {
630 state
.startpos
[i
++] = -1;
632 fc_assert(i
== config
.usable_startpos_num
);
633 while (j
< config
.total_startpos_num
) {
634 state
.startpos
[j
++] = -1;
636 fc_assert(j
== config
.total_startpos_num
);
638 /* Look for best team placement. */
639 do_team_placement(&config
, &state
, team_placement_players_to_place
);
642 for (i
= 0; i
< config
.usable_startpos_num
; i
++) {
643 t
= state
.startpos
[i
];
645 const struct team
*pteam
= team_by_number(t
);
646 int candidate_index
= -1;
647 int candidate_num
= 0;
649 log_verbose("Start position (%d, %d) assigned to team %d (%s)",
650 TILE_XY(config
.startpos
[i
]),
651 t
, team_rule_name(pteam
));
653 player_list_iterate(team_members(pteam
), member
) {
654 if (player_startpos
[player_index(member
)] == NULL
655 && fc_rand(++candidate_num
) == 0) {
656 candidate_index
= player_index(member
);
658 } player_list_iterate_end
;
659 fc_assert(candidate_index
>= 0);
660 player_startpos
[candidate_index
] = config
.startpos
[i
];
661 team_placement_players_to_place
--;
665 fc_assert(team_placement_players_to_place
== 0);
668 if (players_to_place
> 0) {
669 /* We need to remove used startpos from the lists. */
671 startpos_list_iterate(flexible_list
, plink
, psp
) {
672 fc_assert(config
.startpos
[i
] == startpos_tile(psp
));
673 if (state
.startpos
[i
] != -1) {
674 startpos_list_erase(flexible_list
, plink
);
677 } startpos_list_iterate_end
;
678 fc_assert(i
== config
.flexible_startpos_num
);
679 if (i
< config
.usable_startpos_num
) {
680 startpos_list_iterate(impossible_list
, plink
, psp
) {
681 fc_assert(config
.startpos
[i
] == startpos_tile(psp
));
682 if (state
.startpos
[i
] != -1) {
683 startpos_list_erase(impossible_list
, plink
);
686 } startpos_list_iterate_end
;
688 fc_assert(i
== config
.usable_startpos_num
);
691 free(config
.startpos
);
695 /* Now assign unrestricted start positions to any remaining players. */
696 if (0 < players_to_place
&& 0 < startpos_list_size(flexible_list
)) {
699 log_verbose("Assigning unrestricted start positions.");
701 startpos_list_shuffle(flexible_list
); /* Randomize. */
702 players_iterate(pplayer
) {
703 if (NULL
!= player_startpos
[player_index(pplayer
)]) {
704 /* Already assigned. */
708 ptile
= startpos_tile(startpos_list_front(flexible_list
));
709 player_startpos
[player_index(pplayer
)] = ptile
;
711 startpos_list_pop_front(flexible_list
);
712 log_verbose("Start position (%d, %d) assigned randomly "
713 "to player %s (%s).", TILE_XY(ptile
), player_name(pplayer
),
714 nation_rule_name(nation_of_player(pplayer
)));
715 if (0 == startpos_list_size(flexible_list
)) {
718 } players_iterate_end
;
721 if (0 < players_to_place
&& 0 < startpos_list_size(impossible_list
)) {
722 /* We still have players to place, and we have some restricted start
723 * positions whose nation requirements can't be satisfied given existing
724 * assignments. Fall back to making assignments ignoring the positions'
725 * nation requirements. */
729 log_verbose("Ignoring nation restrictions on remaining start positions.");
731 startpos_list_shuffle(impossible_list
); /* Randomize. */
732 players_iterate(pplayer
) {
733 if (NULL
!= player_startpos
[player_index(pplayer
)]) {
734 /* Already assigned. */
738 ptile
= startpos_tile(startpos_list_front(impossible_list
));
739 player_startpos
[player_index(pplayer
)] = ptile
;
741 startpos_list_pop_front(impossible_list
);
742 log_verbose("Start position (%d, %d) assigned to mismatched "
743 "player %s (%s).", TILE_XY(ptile
), player_name(pplayer
),
744 nation_rule_name(nation_of_player(pplayer
)));
745 if (0 == startpos_list_size(impossible_list
)) {
748 } players_iterate_end
;
751 fc_assert(0 == players_to_place
);
753 startpos_list_destroy(impossible_list
);
754 startpos_list_destroy(targeted_list
);
755 startpos_list_destroy(flexible_list
);
757 sulen
= strlen(game
.server
.start_units
);
759 /* Loop over all players, creating their initial units... */
760 players_iterate(pplayer
) {
763 /* We have to initialise the advisor and ai here as we could make contact
764 * to other nations at this point. */
765 adv_data_phase_init(pplayer
, FALSE
);
766 CALL_PLR_AI_FUNC(phase_begin
, pplayer
, pplayer
, FALSE
);
768 ptile
= player_startpos
[player_index(pplayer
)];
770 fc_assert_action(NULL
!= ptile
, continue);
772 /* Place first city */
773 if (game
.server
.start_city
) {
774 create_city(pplayer
, ptile
, city_name_suggestion(pplayer
, ptile
),
779 /* Place the first unit. */
780 if (place_starting_unit(ptile
, pplayer
,
781 game
.server
.start_units
[0]) != NULL
) {
782 placed_units
[player_index(pplayer
)] = 1;
784 placed_units
[player_index(pplayer
)] = 0;
787 placed_units
[player_index(pplayer
)] = 0;
789 } players_iterate_end
;
791 /* Place all other units. */
792 players_iterate(pplayer
) {
794 struct tile
*const ptile
= player_startpos
[player_index(pplayer
)];
795 struct nation_type
*nation
= nation_of_player(pplayer
);
797 fc_assert_action(NULL
!= ptile
, continue);
799 /* Place global start units */
800 for (i
= 1; i
< sulen
; i
++) {
801 struct tile
*rand_tile
= find_dispersed_position(pplayer
, ptile
);
803 /* Create the unit of an appropriate type. */
804 if (place_starting_unit(rand_tile
, pplayer
,
805 game
.server
.start_units
[i
]) != NULL
) {
806 placed_units
[player_index(pplayer
)]++;
810 /* Place nation specific start units (not role based!) */
812 while (NULL
!= nation
->init_units
[i
] && MAX_NUM_UNIT_LIST
> i
) {
813 struct tile
*rand_tile
= find_dispersed_position(pplayer
, ptile
);
815 create_unit(pplayer
, rand_tile
, nation
->init_units
[i
], FALSE
, 0, 0);
816 placed_units
[player_index(pplayer
)]++;
819 } players_iterate_end
;
821 players_iterate(pplayer
) {
822 /* Close the active phase for advisor and ai for all players; it was
823 * opened in the first loop above. */
824 adv_data_phase_done(pplayer
);
825 CALL_PLR_AI_FUNC(phase_finished
, pplayer
, pplayer
);
827 fc_assert_msg(game
.server
.start_city
|| 0 < placed_units
[player_index(pplayer
)],
828 _("No units placed for %s!"), player_name(pplayer
));
829 } players_iterate_end
;
834 /**************************************************************************
835 Tell clients the year, and also update turn_done and nturns_idle fields
837 **************************************************************************/
838 void send_year_to_clients(void)
840 struct packet_new_year apacket
;
842 players_iterate(pplayer
) {
843 pplayer
->nturns_idle
++;
844 } players_iterate_end
;
846 apacket
.year
= game
.info
.year
;
847 apacket
.fragments
= game
.info
.fragment_count
;
848 apacket
.turn
= game
.info
.turn
;
849 lsend_packet_new_year(game
.est_connections
, &apacket
);
851 /* Hmm, clients could add this themselves based on above packet? */
852 notify_conn(game
.est_connections
, NULL
, E_NEXT_YEAR
, ftc_any
,
853 _("Year: %s"), calendar_text());
856 /**************************************************************************
857 Send game_info packet; some server options and various stuff...
858 dest == NULL means game.est_connections
860 It may be sent at any time. It MUST be sent before any player info,
861 as it contains the number of players. To avoid inconsistency, it
862 SHOULD be sent after rulesets and any other server settings.
863 **************************************************************************/
864 void send_game_info(struct conn_list
*dest
)
866 struct packet_timeout_info tinfo
;
869 dest
= game
.est_connections
;
874 /* the following values are computed every
875 time a packet_game_info packet is created */
877 /* Sometimes this function is called before the phase_timer is
878 * initialized. In that case we want to send the dummy value. */
879 if (current_turn_timeout() > 0 && game
.server
.phase_timer
) {
880 /* Whenever the client sees this packet, it starts a new timer at 0;
881 * but the server's timer is only ever reset at the start of a phase
882 * (and game.tinfo.seconds_to_phasedone is relative to this).
883 * Account for the difference. */
884 tinfo
.seconds_to_phasedone
= game
.tinfo
.seconds_to_phasedone
885 - timer_read_seconds(game
.server
.phase_timer
);
887 /* unused but at least initialized */
888 tinfo
.seconds_to_phasedone
= -1.0;
891 conn_list_iterate(dest
, pconn
) {
892 /* Timeout info is separate from other packets since it has to
893 * be sent always (it's not 'is-info') while the others are 'is-info'
894 * Calendar info has been split from Game info packet to make packet
895 * size more tolerable when json protocol is in use. */
896 send_packet_game_info(pconn
, &(game
.info
));
897 send_packet_calendar_info(pconn
, &(game
.calendar
));
898 send_packet_timeout_info(pconn
, &tinfo
);
900 conn_list_iterate_end
;
903 /**************************************************************************
904 Send current scenario info. dest NULL causes send to everyone
905 **************************************************************************/
906 void send_scenario_info(struct conn_list
*dest
)
909 dest
= game
.est_connections
;
912 conn_list_iterate(dest
, pconn
) {
913 send_packet_scenario_info(pconn
, &(game
.scenario
));
914 } conn_list_iterate_end
;
917 /**************************************************************************
918 Send description of the current scenario. dest NULL causes send to everyone
919 **************************************************************************/
920 void send_scenario_description(struct conn_list
*dest
)
923 dest
= game
.est_connections
;
926 conn_list_iterate(dest
, pconn
) {
927 send_packet_scenario_description(pconn
, &(game
.scenario_desc
));
928 } conn_list_iterate_end
;
931 /**************************************************************************
932 adjusts game.info.timeout based on various server options
934 timeoutint: adjust game.info.timeout every timeoutint turns
935 timeoutinc: adjust game.info.timeout by adding timeoutinc to it.
936 timeoutintinc: every time we adjust game.info.timeout, we add timeoutintinc
938 timeoutincmult: every time we adjust game.info.timeout, we multiply timeoutinc
940 **************************************************************************/
941 int update_timeout(void)
943 /* if there's no timer or we're doing autogame, do nothing */
944 if (game
.info
.timeout
< 1 || game
.server
.timeoutint
== 0) {
945 return game
.info
.timeout
;
948 if (game
.server
.timeoutcounter
>= game
.server
.timeoutint
) {
949 game
.info
.timeout
+= game
.server
.timeoutinc
;
950 game
.server
.timeoutinc
*= game
.server
.timeoutincmult
;
952 game
.server
.timeoutcounter
= 1;
953 game
.server
.timeoutint
+= game
.server
.timeoutintinc
;
955 if (game
.info
.timeout
> GAME_MAX_TIMEOUT
) {
956 notify_conn(game
.est_connections
, NULL
, E_SETTING
, ftc_server
,
957 _("The turn timeout has exceeded its maximum value, "
958 "fixing at its maximum."));
959 log_debug("game.info.timeout exceeded maximum value");
960 game
.info
.timeout
= GAME_MAX_TIMEOUT
;
961 game
.server
.timeoutint
= 0;
962 game
.server
.timeoutinc
= 0;
963 } else if (game
.info
.timeout
< 0) {
964 notify_conn(game
.est_connections
, NULL
, E_SETTING
, ftc_server
,
965 _("The turn timeout is smaller than zero, "
967 log_debug("game.info.timeout less than zero");
968 game
.info
.timeout
= 0;
971 game
.server
.timeoutcounter
++;
974 log_debug("timeout=%d, inc=%d incmult=%d\n "
975 "int=%d, intinc=%d, turns till next=%d",
976 game
.info
.timeout
, game
.server
.timeoutinc
,
977 game
.server
.timeoutincmult
, game
.server
.timeoutint
,
978 game
.server
.timeoutintinc
,
979 game
.server
.timeoutint
- game
.server
.timeoutcounter
);
981 return game
.info
.timeout
;
984 /**************************************************************************
985 adjusts game.seconds_to_turn_done when enemy moves a unit, we see it and
986 the remaining timeout is smaller than the timeoutaddenemymove option.
988 It's possible to use a similar function to do that per-player. In
989 theory there should be a separate timeout for each player and the
990 added time should only go onto the victim's timer.
991 **************************************************************************/
992 void increase_timeout_because_unit_moved(void)
994 if (current_turn_timeout() > 0 && game
.server
.timeoutaddenemymove
> 0) {
995 double maxsec
= (timer_read_seconds(game
.server
.phase_timer
)
996 + (double) game
.server
.timeoutaddenemymove
);
998 if (maxsec
> game
.tinfo
.seconds_to_phasedone
) {
999 game
.tinfo
.seconds_to_phasedone
= maxsec
;
1000 send_game_info(NULL
);
1005 /**************************************************************************
1006 generate challenge filename for this connection, cannot fail.
1007 **************************************************************************/
1008 static void gen_challenge_filename(struct connection
*pc
)
1012 /**************************************************************************
1013 get challenge filename for this connection.
1014 **************************************************************************/
1015 static const char *get_challenge_filename(struct connection
*pc
)
1017 static char filename
[MAX_LEN_PATH
];
1019 fc_snprintf(filename
, sizeof(filename
), "%s_%d_%d",
1020 CHALLENGE_ROOT
, srvarg
.port
, pc
->id
);
1025 /**************************************************************************
1026 get challenge full filename for this connection.
1027 **************************************************************************/
1028 static const char *get_challenge_fullname(struct connection
*pc
)
1030 static char fullname
[MAX_LEN_PATH
];
1031 const char *sdir
= freeciv_storage_dir();
1038 cname
= get_challenge_filename(pc
);
1040 if (cname
== NULL
) {
1044 fc_snprintf(fullname
, sizeof(fullname
), "%s" DIR_SEPARATOR
"%s", sdir
, cname
);
1049 /**************************************************************************
1050 find a file that we can write too, and return it's name.
1051 **************************************************************************/
1052 const char *new_challenge_filename(struct connection
*pc
)
1054 gen_challenge_filename(pc
);
1055 return get_challenge_filename(pc
);
1059 /**************************************************************************
1060 Call this on a connection with HACK access to send it a set of ruleset
1061 choices. Probably this should be called immediately when granting
1062 HACK access to a connection.
1063 **************************************************************************/
1064 static void send_ruleset_choices(struct connection
*pc
)
1066 struct packet_ruleset_choices packet
;
1069 if (ruleset_choices
== NULL
) {
1070 /* This is only read once per server invocation. Add a new ruleset
1071 * and you have to restart the server. */
1072 ruleset_choices
= fileinfolist(get_data_dirs(), RULESET_SUFFIX
);
1075 packet
.ruleset_count
= MIN(MAX_NUM_RULESETS
, strvec_size(ruleset_choices
));
1076 for (i
= 0; i
< packet
.ruleset_count
; i
++) {
1077 sz_strlcpy(packet
.rulesets
[i
], strvec_get(ruleset_choices
, i
));
1080 send_packet_ruleset_choices(pc
, &packet
);
1083 /**************************************************************************
1084 Free list of ruleset choices.
1085 **************************************************************************/
1086 void ruleset_choices_free(void)
1088 if (ruleset_choices
!= NULL
) {
1089 strvec_destroy(ruleset_choices
);
1090 ruleset_choices
= NULL
;
1094 /****************************************************************************
1095 Opens a file specified by the packet and compares the packet values with
1096 the file values. Sends an answer to the client once it's done.
1097 ****************************************************************************/
1098 void handle_single_want_hack_req(struct connection
*pc
,
1099 const struct packet_single_want_hack_req
*
1102 struct section_file
*secfile
;
1103 const char *token
= NULL
;
1104 bool you_have_hack
= FALSE
;
1106 if ((secfile
= secfile_load(get_challenge_fullname(pc
), FALSE
))) {
1107 token
= secfile_lookup_str(secfile
, "challenge.token");
1108 you_have_hack
= (token
&& strcmp(token
, packet
->token
) == 0);
1109 secfile_destroy(secfile
);
1111 log_debug("Error reading '%s':\n%s", get_challenge_fullname(pc
),
1116 log_debug("Failed to read authentication token");
1119 if (you_have_hack
) {
1120 conn_set_access(pc
, ALLOW_HACK
, TRUE
);
1123 dsend_packet_single_want_hack_reply(pc
, you_have_hack
);
1125 send_ruleset_choices(pc
);
1126 send_conn_info(pc
->self
, NULL
);