1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
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 { }
});
}
}
}
|