/*******************************************************************
/*  GroupUnit.cpp
/*  Author: Vadim Berman
/*
/*  Description:
/*  implementation of Group class. Group is an Agent managing other Agents,
/*  which is able to use squad tactics
/*
/*  The contents of this file are subject to the Brainiac Public License.
/*  Version 1.0 (the "License"); you may not use this file except in
/*  compliance with the License. You may obtain a copy of the License at
/*  http://www.twilightminds.com
/*
/*  Software distributed under the License is distributed on an "AS IS"
/*  basis WITHOUT WARRANTY OF ANY KIND, either express or implied. See
/*  the License for the specific language governing rights and limitations
/*  under the License.
/*
/*  Copyright (C) 1999 Twilight Minds. All rights reserved.
/********************************************************************/
//---------------------------------------------------------------------------
// "It works".
// This is the only thing I can say in my defense once you'll read this
// %$#$%* source. You see, I had neither the time nor the motive to
// improve its looks. Global variables of the worst kind, almost
// duplicate code, other unpleasant things - you'll see it all. Sorry.
//---------------------------------------------------------------------------
#include "GroupUnit.h"
//---------------------------------------------------------------------------
extern GoalDefNodeP 		GoalListHead;
extern ActionDefNodeP		ActionListHead;
extern StrategyDefNodeP		StrategyListHead;
extern char    LastThought[MAX_VERBAL_LEN];
extern unsigned short LastSelected;
int (BBECALL *gfpMemberChkFn)(void*) = NULL;
extern BOOL SoloAssumptionMode, ActionsSoloMode, UseHistory;
extern char VerbalOutput[MAX_VERBAL_LEN];
extern unsigned short nMaxFailures, LastSelected;

extern Core *pbsAlternateInitiator;
extern Strategy    *pAlternateStrategy;
extern EventsRegistry History;

//***************************************************************************
//CreateGroup
//********** Description: **************
//create a new Group especially for completing a Strategy
//********** Parameters:  **************
//Agent *Leader        - pointer to the Group leading Agent
//ID StrategyID        - ID of the Strategy to complete
//Core *SObjective     - pointer to Objective Core
//ItemScores *SObjItem - pointer to Objective Item
//---------------------------------------------------------------------------
Group* BBECALL CreateGroup(Agent *Leader,ID StrategyID,Core *SObjective,
        ItemScores *SObjItem)
{
    Strategy *NewStrategy;
    Group *NewGroup;
    ActionNodeP ap;
    Action *pAct;
    unsigned short StrategyParticipantsQty = 0, PrevStrID = 0;
    NewGroup = new Group;
    if(NewGroup == NULL)
    {
        return NULL;
    }

    NewGroup->AddMember(Leader);
    NewGroup->SetLeader(0);//'cause he's the first and only one
    NewStrategy = CreateStrategy(NULL,StrategyID);
    if(NewStrategy != NULL)
    {
        NewGroup->ExecType = COMMON_EFFORT;
        ap = NewStrategy->GetActionList();
        if(ap != NULL)
            PrevStrID = ap->action.GetID();
        for(; ap != NULL; ap = ap->next)
        {
        //count strategy participants...
            StrategyParticipantsQty++;
        //...and check for strategy type
            if(PrevStrID != ap->action.GetID())
                NewGroup->ExecType = COORDINATE;
            PrevStrID = ap->action.GetID();
        }

        NewStrategy->SetObjective(SObjective);
        NewStrategy->SetObjItem(SObjItem);

        //also set targets for the first action
        pAct = NewStrategy->GetNextAction();
        if(pAct != NULL)
        {
            pAct->InitTargets(Leader->GetCoreAddress(),
                SObjective,SObjItem);
        }
        NewGroup->CurrentStrategy = *NewStrategy;
        NewGroup->MaxMembersQty = StrategyParticipantsQty;
        if(NewGroup->ExecType == COMMON_EFFORT)
        //the leader is always found
            NewGroup->CurrentStrategy.SkipNextAction();
        NewGroup->LeadershipType = DISMANTLE_ON_STR_END;
    }

    NewGroup->SetActivityMode(MEMBERS_SELECT);
    NewGroup->SetNewMemberChkFn(gfpMemberChkFn);

    delete NewStrategy;
    return NewGroup;
}

//---------------------------------------------------------------------------
//---------------------- Group methods implementation -----------------------
//---------------------------------------------------------------------------
BBECALL Group::Group()
{
    Members = NULL;
    NewMemberChkFn = NULL;
    StrParts = NULL;
    Activity = FALSE;
    ExecType = COORDINATE;
    LeaderIndex = MembersQty = 0;
    LeadershipType = PRESENT_LEADER_ONLY;
    MaxMembersQty = 1;
    MemberCriterion = GOAL_NONE;
}

//---------------------------------------------------------------------------
BBECALL Group::~Group()
{
    Dismantle();
}

//***************************************************************************
//Group::Dismantle
//********** Description: **************
//dismantle the current Group and delete all the Strategies
//********** Parameters:  **************
//none
//---------------------------------------------------------------------------
void BBECALL Group::Dismantle()
{
    DisposeStrategies();
    if(StrParts != NULL)
    {
        delete StrParts;
        StrParts = NULL;
    }

    if(Members != NULL)
    {
        delete Members;
        Members = NULL;
    }

    Activity = FALSE;
    LeaderIndex = MembersQty = 0;
}

//***************************************************************************
//Group::DisposeStrategies
//********** Description: **************
//delete all the Strategies
//********** Parameters:  **************
//none
//---------------------------------------------------------------------------
void BBECALL Group::DisposeStrategies()
{
    int i;
    if(StrParts == NULL)
        return;

    CurrentStrategy.End();
    for(i = 0; i < MembersQty; i++)
    {
        if(StrParts[i] != NULL)
        {
            StrParts[i]->End();
            delete StrParts[i];
        }
    }
}

//***************************************************************************
//Group::ExecuteNextAction
//********** Description: **************
//Agent::ExecuteNextAction override. The type of Action execution depends on
//Activity attribute. It can be MEMBERS_SELECT, STRATEGY_EXECUTION or FALSE
//(no activity at all). There are two types of squad Strategies execution:
//A. Common effort (everyone does the same, simultaneously)
//B. Different Actions (COORDINATE) - everyone has his / her own role
//********** Parameters:  **************
//void *aArg                - parameter to the user-defined Action handler
//Agent **Population        - array of pointers to the Agents being scanned
//unsigned short nPopSize   - the above array length
//ItemPtrArr *ipapAllInv    - pointer to the arrays of the Agents' item pointers
//unsigned short *nInvSizes - lengths of the above Items arrays
//---------------------------------------------------------------------------
ActionError BBECALL Group::ExecuteNextAction(void *aArg, Agent** Population,
        unsigned short nPopSize,
        ItemPtrArr* ipapAllInv, unsigned short *nInvSizes)
{
    Core *pcMemb = GetLeaderCoreAddress();
    if(LeadershipType != NO_LEADER)
        if(pcMemb == NULL || pcMemb->Dead)
            Dismantle();

    if(Activity == FALSE)
        return ACTION_NOT_PRIMED;

    if(Activity == MEMBERS_SELECT)
    {
        return ExecuteMemberSelection(aArg,Population,
                    nPopSize,ipapAllInv,nInvSizes);
    }

    //Activity == STRATEGY_EXECUTION
    if(ExecType == COMMON_EFFORT)
    {
        return ExecuteCommonEffort(aArg, Population,
                nPopSize,ipapAllInv, nInvSizes);
    }
    else
    //everyone does his/her own part, that is, ExecuteNextAction
    //per every member. This is for ExecType COORDINATE or IMMEDIATE
        return ExecuteDifferentActions(aArg, Population,
                nPopSize,ipapAllInv, nInvSizes);

}

//***************************************************************************
//Group::ExecuteDifferentActions
//********** Description: **************
//execute COORDINATE Action where everyone does his / her own part
//********** Parameters:  **************
//void *aArg                - parameter to the user-defined Action handler
//Agent **Population        - array of pointers to the Agents being scanned
//unsigned short nPopSize   - the above array length
//ItemPtrArr *ipapAllInv    - pointer to the arrays of the Agents' item pointers
//unsigned short *nInvSizes - lengths of the above Items arrays
//---------------------------------------------------------------------------
ActionError BBECALL Group::ExecuteDifferentActions(void *aArg, Agent** Population,
        unsigned short nPopSize,
        ItemPtrArr* ipapAllInv, unsigned short *nInvSizes)
{
    BOOL AssignSuccess = TRUE, AllActionsAreNULL = TRUE;
    ActionError aeRetVal = ACTION_SUCCESS;
    Action *pNextA;
    unsigned short i,j;
    percentage NeutralSideMeanness;

    for(i = 0; (i < MembersQty) && AssignSuccess && (aeRetVal == ACTION_SUCCESS);
                        i++)
    {
        pNextA = StrParts[i]->GetNextAction();
        if(pNextA == NULL)
            continue;
        else
            AllActionsAreNULL = FALSE;

        if(Members[i]->AssignTargetsForAction(pNextA,
                Population,
                nPopSize,
                ipapAllInv,
		        nInvSizes,
                StrParts[i]))
        {
            if(ExecType != COORDINATE)
            {
                NeutralSideMeanness = CalcNeutralSideMeanness(Population,
                        nPopSize,pNextA);
                //execute action
                aeRetVal = StrParts[i]->ExecuteNextAction(aArg,
                        Members[i]->GetTrueStatsByte(),
                        NeutralSideMeanness);
            }
        }
        else
            AssignSuccess = FALSE;
    }

    if(ExecType == COORDINATE)
    {
        if(AssignSuccess)
            for(i = 0; (i < MembersQty) && (aeRetVal == ACTION_SUCCESS); i++)
            {
                NeutralSideMeanness = CalcNeutralSideMeanness(Population,
                        nPopSize,pNextA);
                aeRetVal = StrParts[i]->ExecuteNextAction(aArg,
                        Members[i]->GetTrueStatsByte(),
                        NeutralSideMeanness);
            }
    }
    else
        if(!AssignSuccess && (ExecType != NO_RETURN))
        //then, return all those who executed action although it must be
        //simultaneous
            for(j = 0; j < i-1; j++)
                StrParts[j]->ReturnToLastAction();
    if(AllActionsAreNULL && (LeadershipType == DISMANTLE_ON_STR_END))
        Dismantle();

    return aeRetVal;
}

//***************************************************************************
//Group::ExecuteCommonEffort
//********** Description: **************
//execute COORDINATE Action where everyone does his / her own part
//********** Parameters:  **************
//void *aArg                - parameter to the user-defined Action handler
//Agent **Population        - array of pointers to the Agents being scanned
//unsigned short nPopSize   - the above array length
//ItemPtrArr *ipapAllInv    - pointer to the arrays of the Agents' item pointers
//unsigned short *nInvSizes - lengths of the above Items arrays
//---------------------------------------------------------------------------
ActionError BBECALL Group::ExecuteCommonEffort(void *aArg, Agent** Population,
        unsigned short nPopSize,
        ItemPtrArr* ipapAllInv, unsigned short *nInvSizes)
{
    ActionError ErrCode = NO_CANDIDATES;
    percentage ActualWick, ActualAnar, ActualDiff, ActualAttChg,
        NeutralSideMeanness;
    ActionNodeP fail_goto;
    Action *pNextA;

    VerbalOutput[0] = '\0';
    LastThought[0] = '\0';

    if ( CurrentStrategy.GetGoal().Itself == GOAL_NONE )
    	return ACTION_NOT_PRIMED;

    actAssignTargetsTo = CurrentStrategy.GetNextAction();
    pAlternateStrategy = NULL;

    if(actAssignTargetsTo == NULL || IsStrategyStillRelevant(Population,nPopSize,
         ipapAllInv,nInvSizes) == FALSE)
    {
        CurrentStrategy.End();    //in case it wasn't called yet
    	return ACTION_NOT_PRIMED;
    }
    pNextA = actAssignTargetsTo;

    if(AssignTargetForNextAction(Population,nPopSize,ipapAllInv,nInvSizes))
    {
        pNextA->GetActualScores(ActualDiff, ActualWick,
    	    ActualAnar, ActualAttChg);
        NeutralSideMeanness = CalcNeutralSideMeanness(Population,
            nPopSize,pNextA);
        ErrCode = CurrentStrategy.ExecuteNextAction(aArg, UseTrueStats,
            NeutralSideMeanness);
    }

    if(!(ErrCode == ACTION_SUCCESS || ErrCode == ACTION_NOT_PRIMED
        //end strategy on success means also go on in case of failure
            || pNextA->GetFailureGoto() == END_STR_ON_SUCCESS))
    {
        FailuresCount++;
        if(UseHistory)
        {
            //now register failure
            History.Register(core.attId,
                    pNextA->GetID(),
                    pNextA->GetObjective()->attId,
                    ACTION_ITEMID(pNextA),
                    ACTION_FAILURE);
        }
    }
    if(ErrCode == ACTION_SUCCESS)
    {
        FailuresCount = 0;
        LoopFailuresCount = 0;
        if(pNextA->GetFailureGoto() == END_STR_ON_SUCCESS)
            CurrentStrategy.End();
        core.Wickedness = (percentage)((1-SINGLE_ACTION_INFLUENCE) * core.Wickedness
            + SINGLE_ACTION_INFLUENCE * ActualWick);
        core.Anarchy = (percentage)((1-SINGLE_ACTION_INFLUENCE) * core.Anarchy
            + SINGLE_ACTION_INFLUENCE * ActualAnar);
    }

    if(FailuresCount >= nMaxFailures)
    {
        if(pNextA->GetFailureGoto() && (LoopFailuresCount < nMaxFailures))
        {
            fail_goto = CurrentStrategy.FindAction(pNextA->GetFailureGoto());
            if(fail_goto)
            {
                LoopFailuresCount++;
                CurrentStrategy.SetNextActionAddress(fail_goto);
                sprintf(LastThought,"We can't do it like that. I must act another way... ");
            }
            else
            {
                sprintf(LastThought,"We'll never be able to complete this plan. No use to go on... ");
                if(UseHistory)
                {
                    History.Register(core.attId,
                        CurrentStrategy.GetID(),
                        CurrentStrategy.GetObjective()->attId,
                        STRATEGY_ITEMID(CurrentStrategy),
                        STRATEGY_FAILURE);
                        //so next time the initiator will be smarter;)
                }
                CurrentStrategy.End();
                ErrCode = STRATEGY_ABORTED;
            }
        }
        else
        {
            sprintf(LastThought,"We'll never be able to complete this plan. No use to go on... ");
            if(UseHistory)
            {
                History.Register(core.attId,
                    CurrentStrategy.GetID(),
                    CurrentStrategy.GetObjective()->attId,
                    STRATEGY_ITEMID(CurrentStrategy),
                    STRATEGY_FAILURE);
            }
                    //so next time the initiator will be smarter
            CurrentStrategy.End();
            ErrCode = STRATEGY_ABORTED;
        }
        FailuresCount = 0;
    }
    return ErrCode;
}

//***************************************************************************
//Group::ExecuteMemberSelection
//********** Description: **************
//seeks Agent suitable to become additional Group member.
//First, a pseudo-strategy containing, in fact, member strategies and
//criteria IDs, is executed. The fitting members are added without order.
//Once it is 'completed', the members' order is fixed up. Then
//everybody gets his/her role.
//********** Parameters:  **************
//void *aArg                - parameter to the user-defined Action handler
//Agent **Population        - array of pointers to the Agents being scanned
//unsigned short nPopSize   - the above array length
//ItemPtrArr *ipapAllInv    - pointer to the arrays of the Agents' item pointers
//unsigned short *nInvSizes - lengths of the above Items arrays
//---------------------------------------------------------------------------
ActionError BBECALL Group::ExecuteMemberSelection(void *aArg, Agent** Population,
        unsigned short nPopSize,
        ItemPtrArr* ipapAllInv, unsigned short *nInvSizes)
{
    int nNewMemberIndex = -1;
    Action *pNextA = CurrentStrategy.GetNextAction();
    if(pNextA == NULL)
        return ACTION_NOT_PRIMED;
    if(AssignTargetForNextAction(
                Population,
                nPopSize,
                ipapAllInv,
		        nInvSizes))
    {
       //Note: LastSelected=global var. Bad!!! Very bad!!!!!!
        if(NewMemberChkFn == NULL)
            nNewMemberIndex = AddMember(Population[LastSelected]);
        else
            if((*NewMemberChkFn)(aArg))
                nNewMemberIndex = AddMember(Population[LastSelected]);
            else
                if(UseHistory)
                {
                //remember not to use this member again
                    History.Register(core.attId,
                        pNextA->GetID(),
                        pNextA->GetObjective()->attId,
                        0,
                        ACTION_FAILURE);
                }

        if(nNewMemberIndex < 0)
        {
            return ACTION_FAILURE;
        }

        CurrentStrategy.SkipNextAction();
        if(CurrentStrategy.GetNextAction() == NULL)
        {
            if(AssignInitiatorsInGroupStrategy(&CurrentStrategy))
            {
                Activity = STRATEGY_EXECUTION;
                sprintf(LastThought,"At last we can use our plan...");
            }
            else
            {
            //unsuccessful group. Dismantle!
                //Activity = FALSE;
            }
        }

        FailuresCount = 0;
        LoopFailuresCount = 0;
        return ACTION_SUCCESS;
    }

    return ACTION_FAILURE;
}

//***************************************************************************
//Group::AddMember
//********** Description: **************
//add a new member, and store it in a needed slot (if forced
//and parameter is OK). If no index was specified, the new member
//is referenced by the last pointer
//********** Parameters:  **************
//Agent *apNewMember - pointer to the new member Agent
//int nForceIndex    - forced index for the new index; the default is the last one
//---------------------------------------------------------------------------
int BBECALL Group::AddMember(Agent *apNewMember,int nForceIndex)
{
    PostError(0);
    if(apNewMember == NULL || MembersQty >= MaxMembersQty)
    {
        PostError(INVALID_PARAM);
        return -1;
    }

    Agent **appSwapBuf = new Agent *[MembersQty+1];
    unsigned short i,j = 0, nNewMemberIndex;

    if(appSwapBuf == NULL)
    {
        PostError(MEM_ALLOC_FAILED);
        return -1;
    }

    if(Members != NULL)
    {
        memcpy(appSwapBuf,Members,MembersQty*sizeof(Agent *));
        delete Members;
    }
    else
        memset(appSwapBuf,0,(MembersQty+1)*sizeof(Agent *));

    MembersQty++;
    if(nForceIndex < 0 || nForceIndex > MembersQty)
    {
        nNewMemberIndex = (unsigned short)(MembersQty - 1);
    }
    else
    {
        nNewMemberIndex = (unsigned short)nForceIndex;
    }

    Members = new Agent *[MembersQty];
    for(i = 0; i < MembersQty; i++)
    {
        if(nNewMemberIndex == i)
            Members[i] = apNewMember;
        else
        {
            Members[i] = appSwapBuf[j];
            j++;
        }
    }

    delete appSwapBuf;
    sprintf(LastThought,"Our group got a new member");
    CalcCore();
    return nNewMemberIndex;
}

//***************************************************************************
//Group::CalcCore
//********** Description: **************
//calculate the overall Core of the Group based on the members overall and some
//leader's attributes. The attitude tree is copied from the leader's Core
//********** Parameters:  **************
//none
//---------------------------------------------------------------------------
void BBECALL Group::CalcCore()
{
    Core bsCurrent;
    int i;
    short MeannessSum[2],WealthSum[2],BeautySum,ForeignnessSum,
        PersonalitySum,CourageSum;

    if(MembersQty < 1)
        return;

    if(Activity)
        core.Dead = FALSE;
    else
        core.Dead = TRUE;

    DelAttitudeTree(&core.attPersonal);
    if(LeadershipType != NO_LEADER)
    {
        bsCurrent = Members[LeaderIndex]->GetCore();

        core.attPersonal
            = CopyAttitudeTree(bsCurrent.attPersonal);
        core.Intelligence[1] = bsCurrent.Intelligence[1];
    }
    else
    {
        core.attPersonal = NULL;
        core.Intelligence[1] = Members[0]->GetCore().Intelligence[1];
        for(i = 1; i < MembersQty; i++)
        {
            bsCurrent = Members[i]->GetCore();
            if(bsCurrent.Intelligence[1] > core.Intelligence[1])
                core.Intelligence[1] = bsCurrent.Intelligence[1];
        }
    }

    MeannessSum[0] = 0; MeannessSum[1] = 0;
    WealthSum[0] = 0; WealthSum[1] = 0;
    BeautySum = 0;
    ForeignnessSum = 0;
    PersonalitySum = 0;
    CourageSum = 0;
    for(i = 0; i < MembersQty; i++)
    {
        bsCurrent = Members[i]->GetCore();
        MeannessSum[0] += bsCurrent.Meanness[0];
        MeannessSum[1] += bsCurrent.Meanness[1];
        WealthSum[0] += bsCurrent.Wealth[0];
        WealthSum[1] += bsCurrent.Wealth[1];
        BeautySum += bsCurrent.Beauty;
        ForeignnessSum += bsCurrent.Foreignness;
        PersonalitySum += bsCurrent.Intelligence[0];
        CourageSum += bsCurrent.Courage;
    }

    core.Meanness[0] = VALID_PERCENTAGE(MeannessSum[0]);
    core.Meanness[1] = VALID_PERCENTAGE(MeannessSum[1]);
    core.Wealth[0] = VALID_PERCENTAGE(WealthSum[0]);
    core.Wealth[1] = VALID_PERCENTAGE(WealthSum[1]);
    //the averages...
    core.Beauty = VALID_PERCENTAGE(BeautySum/MembersQty);
    core.Foreignness = VALID_PERCENTAGE(ForeignnessSum/MembersQty);
    core.Intelligence[0] = VALID_PERCENTAGE(PersonalitySum/MembersQty);
    core.Courage = VALID_PERCENTAGE(CourageSum/MembersQty);

    UpdateOriginalCore();//from the actual ones...
}

//***************************************************************************
//Group::RemoveMember
//********** Description: **************
//remove a specified member from the party
//********** Parameters:  **************
//unsigned short nMemberToRemove - index of the member to remove
//---------------------------------------------------------------------------
void BBECALL Group::RemoveMember(unsigned short nMemberToRemove)
{
    PostError(0);
    if(MembersQty == 0 || Members == NULL
            || nMemberToRemove >= MembersQty)
    {
        PostError(INVALID_PARAM);
        return;
    }

    Agent **appSwapBuf = new Agent *[MembersQty-1];
    unsigned short i,j = 0;
    BOOL bWasRemoved = FALSE;

    if(appSwapBuf == NULL)
    {
        PostError(MEM_ALLOC_FAILED);
        return;
    }

    for(i = 0; i < MembersQty; i++)
        if(nMemberToRemove != i)
        {
            appSwapBuf[j] = Members[i];
            j++;
            if(bWasRemoved && (i == LeaderIndex))
            //update LeaderIndex if needed
                LeaderIndex = j;
        }
        else
            bWasRemoved = TRUE;

    delete Members;
    MembersQty--;
    Members = new Agent *[MembersQty];
    memcpy(Members,appSwapBuf,MembersQty*sizeof(Agent *));
    delete appSwapBuf;
}

//***************************************************************************
//Group::SetLeader
//********** Description: **************
//assign a new leader to the Group
//********** Parameters:  **************
//unsigned short nNewLeaderIndex - index of the member to assign as the Group leader
//---------------------------------------------------------------------------
void BBECALL Group::SetLeader(unsigned short nNewLeaderIndex)
{
    if(nNewLeaderIndex < MembersQty)
        LeaderIndex = nNewLeaderIndex;
}

//***************************************************************************
//Group::GetLeaderIndex
//********** Description: **************
//return the leader's index in the members' array
//********** Parameters:  **************
//none
//---------------------------------------------------------------------------
unsigned short BBECALL Group::GetLeaderIndex()
{ return LeaderIndex; }

//***************************************************************************
//Group::FindCriterionMatch
//********** Description: **************
//find a member matching the criterion. If none found, -1 is returned
//********** Parameters:  **************
//ID CritID - criterion ID to compare with
//---------------------------------------------------------------------------
int BBECALL Group::FindCriterionMatch(ID CritID)
{
    GoalDefNodeP gp;
    int i;
    BOOL CritFit = FALSE;
    gp = GetGoalNode(CritID);
    if(gp == NULL)
        return -1;

    for(i = 0; (i < MembersQty) && !CritFit; i++)
        if(Members[i] != NULL)
            CritFit = VerifyCriteria(gp,
                    Members[i]->GetCoreAddress());

    if(CritFit)
        return i - 1;
    else
        return -1;
}

//***************************************************************************
//Group::AssignStrategy
//********** Description: **************
//create and assign a Strategy to a specified member
//********** Parameters:  **************
//ID StrategyID              - ID of the Strategy to assign
//unsigned short MemberOrder - member to assign Strategy to
//BOOL CheckMode             - whether the function is executed only to check
//                             the possibility of assignment
//---------------------------------------------------------------------------
BOOL BBECALL Group::AssignStrategy(ID StrategyID,unsigned short MemberOrder,
        BOOL CheckMode)
{
    Strategy *NewStrategy;
    Goal StratGoal;
    PostError(0);
    if(MemberOrder > MembersQty && MemberOrder != ALTOGETHER)
    {
        PostError(INVALID_PARAM);
        return FALSE;
    }

    NewStrategy = CreateStrategy(NULL,StrategyID);
    if(NewStrategy == NULL)
    {
        PostError(INVALID_PARAM);
        return FALSE;
    }

    NewStrategy->SetObjective(CurrentStrategy.GetObjective());
    NewStrategy->SetObjItem(CurrentStrategy.GetObjItem());
    StratGoal.Itself = MANUAL_GOAL;
    StratGoal.Importance = NewStrategy->GetImportance();
    if(MemberOrder == ALTOGETHER)
    {
        if(PrimeAllActionTargets(NewStrategy) &&
                NewStrategy->PossibilityCheck() )
        {
            if(!CheckMode)
            {
                CurrentStrategy.End();
                CurrentStrategy = *NewStrategy;
            }
            delete NewStrategy;
            return TRUE;
        }
        else
        {
            delete NewStrategy;
            return FALSE;
        }
    }

    if(Members[MemberOrder]->PrimeAllActionTargets(NewStrategy)
            && NewStrategy->PossibilityCheck() )
    {
        if(CheckMode)
            StrParts[MemberOrder] = NewStrategy;
        return TRUE;
    }
    else
    {
        delete NewStrategy;
        return FALSE;
    }
}

//***************************************************************************
//Group::AssignInitiatorsInGroupStrategy
//********** Description: **************
//assign Strategies to all the members of the Group
//********** Parameters:  **************
//Strategy *strToAssign - squad Strategy to assign
//---------------------------------------------------------------------------
BOOL BBECALL Group::AssignInitiatorsInGroupStrategy(Strategy *strToAssign)
{
	ActionNodeP ap;
    int i = 0,NewIndex,*NewMembersScheme;

    if(ExecType == COMMON_EFFORT)
    //then all we must do is replacing the master strategy with true
    //strategy
    {
        ap = strToAssign->GetActionList();
        if(ap == NULL)
            return FALSE;

        if(AssignStrategy(ap->action.GetID(),ALTOGETHER))
            return TRUE;
        else
            return FALSE;
    }

    NewMembersScheme = new int[MembersQty];
    if(NewMembersScheme == NULL)
    {
        PostError(MEM_ALLOC_FAILED);
        return FALSE;
    }

	for( ap = strToAssign->GetActionList(); ap != NULL ; ap = ap->next )
    {
        NewIndex = FindCriterionMatch(ap->action.GetTargetAssignmentCriteria());
        if(NewIndex > -1)
            NewMembersScheme[i++] = NewIndex;
        else
            break;
    }

    if(NewIndex > -1)
    {
        SetActionsOrder(NewMembersScheme);
        i = 0;
        for(ap = strToAssign->GetActionList(); ap != NULL; ap = ap->next)
            if(AssignStrategy(ap->action.GetID(),(unsigned short)i++) == FALSE)
                return FALSE;
        return TRUE;
    }
    else
        return FALSE;
}

//***************************************************************************
//Group::SetActionsOrder
//********** Description: **************
//rearrange members' roles in the current Strategy by a new scheme
//********** Parameters:  **************
//int *NewScheme - array of members' indexes to act by
//int MaxLen     - limit the rearrangement to a certain number of the first members.
//                 If unused, MembersQty is used.
//---------------------------------------------------------------------------
void BBECALL Group::SetActionsOrder(int *NewScheme, int MaxLen)
{
    Agent **appSwapBuf;
    Strategy  **sppSwapBuf;
    int i;

    if(MaxLen == 0)
        MaxLen = MembersQty;
    appSwapBuf = new Agent *[MembersQty];
    sppSwapBuf = new Strategy *[MembersQty];

    if(appSwapBuf == NULL || sppSwapBuf == NULL)
    {
        PostError(MEM_ALLOC_FAILED);
        return;
    }

    memcpy(appSwapBuf,Members,MembersQty*sizeof(Agent *));
    memcpy(sppSwapBuf,StrParts,MembersQty*sizeof(Strategy *));

    for(i = 0; i < MembersQty; i++)
        if(i < MaxLen)
        {
            Members[i] = appSwapBuf[NewScheme[i]];
            StrParts[i] = sppSwapBuf[NewScheme[i]];
            //update leader pointer if swapped
            if(i == LeaderIndex)
                LeaderIndex = (unsigned short)NewScheme[i];
        }
        else
            Members[i] = appSwapBuf[i];

    delete appSwapBuf;
    delete sppSwapBuf;
}

//***************************************************************************
//Group::PickStrategy
//********** Description: **************
//pick an appropriate squad Strategy
//********** Parameters:  **************
//Goal StrGoal       - Goal to pick Strategy for
//Core *pbsObj       - Strategy Objective
//ItemScores *pisObj - Strategy Objective Item
//---------------------------------------------------------------------------
BOOL BBECALL Group::PickStrategy(Goal StrGoal, Core *pbsObj,
    ItemScores *pisObj)
{
    Strategy *NewStrategy = NULL;
    StrategyDefNodeP sp;
    percentage NewImportance;
    Agent **SaveBuf;

    PostError(0);
    SaveBuf = new Agent *[MembersQty];
    memcpy(SaveBuf,Members,MembersQty*sizeof(Agent *));

    for( sp = StrategyListHead ; sp != NULL ; sp = sp->next )
    {
        if (sp->TheGoal.Itself == StrGoal.Itself )
        {
            if(sp->MaxIntel < core.Intelligence[0])
                continue;
            NewStrategy = CreateStrategy( sp );
            if(NewStrategy == NULL)
                continue;
            NewStrategy->SetObjective( pbsObj );
            if(pisObj != NULL)
                NewStrategy->SetObjItem( pisObj );
            //if NULL... so be it
            //important to set item there as actions
            //might use it as reference
            NewImportance = NewStrategy->GetImportance();

            if(AssignInitiatorsInGroupStrategy(NewStrategy)
                && IsNewStrategyMoreImportant(NewImportance))
            {
                CurrentStrategy.End();
                CurrentStrategy = *NewStrategy;
                if(AssignGroupStrategy())
                {
                    CurrentStrategy.ResetAllActionTargets();
                    delete NewStrategy;
                    if(pisObj == NULL)
                        sprintf(LastThought,"We'll try %s on this person. ",
                            sp->Name);
                    else
                        sprintf(LastThought,"We'll try %s to obtain an item in this person's possession. ",
                            sp->Name);
                    memcpy(Members,SaveBuf,MembersQty*sizeof(Agent *));
                    delete SaveBuf;
                    return TRUE;
                }
                else
                {
                    delete NewStrategy;
                    NewStrategy = NULL;
                }
            }
            else
            {
                NewStrategy->End();
                delete NewStrategy;
                NewStrategy = NULL;
            }
        }
    }

    if(NewStrategy != NULL)
    {
        NewStrategy->End();
        delete NewStrategy;
        memcpy(Members,SaveBuf,MembersQty*sizeof(Agent *));
        //otherwise, nothing was changed; no need to restore members
    }

    delete SaveBuf;
    return FALSE;
}

//***************************************************************************
//Group::AssignGroupStrategy
//********** Description: **************
//assign all the needed partial Strategies
//********** Parameters:  **************
//none
//---------------------------------------------------------------------------
BOOL BBECALL Group::AssignGroupStrategy()
{
    int i;
    ActionNodeP ap = CurrentStrategy.GetActionList();
    if(ap == NULL)
    {
        PostError(INVALID_PARAM);
        return FALSE;
    }

    DisposeStrategies();
    //Note: doesn't discard the pointers' array itself
    //and this should stay that way
    for(i = 0; (i < MembersQty) && (ap != NULL); i++)
    {
        StrParts[i] = CreateStrategy(NULL,ap->action.GetID());
        if(StrParts[i] != NULL)
        {
            StrParts[i]->SetObjective(CurrentStrategy.GetObjective());
            StrParts[i]->SetObjItem(CurrentStrategy.GetObjItem());
            StrParts[i]->ResetAllActionTargets();
        }
        ap = ap->next;
    }
    return TRUE;
}

//***************************************************************************
//Group::ActivityMode
//********** Description: **************
//return the current Activity node
//********** Parameters:  **************
//none
//---------------------------------------------------------------------------
unsigned char BBECALL Group::ActivityMode()
{ return Activity; }

//***************************************************************************
//Group::SetActivityMode
//********** Description: **************
//set the current Activity node
//********** Parameters:  **************
//unsigned char NewActivity - current Activity
//---------------------------------------------------------------------------
void BBECALL Group::SetActivityMode(unsigned char NewActivity)
{ Activity = NewActivity; }

//***************************************************************************
//Group::Scan
//********** Description: **************
//scan Agent for possible enlistment to the Group or anything else
//********** Parameters:  **************
//Agent *paScanned            - pointer to the scanned Agent
//ItemPtrArr Items            - pointer to the array of the Agent's item pointers
//unsigned short ItemArrayLen - length of the above Items array
//ID ScanGoalItselfArg        - scan goal ID. Default is GOAL_NONE
//void *lpvFnArg              - parameters to the user-defined selection handler
//---------------------------------------------------------------------------
BOOL BBECALL Group::Scan(Agent *paScanned,ItemPtrArr Items,
		unsigned short ItemArrayLen, ID ScanGoalItselfArg, void *lpvFnArg)
{
    percentage NewImportance,ScannedAtt,EvalAtt;
    Goal NewGoal, ScanGoal;
    GoalDefNodeP	gp,gpMembCritDef;
    Strategy *NewStrategy = NULL;
    BOOL bNotFamiliar, bRetVal = FALSE;
    Core bsScanned, *pbsScanned;
    unsigned short i;

    PostError(0);//for now, everything is OK

    if(paScanned == NULL)
    {
        PostError(INVALID_PARAM);
        return FALSE;
    }

    pbsScanned = paScanned->GetCoreAddress();
    ScanGoal.Itself = ScanGoalItselfArg;

    if (ScanGoal.Itself == GOAL_NONE)
        ScanGoal.Importance = 0;
    else
    {
        gp = GetGoalNode(ScanGoal.Itself);
        if (gp == NULL)
            ScanGoal.Importance = 0;
        else
            ScanGoal.Importance = gp->goal.Importance;
    }

    bsScanned = *pbsScanned; //Make local copy of the original struct
                            //so nobody would get hurt...
    if(CreateNewAttitude(bsScanned))
    {
        bNotFamiliar = TRUE;
    }
    else
    {
        bNotFamiliar = FALSE;
    }

    //get the mutual attitudes
    ScannedAtt = GetAttitude(core.attId,pbsScanned->attPersonal);
    EvalAtt    = AttitudeTo(bsScanned);
    //bsScanned.InitialAttitude and bsScanned.AttitudeChangeUnit
    // are used to pass attitudes.
    // if the "not politically correct" problem persists,
    // I'll change it to flags or something more appropriate...
    bsScanned.InitialAttitude = EvalAtt;
    bsScanned.AttitudeChangeUnit = ScannedAtt;

    if(ScanGoal.Itself != GOAL_NONE)
    {
        if (ScanGoal.Importance == 0 && gp != NULL)
            bRetVal = ScanForCriterion(paScanned,Items,
                ItemArrayLen, gp);
        else
                //this part is used for manually operated scan
            if(PickStrategy(gp->goal,pbsScanned))
                return TRUE;
    }

    gpMembCritDef = GetGoalNode(MemberCriterion);
    if(gpMembCritDef != NULL)
    //now check if this person is suitable to become a new member
        if(VerifyCriteria(gpMembCritDef,&bsScanned))
        {
            if(NewMemberChkFn == NULL)
                AddMember(paScanned);
            else
            {
                if((*NewMemberChkFn)(lpvFnArg))
                    AddMember(paScanned);
            }
            return TRUE;//forget about the rest
        }

    if(LeadershipType != DISMANTLE_ON_STR_END)
//which means, the group is not created only to achieve a particular goal
    switch(ScanGoal.Itself){
      case GOAL_NONE:
      	for (gp = GoalListHead; gp != NULL; gp = gp->next)
        {
            if ( gp->goal.Importance == 0 )   //skip criteria records
                continue;

        	if ( VerifyCriteria(gp, &bsScanned) )
                if(PickStrategy(gp->goal,pbsScanned))
                    bRetVal = TRUE;
        }
         bsScanned.InitialAttitude = pbsScanned->InitialAttitude;  //return original attitude
         gp = GetGoalNode( GOAL_ITEM_ACQUISITION );
         if (gp == NULL)
            break; //nothing to do here - some asshole deleted
                          //ITEM ACQUISITION goal
         NewGoal = gp->goal;
         //scanning item
         for ( i = 0; i < ItemArrayLen; i++)
         {
            if(Items[i] == NULL)    //can it fail here?
                continue;
            NewImportance = ItemAcquisitionImportance(*(Items[i]));
         	if ( IsNewStrategyMoreImportant(NewImportance)
                && (NewImportance > MIN_IMPORTANCE)
                )
            //so a character wouldn't want to buy everything in sight
            //once s/he has no active goal
            {
                //ItemAcquisitionImportance returns importance of this item
                if(PickStrategy(NewGoal,pbsScanned,Items[i]))
                {
                    bRetVal = TRUE;
                }
            }
        }

        break;
   }//end switch

   if(!bRetVal)
        sprintf(LastThought,"We feel %s towards this person. Nothing we could or wanted to do with that. ",
            GetAttitudeDesc(GetAttitude(pbsScanned->attId,core.attPersonal)));

   //arbitrary: should the attitudes leading nowhere be deleted?
   if(bNotFamiliar && !bRetVal)    //delete the temporary node
        DelAttitude(pbsScanned->attId, &(core.attPersonal));

   if(NewStrategy != NULL)
   {
        NewStrategy->End();
        delete NewStrategy;
   }

   return bRetVal;
}


//***************************************************************************
//Group::GetMemberCriterion
//********** Description: **************
//return common member criterion ID
//********** Parameters:  **************
//none
//---------------------------------------------------------------------------
ID BBECALL Group::GetMemberCriterion()
{ return MemberCriterion; }

//***************************************************************************
//Group::SetMemberCriterion
//********** Description: **************
//set common member criterion ID
//********** Parameters:  **************
//ID idNewMemberCriterion - common member criterion ID
//---------------------------------------------------------------------------
void BBECALL Group::SetMemberCriterion(ID idNewMemberCriterion)
{ MemberCriterion = idNewMemberCriterion; }

//***************************************************************************
//Group::SetNewMemberChkFn
//********** Description: **************
//set member check handler function
//********** Parameters:  **************
//int (BBECALL *lpargNewMemberChkFn)(void*) - new member check handler function
//---------------------------------------------------------------------------
void BBECALL Group::SetNewMemberChkFn(int (BBECALL *lpargNewMemberChkFn)(void*))
 { NewMemberChkFn = lpargNewMemberChkFn; }

//***************************************************************************
//Group::GetLeaderCoreAddress
//********** Description: **************
//return pointer to the leader's Core
//********** Parameters:  **************
//none
//---------------------------------------------------------------------------
Core *BBECALL Group::GetLeaderCoreAddress()
{
    if(Members == NULL || LeadershipType == NO_LEADER)
        return NULL;
    if(Members[LeaderIndex] == NULL)
        return NULL;
    else
        return Members[LeaderIndex]->GetCoreAddress();
}

//***************************************************************************
//Group::GetMembersQty
//********** Description: **************
//return members' quantity
//********** Parameters:  **************
//none
//---------------------------------------------------------------------------
unsigned short BBECALL Group::GetMembersQty()
{ return MembersQty; }

//***************************************************************************
//Group::GetMemberCoreAddress
//********** Description: **************
//return pointer to a member's Core
//********** Parameters:  **************
//unsigned short nMemberIndex - member's index
//---------------------------------------------------------------------------
Core *BBECALL Group::GetMemberCoreAddress(unsigned short nMemberIndex)
{
    if(Members[nMemberIndex] != NULL)
        return Members[nMemberIndex]->GetCoreAddress();
    else
        return NULL;
}

//***************************************************************************
//Group::GetLeader
//********** Description: **************
//return pointer to the leader Agent
//********** Parameters:  **************
//none
//---------------------------------------------------------------------------
Agent *BBECALL Group::GetLeader()
{
    if(Members == NULL || LeadershipType == NO_LEADER)
        return NULL;
    return Members[LeaderIndex];
}

//***************************************************************************
//Group::GetMember
//********** Description: **************
//return pointer to a member Agent
//********** Parameters:  **************
//unsigned short nMemberIndex - member's index
//---------------------------------------------------------------------------
Agent *BBECALL Group::GetMember(unsigned short nMemberIndex)
{ return Members[nMemberIndex]; }

//***************************************************************************
//Group::CalcNeutralSideMeanness
//********** Description: **************
//return the Combat Overall of all the Agents in the given population who are
//neither the objectives nor the initiators of the current Action nor have a
//positive attitude towards this Group
//********** Parameters:  **************
//Agent **pbspPopulation  - the population
//unsigned short nPopSize - number of Agents in the population
//Action *actToExecute    - pointer to the referenced Action
//-----------------------------------------------------------------------
percentage BBECALL Group::CalcNeutralSideMeanness(Agent **pbspPopulation,
        unsigned short nPopSize, Action *actToExecute)
{
    unsigned short i;
    BOOL nUseWhat;
    Core *Obj,*Initi, *Current;
    percentage NeutralSideMeanness = 0;
    if(pbspPopulation == NULL || actToExecute == NULL
            || ActionsSoloMode)
        return 0;
    nUseWhat = UseTrueStats & 1;    //check LSB
    Obj = actToExecute->GetObjective();
    Initi = actToExecute->GetInitiator();
    if(Obj == NULL || Initi == NULL)
        return 0;

    for(i = 0; i < nPopSize ; i++)
        if(pbspPopulation[i] != NULL)
        {
            Current = pbspPopulation[i]->GetCoreAddress();
            if(Current == NULL)
                continue;
            if(Obj != Current
                && Initi != Current
                && !IsGroupMember(Current->attId)
                //if person's attitude to "SELF" is not positive...
                && GetAttitude(Initi->attId,
                    Current->attPersonal,UseTrueAttitude) < 1
                //...and to the objective not negative...
                //...then the person is neutral
                )
            NeutralSideMeanness += Current->Meanness[nUseWhat];
        }

    return NeutralSideMeanness;
}

//***************************************************************************
//Group::IsGroupMember
//********** Description: **************
//return whether the Agent with the given ID is in this Group
//********** Parameters:  **************
//ID ToCheck - ID to check
//---------------------------------------------------------------------------
BOOL BBECALL Group::IsGroupMember(ID ToCheck)
{
    unsigned short i;
    for(i = 0; i < MembersQty; i++)
        if(Members[i] != NULL)
            if(Members[i]->GetCore().attId == ToCheck)
                return TRUE;
    return FALSE;
}

//***************************************************************************
//Group::SetDefaultMemberCheckFn
//********** Description: **************
//set a new user-defined member check handler
//********** Parameters:  **************
//int (BBECALL *NewMemberChkFn)(void*) - new user-defined member check handler
//---------------------------------------------------------------------------
void BBECALL SetDefaultMemberCheckFn(int (BBECALL *NewMemberChkFn)(void*))
{ gfpMemberChkFn = NewMemberChkFn; }

//---------------------------------------------------------------------------

