Dual-track plan covering Global Renaming (NahidaImpact → PaimonMultiplier) and Disguise System (Host-Tunneling entity morphing for mass-multiplayer).
| Category | Count | Action |
|---|---|---|
| Project directories | 6 | Rename |
.sln / .csproj files |
7 | Rename + rewrite refs |
| Namespace declarations | ~110 | Replace NahidaImpact. → PaimonMultiplier. |
using statements |
~117 | Replace NahidaImpact. → PaimonMultiplier. |
Type-alias using (Kcp) |
12 | Replace NahidaImpact. → PaimonMultiplier. |
| String literals (console, email, title) | 5 | Targeted edits |
| Documentation (AGENTS.md, README.md) | ~16 | Targeted edits |
| Proto files (590+) | 0 refs | No change needed |
Game asset JSON (ConfigAvatar_Nahida.json, icons, abilities) |
90+ refs | DO NOT RENAME — these reference the in-game character Nahida, not the project |
mv NahidaImpact.sln PaimonMultiplier.sln
mv NahidaImpact.Common PaimonMultiplier.Common
mv NahidaImpact.Gameserver PaimonMultiplier.Gameserver
mv NahidaImpact.Kcp PaimonMultiplier.Kcp
mv NahidaImpact.Protocol PaimonMultiplier.Protocol
mv NahidaImpact.Proxy PaimonMultiplier.Proxy
mv NahidaImpact.SDK PaimonMultiplier.SDKRename .csproj files within each directory:
NahidaImpact.Common/NahidaImpact.Common.csproj → PaimonMultiplier.Common/PaimonMultiplier.Common.csproj
NahidaImpact.Gameserver/NahidaImpact.Gameserver.csproj → PaimonMultiplier.Gameserver/PaimonMultiplier.Gameserver.csproj
NahidaImpact.Kcp/NahidaImpact.Kcp.csproj → PaimonMultiplier.Kcp/PaimonMultiplier.Kcp.csproj
NahidaImpact.Protocol/NahidaImpact.Protocol.csproj → PaimonMultiplier.Protocol/PaimonMultiplier.Protocol.csproj
NahidaImpact.Proxy/NahidaImpact.Proxy.csproj → PaimonMultiplier.Proxy/PaimonMultiplier.Proxy.csproj
NahidaImpact.SDK/NahidaImpact.SDK.csproj → PaimonMultiplier.SDK/PaimonMultiplier.SDK.csproj
Replace all 6 Project(...) entries: update project name strings and .csproj paths from NahidaImpact to PaimonMultiplier. Keep GUIDs unchanged.
| File | Old Reference | New Reference |
|---|---|---|
PaimonMultiplier.Common.csproj |
..\NahidaImpact.Protocol\NahidaImpact.Protocol.csproj |
..\PaimonMultiplier.Protocol\PaimonMultiplier.Protocol.csproj |
PaimonMultiplier.Gameserver.csproj |
..\NahidaImpact.Common\NahidaImpact.Common.csproj |
..\PaimonMultiplier.Common\PaimonMultiplier.Common.csproj |
PaimonMultiplier.Gameserver.csproj |
..\NahidaImpact.Kcp\NahidaImpact.Kcp.csproj |
..\PaimonMultiplier.Kcp\PaimonMultiplier.Kcp.csproj |
PaimonMultiplier.Gameserver.csproj |
..\NahidaImpact.Protocol\NahidaImpact.Protocol.csproj |
..\PaimonMultiplier.Protocol\PaimonMultiplier.Protocol.csproj |
PaimonMultiplier.SDK.csproj |
..\NahidaImpact.Common\NahidaImpact.Common.csproj |
..\PaimonMultiplier.Common\PaimonMultiplier.Common.csproj |
PaimonMultiplier.SDK.csproj |
..\NahidaImpact.Protocol\NahidaImpact.Protocol.csproj |
..\PaimonMultiplier.Protocol\PaimonMultiplier.Protocol.csproj |
Target: ALL .cs files across all 6 projects.
Single rule: Replace NahidaImpact. → PaimonMultiplier. (trailing dot prevents matching game character "Nahida" in asset JSON).
This covers:
- All
namespace NahidaImpact.xxx;declarations - All
using NahidaImpact.xxx;statements - All type-alias usings (e.g.,
NahidaImpact.Kcp.KcpBuffer→PaimonMultiplier.Kcp.KcpBuffer) - Cross-project type references
| File | Old | New |
|---|---|---|
Gameserver/Program.cs line 20 |
Console.Title = "NahidaImpact | Game Server [Experimental] (4.3.50)" |
Console.Title = "PaimonMultiplier 4.3.50 - Infinite Coop Architecture" |
Gameserver/Program.cs line 22 (ASCII art) |
NahidaImpact |
PaimonMultiplier |
Proxy/Program.cs line 3 |
Console.Title = "NahidaImpact | Proxy" |
"PaimonMultiplier | Proxy" |
SDK/Program.cs line 3 |
Console.Title = "NahidaImpact | SDK Server" |
"PaimonMultiplier | SDK Server" |
SDK/Handlers/AuthHandler.cs line 20 |
Email = "NahidaImpact" |
"PaimonMultiplier" |
SDK/Handlers/RegionHandler.cs line 41 |
Title = "NahidaImpact" |
"PaimonMultiplier" |
AGENTS.md:
- Line 1:
# AGENTS.md — PaimonMultiplier - Line 6:
dotnet build PaimonMultiplier.sln - Lines 10-11: project names
- Lines 16-21: architecture table entries
- Line 40:
PaimonMultiplier.Common/assets/ - Line 41: git remote URL
README.md:
- Line 2:
# PaimonMultiplier - Line 9: project names
- Line 13: project name references
dotnet build PaimonMultiplier.slnThe Disguise System is the core architectural innovation enabling mass-multiplayer. Guest players are instantiated as ProtEntityType.Monster entities with EntityDisguiseInfo payloads, causing the 4.4.0 client to render them as full avatars. This bypasses the hardcoded 4-player team limit.
New file: PaimonMultiplier.Protocol/EntityDisguiseInfo.proto
syntax = "proto3";
message EntityDisguiseInfo {
uint32 avatar_id = 1; // Visual avatar ID (e.g. 10000073 = Albedo)
uint32 costume_id = 2; // Costume override (0 = default)
uint32 flycloak_id = 3; // Glider visual (0 = default)
uint32 skill_depot_id = 4; // Ability visual effects
uint32 weapon_id = 5; // Visual weapon (0 = hide)
uint32 weapon_costume_id = 6; // Weapon skin override
}Modify: PaimonMultiplier.Protocol/SceneMonsterInfo.proto
Add import and field:
import "EntityDisguiseInfo.proto";
// Inside SceneMonsterInfo message:
EntityDisguiseInfo entity_disguise_info = 24;Field 24 is safe — the 4.2.5 proto zip shows fields 1-23 used.
New file: PaimonMultiplier.Gameserver/Game/Entity/DisguisedMonsterEntity.cs
using NahidaImpact.Common.Constants;
using NahidaImpact.Common.Data.Binout;
using NahidaImpact.Common.Data.Binout.Ability;
using NahidaImpact.Common.Extensions;
using NahidaImpact.Gameserver.Game.Avatar;
using NahidaImpact.Protocol;
namespace NahidaImpact.Gameserver.Game.Entity;
internal class DisguisedMonsterEntity : SceneEntity
{
public override ProtEntityType EntityType => ProtEntityType.Monster;
public uint Uid { get; }
public uint WeaponEntityId { get; }
public GameAvatar GameAvatar { get; }
public DisguisedMonsterEntity(GameAvatar gameAvatar, uint uid, uint entityId, uint weaponEntityId)
: base(entityId)
{
Uid = uid;
WeaponEntityId = ((uint)ProtEntityType.Weapon << 24) + weaponEntityId;
GameAvatar = gameAvatar;
Properties = gameAvatar.Properties;
FightProperties = gameAvatar.FightProperties;
}
public AbilityControlBlock BuildAbilityControlBlock(BinDataCollection binData)
{
AbilityControlBlock block = new();
AvatarConfig avatarConfig = binData.GetAvatarConfig(GameAvatar.AvatarId);
uint defaultOverrideHash = "Default".GetStableHash();
foreach (string abilityName in AvatarConstants.CommonAbilities)
{
block.AbilityEmbryoList.Add(new AbilityEmbryo
{
AbilityId = (uint)(block.AbilityEmbryoList.Count + 1),
AbilityNameHash = abilityName.GetStableHash(),
AbilityOverrideNameHash = defaultOverrideHash
});
}
foreach (AbilityData ability in avatarConfig.Abilities)
{
block.AbilityEmbryoList.Add(new AbilityEmbryo
{
AbilityId = (uint)(block.AbilityEmbryoList.Count + 1),
AbilityNameHash = ability.AbilityName.GetStableHash(),
AbilityOverrideNameHash = ability.GetAbilityOverride().GetStableHash()
});
}
return block;
}
public override SceneEntityInfo AsInfo()
{
SceneEntityInfo info = base.AsInfo();
info.Monster = new SceneMonsterInfo
{
MonsterId = 210101001,
ConfigId = 1,
AuthorityPeerId = Uid,
EntityDisguiseInfo = new EntityDisguiseInfo
{
AvatarId = GameAvatar.AvatarId,
CostumeId = 0,
FlycloakId = GameAvatar.WearingFlycloakId,
SkillDepotId = GameAvatar.SkillDepotId,
WeaponId = GameAvatar.WeaponId,
WeaponCostumeId = 0
}
};
return info;
}
}Design notes:
EntityTypereturnsProtEntityType.Monster(not Avatar)AsInfo()populatesSceneEntityInfo.monsterwithSceneMonsterInfocontainingEntityDisguiseInfoMonsterId210101001 is a placeholder — any valid monster ID works since the client ignores it whenEntityDisguiseInfois present- Carries the same
AbilityControlBlockpipeline asAvatarEntityfor client-side ability effects - Shares
MotionInfo,Properties, andFightPropertiesfrom the sourceGameAvatar
Modify: PaimonMultiplier.Gameserver/Game/Entity/Factory/EntityFactory.cs
Add method:
public DisguisedMonsterEntity CreateDisguisedMonster(GameAvatar gameAvatar, uint belongUid)
{
return new(gameAvatar, belongUid, ++_entityIdSeed, ++_entityIdSeed);
}Modify: PaimonMultiplier.Gameserver/Game/Scene/SceneManager.cs
Add field and methods:
private readonly List<DisguisedMonsterEntity> _disguisedGuests = [];
public async ValueTask SpawnDisguisedGuestsAsync(List<GameAvatar> guestAvatars, uint guestUid)
{
SceneEntity? hostEntity = _entityManager.GetEntityById(_player.CurAvatarEntityId);
Vector pos = hostEntity?.MotionInfo.Pos ?? _player.LastMainWorldPos;
foreach (GameAvatar guestAvatar in guestAvatars)
{
DisguisedMonsterEntity entity = _entityFactory.CreateDisguisedMonster(guestAvatar, guestUid);
entity.SetPosition(pos.X, pos.Y, pos.Z);
_disguisedGuests.Add(entity);
await _entityManager.SpawnEntityAsync(entity, VisionType.Born);
}
}
public async ValueTask RemoveDisguisedGuestsAsync()
{
foreach (DisguisedMonsterEntity entity in _disguisedGuests)
{
await _session.NotifyAsync(CmdType.SceneEntityDisappearNotify,
new SceneEntityDisappearNotify
{
EntityList = { entity.EntityId },
DisappearType = VisionType.Dismiss
});
}
_disguisedGuests.Clear();
}Update ResetState() to clear disguised guests:
private void ResetState()
{
_teamAvatars.Clear();
_disguisedGuests.Clear();
_entityManager.Reset();
}SessionEntityEventListener.OnEntitySpawned already broadcasts via CmdType.SceneEntityAppearNotify with entity.AsInfo(). The polymorphic dispatch ensures DisguisedMonsterEntity.AsInfo() produces the correct SceneMonsterInfo with EntityDisguiseInfo.
IEntityEventListener interface already handles generic SceneEntity — no modification required.
New file: PaimonMultiplier.Gameserver/Controllers/MultiplayerController.cs
using NahidaImpact.Gameserver.Controllers.Attributes;
using NahidaImpact.Gameserver.Controllers.Result;
using NahidaImpact.Gameserver.Game;
using NahidaImpact.Gameserver.Game.Scene;
using NahidaImpact.Protocol;
namespace NahidaImpact.Gameserver.Controllers;
[NetController]
internal class MultiplayerController : ControllerBase
{
[NetCommand(CmdType.GetWorldMpInfoReq)]
public ValueTask<IResult> OnGetWorldMpInfoReq(Player player)
{
return ValueTask.FromResult(Response(CmdType.GetWorldMpInfoRsp, new GetWorldMpInfoRsp
{
IsMp = true,
PlayerCount = 1
}));
}
[NetCommand(CmdType.GetPlayerMpModeAvailabilityReq)]
public ValueTask<IResult> OnGetPlayerMpModeAvailabilityReq()
{
return ValueTask.FromResult(Response(CmdType.GetPlayerMpModeAvailabilityRsp,
new GetPlayerMpModeAvailabilityRsp
{
MpModeAvailability = 0
}));
}
}dotnet build PaimonMultiplier.slnBoth tracks must be executed in order because Track B code uses the OLD namespaces during development, then Track A renames everything.
Recommended order:
- Track B first — Implement Disguise System with current
NahidaImpactnamespaces - Build and verify Track B works
- Track A — Global rename NahidaImpact → PaimonMultiplier
- Final build verification
Alternatively, if doing both simultaneously: rename first (Track A), then implement new code (Track B) using PaimonMultiplier namespaces from the start.
| Operation | File |
|---|---|
| Rename | NahidaImpact.sln → PaimonMultiplier.sln |
| Rename | NahidaImpact.Common/ → PaimonMultiplier.Common/ (+ csproj) |
| Rename | NahidaImpact.Gameserver/ → PaimonMultiplier.Gameserver/ (+ csproj) |
| Rename | NahidaImpact.Kcp/ → PaimonMultiplier.Kcp/ (+ csproj) |
| Rename | NahidaImpact.Protocol/ → PaimonMultiplier.Protocol/ (+ csproj) |
| Rename | NahidaImpact.Proxy/ → PaimonMultiplier.Proxy/ (+ csproj) |
| Rename | NahidaImpact.SDK/ → PaimonMultiplier.SDK/ (+ csproj) |
| Edit | .sln — all 6 Project entries |
| Edit | 4 .csproj files — ProjectReference paths |
| Edit | ~110 .cs files — namespace + using replacements |
| Edit | Gameserver/Program.cs — Console.Title + banner |
| Edit | Proxy/Program.cs — Console.Title |
| Edit | SDK/Program.cs — Console.Title |
| Edit | SDK/Handlers/AuthHandler.cs — Email string |
| Edit | SDK/Handlers/RegionHandler.cs — Title string |
| Edit | AGENTS.md — all project references |
| Edit | README.md — all project references |
| Operation | File |
|---|---|
| Create | Protocol/EntityDisguiseInfo.proto |
| Edit | Protocol/SceneMonsterInfo.proto — add field 24 |
| Create | Gameserver/Game/Entity/DisguisedMonsterEntity.cs |
| Edit | Gameserver/Game/Entity/Factory/EntityFactory.cs — add CreateDisguisedMonster |
| Edit | Gameserver/Game/Scene/SceneManager.cs — add guest spawn/remove methods |
| Create | Gameserver/Controllers/MultiplayerController.cs |
ConfigAvatar_Nahida.json— in-game character config (Nahida = Lesser Lord Kusanali)AvatarExcelConfigData.json—UI_AvatarIcon_Nahida,AvatarImage_Forward_Nahida(game assets)- All 590+
.protofiles — no project name references - All
appsettings.jsonfiles — no project name references