private static int SpawnCustomNPC(int x, int y, CustomNPCDefinition definition) { //DEBUG TShock.Log.ConsoleInfo("DEBUG Spawning Custom NPC at {0}, {1} with customID {2}", x, y, definition.customID); //DEBUG int npcid = NPC.NewNPC(x, y, definition.customBase.type); if (npcid == 200) { //DEBUG TShock.Log.ConsoleInfo("DEBUG Spawning FAILED (mobcap) at {0}, {1} for customID {2}", x, y, definition.customID); //DEBUG return(-1); } Data.ConvertNPCToCustom(npcid, definition); DateTime[] dt = null; if (definition.customProjectiles != null) { dt = Enumerable.Repeat(DateTime.Now, definition.customProjectiles.Count).ToArray(); } NPCs[npcid] = new CustomNPCVars(definition, dt, Main.npc[npcid]); TSPlayer.All.SendData(PacketTypes.NpcUpdate, "", npcid); return(npcid); }
/// <summary> /// For Custom Loot /// </summary> /// <param name="args"></param> private void OnLootDrop(NpcLootDropEventArgs args) { CustomNPCVars npcvar = NPCManager.GetCustomNPCByIndex(args.NpcArrayIndex); if (npcvar == null || npcvar.droppedLoot) { return; } args.Handled = npcvar.customNPC.overrideBaseNPCLoot; //Check if monster has been customized if (npcvar.customNPC.customNPCLoots != null) { foreach (CustomNPCLoot obj in npcvar.customNPC.customNPCLoots) { if (obj.itemDropChance >= 100 || NPCManager.Chance(obj.itemDropChance)) { int pre = 0; if (obj.itemPrefix != null) { pre = obj.itemPrefix[rand.Next(obj.itemPrefix.Count)]; } Item.NewItem((int)npcvar.mainNPC.position.X, (int)npcvar.mainNPC.position.Y, npcvar.mainNPC.width, npcvar.mainNPC.height, obj.itemID, obj.itemStack, false, pre, false); } } } npcvar.isDead = true; npcvar.droppedLoot = true; npcvar.OnDeath(); }
/// <summary> /// Teleports a NPC to a specific location in a region /// </summary> /// <param name="region"></param> /// <param name="randompos"></param> /// <param name="x"></param> /// <param name="y"></param> public void TeleportNPC(CustomNPCVars npcvar, string region, bool randompos = true, int x = 0, int y = 0) { Region obj = null; try { obj = TShock.Regions.GetRegionByName(region); } catch { TShock.Log.ConsoleError("Error: a defined region does not exist on this map \"{0}\"", region); return; } if (randompos) { TShock.Utils.GetRandomClearTileWithInRange(obj.Area.Left, obj.Area.Top, obj.Area.Width, obj.Area.Height, out x, out y); } else { x += obj.Area.Left; y += obj.Area.Top; } TeleportNPC(npcvar, x, y); }
/// <summary> /// (Attempts to) Spawn monsters randomly around the current x y position. /// </summary> /// <param name="npcvar">The Custom NPC to use to spawn children</param> /// <param name="amount">The amount to spawn</param> /// <param name="sethealth"></param> /// <param name="health"></param> public void Multiply(CustomNPCVars npcvar, int amount, bool sethealth = false, int health = 0) { // MainNPC is gone. if (mainNPC == null) { return; } if (npcvar == null) { return; } Multiply(npcvar.customNPC, amount, sethealth, health); }
/// <summary> /// Sends a message to all nearby players, distance define-able /// </summary> /// <param name="distance"></param> /// <param name="message"></param> /// <param name="color"></param> public void MessageNearByPlayers(CustomNPCVars npcvar, int distance, string message, Color color) { Vector2 temp = ReturnPos(npcvar); int squaredist = distance * distance; foreach (TSPlayer obj in TShock.Players) { if (obj == null || !obj.ConnectionAlive) { continue; } if (Vector2.DistanceSquared(temp, obj.LastNetPosition) <= squaredist) { obj.SendMessage(message, color); } } }
/// <summary> /// Fires a projectile given the target starting position and projectile class /// </summary> /// <param name="target"></param> /// <param name="origin"></param> /// <param name="projectile"></param> private void FireProjectile(TSPlayer target, CustomNPCVars origin, CustomNPCProjectiles projectile) { //Loop through all ShotTiles foreach (ShotTile obj in projectile.projectileShotTiles) { //Make sure target actually exists - at this point it should always exist if (target == null) { continue; } //Calculate starting position Vector2 start = GetStartPosition(origin, obj); //Calculate speed of projectile Vector2 speed = CalculateSpeed(start, target); //Get the projectile AI Tuple <float, float> ai = Tuple.Create(projectile.projectileAIParams1, projectile.projectileAIParams2); // Create new projectile VIA Terraria's method, with all customizations int projectileIndex = Projectile.NewProjectile( start.X, start.Y, speed.X, speed.Y, projectile.projectileID, projectile.projectileDamage, 0, 255, ai.Item1, ai.Item2); // customize AI for projectile again, as it gets overwritten var proj = Main.projectile[projectileIndex]; proj.ai[0] = ai.Item1; proj.ai[1] = ai.Item2; // send projectile as a packet NetMessage.SendData(27, -1, -1, null, projectileIndex); } }
/// <summary> /// Returns the current position of the monster /// </summary> /// <returns></returns> public Vector2 ReturnPos(CustomNPCVars npcvar) { return(new Vector2(npcvar.mainNPC.position.X, npcvar.mainNPC.position.Y)); }
/// <summary> /// Properly teleports an NPC to a new location. /// </summary> /// <param name="npcvar"></param> /// <param name="x"></param> /// <param name="y"></param> public void TeleportNPC(CustomNPCVars npcvar, int x, int y) { npcvar.mainNPC.Teleport(new Vector2(x * 16, y * 16)); }
/// <summary> /// Statically set x y based on the world coordinates, and teleport npc there /// </summary> /// <param name="x"></param> /// <param name="y"></param> public void TeleportNPC_NoUpdate(CustomNPCVars npcvar, int x, int y) { npcvar.mainNPC.position = new Vector2(x * 16, y * 16); }
/// <summary> /// NPC MISC /// </summary> //internal CustomParticle customParticle { get; set; }; public virtual void OnDeath(CustomNPCVars vars) { }
// Returns start position of projectile with shottile offset private Vector2 GetStartPosition(CustomNPCVars origin, ShotTile shottile) { Vector2 offset = new Vector2(shottile.X, shottile.Y); return(origin.mainNPC.frame.Center() + offset); }
private void OnNpcDamaged(TSPlayer player, int npcIndex, int damage, float knockback, byte direction, bool critical) { //DEBUG TShock.Log.ConsoleInfo("DEBUG [NPCDamage] NPCIndex {0}", npcIndex); //DEBUG NPC npc = Main.npc[npcIndex]; double damageDone = Main.CalculateDamage(damage, npc.ichor ? npc.defense - 20 : npc.defense); if (critical) { damageDone *= 2; } //Damage event var e = new NpcDamageEvent { NpcIndex = npcIndex, PlayerIndex = player.Index, Damage = damage, Knockback = knockback, Direction = direction, CriticalHit = critical, NpcHealth = Math.Max(0, npc.life - (int)damageDone) }; eventManager.InvokeHandler(e, EventType.NpcDamage); //This damage will kill the NPC. if (npc.active && npc.life > 0 && damageDone >= npc.life) { CustomNPCVars npcvar = NPCManager.NPCs[npcIndex]; if (npcvar != null) { npcvar.markDead(); } //Kill event var killedArgs = new NpcKilledEvent { NpcIndex = npcIndex, PlayerIndex = player.Index, Damage = damage, Knockback = knockback, Direction = direction, CriticalHit = critical, LastPosition = npc.position }; var SEconReward = new Money(npcvar.customNPC.SEconReward); // TODO: Improve Seconomy Code if needed // My Implementation of Seconomy Rewards, it's bountys for the last player who hits and kills the enemy, Not sure if this is the best way to do this // Remove when a actual fix is provide if (npcvar != null) { var economyPlayer = SEconomyPlugin.Instance.GetBankAccount(TSPlayer.Server.User.ID); if (!UsingSEConomy || !economyPlayer.IsAccountEnabled) { if (!economyPlayer.IsAccountEnabled) { TShock.Log.Error("You cannot gain any bounty because your account is disabled."); } } else { if (npcvar.customNPC.SEconReward > 0) { IBankAccount Player = SEconomyPlugin.Instance.GetBankAccount(TSPlayer.Server.User.ID); SEconomyPlugin.Instance.WorldAccount.TransferToAsync(Player, SEconReward, BankAccountTransferOptions.AnnounceToReceiver, npcvar.customNPC.customName + " Bountie", "Custom Npc Kill"); SEconReward = 0; } } } eventManager.InvokeHandler(killedArgs, EventType.NpcKill); if (npcvar != null && npcvar.isInvasion) { NPCManager.CustomNPCInvasion.WaveSize--; } } }