implement basic scene and cocoon ( not drop )

This commit is contained in:
Somebody
2024-03-10 16:55:50 +08:00
parent d2c62d2659
commit 505c3caa28
82 changed files with 2553 additions and 145 deletions

View File

@@ -13,6 +13,7 @@
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="8.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.2" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Spectre.Console" Version="0.48.0" />
<PackageReference Include="SQLitePCLRaw.core" Version="2.1.8" />
<PackageReference Include="SQLitePCLRaw.provider.e_sqlite3" Version="2.1.8" />
<PackageReference Include="SqlSugarCore" Version="5.1.4.143" />

View File

@@ -14,8 +14,8 @@ namespace EggLink.DanhengServer.Data.Config
public bool Loaded = false;
public Dictionary<int, GroupInfo> Groups = [];
private Dictionary<int, PropInfo> CachedTeleports = [];
private List<PropInfo> UnlockedCheckpoints = []; // DEBUG
public Dictionary<int, PropInfo> CachedTeleports = [];
public List<PropInfo> UnlockedCheckpoints = [];
public AnchorInfo? GetAnchorInfo(int groupId, int anchorId)
{
@@ -47,7 +47,7 @@ namespace EggLink.DanhengServer.Data.Config
UnlockedCheckpoints.Add(prop);
// Force prop to be in the unlocked state
prop.State = PropState.CheckPointEnable;
prop.State = PropStateEnum.CheckPointEnable;
}
else if (!string.IsNullOrEmpty(prop.InitLevelGraph))
{

View File

@@ -7,7 +7,7 @@ using static System.Runtime.InteropServices.JavaScript.JSType;
namespace EggLink.DanhengServer.Data.Config
{
public class MonsterInfo : GroupInfo
public class MonsterInfo : PositionInfo
{
public int NPCMonsterID { get; set; }
public int EventID { get; set; }

View File

@@ -1,4 +1,5 @@
using EggLink.DanhengServer.Data.Excel;
using EggLink.DanhengServer.Util;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -16,5 +17,25 @@ namespace EggLink.DanhengServer.Data.Config
public bool IsDelete { get; set; }
public string Name { get; set; } = "";
public float RotY { get; set; }
public Position ToPositionProto()
{
return new()
{
X = (int)(PosX * 1000f),
Y = (int)(PosY * 1000f),
Z = (int)(PosZ * 1000f),
};
}
public Position ToRotationProto()
{
return new()
{
Y = (int)(RotY * 1000f),
X = 0,
Z = 0,
};
}
}
}

View File

@@ -22,7 +22,7 @@ namespace EggLink.DanhengServer.Data.Config
public string? InitLevelGraph { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public PropState State { get; set; } = PropState.Closed;
public PropStateEnum State { get; set; } = PropStateEnum.Closed;
}
public class PropValueSource

View File

@@ -0,0 +1,27 @@
using EggLink.DanhengServer.Util;
namespace EggLink.DanhengServer.Data.Excel
{
[ResourceEntity("CocoonConfig.json")]
public class CocoonConfigExcel : ExcelResource
{
public int ID { get; set; }
public int MappingInfoID { get; set; }
public int WorldLevel { get; set; }
public int PropID { get; set; }
public int StaminaCost { get; set; }
public int MaxWave { get; set; }
public List<int> StageIDList { get; set; } = [];
public List<int> DropList { get; set; } = [];
public override int GetId()
{
return (ID * 100) + WorldLevel;
}
public override void Loaded()
{
GameData.CocoonConfigData.Add(GetId(), this);
}
}
}

View File

@@ -0,0 +1,28 @@
using EggLink.DanhengServer.Enums;
using Newtonsoft.Json.Converters;
using System.Text.Json.Serialization;
namespace EggLink.DanhengServer.Data.Excel
{
[ResourceEntity("InteractConfig.json")]
public class InteractConfigExcel : ExcelResource
{
public int InteractID { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public PropStateEnum SrcState { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public PropStateEnum TargetState { get; set; } = PropStateEnum.Closed;
public override int GetId()
{
return InteractID;
}
public override void Loaded()
{
GameData.InteractConfigData.Add(InteractID, this);
}
}
}

View File

@@ -0,0 +1,53 @@
using EggLink.DanhengServer.Enums;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Data.Excel
{
[ResourceEntity("MazeProp.json")]
public class MazePropExcel : ExcelResource
{
public int ID { get; set; }
public HashName PropName { get; set; } = new();
public string JsonPath { get; set; } = "";
[JsonConverter(typeof(StringEnumConverter))]
public PropTypeEnum PropType { get; set; }
[JsonProperty(ItemConverterType = typeof(StringEnumConverter))]
public List<PropStateEnum> PropStateList { get; set; } = [];
public bool IsHpRecover = false;
public bool IsMpRecover = false;
public bool IsDoor = false;
public override int GetId()
{
return ID;
}
public override void Loaded()
{
if (JsonPath != "")
{
if (JsonPath.Contains("MPBox") || JsonPath.Contains("MPRecover"))
{
IsMpRecover = true;
} else if (JsonPath.Contains("HPBox") || JsonPath.Contains("HPRecover"))
{
IsHpRecover = true;
} else if (JsonPath.Contains("_Door_"))
{
IsDoor = true;
}
}
GameData.MazePropData.Add(ID, this);
}
}
}

View File

@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Data.Excel
{
[ResourceEntity("NPCData.json")]
public class NPCDataExcel : ExcelResource
{
public int ID { get; set; }
public override int GetId()
{
return ID;
}
public override void Loaded()
{
GameData.NpcDataData.Add(ID, this);
}
}
}

View File

@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Data.Excel
{
[ResourceEntity("NPCMonsterData.json")]
public class NPCMonsterDataExcel : ExcelResource
{
public int ID { get; set; }
public HashName NPCName { get; set; } = new();
public override int GetId()
{
return ID;
}
public override void Loaded()
{
GameData.NpcMonsterDataData.Add(ID, this);
}
}
}

View File

@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Data.Excel
{
[ResourceEntity("QuestData.json")]
public class QuestDataExcel : ExcelResource
{
public int QuestID { get; set; }
public int QuestType { get; set; }
public HashName QuestTitle { get; set; } = new();
public override int GetId()
{
return QuestID;
}
public override void AfterAllDone()
{
GameData.QuestDataData.Add(QuestID, this);
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using EggLink.DanhengServer.Proto;
using System.Collections.Generic;
namespace EggLink.DanhengServer.Data.Excel
{
@@ -7,7 +8,7 @@ namespace EggLink.DanhengServer.Data.Excel
{
public int StageID { get; set; } = 0;
public HashName StageName { get; set; } = new HashName();
public List<StageMonsterList> MonsterList { get; set; } = new List<StageMonsterList>();
public List<StageMonsterList> MonsterList { get; set; } = [];
public override int GetId()
@@ -18,6 +19,39 @@ namespace EggLink.DanhengServer.Data.Excel
{
GameData.StageConfigData.Add(StageID, this);
}
public SceneMonsterWave ToProto()
{
var proto = new SceneMonsterWave()
{
WaveId = 1,
StageId = (uint)StageID,
};
foreach (var monsters in MonsterList)
{
proto.MonsterList.Add(new SceneMonster()
{
MonsterId = (uint)monsters.Monster0,
});
proto.MonsterList.Add(new SceneMonster()
{
MonsterId = (uint)monsters.Monster1,
});
proto.MonsterList.Add(new SceneMonster()
{
MonsterId = (uint)monsters.Monster2,
});
proto.MonsterList.Add(new SceneMonster()
{
MonsterId = (uint)monsters.Monster3,
});
proto.MonsterList.Add(new SceneMonster()
{
MonsterId = (uint)monsters.Monster4,
});
}
return proto;
}
}
public class StageMonsterList

View File

@@ -7,9 +7,15 @@ namespace EggLink.DanhengServer.Data
public static class GameData
{
public static Dictionary<int, AvatarConfigExcel> AvatarConfigData { get; private set; } = [];
public static Dictionary<int, CocoonConfigExcel> CocoonConfigData { get; private set; } = [];
public static Dictionary<int, StageConfigExcel> StageConfigData { get; private set; } = [];
public static Dictionary<int, MapEntranceExcel> MapEntranceData { get; private set; } = [];
public static Dictionary<int, MazePlaneExcel> MazePlaneData { get; private set; } = [];
public static Dictionary<int, MazePropExcel> MazePropData { get; private set; } = [];
public static Dictionary<int, InteractConfigExcel> InteractConfigData { get; private set; } = [];
public static Dictionary<int, NPCMonsterDataExcel> NpcMonsterDataData { get; private set; } = [];
public static Dictionary<int, NPCDataExcel> NpcDataData { get; private set; } = [];
public static Dictionary<int, QuestDataExcel> QuestDataData { get; private set; } = [];
public static Dictionary<string, FloorInfo> FloorInfoData { get; private set; } = [];

View File

@@ -68,16 +68,18 @@ namespace EggLink.DanhengServer.Data
{
var id = int.Parse(item.Key);
var obj = item.Value;
var instance = JsonConvert.DeserializeObject(obj.ToString(), cls);
if (instance == null)
var instance = JsonConvert.DeserializeObject(obj!.ToString(), cls);
if (((ExcelResource?)instance)?.GetId() == 0 || ((ExcelResource?)instance) == null)
{
// Deserialize as JObject to handle nested dictionaries
var nestedObject = JsonConvert.DeserializeObject<JObject>(obj.ToString());
// Process only if it's a top-level dictionary, not nested
if (nestedObject?.Count > 0 && nestedObject?.First?.First?.Type != JTokenType.Object)
foreach (var nestedItem in nestedObject ?? [])
{
((ExcelResource?)instance)?.Loaded();
var nestedInstance = JsonConvert.DeserializeObject(nestedItem.Value!.ToString(), cls);
((ExcelResource?)nestedInstance)?.Loaded();
count++;
}
}
else

View File

@@ -1,5 +1,10 @@
using EggLink.DanhengServer.Data.Excel;
using EggLink.DanhengServer.Data;
using EggLink.DanhengServer.Data.Excel;
using EggLink.DanhengServer.Database.Inventory;
using EggLink.DanhengServer.Database.Player;
using EggLink.DanhengServer.Proto;
using EggLink.DanhengServer.Util;
using Newtonsoft.Json;
using SqlSugar;
namespace EggLink.DanhengServer.Database.Avatar
@@ -7,7 +12,7 @@ namespace EggLink.DanhengServer.Database.Avatar
[SugarTable("Avatar")]
public class AvatarData : BaseDatabaseData
{
[SugarColumn(IsNullable = true)]
[SugarColumn(IsNullable = true, IsJson = true)]
public List<AvatarInfo>? Avatars { get; set; }
public List<int> AssistAvatars { get; set; } = [];
@@ -22,16 +27,26 @@ namespace EggLink.DanhengServer.Database.Avatar
public int Promotion { get; set; }
public int Rewards { get; set; }
public long Timestamp { get; set; }
public int CurrentHp { get; set; }
public int CurrentSp { get; set; }
public int ExtraLineupHp { get; set; }
public int ExtraLineupSp { get; set; }
public int CurrentHp { get; set; } = 10000;
public int CurrentSp { get; set; } = 10000;
public int ExtraLineupHp { get; set; } = 10000;
public int ExtraLineupSp { get; set; } = 10000;
public int Rank { get; set; }
public Dictionary<int, int> SkillTree { get; set; } = [];
public int EquipId { get; set; } = 0;
public Dictionary<int, int> Relic { get; set; } = [];
[JsonIgnore()]
public AvatarConfigExcel Excel;
[JsonIgnore()]
public int EntityId;
[JsonIgnore()]
public PlayerData? PlayerData;
public AvatarInfo()
{
// only for db
}
public AvatarInfo(AvatarConfigExcel excel)
{
@@ -48,11 +63,45 @@ namespace EggLink.DanhengServer.Database.Avatar
return (Rewards & (1 << promotion)) != 0;
}
public int GetCurHp(bool isExtraLineup)
{
return isExtraLineup ? ExtraLineupHp : CurrentHp;
}
public int GetCurSp(bool isExtraLineup)
{
return isExtraLineup ? ExtraLineupSp : CurrentSp;
}
public void SetCurHp(int value, bool isExtraLineup)
{
if (isExtraLineup)
{
ExtraLineupHp = value;
}
else
{
CurrentHp = value;
}
}
public void SetCurSp(int value, bool isExtraLineup)
{
if (isExtraLineup)
{
ExtraLineupSp = value;
}
else
{
CurrentSp = value;
}
}
public Proto.Avatar ToProto()
{
var proto = new Proto.Avatar()
{
BaseAvatarId = (uint)AvatarId,
BaseAvatarId = (uint)(AvatarId == 1005 ? 8001 : AvatarId), // 1005 will trigger npe
Level = (uint)Level,
Exp = (uint)Exp,
Promotion = (uint)Promotion,
@@ -93,5 +142,110 @@ namespace EggLink.DanhengServer.Database.Avatar
return proto;
}
public SceneEntityInfo ToSceneEntityInfo(AvatarType avatarType = AvatarType.AvatarFormalType)
{
return new()
{
EntityId = (uint)EntityId,
Motion = new()
{
Pos = PlayerData?.Pos?.ToProto() ?? new(),
Rot = PlayerData?.Rot?.ToProto() ?? new(),
},
Actor = new()
{
BaseAvatarId = (uint)(AvatarId == 1005? 8001 : AvatarId),
AvatarType = avatarType
}
};
}
public LineupAvatar ToLineupInfo(int slot, Lineup.LineupInfo info, AvatarType avatarType = AvatarType.AvatarFormalType)
{
return new()
{
Id = (uint)(AvatarId == 1005 ? 8001 : AvatarId),
Slot = (uint)slot,
AvatarType = avatarType,
Hp = info.IsExtraLineup() ? (uint)ExtraLineupHp : (uint)CurrentHp,
SpBar = new()
{
CurSp = info.IsExtraLineup() ? (uint)ExtraLineupSp : (uint)CurrentSp,
MaxSp = 10000,
},
};
}
public BattleAvatar ToBattleProto(Lineup.LineupInfo lineup, InventoryData inventory, AvatarType avatarType = AvatarType.AvatarFormalType)
{
var proto = new BattleAvatar()
{
Id = (uint)(AvatarId == 1005 ? 8001 : AvatarId),
AvatarType = avatarType,
Level = (uint)Level,
Promotion = (uint)Promotion,
Rank = (uint)Rank,
Index = (uint)lineup.GetSlot(AvatarId),
Hp = (uint)GetCurHp(lineup.LineupType != 0),
SpBar = new()
{
CurSp = (uint)GetCurSp(lineup.LineupType != 0),
MaxSp = 10000,
},
WorldLevel = (uint)(PlayerData?.WorldLevel ?? 0),
};
foreach (var skill in SkillTree)
{
proto.SkilltreeList.Add(new AvatarSkillTree()
{
PointId = (uint)skill.Key,
Level = (uint)skill.Value
});
}
foreach (var relic in Relic)
{
var item = inventory.RelicItems?.Find(item => item.UniqueId == relic.Value);
if (item != null)
{
var protoRelic = new BattleRelic()
{
Id = (uint)item.ItemId,
UniqueId = (uint)item.UniqueId,
Level = (uint)item.Level,
MainAffixId = (uint)item.MainAffix,
};
if (item.SubAffixes.Count >= 1)
{
foreach (var subAffix in item.SubAffixes)
{
protoRelic.SubAffixList.Add(subAffix.ToProto());
}
}
proto.RelicList.Add(protoRelic);
}
}
if (EquipId != 0)
{
var item = inventory.EquipmentItems?.Find(item => item.UniqueId == EquipId);
if (item != null)
{
proto.EquipmentList.Add(new BattleEquipment()
{
Id = (uint)item.ItemId,
Level = (uint)item.Level,
Promotion = (uint)item.Promotion,
Rank = (uint)item.Rank,
});
}
}
return proto;
}
}
}

View File

@@ -13,6 +13,7 @@ namespace EggLink.DanhengServer.Database
public ConfigContainer config = ConfigManager.Config;
public static SqlSugarScope? sqlSugarScope;
public static DatabaseHelper? Instance;
private static readonly object _lock = new();
public DatabaseHelper()
{
@@ -69,10 +70,14 @@ namespace EggLink.DanhengServer.Database
{
try
{
return sqlSugarScope?.Queryable<T>().Where(it => (it as BaseDatabaseData).Uid == uid).First();
} catch
lock (_lock)
{
return sqlSugarScope?.Queryable<T>().Where(it => (it as BaseDatabaseData).Uid == uid).First();
}
}
catch (Exception e)
{
logger.Error("Unsupported type");
logger.Error("Unsupported type", e);
return null;
}
}
@@ -81,27 +86,39 @@ namespace EggLink.DanhengServer.Database
{
try
{
return sqlSugarScope?.Queryable<T>().ToList();
} catch
lock (_lock)
{
return sqlSugarScope?.Queryable<T>().ToList();
}
} catch(Exception e)
{
logger.Error("Unsupported type");
logger.Error("Unsupported type", e);
return null;
}
}
public void SaveInstance<T>(T instance) where T : class, new()
{
sqlSugarScope?.Insertable(instance).ExecuteCommand();
lock (_lock)
{
sqlSugarScope?.Insertable(instance).ExecuteCommand();
}
}
public void UpdateInstance<T>(T instance) where T : class, new()
{
sqlSugarScope?.Updateable(instance).ExecuteCommand();
lock (_lock)
{
sqlSugarScope?.Updateable(instance).ExecuteCommand();
}
}
public void DeleteInstance<T>(T instance) where T : class, new()
{
sqlSugarScope?.Deleteable(instance).ExecuteCommand();
lock (_lock)
{
sqlSugarScope?.Deleteable(instance).ExecuteCommand();
}
}
}
}

View File

@@ -15,7 +15,6 @@ namespace EggLink.DanhengServer.Database.Inventory
public class ItemData
{
[SugarColumn(IsPrimaryKey = true)]
public int UniqueId { get; set; }
public int ItemId { get; set; }
public int Count { get; set; }
@@ -62,6 +61,7 @@ namespace EggLink.DanhengServer.Database.Inventory
}
return relic;
}
public Equipment ToEquipmentProto()
{
return new()

View File

@@ -1,4 +1,6 @@
using SqlSugar;
using EggLink.DanhengServer.Database.Avatar;
using Newtonsoft.Json;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -11,18 +13,107 @@ namespace EggLink.DanhengServer.Database.Lineup
public class LineupData : BaseDatabaseData
{
public int CurLineup { get; set; } // index of current lineup
public string? Lineups { get; set; } // 9 * 4
[SugarColumn(IsJson = true)]
public Dictionary<int, LineupInfo> Lineups { get; set; } = []; // 9 * 4
public int Mp { get; set; } = 5;
}
public class LineupInfo
{
public string? Name { get; set; }
public int LineupType { get; set; }
public List<int>? BaseAvatars { get; set; }
public int LeaderAvatarId { get; set; }
public List<AvatarInfo>? BaseAvatars { get; set; }
[JsonIgnore()]
public LineupData? LineupData { get; set; }
[JsonIgnore()]
public AvatarData? AvatarData { get; set; }
public int GetSlot(int avatarId)
{
return BaseAvatars?.FindIndex(item => item.BaseAvatarId == avatarId) ?? -1;
}
public bool Heal(int count, bool allowRevive)
{
bool result = false;
if (BaseAvatars != null && AvatarData != null)
{
foreach (var avatar in BaseAvatars)
{
var avatarInfo = AvatarData?.Avatars?.Find(item => item.AvatarId == avatar.BaseAvatarId);
if (avatarInfo != null)
{
if (avatarInfo.CurrentHp <= 0 && !allowRevive)
{
continue;
}
if (avatarInfo.CurrentHp >= 10000)
{
continue;
}
avatarInfo.CurrentHp = Math.Min(avatarInfo.GetCurHp(LineupType != 0) + count, 10000);
result = true;
}
}
DatabaseHelper.Instance?.UpdateInstance(AvatarData!);
}
return result;
}
public bool IsExtraLineup()
{
return LineupType != 0;
}
public Proto.LineupInfo ToProto()
{
Proto.LineupInfo info = new()
{
Name = Name,
MaxMp = 5,
Mp = (uint)(LineupData?.Mp ?? 0),
ExtraLineupType = Proto.ExtraLineupType.LineupNone,
Index = (uint)(LineupData?.Lineups?.Values.ToList().IndexOf(this) ?? 0),
};
if (BaseAvatars?.Find(item => item.BaseAvatarId == LeaderAvatarId) != null)
{
info.LeaderSlot = (uint)BaseAvatars.IndexOf(BaseAvatars.Find(item => item.BaseAvatarId == LeaderAvatarId)!);
} else
{
info.LeaderSlot = 0;
}
if (BaseAvatars != null)
{
foreach (var avatar in BaseAvatars)
{
if (avatar.AssistUid != 0)
{
var assistPlayer = DatabaseHelper.Instance?.GetInstance<AvatarData>(avatar.AssistUid);
if (assistPlayer != null)
{
info.AvatarList.Add(assistPlayer?.Avatars?.Find(item => item.AvatarId == avatar.BaseAvatarId)?.ToLineupInfo(BaseAvatars.IndexOf(avatar), this, Proto.AvatarType.AvatarAssistType));
}
} else if (avatar.SpecialAvatarId != 0)
{
} else
{
info.AvatarList.Add(AvatarData?.Avatars?.Find(item => item.AvatarId == avatar.BaseAvatarId)?.ToLineupInfo(BaseAvatars.IndexOf(avatar), this));
}
}
}
return info;
}
}
public class LineupInfoJson
public class AvatarInfo
{
public Dictionary<int, LineupInfo>? Lineups { get; set; } = []; // 9 * 4
public int BaseAvatarId { get; set; }
public int AssistUid { get; set; }
public int SpecialAvatarId { get; set; }
}
}

View File

@@ -29,9 +29,9 @@ namespace EggLink.DanhengServer.Database.Player
public double StaminaReserve { get; set; }
public long NextStaminaRecover { get; set; }
[SugarColumn(IsNullable = true)]
[SugarColumn(IsNullable = true, IsJson = true)]
public Position? Pos { get; set; }
[SugarColumn(IsNullable = true)]
[SugarColumn(IsNullable = true, IsJson = true)]
public Position? Rot { get; set; }
[SugarColumn(IsNullable = true)]
public int PlaneId { get; set; }

View File

@@ -1,12 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Enums
namespace EggLink.DanhengServer.Enums
{
public enum PropState
public enum PropStateEnum
{
Closed = 0,
Open = 1,
@@ -44,5 +38,4 @@ namespace EggLink.DanhengServer.Enums
CustomState08 = 108,
CustomState09 = 109
}
}

View File

@@ -0,0 +1,35 @@
namespace EggLink.DanhengServer.Enums
{
public enum PropTypeEnum
{
PROP_NONE = 0,
PROP_ORDINARY = 1,
PROP_SUMMON = 2,
PROP_DESTRUCT = 3,
PROP_SPRING = 4,
PROP_PLATFORM = 5,
PROP_TREASURE_CHEST = 6,
PROP_MATERIAL_ZONE = 7,
PROP_COCOON = 8,
PROP_MAPPINGINFO = 9,
PROP_PUZZLES = 10,
PROP_ELEVATOR = 11,
PROP_NO_REWARD_DESTRUCT = 12,
PROP_LIGHT = 13,
PROP_ROGUE_DOOR = 14,
PROP_ROGUE_OBJECT = 15,
PROP_ROGUE_CHEST = 16,
PROP_TELEVISION = 17,
PROP_RELIC = 18,
PROP_ELEMENT = 19,
PROP_ROGUE_HIDDEN_DOOR = 20,
PROP_PERSPECTIVE_WALL = 21,
PROP_MAZE_PUZZLE = 22,
PROP_MAZE_DECAL = 23,
PROP_ROGUE_REWARD_OBJECT = 24,
PROP_MAP_ROTATION_CHARGER = 25,
PROP_MAP_ROTATION_VOLUME = 26,
PROP_MAP_ROTATION_SWITCHER = 27,
PROP_BOXMAN_BINDED = 28
}
}

View File

@@ -1,4 +1,6 @@
using System.Buffers.Binary;
using EggLink.DanhengServer.Proto;
using Newtonsoft.Json;
using System.Buffers.Binary;
namespace EggLink.DanhengServer.Util;
@@ -67,4 +69,34 @@ public static class Extensions
BinaryPrimitives.WriteUInt64BigEndian(data, value);
bw.Write(data);
}
public static long GetNowTimeMillis(this DateTime dt)
{
return dt.Ticks / TimeSpan.TicksPerMillisecond;
}
public static Position ToPosition(this Vector vector)
{
return new Position
{
X = vector.X,
Y = vector.Y,
Z = vector.Z
};
}
public static T RandomElement<T> (this List<T> values)
{
var index = new Random().Next(values.Count);
return values[index];
}
public static string ToArrayString(this List<string> list)
{
return list.JoinFormat(", ", "");
}
public static string ToJsonString(this Dictionary<string, string> dic)
{
return JsonConvert.SerializeObject(dic);
}
}

View File

@@ -117,5 +117,22 @@ namespace EggLink.DanhengServer.Util
Y /= position.Y;
Z /= position.Z;
}
public Vector ToProto()
{
return new()
{
X = X,
Y = Y,
Z = Z
};
}
public long GetFast2dDist(Position pos)
{
long x = X - pos.X;
long z = Z - pos.Z;
return (x * x) + (z * z);
}
}
}

View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Command.Cmd
{
[CommandInfo("give", "Give item to player", "give <item> l<level> r<rank> p<promotion> x<amount>")]
public class CommandGive : ICommand
{
[CommandDefault]
public void GiveItem(CommandArg arg)
{
if (arg.Target == null)
{
arg.SendMsg("Target not found.");
return;
}
var player = arg.Target.Player;
if (player == null)
{
arg.SendMsg("Target not found.");
return;
}
}
}
}

View File

@@ -0,0 +1,73 @@
using EggLink.DanhengServer.Server;
using EggLink.DanhengServer.Util;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Command
{
public class CommandArg
{
public string Raw { get; }
public List<string> BasicArgs { get; } = [];
public Dictionary<string, string> CharacterArgs { get; } = [];
public Connection? Target { get; set; }
public ICommandSender Sender { get; }
public CommandArg(string raw, ICommandSender sender, Connection? con = null)
{
Raw = raw;
Sender = sender;
var args = raw.Split(' ');
foreach (var arg in args)
{
if (string.IsNullOrEmpty(arg))
{
continue;
}
var character = arg[0];
if (!int.TryParse(character.ToString(), out var _))
{
try
{
CharacterArgs.Add(arg[..1], arg[1..]);
} catch
{
BasicArgs.Add(arg);
}
}
else
{
BasicArgs.Add(arg);
}
}
if (con != null)
{
Target = con;
} else
{
CharacterArgs.TryGetValue("@", out var target);
if (target != null)
{
var connection = Listener.Connections.Values.ToList().Find(item => item.Player?.Uid.ToString() == target);
if (connection != null)
{
Target = connection;
}
}
}
}
public void SendMsg(string msg)
{
Sender.SendMsg(msg);
}
public override string ToString()
{
return $"BasicArg: {BasicArgs.ToArrayString()}. CharacterArg: {CharacterArgs.ToJsonString()}.";
}
}
}

View File

@@ -0,0 +1,34 @@
namespace EggLink.DanhengServer.Command
{
[AttributeUsage(AttributeTargets.Class)]
public class CommandInfo(string name, string description, string usage, string keyword = "") : Attribute
{
public CommandInfo(string name, string description, string usage, List<string> alias, string keyword = "") : this(name, description, usage, keyword)
{
Alias = alias ?? [];
}
public string Name { get; } = name;
public string Description { get; } = description;
public string Usage { get; } = usage;
public string Keyword { get; } = keyword;
public List<string> Alias { get; } = [];
}
[AttributeUsage(AttributeTargets.Method)]
public class CommandMethod(List<CommandCondition> conditions) : Attribute
{
public List<CommandCondition> Conditions { get; } = conditions;
}
[AttributeUsage(AttributeTargets.Method)]
public class CommandDefault : Attribute
{
}
public class CommandCondition
{
public int Index { get; set; }
public string ShouldBe { get; set; } = "";
}
}

View File

@@ -0,0 +1,122 @@
using EggLink.DanhengServer.Database;
using EggLink.DanhengServer.Program;
using EggLink.DanhengServer.Server;
using EggLink.DanhengServer.Util;
using Spectre.Console;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Command
{
public class CommandManager
{
public Dictionary<string, ICommand> Commands { get; } = [];
public Dictionary<string, CommandInfo> CommandInfo { get; } = [];
public Logger Logger { get; } = new Logger("CommandManager");
public Connection? Target { get; set; } = null;
public void RegisterCommand()
{
foreach (var type in Assembly.GetExecutingAssembly().GetTypes())
{
var attr = type.GetCustomAttribute<CommandInfo>();
if (attr != null)
{
var instance = Activator.CreateInstance(type);
if (instance is ICommand command)
{
Commands.Add(attr.Name, command);
CommandInfo.Add(attr.Name, attr);
}
}
}
Logger.Info($"Register {Commands.Count} commands.");
}
public void Start()
{
while (true)
{
string? input = AnsiConsole.Ask<string>("> ");
if (string.IsNullOrEmpty(input))
{
continue;
}
var cmd = input.Split(' ')[0];
if (cmd.StartsWith('@'))
{
var target = cmd[1..];
var con = Listener.Connections.Values.ToList().Find(item => item.Player?.Uid.ToString() == target);
if (con != null)
{
Target = con;
Logger.Info($"Online player {target}({con.Player!.Data.Name}) is found, the next command will target it by default.");
}
else
{
// offline or not exist
Logger.Warn($"Target {target} is offline or not found.");
}
continue;
}
if (Commands.TryGetValue(cmd, out var command))
{
var split = input.Split(' ').ToList();
split.RemoveAt(0);
var arg = new CommandArg(split.JoinFormat(" ", ""), new ConsoleCommandSender(Logger), Target);
// find the proper method with attribute CommandMethod
var isFound = false;
foreach (var method in command.GetType().GetMethods())
{
var attr = method.GetCustomAttribute<CommandMethod>();
if (attr != null)
{
var canRun = true;
foreach (var condition in attr.Conditions)
{
if (split.Count <= condition.Index)
{
canRun = false;
break;
}
if (!split[condition.Index].Equals(condition.ShouldBe))
{
canRun = false;
break;
}
}
if (canRun)
{
isFound = true;
method.Invoke(command, [arg]);
break;
}
}
}
if (!isFound)
{
// find the default method with attribute CommandDefault
foreach (var method in command.GetType().GetMethods())
{
var attr = method.GetCustomAttribute<CommandDefault>();
if (attr != null)
{
method.Invoke(command, [arg]);
break;
}
}
}
}
else
{
Logger.Info($"Command {cmd} not found.");
}
}
}
}
}

View File

@@ -0,0 +1,22 @@
using EggLink.DanhengServer.Util;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Command
{
public interface ICommandSender
{
public void SendMsg(string msg);
}
public class ConsoleCommandSender(Logger logger) : ICommandSender
{
public void SendMsg(string msg)
{
logger.Info(msg);
}
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Command
{
public interface ICommand
{
}
}

View File

@@ -8,7 +8,7 @@ namespace EggLink.DanhengServer.Game.Avatar
{
public class AvatarManager : BasePlayerManager
{
public AvatarData AvatarData { get; private set; }
public AvatarData? AvatarData { get; private set; }
public AvatarManager(PlayerInstance player) : base(player)
{
@@ -25,6 +25,11 @@ namespace EggLink.DanhengServer.Game.Avatar
else
{
AvatarData = avatars;
foreach (var avatar in AvatarData?.Avatars ?? [])
{
avatar.PlayerData = player.Data;
avatar.Excel = GameData.AvatarConfigData[avatar.AvatarId];
}
}
}
@@ -44,13 +49,18 @@ namespace EggLink.DanhengServer.Game.Avatar
CurrentSp = 0
};
if (AvatarData.Avatars == null)
if (AvatarData?.Avatars == null)
{
AvatarData.Avatars = [];
AvatarData!.Avatars = [];
}
AvatarData.Avatars.Add(avatar);
DatabaseHelper.Instance?.UpdateInstance(AvatarData);
}
public AvatarInfo? GetAvatar(int baseAvatarId)
{
return AvatarData?.Avatars?.Find(avatar => avatar.AvatarId == baseAvatarId);
}
}
}

View File

@@ -0,0 +1,56 @@
using EggLink.DanhengServer.Data.Excel;
using EggLink.DanhengServer.Game.Player;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.Game.Battle
{
public class BattleInstance(PlayerInstance player, Database.Lineup.LineupInfo lineup, List<StageConfigExcel> stages) : BasePlayerManager(player)
{
public int BattleId { get; set; } = ++player.NextBattleId;
public int StaminaCost { get; set; }
public int WorldLevel { get; set; }
public int CocoonWave { get; set; }
public int MappingInfoId { get; set; }
public int RoundLimit { get; set; }
public int StageId { get; set; } = stages[0].StageID;
public BattleEndStatus BattleEndStatus { get; set; }
public List<StageConfigExcel> Stages { get; set; } = stages;
public Database.Lineup.LineupInfo Lineup { get; set; } = lineup;
public ItemList GetDropItemList()
{
return new()
{
};
}
public SceneBattleInfo ToProto()
{
var proto = new SceneBattleInfo()
{
BattleId = (uint)BattleId,
WorldLevel = (uint)WorldLevel,
RoundsLimit = (uint)RoundLimit,
StageId = (uint)StageId,
LogicRandomSeed = (uint)Random.Shared.Next(),
};
foreach (var wave in Stages)
{
proto.MonsterWaveList.Add(wave.ToProto());
}
foreach (var avatar in Lineup.BaseAvatars!)
{
var avatarInstance = Player.AvatarManager.GetAvatar(avatar.BaseAvatarId);
if (avatarInstance == null) continue;
proto.BattleAvatarList.Add(avatarInstance.ToBattleProto(Player.LineupManager.GetCurLineup()!, Player.InventoryManager.Data));
}
return proto;
}
}
}

View File

@@ -0,0 +1,152 @@
using EggLink.DanhengServer.Data;
using EggLink.DanhengServer.Data.Excel;
using EggLink.DanhengServer.Database;
using EggLink.DanhengServer.Game.Player;
using EggLink.DanhengServer.Proto;
using EggLink.DanhengServer.Server.Packet.Send.Battle;
using EggLink.DanhengServer.Server.Packet.Send.Lineup;
using EggLink.DanhengServer.Util;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Game.Battle
{
public class BattleManager(PlayerInstance player) : BasePlayerManager(player)
{
public void StartCocoonStage(int cocoonId, int wave, int worldLevel)
{
if (Player.BattleInstance != null) return;
GameData.CocoonConfigData.TryGetValue(cocoonId * 100 + worldLevel, out var config);
if (config == null)
{
Player.SendPacket(new PacketStartCocoonStageScRsp());
return;
}
wave = Math.Min(Math.Max(wave, 1), config.MaxWave);
int cost = config.StaminaCost * wave;
if (Player.Data.Stamina < cost)
{
Player.SendPacket(new PacketStartCocoonStageScRsp());
return;
}
List<StageConfigExcel> stageConfigExcels = [];
for (int i = 0; i < wave; i++)
{
var stageId = config.StageIDList.RandomElement();
GameData.StageConfigData.TryGetValue(stageId, out var stageConfig);
if (stageConfig == null) continue;
stageConfigExcels.Add(stageConfig);
}
if (stageConfigExcels.Count == 0)
{
Player.SendPacket(new PacketStartCocoonStageScRsp());
return;
}
BattleInstance battleInstance = new(Player, Player.LineupManager.GetCurLineup()!, stageConfigExcels)
{
StaminaCost = cost,
WorldLevel = config.WorldLevel,
CocoonWave = wave,
MappingInfoId = config.MappingInfoID,
};
Player.BattleInstance = battleInstance;
Player.SendPacket(new PacketStartCocoonStageScRsp(battleInstance, cocoonId, wave));
}
public void EndBattle(PVEBattleResultCsReq req)
{
if (Player.BattleInstance == null)
{
Player.SendPacket(new PacketPVEBattleResultScRsp());
return;
}
Player.BattleInstance.BattleEndStatus = req.EndStatus;
var battle = Player.BattleInstance;
bool updateStatus = true;
bool teleportToAnchor = false;
var minimumHp = 0;
switch (req.EndStatus)
{
case BattleEndStatus.BattleEndWin:
// Remove monsters from the map - Could optimize it a little better
//for (var monster in battle.NpcMonsters)
//{
// // Dont remove farmable monsters from the scene when they are defeated
// if (monster.isFarmElement()) continue;
// // Remove monster
// player.SceneInstance.RemoveEntity(monster);
//}
// Drops
// Spend stamina
if (battle.StaminaCost > 0)
{
player.SpendStamina(battle.StaminaCost);
}
break;
case BattleEndStatus.BattleEndLose:
// Set avatar hp to 20% if the player's party is downed
minimumHp = 2000;
teleportToAnchor = true;
break;
case BattleEndStatus.BattleEndQuit:
updateStatus = false;
break;
default:
updateStatus = false;
break;
}
if (updateStatus)
{
var lineup = player.LineupManager.GetCurLineup()!;
// Update battle status
foreach (var avatar in req.Stt.BattleAvatarList)
{
var avatarInstance = player.AvatarManager.GetAvatar((int)avatar.Id);
if (avatarInstance == null) continue;
var prop = avatar.AvatarStatus;
int curHp = (int)Math.Round(prop.LeftHp / prop.MaxHp * 10000);
int curSp = (int)prop.LeftSp * 100;
avatarInstance.SetCurHp(curHp, lineup.LineupType != 0);
avatarInstance.SetCurSp(curSp, lineup.LineupType != 0);
}
DatabaseHelper.Instance?.UpdateInstance(Player.AvatarManager.AvatarData!);
Player.SendPacket(new PacketSyncLineupNotify(battle.Lineup));
}
if (teleportToAnchor)
{
var anchorProp = player.SceneInstance.GetNearestSpring(long.MaxValue);
if (anchorProp != null && anchorProp.PropInfo != null)
{
var anchor = player?.SceneInstance?.FloorInfo?.GetAnchorInfo(
anchorProp.PropInfo.AnchorGroupID,
anchorProp.PropInfo.AnchorID
);
if (anchor != null)
{
Player.MoveTo(anchor.ToPositionProto());
}
}
}
Player.BattleInstance = null;
Player.SendPacket(new PacketPVEBattleResultScRsp(req, Player, battle));
}
}
}

View File

@@ -8,7 +8,7 @@ namespace EggLink.DanhengServer.Game.Lineup
public class LineupManager : BasePlayerManager
{
public LineupData LineupData { get; private set; }
public LineupInfoJson LineupInfoJson { get; private set; }
public Dictionary<int, LineupInfo> LineupInfo { get; private set; }
public LineupManager(PlayerInstance player) : base(player)
{
@@ -18,29 +18,29 @@ namespace EggLink.DanhengServer.Game.Lineup
LineupData = new()
{
Uid = player.Uid,
CurLineup = 0,
Lineups = "{}",
CurLineup = 1,
};
DatabaseHelper.Instance?.SaveInstance(LineupData);
}
else
{
LineupData = lineup;
if (LineupData.Lineups != null)
{
foreach (var lineupInfo in LineupData.Lineups?.Values!)
{
lineupInfo.LineupData = LineupData;
lineupInfo.AvatarData = player.AvatarManager.AvatarData;
}
}
}
LineupInfoJson = JsonConvert.DeserializeObject<LineupInfoJson>(LineupData.Lineups ?? "{}") ?? new();
LineupInfo = LineupData.Lineups ?? [];
}
public LineupInfo? GetLineup(int lineupIndex)
{
if (LineupData.Lineups == null)
{
return null;
}
if (lineupIndex < 0 || lineupIndex >= LineupInfoJson.Lineups?.Count)
{
return null;
}
return LineupInfoJson.Lineups?[lineupIndex];
LineupInfo.TryGetValue(lineupIndex, out var lineup);
return lineup;
}
public LineupInfo? GetCurLineup()
@@ -50,7 +50,7 @@ namespace EggLink.DanhengServer.Game.Lineup
public void SetCurLineup(int lineupIndex)
{
if (lineupIndex < 0 || lineupIndex >= LineupInfoJson.Lineups?.Count)
if (lineupIndex < 0 || !LineupInfo.ContainsKey(lineupIndex))
{
return;
}
@@ -60,31 +60,28 @@ namespace EggLink.DanhengServer.Game.Lineup
public void AddAvatar(int lineupIndex, int avatarId)
{
if (lineupIndex < 0 || LineupData == null)
if (lineupIndex < 0)
{
return;
}
if (LineupData.Lineups == null)
{
LineupData.Lineups = "";
}
LineupInfo? lineup = null;
LineupInfoJson.Lineups?.TryGetValue(lineupIndex, out lineup);
LineupInfo.TryGetValue(lineupIndex, out LineupInfo? lineup);
if (lineup == null)
{
lineup = new()
{
Name = "Lineup " + lineupIndex,
LineupType = 0,
BaseAvatars = [avatarId],
BaseAvatars = [new() { BaseAvatarId = avatarId }],
LineupData = LineupData,
AvatarData = Player.AvatarManager.AvatarData,
};
LineupInfoJson.Lineups?.Add(lineupIndex, lineup);
LineupInfo.Add(lineupIndex, lineup);
} else
{
lineup.BaseAvatars?.Add(avatarId);
lineup.BaseAvatars?.Add(new() { BaseAvatarId = avatarId });
LineupInfo[lineupIndex] = lineup;
}
LineupData.Lineups = JsonConvert.SerializeObject(LineupInfoJson);
DatabaseHelper.Instance?.UpdateInstance(LineupData!);
DatabaseHelper.Instance?.UpdateInstance(LineupData);
}
public void AddAvatarToCurTeam(int avatarId)

View File

@@ -1,15 +1,21 @@
using EggLink.DanhengServer.Data;
using EggLink.DanhengServer.Data.Config;
using EggLink.DanhengServer.Data.Excel;
using EggLink.DanhengServer.Database;
using EggLink.DanhengServer.Database.Player;
using EggLink.DanhengServer.Enums;
using EggLink.DanhengServer.Game.Avatar;
using EggLink.DanhengServer.Game.Battle;
using EggLink.DanhengServer.Game.Inventory;
using EggLink.DanhengServer.Game.Lineup;
using EggLink.DanhengServer.Game.Scene;
using EggLink.DanhengServer.Game.Scene.Entity;
using EggLink.DanhengServer.Proto;
using EggLink.DanhengServer.Server;
using EggLink.DanhengServer.Server.Packet;
using EggLink.DanhengServer.Server.Packet.Send.Lineup;
using EggLink.DanhengServer.Server.Packet.Send.Player;
using EggLink.DanhengServer.Server.Packet.Send.Scene;
using EggLink.DanhengServer.Util;
namespace EggLink.DanhengServer.Game.Player
{
@@ -19,12 +25,16 @@ namespace EggLink.DanhengServer.Game.Player
public ushort Uid { get; set; }
public Connection? Connection { get; set; }
public bool Initialized { get; set; } = false;
public bool IsNewPlayer { get; set; } = false;
public int NextBattleId { get; set; } = 0;
#region Managers
public AvatarManager AvatarManager { get; private set; }
public LineupManager LineupManager { get; private set; }
public InventoryManager InventoryManager { get; private set; }
public BattleManager? BattleManager { get; private set; }
public BattleInstance? BattleInstance { get; set; }
#endregion
@@ -35,9 +45,10 @@ namespace EggLink.DanhengServer.Game.Player
#endregion
public PlayerInstance() : this(new PlayerData())
public PlayerInstance(int uid) : this(new PlayerData() { Uid = uid })
{
// new player
IsNewPlayer = true;
Data.Name = "无名客"; // Trailblazer in EN TODO: Add localization
Data.Signature = "";
Data.Birthday = 0;
@@ -56,13 +67,19 @@ namespace EggLink.DanhengServer.Game.Player
Data.Scoin = 0;
Data.Hcoin = 0;
Data.Mcoin = 0;
Data.PlaneId = 20001;
Data.FloorId = 20001001;
Data.TalentPoints = 0;
DatabaseHelper.Instance?.SaveInstance(Data);
InitialPlayerManager();
AddAvatar(1005);
LineupManager.SetCurLineup(0);
LineupManager.SetCurLineup(1);
LineupManager.AddAvatarToCurTeam(1005);
EnterScene(2000101, 0, false);
Initialized = true;
}
@@ -72,6 +89,7 @@ namespace EggLink.DanhengServer.Game.Player
AvatarManager = new(this);
LineupManager = new(this);
InventoryManager = new(this);
BattleManager = new(this);
var unlock = DatabaseHelper.Instance?.GetInstance<PlayerUnlockData>(Uid);
if (unlock == null)
@@ -83,7 +101,11 @@ namespace EggLink.DanhengServer.Game.Player
unlock = DatabaseHelper.Instance?.GetInstance<PlayerUnlockData>(Uid);
}
PlayerUnlockData = unlock!;
SceneInstance = new(this, GameData.MazePlaneData[20001], 20001001);
if (!IsNewPlayer)
{
LoadScene(Data.PlaneId, Data.FloorId, Data.EntryId, Data.Pos!, Data.Rot!, false);
}
}
@@ -102,7 +124,11 @@ namespace EggLink.DanhengServer.Game.Player
public async Task OnLogoutAsync()
{
DatabaseHelper.Instance?.UpdateInstance(Data);
DatabaseHelper.Instance?.UpdateInstance(PlayerUnlockData);
DatabaseHelper.Instance?.UpdateInstance(LineupManager.LineupData);
DatabaseHelper.Instance?.UpdateInstance(InventoryManager.Data);
DatabaseHelper.Instance?.UpdateInstance(AvatarManager.AvatarData!);
}
public void SendPacket(BasePacket packet)
@@ -118,6 +144,145 @@ namespace EggLink.DanhengServer.Game.Player
AvatarManager.AddAvatar(avatarId);
}
public void OnMove()
{
if (SceneInstance != null)
{
EntityProp? prop = SceneInstance.GetNearestSpring(25_000_000);
bool isInRange = prop != null;
if (isInRange)
{
if (LineupManager.GetCurLineup()?.Heal(10000, true) == true)
{
SendPacket(new PacketSyncLineupNotify(LineupManager.GetCurLineup()!));
}
}
}
}
public EntityProp? InteractProp(int propEntityId, int interactId)
{
if (SceneInstance != null)
{
SceneInstance.Entities.TryGetValue(propEntityId, out IGameEntity? entity);
if (entity == null) return null;
if (entity is EntityProp prop)
{
GameData.InteractConfigData.TryGetValue(interactId, out var config);
if (config == null || config.SrcState != prop.State) return prop;
var oldState = prop.State;
var newState = prop.State = config.TargetState;
SendPacket(new PacketGroupStateChangeScNotify(Data.EntryId, prop.GroupID, prop.State));
switch (prop.Excel.PropType)
{
case Enums.PropTypeEnum.PROP_TREASURE_CHEST:
if (oldState == Enums.PropStateEnum.ChestClosed && newState == Enums.PropStateEnum.ChestUsed)
{
// TODO: Add treasure chest handling
}
break;
case Enums.PropTypeEnum.PROP_DESTRUCT:
if (newState == Enums.PropStateEnum.Closed)
{
prop.State = Enums.PropStateEnum.Open;
}
break;
case Enums.PropTypeEnum.PROP_MAZE_PUZZLE:
if (newState == Enums.PropStateEnum.Closed || newState == Enums.PropStateEnum.Open)
{
foreach (var p in SceneInstance.GetEntitiesInGroup<EntityProp>(prop.GroupID))
{
if (p.Excel.PropType == Enums.PropTypeEnum.PROP_TREASURE_CHEST)
{
p.State = Enums.PropStateEnum.ChestClosed;
}
else if (p.Excel.PropType == Enums.PropTypeEnum.PROP_MAZE_PUZZLE)
{
// Skip
}
else
{
p.State = Enums.PropStateEnum.Open;
}
}
}
break;
}
return prop;
}
}
return null;
}
public void EnterScene(int entryId, int teleportId, bool sendPacket)
{
GameData.MapEntranceData.TryGetValue(entryId, out var entrance);
if (entrance == null) return;
GameData.GetFloorInfo(entrance.PlaneID, entrance.FloorID, out var floorInfo);
if (floorInfo == null) return;
int StartGroup = entrance.StartGroupID;
int StartAnchor = entrance.StartAnchorID;
if (teleportId != 0)
{
floorInfo.CachedTeleports.TryGetValue(teleportId, out var teleport);
if (teleport != null)
{
StartGroup = teleport.AnchorGroupID;
StartAnchor = teleport.AnchorID;
}
} else if (StartAnchor == 0)
{
StartGroup = floorInfo.StartGroupID;
StartAnchor = floorInfo.StartAnchorID;
}
AnchorInfo? anchor = floorInfo.GetAnchorInfo(StartGroup, StartAnchor);
LoadScene(entrance.PlaneID, entrance.FloorID, entryId, anchor!.ToPositionProto(), anchor.ToRotationProto(), sendPacket);
}
public void MoveTo(Position position)
{
Data.Pos = position;
SendPacket(new PacketSceneEntityMoveScNotify(this));
}
public void LoadScene(int planeId, int floorId, int entryId, Position pos, Position rot, bool sendPacket)
{
GameData.MazePlaneData.TryGetValue(planeId, out var plane);
if (plane == null) return;
// TODO: Sanify check
Data.Pos = pos;
Data.Rot = rot;
SceneInstance instance = new(this, plane, floorId, entryId);
if (planeId != Data.PlaneId || floorId != Data.FloorId || entryId != Data.EntryId)
{
Data.PlaneId = planeId;
Data.FloorId = floorId;
Data.EntryId = entryId;
DatabaseHelper.Instance?.UpdateInstance(Data);
}
SceneInstance = instance;
if (sendPacket)
{
SendPacket(new PacketEnterSceneByServerScNotify(instance));
}
}
public void SpendStamina(int staminaCost)
{
Data.Stamina -= staminaCost;
SendPacket(new PacketStaminaInfoScNotify(this));
}
#endregion
#region Proto

View File

@@ -0,0 +1,45 @@
using EggLink.DanhengServer.Data.Config;
using EggLink.DanhengServer.Data.Excel;
using EggLink.DanhengServer.Proto;
using EggLink.DanhengServer.Util;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Game.Scene.Entity
{
public class EntityMonster(SceneInstance scene, Position pos, Position rot, int GroupID, int InstID, NPCMonsterDataExcel excel, MonsterInfo info) : IGameEntity
{
public int EntityID { get; set; } = 0;
public int GroupID { get; set; } = GroupID;
public Position Position { get; set; } = pos;
public Position Rotation { get; set; } = rot;
public int InstID { get; set; } = InstID;
public NPCMonsterDataExcel MonsterData { get; set; } = excel;
public MonsterInfo Info { get; set; } = info;
public SceneEntityInfo ToProto()
{
return new()
{
EntityId = (uint)EntityID,
GroupId = (uint)GroupID,
InstId = (uint)InstID,
Motion = new()
{
Pos = Position.ToProto(),
Rot = Rotation.ToProto()
},
NpcMonster = new()
{
EventId = (uint)Info.EventID,
MonsterId = (uint)MonsterData.ID,
WorldLevel = (uint)scene.Player.Data.WorldLevel,
}
};
}
}
}

View File

@@ -0,0 +1,62 @@
using EggLink.DanhengServer.Data.Config;
using EggLink.DanhengServer.Proto;
using EggLink.DanhengServer.Util;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Game.Scene.Entity
{
public class EntityNpc(SceneInstance scene, GroupInfo group, NpcInfo npcInfo) : IGameEntity
{
public int EntityID { get; set; }
public int GroupID { get; set; } = group.Id;
public Position Position { get; set; } = npcInfo.ToPositionProto();
public Position Rotation { get; set; } = npcInfo.ToRotationProto();
public int NpcId { get; set; } = npcInfo.NPCID;
public int InstId { get; set; } = npcInfo.ID;
#region For Rogue
public int RogueNpcId { get; set; }
public bool IsFinishedTalk { get; set; }
public int EventUniqueId { get; set; }
#endregion
public SceneEntityInfo ToProto()
{
SceneNpcInfo npc = new()
{
NpcId = (uint)NpcId,
};
if (RogueNpcId > 0)
{
var rogue = new NpcRogueInfo()
{
RogueNpcId = (uint)RogueNpcId,
FinishDialogue = IsFinishedTalk,
UniqueId = (uint)EventUniqueId,
};
npc.ExtraInfo.RogueInfo = rogue;
}
return new SceneEntityInfo()
{
EntityId = (uint)EntityID,
GroupId = (uint)GroupID,
Motion = new MotionInfo()
{
Pos = Position.ToProto(),
Rot = Rotation.ToProto(),
},
InstId = (uint)InstId,
Npc = npc,
};
}
}
}

View File

@@ -0,0 +1,49 @@
using EggLink.DanhengServer.Data.Config;
using EggLink.DanhengServer.Data.Excel;
using EggLink.DanhengServer.Enums;
using EggLink.DanhengServer.Proto;
using EggLink.DanhengServer.Util;
namespace EggLink.DanhengServer.Game.Scene.Entity
{
public class EntityProp(SceneInstance scene, MazePropExcel excel, GroupInfo group, PropInfo prop) : IGameEntity
{
public int EntityID { get; set; }
public int GroupID { get; set; } = group.Id;
public Position Position { get; set; } = prop.ToPositionProto();
public Position Rotation { get; set; } = prop.ToRotationProto();
public PropStateEnum State { get; set; } = PropStateEnum.Closed;
public int InstId { get; set; } = prop.ID;
public MazePropExcel Excel { get; set; } = excel;
public PropInfo PropInfo { get; set; } = prop;
public PropRogueInfo? RogueInfo { get; set; }
public SceneEntityInfo ToProto()
{
var prop = new ScenePropInfo()
{
PropId = (uint)Excel.ID,
PropState = (uint)State,
};
if (RogueInfo != null)
{
prop.ExtraInfo.RogueInfo = RogueInfo;
}
return new SceneEntityInfo()
{
EntityId = (uint)EntityID,
GroupId = (uint)GroupID,
Motion = new MotionInfo()
{
Pos = Position.ToProto(),
Rot = Rotation.ToProto(),
},
InstId = (uint)InstId,
Prop = prop,
};
}
}
}

View File

@@ -0,0 +1,20 @@
using EggLink.DanhengServer.Proto;
using EggLink.DanhengServer.Util;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Game.Scene.Entity
{
public interface IGameEntity
{
public int EntityID { get; set; }
public int GroupID { get; set; }
public Position Position { get; set; }
public Position Rotation { get; set; }
public SceneEntityInfo ToProto();
}
}

View File

@@ -0,0 +1,128 @@
using EggLink.DanhengServer.Data;
using EggLink.DanhengServer.Data.Config;
using EggLink.DanhengServer.Enums;
using EggLink.DanhengServer.Game.Scene.Entity;
namespace EggLink.DanhengServer.Game.Scene
{
public class SceneEntityLoader(SceneInstance scene)
{
public void LoadEntity()
{
if (scene.IsLoaded) return;
foreach (var group in scene?.FloorInfo?.Groups.Values!) // Sanity check in SceneInstance
{
if (group.LoadSide == GroupLoadSideEnum.Client)
{
continue;
}
LoadGroup(group);
}
scene.IsLoaded = true;
}
public void LoadGroup(GroupInfo info)
{
foreach (var npc in info.NPCList)
{
try
{
LoadNpc(npc, info);
} catch
{
}
}
foreach (var monster in info.MonsterList)
{
try
{
LoadMonster(monster, info);
} catch
{
}
}
foreach (var prop in info.PropList)
{
try
{
LoadProp(prop, info);
} catch
{
}
}
}
public void LoadNpc(NpcInfo info, GroupInfo group)
{
if (info.IsClientOnly || info.IsDelete)
{
return;
}
if (!GameData.NpcDataData.ContainsKey(info.NPCID))
{
return;
}
bool hasDuplicateNpcId = false;
foreach (IGameEntity entity in scene.Entities.Values)
{
if (entity is EntityNpc eNpc && eNpc.NpcId == info.NPCID)
{
hasDuplicateNpcId = true;
break;
}
}
if (hasDuplicateNpcId)
{
return;
}
EntityNpc npc = new(scene, group, info);
scene.AddEntity(npc);
}
public void LoadMonster(MonsterInfo info, GroupInfo group)
{
if (info.IsClientOnly || info.IsDelete)
{
return;
}
GameData.NpcMonsterDataData.TryGetValue(info.NPCMonsterID, out var excel);
if (excel == null)
{
return;
}
EntityMonster entity = new(scene ,info.ToPositionProto(), info.ToRotationProto(), group.Id, excel.ID, excel, info);
scene.AddEntity(entity);
}
public void LoadProp(PropInfo info, GroupInfo group)
{
if (info.IsClientOnly || info.IsDelete)
{
return;
}
GameData.MazePropData.TryGetValue(info.PropID, out var excel);
if (excel == null)
{
return;
}
var prop = new EntityProp(scene, excel, group, info);
scene.AddEntity(prop);
if (excel.PropType == PropTypeEnum.PROP_SPRING)
{
scene.HealingSprings.Add(prop);
prop.State = PropStateEnum.CheckPointEnable;
} else
prop.State = PropStateEnum.Open;
}
}
}

View File

@@ -1,7 +1,10 @@
using EggLink.DanhengServer.Data;
using EggLink.DanhengServer.Data.Config;
using EggLink.DanhengServer.Data.Excel;
using EggLink.DanhengServer.Database;
using EggLink.DanhengServer.Database.Avatar;
using EggLink.DanhengServer.Game.Player;
using EggLink.DanhengServer.Game.Scene.Entity;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.Game.Scene
@@ -10,26 +13,130 @@ namespace EggLink.DanhengServer.Game.Scene
{
public PlayerInstance Player;
public MazePlaneExcel Excel;
public FloorInfo FloorInfo;
public FloorInfo? FloorInfo;
public int FloorId;
public int PlaneId;
public int EntryId;
public int LeaveEntryId;
public int LastEntityId;
public bool IsLoaded = false;
public SceneInstance(PlayerInstance player, MazePlaneExcel excel, int floorId)
public Dictionary<int, AvatarSceneInfo> AvatarInfo = [];
public int LeaderAvatarId;
public Dictionary<int, IGameEntity> Entities = [];
public List<EntityProp> HealingSprings = [];
public SceneEntityLoader? EntityLoader;
public SceneInstance(PlayerInstance player, MazePlaneExcel excel, int floorId, int entryId)
{
Player = player;
Excel = excel;
PlaneId = excel.PlaneID;
FloorId = floorId;
EntryId = entryId;
SyncLineup();
GameData.GetFloorInfo(PlaneId, FloorId, out FloorInfo);
if (FloorInfo == null) return;
switch (Excel.PlaneType)
{
default:
EntityLoader = new(this);
break;
}
EntityLoader.LoadEntity();
}
public void SyncLineup()
{
AvatarInfo = [];
foreach (var avatar in Player.LineupManager?.GetCurLineup()?.BaseAvatars ?? [])
{
if (avatar.AssistUid != 0)
{
var assistPlayer = DatabaseHelper.Instance?.GetInstance<AvatarData>(avatar.AssistUid);
if (assistPlayer != null)
{
var assistAvatar = assistPlayer.Avatars?.Find(x => x.AvatarId == avatar.BaseAvatarId);
if (assistAvatar != null)
{
AvatarInfo.Add(assistAvatar.AvatarId, new(assistAvatar, AvatarType.AvatarAssistType));
}
}
} else if (avatar.SpecialAvatarId != 0)
{
} else
{
var avatarData = Player.AvatarManager?.GetAvatar(avatar.BaseAvatarId);
if (avatarData?.AvatarId == avatar.BaseAvatarId)
{
avatarData.EntityId = ++LastEntityId;
AvatarInfo.Add(avatarData.EntityId, new(avatarData, AvatarType.AvatarFormalType));
}
}
};
LeaderAvatarId = Player.LineupManager?.GetCurLineup()?.LeaderAvatarId ?? 0;
}
public EntityProp? GetNearestSpring(long minDistSq)
{
EntityProp? spring = null;
long springDist = 0;
foreach (EntityProp prop in HealingSprings)
{
long dist = Player.Data?.Pos?.GetFast2dDist(prop.Position) ?? 1000000;
if (dist > minDistSq) continue;
if (spring == null || dist < springDist)
{
spring = prop;
springDist = dist;
}
}
return spring;
}
public void AddEntity(IGameEntity entity)
{
AddEntity(entity, IsLoaded);
}
public void AddEntity(IGameEntity entity, bool SendPacket)
{
if (entity == null || entity.EntityID != 0) return;
entity.EntityID = ++LastEntityId;
Entities.Add(entity.EntityID, entity);
}
public void RemoveEntity(IGameEntity monster, bool SendPacket = false)
{
Entities.Remove(monster.EntityID);
}
public List<T> GetEntitiesInGroup<T>(int groupID)
{
List<T> entities = [];
foreach (var entity in Entities)
{
if (entity.Value.GroupID == groupID && entity.Value is T t)
{
entities.Add(t);
}
}
return entities;
}
#region Proto Convert
public SceneInfo ToProto()
{
SceneInfo sceneInfo = new()
@@ -40,8 +147,51 @@ namespace EggLink.DanhengServer.Game.Scene
FloorId = (uint)FloorId,
EntryId = (uint)EntryId,
};
var playerGroupInfo = new SceneEntityGroupInfo(); // avatar group
foreach (var avatar in AvatarInfo)
{
playerGroupInfo.EntityList.Add(avatar.Value.AvatarInfo.ToSceneEntityInfo(avatar.Value.AvatarType));
}
if (LeaderAvatarId != 0)
{
sceneInfo.LeaderEntityId = (uint)LeaderAvatarId;
} else
{
LeaderAvatarId = AvatarInfo.Keys.First();
sceneInfo.LeaderEntityId = (uint)LeaderAvatarId;
}
sceneInfo.EntityGroupList.Add(playerGroupInfo);
List<SceneEntityGroupInfo> groups = []; // other groups
foreach (var entity in Entities)
{
if (entity.Value.GroupID == 0) continue;
if (groups.FindIndex(x => x.GroupId == entity.Value.GroupID) == -1)
{
groups.Add(new SceneEntityGroupInfo()
{
GroupId = (uint)entity.Value.GroupID
});
}
groups[groups.FindIndex(x => x.GroupId == entity.Value.GroupID)].EntityList.Add(entity.Value.ToProto());
}
foreach (var group in groups)
{
sceneInfo.EntityGroupList.Add(group);
}
return sceneInfo;
}
#endregion
}
public class AvatarSceneInfo(AvatarInfo avatarInfo, AvatarType avatarType)
{
public AvatarInfo AvatarInfo = avatarInfo;
public AvatarType AvatarType = avatarType;
}
}

View File

@@ -15,27 +15,23 @@
</ItemGroup>
<ItemGroup>
<Folder Include="Server\Packet\Recv\Rogue\" />
<Folder Include="Server\Packet\Recv\Achievement\" />
<Folder Include="Server\Packet\Recv\ChessRogue\" />
<Folder Include="Server\Packet\Recv\Tutorial\" />
<Folder Include="Server\Packet\Recv\Quest\" />
<Folder Include="Server\Packet\Recv\Mail\" />
<Folder Include="Server\Packet\Recv\Friend\" />
<Folder Include="Server\Packet\Recv\Battle\" />
<Folder Include="Server\Packet\Recv\Others\" />
<Folder Include="Server\Packet\Send\Achievement\" />
<Folder Include="Server\Packet\Send\Battle\" />
<Folder Include="Server\Packet\Send\ChessRogue\" />
<Folder Include="Server\Packet\Send\Friend\" />
<Folder Include="Server\Packet\Send\Mail\" />
<Folder Include="Server\Packet\Send\Others\" />
<Folder Include="Server\Packet\Send\Quest\" />
<Folder Include="Server\Packet\Send\Rogue\" />
<Folder Include="Server\Packet\Send\Tutorial\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Spectre.Console" Version="0.48.0" />
<PackageReference Include="SQLitePCLRaw.core" Version="2.1.8" />
<PackageReference Include="SQLitePCLRaw.provider.e_sqlite3" Version="2.1.8" />
<PackageReference Include="SqlSugarCore" Version="5.1.4.143" />

View File

@@ -5,6 +5,9 @@ using EggLink.DanhengServer.WebServer;
using EggLink.DanhengServer.Database;
using EggLink.DanhengServer.Server;
using EggLink.DanhengServer.Server.Packet;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
using EggLink.DanhengServer.Command;
namespace EggLink.DanhengServer.Program
{
@@ -14,6 +17,7 @@ namespace EggLink.DanhengServer.Program
public static DatabaseHelper DatabaseHelper = new();
public static Listener Listener = new();
public static HandlerManager HandlerManager = new();
public static CommandManager CommandManager = new();
public static void Main(string[] args)
{
@@ -65,6 +69,15 @@ namespace EggLink.DanhengServer.Program
Console.ReadLine();
return;
}
try
{
CommandManager.RegisterCommand();
} catch (Exception e)
{
logger.Error("Failed to initialize command manager", e);
Console.ReadLine();
return;
}
WebProgram.Main([$"--urls=http://{GetConfig().HttpServer.PublicAddress}:{GetConfig().HttpServer.PublicPort}/"]);
logger.Info($"DispatchServer is running on http://{GetConfig().HttpServer.PublicAddress}:{GetConfig().HttpServer.PublicPort}/");
@@ -73,10 +86,10 @@ namespace EggLink.DanhengServer.Program
var elapsed = DateTime.Now - time;
logger.Info($"Done in {elapsed.TotalSeconds.ToString()[..4]}s! Type '/help' to get help of commands.");
while (true)
{
Console.ReadLine();
}
#if DEBUG
JsonConvert.DeserializeObject<JObject>(File.ReadAllText("LogMap.json"))!.Properties().ToList().ForEach(x => Connection.LogMap.Add(x.Name, x.Value.ToString()));
#endif
CommandManager.Start();
}
public static ConfigContainer GetConfig()

View File

@@ -23,13 +23,10 @@ public partial class Connection
public readonly IPEndPoint RemoteEndPoint;
public SessionState State { get; set; } = SessionState.INACTIVE;
public PlayerInstance? Player { get; set; }
public uint ClientTime { get; private set; }
public long LastPingTime { get; private set; }
private uint LastClientSeq = 10;
public static readonly List<int> BANNED_PACKETS = [];
private static readonly Logger Logger = new("GameServer");
#if DEBUG
private static readonly Dictionary<string, string> LogMap = [];
public static readonly Dictionary<string, string> LogMap = [];
#endif
public Connection(KcpConversation conversation, IPEndPoint remote)
{
@@ -37,9 +34,6 @@ public partial class Connection
RemoteEndPoint = remote;
CancelToken = new CancellationTokenSource();
Start();
#if DEBUG
JsonConvert.DeserializeObject<JObject>(File.ReadAllText("LogMap.json")).Properties().ToList().ForEach(x => LogMap.Add(x.Name, x.Value.ToString()));
#endif
}
private async void Start()
@@ -65,12 +59,6 @@ public partial class Connection
}
private void UpdateLastPingTime(uint clientTime)
{
ClientTime = clientTime;
LastPingTime = DateTimeOffset.Now.ToUnixTimeMilliseconds();
}
#if DEBUG
public static void LogPacket(string sendOrRecv, ushort opcode, byte[] payload)
{
@@ -246,27 +234,6 @@ public partial class Connection
public void SendPacket(int cmdId)
{
// Test
if (cmdId <= 0)
{
Logger.Debug("Tried to send packet with missing cmd id!");
return;
}
// DO NOT REMOVE (unless we find a way to validate code before sending to client which I don't think we can)
if (BANNED_PACKETS.Contains(cmdId))
{
return;
}
#if DEBUG
LogPacket("Send", (ushort)cmdId, []);
#endif
// Header
byte[] packetBytes = new BasePacket((ushort)cmdId).BuildPacket();
#pragma warning disable CA2012
_ = Conversation.SendAsync(packetBytes, CancelToken.Token);
#pragma warning restore CA2012
SendPacket(new BasePacket((ushort)cmdId));
}
}

View File

@@ -14,7 +14,6 @@ namespace EggLink.DanhengServer.Server
private static UdpClient? UDPClient;
private static IPEndPoint? ListenAddress;
private static IKcpTransport<IKcpMultiplexConnection>? KCPTransport;
private static readonly CancellationTokenSource CancelToken = new();
private static readonly Logger Logger = new("GameServer");
private static IKcpMultiplexConnection? Multiplex => KCPTransport?.Connection;
public static readonly SortedList<long, Connection> Connections = [];

View File

@@ -0,0 +1,18 @@
using EggLink.DanhengServer.Server.Packet.Send.Avatar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Server.Packet.Recv.Avatar
{
[Opcode(CmdIds.GetAssistHistoryCsReq)]
public class HandlerGetAssistHistoryCsReq : Handler
{
public override void OnHandle(Connection connection, byte[] header, byte[] data)
{
connection.SendPacket(new PacketGetAssistHistoryScRsp(connection.Player!));
}
}
}

View File

@@ -1,6 +1,6 @@
using EggLink.DanhengServer.Server.Packet.Send.Player;
using EggLink.DanhengServer.Server.Packet.Send.Avatar;
namespace EggLink.DanhengServer.Server.Packet.Recv.Player
namespace EggLink.DanhengServer.Server.Packet.Recv.Avatar
{
[Opcode(CmdIds.GetHeroBasicTypeInfoCsReq)]
public class HandlerGetHeroBasicTypeInfoCsReq : Handler

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Server.Packet.Recv.Battle
{
[Opcode(CmdIds.GetCurBattleInfoCsReq)]
public class HandlerGetCurBattleInfoCsReq : Handler
{
public override void OnHandle(Connection connection, byte[] header, byte[] data)
{
connection.SendPacket(CmdIds.GetCurBattleInfoScRsp);
}
}
}

View File

@@ -0,0 +1,19 @@
using EggLink.DanhengServer.Proto;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Server.Packet.Recv.Battle
{
[Opcode(CmdIds.PVEBattleResultCsReq)]
public class HandlerPVEBattleResultCsReq : Handler
{
public override void OnHandle(Connection connection, byte[] header, byte[] data)
{
var req = PVEBattleResultCsReq.Parser.ParseFrom(data);
connection.Player?.BattleManager?.EndBattle(req);
}
}
}

View File

@@ -0,0 +1,18 @@
using EggLink.DanhengServer.Server.Packet.Send.Battle;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Server.Packet.Recv.Battle
{
[Opcode(CmdIds.SceneCastSkillCsReq)]
public class HandlerSceneCastSkillCsReq : Handler
{
public override void OnHandle(Connection connection, byte[] header, byte[] data)
{
connection.SendPacket(new PacketSceneCastSkillScRsp());
}
}
}

View File

@@ -0,0 +1,19 @@
using EggLink.DanhengServer.Proto;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Server.Packet.Recv.Battle
{
[Opcode(CmdIds.StartCocoonStageCsReq)]
public class HandlerStartCocoonStageCsReq : Handler
{
public override void OnHandle(Connection connection, byte[] header, byte[] data)
{
var req = StartCocoonStageCsReq.Parser.ParseFrom(data);
connection.Player?.BattleManager?.StartCocoonStage((int)req.CocoonId, (int)req.Wave, (int)req.WorldLevel);
}
}
}

View File

@@ -0,0 +1,13 @@
using EggLink.DanhengServer.Server.Packet.Send.Lineup;
namespace EggLink.DanhengServer.Server.Packet.Recv.Lineup
{
[Opcode(CmdIds.GetAllLineupDataCsReq)]
public class HandlerGetAllLineupDataCsReq : Handler
{
public override void OnHandle(Connection connection, byte[] header, byte[] data)
{
connection.SendPacket(new PacketGetAllLineupDataScRsp(connection.Player!));
}
}
}

View File

@@ -0,0 +1,18 @@
using EggLink.DanhengServer.Server.Packet.Send.Lineup;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Server.Packet.Recv.Lineup
{
[Opcode(CmdIds.GetCurLineupDataCsReq)]
public class HandlerGetCurLineupDataCsReq : Handler
{
public override void OnHandle(Connection connection, byte[] header, byte[] data)
{
connection.SendPacket(new PacketGetCurLineupDataScRsp(connection.Player!));
}
}
}

View File

@@ -1,10 +1,10 @@
using EggLink.DanhengServer.Common.Enums;
using EggLink.DanhengServer.Database;
using EggLink.DanhengServer.Database.Account;
using EggLink.DanhengServer.Database.Player;
using EggLink.DanhengServer.Game.Player;
using EggLink.DanhengServer.Proto;
using EggLink.DanhengServer.Server.Packet.Send.Player;
using EggLink.DanhengServer.Util;
namespace EggLink.DanhengServer.Server.Packet.Recv.Player
{
@@ -14,17 +14,20 @@ namespace EggLink.DanhengServer.Server.Packet.Recv.Player
public override void OnHandle(Connection connection, byte[] header, byte[] data)
{
var req = PlayerGetTokenCsReq.Parser.ParseFrom(data);
var account = DatabaseHelper.Instance?.GetInstance<AccountData>(long.Parse(req.AccountUid));
if (account == null)
{
connection.SendPacket(new PacketPlayerGetTokenScRsp());
return;
}
connection.State = SessionState.WAITING_FOR_LOGIN;
var pd = DatabaseHelper.Instance?.GetInstance<PlayerData>(long.Parse(req.AccountUid));
if (pd == null)
connection.Player = new PlayerInstance()
{
Uid = ushort.Parse(req.AccountUid),
};
connection.Player = new PlayerInstance(int.Parse(req.AccountUid));
else
{
connection.Player = new PlayerInstance(pd);
}
connection.Player.OnLogin();
connection.Player.Connection = connection;
connection.SendPacket(new PacketPlayerGetTokenScRsp(connection));

View File

@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Server.Packet.Recv.Player
{
[Opcode(CmdIds.PlayerLogoutCsReq)]
public class HandlerPlayerLogoutCsReq : Handler
{
public override void OnHandle(Connection connection, byte[] header, byte[] data)
{
connection.SendPacket(CmdIds.PlayerLogoutScRsp);
connection.Stop();
}
}
}

View File

@@ -0,0 +1,18 @@
using EggLink.DanhengServer.Server.Packet.Send.Quest;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Server.Packet.Recv.Quest
{
[Opcode(CmdIds.GetQuestDataCsReq)]
public class HandlerGetQuestDataCsReq : Handler
{
public override void OnHandle(Connection connection, byte[] header, byte[] data)
{
connection.SendPacket(new PacketGetQuestDataScRsp());
}
}
}

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Server.Packet.Recv.Rogue
{
[Opcode(CmdIds.GetRogueHandbookDataCsReq)]
public class HandlerGetRogueHandbookDataCsReq : Handler
{
public override void OnHandle(Connection connection, byte[] header, byte[] data)
{
connection.SendPacket(CmdIds.GetRogueHandbookDataScRsp);
}
}
}

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Server.Packet.Recv.Rogue
{
[Opcode(CmdIds.GetRogueInfoCsReq)]
public class HandlerGetRogueInfoCsReq : Handler
{
public override void OnHandle(Connection connection, byte[] header, byte[] data)
{
connection.SendPacket(CmdIds.GetRogueInfoScRsp);
}
}
}

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Server.Packet.Recv.Rogue
{
[Opcode(CmdIds.GetRogueScoreRewardInfoCsReq)]
public class HandlerGetRogueScoreRewardInfoCsReq : Handler
{
public override void OnHandle(Connection connection, byte[] header, byte[] data)
{
connection.SendPacket(CmdIds.GetRogueScoreRewardInfoScRsp);
}
}
}

View File

@@ -0,0 +1,21 @@
using EggLink.DanhengServer.Proto;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Server.Packet.Recv.Scene
{
[Opcode(CmdIds.EnterSceneCsReq)]
public class HandlerEnterSceneCsReq : Handler
{
public override void OnHandle(Connection connection, byte[] header, byte[] data)
{
var req = EnterSceneCsReq.Parser.ParseFrom(data);
connection.Player?.EnterScene((int)req.EntryId, (int)req.TeleportId, true);
connection.SendPacket(CmdIds.EnterSceneScRsp);
}
}
}

View File

@@ -1,9 +1,4 @@
using EggLink.DanhengServer.Server.Packet.Send.Scene;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Server.Packet.Recv.Scene
{

View File

@@ -0,0 +1,15 @@
using EggLink.DanhengServer.Proto;
using EggLink.DanhengServer.Server.Packet.Send.Scene;
namespace EggLink.DanhengServer.Server.Packet.Recv.Scene
{
[Opcode(CmdIds.GetFirstTalkByPerformanceNpcCsReq)]
public class HandlerGetFirstTalkByPerformanceNpcCsReq : Handler
{
public override void OnHandle(Connection connection, byte[] header, byte[] data)
{
var req = GetFirstTalkByPerformanceNpcCsReq.Parser.ParseFrom(data);
connection.SendPacket(new PacketGetFirstTalkByPerformanceNpcScRsp(req));
}
}
}

View File

@@ -0,0 +1,15 @@
using EggLink.DanhengServer.Proto;
using EggLink.DanhengServer.Server.Packet.Send.Scene;
namespace EggLink.DanhengServer.Server.Packet.Recv.Scene
{
[Opcode(CmdIds.GetSceneMapInfoCsReq)]
public class HandlerGetSceneMapInfoCsReq : Handler
{
public override void OnHandle(Connection connection, byte[] header, byte[] data)
{
var req = GetSceneMapInfoCsReq.Parser.ParseFrom(data);
connection.SendPacket(new PacketGetSceneMapInfoScRsp(req));
}
}
}

View File

@@ -0,0 +1,16 @@
using EggLink.DanhengServer.Proto;
using EggLink.DanhengServer.Server.Packet.Send.Scene;
namespace EggLink.DanhengServer.Server.Packet.Recv.Scene
{
[Opcode(CmdIds.InteractPropCsReq)]
public class HandlerInteractPropCsReq : Handler
{
public override void OnHandle(Connection connection, byte[] header, byte[] data)
{
var req = InteractPropCsReq.Parser.ParseFrom(data);
var prop = connection.Player?.InteractProp((int)req.PropEntityId, (int)req.InteractId);
connection.SendPacket(new PacketInteractPropScRsp(prop));
}
}
}

View File

@@ -0,0 +1,34 @@
using EggLink.DanhengServer.Proto;
using EggLink.DanhengServer.Util;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Server.Packet.Recv.Scene
{
[Opcode(CmdIds.SceneEntityMoveCsReq)]
public class HandlerSceneEntityMoveCsReq : Handler
{
public override void OnHandle(Connection connection, byte[] header, byte[] data)
{
var req = SceneEntityMoveCsReq.Parser.ParseFrom(data);
if (req != null)
{
foreach (var motion in req.EntityMotionList)
{
var avatar = connection?.Player?.SceneInstance.AvatarInfo.ToList().Find(x => x.Value.AvatarInfo.EntityId == motion.EntityId);
if (avatar != null)
{
connection!.Player!.Data.Pos = motion.Motion.Pos.ToPosition();
connection.Player.Data.Rot = motion.Motion.Rot.ToPosition();
connection.Player.OnMove();
}
}
}
connection!.SendPacket(CmdIds.SceneEntityMoveScRsp);
}
}
}

View File

@@ -0,0 +1,17 @@
using EggLink.DanhengServer.Game.Player;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Server.Packet.Send.Avatar
{
public class PacketGetAssistHistoryScRsp : BasePacket
{
public PacketGetAssistHistoryScRsp(PlayerInstance player) : base(CmdIds.GetAssistHistoryScRsp)
{
}
}
}

View File

@@ -1,7 +1,7 @@
using EggLink.DanhengServer.Game.Player;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.Server.Packet.Send.Player
namespace EggLink.DanhengServer.Server.Packet.Send.Avatar
{
public class PacketGetHeroBasicTypeInfoScRsp : BasePacket
{

View File

@@ -0,0 +1,38 @@
using EggLink.DanhengServer.Game.Battle;
using EggLink.DanhengServer.Game.Player;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.Server.Packet.Send.Battle
{
public class PacketPVEBattleResultScRsp : BasePacket
{
public PacketPVEBattleResultScRsp() : base(CmdIds.PVEBattleResultScRsp)
{
var proto = new PVEBattleResultScRsp()
{
Retcode = 1,
};
SetData(proto);
}
public PacketPVEBattleResultScRsp(PVEBattleResultCsReq req, PlayerInstance player, BattleInstance battle) : base(CmdIds.PVEBattleResultScRsp)
{
var proto = new PVEBattleResultScRsp()
{
DropData = battle.GetDropItemList(),
ResVersion = req.ClientResVersion.ToString(),
BinVersion = "",
StageId = req.StageId,
BattleId = req.BattleId,
EndStatus = req.EndStatus,
CheckIdentical = true,
Unk1 = new(),
Unk2 = new(),
Unk3 = new(),
};
SetData(proto);
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Server.Packet.Send.Battle
{
public class PacketSceneCastSkillScRsp : BasePacket
{
public PacketSceneCastSkillScRsp() : base(CmdIds.SceneCastSkillScRsp)
{
}
}
}

View File

@@ -0,0 +1,35 @@
using EggLink.DanhengServer.Game.Battle;
using EggLink.DanhengServer.Proto;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Server.Packet.Send.Battle
{
public class PacketStartCocoonStageScRsp : BasePacket
{
public PacketStartCocoonStageScRsp() : base(CmdIds.StartCocoonStageScRsp)
{
var rsp = new StartCocoonStageScRsp()
{
Retcode = 1
};
SetData(rsp);
}
public PacketStartCocoonStageScRsp(BattleInstance battle, int cocoonId, int wave) : base(CmdIds.StartCocoonStageScRsp)
{
var rsp = new StartCocoonStageScRsp()
{
CocoonId = (uint)cocoonId,
Wave = (uint)wave,
BattleInfo = battle.ToProto()
};
SetData(rsp);
}
}
}

View File

@@ -0,0 +1,27 @@
using EggLink.DanhengServer.Game.Player;
using EggLink.DanhengServer.Proto;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Server.Packet.Send.Lineup
{
public class PacketGetAllLineupDataScRsp : BasePacket
{
public PacketGetAllLineupDataScRsp(PlayerInstance player) : base(CmdIds.GetAllLineupDataScRsp)
{
var proto = new GetAllLineupDataScRsp()
{
CurIndex = (uint)(player.LineupManager.LineupData.CurLineup - 1),
};
foreach (var lineup in player.LineupManager.LineupInfo.Values)
{
proto.LineupList.Add(lineup.ToProto());
}
SetData(proto);
}
}
}

View File

@@ -0,0 +1,23 @@
using EggLink.DanhengServer.Game.Player;
using EggLink.DanhengServer.Proto;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Server.Packet.Send.Lineup
{
public class PacketGetCurLineupDataScRsp : BasePacket
{
public PacketGetCurLineupDataScRsp(PlayerInstance player) : base(CmdIds.GetCurLineupDataScRsp)
{
var data = new GetCurLineupDataScRsp()
{
Lineup = player.LineupManager.GetCurLineup()!.ToProto(),
};
SetData(data);
}
}
}

View File

@@ -0,0 +1,17 @@
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.Server.Packet.Send.Lineup
{
public class PacketSyncLineupNotify : BasePacket
{
public PacketSyncLineupNotify(Database.Lineup.LineupInfo info) : base(CmdIds.SyncLineupNotify)
{
var proto = new SyncLineupNotify()
{
Lineup = info.ToProto(),
};
SetData(proto);
}
}
}

View File

@@ -8,10 +8,19 @@ namespace EggLink.DanhengServer.Server.Packet.Send.Player
{
var rsp = new PlayerGetTokenScRsp()
{
BlackInfo = new BlackInfo(),
BlackInfo = new(),
Uid = connection.Player?.Uid ?? 0,
};
SetData(rsp);
}
public PacketPlayerGetTokenScRsp() : base(CmdIds.PlayerGetTokenScRsp)
{
var rsp = new PlayerGetTokenScRsp()
{
Retcode = 114514,
};
SetData(rsp);
}
}

View File

@@ -0,0 +1,27 @@
using EggLink.DanhengServer.Data;
using EggLink.DanhengServer.Proto;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Server.Packet.Send.Quest
{
public class PacketGetQuestDataScRsp : BasePacket
{
public PacketGetQuestDataScRsp() : base(CmdIds.GetQuestDataScRsp)
{
var proto = new GetQuestDataScRsp();
foreach (var quest in GameData.QuestDataData.Values)
{
proto.QuestList.Add(new Proto.Quest()
{
Id = (uint)quest.QuestID,
Status = QuestStatus.QuestFinish
});
}
SetData(proto);
}
}
}

View File

@@ -0,0 +1,20 @@
using EggLink.DanhengServer.Game.Scene;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.Server.Packet.Send.Scene
{
public class PacketEnterSceneByServerScNotify : BasePacket
{
public PacketEnterSceneByServerScNotify(SceneInstance scene) : base(CmdIds.EnterSceneByServerScNotify)
{
var sceneInfo = scene.ToProto();
var notify = new EnterSceneByServerScNotify()
{
Scene = sceneInfo,
Lineup = scene.Player.LineupManager.GetCurLineup()!.ToProto(),
};
SetData(notify);
}
}
}

View File

@@ -0,0 +1,22 @@
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.Server.Packet.Send.Scene
{
public class PacketGetFirstTalkByPerformanceNpcScRsp : BasePacket
{
public PacketGetFirstTalkByPerformanceNpcScRsp(GetFirstTalkByPerformanceNpcCsReq req) : base(CmdIds.GetFirstTalkByPerformanceNpcScRsp)
{
var rsp = new GetFirstTalkByPerformanceNpcScRsp();
foreach (var id in req.NpcTalkList)
{
rsp.NpcTalkInfoList.Add(new NpcTalkInfo
{
NpcTalkId = id,
});
}
SetData(rsp);
}
}
}

View File

@@ -0,0 +1,86 @@
using EggLink.DanhengServer.Data;
using EggLink.DanhengServer.Data.Config;
using EggLink.DanhengServer.Enums;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.Server.Packet.Send.Scene
{
public class PacketGetSceneMapInfoScRsp : BasePacket
{
public PacketGetSceneMapInfoScRsp(GetSceneMapInfoCsReq req) : base(CmdIds.GetSceneMapInfoScRsp)
{
var rsp = new GetSceneMapInfoScRsp();
foreach (var entry in req.EntryIdList)
{
var mazeMap = new MazeMapData()
{
EntryId = entry,
};
GameData.MapEntranceData.TryGetValue((int)entry, out var mapData);
if (mapData == null)
{
rsp.MapList.Add(mazeMap);
continue;
}
GameData.GetFloorInfo(mapData.PlaneID, mapData.FloorID, out var floorInfo);
if (floorInfo == null)
{
rsp.MapList.Add(mazeMap);
continue;
}
mazeMap.UnlockedChestList.Add(new MazeChest()
{
TotalAmountList = 1,
MapInfoChestType = MapInfoChestType.Normal
});
mazeMap.UnlockedChestList.Add(new MazeChest()
{
TotalAmountList = 1,
MapInfoChestType = MapInfoChestType.Puzzle
});
mazeMap.UnlockedChestList.Add(new MazeChest()
{
TotalAmountList = 1,
MapInfoChestType = MapInfoChestType.Challenge
});
foreach (GroupInfo groupInfo in floorInfo.Groups.Values) // all the icons on the map
{
var mazeGroup = new MazeGroup()
{
GroupId = (uint)groupInfo.Id,
};
mazeMap.MazeGroupList.Add(mazeGroup);
}
foreach (var teleport in floorInfo.CachedTeleports.Values)
{
mazeMap.UnlockedTeleportList.Add((uint)teleport.MappingInfoID);
}
foreach (var prop in floorInfo.UnlockedCheckpoints)
{
var mazeProp = new MazeProp()
{
GroupId = (uint)prop.AnchorGroupID,
ConfigId = (uint)prop.ID,
State = (uint)PropStateEnum.CheckPointEnable,
};
mazeMap.MazePropList.Add(mazeProp);
}
for (int i = 0; i < 100; i++)
{
mazeMap.LightenSectionList.Add((uint)i);
}
rsp.MapList.Add(mazeMap);
}
SetData(rsp);
}
}
}

View File

@@ -0,0 +1,23 @@
using EggLink.DanhengServer.Enums;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.Server.Packet.Send.Scene
{
public class PacketGroupStateChangeScNotify : BasePacket
{
public PacketGroupStateChangeScNotify(int entryId, int groupId, PropStateEnum propState) : base(CmdIds.GroupStateChangeScNotify)
{
var notify = new GroupStateChangeScNotify()
{
GroupStateInfo = new GroupStateInfo()
{
EntryId = (uint)entryId,
GroupId = (uint)groupId,
GroupState = (uint)propState,
}
};
SetData(notify);
}
}
}

View File

@@ -0,0 +1,20 @@
using EggLink.DanhengServer.Game.Scene.Entity;
using EggLink.DanhengServer.Proto;
namespace EggLink.DanhengServer.Server.Packet.Send.Scene
{
public class PacketInteractPropScRsp : BasePacket
{
public PacketInteractPropScRsp(EntityProp? prop) : base(CmdIds.InteractPropScRsp)
{
var proto = new InteractPropScRsp();
if (prop != null)
{
proto.PropState = (uint)prop.State;
proto.PropEntityId = (uint)prop.EntityID;
}
SetData(proto);
}
}
}

View File

@@ -0,0 +1,28 @@
using EggLink.DanhengServer.Game.Player;
using EggLink.DanhengServer.Proto;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EggLink.DanhengServer.Server.Packet.Send.Scene
{
public class PacketSceneEntityMoveScNotify : BasePacket
{
public PacketSceneEntityMoveScNotify(PlayerInstance player) : base(CmdIds.SceneEntityMoveScNotify)
{
var proto = new SceneEntityMoveScNotify()
{
EntryId = (uint)player.Data.EntryId,
Motion = new MotionInfo()
{
Pos = player.Data.Pos!.ToProto(),
Rot = player.Data.Rot!.ToProto(),
},
};
SetData(proto);
}
}
}

View File

@@ -10,4 +10,6 @@
</p>
## Thanks
- Weedwacker - offers kcp processor
- Weedwacker - Provide a kcp implementation for C#
- [SqlSugar](https://github.com/donet5/SqlSugar) - Provide a ORM for C#
- [LunarCore](https://github.com/Melledy/LunarCore) - Some data structures and algorithms

View File

@@ -12,6 +12,7 @@
<PackageReference Include="Google.Protobuf" Version="3.25.3" />
<PackageReference Include="Google.Protobuf.Tools" Version="3.25.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Spectre.Console" Version="0.48.0" />
<PackageReference Include="SQLitePCLRaw.core" Version="2.1.8" />
<PackageReference Include="SQLitePCLRaw.provider.e_sqlite3" Version="2.1.8" />
<PackageReference Include="SqlSugarCore" Version="5.1.4.143" />