/// <summary> /// Attempts to spawn the given minion for every player online, where applicable. /// </summary> /// <param name="spawnRegion"></param> /// <param name="minion"></param> /// <param name="npcdef"></param> /// <param name="attempts"></param> private static void SpawnMinion(Rectangle spawnRegion, SpawnMinion minion, CustomNPCDefinition npcdef, int attempts) { //Loop through players foreach (TSPlayer player in TShock.Players) { if (player == null || player.Dead || !player.Active || !NPCManager.Chance(minion.Chance)) { continue; } //Check if the minions can spawn anywhere, or if we need to check if players see them. if (!CurrentWave.SpawnAnywhere) { Rectangle playerFrame = new Rectangle((int)player.TPlayer.position.X, (int)player.TPlayer.position.Y, player.TPlayer.width, player.TPlayer.height); if (!playerFrame.Intersects(spawnRegion)) { continue; } } //Check biomes if (minion.BiomeConditions != BiomeTypes.None) { BiomeTypes biomes = player.GetCurrentBiomes(); if ((minion.BiomeConditions & biomes) == 0) { continue; } } int mobid = -1; //Try max attempts times. This gives attempts*50 spawn attempts at random positions. for (int i = 0; mobid == -1 && i < attempts; i++) { mobid = NPCManager.SpawnMobAroundPlayer(player, npcdef); } //Spawning failed :( if (mobid == -1) { continue; } NPCManager.GetCustomNPCByIndex(mobid).isInvasion = true; } }
internal static void SpawnMobsInBiomeAndRegion() { //loop through all players foreach (TSPlayer player in TShock.Players) { //Check if player exist and is connected if (player == null || !player.ConnectionAlive) { continue; } //Log.ConsoleInfo("{0} - Checking spawn for player", player.Name); //Check all biome spawns BiomeTypes biomes = player.GetCurrentBiomes(); foreach (BiomeTypes biome in availableBiomes.Where(x => biomes.HasFlag(x))) { //Log.ConsoleInfo("{0} - Checking biome for player", biome.ToString()); //Get list of mobs that can be spawned in that biome List <Tuple <string, CustomNPCSpawning> > biomeSpawns; if (!Data.BiomeSpawns.TryGetValue(biome, out biomeSpawns)) { continue; } foreach (Tuple <string, CustomNPCSpawning> obj in biomeSpawns) { //Log.ConsoleInfo("{0} - Checking mob spawn", obj.Item1); //Check spawn conditions if (!CheckSpawnConditions(obj.Item2.spawnConditions)) { //Log.ConsoleInfo("False Conditions"); continue; } CustomNPCDefinition customnpc = Data.GetNPCbyID(obj.Item1); if (customnpc.maxSpawns <= -1) { customnpc.currSpawnsVar++; } //Make sure not spawning more then maxSpawns if (customnpc.maxSpawns != -1 && customnpc.currSpawnsVar >= customnpc.maxSpawns) { continue; } //Get the last spawn attempt DateTime lastSpawnAttempt; if (!Data.LastSpawnAttempt.TryGetValue(customnpc.customID, out lastSpawnAttempt)) { lastSpawnAttempt = default(DateTime); Data.LastSpawnAttempt[customnpc.customID] = lastSpawnAttempt; } //If not enough time has passed, we skip and go to the next NPC. if ((DateTime.Now - lastSpawnAttempt).TotalSeconds < obj.Item2.spawnRate) { continue; } //Check spawn chance if (!NPCManager.Chance(obj.Item2.spawnChance)) { continue; } // Check Player Depth if (player.TileY > obj.Item2.minDepth && player.TileY < obj.Item2.maxDepth || obj.Item2.minDepth == -1 || obj.Item2.maxDepth == -1) { //Check spawn method if (obj.Item2.useTerrariaSpawn) { //All checks completed --> spawn mob int npcid = SpawnMobAroundPlayer(player, customnpc); if (npcid != -1) { Main.npc[npcid].target = player.Index; Data.LastSpawnAttempt[customnpc.customID] = DateTime.Now; customnpc.currSpawnsVar++; } } else { //All checks completed --> spawn mob int spawnX; int spawnY; TShock.Utils.GetRandomClearTileWithInRange(player.TileX, player.TileY, 50, 50, out spawnX, out spawnY); int npcid = SpawnNPCAtLocation((spawnX * 16) + 8, spawnY * 16, customnpc); if (npcid == -1) { continue; } Data.LastSpawnAttempt[customnpc.customID] = DateTime.Now; Main.npc[npcid].target = player.Index; customnpc.currSpawnsVar++; } } } } //Then check regions as well Rectangle playerRectangle = new Rectangle(player.TileX, player.TileY, player.TPlayer.width, player.TPlayer.height); foreach (Region obj in Data.RegionSpawns.Keys.Select(name => TShock.Regions.GetRegionByName(name)).Where(region => region != null && region.InArea(playerRectangle))) { List <Tuple <string, CustomNPCSpawning> > regionSpawns; if (!Data.RegionSpawns.TryGetValue(obj.Name, out regionSpawns)) { continue; } foreach (Tuple <string, CustomNPCSpawning> obj2 in regionSpawns) { //Invalid spawn conditions if (!CheckSpawnConditions(obj2.Item2.spawnConditions)) { continue; } CustomNPCDefinition customnpc = Data.GetNPCbyID(obj2.Item1); //Make sure not spawning more then maxSpawns if (customnpc.maxSpawns != -1 && customnpc.currSpawnsVar >= customnpc.maxSpawns) { continue; } //Get the last spawn attempt DateTime lastSpawnAttempt; if (!Data.LastSpawnAttempt.TryGetValue(customnpc.customID, out lastSpawnAttempt)) { lastSpawnAttempt = default(DateTime); Data.LastSpawnAttempt[customnpc.customID] = lastSpawnAttempt; } //If not enough time passed, we skip and go to the next NPC. if ((DateTime.Now - lastSpawnAttempt).TotalSeconds < obj2.Item2.spawnRate) { continue; } Data.LastSpawnAttempt[customnpc.customID] = DateTime.Now; if (!NPCManager.Chance(obj2.Item2.spawnChance)) { continue; } // Check Player Depth if (player.TileY > obj2.Item2.minDepth && player.TileY < obj2.Item2.maxDepth || obj2.Item2.minDepth == -1 || obj2.Item2.maxDepth == -1) { var region = TShock.Regions.GetRegionByName(obj2.Item2.spawnRegion); ActiveArenas.Add(region); ActiveArenas.Count(); var arenaX = region.Area.X + (region.Area.Width / 2); var arenaY = region.Area.Y + (region.Area.Height / 2); var rangeX = region.Area.Width / 2; var rangeY = region.Area.Height / 2; int spawnX; int spawnY; TShock.Utils.GetRandomClearTileWithInRange(arenaX, arenaY, rangeX, rangeY, out spawnX, out spawnY); int npcid = SpawnNPCAtLocation((spawnX * 16) + 8, spawnY * 16, customnpc); if (npcid == -1) { continue; } Main.npc[npcid].target = player.Index; customnpc.currSpawnsVar++; ActiveArenas.Remove(region); continue; } } } } }
/// <summary> /// Checks if any player is within targetable range of the npc, without obstacle and within firing cooldown timer. /// </summary> private void ProjectileCheck() { //Loop through all custom npcs currently spawned foreach (CustomNPCVars obj in NPCManager.NPCs) { //Check if they exists and are active if (obj == null || obj.isDead || !obj.mainNPC.active) { continue; } //We only want the npcs with custom projectiles if (obj.customNPC.customProjectiles == null) { continue; } //Save the current time DateTime savedNow = DateTime.Now; int k = 0; //Loop through all npc projectiles they can fire foreach (CustomNPCProjectiles projectile in obj.customNPC.customProjectiles) { //Check if projectile last fire time is greater then equal to its next allowed fire time if ((savedNow - obj.lastAttemptedProjectile[k]).TotalMilliseconds >= projectile.projectileFireRate) { //Make sure chance is checked too, don't bother checking if its 100 if (projectile.projectileFireChance == 100 || NPCManager.Chance(projectile.projectileFireChance)) { TSPlayer target = null; if (projectile.projectileLookForTarget) { //Find a target for it to shoot that isn't dead or disconnected foreach (TSPlayer player in TShock.Players.Where(x => x != null && !x.Dead && x.ConnectionAlive)) { //Check if that target can be shot ie/ no obstacles, or if it if projectile goes through walls ignore this check if (!projectile.projectileCheckCollision || Collision.CanHit(player.TPlayer.position, player.TPlayer.bodyFrame.Width, player.TPlayer.bodyFrame.Height, obj.mainNPC.position, obj.mainNPC.width, obj.mainNPC.height)) { //Make sure distance isn't further then what tshock allows float currDistance = Vector2.DistanceSquared(player.TPlayer.position, obj.mainNPC.frame.Center()); //Distance^2 < 4194304 is the same as Distance < 2048, but faster if (currDistance < 4194304) { //Set the target player target = player; //Set npcs target to the player its shooting at obj.mainNPC.target = player.Index; //Break since no need to find another target break; } } } } else { target = TShock.Players[obj.mainNPC.target]; } //Check if we found a valid target if (target != null) { //All checks completed. Fire projectile FireProjectile(target, obj, projectile); //Set last attempted projectile to now obj.lastAttemptedProjectile[k] = savedNow; } } else { obj.lastAttemptedProjectile[k] = savedNow; } } //Increment Index k++; } } }
private void InvasionTimer_Elapsed(object sender, ElapsedEventArgs e) { if (TShock.Utils.ActivePlayers() == 0) { return; } int spawnFails = 0; int spawnsThisWave = 0; int spawnX = Main.spawnTileX - 150; int spawnY = Main.spawnTileY - 150; Rectangle SpawnRegion = new Rectangle(spawnX, spawnY, 300, 300); foreach (SpawnMinion minions in CurrentWave.SpawnGroup.SpawnMinions) { var npcdef = NPCManager.Data.GetNPCbyID(minions.MobID); if (npcdef == null) { TShock.Log.ConsoleError("[CustomNPC]: Error! The custom mob id \"{0}\" does not exist!", minions.MobID); continue; } // Check spawn conditions if (minions.SpawnConditions != SpawnConditions.None) { if (NPCManager.CheckSpawnConditions(minions.SpawnConditions)) { continue; } } foreach (TSPlayer player in TShock.Players) { //Skip spawning more NPCs when we have likely hit the server's mob limit. if (spawnFails > 40 && spawnsThisWave >= 150) { continue; } if (player == null || player.Dead || !player.Active || !NPCManager.Chance(minions.Chance)) { continue; } Rectangle playerframe = new Rectangle((int)player.TPlayer.position.X, (int)player.TPlayer.position.Y, player.TPlayer.width, player.TPlayer.height); if (!playerframe.Intersects(SpawnRegion)) { continue; } if (minions.BiomeConditions != BiomeTypes.None) { BiomeTypes biomes = player.GetCurrentBiomes(); if ((minions.BiomeConditions & biomes) == 0) { continue; } } // Prevent multiple bosses from spawning during invasions if (minions.isBoss && NPCManager.AliveCount(minions.MobID) > 0) { continue; } int mobid = -1; //Try max 3 times. Since every try checks 50 positions around the player to spawn the mob, //3 tries means a maximum of 150 spawn attempts. for (int i = 0; mobid == -1 && i < 3; i++) { mobid = NPCManager.SpawnMobAroundPlayer(player, npcdef); } if (mobid == -1) { spawnFails++; continue; } NPCManager.GetCustomNPCByIndex(mobid).isInvasion = true; spawnsThisWave++; } } if (spawnFails > 0) { TShock.Log.ConsoleInfo("[CustomNPC]: Failed to spawn {0} npcs this wave!", spawnFails); } }
private void OnNPCSpawn(NpcSpawnEventArgs args) { //DEBUG TShock.Log.ConsoleInfo("DEBUG [NPCSpawn] NPCIndex {0}", args.NpcId); //DEBUG //If the id falls outside the possible range, we can return. if (args.NpcId < 0 || args.NpcId >= 200) { return; } //This NPC is custom and not dead. if (NPCManager.NPCs[args.NpcId] != null && !NPCManager.NPCs[args.NpcId].isDead) { return; } NPC spawned = Main.npc[args.NpcId]; foreach (CustomNPCDefinition customnpc in NPCManager.Data.CustomNPCs.Values) { if (!customnpc.isReplacement || customnpc.ReplacementChance >= 100 || NPCManager.Chance(customnpc.ReplacementChance) || spawned.netID != customnpc.customBase.netID) { continue; } DateTime[] dt = null; if (customnpc.customProjectiles != null) { dt = Enumerable.Repeat(DateTime.Now, customnpc.customProjectiles.Count).ToArray(); } NPCManager.NPCs[spawned.whoAmI] = new CustomNPCVars(customnpc, dt, spawned); NPCManager.Data.ConvertNPCToCustom(spawned.whoAmI, customnpc); break; } }