void
CampaignPlanStrategic::PlaceGroup(CombatGroup* g)
{
    if (!g)
    return;

    Text        rgn  = g->GetRegion();
    CombatZone* zone = campaign->GetZone(rgn);

    // if we couldn't find anything suitable,
    // just pick a zone at random:
    if (!zone && g->IsMovable()) {
        int nzones = campaign->GetZones().size();
        int n      = RandomIndex() % nzones;
        zone       = campaign->GetZones().at(n);

        Text assigned_rgn;
        if (!campaign->GetZone(rgn)) {
            assigned_rgn = *zone->GetRegions().at(0);
            g->AssignRegion(assigned_rgn);
        }
    }

    if (zone && !zone->HasGroup(g))
    zone->AddGroup(g);

    ListIter<CombatGroup> iter = g->GetComponents();
    while (++iter)
    PlaceGroup(iter.value());
}
CampaignMissionRequest*
CampaignPlanMission::PlanRandomStarshipMission()
{
    int type    = Mission::PATROL;
    int r       = RandomIndex();
    int ownside = player_group->GetIFF();

    if (mission_type_index < 0)
    mission_type_index = r;

    else if (mission_type_index >= 16)
    mission_type_index = 0;

    type = mission_types[mission_type_index++];

    if (type == Mission::ESCORT_FREIGHT) {
        CombatGroup*  freight  = campaign->FindGroup(ownside, CombatGroup::FREIGHT);
        if (!freight || freight->CountUnits() < 1)
        type = Mission::PATROL;
    }

    CampaignMissionRequest* request = 0;
    request = new(__FILE__,__LINE__)
    CampaignMissionRequest(campaign, type, start, player_group);

    return request;
}
static
void RandomShuffle(My402List *pList, int num_items)
{
    int i=0;
    My402List list2;
    My402ListElem *elem=NULL;

    memset(&list2, 0, sizeof(My402List));
    (void)My402ListInit(&list2);

    for (i=0; i < num_items; i++) {
        int j=0, idx=0, num_in_list=num_items-i;
        void *obj=NULL;

        idx = RandomIndex(num_in_list);
        for (elem=My402ListFirst(pList); elem != NULL; elem=My402ListNext(pList, elem)) {
            if (j == idx) {
                break;
            }
            j++;
        }
        if (elem == NULL) {
            fprintf(stderr, "Unrecoverable error in RandomShuffle().\n");
            exit(1);
        }
        obj = elem->obj;
        My402ListUnlink(pList, elem);
        (void)My402ListAppend(&list2, obj);
    }
    if (!My402ListEmpty(pList)) {
        fprintf(stderr, "List not empty in RandomShuffle().\n");
        exit(1);
    }
    for (elem=My402ListFirst(&list2); elem != NULL; elem=My402ListNext(&list2, elem)) {
        (void)My402ListAppend(pList, elem->obj);
    }
    My402ListUnlinkAll(&list2);
}
void
CampaignPlanStrategic::AssignZones(Combatant* c)
{
    // find the list of assignable groups, in priority order:
    List<CombatGroup> groups;
    BuildGroupList(c->GetForce(), groups);
    groups.sort();

    // for each group, assign a zone:
    ListIter<CombatGroup> g_iter = groups;

    // first pass: fighter and attack squadrons assigned to star bases
    while (++g_iter) {
        CombatGroup* g     = g_iter.value();
        int          gtype = g->Type();

        if (gtype == CombatGroup::ATTACK_SQUADRON    ||
                gtype == CombatGroup::FIGHTER_SQUADRON   ||
                gtype == CombatGroup::INTERCEPT_SQUADRON) {
            CombatGroup* parent = g->GetParent();

            if (parent && parent->Type() == CombatGroup::WING)
            parent = parent->GetParent();

            if (!parent || parent->Type() == CombatGroup::CARRIER_GROUP)
            continue;

            // these groups are attached to fixed resources,
            // so they must be assigned to the parent's zone:
            CombatZone* parent_zone = campaign->GetZone(parent->GetRegion());

            if (parent_zone) {
                ZoneForce*  parent_force = parent_zone->FindForce(g->GetIFF());

                if (parent_force) {
                    g->SetAssignedZone(parent_zone);
                    parent_force->AddNeed(g->Type(), -(g->Value()));
                }
            }
        }
    }

    // second pass: carrier groups
    g_iter.reset();
    while (++g_iter) {
        CombatGroup* g     = g_iter.value();
        int          gtype = g->Type();

        if (gtype == CombatGroup::CARRIER_GROUP) {
            int         current_zone_need = 0;
            int         highest_zone_need = 0;
            CombatZone* highest_zone      = 0;
            ZoneForce*  highest_force     = 0;
            CombatZone* current_zone      = 0;
            ZoneForce*  current_force     = 0;

            List<CombatZone> possible_zones;

            if (g->IsZoneLocked()) {
                current_zone  = g->GetAssignedZone();
                current_force = current_zone->FindForce(g->GetIFF());
            }

            else {
                ListIter<CombatZone> z_iter = campaign->GetZones();
                while (++z_iter) {
                    CombatZone* zone  = z_iter.value();
                    ZoneForce*  force = zone->FindForce(g->GetIFF());
                    int         need  = force->GetNeed(CombatGroup::CARRIER_GROUP)     +
                    force->GetNeed(CombatGroup::ATTACK_SQUADRON)   +
                    force->GetNeed(CombatGroup::FIGHTER_SQUADRON)  +
                    force->GetNeed(CombatGroup::INTERCEPT_SQUADRON);

                    if (g->IsSystemLocked() && zone->System() != g->GetAssignedSystem())
                    continue;

                    possible_zones.append(zone);

                    if (zone->HasRegion(g->GetRegion())) {
                        current_zone_need = need;
                        current_zone      = zone;
                        current_force     = force;
                    }

                    if (need > highest_zone_need) {
                        highest_zone_need = need;
                        highest_zone      = zone;
                        highest_force     = force;
                    }
                }
            }

            CombatZone* assigned_zone  = current_zone;
            ZoneForce*  assigned_force = current_force;

            if (highest_zone_need > current_zone_need) {
                assigned_zone  = highest_zone;
                assigned_force = highest_force;
            }

            // if we couldn't find anything suitable,
            // just pick a zone at random:
            if (!assigned_zone) {
                if (possible_zones.isEmpty())
                possible_zones.append(campaign->GetZones());

                int nzones = possible_zones.size();
                int n      = RandomIndex() % nzones;

                assigned_zone  = possible_zones.at(n);
                assigned_force = assigned_zone->FindForce(g->GetIFF());
            }

            if (assigned_force && assigned_zone) {
                Text assigned_rgn;
                if (!campaign->GetZone(g->GetRegion())) {
                    assigned_rgn = *assigned_zone->GetRegions().at(0);
                    g->AssignRegion(assigned_rgn);
                }

                g->SetAssignedZone(assigned_zone);
                assigned_force->AddNeed(g->Type(), -(g->Value()));

                // also assign the carrier's wing and squadrons to the same zone:
                ListIter<CombatGroup> squadron = g->GetComponents();
                while (++squadron) {
                    squadron->SetAssignedZone(assigned_zone);
                    assigned_force->AddNeed(squadron->Type(), -(squadron->Value()));

                    if (squadron->Type() == CombatGroup::WING) {
                        ListIter<CombatGroup> s = squadron->GetComponents();
                        while (++s) {
                            s->SetAssignedZone(assigned_zone);
                            assigned_force->AddNeed(s->Type(), -(s->Value()));
                        }
                    }
                }
            }
        }
    }

    // third pass: everything else
    g_iter.reset();
    while (++g_iter) {
        CombatGroup* g     = g_iter.value();
        int          gtype = g->Type();

        if (gtype == CombatGroup::BATTLE_GROUP || gtype == CombatGroup::DESTROYER_SQUADRON) {
            int         current_zone_need = 0;
            int         highest_zone_need = 0;
            CombatZone* highest_zone      = 0;
            ZoneForce*  highest_force     = 0;
            CombatZone* current_zone      = 0;
            ZoneForce*  current_force     = 0;

            List<CombatZone> possible_zones;

            if (g->IsZoneLocked()) {
                current_zone  = g->GetAssignedZone();
                current_force = current_zone->FindForce(g->GetIFF());
            }

            else {
                ListIter<CombatZone> z_iter = campaign->GetZones();
                while (++z_iter) {
                    CombatZone* zone  = z_iter.value();
                    ZoneForce*  force = zone->FindForce(g->GetIFF());
                    int         need  = force->GetNeed(g->Type());

                    if (g->IsSystemLocked() && zone->System() != g->GetAssignedSystem())
                    continue;

                    possible_zones.append(zone);

                    // battle groups can do double-duty:
                    if (gtype == CombatGroup::BATTLE_GROUP)
                    need += force->GetNeed(CombatGroup::DESTROYER_SQUADRON);

                    if (zone->HasRegion(g->GetRegion())) {
                        current_zone_need = need;
                        current_zone      = zone;
                        current_force     = force;
                    }

                    if (need > highest_zone_need) {
                        highest_zone_need = need;
                        highest_zone      = zone;
                        highest_force     = force;
                    }
                }
            }

            if (highest_zone_need > current_zone_need) {
                g->SetAssignedZone(highest_zone);

                if (highest_force)
                highest_force->AddNeed(g->Type(), -(g->Value()));
            }
            else {
                if (!current_zone) {
                    if (possible_zones.isEmpty())
                    possible_zones.append(campaign->GetZones());

                    int nzones = possible_zones.size();
                    int n      = RandomIndex() % nzones;

                    current_zone  = possible_zones.at(n);
                    current_force = current_zone->FindForce(g->GetIFF());
                }

                g->SetAssignedZone(current_zone);

                if (current_force)
                current_force->AddNeed(g->Type(), -(g->Value()));

                Text assigned_rgn;
                if (!campaign->GetZone(g->GetRegion())) {
                    assigned_rgn = *current_zone->GetRegions().at(0);
                    g->AssignRegion(assigned_rgn);
                }
            }
        }
    }
}