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; } }
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); } } }
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); } }
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); } } } } } } } }
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; } }
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); } } }
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}"); } }
/// <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); } }
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); }
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}"); } } }
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}"); } }
/// <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)); } } }
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(); } } } } }
/// <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 }