File: movement.c

package info (click to toggle)
freeciv 2.6.2-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye, sid
  • size: 212,508 kB
  • sloc: ansic: 443,831; cpp: 29,541; sh: 7,982; makefile: 7,886; python: 1,933; xml: 945
file content (837 lines) | stat: -rw-r--r-- 30,307 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
/****************************************************************************
 Freeciv - Copyright (C) 2004 - The Freeciv Team
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
****************************************************************************/

#ifdef HAVE_CONFIG_H
#include <fc_config.h>
#endif

/* utility */
#include "bitvector.h"
#include "fcintl.h"
#include "log.h"
#include "shared.h"
#include "support.h"

/* common */
#include "astring.h"
#include "base.h"
#include "effects.h"
#include "fc_types.h"
#include "game.h"
#include "map.h"
#include "road.h"
#include "unit.h"
#include "unitlist.h"
#include "unittype.h"
#include "terrain.h"

#include "movement.h"

/****************************************************************************
  This function calculates the move rate of the unit, taking into account
  the penalty for reduced hitpoints, the active effects, and any veteran
  bonuses.

  'utype' and 'pplayer' must be set. 'ptile' can be NULL.
****************************************************************************/
int utype_move_rate(const struct unit_type *utype, const struct tile *ptile,
                    const struct player *pplayer, int veteran_level,
                    int hitpoints)
{
  const struct unit_class *uclass;
  const struct veteran_level *vlevel;
  int base_move_rate, move_rate;

  fc_assert_ret_val(NULL != utype, 0);
  fc_assert_ret_val(NULL != pplayer, 0);
  vlevel = utype_veteran_level(utype, veteran_level);
  fc_assert_ret_val(NULL != vlevel, 0);
  uclass = utype_class(utype);

  base_move_rate = utype->move_rate + vlevel->move_bonus;
  move_rate = base_move_rate;

  if (uclass_has_flag(uclass, UCF_DAMAGE_SLOWS)) {
    /* Scale the MP based on how many HP the unit has. */
    move_rate = (move_rate * hitpoints) / utype->hp;
  }

  /* Add on effects bonus (Magellan's Expedition, Lighthouse,
   * Nuclear Power). */
  move_rate += (get_unittype_bonus(pplayer, ptile, utype, EFT_MOVE_BONUS)
                * SINGLE_MOVE);

  /* Don't let the move_rate be less than min_speed unless the base_move_rate is
   * also less than min_speed. */
  if (move_rate < uclass->min_speed) {
    move_rate = MIN(uclass->min_speed, base_move_rate);
  }

  return move_rate;
}

/****************************************************************************
  This function calculates the move rate of the unit. See utype_move_rate()
  for further details.
****************************************************************************/
int unit_move_rate(const struct unit *punit)
{
  fc_assert_ret_val(NULL != punit, 0);

  return utype_move_rate(unit_type_get(punit), unit_tile(punit),
                         unit_owner(punit), punit->veteran, punit->hp);
}

/****************************************************************************
  This function calculates the movement cost to unknown tiles. The base
  value is equal to the highest movement cost the unit can encounter. If
  the unit cannot enter all terrains, a malus is applied.
  The returned value is usually cached into utype->unknown_move_cost and
  used in the path-finding module.
****************************************************************************/
int utype_unknown_move_cost(const struct unit_type *utype)
{
  const struct unit_class *uclass = utype_class(utype);
  int move_cost;

  if (!uclass_has_flag(uclass, UCF_TERRAIN_SPEED)) {
    /* Unit is not subject to terrain movement costs. */
    move_cost = SINGLE_MOVE;
  } else if (utype_has_flag(utype, UTYF_IGTER)) {
    /* All terrain movement costs are equal! */
    move_cost = MOVE_COST_IGTER;
  } else {
    /* Unit is subject to terrain movement costs. */
    move_cost = 1; /* Arbitrary minimum. */
    terrain_type_iterate(pterrain) {
      if (is_native_to_class(uclass, pterrain, NULL)
          && pterrain->movement_cost > move_cost) {
        /* Exact movement cost matters only if we can enter
         * the tile. */
        move_cost = pterrain->movement_cost;
      }
    } terrain_type_iterate_end;
    move_cost *= SINGLE_MOVE; /* Real value. */
  }

  /* Let's see if we can cross over all terrain types, else apply a malus.
   * We want units that may encounter unsuitable terrain explore less.
   * N.B.: We don't take in account terrain no unit can enter here. */
  terrain_type_iterate(pterrain) {
    if (BV_ISSET_ANY(pterrain->native_to)
        && !is_native_to_class(uclass, pterrain, NULL)) {
      /* Units that may encounter unsuitable terrain explore less. */
      move_cost *= 2;
      break;
    }
  } terrain_type_iterate_end;

  return move_cost;
}


/****************************************************************************
  Return TRUE iff the unit can be a defender at its current location.  This
  should be checked when looking for a defender - not all units on the
  tile are valid defenders.
****************************************************************************/
bool unit_can_defend_here(const struct unit *punit)
{
  struct unit *ptrans = unit_transport_get(punit);

  /* Do not just check if unit is transported.
   * Even transported units may step out from transport to fight,
   * if this is their native terrain. */
  return (can_unit_exist_at_tile(punit, unit_tile(punit))
          && (ptrans == NULL || can_unit_unload(punit, ptrans)));
}

/****************************************************************************
 This unit can attack non-native tiles (eg. Ships ability to
 shore bombardment)
****************************************************************************/
bool can_attack_non_native(const struct unit_type *utype)
{
  return uclass_has_flag(utype_class(utype), UCF_ATTACK_NON_NATIVE)
         && utype->attack_strength > 0
         && !utype_has_flag(utype, UTYF_ONLY_NATIVE_ATTACK);
}

/****************************************************************************
 This unit can attack from non-native tiles (Marines can attack from
 transport, ships from harbour cities)
****************************************************************************/
bool can_attack_from_non_native(const struct unit_type *utype)
{
  return uclass_has_flag(utype_class(utype), UCF_ATT_FROM_NON_NATIVE)
         || utype_has_flag(utype, UTYF_MARINES);
}

/****************************************************************************
  Check for a city channel.
****************************************************************************/
bool is_city_channel_tile(const struct unit_class *punitclass,
                          const struct tile *ptile,
                          const struct tile *pexclude)
{
  struct dbv tile_processed;
  struct tile_list *process_queue = tile_list_new();
  bool found = FALSE;

  dbv_init(&tile_processed, map_num_tiles());
  for (;;) {
    dbv_set(&tile_processed, tile_index(ptile));
    adjc_iterate(ptile, piter) {
      if (dbv_isset(&tile_processed, tile_index(piter))) {
        continue;
      } else if (piter != pexclude
                 && is_native_to_class(punitclass, tile_terrain(piter),
                                       tile_extras(piter))) {
        found = TRUE;
        break;
      } else if (piter != pexclude
                 && NULL != tile_city(piter)) {
        tile_list_append(process_queue, piter);
      } else {
        dbv_set(&tile_processed, tile_index(piter));
      }
    } adjc_iterate_end;

    if (found || 0 == tile_list_size(process_queue)) {
      break; /* No more tile to process. */
    } else {
      ptile = tile_list_front(process_queue);
      tile_list_pop_front(process_queue);
    }
  }

  dbv_free(&tile_processed);
  tile_list_destroy(process_queue);
  return found;
}

/****************************************************************************
  Return TRUE iff a unit of the given unit type can "exist" at this location.
  This means it can physically be present on the tile (without the use of a
  transporter). See also can_unit_survive_at_tile.
****************************************************************************/
bool can_exist_at_tile(const struct unit_type *utype,
                       const struct tile *ptile)
{
  /* Cities are safe havens except for units in the middle of non-native
   * terrain. This can happen if adjacent terrain is changed after unit
   * arrived to city. */
  if (NULL != tile_city(ptile)
      && (uclass_has_flag(utype_class(utype), UCF_BUILD_ANYWHERE)
          || is_native_near_tile(utype_class(utype), ptile)
          || (1 == game.info.citymindist
              && is_city_channel_tile(utype_class(utype), ptile, NULL)))) {
    return TRUE;
  }

  /* A trireme unit cannot exist in an ocean tile without access to land. */
  if (utype_has_flag(utype, UTYF_TRIREME) && !is_safe_ocean(ptile)) {
    return FALSE;
  }

  return is_native_tile(utype, ptile);
}

/****************************************************************************
  Return TRUE iff the unit can "exist" at this location.  This means it can
  physically be present on the tile (without the use of a transporter).  See
  also can_unit_survive_at_tile.
****************************************************************************/
bool can_unit_exist_at_tile(const struct unit *punit,
                            const struct tile *ptile)
{
  return can_exist_at_tile(unit_type_get(punit), ptile);
}

/****************************************************************************
  This tile is native to unit.

  See is_native_to_class()
****************************************************************************/
bool is_native_tile(const struct unit_type *punittype,
                    const struct tile *ptile)
{
  return is_native_to_class(utype_class(punittype), tile_terrain(ptile),
                            tile_extras(ptile));
}

/****************************************************************************
  This terrain is native to unit class. Units that require fuel dont survive
  even on native terrain.
****************************************************************************/
bool is_native_to_class(const struct unit_class *punitclass,
                        const struct terrain *pterrain,
                        const bv_extras *extras)
{
  if (!pterrain) {
    /* Unknown is considered native terrain */
    return TRUE;
  }

  if (BV_ISSET(pterrain->native_to, uclass_index(punitclass))) {
    return TRUE;
  }

  if (extras != NULL) {
    extra_type_list_iterate(punitclass->cache.native_tile_extras, pextra) {
      if (BV_ISSET(*extras, extra_index(pextra))) {
        return TRUE;
      }
    } extra_type_list_iterate_end;
  }

  return FALSE;
}


/****************************************************************************
  Is the move under consideration a native move?
  Note that this function does not check for possible moves, only native
  moves, so that callers are responsible for checking for other sources of
  legal moves (e.g. cities, transports, etc.).
****************************************************************************/
bool is_native_move(const struct unit_class *punitclass,
                    const struct tile *src_tile,
                    const struct tile *dst_tile)
{
  const struct road_type *proad;

  if (is_native_to_class(punitclass, tile_terrain(dst_tile), NULL)) {
    /* We aren't using extras to make the destination native. */
    return TRUE;
  } else if (!is_native_tile_to_class(punitclass, src_tile)) {
    /* Disembarking or leaving port, so ignore road connectivity. */
    return TRUE;
  } else if (is_native_to_class(punitclass, tile_terrain(src_tile), NULL)) {
    /* Native source terrain depends entirely on destination tile nativity. */
    return is_native_tile_to_class(punitclass, dst_tile);
  }

  /* Check for non-road native extras on the source tile. */
  extra_type_list_iterate(punitclass->cache.native_tile_extras, pextra) {
    if (tile_has_extra(src_tile, pextra)
        && !is_extra_caused_by(pextra, EC_ROAD)
        && is_native_tile_to_class(punitclass, dst_tile)) {
      /* If there is one, and the destination is native, the move is native. */
      return TRUE;
    }
  } extra_type_list_iterate_end;

  extra_type_list_iterate(punitclass->cache.native_tile_extras, pextra) {
    if (!tile_has_extra(dst_tile, pextra)) {
      continue;
    } else if (!is_extra_caused_by(pextra, EC_ROAD)) {
      /* The destination is native because of a non-road extra. */
      return TRUE;
    }

    proad = extra_road_get(pextra);

    if (road_has_flag(proad, RF_JUMP_TO)) {
      extra_type_list_iterate(punitclass->cache.native_tile_extras, jextra) {
        if (pextra != jextra
            && is_extra_caused_by(jextra, EC_ROAD)
            && tile_has_extra(src_tile, jextra)
            && road_has_flag(jextra->data.road, RF_JUMP_FROM)) {
          return TRUE;
        }
      } extra_type_list_iterate_end;
    }

    extra_type_list_iterate(proad->integrators, iextra) {
      if (!tile_has_extra(src_tile, iextra)) {
        continue;
      }
      if (ALL_DIRECTIONS_CARDINAL()) {
        /* move_mode does not matter as all of them accept cardinal move */
        return TRUE;
      }
      switch (extra_road_get(iextra)->move_mode) {
      case RMM_FAST_ALWAYS:
        /* Road connects source and destination, so we're fine. */
        return TRUE;
      case RMM_CARDINAL:
        /* Road connects source and destination if cardinal move. */
        if (is_move_cardinal(src_tile, dst_tile)) {
          return TRUE;
        }
        break;
      case RMM_RELAXED:
        if (is_move_cardinal(src_tile, dst_tile)) {
          /* Cardinal moves have no between tiles, so connected. */
          return TRUE;
        }
        cardinal_between_iterate(src_tile, dst_tile, between) {
          if (tile_has_extra(between, iextra)
              || (pextra != iextra && tile_has_extra(between, pextra))) {
            /* We have a link for the connection.
             * 'pextra != iextra' is there just to avoid tile_has_extra()
             * in by far more common case that 'pextra == iextra' */
            return TRUE;
          }
        } cardinal_between_iterate_end;
        break;
      }
    } extra_type_list_iterate_end;
  } extra_type_list_iterate_end;

  return FALSE;
}

/****************************************************************************
  Is there native tile adjacent to given tile
****************************************************************************/
bool is_native_near_tile(const struct unit_class *uclass, const struct tile *ptile)
{
  if (is_native_tile_to_class(uclass, ptile)) {
    return TRUE;
  }

  adjc_iterate(ptile, ptile2) {
    if (is_native_tile_to_class(uclass, ptile2)) {
      return TRUE;
    }
  } adjc_iterate_end;

  return FALSE;
}

/****************************************************************************
  Return TRUE iff the unit can "survive" at this location.  This means it can
  not only be physically present at the tile but will be able to survive
  indefinitely on its own (without a transporter).  Units that require fuel
  or have a danger of drowning are examples of non-survivable units.  See
  also can_unit_exist_at_tile.

  (This function could be renamed as unit_wants_transporter.)
****************************************************************************/
bool can_unit_survive_at_tile(const struct unit *punit,
                              const struct tile *ptile)
{
  if (!can_unit_exist_at_tile(punit, ptile)) {
    return FALSE;
  }

  if (tile_city(ptile)) {
    return TRUE;
  }

  if (tile_has_refuel_extra(ptile, unit_type_get(punit))) {
    /* Unit can always survive at refueling base */
    return TRUE;
  }

  if (utype_fuel(unit_type_get(punit))) {
    /* Unit requires fuel and this is not refueling tile */
    return FALSE;
  }

  if (is_losing_hp(punit)) {
    /* Unit is losing HP over time in this tile (no city or native base) */
    return FALSE;
  }

  return TRUE;
}


/****************************************************************************
  Returns whether the unit is allowed (by ZOC) to move from src_tile
  to dest_tile (assumed adjacent).

  You CAN move if:
    1. You have units there already
    2. Your unit isn't a ground unit
    3. Your unit ignores ZOC (diplomat, freight, etc.)
    4. You're moving from or to a city
    5. You're moving from an ocean square (from a boat)
    6. The spot you're moving from or to is in your ZOC
****************************************************************************/
bool can_step_taken_wrt_to_zoc(const struct unit_type *punittype,
                               const struct player *unit_owner,
                               const struct tile *src_tile,
                               const struct tile *dst_tile)
{
  if (unit_type_really_ignores_zoc(punittype)) {
    return TRUE;
  }
  if (is_allied_unit_tile(dst_tile, unit_owner)) {
    return TRUE;
  }
  if (tile_city(src_tile) || tile_city(dst_tile)) {
    return TRUE;
  }
  if (terrain_has_flag(tile_terrain(src_tile), TER_NO_ZOC)
      || terrain_has_flag(tile_terrain(dst_tile), TER_NO_ZOC)) {
    return TRUE;
  }

  return (is_my_zoc(unit_owner, src_tile)
	  || is_my_zoc(unit_owner, dst_tile));
}


/****************************************************************************
  See can_step_take_wrt_to_zoc().  This function is exactly the same but
  it takes a unit instead of a unittype and player.
****************************************************************************/
static bool zoc_ok_move_gen(const struct unit *punit,
                            const struct tile *src_tile,
                            const struct tile *dst_tile)
{
  return can_step_taken_wrt_to_zoc(unit_type_get(punit), unit_owner(punit),
				   src_tile, dst_tile);
}


/****************************************************************************
  Returns whether the unit can safely move from its current position to
  the adjacent dst_tile.  This function checks only ZOC.

  See can_step_taken_wrt_to_zoc().
****************************************************************************/
bool zoc_ok_move(const struct unit *punit, const struct tile *dst_tile)
{
  return zoc_ok_move_gen(punit, unit_tile(punit), dst_tile);
}


/****************************************************************************
  Returns whether the unit can move from its current tile to the destination
  tile.

  See unit_move_to_tile_test().
****************************************************************************/
bool unit_can_move_to_tile(const struct unit *punit,
                           const struct tile *dst_tile,
                           bool igzoc)
{
  return (MR_OK == unit_move_to_tile_test(punit,
                                          punit->activity, unit_tile(punit),
                                          dst_tile, igzoc, NULL));
}

/**************************************************************************
  Returns whether the unit can move from its current tile to the
  destination tile.  An enumerated value is returned indication the error
  or success status.

  The unit can move if:
    1) The unit is idle or on server goto.
    2) The target location is next to the unit.
    3) There are no non-allied units on the target tile.
    4) Animals cannot move out from home terrains
    5) Unit can move to a tile where it can't survive on its own if there
       is free transport capacity.
    6) Some units cannot take over a city.
    7) Only units permitted to attack from non-native tiles may do so.
    8) There are no peaceful but non allied units on the target tile.
    9) There is not a peaceful but non allied city on the target tile.
   10) There is no non-allied unit blocking (zoc) [or igzoc is true].
   11) Triremes cannot move out of sight from land.
   12) It is not the territory of a player we are at peace with.
   13) The unit is unable to disembark from current transporter.
   14) The unit is making a non-native move (e.g. lack of road)
**************************************************************************/
enum unit_move_result
unit_move_to_tile_test(const struct unit *punit,
                       enum unit_activity activity,
                       const struct tile *src_tile,
                       const struct tile *dst_tile, bool igzoc,
                       struct unit *embark_to)
{
  bool zoc;
  struct city *pcity;
  const struct unit_type *punittype = unit_type_get(punit);
  const struct player *puowner = unit_owner(punit);

  /* 1) */
  if (activity != ACTIVITY_IDLE
      && activity != ACTIVITY_GOTO) {
    /* For other activities the unit must be stationary. */
    return MR_BAD_ACTIVITY;
  }

  /* 2) */
  if (!is_tiles_adjacent(src_tile, dst_tile)) {
    /* Of course you can only move to adjacent positions. */
    return MR_BAD_DESTINATION;
  }

  /* 3) */
  if (is_non_allied_unit_tile(dst_tile, puowner)) {
    /* You can't move onto a tile with non-allied units on it (try
     * attacking instead). */
    return MR_DESTINATION_OCCUPIED_BY_NON_ALLIED_UNIT;
  }

  /* 4) */
  if (puowner->ai_common.barbarian_type == ANIMAL_BARBARIAN
      && dst_tile->terrain->animal != punittype) {
    return MR_ANIMAL_DISALLOWED;
  }

  /* 5) */
  if (embark_to != NULL) {
    if (!could_unit_load(punit, embark_to)) {
      return MR_NO_TRANSPORTER_CAPACITY;
    }
  } else if (!(can_exist_at_tile(punittype, dst_tile)
               || unit_could_load_at(punit, dst_tile))) {
    return MR_NO_TRANSPORTER_CAPACITY;
  }

  pcity = is_enemy_city_tile(dst_tile, puowner);
  if (NULL != pcity) {
    /* 6) */
    if (!unit_can_take_over(punit)) {
      return MR_BAD_TYPE_FOR_CITY_TAKE_OVER;
    } else {
      /* No point checking for being able to take over from non-native
       * for units that can't take over a city anyway. */

      /* 7) */
      if (!can_exist_at_tile(punittype, src_tile)
          && !can_attack_from_non_native(punittype)) {
        /* Don't use is_native_tile() because any unit in an
         * adjacent city may conquer, regardless of flags. */
        return MR_BAD_TYPE_FOR_CITY_TAKE_OVER_FROM_NON_NATIVE;
      }
    }
  }

  /* 8) */
  if (is_non_attack_unit_tile(dst_tile, puowner)) {
    /* You can't move into a non-allied tile.
     *
     * FIXME: this should never happen since it should be caught by check
     * #3. */
    return MR_NO_WAR;
  }

  /* 9) */
  pcity = tile_city(dst_tile);
  if (pcity && pplayers_non_attack(city_owner(pcity), puowner)) {
    /* You can't move into an empty city of a civilization you're at
     * peace with - you must first either declare war or make alliance. */
    return MR_NO_WAR;
  }

  /* 10) */
  zoc = igzoc
    || can_step_taken_wrt_to_zoc(punittype, puowner, src_tile, dst_tile);
  if (!zoc) {
    /* The move is illegal because of zones of control. */
    return MR_ZOC;
  }

  /* 11) */
  if (utype_has_flag(punittype, UTYF_TRIREME) && !is_safe_ocean(dst_tile)) {
    return MR_TRIREME;
  }

  /* 12) */
  if (!utype_has_flag(punittype, UTYF_CIVILIAN)
      && !player_can_invade_tile(puowner, dst_tile)) {
    return MR_PEACE;
  }

  /* 13) */
  if (unit_transported(punit)
     && !can_unit_unload(punit, unit_transport_get(punit))) {
    return MR_CANNOT_DISEMBARK;
  }

  /* 14) */
  if (!(is_native_move(utype_class(punittype), src_tile, dst_tile)
        /* Allow non-native moves into cities or boarding transport. */
        || pcity
        || unit_could_load_at(punit, dst_tile))) {
    return MR_NON_NATIVE_MOVE;
  }

  return MR_OK;
}

/**************************************************************************
  Return true iff transporter has ability to transport transported.
**************************************************************************/
bool can_unit_transport(const struct unit *transporter,
                        const struct unit *transported)
{
  fc_assert_ret_val(transporter != NULL, FALSE);
  fc_assert_ret_val(transported != NULL, FALSE);

  return can_unit_type_transport(unit_type_get(transporter),
                                 unit_class_get(transported));
}

/**************************************************************************
  Return TRUE iff transporter type has ability to transport transported class.
**************************************************************************/
bool can_unit_type_transport(const struct unit_type *transporter,
                             const struct unit_class *transported)
{
  if (transporter->transport_capacity <= 0) {
    return FALSE;
  }

  return BV_ISSET(transporter->cargo, uclass_index(transported));
}

/****************************************************************************
  Return whether we can find a suitable transporter for given unit at
  current location. It needs to have free space. To find the best transporter,
  see transporter_for_unit().
****************************************************************************/
bool unit_can_load(const struct unit *punit)
{
  if (unit_transported(punit)) {
    /* In another transport already. Can it unload first? */
    if (!can_unit_unload(punit, punit->transporter)) {
      return FALSE;
    }
  }

  unit_list_iterate(unit_tile(punit)->units, ptransport) {
    /* could_unit_load() instead of can_unit_load() since latter
     * would check against unit already being transported, and we need
     * to support unload+load to a new transport. */
    if (ptransport != punit->transporter) {
      if (could_unit_load(punit, ptransport)) {
        return TRUE;
      }
    }
  } unit_list_iterate_end;

  return FALSE;
}

/****************************************************************************
  Return whether we could find a suitable transporter for given unit at
  'ptile'. It needs to have free space. To find the best transporter, see
  transporter_for_unit_at().
****************************************************************************/
bool unit_could_load_at(const struct unit *punit, const struct tile *ptile)
{
  unit_list_iterate(ptile->units, ptransport) {
    if (could_unit_load(punit, ptransport)) {
      return TRUE;
    }
  } unit_list_iterate_end;

  return FALSE;
}

static int move_points_denomlen = 0;

/****************************************************************************
  Call whenever terrain_control.move_fragments / SINGLE_MOVE changes.
****************************************************************************/
void init_move_fragments(void)
{
  char denomstr[10];
  /* String length of maximum denominator for fractional representation of
   * movement points, for padding of text representation */
  fc_snprintf(denomstr, sizeof(denomstr), "%d", SINGLE_MOVE);
  move_points_denomlen = strlen(denomstr);
}

/****************************************************************************
  Render positive movement points as text, including fractional movement
  points, scaled by SINGLE_MOVE. Returns a pointer to a static buffer.
  'reduce' is whether fractional movement points should be reduced to
    lowest terms (this might be confusing in some cases).
  'prefix' is a string put in front of all numeric output.
  'none' is the string to display in place of the integer part if no
    movement points (or NULL to just say 0).
  'align' controls whether this is for a fixed-width table, in which case
    padding spaces will be included to make all such strings line up when
    right-aligned.
****************************************************************************/
const char *move_points_text_full(int mp, bool reduce, const char *prefix,
                                  const char *none, bool align)
{
  static struct astring str = ASTRING_INIT;
  int pad1, pad2;

  if (align && SINGLE_MOVE > 1) {
    /* Align to worst-case denominator even if we might be reducing to
     * lowest terms, as other entries in a table might not reduce */
    pad1 = move_points_denomlen;      /* numerator or denominator */
    pad2 = move_points_denomlen*2+2;  /* everything right of integer part */
  } else {
    /* If no possible fractional part, alignment unneeded even if requested */
    pad1 = pad2 = 0;
  }
  if (!prefix) {
    prefix = "";
  }
  astr_clear(&str);
  if ((mp == 0 && none) || SINGLE_MOVE == 0) {
    /* No movement points, and we have a special representation to use */
    /* (Also used when SINGLE_MOVE==0, to avoid dividing by zero, which is
     * important for client before ruleset has been received. Doesn't much
     * matter what we print in this case.) */
    astr_add(&str, "%s%*s", none ? none : "", pad2, "");
  } else if ((mp % SINGLE_MOVE) == 0) {
    /* Integer move points */
    astr_add(&str, "%s%d%*s", prefix, mp / SINGLE_MOVE, pad2, "");
  } else {
    /* Fractional part */
    int cancel;

    fc_assert(SINGLE_MOVE > 1);
    if (reduce) {
      /* Reduce to lowest terms */
      int gcd = mp;
      /* Calculate greatest common divisor with Euclid's algorithm */
      int b = SINGLE_MOVE;

      while (b != 0) {
        int t = b;
        b = gcd % b;
        gcd = t;
      }
      cancel = gcd;
    } else {
      /* No cancellation */
      cancel = 1;
    }
    if (mp < SINGLE_MOVE) {
      /* Fractional move points */
      astr_add(&str, "%s%*d/%*d", prefix,
               pad1, (mp % SINGLE_MOVE) / cancel, pad1, SINGLE_MOVE / cancel);
    } else {
      /* Integer + fractional move points */
      astr_add(&str,
               "%s%d %*d/%*d", prefix, mp / SINGLE_MOVE,
               pad1, (mp % SINGLE_MOVE) / cancel, pad1, SINGLE_MOVE / cancel);
    }
  }
  return astr_str(&str);
}

/****************************************************************************
  Simple version of move_points_text_full() -- render positive movement
  points as text without any prefix or alignment.
****************************************************************************/
const char *move_points_text(int mp, bool reduce)
{
  return move_points_text_full(mp, reduce, NULL, NULL, FALSE);
}