Untitled

 avatar
unknown
plain_text
a year ago
27 kB
9
Indexable
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

public enum Faction
{
    Civilian,
    Gang,
    Mafia,
    Police
}

public enum SubFaction
{
    None,

    MafiaBlack,
    MafiaBlue,
    MafiaBrown,
    MafiaGreen,
    MafiaOrange,
    MafiaPurple,
    MafiaRed,
    MafiaWhite,

    GangBlack,
    GangBlue,
    GangGreen,
    GangOrange,
    GangPurple,
    GangRed,
    GangYelow
}

public enum Occupation
{
    Unemployed,
    PoliceMan,
    Doctor,
    FireFighter,
    Mechanic,
    Model,
    TopModel,
    Cashier,
    Taxi,
    Musician,
    Programmer,
}

public enum Interests
{
    Sports,
    Park,
    Games
}

public enum Gender
{
    Male,
    Female
}

public enum SkinColor
{
    White,
    Black
}

public enum HairColor
{
    Black,
    Blond,
    Brun,
    Ginger,
    Yellow
}

public enum NPCTask
{
    Sleeping,
    Working,
    Eating,
    RunningErrands,
    FreeTime
}

public enum LocationType
{
    Home,
    Work
}

public enum LocationCategory
{
    None,
    Supermarket,
    Park,
    Restaurant,
    Home,
    Police,
    Hospital,
    FireFighter,
    Mechanic,
    Models,
    Musician,
    Programmer
}

public class NpcData : MonoBehaviour
{
    [Header("NPC Data")]   
    public string npcName;
    public int id;
    public Gender gender;
    public SkinColor skinColor;
    public HairColor hairColor;
    public Faction faction;
    public SubFaction subFaction;
    public Occupation Occupation;
    public Interests interests;
    public NPCTask currentTask;
    public int homeID; // Index into NPCSpawner.homes
    public int workID; // Index into appropriate array (e.g. supermarkets, parks, etc.)
    public LocationCategory workLocationCategory;
    public int numberOfTimesLate;
    public bool nightShift;
    public CarController ownedCar;

    public int salary;
    [Range(-10, 10)] public int playerRelation;
    public int[] friendListid;
    public int[] enemyListId;
    
    [Header("NPC Stats")]
    [Min(1)] public int strength = 1;
    [Min(1)] public int charisma = 1;
    [Min(1)] public int perception = 1;
    [Min(1)] public int dexterity = 1;
    [Min(1)] public int manipulation = 1;
    [Min(1)] public int intelligence = 1;
    [Min(1)] public int stamina = 1;
    [Min(1)] public int appearance = 1;
    [Min(1)] public int wits = 1;

    [Header("NPC Skills")]
    public int brawl;
    public int awareness;
    public int etiquette;
    public int melee;
    public int investigation;
    public int stealth;
    public int firearms;
    public int medicine;
    public int larceny;
    public int athletics;
    public int technology;
    public int presence;
    public int drive;
    public int occult;
    public int survival;

    private EnemyAI enemyAI;
    private Animator animator;
    private RuntimeAnimatorController defaultController;
    private NPCSpawner npcSpawner;
    private CarSavingSystem carSavingSystem;

    private void Awake()
    {
        enemyAI = GetComponent<EnemyAI>();
        npcSpawner = FindObjectOfType<NPCSpawner>();
        carSavingSystem = FindObjectOfType<CarSavingSystem>();
        animator = enemyAI.anim;
        defaultController = animator.runtimeAnimatorController;
        InitializeSkillsAndJobs();
    }

    private void Update()
    {
        UpdateTaskBasedOnTime();
    }

    public void InitializeSkillsAndJobs()
    {
        //unlike skills stats start default by 1
        strength = 1;
        charisma = 1;
        perception = 1;
        dexterity = 1;
        manipulation = 1;
        intelligence = 1;
        stamina = 1;
        appearance = 1;
        wits = 1;

        int xpToDistrubute = npcSpawner.xpForThisLevel;
        DistributeXP(xpToDistrubute);
        AssignName();
        AssignInterest();
        AssignJob();
        AssignJobOutfit();
        UpdateMoneyBasedOnSalary();
        AssignLocations();
        AssignCar();
    }

    public void AssignCar()
    {
        if(true /*salary > 2 && drive > 0*/)
        {
            if (enemyAI.ownedCar)
            {
                //Resets previous Assigned cars on spawn
                enemyAI.ownedCar.npcOwner = null;
                enemyAI.ownedCar = null;


            }
        }
    }

    private void WearCivilianOutfit()
    {
        animator.runtimeAnimatorController = defaultController;
    }

    private void AssignLocations()
    {
        // 1. Select a random home
        Transform randomHome = npcSpawner.homes[UnityEngine.Random.Range(0, npcSpawner.homes.Length)];
        homeID = Array.IndexOf(npcSpawner.homes, randomHome);

        // 2. Determine work location
        Transform workLoc = randomHome; // default fallback
        workID = -1;
        workLocationCategory = LocationCategory.None;

        if (faction != Faction.Gang && faction != Faction.Mafia && Occupation != Occupation.Unemployed)
        {
            switch (Occupation)
            {
                case Occupation.PoliceMan:
                    workLoc = npcSpawner.policeStation;
                    workLocationCategory = LocationCategory.Police;
                    workID = 0;
                    break;

                case Occupation.Doctor:
                    workLoc = npcSpawner.hospital;
                    workLocationCategory = LocationCategory.Hospital;
                    workID = 0;
                    break;

                case Occupation.FireFighter:
                    workLoc = npcSpawner.fireFighterStation;
                    workLocationCategory = LocationCategory.FireFighter;
                    workID = 0;
                    break;

                case Occupation.Mechanic:
                    workLoc = npcSpawner.mechanicStation;
                    workLocationCategory = LocationCategory.Mechanic;
                    workID = 0;
                    break;

                case Occupation.Model:
                case Occupation.TopModel:
                    workLoc = npcSpawner.modelsStudio;
                    workLocationCategory = LocationCategory.Models;
                    workID = 0;
                    break;

                case Occupation.Musician:
                    workLoc = npcSpawner.musicianStudio;
                    workLocationCategory = LocationCategory.Musician;
                    workID = 0;
                    break;

                case Occupation.Programmer:
                    workLoc = npcSpawner.programmerCompany;
                    workLocationCategory = LocationCategory.Programmer;
                    workID = 0;
                    break;

                case Occupation.Cashier:
                    Transform randomSuperMarket = npcSpawner.superMarkets[UnityEngine.Random.Range(0, npcSpawner.superMarkets.Length)];
                    workLoc = randomSuperMarket;
                    workLocationCategory = LocationCategory.Supermarket;
                    workID = Array.IndexOf(npcSpawner.superMarkets, randomSuperMarket);
                    break;

                case Occupation.Taxi:
                    Transform randomPark = npcSpawner.parks[UnityEngine.Random.Range(0, npcSpawner.parks.Length)];
                    workLoc = randomPark;
                    workLocationCategory = LocationCategory.Park;
                    workID = Array.IndexOf(npcSpawner.parks, randomPark);
                    break;
            }
        }

        // 3. Merge restaurant + park + home into eat locations
        List<Transform> eatLocsList = new List<Transform>();
        eatLocsList.AddRange(npcSpawner.restaurants);
        eatLocsList.AddRange(npcSpawner.parks);
        eatLocsList.Add(randomHome);
        Transform[] eatLocs = eatLocsList.ToArray();

        // 4. Assign locations to EnemyAI
        enemyAI.SetLocations(randomHome, npcSpawner.parks, workLoc, eatLocs, npcSpawner.superMarkets);
    }

    public void UpdateMoneyBasedOnSalary()
    {
        enemyAI.UpdateMoney(salary * 50);
    }

    public void UpdateTaskBasedOnTime()
    {
        DateTime now = TimeDisplay.instance.gameTime;
        TimeSpan current = new TimeSpan(now.Hour, now.Minute, 0);

        if (nightShift)
        {
            // === Night Shift Schedule ===

            int sleepHours = Mathf.Clamp(10 - stamina, 5, 9);
            TimeSpan sleepStart = new TimeSpan(8, 0, 0);
            TimeSpan sleepEnd = sleepStart.Add(TimeSpan.FromHours(sleepHours));

            TimeSpan errandStart = sleepEnd;
            TimeSpan errandEnd = errandStart.Add(TimeSpan.FromMinutes(30));

            TimeSpan freeTimeStart = errandEnd;
            TimeSpan dinnerStart = new TimeSpan(22, 0, 0);
            TimeSpan dinnerEnd = new TimeSpan(23, 0, 0);

            TimeSpan workStart1 = new TimeSpan(23, 0, 0);  // After dinner
            TimeSpan workEnd1 = new TimeSpan(3, 0, 0);     // Before lunch

            TimeSpan lunchStart = new TimeSpan(3, 0, 0);
            TimeSpan lunchEnd = new TimeSpan(4, 0, 0);

            TimeSpan workStart2 = new TimeSpan(4, 0, 0);
            TimeSpan workEnd2 = new TimeSpan(8, 0, 0);

            // Assign task based on current time
            if (current >= sleepStart && current < sleepEnd)
            {
                currentTask = NPCTask.Sleeping;
            }
            else if (current >= errandStart && current < errandEnd)
            {
                currentTask = NPCTask.RunningErrands;
            }
            else if (current >= freeTimeStart && current < dinnerStart)
            {
                currentTask = NPCTask.FreeTime;
            }
            else if (current >= dinnerStart && current < dinnerEnd)
            {
                currentTask = NPCTask.Eating;
            }
            else if (current >= workStart1 || current < workEnd1)
            {
                currentTask = NPCTask.Working;
            }
            else if (current >= lunchStart && current < lunchEnd)
            {
                currentTask = NPCTask.Eating;
            }
            else if (current >= workStart2 && current < workEnd2)
            {
                currentTask = NPCTask.Working;
            }
            else
            {
                currentTask = NPCTask.FreeTime; // fallback
            }
        }
        else
        {
            // === Day Shift ===

            int sleepHours = Mathf.Clamp(10 - stamina, 5, 9);
            int sleepStartHour = 22;
            int sleepEndHour = (sleepStartHour + sleepHours) % 24;

            TimeSpan lunchStart = new TimeSpan(12, 0, 0);
            TimeSpan lunchEnd = new TimeSpan(13, 0, 0);
            TimeSpan errandStart = new TimeSpan(13, 0, 0);
            TimeSpan errandEnd = new TimeSpan(13, 30, 0);
            TimeSpan dinnerStart = new TimeSpan(18, 0, 0);
            TimeSpan dinnerEnd = new TimeSpan(19, 0, 0);

            bool isSleeping = sleepStartHour > sleepEndHour
                ? (now.Hour >= sleepStartHour || now.Hour < sleepEndHour)
                : (now.Hour >= sleepStartHour && now.Hour < sleepEndHour);

            if (isSleeping)
            {
                currentTask = NPCTask.Sleeping;
            }
            else if (current >= lunchStart && current < lunchEnd)
            {
                currentTask = NPCTask.Eating;
            }
            else if (current >= errandStart && current < errandEnd)
            {
                currentTask = NPCTask.RunningErrands;
            }
            else if (current >= dinnerStart && current < dinnerEnd)
            {
                currentTask = NPCTask.Eating;
            }
            else
            {
                if (Occupation == Occupation.Unemployed)
                {
                    if (now.Hour < 12 || now.Hour >= 19)
                        currentTask = NPCTask.FreeTime;
                    else
                        currentTask = NPCTask.FreeTime;
                }
                else
                {
                    if (now.Hour < 12 || (now.Hour >= 13 && now.Hour < 18))
                        currentTask = NPCTask.Working;
                    else if (now.Hour >= 19)
                        currentTask = NPCTask.FreeTime;
                }
            }
        }
    }

    public void AssignJobOutfit()
    {
        // === POLICE ===
        if (Occupation == Occupation.PoliceMan)
        {
            if (gender == Gender.Female)
                animator.runtimeAnimatorController = npcSpawner.policeFemaleWhite;
            else if (skinColor == SkinColor.White)
                animator.runtimeAnimatorController = npcSpawner.policeMaleWhite;
            else
                animator.runtimeAnimatorController = npcSpawner.policeMaleBlack;
            return;
        }

        // === DOCTOR ===
        if (Occupation == Occupation.Doctor)
        {
            if (gender == Gender.Female)
            {
                animator.runtimeAnimatorController = npcSpawner.doctorFemaleWhite;
            }     
            else if (skinColor == SkinColor.White)
            {
                if(hairColor == HairColor.Black)
                    animator.runtimeAnimatorController = npcSpawner.doctorMaleWhiteBlackHair;
                else if(hairColor == HairColor.Blond)
                    animator.runtimeAnimatorController = npcSpawner.doctorMaleWhiteBlondHair;
                else if(hairColor == HairColor.Brun)
                    animator.runtimeAnimatorController = npcSpawner.doctorMaleWhiteBrunHair;
                else if(hairColor == HairColor.Ginger)
                    animator.runtimeAnimatorController = npcSpawner.doctorMaleWhiteGingerHair;
            }        
            else
            {
                animator.runtimeAnimatorController = npcSpawner.doctorMaleBlack;
            }        
            return;
        }

        // === FIREFIGHTER ===
        if (Occupation == Occupation.FireFighter)
        {
            if (gender == Gender.Female)
                animator.runtimeAnimatorController = npcSpawner.fireFighterFemaleWhite;
            else if (skinColor == SkinColor.White)
                animator.runtimeAnimatorController = npcSpawner.fireFighterMaleWhite;
            else
                animator.runtimeAnimatorController = npcSpawner.fireFighterMaleBlack;
            return;
        }

        // === MECHANIC ===
        if (Occupation == Occupation.Mechanic)
        {
            if (gender == Gender.Female)
                animator.runtimeAnimatorController = npcSpawner.mechanicFemaleWhite;
            else if (skinColor == SkinColor.White)
                animator.runtimeAnimatorController = npcSpawner.mechanicMaleWhite;
            else
                animator.runtimeAnimatorController = npcSpawner.mechanicMaleBlack;
            return;
        }

        // === GANG MEMBERS ===
        if (faction == Faction.Gang)
        {
            RuntimeAnimatorController controller = null;
            switch (subFaction)
            {
                case SubFaction.GangBlack: controller = GetThugController(skinColor, npcSpawner.afroStreetThugBlack, npcSpawner.whiteStreetThugBlack); break;
                case SubFaction.GangBlue: controller = GetThugController(skinColor, npcSpawner.afroStreetThugBlue, npcSpawner.whiteStreetThugBlue); break;
                case SubFaction.GangGreen: controller = GetThugController(skinColor, npcSpawner.afroStreetThugGreen, npcSpawner.whiteStreetThugGreen); break;
                case SubFaction.GangOrange: controller = GetThugController(skinColor, npcSpawner.afroStreetThugOrange, npcSpawner.whiteStreetThugOrange); break;
                case SubFaction.GangPurple: controller = GetThugController(skinColor, npcSpawner.afroStreetThugPurple, npcSpawner.whiteStreetThugPurple); break;
                case SubFaction.GangRed: controller = GetThugController(skinColor, npcSpawner.afroStreetThugRed, npcSpawner.whiteStreetThugRed); break;
                case SubFaction.GangYelow: controller = GetThugController(skinColor, npcSpawner.afroStreetThugYellow, npcSpawner.whiteStreetThugYellow); break;
            }
            animator.runtimeAnimatorController = controller;
            return;
        }

        // === MAFIA MEMBERS ===
        if (faction == Faction.Mafia)
        {
            switch (subFaction)
            {
                case SubFaction.MafiaBlack: animator.runtimeAnimatorController = npcSpawner.mafiaBlack; break;
                case SubFaction.MafiaBlue: animator.runtimeAnimatorController = npcSpawner.mafiaBlue; break;
                case SubFaction.MafiaBrown: animator.runtimeAnimatorController = npcSpawner.mafiaBrown; break;
                case SubFaction.MafiaGreen: animator.runtimeAnimatorController = npcSpawner.mafiaGreen; break;
                case SubFaction.MafiaOrange: animator.runtimeAnimatorController = npcSpawner.mafiaOrange; break;
                case SubFaction.MafiaPurple: animator.runtimeAnimatorController = npcSpawner.mafiaPurple; break;
                case SubFaction.MafiaRed: animator.runtimeAnimatorController = npcSpawner.mafiaRed; break;
                case SubFaction.MafiaWhite: animator.runtimeAnimatorController = npcSpawner.mafiaWhite; break;
            }
            return;
        }

        // === CIVILIAN DEFAULT ===
        WearCivilianOutfit();  // <- This ensures non-uniform roles get default appearance

        RuntimeAnimatorController GetThugController(SkinColor skin, RuntimeAnimatorController blackController, RuntimeAnimatorController whiteController)
        {
            return skin == SkinColor.Black ? blackController : whiteController;
        }
    }

    private void AssignName()
    {
        System.Random rng = new System.Random();

        if (gender == Gender.Male)
        {
            npcName = maleNames[rng.Next(maleNames.Count)];
        }
        else
        {
            npcName = femaleNames[rng.Next(femaleNames.Count)];
        }
    }

    private void AssignInterest()
    {
        Array values = Enum.GetValues(typeof(Interests));
        interests = (Interests)values.GetValue(UnityEngine.Random.Range(0, values.Length));
    }

    private void AssignJob()
    {
        salary = 0;
        Occupation = Occupation.Unemployed;
        faction = Faction.Civilian;
        subFaction = SubFaction.None;

        // Hardest jobs first
        if (medicine >= 3 && intelligence >= 3)
        {
            Occupation = Occupation.Doctor;
            salary = 3;
            nightShift = UnityEngine.Random.value < 0.5f;
            Debug.Log($"{npcName} assigned job: Doctor");
            return;
        }
        else if (firearms >= 2 && athletics >= 2)
        {
            faction = Faction.Police;
            Occupation = Occupation.PoliceMan;
            salary = 2;
            nightShift = UnityEngine.Random.value < 0.5f;
            Debug.Log($"{npcName} assigned job: PoliceMan");
            return;
        }
        else if (strength >= 3 && athletics >= 3)
        {
            Occupation = Occupation.FireFighter;
            salary = 3;
            nightShift = UnityEngine.Random.value < 0.5f;
            Debug.Log($"{npcName} assigned job: FireFighter");
            return;
        }
        else if (technology >= 3 && NPCSpawner.numberOfProgrammers < 10)
        {
            Occupation = Occupation.Programmer;
            salary = 2;
            NPCSpawner.numberOfProgrammers++;
            Debug.Log($"{npcName} assigned job: Programmer");
            return;
        }
        else if (drive >= 1 && technology >= 2 && NPCSpawner.numberOfMechanics < 10)
        {
            Occupation = Occupation.Mechanic;
            salary = 2;
            NPCSpawner.numberOfMechanics++;
            Debug.Log($"{npcName} assigned job: Mechanic");
            return;
        }
        else if (drive >= 3)
        {
            Occupation = Occupation.Taxi;
            salary = 1 + drive;
            Debug.Log($"{npcName} assigned job: Taxi Driver");
            return;
        }
        else if (dexterity >= 3 && NPCSpawner.numberOfMusicians < 10)
        {
            Occupation = Occupation.Musician;
            salary = 1 + dexterity;
            NPCSpawner.numberOfMusicians++;
            Debug.Log($"{npcName} assigned job: Musician");
            return;
        }
        else if (appearance >= 5 && NPCSpawner.numberOfSuperModels < 5)
        {
            Occupation = Occupation.TopModel;
            salary = 4;
            NPCSpawner.numberOfSuperModels++;
            Debug.Log($"{npcName} assigned job: Top Model");
            return;
        }
        else if (appearance >= 3 && NPCSpawner.numberOfModels < 5)
        {
            Occupation = Occupation.Model;
            salary = 3;
            NPCSpawner.numberOfModels++;
            Debug.Log($"{npcName} assigned job: Model");
            return;
        }
        else if (charisma >= 2 && NPCSpawner.numberOfCashiers < 10)
        {
            Occupation = Occupation.Cashier;
            NPCSpawner.numberOfCashiers++;
           salary = 1;
            Debug.Log($"{npcName} assigned job: Cashier");
            return;
        }

        if (firearms >= 1 || melee >= 1)
        {
            // 50% chance to become a criminal
            if (UnityEngine.Random.value < 0.5f)
            {
                // 50% chance between Gang and Mafia
                bool isMafia = UnityEngine.Random.value < 0.5f;
                nightShift = UnityEngine.Random.value < 0.5f;

                faction = isMafia ? Faction.Mafia : Faction.Gang;
                subFaction = isMafia ? SubFaction.MafiaBlack : SubFaction.GangBlack; 
                Debug.Log($"{npcName} had combat skills and became a {faction} member.");
                return;
            }
        }

        Debug.Log($"{npcName} is Unemployed.");
    }

    private void DistributeXP(int xpToDistribute)
    {
        System.Random rng = new System.Random();

        List<(string name, Func<int> get, Action<int> set, bool isStat)> allAttributes = new()
        {
            // Stats (true)
            ("Strength", () => strength, v => strength = v, true),
            ("Charisma", () => charisma, v => charisma = v, true),
            ("Perception", () => perception, v => perception = v, true),
            ("Dexterity", () => dexterity, v => dexterity = v, true),
            ("Manipulation", () => manipulation, v => manipulation = v, true),
            ("Intelligence", () => intelligence, v => intelligence = v, true),
            ("Stamina", () => stamina, v => stamina = v, true),
            ("Appearance", () => appearance, v => appearance = v, true),
            ("Wits", () => wits, v => wits = v, true),

            // Skills (false)
            ("Brawl", () => brawl, v => brawl = v, false),
            ("Awareness", () => awareness, v => awareness = v, false),
            ("Etiquette", () => etiquette, v => etiquette = v, false),
            ("Melee", () => melee, v => melee = v, false),
            ("Investigation", () => investigation, v => investigation = v, false),
            ("Stealth", () => stealth, v => stealth = v, false),
            ("Firearms", () => firearms, v => firearms = v, false),
            ("Medicine", () => medicine, v => medicine = v, false),
            ("Larceny", () => larceny, v => larceny = v, false),
            ("Athletics", () => athletics, v => athletics = v, false),
            ("Technology", () => technology, v => technology = v, false),
            ("Presence", () => presence, v => presence = v, false),
            ("Drive", () => drive, v => drive = v, false),
            ("Occult", () => occult, v => occult = v, false),
            ("Survival", () => survival, v => survival = v, false),
        };

        int safety = 50;
        while (xpToDistribute > 0 && safety-- > 0)
        {
            var entry = allAttributes[rng.Next(allAttributes.Count)];
            int currentLevel = entry.Item2();

            //Skip if already maxed
            if (currentLevel >= 5)
                continue;

            int xpRequired = entry.isStat
                ? currentLevel * 4
                : (currentLevel + 1) * 2;

            if (xpToDistribute >= xpRequired)
            {
                xpToDistribute -= xpRequired;
                entry.Item3(currentLevel + 1);
                //Debug.Log($"{npcName} leveled up {entry.name} to {currentLevel + 1} (cost {xpRequired})");
            }
        }

        if (safety <= 0 && xpToDistribute > 0)
        {
            //Debug.LogWarning("Safety cap reached. Using fallback deterministic leveling.");

            foreach (var entry in allAttributes)
            {
                while (true)
                {
                    int currentLevel = entry.Item2();

                    if (currentLevel >= 5)
                        break;

                    int xpRequired = entry.isStat
                        ? currentLevel * 4
                        : (currentLevel + 1) * 2;

                    if (xpToDistribute >= xpRequired)
                    {
                        xpToDistribute -= xpRequired;
                        entry.Item3(currentLevel + 1);
                        //Debug.Log($"{npcName} leveled up {entry.name} to {currentLevel + 1} (cost {xpRequired})");
                    }
                    else break;
                }

                if (xpToDistribute <= 0)
                    break;
            }

            //Debug.Log($"XP leftover after fallback leveling: {xpToDistribute}");
        }
    }

    private static readonly List<string> maleNames = new()
    {
        "Liam",
        "Noah",
        "James",
        "Oliver",
        "Benjamin",
        "Lucas",
        "Henry",
        "Alexander",
        "Mason",
        "Ethan",
        "Logan",
        "Jackson",
        "Leo",
        "Sebastian",
        "Jack",
        "Owen",
        "Caleb",
        "Nathan",
        "Aiden",
        "Daniel"
    };

    private static readonly List<string> femaleNames = new()
    {
        "Emma",
        "Olivia",
        "Ava",
        "Isabella",
        "Sophia",
        "Charlotte",
        "Amelia",
        "Mia",
        "Harper",
        "Evelyn",
        "Ella",
        "Scarlett",
        "Lily",
        "Grace",
        "Chloe",
        "Zoe",
        "Nora",
        "Aria",
        "Abigail",
        "Emily"
    };
}
Editor is loading...
Leave a Comment