2025-09-23 21:46:12 +08:00
|
|
|
|
package uno.mloluyu.network;
|
|
|
|
|
|
|
|
|
|
|
|
import com.badlogic.gdx.Gdx;
|
|
|
|
|
|
|
2025-09-24 20:07:32 +08:00
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
|
|
2025-09-23 21:46:12 +08:00
|
|
|
|
public class NetworkManager {
|
|
|
|
|
|
private static NetworkManager instance;
|
|
|
|
|
|
private ConnectServer server;
|
|
|
|
|
|
private ConnectClient client;
|
|
|
|
|
|
private boolean isHost = false;
|
2025-09-24 20:07:32 +08:00
|
|
|
|
private String localPlayerId;
|
|
|
|
|
|
private String localCharacter;
|
|
|
|
|
|
private final Map<String, float[]> playerPositions = new HashMap<>();
|
|
|
|
|
|
private final Map<String, String> playerCharacters = new HashMap<>();
|
2025-09-25 21:24:10 +08:00
|
|
|
|
// 存储远程玩家的攻击类型(attackType)
|
|
|
|
|
|
private final Map<String, String> playerAttacks = new HashMap<>();
|
2025-09-25 22:21:26 +08:00
|
|
|
|
// 攻击方向:playerId -> "R" 或 "L"
|
|
|
|
|
|
private final Map<String, String> playerAttackDirs = new HashMap<>();
|
2025-09-25 22:03:19 +08:00
|
|
|
|
// 伤害事件:targetId -> 累积伤害(本帧内可能多次)
|
|
|
|
|
|
private final Map<String, Integer> damageEvents = new HashMap<>();
|
2025-09-25 22:21:26 +08:00
|
|
|
|
// 伤害方向:targetId -> dirSign (-1 / 0 / 1)
|
|
|
|
|
|
private final Map<String, Integer> damageDirs = new HashMap<>();
|
2025-09-25 22:03:19 +08:00
|
|
|
|
private final Map<String, float[]> respawnEvents = new HashMap<>();
|
2025-09-23 21:46:12 +08:00
|
|
|
|
|
|
|
|
|
|
public static NetworkManager getInstance() {
|
|
|
|
|
|
if (instance == null) {
|
|
|
|
|
|
instance = new NetworkManager();
|
|
|
|
|
|
}
|
|
|
|
|
|
return instance;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-24 20:07:32 +08:00
|
|
|
|
public void setLocalPlayerId(String id) {
|
|
|
|
|
|
this.localPlayerId = id;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public String getLocalPlayerId() {
|
|
|
|
|
|
return localPlayerId;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-25 21:24:10 +08:00
|
|
|
|
public void createRoom() {// 创建房间
|
2025-09-23 21:46:12 +08:00
|
|
|
|
isHost = true;
|
|
|
|
|
|
server = new ConnectServer(11455);
|
|
|
|
|
|
new Thread(server).start();
|
2025-09-24 20:07:32 +08:00
|
|
|
|
Gdx.app.log("Network", "房主模式:服务器已启动");
|
2025-09-23 21:46:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-25 21:24:10 +08:00
|
|
|
|
public void joinRoom(String ip) {// 加入房间
|
2025-09-23 21:46:12 +08:00
|
|
|
|
isHost = false;
|
|
|
|
|
|
client = new ConnectClient(ip, 11455);
|
2025-09-24 20:07:32 +08:00
|
|
|
|
Gdx.app.log("Network", "客户端模式:连接到房主 " + ip);
|
2025-09-23 21:46:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-25 21:24:10 +08:00
|
|
|
|
public void sendPosition(float x, float y) {// 发送位置消息
|
2025-09-24 20:07:32 +08:00
|
|
|
|
String msg = "POS:" + localPlayerId + "," + x + "," + y;
|
2025-09-23 21:46:12 +08:00
|
|
|
|
if (isHost && server != null) {
|
2025-09-24 20:07:32 +08:00
|
|
|
|
server.broadcastToOthers(null, msg);
|
2025-09-25 21:24:10 +08:00
|
|
|
|
receiveMessage(msg);
|
2025-09-23 21:46:12 +08:00
|
|
|
|
} else if (client != null) {
|
2025-09-24 20:07:32 +08:00
|
|
|
|
client.sendMessage(msg);
|
2025-09-23 21:46:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-25 21:24:10 +08:00
|
|
|
|
public void sendCharacterSelection(String character) {// 发送角色选择消息
|
2025-09-24 20:07:32 +08:00
|
|
|
|
this.localCharacter = character;
|
|
|
|
|
|
String msg = "SELECT:" + localPlayerId + "," + character;
|
|
|
|
|
|
if (isHost && server != null) {
|
|
|
|
|
|
server.broadcastToOthers(null, msg);
|
|
|
|
|
|
receiveMessage(msg);
|
|
|
|
|
|
} else if (client != null) {
|
|
|
|
|
|
client.sendMessage(msg);
|
|
|
|
|
|
}
|
2025-09-23 21:46:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-25 21:24:10 +08:00
|
|
|
|
public void receiveMessage(String message) {// 解析消息
|
2025-09-24 20:07:32 +08:00
|
|
|
|
|
|
|
|
|
|
if (message.startsWith("POS:")) {
|
|
|
|
|
|
String[] parts = message.substring(4).split(",");
|
|
|
|
|
|
if (parts.length == 3) {
|
|
|
|
|
|
String playerId = parts[0];
|
|
|
|
|
|
try {
|
|
|
|
|
|
float x = Float.parseFloat(parts[1]);
|
|
|
|
|
|
float y = Float.parseFloat(parts[2]);
|
2025-09-25 21:24:10 +08:00
|
|
|
|
playerPositions.put(playerId, new float[] { x, y });
|
2025-09-24 20:07:32 +08:00
|
|
|
|
} catch (NumberFormatException e) {
|
|
|
|
|
|
Gdx.app.error("Network", "位置解析失败: " + message);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Gdx.app.error("Network", "位置消息格式错误: " + message);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (message.startsWith("SELECT:")) {
|
|
|
|
|
|
String[] parts = message.substring(7).split(",");
|
|
|
|
|
|
if (parts.length == 2) {
|
|
|
|
|
|
String playerId = parts[0];
|
|
|
|
|
|
String character = parts[1];
|
|
|
|
|
|
playerCharacters.put(playerId, character);
|
|
|
|
|
|
Gdx.app.log("Network", "角色选择: " + playerId + " -> " + character);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Gdx.app.error("Network", "角色选择消息格式错误: " + message);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (message.equals("READY")) {
|
|
|
|
|
|
Gdx.app.log("Network", "收到准备信号");
|
2025-09-25 21:24:10 +08:00
|
|
|
|
} else if (message.startsWith("ATTACK:")) {
|
|
|
|
|
|
String[] parts = message.substring(7).split(",");
|
2025-09-25 22:21:26 +08:00
|
|
|
|
// ATTACK:playerId,attackType,dir
|
|
|
|
|
|
if (parts.length >= 2) {
|
2025-09-25 21:24:10 +08:00
|
|
|
|
String playerId = parts[0];
|
|
|
|
|
|
String attackType = parts[1];
|
2025-09-25 22:21:26 +08:00
|
|
|
|
String dir = parts.length >= 3 ? parts[2] : "R"; // 兼容旧版本无方向
|
2025-09-25 21:24:10 +08:00
|
|
|
|
playerAttacks.put(playerId, attackType);
|
2025-09-25 22:21:26 +08:00
|
|
|
|
playerAttackDirs.put(playerId, dir);
|
|
|
|
|
|
Gdx.app.log("Network", "攻击同步: " + playerId + " -> " + attackType + " dir=" + dir);
|
2025-09-25 21:24:10 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
Gdx.app.error("Network", "攻击消息格式错误: " + message);
|
|
|
|
|
|
}
|
2025-09-25 22:03:19 +08:00
|
|
|
|
} else if (message.startsWith("DAMAGE:")) {
|
|
|
|
|
|
// DAMAGE:targetId,amount,(dir)
|
|
|
|
|
|
String[] parts = message.substring(7).split(",");
|
|
|
|
|
|
if (parts.length >= 2) {
|
|
|
|
|
|
String targetId = parts[0];
|
|
|
|
|
|
try {
|
|
|
|
|
|
int amount = Integer.parseInt(parts[1]);
|
|
|
|
|
|
damageEvents.merge(targetId, amount, Integer::sum);
|
2025-09-25 22:21:26 +08:00
|
|
|
|
int dir = 0;
|
|
|
|
|
|
if (parts.length >= 3) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
dir = Integer.parseInt(parts[2]);
|
|
|
|
|
|
} catch (NumberFormatException ignore) {
|
|
|
|
|
|
}
|
|
|
|
|
|
damageDirs.put(targetId, dir);
|
|
|
|
|
|
}
|
|
|
|
|
|
Gdx.app.log("Network", "收到伤害: " + targetId + " -" + amount + (dir != 0 ? (" dir=" + dir) : ""));
|
2025-09-25 22:03:19 +08:00
|
|
|
|
} catch (NumberFormatException e) {
|
|
|
|
|
|
Gdx.app.error("Network", "伤害数值解析失败: " + message);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Gdx.app.error("Network", "伤害消息格式错误: " + message);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (message.startsWith("RESPAWN:")) {
|
|
|
|
|
|
// RESPAWN:playerId,x,y
|
|
|
|
|
|
String[] parts = message.substring(8).split(",");
|
|
|
|
|
|
if (parts.length == 3) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
String pid = parts[0];
|
|
|
|
|
|
float x = Float.parseFloat(parts[1]);
|
|
|
|
|
|
float y = Float.parseFloat(parts[2]);
|
|
|
|
|
|
respawnEvents.put(pid, new float[] { x, y });
|
|
|
|
|
|
Gdx.app.log("Network", "收到重生: " + pid + " -> (" + x + "," + y + ")");
|
|
|
|
|
|
} catch (NumberFormatException e) {
|
|
|
|
|
|
Gdx.app.error("Network", "重生坐标解析失败: " + message);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Gdx.app.error("Network", "重生消息格式错误: " + message);
|
|
|
|
|
|
}
|
2025-09-24 20:07:32 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
Gdx.app.log("Network", "未知消息类型: " + message);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public Map<String, float[]> getPlayerPositions() {
|
2025-09-23 21:46:12 +08:00
|
|
|
|
return playerPositions;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-25 21:24:10 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 获取远程玩家的未处理攻击类型映射
|
|
|
|
|
|
*/
|
|
|
|
|
|
public Map<String, String> getPlayerAttacks() {
|
|
|
|
|
|
return playerAttacks;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-25 22:21:26 +08:00
|
|
|
|
public Map<String, String> getPlayerAttackDirs() {
|
|
|
|
|
|
return playerAttackDirs;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-25 21:24:10 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 发送攻击消息给其他玩家
|
|
|
|
|
|
*/
|
2025-09-25 22:21:26 +08:00
|
|
|
|
public void sendAttack(String attackType, String dir) {
|
2025-09-25 21:24:10 +08:00
|
|
|
|
if (localPlayerId == null)
|
|
|
|
|
|
return;
|
2025-09-25 22:21:26 +08:00
|
|
|
|
String msg = "ATTACK:" + localPlayerId + "," + attackType + "," + dir; // 新格式包含方向
|
2025-09-25 21:24:10 +08:00
|
|
|
|
Gdx.app.log("Network", "发送攻击消息: " + msg);
|
|
|
|
|
|
if (isHost && server != null) {
|
|
|
|
|
|
server.broadcastToOthers(null, msg);
|
|
|
|
|
|
receiveMessage(msg);
|
|
|
|
|
|
} else if (client != null) {
|
|
|
|
|
|
client.sendMessage(msg);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-25 22:21:26 +08:00
|
|
|
|
// 兼容旧代码(若仍有地方调用)默认使用 R
|
|
|
|
|
|
public void sendAttack(String attackType) {
|
|
|
|
|
|
sendAttack(attackType, "R");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-24 20:07:32 +08:00
|
|
|
|
public Map<String, String> getPlayerCharacters() {
|
|
|
|
|
|
return playerCharacters;
|
2025-09-23 21:46:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-25 22:03:19 +08:00
|
|
|
|
public Map<String, Integer> getDamageEvents() {
|
|
|
|
|
|
return damageEvents;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-25 22:21:26 +08:00
|
|
|
|
public Map<String, Integer> getDamageDirs() {
|
|
|
|
|
|
return damageDirs;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void sendDamage(String targetId, int amount, int dirSign) {
|
|
|
|
|
|
String msg = "DAMAGE:" + targetId + "," + amount + "," + dirSign; // 含方向
|
2025-09-25 22:03:19 +08:00
|
|
|
|
if (isHost && server != null) {
|
|
|
|
|
|
server.broadcastToOthers(null, msg);
|
|
|
|
|
|
receiveMessage(msg); // 本地也应用
|
|
|
|
|
|
} else if (client != null) {
|
|
|
|
|
|
client.sendMessage(msg);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-25 22:21:26 +08:00
|
|
|
|
// 兼容旧调用,不带方向(使用0表示沿用被击者朝向逻辑)
|
|
|
|
|
|
public void sendDamage(String targetId, int amount) {
|
|
|
|
|
|
sendDamage(targetId, amount, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-25 22:03:19 +08:00
|
|
|
|
public void sendRespawn(String playerId, float x, float y) {
|
|
|
|
|
|
String msg = "RESPAWN:" + playerId + "," + x + "," + y;
|
|
|
|
|
|
if (isHost && server != null) {
|
|
|
|
|
|
server.broadcastToOthers(null, msg);
|
|
|
|
|
|
receiveMessage(msg);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public Map<String, float[]> getRespawnEvents() {
|
|
|
|
|
|
return respawnEvents;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-23 21:46:12 +08:00
|
|
|
|
public boolean isHost() {
|
|
|
|
|
|
return isHost;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public boolean isConnected() {
|
|
|
|
|
|
return server != null || client != null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-24 20:07:32 +08:00
|
|
|
|
public void disconnect() {
|
|
|
|
|
|
if (server != null) {
|
|
|
|
|
|
server.dispose();
|
|
|
|
|
|
server = null;
|
2025-09-23 21:46:12 +08:00
|
|
|
|
}
|
2025-09-24 20:07:32 +08:00
|
|
|
|
if (client != null) {
|
|
|
|
|
|
client.disconnect();
|
|
|
|
|
|
client = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
playerPositions.clear();
|
|
|
|
|
|
playerCharacters.clear();
|
2025-09-25 22:21:26 +08:00
|
|
|
|
playerAttacks.clear();
|
|
|
|
|
|
playerAttackDirs.clear();
|
|
|
|
|
|
damageEvents.clear();
|
|
|
|
|
|
damageDirs.clear();
|
2025-09-24 20:07:32 +08:00
|
|
|
|
Gdx.app.log("Network", "已断开连接");
|
2025-09-23 21:46:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|