Plugin fix and update

This commit is contained in:
linmeideli committed 2026-05-27 19:45:29 +08:00
1 parent 8cb23cb15a
commit b2d9a179b6
11 files changed
+183 -173

No files matched your search

+3 -3
View File
@@ -24,15 +24,15 @@ docs/
*.md
# Docker
Dockerfile*
.dockerignore
compose.yml
docker-compose*.yml
# Logs
Log/
*.log
# Data
Data/
# Temp
tmp/
temp/
+17 -92
View File
@@ -1,103 +1,28 @@
# ============================================
# Stage 1: Build server + plugins from source
# Usage:
# 1. Publish the server:
# dotnet publish src/Empostor.Server -c Release -o publish
# 2. Publish plugins:
# dotnet publish src/Empostor.Plugins.Welcome -c Release -o publish/plugins
# (repeat for each plugin)
# 3. Build and run:
# docker compose up -d
# ============================================
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG TARGETARCH
WORKDIR /src
# Copy solution and project files first (layer caching)
COPY src/Empostor.sln ./
COPY src/Directory.Build.props ./ 2>/dev/null || true
COPY src/ProjectRules.ruleset ./ 2>/dev/null || true
# Server + API + Hazel
COPY src/Empostor.Server/Empostor.Server.csproj ./Empostor.Server/
COPY src/Empostor.Api/Empostor.Api.csproj ./Empostor.Api/
COPY src/Empostor.Api.Innersloth.Generator/Empostor.Api.Innersloth.Generator.csproj ./Empostor.Api.Innersloth.Generator/
COPY src/NextFast.Hazel-New/Next.Hazel/Next.Hazel.csproj ./NextFast.Hazel-New/Next.Hazel/
COPY src/NextFast.Hazel-New/Next.Hazel.Abstractions/Next.Hazel.Abstractions.csproj ./NextFast.Hazel-New/Next.Hazel.Abstractions/
# Plugins
COPY src/Empostor.Plugin.Chat/Empostor.Plugin.Chat.csproj ./Empostor.Plugin.Chat/
COPY src/Empostor.Plugin.Code/Empostor.Plugin.Code.csproj ./Empostor.Plugin.Code/
COPY src/Empostor.Plugin.Narrator/Empostor.Plugin.Narrator.csproj ./Empostor.Plugin.Narrator/
COPY src/Empostor.Plugins.Example/Empostor.Plugins.Example.csproj ./Empostor.Plugins.Example/
COPY src/Empostor.Plugins.FixedCode/Empostor.Plugins.FixedCode.csproj ./Empostor.Plugins.FixedCode/
COPY src/Empostor.Plugins.FriendCodeValidator/Empostor.Plugins.FriendCodeValidator.csproj ./Empostor.Plugins.FriendCodeValidator/
COPY src/Empostor.Plugins.PlayerChannel/Empostor.Plugins.PlayerChannel.csproj ./Empostor.Plugins.PlayerChannel/
COPY src/Empostor.Plugins.QqVerify/Empostor.Plugins.QqVerify.csproj ./Empostor.Plugins.QqVerify/
COPY src/Empostor.Plugins.Titles/Empostor.Plugins.Titles.csproj ./Empostor.Plugins.Titles/
COPY src/Empostor.Plugins.Welcome/Empostor.Plugins.Welcome.csproj ./Empostor.Plugins.Welcome/
COPY src/Empostor.Plugins.MapVote/Empostor.Plugins.MapVote.csproj ./Empostor.Plugins.MapVote/
COPY src/Empostor.Plugins.Message/Empostor.Plugins.Message.csproj ./Empostor.Plugins.Message/
# Restore dependencies
RUN dotnet restore Empostor.Server/Empostor.Server.csproj -a $TARGETARCH
# Copy all source
COPY src/ ./
# Publish server (self-contained single file)
RUN dotnet publish Empostor.Server/Empostor.Server.csproj \
-c Release \
-a $TARGETARCH \
--self-contained false \
-o /publish/server
# Publish plugins
RUN dotnet publish Empostor.Plugin.Chat/Empostor.Plugin.Chat.csproj \
-c Release -a $TARGETARCH --self-contained false -o /publish/plugins --no-restore && \
dotnet publish Empostor.Plugin.Code/Empostor.Plugin.Code.csproj \
-c Release -a $TARGETARCH --self-contained false -o /publish/plugins --no-restore && \
dotnet publish Empostor.Plugin.Narrator/Empostor.Plugin.Narrator.csproj \
-c Release -a $TARGETARCH --self-contained false -o /publish/plugins --no-restore && \
dotnet publish Empostor.Plugins.FixedCode/Empostor.Plugins.FixedCode.csproj \
-c Release -a $TARGETARCH --self-contained false -o /publish/plugins --no-restore && \
dotnet publish Empostor.Plugins.FriendCodeValidator/Empostor.Plugins.FriendCodeValidator.csproj \
-c Release -a $TARGETARCH --self-contained false -o /publish/plugins --no-restore && \
dotnet publish Empostor.Plugins.PlayerChannel/Empostor.Plugins.PlayerChannel.csproj \
-c Release -a $TARGETARCH --self-contained false -o /publish/plugins --no-restore && \
dotnet publish Empostor.Plugins.QqVerify/Empostor.Plugins.QqVerify.csproj \
-c Release -a $TARGETARCH --self-contained false -o /publish/plugins --no-restore && \
dotnet publish Empostor.Plugins.Titles/Empostor.Plugins.Titles.csproj \
-c Release -a $TARGETARCH --self-contained false -o /publish/plugins --no-restore && \
dotnet publish Empostor.Plugins.Welcome/Empostor.Plugins.Welcome.csproj \
-c Release -a $TARGETARCH --self-contained false -o /publish/plugins --no-restore && \
dotnet publish Empostor.Plugins.MapVote/Empostor.Plugins.MapVote.csproj \
-c Release -a $TARGETARCH --self-contained false -o /publish/plugins --no-restore && \
dotnet publish Empostor.Plugins.Message/Empostor.Plugins.Message.csproj \
-c Release -a $TARGETARCH --self-contained false -o /publish/plugins --no-restore
# ============================================
# Stage 2: Runtime image
# ============================================
FROM --platform=$TARGETPLATFORM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
FROM --platform=$TARGETPLATFORM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
# Create directory structure
RUN mkdir -p /app/plugins /app/libraries /app/Languages /app/Log /app/config
# Copy pre-published server output
COPY ./publish/ ./
# Copy server
COPY --from=build /publish/server/ ./
# Create runtime directories
RUN mkdir -p /app/plugins /app/libraries /app/Languages /app/Log /app/config /app/Data
# Copy plugins (only .dll files, skip dependency DLLs already in server)
COPY --from=build /publish/plugins/Empostor.Plugin.Chat.dll ./plugins/
COPY --from=build /publish/plugins/Empostor.Plugin.Code.dll ./plugins/
COPY --from=build /publish/plugins/Empostor.Plugin.Narrator.dll ./plugins/
COPY --from=build /publish/plugins/Empostor.Plugins.Example.dll ./plugins/
COPY --from=build /publish/plugins/Empostor.Plugins.FixedCode.dll ./plugins/
COPY --from=build /publish/plugins/Empostor.Plugins.FriendCodeValidator.dll ./plugins/
COPY --from=build /publish/plugins/Empostor.Plugins.PlayerChannel.dll ./plugins/
COPY --from=build /publish/plugins/Empostor.Plugins.QqVerify.dll ./plugins/
COPY --from=build /publish/plugins/Empostor.Plugins.Titles.dll ./plugins/
COPY --from=build /publish/plugins/Empostor.Plugins.Welcome.dll ./plugins/
COPY --from=build /publish/plugins/Empostor.Plugins.MapVote.dll ./plugins/
COPY --from=build /publish/plugins/Empostor.Plugins.Message.dll ./plugins/
# Copy plugins from build output
COPY ./plugins/ ./plugins/ 2>/dev/null || true
# Copy marketplace data
COPY marketplace/ ./marketplace/
COPY ./marketplace/ ./marketplace/ 2>/dev/null || true
ENV ASPNETCORE_URLS=http://0.0.0.0:80
ENV EMPOSTOR_Server__PublicIp=0.0.0.0
@@ -106,6 +31,6 @@ EXPOSE 80/tcp
EXPOSE 22023/tcp
EXPOSE 22023/udp
VOLUME ["/app/config", "/app/plugins", "/app/Languages", "/app/Log", "/app/marketplace"]
VOLUME ["/app/config", "/app/plugins", "/app/Languages", "/app/Log", "/app/Data"]
ENTRYPOINT ["./Empostor.Server"]
-34
View File
@@ -1,34 +0,0 @@
# ============================================
# Usage:
# 1. Publish the server:
# dotnet publish src/Empostor.Server -c Release -o publish
# 2. Build and run:
# docker compose -f compose.publish.yml up -d
# ============================================
FROM --platform=$TARGETPLATFORM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
# Copy pre-published server output
COPY ./publish/ ./
# Create runtime directories
RUN mkdir -p /app/plugins /app/libraries /app/Languages /app/Log
# Copy plugins from build output (built separately)
# Place your plugin DLLs in ./plugins/ before building the image
COPY ./plugins/ ./plugins/ 2>/dev/null || true
# Copy marketplace data
COPY ./marketplace/ ./marketplace/ 2>/dev/null || true
ENV ASPNETCORE_URLS=http://0.0.0.0:80
ENV EMPOSTOR_Server__PublicIp=0.0.0.0
EXPOSE 80/tcp
EXPOSE 22023/tcp
EXPOSE 22023/udp
VOLUME ["/app/config", "/app/plugins", "/app/Languages", "/app/Log"]
ENTRYPOINT ["./Empostor.Server"]
+1 -6
View File
@@ -14,12 +14,7 @@ services:
- ./plugins:/app/plugins
- ./Languages:/app/Languages
- ./marketplace:/app/marketplace
- ./Data:/app/Data
environment:
- ASPNETCORE_URLS=http://0.0.0.0:80
- EMPOSTOR_Server__PublicIp=0.0.0.0
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80/api/admin/update/check"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
@@ -182,6 +182,8 @@ public sealed class LanguageService
"command.stat.tasks": "Tasks Completed: {0}",
"command.stat.exiled": "Times Exiled: {0}",
"welcome.join": "Welcome, {0}! Friend code: {1} | Room: {2}",
"welcome.last_connect": "Last seen: {0}",
"welcome.first_time": "First time here, welcome!",
"command.max.host_only": "Only the host can change the max players.",
"command.max.invalid": "Invalid number. Valid range: 1-127.",
"command.max.set": "Max players set to {0}.",
@@ -235,6 +237,8 @@ public sealed class LanguageService
"command.stat.tasks": "完成任务:{0}",
"command.stat.exiled": "被票出:{0}",
"welcome.join": "欢迎,{0}!好友代码:{1} | 房间:{2}",
"welcome.last_connect": "上次在线:{0}",
"welcome.first_time": "首次光临,欢迎!",
"command.max.host_only": "只有房主可以修改最高人数。",
"command.max.invalid": "无效数字。有效范围:1–127。",
"command.max.set": "最高人数已设置为 {0}。",
@@ -288,6 +292,8 @@ public sealed class LanguageService
"command.stat.tasks": "完成任務:{0}",
"command.stat.exiled": "被票出:{0}",
"welcome.join": "歡迎,{0}!好代碼:{1} | 房間:{2}",
"welcome.last_connect": "上次上線:{0}",
"welcome.first_time": "首次光臨,歡迎!",
"command.max.host_only": "只有房主可以修改最高人數。",
"command.max.invalid": "無效數字。有效範圍:1–127。",
"command.max.set": "最高人數已設置為 {0}。",
@@ -341,6 +347,8 @@ public sealed class LanguageService
"command.stat.tasks": "완료한 작업: {0}",
"command.stat.exiled": "추방됨: {0}",
"welcome.join": "환영합니다, {0}! 친구 코드: {1} | 방: {2}",
"welcome.last_connect": "마지막 접속: {0}",
"welcome.first_time": "처음 방문이세요, 환영합니다!",
"command.max.host_only": "방장만 최대 인원을 변경할 수 있습니다.",
"command.max.invalid": "잘못된 숫자입니다. 유효 범위: 1–127.",
"command.max.set": "최대 인원이 {0}명으로 설정되었습니다.",
@@ -380,6 +388,8 @@ public sealed class LanguageService
"command.stat.tasks": "Заданий выполнено: {0}",
"command.stat.exiled": "Изгнаний: {0}",
"welcome.join": "Добро пожаловать, {0}! Код друга: {1} | Комната: {2}",
"welcome.last_connect": "Последний вход: {0}",
"welcome.first_time": "Первый раз здесь, добро пожаловать!",
"command.max.host_only": "Только хост может изменить максимальное количество игроков.",
"command.max.invalid": "Неверное число. Допустимый диапазон: 1–127.",
"command.max.set": "Максимальное количество игроков установлено на {0}.",
@@ -419,6 +429,8 @@ public sealed class LanguageService
"command.stat.tasks": "Aufgaben erledigt: {0}",
"command.stat.exiled": "Verbannt: {0}",
"welcome.join": "Willkommen, {0}! Freundescode: {1} | Raum: {2}",
"welcome.last_connect": "Zuletzt online: {0}",
"welcome.first_time": "Zum ersten Mal hier, willkommen!",
"command.max.host_only": "Nur der Host kann die maximale Spieleranzahl ändern.",
"command.max.invalid": "Ungültige Zahl. Gültiger Bereich: 1–127.",
"command.max.set": "Maximale Spieleranzahl auf {0} gesetzt.",
@@ -458,6 +470,8 @@ public sealed class LanguageService
"command.stat.tasks": "Tâches accomplies : {0}",
"command.stat.exiled": "Exilés : {0}",
"welcome.join": "Bienvenue, {0} ! Code ami : {1} | Salle : {2}",
"welcome.last_connect": "Dernière visite : {0}",
"welcome.first_time": "Première visite, bienvenue !",
"command.max.host_only": "Seul l'hôte peut modifier le nombre maximum de joueurs.",
"command.max.invalid": "Nombre invalide. Plage valide : 1–127.",
"command.max.set": "Nombre maximum de joueurs défini à {0}.",
@@ -497,6 +511,8 @@ public sealed class LanguageService
"command.stat.tasks": "タスク完了:{0}",
"command.stat.exiled": "追放された回数:{0}",
"welcome.join": "ようこそ、{0}!フレンドコード:{1} | ルーム:{2}",
"welcome.last_connect": "最終接続: {0}",
"welcome.first_time": "初めてのご来訪、ようこそ!",
"command.max.host_only": "ホストのみが最大プレイヤー数を変更できます。",
"command.max.invalid": "無効な数字です。有効範囲: 1–127。",
"command.max.set": "最大プレイヤー数を {0} に設定しました。",
@@ -536,6 +552,8 @@ public sealed class LanguageService
"command.stat.tasks": "Tarefas: {0}",
"command.stat.exiled": "Exilado: {0}",
"welcome.join": "Bem-vindo, {0}! Código de amigo: {1} | Sala: {2}",
"welcome.last_connect": "Última visita: {0}",
"welcome.first_time": "Primeira vez aqui, bem-vindo!",
"command.max.host_only": "Apenas o anfitrião pode alterar o máximo de jogadores.",
"command.max.invalid": "Número inválido. Intervalo válido: 1–127.",
"command.max.set": "Máximo de jogadores definido para {0}.",
+78 -9
View File
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Empostor.Api.Commands;
@@ -30,25 +32,68 @@ public sealed class MapVoteCommand : ICommand
{
var sub = ctx.Args[0].ToLowerInvariant();
if (sub == "public" || sub == "start")
{
if (!ctx.Sender.IsHost)
{
await ctx.PlayerControl.SendChatToPlayerAsync(
T(ctx, "mapvote.host_only", "Only the host can start a map vote."), ctx.PlayerControl);
return true;
}
_service.StartSession(gameCode);
await ctx.PlayerControl.SendChatToPlayerAsync(
T(ctx, "mapvote.session_started", "Map vote session started! Players, use #votemap <map> to vote.\nMaps: Skeld, Mira, Polus, Airship, Fungle"), ctx.PlayerControl);
return true;
}
if (sub == "close" || sub == "end")
{
if (!ctx.Sender.IsHost)
{
await ctx.PlayerControl.SendChatToPlayerAsync(
T(ctx, "mapvote.host_only", "Only the host can close the vote."), ctx.PlayerControl);
return true;
}
var winner = _service.GetWinner(gameCode);
var tally = _service.TallyVotes(gameCode);
var voterCount = _service.VoterCount(gameCode);
await ShowWinnerToAllAsync(ctx, winner, tally, voterCount);
_service.StopSession(gameCode);
return true;
}
if (sub == "enable" || sub == "on")
{
if (ctx.Sender.IsHost)
if (!ctx.Sender.IsHost)
{
_service.SetEnabled(gameCode, true);
await ctx.PlayerControl.SendChatToPlayerAsync(
T(ctx, "mapvote.host_enabled", "Map voting enabled."), ctx.PlayerControl);
T(ctx, "mapvote.host_only", "Only the host can change settings."), ctx.PlayerControl);
return true;
}
_service.SetEnabled(gameCode, true);
await ctx.PlayerControl.SendChatToPlayerAsync(
T(ctx, "mapvote.host_enabled", "Map voting enabled."), ctx.PlayerControl);
return true;
}
if (sub == "disable" || sub == "off")
{
if (ctx.Sender.IsHost)
if (!ctx.Sender.IsHost)
{
_service.SetEnabled(gameCode, false);
await ctx.PlayerControl.SendChatToPlayerAsync(
T(ctx, "mapvote.host_disabled", "Map voting disabled."), ctx.PlayerControl);
T(ctx, "mapvote.host_only", "Only the host can change settings."), ctx.PlayerControl);
return true;
}
_service.SetEnabled(gameCode, false);
await ctx.PlayerControl.SendChatToPlayerAsync(
T(ctx, "mapvote.host_disabled", "Map voting disabled."), ctx.PlayerControl);
return true;
}
@@ -114,16 +159,40 @@ public sealed class MapVoteCommand : ICommand
.Replace("{1}", count.ToString()));
}
await ctx.PlayerControl.SendChatToPlayerAsync(sb.ToString(), ctx.PlayerControl);
}
private static async ValueTask ShowWinnerToAllAsync(CommandContext ctx, Api.Innersloth.MapTypes winner,
IReadOnlyDictionary<Api.Innersloth.MapTypes, int> tally, int voterCount)
{
var sb = new StringBuilder();
if (tally.Count == 0)
{
sb.AppendLine(ctx.Lang.Get("mapvote.results_random", ctx.SenderLanguage)
.Replace("{0}", MapVoteService.MapDisplayName(winner)));
}
else
{
var maxVotes = tally.Values.Max();
sb.AppendLine(ctx.Lang.Get("mapvote.results_winner", ctx.SenderLanguage)
.Replace("{0}", MapVoteService.MapDisplayName(winner))
.Replace("{1}", maxVotes.ToString()));
}
foreach (var player in ctx.Game.Players)
{
var ctrl = player.Character;
if (ctrl != null)
await ctrl.SendChatToPlayerAsync(sb.ToString(), ctrl);
}
}
private async ValueTask ShowUsageAsync(CommandContext ctx)
{
var sb = new StringBuilder();
sb.AppendLine(T(ctx, "mapvote.usage", "Usage: #votemap <map>"));
sb.AppendLine(T(ctx, "mapvote.usage", "Usage: #votemap <map>\nMaps: Skeld, Mira, Polus, Airship, Fungle\nExample: #votemap polus"));
if (ctx.Sender.IsHost)
sb.AppendLine(T(ctx, "mapvote.host_commands", "Host commands: ..."));
sb.AppendLine(T(ctx, "mapvote.host_commands", "Host commands: #votemap public | close | enable | disable | results"));
await ctx.PlayerControl.SendChatToPlayerAsync(sb.ToString(), ctx.PlayerControl);
}
@@ -1,4 +1,3 @@
using System.Text;
using System.Threading.Tasks;
using Empostor.Api.Events;
using Empostor.Api.Events.Game;
@@ -27,13 +26,12 @@ public sealed class MapVoteEventListener : IEventListener
var winner = _service.GetWinner(gameCode);
// If host set a specific map and override is allowed, respect it
// If host set a specific map and override is allowed, respect it when no votes cast
if (_config.AllowHostOverride && e.Game.Options.Map != winner)
{
var tally = _service.TallyVotes(gameCode);
if (tally.Count == 0)
{
// No votes at all — host's choice stands
_service.ResetVotes(gameCode);
return;
}
+11 -5
View File
@@ -41,9 +41,11 @@ public sealed class MapVotePlugin : PluginBase, IPluginLanguageProvider
["mapvote.results_winner"] = "Winner: {0} ({1} votes)",
["mapvote.results_random"] = "No votes cast, using random map: {0}",
["mapvote.usage"] = "Usage: #votemap <map>\nMaps: Skeld, Mira, Polus, Airship, Fungle\nExample: #votemap polus",
["mapvote.host_commands"] = "Host commands: #votemap enable | disable | results",
["mapvote.host_commands"] = "Host commands: #votemap public | close | enable | disable | results",
["mapvote.host_only"] = "Only the host can use this command.",
["mapvote.host_enabled"] = "Map voting enabled.",
["mapvote.host_disabled"] = "Map voting disabled.",
["mapvote.session_started"] = "Map vote session started! Players, use #votemap <map> to vote.\nMaps: Skeld, Mira, Polus, Airship, Fungle",
["mapvote.map_set"] = "Map set to {0} by vote.",
},
["zh_CN"] = new Dictionary<string, string>
@@ -60,10 +62,12 @@ public sealed class MapVotePlugin : PluginBase, IPluginLanguageProvider
["mapvote.results_winner"] = "胜出:{0}({1} 票)",
["mapvote.results_random"] = "无人投票,随机选择地图:{0}",
["mapvote.usage"] = "用法:#votemap <地图>\n可选地图:Skeld, Mira, Polus, Airship, Fungle\n示例:#votemap polus",
["mapvote.host_commands"] = "房主指令:#votemap enable | disable | results",
["mapvote.host_commands"] = "房主指令:#votemap public | close | enable | disable | results",
["mapvote.host_only"] = "只有房主可以使用此指令。",
["mapvote.host_enabled"] = "投票选图已启用。",
["mapvote.host_disabled"] = "投票选图已禁用。",
["mapvote.map_set"] = "投票结果为 {0}。",
["mapvote.session_started"] = "地图投票已开始!请使用 #votemap <地图> 投票。\n可选地图:Skeld, Mira, Polus, Airship, Fungle",
["mapvote.map_set"] = "投票结果,地图已设为 {0}。",
},
["zh_TW"] = new Dictionary<string, string>
{
@@ -79,10 +83,12 @@ public sealed class MapVotePlugin : PluginBase, IPluginLanguageProvider
["mapvote.results_winner"] = "勝出:{0}({1} 票)",
["mapvote.results_random"] = "無人投票,隨機選擇地圖:{0}",
["mapvote.usage"] = "用法:#votemap <地圖>\n可選地圖:Skeld, Mira, Polus, Airship, Fungle\n範例:#votemap polus",
["mapvote.host_commands"] = "房主指令:#votemap enable | disable | results",
["mapvote.host_commands"] = "房主指令:#votemap public | close | enable | disable | results",
["mapvote.host_only"] = "只有房主可以使用此指令。",
["mapvote.host_enabled"] = "投票選圖已啟用。",
["mapvote.host_disabled"] = "投票選圖已停用。",
["mapvote.map_set"] = "投票結果為 {0}。",
["mapvote.session_started"] = "地圖投票已開始!請使用 #votemap <地圖> 投票。\n可選地圖:Skeld, Mira, Polus, Airship, Fungle",
["mapvote.map_set"] = "投票結果,地圖已設為 {0}。",
},
};
}
+20 -1
View File
@@ -33,11 +33,22 @@ public sealed class MapVoteService
private readonly ConcurrentDictionary<string, ConcurrentDictionary<string, MapTypes>> _votes = new();
private readonly ConcurrentDictionary<string, bool> _enabled = new();
private readonly ConcurrentDictionary<string, bool> _sessionActive = new();
public bool IsEnabled(string gameCode) => _enabled.GetValueOrDefault(gameCode, true);
public void SetEnabled(string gameCode, bool enabled) => _enabled[gameCode] = enabled;
public bool IsSessionActive(string gameCode) => _sessionActive.GetValueOrDefault(gameCode, false);
public void StartSession(string gameCode) => _sessionActive[gameCode] = true;
public void StopSession(string gameCode)
{
_sessionActive.TryRemove(gameCode, out _);
_votes.TryRemove(gameCode, out _);
}
public bool TryParseMap(string input, out MapTypes map)
{
if (MapAliases.TryGetValue(input.Trim(), out map))
@@ -46,7 +57,7 @@ public sealed class MapVoteService
if (int.TryParse(input.Trim(), out var id) && Enum.IsDefined(typeof(MapTypes), (byte)id))
{
map = (MapTypes)(byte)id;
if (map != MapTypes.Dleks) // Exclude Dleks (mirrored Skeld)
if (map != MapTypes.Dleks)
return true;
}
@@ -86,6 +97,13 @@ public sealed class MapVoteService
.ToDictionary(g => g.Key, g => g.Count());
}
public int VoterCount(string gameCode)
{
if (!_votes.TryGetValue(gameCode, out var gameVotes))
return 0;
return gameVotes.Count;
}
public MapTypes GetWinner(string gameCode)
{
var tally = TallyVotes(gameCode);
@@ -114,5 +132,6 @@ public sealed class MapVoteService
{
_votes.TryRemove(gameCode, out _);
_enabled.TryRemove(gameCode, out _);
_sessionActive.TryRemove(gameCode, out _);
}
}
@@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Empostor.Api.Data;
using Empostor.Api.Events;
using Empostor.Api.Events.Game.Player;
using Empostor.Api.Innersloth;
@@ -48,10 +49,10 @@ public sealed class WelcomeEventListener : IEventListener
}
}
var message = File.ReadAllText(filePath);
var formattedMessage = string.Format(message, player.Client.Name, e.Game.Code.Code);
var template = File.ReadAllText(filePath);
var message = FormatMessage(template, player);
await playerCtrl.SendChatToPlayerAsync(formattedMessage, playerCtrl);
await playerCtrl.SendChatToPlayerAsync(message, playerCtrl);
}
catch (Exception ex)
{
@@ -59,4 +60,17 @@ public sealed class WelcomeEventListener : IEventListener
}
});
}
private static string FormatMessage(string template, Api.Net.IClientPlayer player)
{
var lastConnect = player.Client.ProductUserId != null
? PlayerConnectStore.GetLastConnectString(player.Client.ProductUserId)
: null;
return template
.Replace("{Name}", player.Client.Name ?? "Player")
.Replace("{Room}", player.Game.Code.Code)
.Replace("{FriendCode}", player.Client.FriendCode ?? "None")
.Replace("{LastConnect}", lastConnect ?? "First time!");
}
}
+17 -17
View File
@@ -6,7 +6,7 @@ using Microsoft.Extensions.Logging;
namespace Empostor.Plugins.Welcome;
[EmpostorPlugin("cn.hayashiume.welcome", "Welcome Messages", "HayashiUme", "1.0.0")]
[EmpostorPlugin("cn.hayashiume.welcome", "Welcome Messages", "HayashiUme", "1.1.0")]
public sealed class WelcomePlugin : PluginBase
{
private const string TextDir = "Message";
@@ -38,22 +38,22 @@ public sealed class WelcomePlugin : PluginBase
{
var dir = Path.Combine(Directory.GetCurrentDirectory(), TextDir);
WriteIfMissing(dir, "EnglishHelloWord.txt", "Welcome, {0}! Room: {1}");
WriteIfMissing(dir, "SChineseHelloWord.txt", "欢迎,{0}!房间:{1}");
WriteIfMissing(dir, "TChineseHelloWord.txt", "歡迎,{0}!房間:{1}");
WriteIfMissing(dir, "KoreanHelloWord.txt", "환영합니다, {0}! 방: {1}");
WriteIfMissing(dir, "RussianHelloWord.txt", "Добро пожаловать, {0}! Комната: {1}");
WriteIfMissing(dir, "PortugueseHelloWord.txt", "Bem-vindo, {0}! Sala: {1}");
WriteIfMissing(dir, "BrazilianHelloWord.txt", "Bem-vindo, {0}! Sala: {1}");
WriteIfMissing(dir, "FrenchHelloWord.txt", "Bienvenue, {0}! Salle : {1}");
WriteIfMissing(dir, "GermanHelloWord.txt", "Willkommen, {0}! Raum: {1}");
WriteIfMissing(dir, "SpanishHelloWord.txt", "¡Bienvenido, {0}! Sala: {1}");
WriteIfMissing(dir, "LatamHelloWord.txt", "¡Bienvenido, {0}! Sala: {1}");
WriteIfMissing(dir, "JapaneseHelloWord.txt", "ようこそ、{0}!ルーム:{1}");
WriteIfMissing(dir, "ItalianHelloWord.txt", "Benvenuto, {0}! Stanza: {1}");
WriteIfMissing(dir, "DutchHelloWord.txt", "Welkom, {0}! Kamer: {1}");
WriteIfMissing(dir, "FilipinoHelloWord.txt", "Maligayang pagdating, {0}! Silid: {1}");
WriteIfMissing(dir, "IrishHelloWord.txt", "Fáilte, {0}! Seomra: {1}");
WriteIfMissing(dir, "EnglishHelloWord.txt", "Welcome, {Name}! Room: {Room}");
WriteIfMissing(dir, "SChineseHelloWord.txt", "欢迎,{Name}!房间:{Room}");
WriteIfMissing(dir, "TChineseHelloWord.txt", "歡迎,{Name}!房間:{Room}");
WriteIfMissing(dir, "KoreanHelloWord.txt", "환영합니다, {Name}! 방: {Room}");
WriteIfMissing(dir, "RussianHelloWord.txt", "Добро пожаловать, {Name}! Комната: {Room}");
WriteIfMissing(dir, "PortugueseHelloWord.txt", "Bem-vindo, {Name}! Sala: {Room}");
WriteIfMissing(dir, "BrazilianHelloWord.txt", "Bem-vindo, {Name}! Sala: {Room}");
WriteIfMissing(dir, "FrenchHelloWord.txt", "Bienvenue, {Name}! Salle : {Room}");
WriteIfMissing(dir, "GermanHelloWord.txt", "Willkommen, {Name}! Raum: {Room}");
WriteIfMissing(dir, "SpanishHelloWord.txt", "¡Bienvenido, {Name}! Sala: {Room}");
WriteIfMissing(dir, "LatamHelloWord.txt", "¡Bienvenido, {Name}! Sala: {Room}");
WriteIfMissing(dir, "JapaneseHelloWord.txt", "ようこそ、{Name}!ルーム:{Room}");
WriteIfMissing(dir, "ItalianHelloWord.txt", "Benvenuto, {Name}! Stanza: {Room}");
WriteIfMissing(dir, "DutchHelloWord.txt", "Welkom, {Name}! Kamer: {Room}");
WriteIfMissing(dir, "FilipinoHelloWord.txt", "Maligayang pagdating, {Name}! Silid: {Room}");
WriteIfMissing(dir, "IrishHelloWord.txt", "Fáilte, {Name}! Seomra: {Room}");
}
private void WriteIfMissing(string dir, string fileName, string content)