public static void SpawnPandaZombie(NPCType typeToSpawn, Path path, Colony colony, IPandaBoss boss)
        {
            var monster = new Zombie(typeToSpawn, path, colony.Owner);

            if (boss != null)
            {
                if (boss.ZombieHPBonus != 0)
                {
                    var fi = monster
                             .GetType().GetField("health",
                                                 BindingFlags.GetField | BindingFlags.NonPublic |
                                                 BindingFlags.Instance);

                    fi.SetValue(monster, (float)fi.GetValue(monster) + boss.ZombieHPBonus);
                }
            }

            if (colony.FollowerCount > Configuration.GetorDefault("MinColonistsCountForBosses", 15))
            {
                var fi = monster
                         .GetType().GetField("health",
                                             BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance);

                fi.SetValue(monster, (float)fi.GetValue(monster) + colony.FollowerCount * .05f);
            }

            ModLoader.TriggerCallbacks <IMonster>(ModLoader.EModCallbackType.OnMonsterSpawned, monster);
            MonsterTracker.Add(monster);
            colony.OnZombieSpawn(true);
        }
        public static void ChangedSetting(TupleStruct <Players.Player, JSONNode, string> data)
        {
            switch (data.item3)
            {
            case "world_settings":
                var ps = PlayerState.GetPlayerState(data.item1);

                if (ps != null && data.item2.GetAsOrDefault(_Monsters, Convert.ToInt32(ps.MonstersEnabled)) != Convert.ToInt32(ps.MonstersEnabled))
                {
                    if (!Configuration.GetorDefault("MonstersCanBeDisabled", true))
                    {
                        PandaChat.Send(data.item1, "The server administrator had disabled the changing of Monsters.", ChatColor.red);
                    }
                    else
                    {
                        ps.MonstersEnabled = data.item2.GetAsOrDefault(_Monsters, Convert.ToInt32(ps.MonstersEnabled)) != 0;
                    }

                    PandaChat.Send(data.item1, "Settlers! Mod Monsters are now " + (ps.MonstersEnabled ? "on" : "off"), ChatColor.green);

                    if (!ps.MonstersEnabled)
                    {
                        MonsterTracker.KillAllZombies(data.item1);
                    }
                }

                break;
            }
        }
Exemple #3
0
        public void Update()
        {
            if (!World.Initialized || AIManager.IsBusy())
            {
                return;
            }

            WorldSettings wsettings         = ServerManager.WorldSettings;
            bool          toSpawnZombies    = wsettings.ZombiesEnabled && (TimeCycle.ShouldSpawnMonsters || wsettings.MonstersDayTime);
            int           monsterMultiplier = wsettings.MonstersDoubled ? 2 : 1;
            double        timeSinceStart    = Pipliz.Time.SecondsSinceStartDouble;

            var banners = BannerTracker.GetBanners();

            for (int i = 0; i < banners.Count; i++)
            {
                Banner banner = banners.GetValueAtIndex(i);

                if (banner == null || !banner.KeyLocation.IsValid)
                {
                    continue;
                }

                Colony colony = Colony.Get(banner.Owner);

                if (toSpawnZombies)
                {
                    float zombiesMax = GetMaxZombieCount(colony.FollowerCount) * monsterMultiplier;

                    if (zombiesMax > 0f)
                    {
                        if (MonsterTracker.MonstersPerPlayer(banner.Owner) < zombiesMax)
                        {
                            if (colony.InSiegeMode)
                            {
                                if (timeSinceStart - colony.LastSiegeModeSpawn < siegeModeCooldown)
                                {
                                    continue;
                                }
                                else
                                {
                                    colony.LastSiegeModeSpawn = timeSinceStart;
                                }
                            }

                            SpawnZombie(colony, banner, GetMosterType(colony.FollowerCount, zombiesMax));
                        }
                    }
                    else
                    {
                        colony.OnZombieSpawn(true);
                    }
                }
                else
                {
                    colony.OnZombieSpawn(true);
                }
            }
        }
Exemple #4
0
        public void PerformGoal(ref NPCBase.NPCState state)
        {
            Pipliz.Vector3Int position = GuardJob.Position;
            state.SetCooldown(1);
            state.JobIsDone = true;

            if (!Job.NPC.Inventory.Contains(GuardSettings.ShootItem))
            {
                Shop(Job, ref Job.NPC.state);
                return;
            }

            if (GuardJob.HasTarget)
            {
                UnityEngine.Vector3 npcPos    = position.Add(0, 1, 0).Vector;
                UnityEngine.Vector3 targetPos = GuardJob.Target.PositionToAimFor;
                if (VoxelPhysics.CanSee(npcPos, targetPos))
                {
                    GuardJob.NPC.LookAt(targetPos);
                    ShootAtTarget(GuardJob, ref state);
                    return;
                }
            }

            GuardJob.Target = MonsterTracker.Find(position.Add(0, 1, 0), GuardSettings.Range, GuardSettings.Damage);

            if (GuardJob.HasTarget)
            {
                GuardJob.NPC.LookAt(GuardJob.Target.PositionToAimFor);
                ShootAtTarget(GuardJob, ref state);
                return;
            }

            state.SetCooldown(GuardSettings.CooldownSearchingTarget * Pipliz.Random.NextFloat(0.9f, 1.1f));
            UnityEngine.Vector3 pos = GuardJob.NPC.Position.Vector;

            if (GuardSettings.BlockTypes.ContainsByReference(GuardJob.BlockType, out int index))
            {
                switch (index)
                {
                case 1:
                    pos.x += 1f;
                    break;

                case 2:
                    pos.x -= 1f;
                    break;

                case 3:
                    pos.z += 1f;
                    break;

                case 4:
                    pos.z -= 1f;
                    break;
                }
            }
            GuardJob.NPC.LookAt(pos);
        }
        public override Vector3Int GetJobLocation()
        {
            var currentPos = usedNPC.Position;

            if (_playerState.CallToArmsEnabled && _weapon != null)
            {
                _target = MonsterTracker.Find(currentPos, _weapon.range, _weapon.shootDamage);

                if (_target != null)
                {
                    return(currentPos);
                }
                else
                {
                    _target = MonsterTracker.Find(currentPos, CALL_RAD, _weapon.shootDamage);

                    if (_target != null)
                    {
                        var ranged = _weapon.range - 5;

                        if (ranged < 0)
                        {
                            ranged = 1;
                        }

                        position = new Vector3Int(_target.Position).Add(ranged, 0, ranged);
                        position = Server.AI.AIManager.ClosestPosition(position, currentPos);

                        if (!Server.AI.AIManager.CanStandAt(position))
                        {
                            _tmpVals.Set(COOLDOWN_KEY, _weapon.cooldownMissingItem);
                            _waitingFor++;
                        }
                        else
                        {
                            return(position);
                        }
                    }
                    else
                    {
                        _tmpVals.Set(COOLDOWN_KEY, _weapon.cooldownMissingItem);
                        _waitingFor++;
                    }
                }
            }

            if (_waitingFor > 10)
            {
                var banner = BannerManager.GetClosestBanner(usedNPC.Colony.Owner, currentPos);

                if (banner != null)
                {
                    return(banner.KeyLocation);
                }
            }

            return(currentPos);
        }
        public Vector3Int GetJobLocation()
        {
            var currentPos = NPC.Position;

            if (_colonyState.CallToArmsEnabled && _weapon != null)
            {
                _target = MonsterTracker.Find(currentPos, _weapon.Range, _weapon.Damage);

                if (_target != null)
                {
                    return(currentPos);
                }

                _target = MonsterTracker.Find(currentPos, CALL_RAD, _weapon.Damage);

                if (_target != null)
                {
                    var ranged = _weapon.Range - 5;

                    if (ranged < 0)
                    {
                        ranged = 1;
                    }

                    Position = new Vector3Int(_target.Position).Add(ranged, 0, ranged);
                    PathingManager.TryCanStandNear(Position, out var canStandNear, out var newPosition);

                    if (!canStandNear || (!PathingManager.TryCanStandAt(newPosition, out var canStand) && canStand))
                    {
                        _tmpVals.SetAs(COOLDOWN_KEY, _weapon.CooldownMissingItem);
                        _waitingFor++;
                    }
                    else
                    {
                        return(Position);
                    }
                }
                else
                {
                    _tmpVals.SetAs(COOLDOWN_KEY, _weapon.CooldownMissingItem);
                    _waitingFor++;
                }
            }

            if (_waitingFor > 10)
            {
                var banner = NPC.Colony.GetClosestBanner(currentPos);

                if (banner != null)
                {
                    return(banner.Position);
                }
            }

            return(currentPos);
        }
        public void SpawnZombie(NPCType typeToSpawn, Path path, Colony colony, bool notifyColonyForSiegeMode = true)
        {
            IMonster monster = new Zombie(typeToSpawn, path, colony);

            ModLoader.Callbacks.OnMonsterSpawned.Invoke(monster);
            MonsterTracker.Add(monster);
            if (notifyColonyForSiegeMode)
            {
                colony.OnZombieSpawn(true);
            }
        }
Exemple #8
0
 public override void OnNPCAtJob(ref NPCBase.NPCState state)
 {
     if (TimeCycle.TotalTime > RecruitedTime + Configuration.MititiaTermOfDuty)
     {
         OnRemovedNPC();
     }
     if (HasTarget)
     {
         Vector3 npcPos    = position.Add(0, 1, 0).Vector;
         Vector3 targetPos = target.PositionToAimFor;
         if (General.Physics.Physics.CanSee(npcPos, targetPos))
         {
             usedNPC.LookAt(targetPos);
             ShootAtTarget(ref state); // <- sets cooldown
             return;
         }
         else
         {
             target = null;
         }
     }
     target = MonsterTracker.Find(position.Add(0, 1, 0), guardsettings.range, guardsettings.shootDamage);
     if (HasTarget)
     {
         usedNPC.LookAt(target.PositionToAimFor);
         ShootAtTarget(ref state); // <- sets cooldown
     }
     else
     {
         state.SetCooldown(guardsettings.cooldownSearchingTarget);
         Vector3 pos = usedNPC.Position.Vector;
         if (blockType == guardsettings.typeXP)
         {
             pos += Vector3.right;
         }
         else if (blockType == guardsettings.typeXN)
         {
             pos += Vector3.left;
         }
         else if (blockType == guardsettings.typeZP)
         {
             pos += Vector3.forward;
         }
         else if (blockType == guardsettings.typeZN)
         {
             pos += Vector3.back;
         }
         usedNPC.LookAt(pos);
     }
 }
 public override void OnNPCAtJob(ref NPCBase.NPCState state)
 {
     if (HasTarget)
     {
         Vector3 npcPos    = position.Add(0, 1, 0).Vector;
         Vector3 targetPos = target.PositionToAimFor;
         if (General.Physics.Physics.CanSee(npcPos, targetPos))
         {
             usedNPC.LookAt(targetPos);
             ShootAtTarget(ref state);                     // <- sets cooldown
             return;
         }
         else
         {
             target = null;
         }
     }
     target = MonsterTracker.Find(position.Add(0, 1, 0), guardSettings.range, guardSettings.shootDamage);
     if (HasTarget)
     {
         usedNPC.LookAt(target.PositionToAimFor);
         ShootAtTarget(ref state);                 // <- sets cooldown
     }
     else
     {
         state.SetCooldown(guardSettings.cooldownSearchingTarget);
         if (worldTypeChecked)
         {
             Vector3 pos = usedNPC.Position.Vector;
             if (worldType == guardSettings.typeXP)
             {
                 pos += Vector3.right;
             }
             else if (worldType == guardSettings.typeXN)
             {
                 pos += Vector3.left;
             }
             else if (worldType == guardSettings.typeZP)
             {
                 pos += Vector3.forward;
             }
             else if (worldType == guardSettings.typeZN)
             {
                 pos += Vector3.back;
             }
             usedNPC.LookAt(pos);
         }
     }
 }
        public bool TryDoCommand(Players.Player player, string chat)
        {
            if (player == null || player.ID == NetworkID.Server)
            {
                return(true);
            }

            var array  = CommandManager.SplitCommand(chat);
            var colony = Colony.Get(player);
            var state  = PlayerState.GetPlayerState(player);

            if (array.Length == 1)
            {
                PandaChat.Send(player, "Settlers! Monsters are {0}.", ChatColor.green,
                               state.MonstersEnabled ? "on" : "off");

                return(true);
            }

            if (array.Length == 2 && Configuration.GetorDefault("MonstersCanBeDisabled", true))
            {
                if (array[1].ToLower().Trim() == "on" || array[1].ToLower().Trim() == "true")
                {
                    state.MonstersEnabled = true;
                    PandaChat.Send(player, "Settlers! Mod Monsters are now on.", ChatColor.green);
                }
                else
                {
                    state.MonstersEnabled = false;
                    MonsterTracker.KillAllZombies(player);
                    PandaChat.Send(player, "Settlers! Mod Monsters are now off.", ChatColor.green);
                }
            }

            NetworkUI.NetworkMenuManager.SendWorldSettingsUI(player);
            if (!Configuration.GetorDefault("MonstersCanBeDisabled", true))
            {
                PandaChat.Send(player, "The server administrator had disabled the changing of Monsters.",
                               ChatColor.red);
            }

            return(true);
        }
        public void RegisterMonsterSpawnedElsewhere(IMonster monster, bool notifyColonyForSiegeMode = true)
        {
            ThreadManager.AssertIsMainThread();
            ModLoader.Callbacks.OnMonsterSpawned.Invoke(monster);
            MonsterTracker.Add(monster);
            Colony goalColony = monster.OriginalGoal;

            if (goalColony != null)
            {
                IColonyDifficultySetting difficulty = goalColony.DifficultySetting;
                float cooldown = difficulty.GetZombieSpawnCooldown(goalColony);
                NextZombieSpawnTimes[goalColony] = Extensions.GetValueOrDefault(NextZombieSpawnTimes, goalColony, ServerTime.SecondsSinceStart) + cooldown;

                if (notifyColonyForSiegeMode)
                {
                    goalColony.OnZombieSpawn(true);
                }
            }
        }
 public static ColonyModel MapColony(Colony c)
 {
     return(new ColonyModel()
     {
         ColonyId = c.ColonyID,
         Name = c.Name,
         LaborerCount = c.LaborerCount,
         AutoRecruit = c.JobFinder.AutoRecruit,
         AvailableMeals = c.Stockpile.TotalMeals,
         BedCount = c.BedTracker.BedCount,
         OpenJobCount = ColonyTool.GetJobCounts(c).Select(kvp => kvp.Value.AvailableCount).Sum(),
         StockpileCount = c.Stockpile.ItemCount,
         BannerCount = c.Banners.Length,
         ColonistCount = c.FollowerCount,
         Owners = c.Owners.Select(o => o.Name).ToList(),
         ColonyState = ColonyState.GetColonyState(c),
         MonsterCount = MonsterTracker.GetAllMonstersByID().Where(m => m.Value.OriginalGoal.ColonyID == c.ColonyID).Count()
     });
 }
 public static ColonyModel MapColony(Colony c)
 {
     return(new ColonyModel()
     {
         ColonyId = c.ColonyID,
         Name = c.Name,
         LaborerCount = c.LaborerCount,
         AutoRecruit = c.JobFinder.AutoRecruit,
         AvailableFood = c.Stockpile.TotalFood,
         BedCount = c.BedTracker.BedCount,
         Happiness = c.HappinessData.CachedHappiness,
         OpenJobCount = c.JobFinder.OpenJobCount,
         StockpileCount = c.Stockpile.ItemCount,
         BannerCount = c.Banners.Length,
         ColonistCount = c.FollowerCount,
         Owners = c.Owners.Select(o => o.Name).ToList(),
         ColonyState = ColonyState.GetColonyState(c),
         MonsterCount = MonsterTracker.GetAllMonstersByID().Where(m => m.Value.OriginalGoal.ColonyID == c.ColonyID).Count()
     });
 }
        public static void OnUpdate()
        {
            if (!World.Initialized)
            {
                return;
            }

            while (_spawnQueue.Count > 0)
            {
                var pandaZombie = _spawnQueue.Dequeue();
                var cs          = ColonyState.GetColonyState(pandaZombie.OriginalGoal);
                ModLoader.Callbacks.OnMonsterSpawned.Invoke(pandaZombie);
                MonsterTracker.Add(pandaZombie);
                cs.ColonyRef.OnZombieSpawn(true);
            }

            if (_updateTime < Time.SecondsSinceStartDouble)
            {
                ServerManager.PathingManager.QueueAction(_pandaPathing);
                _updateTime = Time.SecondsSinceStartDouble + Pipliz.Random.NextDouble(30, 45);
            }
        }
        public static void CheckIfMonstersNearby()
        {
            if (ServerManager.ColonyTracker != null)
            {
                var punchDamage = 30;

                foreach (var colony in ServerManager.ColonyTracker.ColoniesByID.Values)
                {
                    if (colony.DifficultySetting.ShouldSpawnZombies(colony))
                    {
                        foreach (var npc in colony.Followers)
                        {
                            var inv = Entities.ColonistInventory.Get(npc);

                            if (inv.Weapon != null && !inv.Weapon.IsEmpty())
                            {
                                var target = MonsterTracker.Find(npc.Position, 2, punchDamage);

                                if (target != null && target.IsValid)
                                {
                                    npc.LookAt(target.Position);
                                    AudioManager.SendAudio(target.PositionToAimFor, "punch");

                                    if (inv.Weapon != null && !inv.Weapon.IsEmpty())
                                    {
                                        target.OnHit(WeaponFactory.WeaponLookup[inv.Weapon.Id].Damage.TotalDamage());
                                    }
                                    else
                                    {
                                        target.OnHit(punchDamage);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
Exemple #16
0
        public static void SpawnZombie(Colony colony, Banner bannerGoal, NPCType typeToSpawn)
        {
            float maxSpawnWalkDistance = 500f;

            if (TimeCycle.ShouldSpawnMonsters && typeToSpawn.TryGetSettings(out NPCTypeMonsterSettings nPCTypeMonsterSettings))
            {
                maxSpawnWalkDistance = TimeCycle.NightLengthLeftInRealSeconds * (nPCTypeMonsterSettings.movementSpeed * 0.85f) + (float)bannerGoal.SafeRadius;
            }

            switch (TryGetSpawnLocation(bannerGoal, maxSpawnWalkDistance, out Vector3Int start))
            {
            case MonsterSpawner.ESpawnResult.Success:
            {
                Path path;
                if (AIManager.ZombiePathFinder.TryFindPath(start, bannerGoal.KeyLocation, out path, 2000000000) == EPathFindingResult.Success)
                {
                    IMonster monster = new Zombie(typeToSpawn, path, bannerGoal.Owner);
                    ModLoader.TriggerCallbacks <IMonster>(ModLoader.EModCallbackType.OnMonsterSpawned, monster);
                    MonsterTracker.Add(monster);
                    colony.OnZombieSpawn(true);
                }
                else
                {
                    colony.OnZombieSpawn(false);
                }

                break;
            }

            case MonsterSpawner.ESpawnResult.NotLoaded:
                colony.OnZombieSpawn(true);
                break;

            case MonsterSpawner.ESpawnResult.Fail:
                colony.OnZombieSpawn(false);
                break;
            }
        }
Exemple #17
0
        public void OnNPCAtJob(ref NPCBase.NPCState state)
        {
            if (CheckTime())
            {
                Items.Armor.GetBestArmorForNPC(_stock, _usedNPC, _inv, 0);

                try
                {
                    var currentposition = _usedNPC.Position;

                    if (_target == null || !_target.IsValid || !General.Physics.Physics.CanSee(_usedNPC.Position.Vector, _target.Position))
                    {
                        _target = MonsterTracker.Find(currentposition, 1, Items.ItemFactory.WeaponLookup[_inv.Weapon.Id].Damage);
                    }

                    if (_target != null && General.Physics.Physics.CanSee(_usedNPC.Position.Vector, _target.Position))
                    {
                        state.SetIndicator(NPCIndicatorType.Crafted, COOLDOWN, _inv.Weapon.Id);
                        _usedNPC.LookAt(_target.Position);
                        ServerManager.SendAudio(_target.PositionToAimFor, "punch");

                        _target.OnHit(Items.ItemFactory.WeaponLookup[_inv.Weapon.Id].Damage);
                        _waitingFor = 0;
                    }
                    else
                    {
                        state.SetIndicator(NPCIndicatorType.MissingItem, COOLDOWN, GameLoader.MissingMonster_Icon);
                        _waitingFor++;
                        _target = null;
                    }
                }
                catch (Exception ex)
                {
                    PandaLogger.LogError(ex);
                }
            }
        }
Exemple #18
0
        public static void DoWork(Players.Player player, MachineState machineState)
        {
            try
            {
                if (TurretSettings.ContainsKey(machineState.MachineType) &&
                    machineState.Durability > 0 &&
                    machineState.Fuel > 0 &&
                    machineState.NextTimeForWork < Time.SecondsSinceStartDouble)
                {
                    var stockpile = Stockpile.GetStockPile(player);

                    machineState.Durability -= TurretSettings[machineState.MachineType].DurabilityPerDoWork;
                    machineState.Fuel       -= TurretSettings[machineState.MachineType].FuelPerDoWork;

                    if (machineState.Durability < 0)
                    {
                        machineState.Durability = 0;
                    }

                    if (machineState.Fuel <= 0)
                    {
                        machineState.Fuel = 0;
                    }

                    if (machineState.Load > 0)
                    {
                        var monster = MonsterTracker.Find(machineState.Position.Add(0, 1, 0), TurretSettings[machineState.MachineType].Range, TurretSettings[machineState.MachineType].Damage);

                        if (monster == null)
                        {
                            monster = MonsterTracker.Find(machineState.Position.Add(1, 0, 0), TurretSettings[machineState.MachineType].Range, TurretSettings[machineState.MachineType].Damage);
                        }

                        if (monster == null)
                        {
                            MonsterTracker.Find(machineState.Position.Add(-1, 0, 0), TurretSettings[machineState.MachineType].Range, TurretSettings[machineState.MachineType].Damage);
                        }

                        if (monster == null)
                        {
                            MonsterTracker.Find(machineState.Position.Add(0, -1, 0), TurretSettings[machineState.MachineType].Range, TurretSettings[machineState.MachineType].Damage);
                        }

                        if (monster == null)
                        {
                            MonsterTracker.Find(machineState.Position.Add(0, 0, 1), TurretSettings[machineState.MachineType].Range, TurretSettings[machineState.MachineType].Damage);
                        }

                        if (monster == null)
                        {
                            MonsterTracker.Find(machineState.Position.Add(0, 0, -1), TurretSettings[machineState.MachineType].Range, TurretSettings[machineState.MachineType].Damage);
                        }

                        if (monster != null)
                        {
                            machineState.Load -= TurretSettings[machineState.MachineType].AmmoValue;

                            if (machineState.Load < 0)
                            {
                                machineState.Load = 0;
                            }

                            if (TurretSettings[machineState.MachineType].OnShootAudio != null)
                            {
                                ServerManager.SendAudio(machineState.Position.Vector, TurretSettings[machineState.MachineType].OnShootAudio);
                            }

                            if (TurretSettings[machineState.MachineType].OnHitAudio != null)
                            {
                                ServerManager.SendAudio(monster.PositionToAimFor, TurretSettings[machineState.MachineType].OnHitAudio);
                            }

                            TurretSettings[machineState.MachineType].ProjectileAnimation.SendMoveToInterpolatedOnce(machineState.Position.Vector, monster.PositionToAimFor);
                            monster.OnHit(TurretSettings[machineState.MachineType].Damage);
                        }
                    }

                    machineState.NextTimeForWork = machineState.MachineSettings.WorkTime + Time.SecondsSinceStartDouble;
                }
            }
            catch (Exception ex)
            {
                PandaLogger.LogError(ex, $"Turret shoot for {machineState.MachineType}");
            }
        }
Exemple #19
0
            /// <summary>Apply any provided non-type-specific settings to a monster.</summary>
            /// <param name="monster">The monster to be customized. This will be modified by reference.</param>
            /// <param name="settings">A dictionary of setting names and values. If null, this method will do nothing.</param>
            /// <param name="areaID">The UniqueAreaID of the related SpawnArea. Required for log messages.</param>
            public static void ApplyMonsterSettings(Monster monster, Dictionary <string, object> settings, string areaID = "")
            {
                if (settings == null) //if no settings were provided
                {
                    return;           //do nothing
                }

                //set max HP
                if (settings.ContainsKey("HP"))
                {
                    monster.MaxHealth = Convert.ToInt32(settings["HP"]);
                }

                //set damage
                if (settings.ContainsKey("Damage"))
                {
                    monster.DamageToFarmer = Convert.ToInt32(settings["Damage"]); //set DamageToFarmer

                    if (monster is ICustomDamage cd)                              //if this monster type uses the custom damage interface
                    {
                        cd.CustomDamage = Convert.ToInt32(settings["Damage"]);    //set CustomDamage as well
                    }
                }

                //set defense
                if (settings.ContainsKey("Defense"))
                {
                    monster.resilience.Value = Convert.ToInt32(settings["Defense"]);
                }

                //set dodge chance
                if (settings.ContainsKey("DodgeChance"))
                {
                    monster.missChance.Value = ((double)Convert.ToInt32(settings["DodgeChance"])) / 100;
                }

                //set experience points
                if (settings.ContainsKey("EXP"))
                {
                    monster.ExperienceGained = Convert.ToInt32(settings["EXP"]);
                }

                //multiply HP and/or damage based on players' highest level in a skill
                if (settings.ContainsKey("RelatedSkill"))
                {
                    //parse the provided skill into an enum
                    Utility.Skills skill = (Utility.Skills)Enum.Parse(typeof(Utility.Skills), ((string)settings["RelatedSkill"]).Trim(), true); //parse while trimming whitespace and ignoring case

                    //multiply HP
                    if (settings.ContainsKey("PercentExtraHPPerSkillLevel"))
                    {
                        //calculate HP multiplier based on skill level
                        double skillMultiplier = Convert.ToInt32(settings["PercentExtraHPPerSkillLevel"]);
                        skillMultiplier = (skillMultiplier / 100); //converted to percent, e.g. "10" (10% per level) converts to "0.1"
                        int highestSkillLevel = 0;                 //highest skill level among all existing farmers, not just the host
                        foreach (Farmer farmer in Game1.getAllFarmers())
                        {
                            highestSkillLevel = Math.Max(highestSkillLevel, farmer.getEffectiveSkillLevel((int)skill)); //record the new level if it's higher than before
                        }
                        skillMultiplier = 1.0 + (skillMultiplier * highestSkillLevel);                                  //final multiplier; e.g. if the setting is "10", this is "1.0" at level 0, "1.7" at level 7, etc

                        //apply the multiplier to the monster's max HP
                        skillMultiplier  *= monster.MaxHealth;                 //multiply the current max HP by the skill multiplier
                        monster.MaxHealth = Math.Max((int)skillMultiplier, 1); //set the monster's new max HP (rounded down to the nearest integer & minimum 1)
                    }

                    //multiply damage
                    if (settings.ContainsKey("PercentExtraDamagePerSkillLevel"))
                    {
                        //calculate damage multiplier based on skill level
                        double skillMultiplier = Convert.ToInt32(settings["PercentExtraDamagePerSkillLevel"]);
                        skillMultiplier = (skillMultiplier / 100); //converted to percent, e.g. "10" (10% per level) converts to "0.1"
                        int highestSkillLevel = 0;                 //highest skill level among all existing farmers, not just the host
                        foreach (Farmer farmer in Game1.getAllFarmers())
                        {
                            highestSkillLevel = Math.Max(highestSkillLevel, farmer.getEffectiveSkillLevel((int)skill)); //record the new level if it's higher than before
                        }
                        skillMultiplier = 1.0 + (skillMultiplier * highestSkillLevel);                                  //final multiplier; e.g. if the setting is "10", this is "1.0" at level 0, "1.7" at level 7, etc

                        //apply the multiplier to the monster's damage
                        skillMultiplier       *= monster.DamageToFarmer;            //multiply the current damage by the skill multiplier
                        monster.DamageToFarmer = Math.Max((int)skillMultiplier, 0); //set the monster's new damage (rounded down to the nearest integer & minimum 0)
                    }

                    //multiply defense
                    if (settings.ContainsKey("PercentExtraDefensePerSkillLevel"))
                    {
                        //calculate defense multiplier based on skill level
                        double skillMultiplier = Convert.ToInt32(settings["PercentExtraDefensePerSkillLevel"]);
                        skillMultiplier = (skillMultiplier / 100); //converted to percent, e.g. "10" (10% per level) converts to "0.1"
                        int highestSkillLevel = 0;                 //highest skill level among all existing farmers, not just the host
                        foreach (Farmer farmer in Game1.getAllFarmers())
                        {
                            highestSkillLevel = Math.Max(highestSkillLevel, farmer.getEffectiveSkillLevel((int)skill)); //record the new level if it's higher than before
                        }
                        skillMultiplier = 1.0 + (skillMultiplier * highestSkillLevel);                                  //final multiplier; e.g. if the setting is "10", this is "1.0" at level 0, "1.7" at level 7, etc

                        //apply the multiplier to the monster's defense
                        skillMultiplier         *= monster.resilience.Value;          //multiply the current damage by the skill multiplier
                        monster.resilience.Value = Math.Max((int)skillMultiplier, 0); //set the monster's new defense (rounded down to the nearest integer & minimum 0)
                    }

                    //multiply dodge chance
                    if (settings.ContainsKey("PercentExtraDodgeChancePerSkillLevel"))
                    {
                        //calculate dodge chance multiplier based on skill level
                        double skillMultiplier = Convert.ToInt32(settings["PercentExtraDodgeChancePerSkillLevel"]);
                        skillMultiplier = (skillMultiplier / 100); //converted to percent, e.g. "10" (10% per level) converts to "0.1"
                        int highestSkillLevel = 0;                 //highest skill level among all existing farmers, not just the host
                        foreach (Farmer farmer in Game1.getAllFarmers())
                        {
                            highestSkillLevel = Math.Max(highestSkillLevel, farmer.getEffectiveSkillLevel((int)skill)); //record the new level if it's higher than before
                        }
                        skillMultiplier = 1.0 + (skillMultiplier * highestSkillLevel);                                  //final multiplier; e.g. if the setting is "10", this is "1.0" at level 0, "1.7" at level 7, etc

                        //apply the multiplier to the monster's dodge chance
                        skillMultiplier         *= monster.missChance.Value;          //multiply the current damage by the skill multiplier
                        monster.missChance.Value = Math.Max((int)skillMultiplier, 0); //set the monster's new dodge chance (rounded down to the nearest integer & minimum 0)
                    }

                    //multiply experience points
                    if (settings.ContainsKey("PercentExtraEXPPerSkillLevel"))
                    {
                        //calculate exp multiplier based on skill level
                        double skillMultiplier = Convert.ToInt32(settings["PercentExtraEXPPerSkillLevel"]);
                        skillMultiplier = (skillMultiplier / 100); //converted to percent, e.g. "10" (10% per level) converts to "0.1"
                        int highestSkillLevel = 0;                 //highest skill level among all existing farmers, not just the host
                        foreach (Farmer farmer in Game1.getAllFarmers())
                        {
                            highestSkillLevel = Math.Max(highestSkillLevel, farmer.getEffectiveSkillLevel((int)skill)); //record the new level if it's higher than before
                        }
                        skillMultiplier = 1.0 + (skillMultiplier * highestSkillLevel);                                  //final multiplier; e.g. if the setting is "10", this is "1.0" at level 0, "1.7" at level 7, etc

                        //apply the multiplier to the monster's exp
                        skillMultiplier         *= monster.ExperienceGained;          //multiply the current exp by the skill multiplier
                        monster.ExperienceGained = Math.Max((int)skillMultiplier, 0); //set the monster's new exp (rounded down to the nearest integer & minimum 0)
                    }
                }

                //set loot (i.e. items dropped on death by the monster)
                if (settings.ContainsKey("Loot"))
                {
                    List <SavedObject> loot = ((JArray)settings["Loot"]).ToObject <List <SavedObject> >(); //cast this list of saved objects (already validated and parsed elsewhere)
                    MonsterTracker.SetLoot(monster, loot);                                                 //set the monster's loot in the monster tracker
                    monster.objectsToDrop.Clear();                                                         //clear any "default" loot the monster might've had
                }

                //set current HP
                //NOTE: do this after any max HP changes, because it may be contingent on max HP)
                if (settings.ContainsKey("CurrentHP"))
                {
                    monster.Health = Convert.ToInt32(settings["CurrentHP"]);
                }
                else //if no current HP setting was provided
                {
                    monster.Health = monster.MaxHealth; //set it to max HP
                }

                //set whether the monster automatically sees players at spawn
                if (settings.ContainsKey("SeesPlayersAtSpawn"))
                {
                    if ((bool)settings["SeesPlayersAtSpawn"]) //if the setting is true (note: supposedly, some monsters behave incorrectly if focusedOnFarmers is set to false)
                    {
                        monster.focusedOnFarmers = true;      //set the monster focus
                    }
                }

                //set sprite
                if (settings.ContainsKey("Sprite"))
                {
                    //replace the monster's sprite, using its existing settings where possible
                    monster.Sprite = new AnimatedSprite((string)settings["Sprite"], monster.Sprite.CurrentFrame, monster.Sprite.SpriteWidth, monster.Sprite.SpriteHeight);
                }
            }
Exemple #20
0
        public static void SaveOffline(Colony colony)
        {
            if (colony.OwnerIsOnline())
            {
                return;
            }

            try
            {
                var folder = $"{GameLoader.GAMEDATA_FOLDER}/savegames/{ServerManager.WorldName}/NPCArchive/";

                if (!Directory.Exists(folder))
                {
                    Directory.CreateDirectory(folder);
                }

                var file = $"{folder}{colony.ColonyID}.json";

                if (!Configuration.OfflineColonies)
                {
                    if (!JSON.Deserialize(file, out var followers, false))
                    {
                        followers = new JSONNode(NodeType.Array);
                    }

                    followers.ClearChildren();

                    PandaLogger.Log(ChatColor.cyan, $"All players from {colony.ColonyID} have disconnected. Clearing colony until reconnect.");

                    var copyOfFollowers = new List <NPCBase>();

                    foreach (var follower in colony.Followers)
                    {
                        JSONNode jobloc = null;

                        if (follower.IsValid)
                        {
                            var job = follower.Job;

                            if (job != null && job.GetJobLocation() != Vector3Int.invalidPos)
                            {
                                jobloc = (JSONNode)job.GetJobLocation();
                                job.SetNPC(null);
                                follower.ClearJob();
                            }
                        }

                        if (follower.TryGetJSON(out var node))
                        {
                            if (jobloc != null)
                            {
                                node.SetAs("JobPoS", jobloc);
                            }

                            ModLoader.TriggerCallbacks(ModLoader.EModCallbackType.OnNPCSaved, follower, node);
                            followers.AddToArray(node);
                            copyOfFollowers.Add(follower);
                        }
                    }

                    JSON.Serialize(file, followers);

                    foreach (var deadMan in copyOfFollowers)
                    {
                        deadMan.OnDeath();
                    }

                    colony.ForEachOwner(o => MonsterTracker.KillAllZombies(o));
                }
            }
            catch (Exception ex)
            {
                PandaLogger.LogError(ex);
            }
        }
        public static void WeaponAttack(Players.Player player, Box <PlayerClickedData> boxedData)
        {
            if (boxedData.item1.IsConsumed)
            {
                return;
            }

            var click      = boxedData.item1;
            var rayCastHit = click.rayCastHit;
            var state      = PlayerState.GetPlayerState(player);

            if (WeaponLookup.ContainsKey(click.typeSelected) &&
                click.rayCastHit.rayHitType == RayHitType.NPC &&
                click.clickType == PlayerClickedData.ClickType.Left)
            {
                var millisecondsSinceStart = Time.MillisecondsSinceStart;

                if (state.Weapon.IsEmpty() || state.Weapon.Id != click.typeSelected)
                {
                    state.Weapon = new SettlerInventory.ArmorState
                    {
                        Id         = click.typeSelected,
                        Durability = WeaponLookup[click.typeSelected].Durability
                    }
                }
                ;

                if (Players.LastPunches.TryGetValue(player, out var num) &&
                    millisecondsSinceStart - num < Players.PlayerPunchCooldownMS)
                {
                    return;
                }

                Players.LastPunches[player]  = millisecondsSinceStart;
                boxedData.item1.consumedType = PlayerClickedData.ConsumedType.UsedByMod;

                if (ZombieID.IsZombieID(rayCastHit.hitNPCID))
                {
                    if (MonsterTracker.TryGetMonsterByID(rayCastHit.hitNPCID, out var monster))
                    {
                        monster.OnHit(WeaponLookup[click.typeSelected].Damage);
                        state.Weapon.Durability--;
                        ServerManager.SendAudio(monster.PositionToAimFor, "punch");
                    }
                }
                else if (NPCTracker.TryGetNPC(rayCastHit.hitNPCID, out var nPCBase))
                {
                    nPCBase.OnHit(WeaponLookup[click.typeSelected].Damage);
                    state.Weapon.Durability--;
                    ServerManager.SendAudio(nPCBase.Position.Vector, "punch");
                }

                if (state.Weapon.Durability <= 0)
                {
                    state.Weapon = new SettlerInventory.ArmorState();
                    player.TakeItemFromInventory(click.typeSelected);

                    PandaChat.Send(player,
                                   $"Your {WeaponLookup[click.typeSelected].Metal} {WeaponLookup[click.typeSelected].WeaponType} has broke!",
                                   ChatColor.orange);
                }
            }
        }
    }
            /// <summary>Generates a monster and places it on the specified map and tile.</summary>
            /// <param name="monsterType">The monster type's name and an optional dictionary of monster-specific settings.</param>
            /// <param name="location">The GameLocation where the monster should be spawned.</param>
            /// <param name="tile">The x/y coordinates of the tile where the monster should be spawned.</param>
            /// <param name="areaID">The UniqueAreaID of the related SpawnArea. Required for log messages.</param>
            /// <returns>Returns the monster's ID value, or null if the spawn process failed.</returns>
            public static int?SpawnMonster(MonsterType monsterType, GameLocation location, Vector2 tile, string areaID = "")
            {
                Monster monster = null;                                                                      //an instatiated monster, to be spawned into the world later

                Color?color = null;                                                                          //the monster's color (used by specific monster types)

                if (monsterType.Settings != null)                                                            //if settings were provided
                {
                    if (monsterType.Settings.ContainsKey("Color"))                                           //if this setting was provided
                    {
                        string[]   colorText    = ((string)monsterType.Settings["Color"]).Trim().Split(' '); //split the setting string into strings for each number
                        List <int> colorNumbers = new List <int>();
                        foreach (string text in colorText)                                                   //for each string
                        {
                            int num = Convert.ToInt32(text);                                                 //convert it to a number
                            if (num < 0)
                            {
                                num = 0;
                            }                         //minimum 0
                            else if (num > 255)
                            {
                                num = 255;
                            }                      //maximum 255
                            colorNumbers.Add(num); //add it to the list
                        }

                        //convert strings into RGBA values
                        int r = Convert.ToInt32(colorNumbers[0]);
                        int g = Convert.ToInt32(colorNumbers[1]);
                        int b = Convert.ToInt32(colorNumbers[2]);
                        int a;
                        if (colorNumbers.Count > 3) //if the setting included an "A" value
                        {
                            a = Convert.ToInt32(colorNumbers[3]);
                        }
                        else //if the setting did not include an "A" value
                        {
                            a = 255; //default to no transparency
                        }

                        color = new Color(r, g, b, a);                                                                     //set the color
                    }
                    else if (monsterType.Settings.ContainsKey("MinColor") && monsterType.Settings.ContainsKey("MaxColor")) //if color wasn't provided, but mincolor & maxcolor were
                    {
                        string[]   minColorText    = ((string)monsterType.Settings["MinColor"]).Trim().Split(' ');         //split the setting string into strings for each number
                        List <int> minColorNumbers = new List <int>();
                        foreach (string text in minColorText)                                                              //for each string
                        {
                            int num = Convert.ToInt32(text);                                                               //convert it to a number
                            if (num < 0)
                            {
                                num = 0;
                            }                         //minimum 0
                            else if (num > 255)
                            {
                                num = 255;
                            }                         //maximum 255
                            minColorNumbers.Add(num); //add it to the list
                        }

                        string[]   maxColorText    = ((string)monsterType.Settings["MaxColor"]).Trim().Split(' '); //split the setting string into strings for each number
                        List <int> maxColorNumbers = new List <int>();
                        foreach (string text in maxColorText)                                                      //for each string
                        {
                            int num = Convert.ToInt32(text);                                                       //convert it to a number
                            if (num < 0)
                            {
                                num = 0;
                            }                         //minimum 0
                            else if (num > 255)
                            {
                                num = 255;
                            }                         //maximum 255
                            maxColorNumbers.Add(num); //convert to number
                        }

                        for (int x = 0; x < minColorNumbers.Count && x < maxColorNumbers.Count; x++) //for each pair of values
                        {
                            if (minColorNumbers[x] > maxColorNumbers[x])                             //if min > max
                            {
                                //swap min and max
                                int temp = minColorNumbers[x];
                                minColorNumbers[x] = maxColorNumbers[x];
                                maxColorNumbers[x] = temp;
                            }
                        }

                        //pick random RGBA values between min and max
                        int r = RNG.Next(minColorNumbers[0], maxColorNumbers[0] + 1);
                        int g = RNG.Next(minColorNumbers[1], maxColorNumbers[1] + 1);
                        int b = RNG.Next(minColorNumbers[2], maxColorNumbers[2] + 1);
                        int a;
                        if (minColorNumbers.Count > 3 && maxColorNumbers.Count > 3) //if both settings included an "A" value
                        {
                            a = RNG.Next(minColorNumbers[3], maxColorNumbers[3] + 1);
                        }
                        else //if one/both of the settings did not include an "A" value
                        {
                            a = 255; //default to no transparency
                        }

                        color = new Color(r, g, b, a); //set the color
                    }
                }

                bool seesPlayers = false;                                               //whether the monster automatically "sees" players at spawn (handled differently by some monster types)

                if (monsterType.Settings != null)                                       //if settings were provided
                {
                    if (monsterType.Settings.ContainsKey("SeesPlayersAtSpawn"))         //if this setting was provided
                    {
                        seesPlayers = (bool)monsterType.Settings["SeesPlayersAtSpawn"]; //use the provided setting
                    }
                }

                //create a new monster based on the provided name & apply type-specific settings
                switch (monsterType.MonsterName.ToLower()) //avoid most casing issues by making this lower-case
                {
                case "bat":
                    monster = new BatFTM(tile, 0);
                    break;

                case "frostbat":
                case "frost bat":
                    monster = new BatFTM(tile, 40);
                    break;

                case "lavabat":
                case "lava bat":
                    monster = new BatFTM(tile, 80);
                    break;

                case "iridiumbat":
                case "iridium bat":
                    monster = new BatFTM(tile, 171);
                    break;

                case "doll":
                case "curseddoll":
                case "cursed doll":
                    monster = new BatFTM(tile, -666);
                    break;

                case "skull":
                case "hauntedskull":
                case "haunted skull":
                    monster = new BatFTM(tile, 77377);
                    break;

                case "bigslime":
                case "big slime":
                case "biggreenslime":
                case "big green slime":
                    monster = new BigSlimeFTM(tile, 0);
                    if (color.HasValue)                               //if color was provided
                    {
                        ((BigSlimeFTM)monster).c.Value = color.Value; //set its color after creation
                    }
                    if (seesPlayers)                                  //if the "SeesPlayersAtSpawn" setting is true
                    {
                        monster.IsWalkingTowardPlayer = true;
                    }
                    break;

                case "bigblueslime":
                case "big blue slime":
                case "bigfrostjelly":
                case "big frost jelly":
                    monster = new BigSlimeFTM(tile, 40);
                    if (color.HasValue)                               //if color was provided
                    {
                        ((BigSlimeFTM)monster).c.Value = color.Value; //set its color after creation
                    }
                    if (seesPlayers)                                  //if the "SeesPlayersAtSpawn" setting is true
                    {
                        monster.IsWalkingTowardPlayer = true;
                    }
                    break;

                case "bigredslime":
                case "big red slime":
                case "bigredsludge":
                case "big red sludge":
                    monster = new BigSlimeFTM(tile, 80);
                    if (color.HasValue)                               //if color was provided
                    {
                        ((BigSlimeFTM)monster).c.Value = color.Value; //set its color after creation
                    }
                    if (seesPlayers)                                  //if the "SeesPlayersAtSpawn" setting is true
                    {
                        monster.IsWalkingTowardPlayer = true;
                    }
                    break;

                case "bigpurpleslime":
                case "big purple slime":
                case "bigpurplesludge":
                case "big purple sludge":
                    monster = new BigSlimeFTM(tile, 121);
                    if (color.HasValue)                               //if color was provided
                    {
                        ((BigSlimeFTM)monster).c.Value = color.Value; //set its color after creation
                    }
                    if (seesPlayers)                                  //if the "SeesPlayersAtSpawn" setting is true
                    {
                        monster.IsWalkingTowardPlayer = true;
                    }
                    break;

                case "bug":
                    monster = new Bug(tile, 0);
                    break;

                case "armoredbug":
                case "armored bug":
                    monster = new Bug(tile, 121);
                    break;

                case "dino":
                case "dinomonster":
                case "dino monster":
                case "pepper":
                case "pepperrex":
                case "pepper rex":
                case "rex":
                    monster = new DinoMonster(tile);
                    break;

                case "duggy":
                    monster = new DuggyFTM(tile);
                    break;

                case "dust":
                case "sprite":
                case "dustsprite":
                case "dust sprite":
                case "spirit":
                case "dustspirit":
                case "dust spirit":
                    monster = new DustSpirit(tile);
                    break;

                case "ghost":
                    monster = new GhostFTM(tile);
                    break;

                case "carbonghost":
                case "carbon ghost":
                    monster = new GhostFTM(tile, "Carbon Ghost");
                    break;

                case "slime":
                case "greenslime":
                case "green slime":
                    monster = new GreenSlime(tile, 0);
                    if (color.HasValue)                                  //if color was also provided
                    {
                        ((GreenSlime)monster).color.Value = color.Value; //set its color after creation
                    }
                    break;

                case "blueslime":
                case "blue slime":
                case "frostjelly":
                case "frost jelly":
                    monster = new GreenSlime(tile, 40);
                    if (color.HasValue)                                  //if color was also provided
                    {
                        ((GreenSlime)monster).color.Value = color.Value; //set its color after creation
                    }
                    break;

                case "redslime":
                case "red slime":
                case "redsludge":
                case "red sludge":
                    monster = new GreenSlime(tile, 80);
                    if (color.HasValue)                                  //if color was also provided
                    {
                        ((GreenSlime)monster).color.Value = color.Value; //set its color after creation
                    }
                    break;

                case "purpleslime":
                case "purple slime":
                case "purplesludge":
                case "purple sludge":
                    monster = new GreenSlime(tile, 121);
                    if (color.HasValue)                                  //if color was also provided
                    {
                        ((GreenSlime)monster).color.Value = color.Value; //set its color after creation
                    }
                    break;

                case "grub":
                case "cavegrub":
                case "cave grub":
                    monster = new GrubFTM(tile, false);
                    break;

                case "fly":
                case "cavefly":
                case "cave fly":
                    monster = new FlyFTM(tile, false);
                    break;

                case "mutantgrub":
                case "mutant grub":
                    monster = new GrubFTM(tile, true);
                    break;

                case "mutantfly":
                case "mutant fly":
                    monster = new FlyFTM(tile, true);
                    break;

                case "metalhead":
                case "metal head":
                    monster = new MetalHead(tile, 0);
                    if (color.HasValue)                             //if color was provided
                    {
                        ((MetalHead)monster).c.Value = color.Value; //set its color after creation
                    }
                    break;

                case "mummy":
                    monster = new MummyFTM(tile);
                    break;

                case "rockcrab":
                case "rock crab":
                    monster = new RockCrab(tile);
                    break;

                case "lavacrab":
                case "lava crab":
                    monster = new LavaCrab(tile);
                    break;

                case "iridiumcrab":
                case "iridium crab":
                    monster = new RockCrab(tile, "Iridium Crab");
                    break;

                case "rockgolem":
                case "rock golem":
                case "stonegolem":
                case "stone golem":
                    monster = new RockGolemFTM(tile);
                    break;

                case "wildernessgolem":
                case "wilderness golem":
                    monster = new RockGolemFTM(tile, Game1.player.CombatLevel);
                    break;

                case "serpent":
                    monster = new SerpentFTM(tile);
                    break;

                case "brute":
                case "shadowbrute":
                case "shadow brute":
                    monster = new ShadowBrute(tile);
                    break;

                case "shaman":
                case "shadowshaman":
                case "shadow shaman":
                    monster = new ShadowShaman(tile);
                    break;

                case "skeleton":
                    monster = new Skeleton(tile);
                    if (seesPlayers)                                                                                        //if the "SeesPlayersAtSpawn" setting is true
                    {
                        IReflectedField <bool> spottedPlayer = Helper.Reflection.GetField <bool>(monster, "spottedPlayer"); //try to access this skeleton's private "spottedPlayer" field
                        spottedPlayer.SetValue(true);
                        monster.IsWalkingTowardPlayer = true;
                    }
                    break;

                case "squid":
                case "kid":
                case "squidkid":
                case "squid kid":
                    monster = new SquidKidFTM(tile);
                    break;

                default:                                                                           //if the name doesn't match any directly known monster types
                    Type externalType = GetTypeFromName(monsterType.MonsterName, typeof(Monster)); //find a monster subclass with a matching name
                    monster = (Monster)Activator.CreateInstance(externalType, tile);               //create a monster with the Vector2 constructor
                    break;
                }

                if (monster == null)
                {
                    Monitor.Log($"The monster to be spawned (\"{monsterType.MonsterName}\") doesn't match any known monster types. Make sure that name isn't misspelled in your config file.", LogLevel.Info);
                    return(null);
                }

                int?ID = MonsterTracker.AddMonster(monster);  //generate an ID for this monster

                if (!ID.HasValue)
                {
                    Monitor.Log("A new monster ID could not be generated. This is may be due to coding issue; please report it to this mod's developer. This monster won't be spawned.", LogLevel.Warn);
                    return(null);
                }
                monster.id = ID.Value;                                       //assign the ID to this monster

                monster.MaxHealth = monster.Health;                          //some monster types set Health on creation and expect MaxHealth to be updated like this

                ApplyMonsterSettings(monster, monsterType.Settings, areaID); //adjust the monster based on any other provided optional settings

                //spawn the completed monster at the target location
                Monitor.VerboseLog($"Spawning monster. Type: {monsterType.MonsterName}. Location: {tile.X},{tile.Y} ({location.Name}).");
                monster.currentLocation = location;
                monster.setTileLocation(tile);
                location.addCharacter(monster);
                return(monster.id);
            }
Exemple #23
0
        public static void DoWork(Players.Player player, MachineState machineState)
        {
            if (!player.IsConnected && Configuration.OfflineColonies || player.IsConnected)
            {
                try
                {
                    if (TurretSettings.ContainsKey(machineState.MachineType) &&
                        machineState.Durability > 0 &&
                        machineState.Fuel > 0 &&
                        machineState.NextTimeForWork < Time.SecondsSinceStartDouble)
                    {
                        var stockpile = Stockpile.GetStockPile(player);

                        machineState.Durability -= TurretSettings[machineState.MachineType].DurabilityPerDoWork;
                        machineState.Fuel       -= TurretSettings[machineState.MachineType].FuelPerDoWork;

                        if (machineState.Durability < 0)
                        {
                            machineState.Durability = 0;
                        }

                        if (machineState.Fuel <= 0)
                        {
                            machineState.Fuel = 0;
                        }

                        if (machineState.Load > 0)
                        {
                            var totalDamage = TurretSettings[machineState.MachineType].TotalDamage;

                            var monster = MonsterTracker.Find(machineState.Position.Add(0, 1, 0),
                                                              TurretSettings[machineState.MachineType].Range,
                                                              totalDamage);

                            if (monster == null)
                            {
                                monster = MonsterTracker.Find(machineState.Position.Add(1, 0, 0),
                                                              TurretSettings[machineState.MachineType].Range,
                                                              totalDamage);
                            }

                            if (monster == null)
                            {
                                monster = MonsterTracker.Find(machineState.Position.Add(-1, 0, 0),
                                                              TurretSettings[machineState.MachineType].Range,
                                                              totalDamage);
                            }

                            if (monster == null)
                            {
                                monster = MonsterTracker.Find(machineState.Position.Add(0, -1, 0),
                                                              TurretSettings[machineState.MachineType].Range,
                                                              totalDamage);
                            }

                            if (monster == null)
                            {
                                monster = MonsterTracker.Find(machineState.Position.Add(0, 0, 1),
                                                              TurretSettings[machineState.MachineType].Range,
                                                              totalDamage);
                            }

                            if (monster == null)
                            {
                                monster = MonsterTracker.Find(machineState.Position.Add(0, 0, -1),
                                                              TurretSettings[machineState.MachineType].Range,
                                                              totalDamage);
                            }

                            if (monster != null)
                            {
                                machineState.Load -= TurretSettings[machineState.MachineType].AmmoValue;

                                Indicator.SendIconIndicatorNear(machineState.Position.Add(0, 1, 0).Vector,
                                                                new
                                                                IndicatorState(TurretSettings[machineState.MachineType].WorkTime,
                                                                               TurretSettings
                                                                               [machineState.MachineType]
                                                                               .Ammo.FirstOrDefault().Type));

                                if (machineState.Load < 0)
                                {
                                    machineState.Load = 0;
                                }

                                if (TurretSettings[machineState.MachineType].OnShootAudio != null)
                                {
                                    ServerManager.SendAudio(machineState.Position.Vector,
                                                            TurretSettings[machineState.MachineType].OnShootAudio);
                                }

                                if (TurretSettings[machineState.MachineType].OnHitAudio != null)
                                {
                                    ServerManager.SendAudio(monster.PositionToAimFor,
                                                            TurretSettings[machineState.MachineType].OnHitAudio);
                                }

                                TurretSettings[machineState.MachineType]
                                .ProjectileAnimation
                                .SendMoveToInterpolatedOnce(machineState.Position.Vector, monster.PositionToAimFor);

                                monster.OnHit(totalDamage, machineState, ModLoader.OnHitData.EHitSourceType.Misc);
                            }
                        }

                        machineState.NextTimeForWork =
                            machineState.MachineSettings.WorkTime + Time.SecondsSinceStartDouble;
                    }
                }
                catch (Exception ex)
                {
                    PandaLogger.LogError(ex, $"Turret shoot for {machineState.MachineType}");
                }
            }
        }
Exemple #24
0
        public static void DoWork(Colony colony, RoamingJobState machineState)
        {
            try
            {
                if (TurretSettings.ContainsKey(machineState.RoamObjective) &&
                    machineState.GetActionEnergy(MachineConstants.REPAIR) > 0 &&
                    machineState.GetActionEnergy(MachineConstants.REFUEL) > 0 &&
                    machineState.NextTimeForWork < Time.SecondsSinceStartDouble)
                {
                    var stockpile = colony.Stockpile;

                    machineState.SubtractFromActionEnergy(MachineConstants.REPAIR, TurretSettings[machineState.RoamObjective].DurabilityPerDoWork);
                    machineState.SubtractFromActionEnergy(MachineConstants.REFUEL, TurretSettings[machineState.RoamObjective].FuelPerDoWork);

                    if (machineState.GetActionEnergy(MachineConstants.RELOAD) > 0)
                    {
                        var totalDamage = TurretSettings[machineState.RoamObjective].TotalDamage;

                        var monster = MonsterTracker.Find(machineState.Position.Add(0, 1, 0),
                                                          TurretSettings[machineState.RoamObjective].Range,
                                                          totalDamage);

                        if (monster == null)
                        {
                            monster = MonsterTracker.Find(machineState.Position.Add(1, 0, 0),
                                                          TurretSettings[machineState.RoamObjective].Range,
                                                          totalDamage);
                        }

                        if (monster == null)
                        {
                            monster = MonsterTracker.Find(machineState.Position.Add(-1, 0, 0),
                                                          TurretSettings[machineState.RoamObjective].Range,
                                                          totalDamage);
                        }

                        if (monster == null)
                        {
                            monster = MonsterTracker.Find(machineState.Position.Add(0, -1, 0),
                                                          TurretSettings[machineState.RoamObjective].Range,
                                                          totalDamage);
                        }

                        if (monster == null)
                        {
                            monster = MonsterTracker.Find(machineState.Position.Add(0, 0, 1),
                                                          TurretSettings[machineState.RoamObjective].Range,
                                                          totalDamage);
                        }

                        if (monster == null)
                        {
                            monster = MonsterTracker.Find(machineState.Position.Add(0, 0, -1),
                                                          TurretSettings[machineState.RoamObjective].Range,
                                                          totalDamage);
                        }

                        if (monster != null)
                        {
                            machineState.SubtractFromActionEnergy(MachineConstants.RELOAD, TurretSettings[machineState.RoamObjective].AmmoValue);

                            if (World.TryGetTypeAt(machineState.Position.Add(0, 1, 0), out ushort above) && above == ColonyBuiltIn.ItemTypes.AIR.Id)
                            {
                                Indicator.SendIconIndicatorNear(machineState.Position.Add(0, 1, 0).Vector,
                                                                new IndicatorState(TurretSettings[machineState.RoamObjective].WorkTime,
                                                                                   TurretSettings[machineState.RoamObjective].Ammo.FirstOrDefault().Type));
                            }

                            if (TurretSettings[machineState.RoamObjective].OnShootAudio != null)
                            {
                                AudioManager.SendAudio(machineState.Position.Vector, TurretSettings[machineState.RoamObjective].OnShootAudio);
                            }

                            if (TurretSettings[machineState.RoamObjective].OnHitAudio != null)
                            {
                                AudioManager.SendAudio(monster.PositionToAimFor, TurretSettings[machineState.RoamObjective].OnHitAudio);
                            }

                            TurretSettings[machineState.RoamObjective]
                            .ProjectileAnimation
                            .SendMoveToInterpolated(machineState.Position.Vector, monster.PositionToAimFor);

                            ServerManager.SendParticleTrail(machineState.Position.Vector, monster.PositionToAimFor, 2);
                            monster.OnHit(totalDamage, machineState, ModLoader.OnHitData.EHitSourceType.Misc);
                        }
                    }

                    machineState.NextTimeForWork =
                        machineState.RoamingJobSettings.WorkTime + Time.SecondsSinceStartDouble;
                }
            }
            catch (Exception ex)
            {
                SettlersLogger.LogError(ex, $"Turret shoot for {machineState.RoamObjective}");
            }
        }
Exemple #25
0
            /// <summary>Generates a monster and places it on the specified map and tile.</summary>
            /// <param name="monsterType">The monster type's name and an optional dictionary of monster-specific settings.</param>
            /// <param name="location">The GameLocation where the monster should be spawned.</param>
            /// <param name="tile">The x/y coordinates of the tile where the monster should be spawned.</param>
            /// <param name="areaID">The UniqueAreaID of the related SpawnArea. Required for log messages.</param>
            /// <returns>Returns the monster's ID value, or null if the spawn process failed.</returns>
            public static int?SpawnMonster(MonsterType monsterType, GameLocation location, Vector2 tile, string areaID = "")
            {
                Monster monster = null;                                                                      //an instatiated monster, to be spawned into the world later

                Color?color = null;                                                                          //the monster's color (used by specific monster types)

                if (monsterType.Settings != null)                                                            //if settings were provided
                {
                    if (monsterType.Settings.ContainsKey("Color"))                                           //if this setting was provided
                    {
                        string[]   colorText    = ((string)monsterType.Settings["Color"]).Trim().Split(' '); //split the setting string into strings for each number
                        List <int> colorNumbers = new List <int>();
                        foreach (string text in colorText)                                                   //for each string
                        {
                            int num = Convert.ToInt32(text);                                                 //convert it to a number
                            if (num < 0)
                            {
                                num = 0;
                            }                         //minimum 0
                            else if (num > 255)
                            {
                                num = 255;
                            }                      //maximum 255
                            colorNumbers.Add(num); //add it to the list
                        }

                        //convert strings into RGBA values
                        int r = Convert.ToInt32(colorNumbers[0]);
                        int g = Convert.ToInt32(colorNumbers[1]);
                        int b = Convert.ToInt32(colorNumbers[2]);
                        int a;
                        if (colorNumbers.Count > 3) //if the setting included an "A" value
                        {
                            a = Convert.ToInt32(colorNumbers[3]);
                        }
                        else //if the setting did not include an "A" value
                        {
                            a = 255; //default to no transparency
                        }

                        color = new Color(r, g, b, a);                                                                     //set the color
                    }
                    else if (monsterType.Settings.ContainsKey("MinColor") && monsterType.Settings.ContainsKey("MaxColor")) //if color wasn't provided, but mincolor & maxcolor were
                    {
                        string[]   minColorText    = ((string)monsterType.Settings["MinColor"]).Trim().Split(' ');         //split the setting string into strings for each number
                        List <int> minColorNumbers = new List <int>();
                        foreach (string text in minColorText)                                                              //for each string
                        {
                            int num = Convert.ToInt32(text);                                                               //convert it to a number
                            if (num < 0)
                            {
                                num = 0;
                            }                         //minimum 0
                            else if (num > 255)
                            {
                                num = 255;
                            }                         //maximum 255
                            minColorNumbers.Add(num); //add it to the list
                        }

                        string[]   maxColorText    = ((string)monsterType.Settings["MaxColor"]).Trim().Split(' '); //split the setting string into strings for each number
                        List <int> maxColorNumbers = new List <int>();
                        foreach (string text in maxColorText)                                                      //for each string
                        {
                            int num = Convert.ToInt32(text);                                                       //convert it to a number
                            if (num < 0)
                            {
                                num = 0;
                            }                         //minimum 0
                            else if (num > 255)
                            {
                                num = 255;
                            }                         //maximum 255
                            maxColorNumbers.Add(num); //convert to number
                        }

                        for (int x = 0; x < minColorNumbers.Count && x < maxColorNumbers.Count; x++) //for each pair of values
                        {
                            if (minColorNumbers[x] > maxColorNumbers[x])                             //if min > max
                            {
                                //swap min and max
                                int temp = minColorNumbers[x];
                                minColorNumbers[x] = maxColorNumbers[x];
                                maxColorNumbers[x] = temp;
                            }
                        }

                        //pick random RGBA values between min and max
                        int r = RNG.Next(minColorNumbers[0], maxColorNumbers[0] + 1);
                        int g = RNG.Next(minColorNumbers[1], maxColorNumbers[1] + 1);
                        int b = RNG.Next(minColorNumbers[2], maxColorNumbers[2] + 1);
                        int a;
                        if (minColorNumbers.Count > 3 && maxColorNumbers.Count > 3) //if both settings included an "A" value
                        {
                            a = RNG.Next(minColorNumbers[3], maxColorNumbers[3] + 1);
                        }
                        else //if one/both of the settings did not include an "A" value
                        {
                            a = 255; //default to no transparency
                        }

                        color = new Color(r, g, b, a); //set the color
                    }
                }

                //set fields that affect some monster types in different ways
                bool seesPlayers     = false;                                           //whether the monster automatically "sees" players at spawn
                int  facingDirection = 2;                                               //the direction the monster should be facing at spawn
                bool rangedAttacks   = true;                                            //whether the monster is allowed to use its ranged attacks (if any)

                if (monsterType.Settings != null)                                       //if settings were provided
                {
                    if (monsterType.Settings.ContainsKey("SeesPlayersAtSpawn"))         //if this setting was provided
                    {
                        seesPlayers = (bool)monsterType.Settings["SeesPlayersAtSpawn"]; //use it
                    }

                    if (monsterType.Settings.ContainsKey("FacingDirection"))                      //if this setting was provided
                    {
                        string directionString = (string)monsterType.Settings["FacingDirection"]; //get it
                        switch (directionString.Trim().ToLower())
                        {
                        //get an integer representing the direction
                        case "up":
                            facingDirection = 0;
                            break;

                        case "right":
                            facingDirection = 1;
                            break;

                        case "down":
                            facingDirection = 2;
                            break;

                        case "left":
                            facingDirection = 3;
                            break;
                        }
                    }

                    if (monsterType.Settings.ContainsKey("RangedAttacks"))           //if this setting was provided
                    {
                        rangedAttacks = (bool)monsterType.Settings["RangedAttacks"]; //use it
                    }
                }

                //create a new monster based on the provided name & apply type-specific settings
                switch (monsterType.MonsterName.ToLower()) //avoid most casing issues by making this lower-case
                {
                case "bat":
                    monster = new BatFTM(tile, 0);
                    break;

                case "frostbat":
                case "frost bat":
                    monster = new BatFTM(tile, 40);
                    break;

                case "lavabat":
                case "lava bat":
                    monster = new BatFTM(tile, 80);
                    break;

                case "iridiumbat":
                case "iridium bat":
                    monster = new BatFTM(tile, 171);
                    break;

                case "doll":
                case "curseddoll":
                case "cursed doll":
                    monster = new BatFTM(tile, -666);
                    break;

                case "skull":
                case "hauntedskull":
                case "haunted skull":
                    monster = new BatFTM(tile, 77377);
                    break;

                case "magmasprite":
                case "magma sprite":
                    monster = new BatFTM(tile, -555);
                    break;

                case "magmasparker":
                case "magma sparker":
                    monster = new BatFTM(tile, -556);
                    break;

                case "bigslime":
                case "big slime":
                case "biggreenslime":
                case "big green slime":
                    monster = new BigSlimeFTM(tile, 0);
                    if (color.HasValue)                               //if color was provided
                    {
                        ((BigSlimeFTM)monster).c.Value = color.Value; //set its color after creation
                    }
                    if (seesPlayers)
                    {
                        monster.IsWalkingTowardPlayer = true;
                    }
                    break;

                case "bigblueslime":
                case "big blue slime":
                case "bigfrostjelly":
                case "big frost jelly":
                    monster = new BigSlimeFTM(tile, 40);
                    if (color.HasValue)                               //if color was provided
                    {
                        ((BigSlimeFTM)monster).c.Value = color.Value; //set its color after creation
                    }
                    if (seesPlayers)
                    {
                        monster.IsWalkingTowardPlayer = true;
                    }
                    break;

                case "bigredslime":
                case "big red slime":
                case "bigredsludge":
                case "big red sludge":
                    monster = new BigSlimeFTM(tile, 80);
                    if (color.HasValue)                               //if color was provided
                    {
                        ((BigSlimeFTM)monster).c.Value = color.Value; //set its color after creation
                    }
                    if (seesPlayers)
                    {
                        monster.IsWalkingTowardPlayer = true;
                    }
                    break;

                case "bigpurpleslime":
                case "big purple slime":
                case "bigpurplesludge":
                case "big purple sludge":
                    monster = new BigSlimeFTM(tile, 121);
                    if (color.HasValue)                               //if color was provided
                    {
                        ((BigSlimeFTM)monster).c.Value = color.Value; //set its color after creation
                    }
                    if (seesPlayers)                                  //if the "SeesPlayersAtSpawn" setting is true
                    {
                        monster.IsWalkingTowardPlayer = true;
                    }
                    break;

                case "bluesquid":
                case "blue squid":
                    monster = new BlueSquid(tile);
                    break;

                case "bug":
                    monster = new Bug(tile, 0);
                    break;

                case "armoredbug":
                case "armored bug":
                    monster = new Bug(tile, 121);
                    break;

                case "dino":
                case "dinomonster":
                case "dino monster":
                case "pepper":
                case "pepperrex":
                case "pepper rex":
                case "rex":
                    monster = new DinoMonster(tile);
                    if (!rangedAttacks)
                    {
                        DinoMonster dino = monster as DinoMonster;
                        dino.timeUntilNextAttack = int.MaxValue;
                        dino.nextFireTime        = int.MaxValue;
                    }
                    break;

                case "duggy":
                    monster = new DuggyFTM(tile);
                    break;

                case "magmaduggy":
                case "magma duggy":
                    monster = new DuggyFTM(tile, true);
                    break;

                case "dust":
                case "sprite":
                case "dustsprite":
                case "dust sprite":
                case "spirit":
                case "dustspirit":
                case "dust spirit":
                    monster = new DustSpiritFTM(tile);
                    break;

                case "dwarvishsentry":
                case "dwarvish sentry":
                case "dwarvish":
                case "sentry":
                    monster = new DwarvishSentry(tile);
                    for (int x = Game1.delayedActions.Count - 1; x >= 0; x--)       //check each existing DelayedAction (from last to first)
                    {
                        if (Game1.delayedActions[x].stringData == "DwarvishSentry") //if this action seems to be playing this monster's sound effect
                        {
                            Game1.delayedActions.Remove(Game1.delayedActions[x]);   //remove the action (preventing this monster's global sound effect after creation)
                            break;                                                  //skip the rest of the actions
                        }
                    }
                    break;

                case "ghost":
                    monster = new GhostFTM(tile);
                    break;

                case "carbonghost":
                case "carbon ghost":
                    monster = new GhostFTM(tile, "Carbon Ghost");
                    break;

                case "putridghost":
                case "putrid ghost":
                    monster = new GhostFTM(tile, "Putrid Ghost");
                    break;

                case "slime":
                case "greenslime":
                case "green slime":
                    monster = new GreenSlime(tile, 0);
                    if (color.HasValue)                                  //if color was also provided
                    {
                        ((GreenSlime)monster).color.Value = color.Value; //set its color after creation
                    }
                    break;

                case "blueslime":
                case "blue slime":
                case "frostjelly":
                case "frost jelly":
                    monster = new GreenSlime(tile, 40);
                    if (color.HasValue)                                  //if color was also provided
                    {
                        ((GreenSlime)monster).color.Value = color.Value; //set its color after creation
                    }
                    break;

                case "redslime":
                case "red slime":
                case "redsludge":
                case "red sludge":
                    monster = new GreenSlime(tile, 80);
                    if (color.HasValue)                                  //if color was also provided
                    {
                        ((GreenSlime)monster).color.Value = color.Value; //set its color after creation
                    }
                    break;

                case "purpleslime":
                case "purple slime":
                case "purplesludge":
                case "purple sludge":
                    monster = new GreenSlime(tile, 121);
                    if (color.HasValue)                                  //if color was also provided
                    {
                        ((GreenSlime)monster).color.Value = color.Value; //set its color after creation
                    }
                    break;

                case "tigerslime":
                case "tiger slime":
                    monster = new GreenSlime(tile, 0);                   //create any "normal" slime
                    ((GreenSlime)monster).makeTigerSlime();              //convert it into a tiger slime
                    if (color.HasValue)                                  //if color was also provided
                    {
                        ((GreenSlime)monster).color.Value = color.Value; //set its color after creation
                    }
                    break;

                case "prismaticslime":
                case "prismatic slime":
                    monster = new GreenSlime(tile, 0);                   //create any "normal" slime
                    ((GreenSlime)monster).makePrismatic();               //convert it into a prismatic slime
                    if (color.HasValue)                                  //if color was also provided
                    {
                        ((GreenSlime)monster).color.Value = color.Value; //set its color after creation
                    }
                    break;

                case "grub":
                case "cavegrub":
                case "cave grub":
                    monster = new GrubFTM(tile, false);
                    break;

                case "fly":
                case "cavefly":
                case "cave fly":
                    monster = new FlyFTM(tile, false);
                    break;

                case "mutantgrub":
                case "mutant grub":
                    monster = new GrubFTM(tile, true);
                    break;

                case "mutantfly":
                case "mutant fly":
                    monster = new FlyFTM(tile, true);
                    break;

                case "metalhead":
                case "metal head":
                    monster = new MetalHead(tile, 0);
                    if (color.HasValue)                             //if color was provided
                    {
                        ((MetalHead)monster).c.Value = color.Value; //set its color after creation
                    }
                    break;

                case "hothead":
                case "hot head":
                    monster = new HotHead(tile);
                    if (color.HasValue)                           //if color was provided
                    {
                        ((HotHead)monster).c.Value = color.Value; //set its color after creation
                    }
                    break;

                case "lavalurk":
                case "lava lurk":
                    monster = new LavaLurkFTM(tile, rangedAttacks);
                    break;

                case "leaper":
                    monster = new Leaper(tile);
                    break;

                case "mummy":
                    monster = new MummyFTM(tile);
                    break;

                case "rockcrab":
                case "rock crab":
                    monster = new RockCrab(tile);
                    break;

                case "lavacrab":
                case "lava crab":
                    monster = new LavaCrab(tile);
                    break;

                case "iridiumcrab":
                case "iridium crab":
                    monster = new RockCrab(tile, "Iridium Crab");
                    break;

                case "falsemagmacap":
                case "false magma cap":
                case "magmacap":
                case "magma cap":
                    monster            = new RockCrab(tile, "False Magma Cap");
                    monster.HideShadow = true;     //hide shadow, making them look more like "real" magma caps
                    break;

                case "stickbug":
                case "stick bug":
                    monster = new RockCrab(tile);
                    (monster as RockCrab).makeStickBug();
                    break;

                case "rockgolem":
                case "rock golem":
                case "stonegolem":
                case "stone golem":
                    monster = new RockGolemFTM(tile);
                    break;

                case "wildernessgolem":
                case "wilderness golem":
                    monster = new RockGolemFTM(tile, Game1.player.CombatLevel);
                    break;

                case "serpent":
                    monster = new SerpentFTM(tile);
                    break;

                case "royalserpent":
                case "royal serpent":
                    monster = new SerpentFTM(tile, "Royal Serpent");
                    break;

                case "brute":
                case "shadowbrute":
                case "shadow brute":
                    monster = new ShadowBrute(tile);
                    break;

                case "shaman":
                case "shadowshaman":
                case "shadow shaman":
                    monster = new ShadowShaman(tile);
                    if (!rangedAttacks)
                    {
                        Helper.Reflection.GetField <int>(monster, "coolDown", false)?.SetValue(int.MaxValue);    //set spell cooldown to max
                    }
                    break;

                case "sniper":
                case "shadowsniper":
                case "shadow sniper":
                    monster = new Shooter(tile);
                    if (!rangedAttacks)
                    {
                        (monster as Shooter).nextShot = float.MaxValue;     //set shot cooldown to max
                    }
                    break;

                case "skeleton":
                    monster = new SkeletonFTM(tile, false, rangedAttacks);
                    if (seesPlayers)
                    {
                        Helper.Reflection.GetField <bool>(monster, "spottedPlayer", false)?.SetValue(true);    //set "spotted player" field to true
                        monster.IsWalkingTowardPlayer = true;
                    }
                    break;

                case "skeletonmage":
                case "skeleton mage":
                    monster = new SkeletonFTM(tile, true, rangedAttacks);
                    if (seesPlayers)
                    {
                        Helper.Reflection.GetField <bool>(monster, "spottedPlayer", false)?.SetValue(true);    //set "spotted player" field to true
                        monster.IsWalkingTowardPlayer = true;
                    }
                    break;

                case "spiker":
                    monster = new Spiker(tile, facingDirection);
                    break;

                case "squidkid":
                case "squid kid":
                    monster = new SquidKidFTM(tile);
                    if (!rangedAttacks)
                    {
                        Helper.Reflection.GetField <int>(monster, "lastFireball", false)?.SetValue(int.MaxValue);    //set fireball cooldown to max
                    }
                    break;

                default:                                                                           //if the name doesn't match any directly known monster types
                    //check MTF monster types
                    if (MonstersTheFrameworkAPI.IsKnownMonsterType(monsterType.MonsterName, true)) //if this is a known (and previously validated) monster type from MTF
                    {
                        monster = MonstersTheFrameworkAPI.CreateMonster(monsterType.MonsterName);  //create it through the MTF interface
                        break;
                    }
                    //handle the name as a custom Type
                    Type externalType = GetTypeFromName(monsterType.MonsterName, typeof(Monster)); //find a monster subclass with a matching name
                    monster = (Monster)Activator.CreateInstance(externalType, tile);               //create a monster with the Vector2 constructor
                    break;
                }

                if (monster == null)
                {
                    Monitor.Log($"The monster to be spawned (\"{monsterType.MonsterName}\") doesn't match any known monster types. Make sure that name isn't misspelled in your config file.", LogLevel.Info);
                    return(null);
                }

                int?ID = MonsterTracker.AddMonster(monster);  //generate an ID for this monster

                if (!ID.HasValue)
                {
                    Monitor.Log("A new monster ID could not be generated. This is may be due to coding issue; please report it to this mod's developer. This monster won't be spawned.", LogLevel.Warn);
                    return(null);
                }
                monster.id = ID.Value;                                       //assign the ID to this monster

                monster.MaxHealth = monster.Health;                          //some monster types set Health on creation and expect MaxHealth to be updated like this

                ApplyMonsterSettings(monster, monsterType.Settings, areaID); //adjust the monster based on any other provided optional settings

                //spawn the completed monster at the target location
                Monitor.VerboseLog($"Spawning monster. Type: {monsterType.MonsterName}. Location: {tile.X},{tile.Y} ({location.Name}).");
                monster.currentLocation = location;
                monster.setTileLocation(tile);
                location.addCharacter(monster);
                return(monster.id);
            }
        public void OnNPCAtJob(ref NPCBase.NPCState state)
        {
            try
            {
                var currentposition = NPC.Position;
                _hadAmmo.Clear();

                if (_inv.Weapon != null && !_inv.Weapon.IsEmpty())
                {
                    if (_target == null || !_target.IsValid)
                    {
                        _target = MonsterTracker.Find(currentposition, 100, WeaponFactory.WeaponLookup[_inv.Weapon.Id].Damage.TotalDamage());
                    }

                    if (_target != null && _target.IsValid)
                    {
                        state.SetIndicator(new IndicatorState(COOLDOWN, _inv.Weapon.Id));
                        state.SetCooldown(COOLDOWN);
                        NPC.LookAt(_target.Position);
                        AudioManager.SendAudio(_target.PositionToAimFor, "punch");

                        _target.OnHit(WeaponFactory.WeaponLookup[_inv.Weapon.Id].Damage.TotalDamage());
                        _waitingFor = 0;
                    }
                    else
                    {
                        state.SetIndicator(new IndicatorState(COOLDOWN, ItemId.GetItemId(GameLoader.NAMESPACE + ".Monster").Id, true));
                        state.SetCooldown(COOLDOWN);
                        _waitingFor++;
                        _target = null;
                    }
                }
                else
                {
                    if (_target == null || !_target.IsValid || !VoxelPhysics.CanSee(NPC.Position.Vector, _target.Position))
                    {
                        _target = MonsterTracker.Find(currentposition, _weapon.Range, _weapon.Damage);
                    }

                    if (_target != null && _target.IsValid && VoxelPhysics.CanSee(NPC.Position.Vector, _target.Position))
                    {
                        foreach (var projectile in _weapon.ShootItem)
                        {
                            _hadAmmo[projectile] = false;

                            if (NPC.Inventory.Contains(projectile))
                            {
                                _hadAmmo[projectile] = true;
                                continue;
                            }

                            if (_stock.Contains(projectile))
                            {
                                _hadAmmo[projectile] = true;
                            }
                        }

                        if (!_hadAmmo.Any(a => !a.Value))
                        {
                            state.SetIndicator(new IndicatorState(_weapon.CooldownShot, _weapon.ShootItem[0].Type));

                            foreach (var ammo in _hadAmmo)
                            {
                                if (NPC.Inventory.Contains(ammo.Key))
                                {
                                    NPC.Inventory.TryRemove(ammo.Key);
                                    continue;
                                }

                                if (_stock.Contains(ammo.Key))
                                {
                                    _stock.TryRemove(ammo.Key);
                                }
                            }

                            NPC.LookAt(_target.Position);

                            if (_weapon.OnShootAudio != null)
                            {
                                AudioManager.SendAudio(Position.Vector, _weapon.OnShootAudio);
                            }

                            if (_weapon.OnHitAudio != null)
                            {
                                AudioManager.SendAudio(_target.PositionToAimFor, _weapon.OnHitAudio);
                            }

                            if (_weapon.ShootItem.Count > 0)
                            {
                                foreach (var proj in _weapon.ShootItem)
                                {
                                    var projName = ItemTypes.IndexLookup.GetName(proj.Type);

                                    if (AnimationManager.AnimatedObjects.ContainsKey(projName))
                                    {
                                        AnimationManager.AnimatedObjects[projName].SendMoveToInterpolated(Position.Vector, _target.PositionToAimFor);

                                        break;
                                    }
                                }
                            }

                            ServerManager.SendParticleTrail(currentposition.Vector, _target.PositionToAimFor, 2);
                            _target.OnHit(_weapon.Damage);
                            state.SetCooldown(_weapon.CooldownShot);
                            _waitingFor = 0;
                        }
                        else
                        {
                            state.SetIndicator(new IndicatorState(_weapon.CooldownMissingItem, _weapon.ShootItem[0].Type, true));
                            state.SetCooldown(_weapon.CooldownMissingItem);
                        }
                    }
                    else
                    {
                        state.SetIndicator(new IndicatorState(_weapon.CooldownSearchingTarget, ItemId.GetItemId(GameLoader.NAMESPACE + ".Monster").Id, true));
                        state.SetCooldown(_weapon.CooldownMissingItem);
                        _target = null;
                    }
                }
            }
            catch (Exception)
            {
                state.SetIndicator(new IndicatorState(_weapon.CooldownSearchingTarget, ItemId.GetItemId(GameLoader.NAMESPACE + ".Monster").Id, true));
                state.SetCooldown(_weapon.CooldownMissingItem);
                _target = null;
            }
        }
        public static void WeaponAttack(Players.Player player, PlayerClickedData playerClickData)
        {
            if (playerClickData.IsConsumed)
            {
                return;
            }

            var click = playerClickData;
            var state = PlayerState.GetPlayerState(player);

            if (WeaponLookup.ContainsKey(click.TypeSelected) &&
                click.HitType == PlayerClickedData.EHitType.NPC &&
                click.ClickType == PlayerClickedData.EClickType.Left)
            {
                var millisecondsSinceStart = Time.MillisecondsSinceStart;

                if (state.Weapon.IsEmpty() || state.Weapon.Id != click.TypeSelected)
                {
                    state.Weapon = new ItemState
                    {
                        Id         = click.TypeSelected,
                        Durability = WeaponLookup[click.TypeSelected].WepDurability
                    }
                }
                ;

                if (Players.LastPunches.TryGetValue(player, out var num) &&
                    num.TimeSinceThis < Players.PlayerPunchCooldownMS)
                {
                    return;
                }

                Players.LastPunches[player]  = ServerTimeStamp.Now;
                playerClickData.ConsumedType = PlayerClickedData.EConsumedType.UsedByMod;
                var rayCastHit = click.GetNPCHit();

                if (ZombieID.IsZombieID(rayCastHit.NPCID))
                {
                    if (MonsterTracker.TryGetMonsterByID(rayCastHit.NPCID, out var monster))
                    {
                        var dmg = WeaponLookup[click.TypeSelected].Damage.TotalDamage();
                        state.IncrimentStat("Damage Delt", dmg);
                        monster.OnHit(dmg);
                        state.Weapon.Durability--;
                        AudioManager.SendAudio(monster.PositionToAimFor, "punch");
                    }
                }
                else if (NPCTracker.TryGetNPC(rayCastHit.NPCID, out var nPCBase))
                {
                    var dmg = WeaponLookup[click.TypeSelected].Damage.TotalDamage();
                    state.IncrimentStat("Damage Delt", dmg);
                    nPCBase.OnHit(dmg, player, ModLoader.OnHitData.EHitSourceType.PlayerClick);
                    state.Weapon.Durability--;
                    AudioManager.SendAudio(nPCBase.Position.Vector, "punch");
                }

                if (state.Weapon.Durability <= 0)
                {
                    state.Weapon = new ItemState();
                    player.TakeItemFromInventory(click.TypeSelected);

                    PandaChat.Send(player,
                                   _localizationHelper,
                                   "Broke",
                                   ChatColor.orange,
                                   ItemId.GetItemId(click.TypeSelected).LocalizedName(player));
                }
            }
        }
Exemple #28
0
        public override void OnNPCAtJob(ref NPCBase.NPCState state)
        {
            float  cooldown = HeraldJob.StaticCraftingCooldown;
            ushort status   = GameLoader.Waiting_Icon;

            if (PlayerState.GetPlayerState(player).EnableHeraldWarning)
            {
                IMonster monster = MonsterTracker.Find(originalPosition.Add(0, 1, 0), Configuration.HeraldWarningDistance, 500.0f);

                if (monster == null)
                {
                    monster = MonsterTracker.Find(originalPosition.Add(1, 0, 0), Configuration.HeraldWarningDistance, 500.0f);
                }

                if (monster == null)
                {
                    monster = MonsterTracker.Find(originalPosition.Add(-1, 0, 0), Configuration.HeraldWarningDistance, 500.0f);
                }

                if (monster == null)
                {
                    monster = MonsterTracker.Find(originalPosition.Add(0, -1, 0), Configuration.HeraldWarningDistance, 500.0f);
                }

                if (monster == null)
                {
                    monster = MonsterTracker.Find(originalPosition.Add(0, 0, 1), Configuration.HeraldWarningDistance, 500.0f);
                }

                if (monster == null)
                {
                    monster = MonsterTracker.Find(originalPosition.Add(0, 0, -1), Configuration.HeraldWarningDistance, 500.0f);
                }

                if (monster != null)
                {
                    if (General.Physics.Physics.CanSee(originalPosition.Add(0, 1, 0).Vector, monster.PositionToAimFor) && TimeCycle.TotalTime >= lastRally + Configuration.MilitiaRallyCooldown)
                    {
                        ServerManager.SendAudio(owner.Position, GameLoader.NAMESPACE + ".Rally");
                        status = GameLoader.Trumpeting_Icon;

                        if (Configuration.AllowMilitiaToBeCalled && MilitiaList.Count == 0 && PlayerState.GetPlayerState(player).EnableMilitia)
                        {
                            lastRally = TimeCycle.TotalTime;
                            ActivateMilitia(player, originalPosition);
                        }
                    }
                }
            }
            if (TimeCycle.IsDay)
            {
                if (PlayerState.GetPlayerState(player).EnableHeraldAnnouncingSunrise)
                {
                    if (TimeCycle.TimeOfDay > TimeCycle.SunRise && TimeCycle.TimeOfDay < TimeCycle.SunRise + 1 && TimeCycle.TotalTime > lastDayTrumpeted + 1)
                    {
                        Logger.Log("SunRise trumpeted at {0} the Last Day Trumpeted at {1}", TimeCycle.TotalTime, lastDayTrumpeted);
                        lastDayTrumpeted = TimeCycle.TotalTime + TimeCycle.DayLength;
                        ServerManager.SendAudio(owner.Position, GameLoader.NAMESPACE + ".DayAudio");
                        cooldown = 20f;
                        status   = GameLoader.Trumpeting_Icon;
                    }
                }
            }
            if (!TimeCycle.IsDay)
            {
                if (PlayerState.GetPlayerState(player).EnableHeraldAnnouncingSunset)
                {
                    if (TimeCycle.TimeOfDay > TimeCycle.SunSet && TimeCycle.TimeOfDay < TimeCycle.SunSet + 1 && TimeCycle.TotalTime > lastNightTrumpeted + 1)
                    {
                        Logger.Log("SunSet trumpeted at {0} the Last Night Trumpeted at {1}", TimeCycle.TotalTime, lastNightTrumpeted);
                        lastNightTrumpeted = TimeCycle.TotalTime + TimeCycle.DayLength;
                        ServerManager.SendAudio(owner.Position, GameLoader.NAMESPACE + ".NightAudio");
                        cooldown = 20f;
                        status   = GameLoader.Trumpeting_Icon;
                    }
                }
            }

            state.SetIndicator(new Shared.IndicatorState(cooldown, status));
        }
        public static void OnUpdate()
        {
            if (!World.Initialized)
            {
                return;
            }

            var secondsSinceStartDouble = Time.SecondsSinceStartDouble;

            if (World.Initialized)
            {
                if (!TimeCycle.IsDay &&
                    !BossActive &&
                    _nextBossUpdateTime <= secondsSinceStartDouble)
                {
                    BossActive = true;
                    var bossType = GetMonsterType();

                    if (bossType != null)
                    {
                        _monsterManager.CurrentPandaBoss = bossType;
                        ServerManager.PathingManager.QueueAction(_monsterManager);
                        _justQueued = secondsSinceStartDouble + 5;

                        if (Players.CountConnected != 0)
                        {
                            APILogger.Log(ChatColor.yellow, $"Boss Active! Boss is: {bossType.name}");
                        }
                    }
                    else
                    {
                        BossActive = false;
                        GetNextBossSpawnTime();
                    }
                }

                if (BossActive && _justQueued < secondsSinceStartDouble)
                {
                    var turnOffBoss = true;

                    if (_pandaBossesSpawnQueue.Count > 0)
                    {
                        var pandaboss = _pandaBossesSpawnQueue.Dequeue();
                        var cs        = ColonyState.GetColonyState(pandaboss.OriginalGoal);
                        BossSpawned?.Invoke(MonsterTracker.MonsterSpawner, new BossSpawnedEvent(cs, pandaboss));

                        ModLoader.Callbacks.OnMonsterSpawned.Invoke(pandaboss);
                        MonsterTracker.Add(pandaboss);
                        cs.ColonyRef.OnZombieSpawn(true);
                        cs.FaiedBossSpawns = 0;
                        PandaChat.Send(cs, _localizationHelper, $"[{pandaboss.name}] {pandaboss.AnnouncementText}", ChatColor.red);

                        if (!string.IsNullOrEmpty(pandaboss.AnnouncementAudio))
                        {
                            cs.ColonyRef.ForEachOwner(o => AudioManager.SendAudio(o.Position, pandaboss.AnnouncementAudio));
                        }
                    }

                    foreach (var colony in ServerManager.ColonyTracker.ColoniesByID.Values)
                    {
                        var bannerGoal = colony.Banners.FirstOrDefault();
                        var cs         = ColonyState.GetColonyState(colony);

                        if (bannerGoal != null &&
                            cs.BossesEnabled &&
                            cs.ColonyRef.OwnerIsOnline())
                        {
                            if (SpawnedBosses.ContainsKey(cs) &&
                                SpawnedBosses[cs].IsValid &&
                                SpawnedBosses[cs].CurrentHealth > 0)
                            {
                                if (colony.TemporaryData.GetAsOrDefault("BossIndicator", 0) < Time.SecondsSinceStartInt)
                                {
                                    Indicator.SendIconIndicatorNear(new Vector3Int(SpawnedBosses[cs].Position),
                                                                    SpawnedBosses[cs].ID,
                                                                    new IndicatorState(1, ItemId.GetItemId(GameInitializer.NAMESPACE + ".Poisoned").Id,
                                                                                       false, false));

                                    colony.TemporaryData.SetAs("BossIndicator", Time.SecondsSinceStartInt + 1);
                                }

                                turnOffBoss = false;
                            }
                        }


                        if (turnOffBoss)
                        {
                            if (Players.CountConnected != 0 && SpawnedBosses.Count != 0)
                            {
                                APILogger.Log(ChatColor.yellow, $"All bosses cleared!");
                                var boss = SpawnedBosses.FirstOrDefault().Value;
                                PandaChat.SendToAll($"[{boss.name}] {boss.DeathText}", _localizationHelper, ChatColor.red);
                            }

                            BossActive = false;
                            SpawnedBosses.Clear();
                            GetNextBossSpawnTime();
                        }
                    }
                }
            }
        }
Exemple #30
0
            /// <summary>Check each saved object's expiration data, updating counters & removing missing/expired objects from the data and custom classes from the game world.</summary>
            /// <param name="save">The save data to the checked.</param>
            /// <param name="endOfDay">If false, expiration dates will be ignored. Used to temporarily remove custom classes during the day.</param>
            public static void ProcessObjectExpiration(InternalSaveData save, bool endOfDay = true)
            {
                List <SavedObject> objectsToRemove = new List <SavedObject>();                         //objects to remove from saved data after processing (note: do not remove them while looping through them)

                foreach (SavedObject saved in save.SavedObjects)                                       //for each saved object & expiration countdown
                {
                    if (saved.DaysUntilExpire == null && saved.Type != SavedObject.ObjectType.Monster) //if the object's expiration setting is null & it's not a monster
                    {
                        Monitor.VerboseLog($"Removing object data saved with a null expiration setting. Type: {saved.Type.ToString()}. ID: {saved.ID}. Location: {saved.MapName}.");
                        objectsToRemove.Add(saved); //mark this for removal from save
                        continue;                   //skip to the next object
                    }

                    GameLocation location = Game1.getLocationFromName(saved.MapName); //get the saved object's location

                    if (location == null)                                             //if this isn't a valid map
                    {
                        Monitor.VerboseLog($"Removing object data saved for a missing location. Type: {saved.Type.ToString()}. ID: {saved.ID}. Location: {saved.MapName}.");
                        objectsToRemove.Add(saved); //mark this for removal from save
                        continue;                   //skip to the next object
                    }

                    if (saved.Type == SavedObject.ObjectType.Monster)                                //if this is a monster
                    {
                        bool stillExists = false;                                                    //does this monster still exist?

                        for (int x = location.characters.Count - 1; x >= 0; x--)                     //for each character at this location (looping backward for removal purposes)
                        {
                            if (location.characters[x] is Monster monster && monster.id == saved.ID) //if this is a monster with an ID that matches the saved ID
                            {
                                stillExists = true;
                                if (endOfDay)                                                        //if expirations should be processed
                                {
                                    if (saved.DaysUntilExpire == 1 || saved.DaysUntilExpire == null) //if this should expire tonight (including monsters generated without expiration settings)
                                    {
                                        Monitor.VerboseLog($"Removing expired object. Type: {saved.Type.ToString()}. ID: {saved.ID}. Location: {saved.MapName}.");
                                        objectsToRemove.Add(saved);     //mark this for removal from save
                                    }
                                    else if (saved.DaysUntilExpire > 1) //if the object should expire, but not tonight
                                    {
                                        saved.DaysUntilExpire--;        //decrease counter by 1
                                    }
                                }

                                if (saved.MonType != null && saved.MonType.Settings.ContainsKey("PersistentHP") && (bool)saved.MonType.Settings["PersistentHP"]) //if the PersistentHP setting is enabled for this monster
                                {
                                    saved.MonType.Settings["CurrentHP"] = monster.Health;                                                                        //save this monster's current HP
                                }

                                location.characters.RemoveAt(x); //remove this monster from the location, regardless of expiration
                                break;                           //stop searching the character list
                            }
                        }

                        if (!stillExists) //if this monster no longer exists
                        {
                            Monitor.VerboseLog($"Removing missing object. Type: {saved.Type.ToString()}. ID: {saved.ID}. Location: {saved.MapName}.");
                            objectsToRemove.Add(saved); //mark this for removal from save
                        }
                    }
                    else if (saved.Type == SavedObject.ObjectType.ResourceClump) //if this is a resource clump
                    {
                        IEnumerable <TerrainFeature> resourceClumps = null;      //a list of resource clumps at this location
                        if (location is Farm farm)
                        {
                            resourceClumps = farm.resourceClumps.ToList(); //use the farm's clump list
                        }
                        else if (location is MineShaft mine)
                        {
                            resourceClumps = mine.resourceClumps.ToList(); //use the mine's clump list
                        }
                        else
                        {
                            resourceClumps = location.largeTerrainFeatures.OfType <LargeResourceClump>(); //use this location's large resource clump list
                        }

                        TerrainFeature existingObject = null;            //the in-game object, if it currently exists

                        foreach (TerrainFeature clump in resourceClumps) //for each of this location's large objects
                        {
                            if (clump is ResourceClump smallClump)
                            {
                                if (smallClump.tile.X == saved.Tile.X && smallClump.tile.Y == saved.Tile.Y && smallClump.parentSheetIndex.Value == saved.ID) //if this clump's location & ID match the saved object
                                {
                                    existingObject = smallClump;
                                    break; //stop searching the clump list
                                }
                            }
                            else if (clump is LargeResourceClump largeClump)
                            {
                                if (largeClump.Clump.Value.tile.X == saved.Tile.X && largeClump.Clump.Value.tile.Y == saved.Tile.Y && largeClump.Clump.Value.parentSheetIndex.Value == saved.ID) //if this clump's location & ID match the saved object
                                {
                                    existingObject = largeClump;
                                    break; //stop searching the clump list
                                }
                            }
                        }

                        if (existingObject != null)             //if the object still exists
                        {
                            if (endOfDay)                       //if expirations should be processed
                            {
                                if (saved.DaysUntilExpire == 1) //if the object should expire tonight
                                {
                                    Monitor.VerboseLog($"Removing expired object. Type: {saved.Type.ToString()}. ID: {saved.ID}. Location: {saved.Tile.X},{saved.Tile.Y} ({saved.MapName}).");

                                    if (existingObject is ResourceClump clump) //if this is NOT a custom class that always needs removal
                                    {
                                        if (location is Farm farmLoc)
                                        {
                                            farmLoc.resourceClumps.Remove(clump); //remove this object from the farm's resource clumps list
                                        }
                                        else if (location is MineShaft mineLoc)
                                        {
                                            mineLoc.resourceClumps.Remove(clump); //remove this object from the mine's resource clumps list
                                        }
                                    }

                                    objectsToRemove.Add(saved);     //mark object for removal from save
                                }
                                else if (saved.DaysUntilExpire > 1) //if the object should expire, but not tonight
                                {
                                    saved.DaysUntilExpire--;        //decrease counter by 1
                                }
                            }

                            if (existingObject is LargeResourceClump largeClump)  //if this is a custom class that always needs removal
                            {
                                location.largeTerrainFeatures.Remove(largeClump); //remove this object from the large terrain features list (NOTE: this must be done even for unexpired LargeResourceClumps to avoid SDV save errors)
                            }
                        }
                        else //if the object no longer exists
                        {
                            Monitor.VerboseLog($"Removing missing object. Type: {saved.Type.ToString()}. ID: {saved.ID}. Location: {saved.MapName}.");
                            objectsToRemove.Add(saved); //mark object for removal from save
                        }
                    }
                    else if (saved.Type == SavedObject.ObjectType.Item) //if this is a forage item, i.e. "debris" containing an item
                    {
                        bool stillExists = false;                       //does this item still exist?

                        //if a PlacedItem terrain feature exists at the saved tile & contains an item with a matching name
                        if (location.terrainFeatures.ContainsKey(saved.Tile) && location.terrainFeatures[saved.Tile] is PlacedItem placedItem && placedItem.Item?.ParentSheetIndex == saved.ID.Value)
                        {
                            stillExists = true;
                            location.terrainFeatures.Remove(saved.Tile);                         //remove this placed item, regardless of expiration

                            if (endOfDay)                                                        //if expirations should be processed
                            {
                                if (saved.DaysUntilExpire == 1 || saved.DaysUntilExpire == null) //if this should expire tonight
                                {
                                    Monitor.VerboseLog($"Removing expired object. Type: {saved.Type.ToString()}. Name: {placedItem.Item?.Name}. Location: {saved.MapName}.");
                                    objectsToRemove.Add(saved);     //mark this for removal from save
                                }
                                else if (saved.DaysUntilExpire > 1) //if this should expire, but not tonight
                                {
                                    saved.DaysUntilExpire--;        //decrease counter by 1
                                }
                            }
                        }

                        if (!stillExists) //if this item no longer exists
                        {
                            Monitor.VerboseLog($"Removing missing object. Type: {saved.Type.ToString()}. ID: {saved.ID}. Location: {saved.MapName}.");
                            objectsToRemove.Add(saved); //mark this for removal from save
                        }
                    }
                    else if (saved.Type == SavedObject.ObjectType.Container)                                              //if this is a container
                    {
                        StardewValley.Object realObject = location.getObjectAtTile((int)saved.Tile.X, (int)saved.Tile.Y); //get the object at the saved location

                        if (realObject != null)                                                                           //if an object exists in the saved location
                        {
                            bool sameContainerCategory = false;
                            switch (saved.ConfigItem?.Category.ToLower()) //compare the saved object's category to this object's class
                            {
                            case "barrel":
                            case "barrels":
                            case "breakable":
                            case "breakables":
                            case "crate":
                            case "crates":
                                if (realObject is BreakableContainerFTM)
                                {
                                    sameContainerCategory = true;
                                }
                                break;

                            case "buried":
                            case "burieditem":
                            case "burieditems":
                            case "buried item":
                            case "buried items":
                                if (realObject is BuriedItems)
                                {
                                    sameContainerCategory = true;
                                }
                                break;

                            case "chest":
                            case "chests":
                                if (realObject is Chest)
                                {
                                    sameContainerCategory = true;
                                }
                                break;
                            }

                            if (sameContainerCategory)                                           //if the real object matches the saved object's category
                            {
                                if (realObject is Chest chest)                                   //if this is a chest
                                {
                                    while (chest.items.Count < saved.ConfigItem?.Contents.Count) //while this chest has less items than the saved object's "contents"
                                    {
                                        saved.ConfigItem.Contents.RemoveAt(0);                   //remove a missing item from the ConfigItem's contents (note: chests output the item at index 0 when used)
                                    }
                                }

                                realObject.CanBeGrabbed = true;           //workaround for certain objects being ignored by the removeObject method
                                location.removeObject(saved.Tile, false); //remove this container from the location, regardless of expiration

                                if (endOfDay)                             //if expirations should be processed
                                {
                                    if (saved.DaysUntilExpire == 1)       //if the object should expire tonight
                                    {
                                        Monitor.VerboseLog($"Removing expired container. Type: {saved.Type.ToString()}. Category: {saved.ConfigItem?.Category}. Location: {saved.Tile.X},{saved.Tile.Y} ({saved.MapName}).");

                                        objectsToRemove.Add(saved);     //mark object for removal from save
                                    }
                                    else if (saved.DaysUntilExpire > 1) //if the object should expire, but not tonight
                                    {
                                        saved.DaysUntilExpire--;        //decrease counter by 1
                                    }
                                }
                            }
                            else //if the real object does NOT match the saved object's category
                            {
                                Monitor.VerboseLog($"Removing missing object. Type: {saved.Type.ToString()}. Category: {saved.ConfigItem?.Category}. Location: {saved.MapName}.");
                                objectsToRemove.Add(saved); //mark object for removal from save
                            }
                        }
                        else //if the object no longer exists
                        {
                            Monitor.VerboseLog($"Removing missing object. Type: {saved.Type.ToString()}. Category: {saved.ConfigItem?.Category}. Location: {saved.MapName}.");
                            objectsToRemove.Add(saved); //mark object for removal from save
                        }
                    }
                    else //if this is a StardewValley.Object (e.g. forage or ore)
                    {
                        StardewValley.Object realObject = location.getObjectAtTile((int)saved.Tile.X, (int)saved.Tile.Y); //get the object at the saved location

                        if (realObject != null && realObject.ParentSheetIndex == saved.ID) //if an object exists in the saved location & matches the saved object's ID
                        {
                            if (endOfDay)                                                  //if expirations should be processed
                            {
                                if (saved.DaysUntilExpire == 1)                            //if the object should expire tonight
                                {
                                    Monitor.VerboseLog($"Removing expired object. Type: {saved.Type.ToString()}. ID: {saved.ID}. Location: {saved.Tile.X},{saved.Tile.Y} ({saved.MapName}).");
                                    realObject.CanBeGrabbed = true;           //workaround for certain objects being ignored by the removeObject method
                                    location.removeObject(saved.Tile, false); //remove the object from the game
                                    objectsToRemove.Add(saved);               //mark object for removal from save
                                }
                                else if (saved.DaysUntilExpire > 1)           //if the object should expire, but not tonight
                                {
                                    saved.DaysUntilExpire--;                  //decrease counter by 1
                                }
                            }
                        }
                        else //if the object no longer exists
                        {
                            Monitor.VerboseLog($"Removing missing object. Type: {saved.Type.ToString()}. ID: {saved.ID}. Location: {saved.MapName}.");
                            objectsToRemove.Add(saved); //mark object for removal from save
                        }
                    }
                }

                Monitor.Log($"Object check complete. Removing {objectsToRemove.Count} missing/expired objects from save data.", LogLevel.Trace);
                foreach (SavedObject saved in objectsToRemove) //for each object that should be removed from the save data
                {
                    save.SavedObjects.Remove(saved);           //remove it
                }

                MonsterTracker.Clear(); //clear all monster IDs and related data, since they've all been removed
            }