mirror of
https://github.com/Empostor/Empostor.git
synced 2026-10-20 01:46:07 +00:00
Plugin fix and update
This commit is contained in:
1 parent
8cb23cb15a
commit
b2d9a179b6
11 files changed
+183
-173
No files matched your search
+3
-3
@@ -24,15 +24,15 @@ docs/
|
||||
*.md
|
||||
|
||||
# Docker
|
||||
Dockerfile*
|
||||
.dockerignore
|
||||
compose.yml
|
||||
docker-compose*.yml
|
||||
|
||||
# Logs
|
||||
Log/
|
||||
*.log
|
||||
|
||||
# Data
|
||||
Data/
|
||||
|
||||
# Temp
|
||||
tmp/
|
||||
temp/
|
||||
|
||||
+17
-92
@@ -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"]
|
||||
@@ -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
@@ -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}.",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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}。",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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!");
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user