Skip to content

Commit 98387d5

Browse files
committed
Total Player Online
- Give a seek of how many players(or machines) is online with WorldLink on the client side - Client side changes color when someone is recruiting - Server side show online counts after a player joins or leaves - Updated ReadMe files for developers
1 parent 3d93369 commit 98387d5

File tree

6 files changed

+172
-3
lines changed

6 files changed

+172
-3
lines changed

README.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,104 @@ WorldLink maimai DX Online C2C Multiplayer Mod
3434
## Running a Server
3535

3636
If you want to self-host a server to play with your friends, read the [Self Host Guide](README.HOST.md).
37+
38+
## Developer Guide
39+
40+
This project consists of two main components:
41+
- **Server** (Kotlin/Ktor) - Handles multiplayer functionality
42+
- **Client Mod** (C#) - DLL for the MAI2 game
43+
44+
### Prerequisites
45+
46+
- **Java 17+** for building the server
47+
- **.NET Framework 4.7.2** for building the client mod
48+
- **MAI2 game DLLs** (see below)
49+
50+
### Building the Server
51+
52+
The server is a Kotlin application using Ktor framework.
53+
54+
```bash
55+
# Build the server (excluding tests)
56+
./gradlew build -x test
57+
58+
# The JAR file will be created at:
59+
# build/libs/worldlinkd.jar
60+
```
61+
62+
### Building the Client Mod
63+
64+
The client mod requires game-specific DLLs that are not included in the repository.
65+
66+
#### 1. Required Game DLLs
67+
68+
You need to obtain these DLLs from your MAI2 installation:
69+
- `mod/Libs/Assembly-CSharp.dll` - Main game assembly
70+
- `mod/Libs/Assembly-CSharp-firstpass.dll` - Game assembly (first pass)
71+
- `mod/Libs/AMDaemon.NET.dll` - AMDaemon library
72+
73+
#### 2. Build the Mod
74+
75+
```bash
76+
# Navigate to the mod directory
77+
cd mod
78+
79+
# Build the mod
80+
dotnet build
81+
82+
# The DLL will be created at:
83+
# bin/Debug/net472/WorldLink.dll
84+
```
85+
86+
#### 3. Installation
87+
88+
Copy the built `WorldLink.dll` to your MAI2 game's `Mods` folder.
89+
90+
### Development Workflow
91+
92+
1. **Server Development:**
93+
- Modify server code in `src/main/kotlin/`
94+
- Run `./gradlew build -x test` to rebuild
95+
- Test with `java -jar build/libs/worldlinkd.jar`
96+
97+
2. **Client Mod Development:**
98+
- Modify mod code in `mod/WorldLink/`
99+
- Run `dotnet build` from the `mod/` directory
100+
- Copy the new DLL to your game's Mods folder
101+
- Restart the game to test changes
102+
103+
### Project Structure
104+
105+
```
106+
worldlinkd/
107+
├── src/main/kotlin/ # Server source code
108+
│ ├── Application.kt # Main server application
109+
│ ├── FutariLobby.kt # Lobby server logic
110+
│ ├── FutariRelay.kt # Relay server logic
111+
│ └── FutariTypes.kt # Data structures
112+
├── mod/ # Client mod source code
113+
│ ├── WorldLink/ # Main mod code
114+
│ │ ├── FutariPatch.cs # Main patch logic
115+
│ │ ├── FutariClient.cs # Client communication
116+
│ │ ├── FutariExt.cs # Utility extensions
117+
│ │ └── FutariTypes.cs # Data structures
118+
│ ├── Libs/ # Game DLLs (not included)
119+
│ └── WorldLink.csproj # Mod project file
120+
└── build.gradle.kts # Server build configuration
121+
```
122+
123+
### Troubleshooting
124+
125+
#### Build Errors
126+
127+
- **Missing DLLs:** Ensure all required game DLLs are in `mod/Libs/`
128+
- **Java version:** Make sure you're using Java 17 or higher
129+
- **.NET Framework:** Ensure .NET Framework 4.7.2 is installed
130+
131+
#### Runtime Issues
132+
133+
- **Server won't start:** Check if port 20100 is available
134+
- **Mod not loading:** Verify the DLL is in the correct Mods folder and do not use dirty packs(脏脏包)
135+
- **Connection issues:** Check firewall settings and server configuration
136+
137+
For more detailed information about the modding process, see the [Self Host Guide](README.HOST.md).

mod/WorldLink/FutariExt.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ public static byte[] View(this byte[] buffer, int offset, int size)
4747

4848
public static V? Get<K, V>(this ConcurrentDictionary<K, V> dict, K key) where V : class
4949
{
50-
return dict.GetValueOrDefault(key);
50+
dict.TryGetValue(key, out V value);
51+
return value;
5152
}
5253

5354
// Call a function using reflection

mod/WorldLink/FutariPatch.cs

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public static class Futari
3737
private static bool checkAuthCalled = false;
3838
private static bool isInit = false;
3939
public static bool stopping = false;
40+
private static int onlineUserCount = 0;
4041

4142
private static MethodBase packetWriteUInt;
4243
private static System.Type StartUpStateType;
@@ -98,6 +99,12 @@ public static bool CheckAuth_Proc()
9899
client.keychip = keychip;
99100
client.ConnectAsync();
100101
isInit = true;
102+
103+
// Fetch initial online user count
104+
FetchOnlineUserCount();
105+
106+
// Start periodic online user count fetching
107+
30000.Interval(() => FetchOnlineUserCount());
101108
}).Start();
102109

103110
return PrefixRet.RUN_ORIGINAL;
@@ -116,6 +123,23 @@ private static bool PreIsLanAvailable(ref bool __result)
116123
return false;
117124
}
118125
#endif
126+
127+
// Fetch online user count from server
128+
private static void FetchOnlineUserCount()
129+
{
130+
try
131+
{
132+
var response = $"{FutariClient.LOBBY_BASE}/online".Get();
133+
var onlineInfo = JsonUtility.FromJson<OnlineUserInfo>(response);
134+
onlineUserCount = onlineInfo.totalUsers;
135+
}
136+
catch (Exception ex)
137+
{
138+
Log.Error($"Failed to fetch online user count: {ex.Message}");
139+
onlineUserCount = 0;
140+
}
141+
}
142+
119143
//Online Display
120144
[HarmonyPostfix]
121145
[HarmonyPatch(typeof(CommonMonitor), "ViewUpdate")]
@@ -138,8 +162,22 @@ public static void CommonMonitorViewUpdate(CommonMonitor __instance,TextMeshProU
138162
____buildVersionText.color = Color.yellow;
139163
break;
140164
case 2:
141-
____buildVersionText.color = Color.cyan;
142-
____buildVersionText.text = PartyMan == null ? $"WorldLink Waiting" : $"WorldLink Recruiting: {PartyMan.GetRecruitList().Count}";
165+
var recruitCount = PartyMan == null ? 0 : PartyMan.GetRecruitList().Count;
166+
if (onlineUserCount > 0)
167+
{
168+
____buildVersionText.text = $"WorldLink Recruiting: {recruitCount}/{onlineUserCount}";
169+
____buildVersionText.color = recruitCount > 0 ? Color.green : Color.cyan;
170+
}
171+
else if (onlineUserCount == 0)
172+
{
173+
____buildVersionText.text = $"No players online";
174+
____buildVersionText.color = Color.gray;
175+
}
176+
else
177+
{
178+
____buildVersionText.text = $"WorldLink Recruiting: {recruitCount}";
179+
____buildVersionText.color = Color.cyan;
180+
}
143181
break;
144182
}
145183
}

mod/WorldLink/FutariTypes.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ public class ServerInfo
4141
public int relayPort;
4242
}
4343

44+
public class OnlineUserInfo
45+
{
46+
public int totalUsers;
47+
public int activeRecruits;
48+
}
49+
4450
public struct FutariMsg
4551
{
4652
public FutariCmd FutariCmd;

src/main/kotlin/FutariLobby.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,23 @@ fun Application.configureRouting() = routing {
8787
).ok()
8888
}
8989

90+
get("/online") {
91+
val time = millis()
92+
// Clean up expired recruits
93+
recruits.filterValues { time - it.Time > MAX_TTL }.keys.forEach { recruits.remove(it) }
94+
95+
// Count actual connected clients from FutariRelay
96+
val totalUsers = clients.size
97+
98+
// Count active recruits (unique users)
99+
val activeRecruits = recruits.size
100+
101+
OnlineUserInfo(
102+
totalUsers = totalUsers,
103+
activeRecruits = activeRecruits
104+
).ok()
105+
}
106+
90107
get("/debug") {
91108
mapOf(
92109
"serverHost" to call.request.local.serverHost,

src/main/kotlin/FutariTypes.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,9 @@ data class RelayServerInfo(
9696
val port: Int = 20101,
9797
val official: Bool = true
9898
)
99+
100+
@Serializable
101+
data class OnlineUserInfo(
102+
val totalUsers: Int,
103+
val activeRecruits: Int
104+
)

0 commit comments

Comments
 (0)