diff options
| author | prospanclaudebot <[email protected]> | 2026-07-10 22:10:31 +0000 |
|---|---|---|
| committer | prospanclaudebot <[email protected]> | 2026-07-10 22:10:31 +0000 |
| commit | f7b25e5d3d01a42c96dee90e5285706a057f6705 (patch) | |
| tree | 284d4404d5797c0d759bc91dd3d3c30aac395e74 | |
| download | jopa-bridge-main.tar.gz jopa-bridge-main.zip | |
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | JopaBridge.cs | 222 | ||||
| -rw-r--r-- | JopaBridge.csproj | 16 | ||||
| -rw-r--r-- | README.md | 11 |
4 files changed, 252 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e76c073 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +bin/ +obj/ +out/ diff --git a/JopaBridge.cs b/JopaBridge.cs new file mode 100644 index 0000000..3c4687d --- /dev/null +++ b/JopaBridge.cs @@ -0,0 +1,222 @@ +using System; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using System.Timers; +using Terraria; +using Terraria.ID; +using TerrariaApi.Server; +using TShockAPI; + +namespace JopaBridge +{ + // Мост-коллаба: TShock-сервер Terraria <-> веб-игра "Планета Жопа" (mp_server.py на :8790). + // События Terraria (заход/выход/босс) улетают в фид Планеты Жопа; команды /svodka /jopa тянут её статус. + [ApiVersion(2, 1)] + public class JopaBridge : TerrariaPlugin + { + public override string Name => "JopaBridge"; + public override string Author => "Планета Жопа × Terraria"; + public override string Description => "Мост между Terraria-сервером и веб-игрой Планета Жопа"; + public override Version Version => new Version(1, 0, 0); + + private const string JOPA = "http://127.0.0.1:8790"; + private static readonly HttpClient Http = new HttpClient { Timeout = TimeSpan.FromSeconds(4) }; + private Timer _svodka; + + public JopaBridge(Main game) : base(game) { } + + public override void Initialize() + { + ServerApi.Hooks.ServerJoin.Register(this, OnJoin); + ServerApi.Hooks.ServerLeave.Register(this, OnLeave); + ServerApi.Hooks.NpcKilled.Register(this, OnNpcKilled); + + Commands.ChatCommands.Add(new Command(CmdJopa, "jopa")); + Commands.ChatCommands.Add(new Command(CmdSvodka, "svodka")); + Commands.ChatCommands.Add(new Command("jopabridge.gear", CmdGear, "gearset", "postml")); + Commands.ChatCommands.Add(new Command("jopabridge.build", CmdHotdog, "hotdog", "хотдог")); + + _svodka = new Timer(5 * 60 * 1000) { AutoReset = true }; + _svodka.Elapsed += (s, e) => Bcast("📡 СВОДКА: фронт Планеты Жопа держится. /jopa — связь миров · /svodka — сводка.", 120, 200, 255); + _svodka.Start(); + + TShock.Log.ConsoleInfo("[JopaBridge] Мост Планета Жопа × Terraria поднят."); + Push("server", "TShock-сервер JopaSMP на связи с фронтом"); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + ServerApi.Hooks.ServerJoin.Deregister(this, OnJoin); + ServerApi.Hooks.ServerLeave.Deregister(this, OnLeave); + ServerApi.Hooks.NpcKilled.Deregister(this, OnNpcKilled); + _svodka?.Stop(); _svodka?.Dispose(); + } + base.Dispose(disposing); + } + + private static string PlayerName(int who) + { + try { var p = TShock.Players[who]; return (p != null && !string.IsNullOrEmpty(p.Name)) ? p.Name : "боец"; } + catch { return "боец"; } + } + + private void OnJoin(JoinEventArgs args) + { + string n = PlayerName(args.Who); + Bcast($"🪖 {n} десантировался на JopaSMP. Добро пожаловать на фронт.", 180, 255, 140); + Push("join", $"{n} зашёл на Terraria-фронт JopaSMP"); + } + + private void OnLeave(LeaveEventArgs args) + { + string n = PlayerName(args.Who); + Push("leave", $"{n} покинул Terraria-фронт"); + } + + private void OnNpcKilled(NpcKilledEventArgs args) + { + try + { + var npc = args.npc; + if (npc == null || !npc.boss) return; + string boss = string.IsNullOrEmpty(npc.FullName) ? "босс" : npc.FullName; + Bcast($"🏆 На JopaSMP повержен {boss}! Слава фронту.", 255, 180, 80); + Push("boss", $"Terraria: повержен босс «{boss}»"); + } + catch { } + } + + private void CmdJopa(CommandArgs args) + { + args.Player.SendMessage("🌐 Планета Жопа × Terraria — миры на связи.", 120, 200, 255); + args.Player.SendInfoMessage("Веб-игра (дрон-гонка/песочница) крутится на том же сервере. Твои события тут летят в её фид."); + args.Player.SendInfoMessage("Команды: /svodka — сводка с фронта Планеты Жопа."); + } + + private void CmdSvodka(CommandArgs args) + { + _ = Task.Run(async () => + { + try + { + var s = await Http.GetStringAsync(JOPA + "/health"); + using var doc = JsonDocument.Parse(s); + var r = doc.RootElement; + int drones = r.TryGetProperty("players", out var p) ? p.GetInt32() : 0; + int herd = r.TryGetProperty("herd", out var h) ? h.GetInt32() : 0; + args.Player.SendMessage("📡 СВОДКА С ПЛАНЕТЫ ЖОПА", 120, 200, 255); + args.Player.SendInfoMessage($" дронов онлайн: {drones} · стадо сфинктеров: {herd} голов"); + args.Player.SendInfoMessage($" Terraria-фронт JopaSMP: {TShock.Utils.GetActivePlayerCount()} бойцов онлайн"); + } + catch + { + args.Player.SendErrorMessage("Планета Жопа не отвечает — фронт вне связи."); + } + }); + } + + // пост-Мунлорд лоадауты: (item, stack, prefix) + private static readonly (int id, int stack, int prefix)[] WARRIOR = { + (ItemID.Zenith, 1, PrefixID.Legendary), + (ItemID.SolarFlareHelmet, 1, 0), (ItemID.SolarFlareBreastplate, 1, 0), (ItemID.SolarFlareLeggings, 1, 0), + (ItemID.LongRainbowTrailWings, 1, 0), // Celestial Starboard (внутреннее имя) + (ItemID.AnkhShield, 1, PrefixID.Warding), + (ItemID.MasterNinjaGear, 1, PrefixID.Menacing), + (ItemID.FireGauntlet, 1, PrefixID.Menacing), + (ItemID.CelestialShell, 1, PrefixID.Menacing), + (ItemID.WarriorEmblem, 1, PrefixID.Menacing), + (ItemID.CharmofMyths, 1, 0), + (ItemID.GreaterHealingPotion, 40, 0), + }; + private static readonly (int id, int stack, int prefix)[] MAGE = { + (ItemID.LastPrism, 1, PrefixID.Mythical), + (ItemID.NebulaHelmet, 1, 0), (ItemID.NebulaBreastplate, 1, 0), (ItemID.NebulaLeggings, 1, 0), + (ItemID.LongRainbowTrailWings, 1, 0), // Celestial Starboard (внутреннее имя) + (ItemID.AnkhShield, 1, PrefixID.Warding), + (ItemID.CelestialCuffs, 1, PrefixID.Menacing), + (ItemID.CelestialEmblem, 1, PrefixID.Menacing), + (ItemID.CelestialShell, 1, PrefixID.Menacing), + (ItemID.CharmofMyths, 1, 0), + (ItemID.SuperManaPotion, 40, 0), (ItemID.GreaterHealingPotion, 40, 0), + }; + + private void CmdHotdog(CommandArgs args) + { + int cx = Main.spawnTileX, cy = Main.spawnTileY - 34; // парит над спавном + const int HW = 26, HH = 11; + void SetTile(int wx, int wy, int type) + { + if (wx < 5 || wy < 5 || wx >= Main.maxTilesX - 5 || wy >= Main.maxTilesY - 5) return; + var t = Main.tile[wx, wy]; + t.active(true); t.type = (ushort)type; t.slope(0); t.halfBrick(false); + } + for (int lx = -HW; lx <= HW; lx++) + for (int ly = -HH; ly <= HH; ly++) + { + double nx = (double)lx / HW, ny = (double)ly / HH; // булка (эллипс) + double sx = (double)lx / (HW * 1.14), sy = (double)ly / (HH * 0.34); // сосиска (тонкая, торчит по краям) + if (sx * sx + sy * sy <= 1.0) SetTile(cx + lx, cy + ly, TileID.FleshBlock); + else if (nx * nx + ny * ny <= 1.0) SetTile(cx + lx, cy + ly, TileID.Sandstone); + } + for (int lx = -HW + 2; lx <= HW - 2; lx++) // горчица-зигзаг поверх сосиски + { + double sx = (double)lx / (HW * 1.14); + if (sx * sx > 0.92) continue; + int wy = cy + (int)Math.Round(-HH * 0.12 + 2.4 * Math.Sin(lx * 0.55)); + SetTile(cx + lx, wy, TileID.AmberGemspark); + SetTile(cx + lx, wy - 1, TileID.AmberGemspark); + } + int x0 = cx - HW - 1, y0 = cy - HH - 1, W = 2 * HW + 3, H = 2 * HH + 3; + for (int gx = x0; gx < x0 + W; gx += 40) + for (int gy = y0; gy < y0 + H; gy += 40) + TSPlayer.All.SendTileRect((short)gx, (short)gy, (byte)Math.Min(40, x0 + W - gx), (byte)Math.Min(40, y0 + H - gy)); + args.Player.SendSuccessMessage("🌭 Гигантский хот-дог воздвигнут над спавном."); + Bcast("🌭 Над спавном воздвигнут ГИГАНТСКИЙ ХОТ-ДОГ волей админа. Слава фронту.", 255, 200, 80); + } + + private void CmdGear(CommandArgs args) + { + if (args.Parameters.Count < 2) + { + args.Player.SendErrorMessage("Использование: /gearset <игрок> <mage|warrior>"); + return; + } + var found = TSPlayer.FindByNameOrID(args.Parameters[0]); + if (found.Count == 0) { args.Player.SendErrorMessage("Игрок не найден."); return; } + if (found.Count > 1) { args.Player.SendMultipleMatchError(found.Select(p => p.Name)); return; } + var target = found[0]; + string cls = args.Parameters[1].ToLowerInvariant(); + (int id, int stack, int prefix)[] set; string label; + if (cls.StartsWith("mag") || cls == "маг") { set = MAGE; label = "маг · Nebula + Last Prism"; } + else if (cls.StartsWith("war") || cls.StartsWith("mel") || cls == "воин") { set = WARRIOR; label = "воин · Solar Flare + Zenith"; } + else { args.Player.SendErrorMessage("Класс: mage или warrior."); return; } + foreach (var it in set) target.GiveItem(it.id, it.stack, it.prefix); + target.SendSuccessMessage($"⚔️ Админ выдал тебе пост-Мунлорд сет: {label}."); + args.Player.SendSuccessMessage($"Выдал {target.Name} пост-Мунлорд сет: {label}."); + Bcast($"⚔️ {target.Name} экипирован пост-Мунлорд сетом ({label}) волей админа.", 255, 200, 80); + } + + private static void Bcast(string msg, byte r, byte g, byte b) + { + try { TShock.Utils.Broadcast(msg, r, g, b); } catch { } + } + + private static void Push(string kind, string msg) + { + _ = Task.Run(async () => + { + try + { + string json = JsonSerializer.Serialize(new { t = "jopa_event", kind, msg }); + await Http.PostAsync(JOPA + "/jopa_event", new StringContent(json, Encoding.UTF8, "application/json")); + } + catch { } + }); + } + } +} diff --git a/JopaBridge.csproj b/JopaBridge.csproj new file mode 100644 index 0000000..c19cd3d --- /dev/null +++ b/JopaBridge.csproj @@ -0,0 +1,16 @@ +<Project Sdk="Microsoft.NET.Sdk"> + <PropertyGroup> + <TargetFramework>net9.0</TargetFramework> + <Nullable>disable</Nullable> + <ImplicitUsings>disable</ImplicitUsings> + <AssemblyName>JopaBridge</AssemblyName> + <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> + <GenerateAssemblyInfo>true</GenerateAssemblyInfo> + </PropertyGroup> + <ItemGroup> + <Reference Include="TShockAPI"><HintPath>../ts/ServerPlugins/TShockAPI.dll</HintPath><Private>false</Private></Reference> + <Reference Include="TerrariaServer"><HintPath>../ts/bin/TerrariaServer.dll</HintPath><Private>false</Private></Reference> + <Reference Include="OTAPI"><HintPath>../ts/bin/OTAPI.dll</HintPath><Private>false</Private></Reference> + <Reference Include="ModFramework"><HintPath>../ts/bin/ModFramework.dll</HintPath><Private>false</Private></Reference> + </ItemGroup> +</Project> diff --git a/README.md b/README.md new file mode 100644 index 0000000..12eec25 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# JopaBridge + +TShock-плагин для Terraria: ловит игровые события (смерти, боссы, вход/выход) +и шлёт их в «Планету Жопа» HTTP-мостом (`POST /jopa_event`), чтобы мир игры +реагировал на происходящее на сервере Terraria. + +## Сборка +``` +dotnet build -c Release +cp out/JopaBridge.dll <TShock>/ServerPlugins/ +``` |
