feat: Add NPC spawner and spawn rate multiplier
This commit is contained in:
1 parent
5bf19308dc
commit
55150f3c47
11 files changed
+193
-3
No files matched your search
@@ -28,6 +28,7 @@ public static class Hotkeys
|
||||
new(InterfaceGroup, "Menu", "Open / close menu", () => WindowManager.Toggle(WindowManager.Menu)),
|
||||
new(InterfaceGroup, "ItemBrowser", "Item Browser", () => WindowManager.Toggle(WindowManager.Items)),
|
||||
new(InterfaceGroup, "BuffBrowser", "Buff Browser", () => WindowManager.Toggle(WindowManager.Buffs)),
|
||||
new(InterfaceGroup, "NpcSpawner", "NPC Spawner", () => WindowManager.Toggle(WindowManager.Npcs)),
|
||||
|
||||
Toggle("Player", "GodMode", "God Mode", () => Settings.GodMode, v => Settings.GodMode = v),
|
||||
Toggle("Player", "AutoHeal", "Auto Heal", () => Settings.AutoHeal, v => Settings.AutoHeal = v),
|
||||
@@ -69,6 +70,7 @@ public static class Hotkeys
|
||||
Toggle("Visuals", "Shine", "Shine", () => Settings.Shine, v => Settings.Shine = v),
|
||||
|
||||
Toggle("World", "FreezeTime", "Freeze Time", () => Settings.FreezeTime, v => Settings.FreezeTime = v),
|
||||
Toggle("World", "SpawnRate", "Spawn Rate", () => Settings.SpawnRateBoost, v => Settings.SpawnRateBoost = v),
|
||||
new("World", "SavePosition", "Save Current Position", Waypoints.AddHere),
|
||||
];
|
||||
|
||||
|
||||
@@ -104,6 +104,8 @@ public static partial class Settings
|
||||
[nameof(BiomeSight)] = "Vision.BiomeSight",
|
||||
|
||||
[nameof(FreezeTime)] = "World.FreezeTime",
|
||||
[nameof(SpawnRateBoost)] = "World.SpawnRate.Enabled",
|
||||
[nameof(SpawnRateMult)] = "World.SpawnRate.Multiplier",
|
||||
|
||||
[nameof(LogBallistics)] = "Ballistics.Log",
|
||||
};
|
||||
|
||||
@@ -48,7 +48,8 @@ public static partial class Settings
|
||||
public static Color ColLocal = Color.White, ColPlayers = new(70, 200, 255), ColHostile = new(235, 60, 60), ColCritters = new(200, 170, 120), ColTown = new(80, 220, 110), ColBoss = new(255, 120, 40), ColItems = new(255, 210, 70), ColProjHostile = new(255, 80, 200), ColProjFriendly = new(120, 160, 255), ColWaypoint = new(220, 90, 255);
|
||||
public static bool Spelunker, Hunter, Shine, NightOwl, DangerSense, BiomeSight;
|
||||
|
||||
public static bool FreezeTime;
|
||||
public static bool FreezeTime, SpawnRateBoost;
|
||||
public static float SpawnRateMult = 5f;
|
||||
|
||||
public static bool LogBallistics = true;
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using Terraria;
|
||||
using Terraria.DataStructures;
|
||||
using Terraria.ID;
|
||||
using TerraSharp.Core;
|
||||
|
||||
namespace TerraSharp.Features;
|
||||
|
||||
public static class NpcSpawner
|
||||
{
|
||||
const float SpawnDistance = 10 * 16f;
|
||||
const int MultiplayerClient = 1;
|
||||
|
||||
#if TMODLOADER
|
||||
static readonly EntitySource_DebugCommand Source = new(nameof(NpcSpawner));
|
||||
#else
|
||||
static readonly EntitySource_DebugCommand Source = new();
|
||||
#endif
|
||||
|
||||
public static bool Multiplayer => Main.netMode == MultiplayerClient;
|
||||
|
||||
// The server only honors summon requests for these types.
|
||||
public static bool CanSummon(int type) => type >= 0 && type < NPCID.Sets.MPAllowedEnemies.Length && NPCID.Sets.MPAllowedEnemies[type];
|
||||
|
||||
public static bool Available(int type) => !Multiplayer || CanSummon(type);
|
||||
|
||||
public static void Spawn(int type, int count)
|
||||
{
|
||||
if (Multiplayer)
|
||||
{
|
||||
if (CanSummon(type))
|
||||
NetMessage.SendData(MessageID.SpawnBossUseLicenseStartEvent, -1, -1, null, Main.myPlayer, type);
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = Main.LocalPlayer;
|
||||
int x = (int)(player.Center.X + player.direction * SpawnDistance);
|
||||
int y = (int)player.Bottom.Y;
|
||||
for (int i = 0; i < count; i++)
|
||||
if (NPC.NewNPC(Source, x, y, type) >= Main.maxNPCs)
|
||||
break;
|
||||
}
|
||||
|
||||
// Spawner.GetSpawnRate postfix; spawnRate is the 1-in-N chance per tick.
|
||||
public static void ScaleSpawnRate(ref int spawnRate, ref int maxSpawns)
|
||||
{
|
||||
if (!Settings.SpawnRateBoost)
|
||||
return;
|
||||
|
||||
if (Settings.SpawnRateMult <= 0f)
|
||||
{
|
||||
maxSpawns = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
spawnRate = Math.Max(1, (int)(spawnRate / Settings.SpawnRateMult));
|
||||
maxSpawns = (int)Math.Ceiling(maxSpawns * Settings.SpawnRateMult);
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ internal sealed class HookRegistry : IDisposable
|
||||
MainPatches.Install(hooks);
|
||||
PlayerPatches.Install(hooks);
|
||||
NpcPatches.Install(hooks);
|
||||
SpawnRatePatch.Install(hooks);
|
||||
ItemCheckShootPatch.Install(hooks);
|
||||
SandboxGuards.Install(hooks);
|
||||
return hooks;
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using Terraria;
|
||||
using TerraSharp.Core;
|
||||
using TerraSharp.Features;
|
||||
#if TMODLOADER
|
||||
using Terraria.ModLoader;
|
||||
#endif
|
||||
|
||||
namespace TerraSharp.Patches;
|
||||
|
||||
// Natural spawns run only in single player and on the server.
|
||||
internal static class SpawnRatePatch
|
||||
{
|
||||
#if TMODLOADER
|
||||
// tModLoader's NPC.SpawnNPC passes the final rate through NPCLoader.EditSpawnRate.
|
||||
delegate void Original(Player player, ref int spawnRate, ref int maxSpawns);
|
||||
delegate void Hooked(Original original, Player player, ref int spawnRate, ref int maxSpawns);
|
||||
|
||||
internal static void Install(HookRegistry hooks) => hooks.Add(typeof(NPCLoader), nameof(NPCLoader.EditSpawnRate), (Hooked)EditSpawnRate);
|
||||
|
||||
static void EditSpawnRate(Original original, Player player, ref int spawnRate, ref int maxSpawns)
|
||||
{
|
||||
original(player, ref spawnRate, ref maxSpawns);
|
||||
Scale(ref spawnRate, ref maxSpawns);
|
||||
}
|
||||
#else
|
||||
delegate void Original(NPC.Spawner self, Player player, out int spawnRate, out int maxSpawns);
|
||||
delegate void Hooked(Original original, NPC.Spawner self, Player player, out int spawnRate, out int maxSpawns);
|
||||
|
||||
internal static void Install(HookRegistry hooks) => hooks.Add(typeof(NPC.Spawner), "GetSpawnRate", (Hooked)GetSpawnRate);
|
||||
|
||||
static void GetSpawnRate(Original original, NPC.Spawner self, Player player, out int spawnRate, out int maxSpawns)
|
||||
{
|
||||
original(self, player, out spawnRate, out maxSpawns);
|
||||
Scale(ref spawnRate, ref maxSpawns);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void Scale(ref int spawnRate, ref int maxSpawns)
|
||||
{
|
||||
if (!Plugin.Active)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
NpcSpawner.ScaleSpawnRate(ref spawnRate, ref maxSpawns);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error("Spawn rate", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,8 @@ public sealed class IdIndex(Func<int> count, Func<int, string?> name)
|
||||
|
||||
public string Name(int id) => id > 0 && id < Names.Length ? Names[id] : "";
|
||||
|
||||
public void Refresh() => _lastQuery = null;
|
||||
|
||||
public void Filter(string query, Func<int, bool> fits)
|
||||
{
|
||||
query = query.Trim();
|
||||
|
||||
@@ -228,6 +228,13 @@ public sealed partial class MainWindow
|
||||
if (WorldFeatures.InvasionName is { } active && Buttons($"Stop {active}") == 0)
|
||||
WorldFeatures.StopInvasion();
|
||||
|
||||
Group("Spawning");
|
||||
Note(NpcSpawner.Multiplayer ? "Multiplayer: boss summons only, spawn rate is ignored." : "Spawn rate only affects single player.");
|
||||
if (Toggle("Spawn Rate", ref Settings.SpawnRateBoost))
|
||||
Sub(() => Slider("Multiplier", ref Settings.SpawnRateMult, 0f, 20f, 5f, "x{0:0.0}", v => v < 0.05f ? "No spawns" : null));
|
||||
if (Buttons(WindowManager.Npcs.Open ? "Close NPC Spawner" : "NPC Spawner") == 0)
|
||||
WindowManager.Toggle(WindowManager.Npcs);
|
||||
|
||||
Group("Waypoints");
|
||||
Note("Saved per world.");
|
||||
if (Buttons("Save Current Position") == 0)
|
||||
|
||||
@@ -35,7 +35,7 @@ public sealed partial class MainWindow : Window
|
||||
new Page("Combat", CombatPage, () => Count(Settings.RapidAttack, Settings.AutoSwing, Settings.DamageBoost, Settings.NoManaCost, Settings.NoSpread, Settings.Aimbot, Settings.AutoAttack, Settings.KillAura, Settings.HitMagnet, Settings.FreezeEnemies, Settings.WeakenEnemies)),
|
||||
new Page("Items", ItemsPage, () => Count(Settings.InstantBreak, Settings.InfItems, Settings.ItemVacuum, Settings.AutoFish, Settings.Sonar)),
|
||||
new Page("Visuals", VisualsPage, () => Count(Settings.Esp, Settings.Spelunker, Settings.Hunter, Settings.Shine, Settings.NightOwl, Settings.DangerSense, Settings.BiomeSight)),
|
||||
new Page("World", WorldPage, () => Count(Settings.FreezeTime)),
|
||||
new Page("World", WorldPage, () => Count(Settings.FreezeTime, Settings.SpawnRateBoost)),
|
||||
new Page("Keys", KeysPage, () => 0),
|
||||
];
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Terraria;
|
||||
using Terraria.ID;
|
||||
#if TMODLOADER
|
||||
using Terraria.ModLoader;
|
||||
#endif
|
||||
using TerraSharp.Features;
|
||||
|
||||
namespace TerraSharp.UI;
|
||||
|
||||
public sealed class NpcBrowser() : GridBrowser("npcs", "NPC Spawner", new Vector2(660, 140), 532, 520, "Search NPCs by name or ID", "NPCs", new IdIndex(() => NpcCount, Lang.GetNPCNameValue), Categories)
|
||||
{
|
||||
const int Batch = 10;
|
||||
|
||||
#if TMODLOADER
|
||||
static int NpcCount => NPCLoader.NPCCount;
|
||||
#else
|
||||
static int NpcCount => NPCID.Count;
|
||||
#endif
|
||||
|
||||
static readonly BrowserCategory[] Categories = [new("All", NpcSpawner.Available), new("Bosses", type => NpcSpawner.Available(type) && IsBoss(type)), new("Enemies", type => NpcSpawner.Available(type) && Sample(type) is { friendly: false, townNPC: false } && !IsBoss(type) && !IsCritter(type)), new("Town", type => NpcSpawner.Available(type) && Sample(type) is { townNPC: true }), new("Critters", type => NpcSpawner.Available(type) && IsCritter(type))];
|
||||
|
||||
bool _multiplayer;
|
||||
|
||||
static NPC? Sample(int type) => ContentSamples.NpcsByNetId.TryGetValue(type, out NPC? npc) ? npc : null;
|
||||
|
||||
static bool IsBoss(int type) => Sample(type) is { boss: true };
|
||||
|
||||
static bool IsCritter(int type) => NPCID.Sets.CountsAsCritter[type] || Main.npcCatchable[type];
|
||||
|
||||
protected override void DrawContent(Rectangle body)
|
||||
{
|
||||
if (_multiplayer != NpcSpawner.Multiplayer)
|
||||
{
|
||||
_multiplayer = NpcSpawner.Multiplayer;
|
||||
Index.Refresh();
|
||||
}
|
||||
|
||||
DrawSearchGrid(body, body.Y, body.Bottom - 30);
|
||||
string hint = _multiplayer ? "Multiplayer: only boss summons the server accepts" : $"Click: spawn in front of you Shift click: spawn {Batch}";
|
||||
Render.TextMid(Widgets.SpriteBatch, hint, new Rectangle(body.X + 4, body.Bottom - 26, body.Width, 26), Widgets.Dim, 0.72f);
|
||||
}
|
||||
|
||||
protected override void DrawSlot(int type, Rectangle bounds, bool hot)
|
||||
{
|
||||
Widgets.Slot(bounds, hot);
|
||||
Main.instance.LoadNPC(type);
|
||||
if (GameTextures.Get("Npc", type) is { } texture)
|
||||
Widgets.Icon(texture, new Rectangle(bounds.X + 4, bounds.Y + 4, bounds.Width - 8, bounds.Height - 8), GameTextures.Frame(texture, 1, Main.npcFrameCount[type], 0, 0));
|
||||
if (!hot)
|
||||
return;
|
||||
|
||||
string action = _multiplayer ? "Click to summon" : "Click to spawn";
|
||||
Widgets.Tooltip = $"{Index.Name(type)}\n{(IsBoss(type) ? "Boss" : "NPC")} #{type} {action}";
|
||||
if (!Widgets.Click)
|
||||
return;
|
||||
|
||||
NpcSpawner.Spawn(type, Widgets.Shift ? Batch : 1);
|
||||
Widgets.Sound(12);
|
||||
}
|
||||
}
|
||||
@@ -13,11 +13,12 @@ public static class WindowManager
|
||||
public static readonly MainWindow Menu = new();
|
||||
public static readonly ItemBrowser Items = new();
|
||||
public static readonly BuffBrowser Buffs = new();
|
||||
public static readonly NpcBrowser Npcs = new();
|
||||
|
||||
const int TitleHeight = 40;
|
||||
const int MaxInputLength = 40;
|
||||
|
||||
static readonly List<Window> Stack = [Items, Buffs, Menu];
|
||||
static readonly List<Window> Stack = [Items, Buffs, Npcs, Menu];
|
||||
static Window? _dragging;
|
||||
static Vector2 _dragOffset;
|
||||
|
||||
|
||||
Reference in new issue
Block a user