#include "_sh_inc_list"
#include "events_inc"

//A function to spawn enemies
void SpawnRandomEnemies(object oPC, string sAreaString, int nEncounterType)
{
    //Identify a region by the area string
    string sRegionString = GetStringLeft(sAreaString, 1);

    //Get PC's level to determine enemies to spawn
    int nLevel = GetHitDice(oPC);

    //Modify the level to spawn weaker enemies for encounters other than with a "mini-boss"
    if (nEncounterType != ENCOUNTER_TYPE_MINIBOSS)
    {
        if (nLevel == 1) nLevel = 0;
        else if (nLevel <= 3) nLevel = 1;
        else if (nLevel <= 20) nLevel = nLevel - 2;
        else nLevel = nLevel - 3;

        //30% of the time (unless it's an encounter with a mini-boss) the PC will find weaker enemies
        if (Random(100) <= 29 && nLevel >= 4)
        {
            if (nLevel <= 20) nLevel = Random(nLevel-1);
            else nLevel = Random(21);
        }
    }

    //Select possible enemies
    SelectEnemies(sRegionString, nLevel, oPC, nEncounterType);
    string sEnemy1 = GetLocalString(oPC, "sEnemy1");
    string sEnemy2 = GetLocalString(oPC, "sEnemy2");
    string sEnemy3 = GetLocalString(oPC, "sEnemy3");
    string sEnemy4 = GetLocalString(oPC, "sEnemy4");
    string sEnemy5 = GetLocalString(oPC, "sEnemy5");
    string sEnemy6 = GetLocalString(oPC, "sEnemy6");
    string sAdd1 = GetLocalString(oPC, "sAdd1");
    string sAdd2 = GetLocalString(oPC, "sAdd2");
    string sAdd3 = GetLocalString(oPC, "sAdd3");
    string sAdd4 = GetLocalString(oPC, "sAdd4");
    DeleteEnemyVar(oPC);

    //Get the number of players in a party and spawn up to 5 times the number of enemies
    int nPlayers = 0;
    object oParty = GetFirstFactionMember(oPC);
    while (GetIsObjectValid(oParty))
    {
        nPlayers++;

        //include henchmen in the count
        object oAssociate = GetAssociate(ASSOCIATE_TYPE_HENCHMAN, oParty, 1);
        int k;
        for (k = 2; GetIsObjectValid(oAssociate); k++)
        {
            if (GetLocalInt(oAssociate, "HenchPosition") > 0) nPlayers++;
            oAssociate = GetAssociate(ASSOCIATE_TYPE_HENCHMAN, oParty, k);
        }

        oParty = GetNextFactionMember(oPC);
    }
    if (nPlayers > 5) nPlayers = 5;
    int j;

    //Spawn 2 enemies for ENCOUNTER_TYPE_FEW, 3 for ENCOUNTER_TYPE_NORMAL and 4 for ENCOUNTER_TYPE_MANY if there are no adds
    location lWaypoint;
    string sWayTag;
    int nWayNumber;
    int i;
    object oCreature;
    if (sAdd1 == "")
    {
        //Spawning main enemies
        if (nEncounterType == ENCOUNTER_TYPE_FEW) nWayNumber = 2;
        if (nEncounterType == ENCOUNTER_TYPE_NORMAL) nWayNumber = 3;
        if (nEncounterType == ENCOUNTER_TYPE_MANY) nWayNumber = 4;
        if (nEncounterType == ENCOUNTER_TYPE_MINIBOSS) nWayNumber = 1;
        for (i = 1; i <= nWayNumber; i++)
            {
                sWayTag = "rand_wp_" + sAreaString + "_" + IntToString(i);
                lWaypoint = GetLocation(GetWaypointByTag(sWayTag));
                for (j = 1; j <= nPlayers; j++)
                {
                    oCreature = CreateObject(OBJECT_TYPE_CREATURE, SelectMain(sEnemy1, sEnemy2, sEnemy3, sEnemy4, sEnemy5, sEnemy6), lWaypoint);
                    ScaleCreature(oCreature, nLevel);
                    AssignCommand(oCreature, ActionRandomWalk());
                }
            }
    }
    //Spawn 4 enemies (3 adds) for ENCOUNTER_TYPE_FEW, 5 (3 adds) for ENCOUNTER_TYPE_NORMAL and 6 (4 adds) for ENCOUNTER_TYPE_MANY if there are no adds
    else
    {
        //Spawning main enemies
        if (nEncounterType == ENCOUNTER_TYPE_FEW) nWayNumber = 1;
        if (nEncounterType == ENCOUNTER_TYPE_NORMAL) nWayNumber = 2;
        if (nEncounterType == ENCOUNTER_TYPE_MANY) nWayNumber = 2;
        if (nEncounterType == ENCOUNTER_TYPE_MINIBOSS) nWayNumber = 1;
        for (i = 1; i <= nWayNumber; i++)
            {
                sWayTag = "rand_wp_" + sAreaString + "_" + IntToString(i);
                lWaypoint = GetLocation(GetWaypointByTag(sWayTag));
                for (j = 1; j <= nPlayers; j++)
                {
                    oCreature = CreateObject(OBJECT_TYPE_CREATURE, SelectMain(sEnemy1, sEnemy2, sEnemy3, sEnemy4, sEnemy5, sEnemy6), lWaypoint);
                    AssignCommand(oCreature, ActionRandomWalk());
                }
            }

        //Spawning adds
        if (nEncounterType == ENCOUNTER_TYPE_FEW) nWayNumber = i+3;
        if (nEncounterType == ENCOUNTER_TYPE_NORMAL) nWayNumber = i+3;
        if (nEncounterType == ENCOUNTER_TYPE_MANY) nWayNumber = i+4;
        if (nEncounterType == ENCOUNTER_TYPE_MINIBOSS) nWayNumber = 0;
        for (i = i; i <= nWayNumber; i++)
            {
                if (nWayNumber == 0) break; //Nothing to do here if it's a mini-boss encounter
                sWayTag = "rand_wp_" + sAreaString + "_" + IntToString(i);
                lWaypoint = GetLocation(GetWaypointByTag(sWayTag));
                for (j = 1; j <= nPlayers; j++)
                {
                    oCreature = CreateObject(OBJECT_TYPE_CREATURE, SelectAdd(sAdd1, sAdd2, sAdd3, sAdd4), lWaypoint);
                    AssignCommand(oCreature, ActionRandomWalk());
                }
            }
    }

}

void main()
{
    //Let's set this area's pseudo OnExit event (as a workaround for players quitting the game)
    SetLocalString(OBJECT_SELF, "OnExit", "pseudo_onexit");

    //A workaround to enable selecting this area in the OnClientLeave event, since GetArea returns OBJECT_INVALID there
    SetLocalObject(GetEnteringObject(), "StoredArea", OBJECT_SELF);

    //A workaround to prevent the OnEnter event body from firing after loading a saved game
    if (GetLocalInt(GetModule(), "LoadCooldown") == TRUE) return;

    //Get the area we're in from the area variable, get the PC and declare some variables
    string sAreaString = GetLocalString(OBJECT_SELF, "AreaString");
    object oPC = GetEnteringObject();
    int nEncounterType;

    //Sanity check
    if (!GetIsPC(oPC)) return;

    //Add to the counter of PCs in the area
    SetLocalInt(OBJECT_SELF, "Players", GetLocalInt(OBJECT_SELF, "Players")+1);
    //TEST
    //FloatingTextStringOnCreature(IntToString(GetLocalInt(OBJECT_SELF, "Players")), oPC);

    //Determine whether a/chestvillage/dungeon should be possible to discover
    int nCanDiscover = GetLocalInt(oPC, "CanDiscover");

    //Spawn an escorted client
    RetrieveClient(oPC);

    //Do nothing if there is already another PC in the area (and the area integer is TRUE) or try a spot/listen check if an ambush is prepared
    if (GetLocalInt(OBJECT_SELF, "IsPopulated") == TRUE)
    {
        int nDC = GetLocalInt(OBJECT_SELF, "Level");

        if (nDC == 0) return;

        int nSkillRoll;
        string sString;
        nSkillRoll = GetSkillRank(SKILL_SPOT, oPC) + d20();
        if (nSkillRoll >= nDC) sString = "[Spot] Ambush detected!";
        else
        {
            nSkillRoll = GetSkillRank(SKILL_LISTEN, oPC) + d20();
            if (nSkillRoll >= nDC) sString = "[Listen] Ambush detected!";
        }
        FloatingTextStringOnCreature(sString, oPC, FALSE);
        WriteTimestampedLogEntry(GetName(oPC)+": populated, OnEnter stops"); //TEST

        return;
    }

    //Otherwise flag this area as populated, so that no event is generated for next players entering it
    SetLocalInt(OBJECT_SELF, "IsPopulated", TRUE);

    //Now select the leader of the entering party, stored earlied - we can delete this variable afterwards
    oPC = GetLocalObject(OBJECT_SELF, "PartyLeader");
    DeleteLocalObject(OBJECT_SELF, "PartyLeader");

    //TEST
    //FloatingTextStringOnCreature(GetName(oPC), oPC);

    //Delete this area from the list of free areas
    string sRegionString = GetStringLeft(sAreaString, 1);
    string sList = "sList"+sRegionString;
    object oList = GetLocalObject(GetModule(), sList);
    int i;
    for (i = 1; i <= ListGetElementCount(oList); i++)
    {
        if (ListGetString(oList, i) == sAreaString) ListRemoveElement(oList, i);
    }

    //TEST
    for (i = 1; i <= ListGetElementCount(oList); i++)
    {
        WriteTimestampedLogEntry(GetName(oPC)+": "+ListGetString(oList, i));
    }

    int nRandom = Random(100)+1;
    WriteTimestampedLogEntry("Area event number (1-25: nothing, 26-80: monsters, 81-100: event): "+IntToString(nRandom)); //TEST
    //FloatingTextStringOnCreature("Wylosowano: "+IntToString(nRandom), oPC); //TEST

    //Spawn a random chest 5%+(players in party)*5% (max 25%) of the time on one of three chest waypoints (only if CanDiscover is TRUE)
    int nChestWaypoint;
    string sChestWaypoint;
    object oChestWp;
    int nPCs;
    object oPartyMember = GetFirstFactionMember(oPC, TRUE);
    while (oPartyMember != OBJECT_INVALID)
    {
        nPCs++;
        oPartyMember = GetNextFactionMember(oPC, TRUE);
    }
    int nPercentage = 5*nPCs+5;
    if (nPercentage >= 25) nPercentage = 25;
    int nTestRand = Random(100)+1;
    WriteTimestampedLogEntry("Chest: "+IntToString(nTestRand)+" | "+IntToString(nPercentage)); //TEST
    if (nTestRand <= nPercentage && nCanDiscover > 1)
    {
        WriteTimestampedLogEntry("Chest spawned"); //TEST
        nChestWaypoint = Random(3)+1;
        sChestWaypoint = IntToString(nChestWaypoint);
        oChestWp = GetWaypointByTag("chest_wp_"+sAreaString+"_"+sChestWaypoint);
        SpawnTreasureChest(oPC, oChestWp);
    }

    //Determine whether a PC encounters a village/dungeon at the end of the area
    if (Random(10) == 0) SetLocalInt(OBJECT_SELF, "DiscoveredVillage", TRUE);
    if (nCanDiscover > 1)
    {
        if (Random(20) == 0) SetLocalInt(OBJECT_SELF, "DiscoveredDungeon", TRUE);
    }

    //Nothing in particular happens 25% of the time
    if (nRandom <= 25) return;

    //Regular enemies encountered 55% of the time
    if (nRandom <= 80)
        {
            nRandom = Random(100)+1;

            //A single strong opponent is encountered 20% of the time
            if (nRandom <= 20) nEncounterType = ENCOUNTER_TYPE_MINIBOSS;

            //A few regular enemies are encountered 20% of the time
            else if (nRandom <= 40) nEncounterType = ENCOUNTER_TYPE_FEW;

            //Many regular enemies are encountered 20% of the time
            else if (nRandom <= 60) nEncounterType = ENCOUNTER_TYPE_MANY;

            //An average number of regular enemies is encountered 40% of the time
            else nEncounterType = ENCOUNTER_TYPE_NORMAL;
        }

    //An event occurs 20% of the time
    else
        {
            switch (Random(10))
            {
                case 0: SpawnNemesisEvent(oPC); break;
                case 1: SpawnEscortEvent(oPC); break;
                case 2: SpawnTributeEvent(oPC); break;
                case 3: SpawnAmbushEvent(oPC); break;
                case 4: SpawnAdventurerEvent(oPC); break;
                case 5: SpawnDemonEvent(oPC); break;
                case 6: SpawnBulliedEvent(oPC); break;
                case 7: SpawnMerchantEvent(oPC); break;
                case 8: SpawnBossEvent(oPC); break;
                case 9: SpawnFriendlyEvent(oPC); break;
            }
        }

    //If it's a regular enemy encounter, spawn them
    SpawnRandomEnemies(oPC, sAreaString, nEncounterType);
}