mirror of
https://github.com/EpinelPS/EpinelPS.git
synced 2025-12-12 23:14:34 +01:00
Compare commits
3 Commits
a45dd2305e
...
8b8d58854f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b8d58854f | ||
|
|
5f27e2ea44 | ||
|
|
55e5cdcdb8 |
@@ -267,6 +267,14 @@ namespace EpinelPS.Data
|
|||||||
|
|
||||||
[LoadRecord("LoginEventTable.json", "Id")]
|
[LoadRecord("LoginEventTable.json", "Id")]
|
||||||
public readonly Dictionary<int, LoginEventRecord> LoginEventTable = [];
|
public readonly Dictionary<int, LoginEventRecord> LoginEventTable = [];
|
||||||
|
|
||||||
|
// Contents Shop Data Tables
|
||||||
|
[LoadRecord("ContentsShopTable.json", "Id")]
|
||||||
|
public readonly Dictionary<int, ContentsShopRecord> ContentsShopTable = [];
|
||||||
|
[LoadRecord("ContentsShopProductTable.json", "Id")]
|
||||||
|
public readonly Dictionary<int, ContentsShopProductRecord> ContentsShopProductTable = [];
|
||||||
|
[LoadRecord("ShopDiscountProbTable.json", "Id")]
|
||||||
|
public readonly Dictionary<int, ShopDiscountProbRecord> ShopDiscountProbTable = [];
|
||||||
|
|
||||||
// Event Dungeon data Table
|
// Event Dungeon data Table
|
||||||
[LoadRecord("EventDungeonTable.json", "Id")]
|
[LoadRecord("EventDungeonTable.json", "Id")]
|
||||||
@@ -277,6 +285,10 @@ namespace EpinelPS.Data
|
|||||||
public readonly Dictionary<int, EventDungeonSpotBattleRecord> EventDungeonSpotBattleTable = [];
|
public readonly Dictionary<int, EventDungeonSpotBattleRecord> EventDungeonSpotBattleTable = [];
|
||||||
[LoadRecord("EventDungeonDifficultTable.json", "Id")]
|
[LoadRecord("EventDungeonDifficultTable.json", "Id")]
|
||||||
public readonly Dictionary<int, EventDungeonDifficultRecord> EventDungeonDifficultTable = [];
|
public readonly Dictionary<int, EventDungeonDifficultRecord> EventDungeonDifficultTable = [];
|
||||||
|
[LoadRecord("EventStoryTable.json", "Id")]
|
||||||
|
public readonly Dictionary<int, EventStoryRecord> EventStoryTable = [];
|
||||||
|
[LoadRecord("AutoChargeTable.json", "Id")]
|
||||||
|
public readonly Dictionary<int, AutoChargeRecord> AutoChargeTable = [];
|
||||||
|
|
||||||
// Pass Data Tables
|
// Pass Data Tables
|
||||||
[LoadRecord("PassManagerTable.json", "Id")]
|
[LoadRecord("PassManagerTable.json", "Id")]
|
||||||
@@ -288,6 +300,12 @@ namespace EpinelPS.Data
|
|||||||
[LoadRecord("PassMissionTable.json", "Id")]
|
[LoadRecord("PassMissionTable.json", "Id")]
|
||||||
public readonly Dictionary<int, PassMissionRecord> PassMissionTable = [];
|
public readonly Dictionary<int, PassMissionRecord> PassMissionTable = [];
|
||||||
|
|
||||||
|
// Event Mission Data Tables
|
||||||
|
[LoadRecord("EventMissionListTable.json", "Id")]
|
||||||
|
public readonly Dictionary<int, EventMissionListRecord> EventMissionListTable = [];
|
||||||
|
[LoadRecord("EventMissionCategoryTable.json", "Id")]
|
||||||
|
public readonly Dictionary<int, EventMissionCategoryRecord> EventMissionCategoryTable = [];
|
||||||
|
|
||||||
// Daily Mission Event Data Tables
|
// Daily Mission Event Data Tables
|
||||||
[LoadRecord("DailyMissionEventSettingTable.json", "Id")]
|
[LoadRecord("DailyMissionEventSettingTable.json", "Id")]
|
||||||
public readonly Dictionary<int, DailyMissionEventSettingRecord_Raw> DailyMissionEventSettingTable = [];
|
public readonly Dictionary<int, DailyMissionEventSettingRecord_Raw> DailyMissionEventSettingTable = [];
|
||||||
|
|||||||
@@ -57,6 +57,12 @@ namespace EpinelPS.LobbyServer.Event
|
|||||||
var eventManagers = GameData.Instance.eventManagers.Values.ToList();
|
var eventManagers = GameData.Instance.eventManagers.Values.ToList();
|
||||||
foreach (var banner in lobbyPrivateBanners)
|
foreach (var banner in lobbyPrivateBanners)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// Get all events (including child events) associated with this banner
|
||||||
|
var events = GetEventData(banner, eventManagers);
|
||||||
|
log.Debug($"Banner EventId: {banner.EventId} has {events.Count} associated events: {JsonConvert.SerializeObject(events)}");
|
||||||
|
AddJoinedEvents(ref response, events);
|
||||||
|
|
||||||
// add gacha events
|
// add gacha events
|
||||||
List<EventSystemType> systemTypes = [EventSystemType.PickupGachaEvent, EventSystemType.BoxGachaEvent, EventSystemType.LoginEvent];
|
List<EventSystemType> systemTypes = [EventSystemType.PickupGachaEvent, EventSystemType.BoxGachaEvent, EventSystemType.LoginEvent];
|
||||||
List<NetEventData> gachaEvents = GetEventDataBySystemTypes(banner, eventManagers, systemTypes);
|
List<NetEventData> gachaEvents = GetEventDataBySystemTypes(banner, eventManagers, systemTypes);
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
using EpinelPS.Utils;
|
|
||||||
|
|
||||||
namespace EpinelPS.LobbyServer.Event
|
|
||||||
{
|
|
||||||
[PacketPath("/event/mission/getclear")]
|
|
||||||
public class GetClearedMissions : LobbyMsgHandler
|
|
||||||
{
|
|
||||||
protected override async Task HandleAsync()
|
|
||||||
{
|
|
||||||
ReqGetEventMissionClear req = await ReadData<ReqGetEventMissionClear>(); //has EventIdList
|
|
||||||
|
|
||||||
|
|
||||||
ResGetEventMissionClear response = new();
|
|
||||||
// response.ResGetEventMissionClear.Add(new NetEventMissionClearData(EventId = 0, EventMissionId = 0 , CreatedAt = 0));
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
await WriteDataAsync(response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
188
EpinelPS/LobbyServer/Event/Mission/EventMissionHelper.cs
Normal file
188
EpinelPS/LobbyServer/Event/Mission/EventMissionHelper.cs
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
|
||||||
|
using EpinelPS.Data;
|
||||||
|
using EpinelPS.Database;
|
||||||
|
using EpinelPS.Utils;
|
||||||
|
using Google.Protobuf.Collections;
|
||||||
|
using Google.Protobuf.WellKnownTypes;
|
||||||
|
using log4net;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using static ResGetEventMissionClearList.Types;
|
||||||
|
|
||||||
|
namespace EpinelPS.LobbyServer.Event.Mission
|
||||||
|
{
|
||||||
|
public static class EventMissionHelper
|
||||||
|
{
|
||||||
|
private static readonly ILog log = LogManager.GetLogger(typeof(EventMissionHelper));
|
||||||
|
|
||||||
|
public static RepeatedField<NetEventMissionClearData> GetCleared(User user, int eventId)
|
||||||
|
{
|
||||||
|
var clearData = new RepeatedField<NetEventMissionClearData>();
|
||||||
|
if (!user.EventMissionInfo.TryGetValue(eventId, out var userEvent)) return clearData;
|
||||||
|
log.Debug($"GetClear UserEvent: {JsonConvert.SerializeObject(userEvent)}");
|
||||||
|
int dateDay = user.GetDateDay();
|
||||||
|
// Check if it's a new day, reset daily missions
|
||||||
|
if (userEvent.LastDay != dateDay)
|
||||||
|
{
|
||||||
|
ResetUserDailyMission(user, eventId, dateDay);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var id in userEvent.DailyMissionIdList)
|
||||||
|
{
|
||||||
|
clearData.Add(new NetEventMissionClearData()
|
||||||
|
{
|
||||||
|
EventId = eventId,
|
||||||
|
EventMissionId = id,
|
||||||
|
CreatedAt = userEvent.LastDate
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var id in userEvent.MissionIdList)
|
||||||
|
{
|
||||||
|
clearData.Add(new NetEventMissionClearData()
|
||||||
|
{
|
||||||
|
EventId = eventId,
|
||||||
|
EventMissionId = id,
|
||||||
|
CreatedAt = userEvent.LastDate
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return clearData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RepeatedField<NestEventMissionClear> GetClearedList(User user, RepeatedField<int> eventIds)
|
||||||
|
{
|
||||||
|
var clearDatas = new RepeatedField<NestEventMissionClear>();
|
||||||
|
if (eventIds.Count == 0) return clearDatas;
|
||||||
|
foreach (var eventId in eventIds)
|
||||||
|
{
|
||||||
|
var clearData = new NestEventMissionClear
|
||||||
|
{
|
||||||
|
EventId = eventId
|
||||||
|
};
|
||||||
|
clearData.EventMissionClearList.AddRange(GetCleared(user, eventId));
|
||||||
|
}
|
||||||
|
|
||||||
|
return clearDatas;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Obtain reward for event mission
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user"></param>
|
||||||
|
/// <param name="reward"></param>
|
||||||
|
/// <param name="eventId"></param>
|
||||||
|
/// <param name="missionIds"></param>
|
||||||
|
/// <param name="timeStamp"></param>
|
||||||
|
public static void ObtainReward(User user, ref NetRewardData reward, int eventId, RepeatedField<int> missionIds, Timestamp timeStamp)
|
||||||
|
{
|
||||||
|
EventMissionData userEvent = GetUserEventMissionData(user, eventId);
|
||||||
|
log.Debug($"ObtainReward UserEvent Before: {JsonConvert.SerializeObject(userEvent)}");
|
||||||
|
|
||||||
|
int dateDay = user.GetDateDay();
|
||||||
|
// Check if it's a new day, reset daily missions
|
||||||
|
if (userEvent.LastDay != dateDay)
|
||||||
|
{
|
||||||
|
log.Debug($"ObtainReward New Day: {dateDay}");
|
||||||
|
ResetUserDailyMission(user, eventId, dateDay);
|
||||||
|
}
|
||||||
|
|
||||||
|
var userMissionIds = userEvent.MissionIdList ?? [];
|
||||||
|
var userDailyMissionIds = userEvent.DailyMissionIdList ?? [];
|
||||||
|
|
||||||
|
var eventMissionRecords = GameData.Instance.EventMissionListTable.Values.Where(em =>
|
||||||
|
missionIds.Contains(em.Id)
|
||||||
|
&& !userMissionIds.Contains(em.Id)
|
||||||
|
&& !userDailyMissionIds.Contains(em.Id)).ToList();
|
||||||
|
if (eventMissionRecords.Count == 0) return;
|
||||||
|
log.Debug($"ObtainReward Event Mission Records: {JsonConvert.SerializeObject(eventMissionRecords)}");
|
||||||
|
|
||||||
|
List<Reward_Data> rewards = [];
|
||||||
|
foreach (var mission in eventMissionRecords)
|
||||||
|
{
|
||||||
|
if (mission.RewardId == 0)
|
||||||
|
{
|
||||||
|
if (mission.RewardPointValue > 0)
|
||||||
|
{
|
||||||
|
user.AddTrigger(Trigger.PointRewardEvent, mission.RewardPointValue, mission.Group);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
user.AddTrigger(Trigger.MissionClearEvent, 1, mission.Group);
|
||||||
|
var rewardRecord = GameData.Instance.GetRewardTableEntry(mission.RewardId);
|
||||||
|
if (rewardRecord is null || rewardRecord.Rewards.Count == 0) continue;
|
||||||
|
foreach (var item in rewardRecord.Rewards)
|
||||||
|
{
|
||||||
|
var itemIndex = rewards.FindIndex(x => x.RewardId == item.RewardId);
|
||||||
|
if (itemIndex >= 0)
|
||||||
|
rewards[itemIndex].RewardValue += item.RewardValue;
|
||||||
|
else
|
||||||
|
rewards.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (rewards.Count == 0) return;
|
||||||
|
log.Debug($"ObtainReward Rewards: {JsonConvert.SerializeObject(rewards)}");
|
||||||
|
// Add rewards to user
|
||||||
|
foreach (var r in rewards)
|
||||||
|
{
|
||||||
|
RewardUtils.AddSingleObject(user, ref reward, r.RewardId, r.RewardType, r.RewardValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add mission ids to user
|
||||||
|
var groupIds = eventMissionRecords.Select(x => x.Group).Distinct();
|
||||||
|
var categoryRecords = GameData.Instance.EventMissionCategoryTable.Values.Where(ec => groupIds.Contains(ec.MissionListGroup)).ToList();
|
||||||
|
if (categoryRecords.Count == 0) return;
|
||||||
|
foreach (var mission in eventMissionRecords)
|
||||||
|
{
|
||||||
|
var categoryRecord = categoryRecords.FirstOrDefault(ec => ec.MissionListGroup == mission.Group);
|
||||||
|
if (categoryRecord is null) continue;
|
||||||
|
if (categoryRecord.InitType == EventMissionInitType.Daily)
|
||||||
|
{
|
||||||
|
userEvent.DailyMissionIdList.Add(mission.Id);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
userEvent.MissionIdList.Add(mission.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var item in reward.Item)
|
||||||
|
{
|
||||||
|
user.AddTrigger(Trigger.ObtainEventCurrencyMaterial, item.Count, item.Tid);
|
||||||
|
}
|
||||||
|
|
||||||
|
userEvent.LastDate = timeStamp.ToDateTime().Ticks;
|
||||||
|
user.EventMissionInfo[eventId] = userEvent;
|
||||||
|
log.Debug($"ObtainReward UserEvent After: {JsonConvert.SerializeObject(userEvent)}");
|
||||||
|
|
||||||
|
JsonDb.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static void ResetUserDailyMission(User user, int eventId, int dateDay)
|
||||||
|
{
|
||||||
|
if (!user.EventMissionInfo.TryGetValue(eventId, out var userEvent)) return;
|
||||||
|
if (userEvent.LastDay == dateDay) return;
|
||||||
|
user.EventMissionInfo[eventId].DailyMissionIdList = [];
|
||||||
|
user.EventMissionInfo[eventId].LastDay = dateDay;
|
||||||
|
JsonDb.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get user event mission data, if not exists, create a new one
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user">User</param>
|
||||||
|
/// <param name="eventId">EventId</param>
|
||||||
|
/// <returns>EventMissionData</returns>
|
||||||
|
private static EventMissionData GetUserEventMissionData(User user, int eventId)
|
||||||
|
{
|
||||||
|
// Get user event mission data, if not exists, create a new one
|
||||||
|
if (!user.EventMissionInfo.TryGetValue(eventId, out var userEvent))
|
||||||
|
{
|
||||||
|
userEvent = new EventMissionData();
|
||||||
|
user.EventMissionInfo.Add(eventId, userEvent);
|
||||||
|
}
|
||||||
|
return userEvent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
using EpinelPS.Utils;
|
|
||||||
|
|
||||||
namespace EpinelPS.LobbyServer.Event.Mission
|
|
||||||
{
|
|
||||||
[PacketPath("/event/mission/getclearlist")]
|
|
||||||
public class GetClearList : LobbyMsgHandler
|
|
||||||
{
|
|
||||||
protected override async Task HandleAsync()
|
|
||||||
{
|
|
||||||
ReqGetEventMissionClearList req = await ReadData<ReqGetEventMissionClearList>();
|
|
||||||
|
|
||||||
ResGetEventMissionClearList response = new(); //field ResGetEventMissionClearMap data type NestEventMissionClear field NestEventMissionClear data type NetEventMissionClearData fields EventId EventMissionId CreatedAt
|
|
||||||
|
|
||||||
// TOOD
|
|
||||||
|
|
||||||
await WriteDataAsync(response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
29
EpinelPS/LobbyServer/Event/Mission/GetMissionClear.cs
Normal file
29
EpinelPS/LobbyServer/Event/Mission/GetMissionClear.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
using EpinelPS.Utils;
|
||||||
|
|
||||||
|
namespace EpinelPS.LobbyServer.Event.Mission
|
||||||
|
{
|
||||||
|
[PacketPath("/event/mission/getclear")]
|
||||||
|
public class GetMissionClear : LobbyMsgHandler
|
||||||
|
{
|
||||||
|
|
||||||
|
protected override async Task HandleAsync()
|
||||||
|
{
|
||||||
|
var req = await ReadData<ReqGetEventMissionClear>(); //EventId
|
||||||
|
User user = GetUser();
|
||||||
|
|
||||||
|
ResGetEventMissionClear response = new();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
response.EventMissionClearList.AddRange(EventMissionHelper.GetCleared(user, req.EventId));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Warn($"GetMissionClear failed: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
EpinelPS/LobbyServer/Event/Mission/GetMissionClearList.cs
Normal file
27
EpinelPS/LobbyServer/Event/Mission/GetMissionClearList.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
using EpinelPS.Utils;
|
||||||
|
|
||||||
|
namespace EpinelPS.LobbyServer.Event.Mission
|
||||||
|
{
|
||||||
|
[PacketPath("/event/mission/getclearlist")]
|
||||||
|
public class GetMissionClearList : LobbyMsgHandler
|
||||||
|
{
|
||||||
|
protected override async Task HandleAsync()
|
||||||
|
{
|
||||||
|
// { "eventIdList": [ 60090, 60092, 20001, 20002 ] }
|
||||||
|
ReqGetEventMissionClearList req = await ReadData<ReqGetEventMissionClearList>();
|
||||||
|
User user = GetUser();
|
||||||
|
ResGetEventMissionClearList response = new();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
response.ResGetEventMissionClearMap.AddRange(EventMissionHelper.GetClearedList(user, req.EventIdList));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Warn($"GetMissionClearList failed: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
40
EpinelPS/LobbyServer/Event/Mission/ObtainMissionReward.cs
Normal file
40
EpinelPS/LobbyServer/Event/Mission/ObtainMissionReward.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using EpinelPS.Utils;
|
||||||
|
|
||||||
|
namespace EpinelPS.LobbyServer.Event.Mission
|
||||||
|
{
|
||||||
|
[PacketPath("/event/mission/reward")]
|
||||||
|
public class ObtainMissionReward : LobbyMsgHandler
|
||||||
|
{
|
||||||
|
protected override async Task HandleAsync()
|
||||||
|
{
|
||||||
|
// { "eventId": 20001, "dailyEventId": [ 200010105 ] }
|
||||||
|
// ReqObtainEventMissionReward Fields
|
||||||
|
// int EventId
|
||||||
|
// RepeatedField<int> EventMissionIdList
|
||||||
|
// Google.Protobuf.WellKnownTypes.Timestamp RequestTimeStamp
|
||||||
|
var req = await ReadData<ReqObtainEventMissionReward>();
|
||||||
|
User user = GetUser();
|
||||||
|
|
||||||
|
// ResObtainEventMissionReward Fields
|
||||||
|
// NetRewardData Reward
|
||||||
|
// ObtainEventMissionRewardResult Result
|
||||||
|
ResObtainEventMissionReward response = new()
|
||||||
|
{
|
||||||
|
Result = ObtainEventMissionRewardResult.Success
|
||||||
|
};
|
||||||
|
|
||||||
|
var reward = new NetRewardData();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
EventMissionHelper.ObtainReward(user, ref reward, req.EventId, req.EventMissionIdList, req.RequestTimeStamp);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.Warn($"ObtainMissionReward failed: {ex.Message}");
|
||||||
|
}
|
||||||
|
response.Reward = reward;
|
||||||
|
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
EpinelPS/LobbyServer/Event/Shop/BuyProduct.cs
Normal file
26
EpinelPS/LobbyServer/Event/Shop/BuyProduct.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using EpinelPS.Utils;
|
||||||
|
|
||||||
|
namespace EpinelPS.LobbyServer.Event.Shop
|
||||||
|
{
|
||||||
|
[PacketPath("/event/shopbuyproduct")]
|
||||||
|
public class BuyProduct : LobbyMsgHandler
|
||||||
|
{
|
||||||
|
protected override async Task HandleAsync()
|
||||||
|
{
|
||||||
|
// int EventId
|
||||||
|
var req = await ReadData<ReqEventShopBuyProduct>();
|
||||||
|
ResEventShopBuyProduct response = new();
|
||||||
|
User user = GetUser();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
EventShopHelper.BuyShopProduct(user, ref response, req);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.WriteLine($"Error buying shop product: {ex.Message}", LogType.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
495
EpinelPS/LobbyServer/Event/Shop/EventShopHelper.cs
Normal file
495
EpinelPS/LobbyServer/Event/Shop/EventShopHelper.cs
Normal file
@@ -0,0 +1,495 @@
|
|||||||
|
using EpinelPS.Data;
|
||||||
|
using EpinelPS.Database;
|
||||||
|
using EpinelPS.Utils;
|
||||||
|
using log4net;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace EpinelPS.LobbyServer.Event.Shop
|
||||||
|
{
|
||||||
|
public static class EventShopHelper
|
||||||
|
{
|
||||||
|
private static readonly ILog log = LogManager.GetLogger(typeof(EventShopHelper));
|
||||||
|
|
||||||
|
|
||||||
|
public static void BuyShopProduct(User user, ref ResEventShopBuyProduct response, ReqEventShopBuyProduct req)
|
||||||
|
{
|
||||||
|
ResEventShopMultipleBuyProduct MultipleResponse = new();
|
||||||
|
List<NetBuyProductRequestData> buyProducts = [new() { ShopProductTid = req.ShopProductTid, Quantity = req.Quantity, Order = req.Order }];
|
||||||
|
bool isSuccess = ExecuteBuyProduct(user, ref MultipleResponse, buyProducts);
|
||||||
|
|
||||||
|
if (!isSuccess) return;
|
||||||
|
AddEventShopBuyCount(user, ref MultipleResponse, req.EventId, req.ShopProductTid, req.Quantity, req.Order);
|
||||||
|
JsonDb.Save();
|
||||||
|
|
||||||
|
// Update currency data
|
||||||
|
if (MultipleResponse.Currencies.Count > 0)
|
||||||
|
response.Currencies.AddRange(MultipleResponse.Currencies);
|
||||||
|
|
||||||
|
// Update item data
|
||||||
|
if (MultipleResponse.Items.Count > 0)
|
||||||
|
response.Item = MultipleResponse.Items[0];
|
||||||
|
|
||||||
|
// Update product data
|
||||||
|
response.Product = new();
|
||||||
|
if (MultipleResponse.Product.UserItems.Count > 0) response.Product.UserItems.AddRange(MultipleResponse.Product.UserItems);
|
||||||
|
|
||||||
|
if (MultipleResponse.Product.Item.Count > 0) response.Product.Item.AddRange(MultipleResponse.Product.Item);
|
||||||
|
|
||||||
|
if (MultipleResponse.Product.Currency.Count > 0) response.Product.Currency.AddRange(MultipleResponse.Product.Currency);
|
||||||
|
|
||||||
|
if (MultipleResponse.Product.BuyCounts.Count > 0) response.Product.BuyCount = MultipleResponse.Product.BuyCounts[0].BuyCount;
|
||||||
|
|
||||||
|
if (MultipleResponse.Product.Character.Count > 0) response.Product.Character.AddRange(MultipleResponse.Product.Character);
|
||||||
|
|
||||||
|
if (MultipleResponse.Product.UserCharacters.Count > 0) response.Product.UserCharacters.AddRange(MultipleResponse.Product.UserCharacters);
|
||||||
|
|
||||||
|
if (MultipleResponse.Product.AutoCharge.Count > 0) response.Product.AutoCharge.AddRange(MultipleResponse.Product.AutoCharge);
|
||||||
|
|
||||||
|
// user.AddTrigger(Trigger.MainShopBuy, req.Quantity);
|
||||||
|
|
||||||
|
// Save changes to the database
|
||||||
|
JsonDb.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void BuyShopMultipleProduct(User user, ref ResEventShopMultipleBuyProduct response, ReqEventShopMultipleBuyProduct req)
|
||||||
|
{
|
||||||
|
bool isSuccess = ExecuteBuyProduct(user, ref response, [.. req.Products]);
|
||||||
|
|
||||||
|
if (!isSuccess) return;
|
||||||
|
foreach (var item in req.Products)
|
||||||
|
{
|
||||||
|
AddEventShopBuyCount(user, ref response, req.EventId, item.ShopProductTid, item.Quantity, item.Order);
|
||||||
|
}
|
||||||
|
JsonDb.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddEventShopBuyCount(User user, ref ResEventShopMultipleBuyProduct response, int eventId, int productTid, int quantity, int order)
|
||||||
|
{
|
||||||
|
if (!user.EventShopBuyCountInfo.TryGetValue(eventId, out var buyCountInfo))
|
||||||
|
{
|
||||||
|
buyCountInfo = new() { EventId = eventId, datas = [] };
|
||||||
|
user.EventShopBuyCountInfo.TryAdd(eventId, buyCountInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
var index = buyCountInfo.datas.FindIndex(x => x.ProductTid == productTid);
|
||||||
|
if (index >= 0)
|
||||||
|
{
|
||||||
|
buyCountInfo.datas[index].BuyCount += quantity;
|
||||||
|
response.Product.BuyCounts.Add(new NetBuyCountData { Order = order, BuyCount = buyCountInfo.datas[index].BuyCount });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
buyCountInfo.datas.Add(new() { ProductTid = productTid, BuyCount = quantity });
|
||||||
|
response.Product.BuyCounts.Add(new NetBuyCountData { Order = order, BuyCount = quantity });
|
||||||
|
}
|
||||||
|
user.EventShopBuyCountInfo[eventId] = buyCountInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ExecuteBuyProduct(User user, ref ResEventShopMultipleBuyProduct response, List<NetBuyProductRequestData> buyProducts)
|
||||||
|
{
|
||||||
|
if (buyProducts == null || buyProducts.Count == 0) return false;
|
||||||
|
|
||||||
|
response.Product = new();
|
||||||
|
|
||||||
|
var productTids = buyProducts.Select(p => p.ShopProductTid).ToList();
|
||||||
|
var shopProducts = GameData.Instance.ContentsShopProductTable.Values.Where(x => productTids.Contains(x.Id)).ToList();
|
||||||
|
|
||||||
|
// Check user currency and item balance
|
||||||
|
if (CheckUserCurrencyAndItemBalance(user, shopProducts, buyProducts, out Dictionary<int, int> totalCurrencyPrice, out Dictionary<int, int> totalItemPrice))
|
||||||
|
{
|
||||||
|
// Deduct user currency and item
|
||||||
|
DeductUserCurrencyAndItems(user, totalCurrencyPrice, totalItemPrice, ref response);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process each shopProduct
|
||||||
|
foreach (var shopProduct in shopProducts)
|
||||||
|
{
|
||||||
|
var buyProduct = buyProducts.FirstOrDefault(bp => bp.ShopProductTid == shopProduct.Id);
|
||||||
|
int quantity = buyProduct.Quantity;
|
||||||
|
int order = buyProduct.Order;
|
||||||
|
if (shopProduct.GoodsType == RewardType.Item || shopProduct.GoodsType.ToString().StartsWith("Equipment"))
|
||||||
|
{
|
||||||
|
AddItemById(user, ref response, itemId: shopProduct.GoodsId, RewardType.Item, shopProduct.GoodsValue, quantity, order);
|
||||||
|
}
|
||||||
|
else if (shopProduct.GoodsType == RewardType.Currency)
|
||||||
|
{
|
||||||
|
long val = shopProduct.GoodsValue * quantity;
|
||||||
|
user.AddCurrency((CurrencyType)shopProduct.GoodsId, val);
|
||||||
|
// buyCounts.Add(new() { Order = order, BuyCount = quantity });
|
||||||
|
response.Product.Currency.Add(new NetCurrencyData() { Type = shopProduct.GoodsId, Value = val, FinalValue = user.GetCurrencyVal((CurrencyType)shopProduct.GoodsId) });
|
||||||
|
}
|
||||||
|
else if (shopProduct.GoodsType == RewardType.Character)
|
||||||
|
{
|
||||||
|
AddCharacterByCharacterTid(user, ref response, shopProduct.GoodsId, shopProduct.GoodsValue, quantity, order);
|
||||||
|
}
|
||||||
|
else if (shopProduct.GoodsType == RewardType.UserTitle)
|
||||||
|
{
|
||||||
|
response.Product.UserTitleList.Add(shopProduct.GoodsId);
|
||||||
|
}
|
||||||
|
else if (shopProduct.GoodsType == RewardType.LiveWallpaper)
|
||||||
|
{
|
||||||
|
response.Product.LiveWallPapers.Add(shopProduct.GoodsId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logging.WriteLine($"Unsupported GoodsType: {shopProduct.GoodsType}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonDb.Save();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DeductUserCurrencyAndItems(User user, Dictionary<int, int> totalCurrencyPrice, Dictionary<int, int> totalItemPrice,
|
||||||
|
ref ResEventShopMultipleBuyProduct response)
|
||||||
|
{
|
||||||
|
foreach (int key in totalCurrencyPrice.Keys)
|
||||||
|
{
|
||||||
|
CurrencyType currencyType = (CurrencyType)key;
|
||||||
|
user.SubtractCurrency(currencyType, totalCurrencyPrice[key]);
|
||||||
|
response.Currencies.Add(new NetUserCurrencyData() { Type = key, Value = user.GetCurrencyVal(currencyType) });
|
||||||
|
}
|
||||||
|
foreach (int tid in totalItemPrice.Keys)
|
||||||
|
{
|
||||||
|
var item = user.Items.FirstOrDefault(i => i.ItemType == tid);
|
||||||
|
user.RemoveItemBySerialNumber(item.Isn, totalItemPrice[tid]);
|
||||||
|
response.Items.Add(new NetUserItemData() { Tid = tid, Count = user.Items.FirstOrDefault(i => i.ItemType == tid).Count, Isn = item.Isn });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool CheckUserCurrencyAndItemBalance(User user, List<ContentsShopProductRecord> shopProducts, List<NetBuyProductRequestData> buyProducts,
|
||||||
|
out Dictionary<int, int> totalCurrencyPrices, out Dictionary<int, int> totalItemPrices)
|
||||||
|
{
|
||||||
|
totalCurrencyPrices = shopProducts
|
||||||
|
.Where(sp => sp.PriceType == PriceType.Currency)
|
||||||
|
.GroupBy(sp => sp.PriceId)
|
||||||
|
.ToDictionary(
|
||||||
|
g => g.Key,
|
||||||
|
g => g.Sum(sp => sp.PriceValue * buyProducts.FirstOrDefault(bp => bp.ShopProductTid == sp.Id).Quantity)
|
||||||
|
);
|
||||||
|
|
||||||
|
totalItemPrices = shopProducts
|
||||||
|
.Where(sp => sp.PriceType == PriceType.Item)
|
||||||
|
.GroupBy(sp => sp.PriceId)
|
||||||
|
.ToDictionary(
|
||||||
|
g => g.Key,
|
||||||
|
g => g.Sum(sp => sp.PriceValue * buyProducts.FirstOrDefault(bp => bp.ShopProductTid == sp.Id).Quantity)
|
||||||
|
);
|
||||||
|
|
||||||
|
Logging.WriteLine($"totalCurrencyPrice: {JsonConvert.SerializeObject(totalCurrencyPrices)}", LogType.Debug);
|
||||||
|
foreach (int currencyType in totalCurrencyPrices.Keys)
|
||||||
|
{
|
||||||
|
var userCurrency = user.Currency.FirstOrDefault(x => x.Key == (CurrencyType)currencyType).Value;
|
||||||
|
if (userCurrency < totalCurrencyPrices[currencyType])
|
||||||
|
{
|
||||||
|
Logging.WriteLine($"Insufficient funds: Have {userCurrency}, need {totalCurrencyPrices[currencyType]}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Logging.WriteLine($"totalItemPrice: {JsonConvert.SerializeObject(totalItemPrices)}", LogType.Debug);
|
||||||
|
foreach (int tid in totalItemPrices.Keys)
|
||||||
|
{
|
||||||
|
var item = user.Items.FirstOrDefault(i => i.ItemType == tid);
|
||||||
|
if (item == null || item.Count < totalItemPrices[tid])
|
||||||
|
{
|
||||||
|
Logging.WriteLine($"Insufficient item funds: Have {item?.Count ?? 0}, need {totalItemPrices[tid]}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static int GetEventShopId(int eventId)
|
||||||
|
{
|
||||||
|
if (eventId <= 0) return 0;
|
||||||
|
|
||||||
|
if (GameData.Instance.eventManagers.TryGetValue(eventId, out var eventRecord))
|
||||||
|
{
|
||||||
|
if (eventRecord.EventShortcutId is not null && eventRecord.EventShortcutId != "")
|
||||||
|
{
|
||||||
|
return Convert.ToInt32(eventRecord.EventShortcutId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Warn($"EventManager not found for EventId: {eventId}");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize Shop Data
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="shopId">Shop ID</param>
|
||||||
|
/// <returns>Shop Data</returns>
|
||||||
|
public static NetEventShopProductData InitShopData(User user, int eventId)
|
||||||
|
{
|
||||||
|
NetEventShopProductData shop = new();
|
||||||
|
|
||||||
|
var shopId = GetEventShopId(eventId);
|
||||||
|
if (shopId <= 0) return shop;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!GameData.Instance.ContentsShopTable.TryGetValue(shopId, out var tableShop))
|
||||||
|
{
|
||||||
|
Logging.WriteLine($"Invalid shopId: {shopId}", LogType.Warning);
|
||||||
|
return shop;
|
||||||
|
}
|
||||||
|
|
||||||
|
var userBuyCounts = new List<EventShopProductData>();
|
||||||
|
if (user.EventShopBuyCountInfo.TryGetValue(eventId, out var userBuyCountInfo)) userBuyCounts = userBuyCountInfo.datas;
|
||||||
|
|
||||||
|
shop.ShopTid = tableShop.Id;
|
||||||
|
shop.ShopCategory = (int)tableShop.ShopCategory;
|
||||||
|
GameData.Instance.ContentsShopProductTable.Values
|
||||||
|
.Where(csp => csp.BundleId == tableShop.BundleId).ToList().ForEach(csp =>
|
||||||
|
{
|
||||||
|
var buyCount = userBuyCounts.FirstOrDefault(x => x.ProductTid == csp.Id)?.BuyCount ?? 0;
|
||||||
|
shop.List.Add(new NetShopProductInfoData
|
||||||
|
{
|
||||||
|
Order = csp.ProductOrder,
|
||||||
|
ProductId = csp.Id,
|
||||||
|
BuyLimitCount = csp.BuyLimitCount,
|
||||||
|
BuyCount = buyCount,
|
||||||
|
// Discount = csp.DiscountProbId,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return shop;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.WriteLine($"Error in InitShopData: {ex}");
|
||||||
|
return shop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<string> GetShopIds()
|
||||||
|
{
|
||||||
|
List<string> shopIds = [];
|
||||||
|
var ContentsShopTable = GameData.Instance.ContentsShopTable;
|
||||||
|
var eventShops = ContentsShopTable.Values.Where(s => s.ShopCategory == ShopCategoryType.ShopStoryEvent || s.ShopType == ShopType.EventShop).ToList();
|
||||||
|
foreach (var shop in eventShops)
|
||||||
|
{
|
||||||
|
shopIds.Add(shop.Id.ToString());
|
||||||
|
}
|
||||||
|
log.Debug($"Final list of shop IDs: {JsonConvert.SerializeObject(shopIds)}");
|
||||||
|
return shopIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool UpdateCurrency(User user, int priceId, int priceValue, int quantity, ref ResEventShopBuyProduct response)
|
||||||
|
{
|
||||||
|
long totalPrice = priceValue * quantity;
|
||||||
|
if (!user.Currency.TryGetValue((CurrencyType)priceId, out var currentAmount))
|
||||||
|
{
|
||||||
|
Logging.WriteLine($"Insufficient funds: Have {currentAmount}, need {totalPrice}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentAmount < totalPrice)
|
||||||
|
{
|
||||||
|
Logging.WriteLine($"Insufficient funds: Have {currentAmount}, need {totalPrice}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
CurrencyType currencyType = (CurrencyType)priceId; // Assuming PriceId maps directly to CurrencyType
|
||||||
|
long newAmount = currentAmount - totalPrice; // calculate new amount
|
||||||
|
user.Currency[currencyType] = newAmount; // update user currency
|
||||||
|
response.Currencies.Add(new NetUserCurrencyData // Update response currency
|
||||||
|
{
|
||||||
|
Type = (int)currencyType,
|
||||||
|
Value = newAmount
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool UpdateItem(User user, int priceId, int priceValue, int quantity, ref ResEventShopBuyProduct response)
|
||||||
|
{
|
||||||
|
var item = user.Items.FirstOrDefault(i => i.ItemType == priceId);
|
||||||
|
if (item == null || item.Count < quantity)
|
||||||
|
{
|
||||||
|
Logging.WriteLine($"Insufficient item funds: Have {item?.Count ?? 0}, need {priceValue * quantity}");
|
||||||
|
return false; // Not enough items
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
item.Count -= priceValue * quantity; // Deduct the item cost
|
||||||
|
if (item.Count <= 0)
|
||||||
|
{
|
||||||
|
user.Items.Remove(item); // Remove item if count is zero or less
|
||||||
|
}
|
||||||
|
// Update response items
|
||||||
|
response.Item = new()
|
||||||
|
{
|
||||||
|
Tid = item.ItemType,
|
||||||
|
Count = item.Count,
|
||||||
|
Isn = item.Isn
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddCharacterByCharacterTid(User user, ref ResEventShopMultipleBuyProduct response, int characterTid, int goodsValue, int quantity, int order)
|
||||||
|
{
|
||||||
|
// Get character data from GameData.Instance.CharacterTable
|
||||||
|
if (!GameData.Instance.CharacterTable.TryGetValue(characterTid, out var characterRecord))
|
||||||
|
{
|
||||||
|
return; // Character data not found, return
|
||||||
|
}
|
||||||
|
// Check if character already exists in user.Characters
|
||||||
|
var userCharacter = user.GetCharacter(characterTid);
|
||||||
|
bool isAddNewCharacter = userCharacter == null;
|
||||||
|
if (isAddNewCharacter)
|
||||||
|
{
|
||||||
|
// Add new character to user.Characters
|
||||||
|
userCharacter = new CharacterModel()
|
||||||
|
{
|
||||||
|
Csn = user.GenerateUniqueCharacterId(),
|
||||||
|
Grade = 1,
|
||||||
|
Tid = characterRecord.Id,
|
||||||
|
};
|
||||||
|
user.Characters.Add(userCharacter);
|
||||||
|
response.Product.UserCharacters.Add(ToNetUserCharacter(userCharacter));
|
||||||
|
}
|
||||||
|
NetCharacterData netCharacter = new() { Csn = userCharacter.Csn, Tid = userCharacter.Tid };
|
||||||
|
|
||||||
|
// Calculate character material num
|
||||||
|
int characterMaterialNum = isAddNewCharacter ? goodsValue * quantity - 1 : goodsValue * quantity;
|
||||||
|
if (characterMaterialNum > 0)
|
||||||
|
{
|
||||||
|
var currentOriginalRare = characterRecord.OriginalRare;
|
||||||
|
// Get max core num
|
||||||
|
int maxCoreNum = currentOriginalRare == OriginalRareType.SSR ? 11 : currentOriginalRare == OriginalRareType.SR ? 3 : 1;
|
||||||
|
|
||||||
|
// Get current core num
|
||||||
|
int currentCoreNum = currentOriginalRare == OriginalRareType.SSR ? userCharacter.Grade : currentOriginalRare == OriginalRareType.SR ? userCharacter.Grade % 100 : 1;
|
||||||
|
// If current core num is greater than max core num, set current core num to max core num
|
||||||
|
if (currentCoreNum > maxCoreNum) currentCoreNum = maxCoreNum;
|
||||||
|
int currentMaterialNum = user.Items.FirstOrDefault(x => x.ItemType == characterRecord.PieceId)?.Count ?? 0;
|
||||||
|
bool isAddMaterial = currentCoreNum < maxCoreNum;
|
||||||
|
int addMaterialNum = characterMaterialNum - currentMaterialNum;
|
||||||
|
int addCurrencyNum = 0;
|
||||||
|
bool isAddCurrency = currentCoreNum + addMaterialNum > maxCoreNum;
|
||||||
|
if (isAddCurrency)
|
||||||
|
{
|
||||||
|
int MaterialCurrencyNum = currentOriginalRare == OriginalRareType.SSR ? 6000 : currentOriginalRare == OriginalRareType.SR ? 200 : 150;
|
||||||
|
addCurrencyNum = (currentCoreNum + addMaterialNum - maxCoreNum) * MaterialCurrencyNum;
|
||||||
|
addMaterialNum = maxCoreNum - currentCoreNum;
|
||||||
|
}
|
||||||
|
Dictionary<CurrencyType, long> currency = [];
|
||||||
|
if (addCurrencyNum > 0)
|
||||||
|
{
|
||||||
|
netCharacter.CurrencyValue = addCurrencyNum;
|
||||||
|
user.AddCurrency(CurrencyType.DissolutionPoint, addCurrencyNum);
|
||||||
|
currency.Add(CurrencyType.DissolutionPoint, addCurrencyNum);
|
||||||
|
}
|
||||||
|
List<ItemData> items = [];
|
||||||
|
List<ItemData> userItems = [];
|
||||||
|
if (addMaterialNum > 0)
|
||||||
|
{
|
||||||
|
netCharacter.PieceCount = addMaterialNum;
|
||||||
|
AddItemById(user, ref response, characterRecord.PieceId, RewardType.Item, goodsValue, addMaterialNum, order);
|
||||||
|
}
|
||||||
|
response.Product.Character.Add(netCharacter);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddItemById(User user, ref ResEventShopMultipleBuyProduct response,
|
||||||
|
int itemId, RewardType itemType, int goodsValue, int quantity, int order)
|
||||||
|
{
|
||||||
|
var userItemIndex = user.Items.FindIndex(i => i.ItemType == itemId);
|
||||||
|
var isEquip = GameData.Instance.ItemEquipTable.TryGetValue(itemId, out var equip);
|
||||||
|
if (userItemIndex >= 0)
|
||||||
|
{
|
||||||
|
if (isEquip)
|
||||||
|
{
|
||||||
|
// the item is not stackable, we need to create new entries for each quantity
|
||||||
|
for (int i = 0; i < goodsValue * quantity; i++)
|
||||||
|
{
|
||||||
|
var (tid, pos, isn) = (itemId, GetItemPos(equip.ItemSubType), user.GenerateUniqueItemId());
|
||||||
|
ItemData newItem = new() { ItemType = tid, Count = 1, Position = pos, Isn = isn, Corp = GetEquipCorp(itemType) };
|
||||||
|
user.Items.Add(newItem);
|
||||||
|
response.Product.Item.Add(NetUtils.ItemDataToNet(newItem));
|
||||||
|
response.Product.UserItems.Add(NetUtils.UserItemDataToNet(newItem));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
user.Items[userItemIndex].Count += goodsValue * quantity;
|
||||||
|
response.Product.UserItems.Add(NetUtils.UserItemDataToNet(user.Items[userItemIndex]));
|
||||||
|
var (tid, count, isn) = (itemId, goodsValue * quantity, user.Items[userItemIndex].Isn);
|
||||||
|
bool isAddAutoCharge = AddAutoChargeByTid(ref response, itemId: itemId, value: count, finalValue: user.Items[userItemIndex].Count);
|
||||||
|
if (!isAddAutoCharge)
|
||||||
|
{
|
||||||
|
response.Product.Item.Add(new NetItemData() { Tid = tid, Count = count, Isn = isn });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var (tid, count, isn) = (itemId, goodsValue * quantity, user.GenerateUniqueItemId());
|
||||||
|
ItemData itemData = new() { ItemType = tid, Count = count, Isn = isn };
|
||||||
|
user.Items.Add(itemData);
|
||||||
|
response.Product.UserItems.Add(NetUtils.UserItemDataToNet(itemData));
|
||||||
|
bool isAddAutoCharge = AddAutoChargeByTid(ref response, itemId: itemId, value: count, finalValue: count);
|
||||||
|
if (!isAddAutoCharge)
|
||||||
|
{
|
||||||
|
response.Product.Item.Add(NetUtils.ItemDataToNet(itemData));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool AddAutoChargeByTid(ref ResEventShopMultipleBuyProduct response, int itemId, int value, int finalValue)
|
||||||
|
{
|
||||||
|
var autoCharge = GameData.Instance.AutoChargeTable.Values.FirstOrDefault(x => x.ItemId == itemId);
|
||||||
|
if (autoCharge is null) return false;
|
||||||
|
response.Product.AutoCharge.Add(new NetAutoChargeData() { AutoChargeId = autoCharge.Id, Value = value, FinalValue = finalValue });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int GetItemPos(ItemSubType subType)
|
||||||
|
{
|
||||||
|
return subType switch
|
||||||
|
{
|
||||||
|
ItemSubType.ModuleA => 0,
|
||||||
|
ItemSubType.ModuleB => 1,
|
||||||
|
ItemSubType.ModuleC => 2,
|
||||||
|
ItemSubType.ModuleD => 3,
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int GetEquipCorp(RewardType subType)
|
||||||
|
{
|
||||||
|
return subType switch
|
||||||
|
{
|
||||||
|
RewardType.EquipmentELYSION => 1,
|
||||||
|
RewardType.EquipmentMISSILIS => 2,
|
||||||
|
RewardType.EquipmentTETRA => 3,
|
||||||
|
RewardType.EquipmentPILGRIM => 4,
|
||||||
|
RewardType.EquipmentABNORMAL => 7,
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NetUserCharacterDefaultData ToNetUserCharacter(CharacterModel character)
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
CostumeId = character.CostumeId,
|
||||||
|
Csn = character.Csn,
|
||||||
|
Grade = character.Grade,
|
||||||
|
Lv = character.Level,
|
||||||
|
UltiSkillLv = character.UltimateLevel,
|
||||||
|
Skill1Lv = character.Skill1Lvl,
|
||||||
|
Skill2Lv = character.Skill2Lvl,
|
||||||
|
Tid = character.Tid,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
using EpinelPS.Utils;
|
|
||||||
|
|
||||||
namespace EpinelPS.LobbyServer.Event.Shop
|
|
||||||
{
|
|
||||||
[PacketPath("/event/shopproductlist")]
|
|
||||||
public class ListProductList : LobbyMsgHandler
|
|
||||||
{
|
|
||||||
protected override async Task HandleAsync()
|
|
||||||
{
|
|
||||||
ReqShopProductList req = await ReadData<ReqShopProductList>();
|
|
||||||
User user = GetUser();
|
|
||||||
|
|
||||||
ResShopProductList response = new();
|
|
||||||
response.Shops.Add(new NetShopProductData()
|
|
||||||
{
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO implement properly
|
|
||||||
|
|
||||||
await WriteDataAsync(response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
45
EpinelPS/LobbyServer/Event/Shop/MultipleBuyProduct.cs
Normal file
45
EpinelPS/LobbyServer/Event/Shop/MultipleBuyProduct.cs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
using EpinelPS.Utils;
|
||||||
|
|
||||||
|
namespace EpinelPS.LobbyServer.Event.Shop
|
||||||
|
{
|
||||||
|
[PacketPath("/event/shopmultiplebuyproduct")]
|
||||||
|
public class MultipleBuyProduct : LobbyMsgHandler
|
||||||
|
{
|
||||||
|
protected override async Task HandleAsync()
|
||||||
|
{
|
||||||
|
// ReqEventShopMultipleBuyProduct Fileds
|
||||||
|
// int EventId
|
||||||
|
// int ShopCategory
|
||||||
|
// RepeatedField<NetBuyProductRequestData> Products
|
||||||
|
// NetBuyProductRequestData Fileds
|
||||||
|
// int ShopProductTid
|
||||||
|
// int Order
|
||||||
|
// int Quantity
|
||||||
|
var req = await ReadData<ReqEventShopMultipleBuyProduct>();
|
||||||
|
|
||||||
|
// ResEventShopMultipleBuyProduct Fileds
|
||||||
|
// EventShopBuyProductResult Result
|
||||||
|
// NetShopBuyMultipleProductData Product
|
||||||
|
// RepeatedField<NetUserItemData> Items
|
||||||
|
// RepeatedField<NetUserCurrencyData> Currencies
|
||||||
|
ResEventShopMultipleBuyProduct response = new()
|
||||||
|
{
|
||||||
|
Result = EventShopBuyProductResult.Success
|
||||||
|
};
|
||||||
|
User user = GetUser();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
EventShopHelper.BuyShopMultipleProduct(user, ref response, req);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.WriteLine($"Error buying shop product: {ex.Message}", LogType.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
EpinelPS/LobbyServer/Event/Shop/ProductList.cs
Normal file
27
EpinelPS/LobbyServer/Event/Shop/ProductList.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
using EpinelPS.Utils;
|
||||||
|
|
||||||
|
namespace EpinelPS.LobbyServer.Event.Shop
|
||||||
|
{
|
||||||
|
[PacketPath("/event/shopproductlist")]
|
||||||
|
public class ProductList : LobbyMsgHandler
|
||||||
|
{
|
||||||
|
protected override async Task HandleAsync()
|
||||||
|
{
|
||||||
|
// int EventId
|
||||||
|
var req = await ReadData<ReqEventShopProductList>();
|
||||||
|
ResEventShopProductList response = new();
|
||||||
|
User user = GetUser();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
response.Shops.Add(EventShopHelper.InitShopData(user, req.EventId));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.WriteLine($"Get EventShopProductList Error: {ex.Message}", LogType.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
await WriteDataAsync(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,7 +25,7 @@ namespace EpinelPS.LobbyServer.Event.StoryEvent
|
|||||||
{
|
{
|
||||||
eventData.ClearedStages.Add(req.StageId);
|
eventData.ClearedStages.Add(req.StageId);
|
||||||
}
|
}
|
||||||
eventData.LastStage = req.StageId;
|
if (eventData.LastStage < req.StageId) eventData.LastStage = req.StageId;
|
||||||
eventData.Diff = difficultId;
|
eventData.Diff = difficultId;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -34,8 +34,15 @@ namespace EpinelPS.LobbyServer.Event.StoryEvent
|
|||||||
}
|
}
|
||||||
user.AddTrigger(Trigger.EventStageClear, 1, req.StageId);
|
user.AddTrigger(Trigger.EventStageClear, 1, req.StageId);
|
||||||
user.AddTrigger(Trigger.EventDungeonStageClear, 1, req.EventId);
|
user.AddTrigger(Trigger.EventDungeonStageClear, 1, req.EventId);
|
||||||
|
if (bonusReward.Item.Count > 0)
|
||||||
|
{
|
||||||
|
bonusReward.Item.ToList().ForEach(item =>
|
||||||
|
{
|
||||||
|
user.AddTrigger(Trigger.ObtainEventCurrencyMaterial, item.Count, item.Tid);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
response.RemainTicket = 4;
|
response.RemainTicket = EventStoryHelper.SubtractTicket(user, req.EventId, 1);
|
||||||
|
|
||||||
response.Reward = reward;
|
response.Reward = reward;
|
||||||
response.BonusReward = bonusReward;
|
response.BonusReward = bonusReward;
|
||||||
|
|||||||
139
EpinelPS/LobbyServer/Event/StoryEvent/EventStoryHelper.cs
Normal file
139
EpinelPS/LobbyServer/Event/StoryEvent/EventStoryHelper.cs
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
using EpinelPS.Data;
|
||||||
|
using EpinelPS.Utils;
|
||||||
|
using log4net;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace EpinelPS.LobbyServer.Event.StoryEvent
|
||||||
|
{
|
||||||
|
public static class EventStoryHelper
|
||||||
|
{
|
||||||
|
private static readonly ILog log = LogManager.GetLogger(typeof(EventStoryHelper));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get user remain ticket by event id
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user"></param>
|
||||||
|
/// <param name="eventId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static int GetTicket(User user, int eventId)
|
||||||
|
{
|
||||||
|
|
||||||
|
int freeTicket = 5; // Default ticket is 5
|
||||||
|
|
||||||
|
// Get user event data, if exists, get free ticket
|
||||||
|
if (user.EventInfo.TryGetValue(eventId, out var eventData))
|
||||||
|
{
|
||||||
|
freeTicket = eventData.FreeTicket;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get item ticket information and free ticket max
|
||||||
|
(ItemData itemTicket, int freeTicketMax) = GetItemTicket(user, eventId);
|
||||||
|
|
||||||
|
// Get remain item ticket
|
||||||
|
int remainItemTicket = itemTicket?.Count ?? 0;
|
||||||
|
|
||||||
|
// If free ticket is greater than free ticket max, set free ticket to free ticket max
|
||||||
|
if (freeTicket > freeTicketMax) freeTicket = freeTicketMax;
|
||||||
|
|
||||||
|
int dateDay = user.GetDateDay();
|
||||||
|
// If dateDay is greater than last day, update user free ticket and last day
|
||||||
|
if (dateDay > eventData.LastDay)
|
||||||
|
{
|
||||||
|
Logging.WriteLine($"GetTicket ResetFreeTicket DateDay: {dateDay}, LastDay: {eventData.LastDay}, FreeTicketMax: {freeTicketMax}", LogType.Debug);
|
||||||
|
freeTicket = freeTicketMax;
|
||||||
|
user.EventInfo[eventId].FreeTicket = freeTicket;
|
||||||
|
user.EventInfo[eventId].LastDay = dateDay;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remain ticket is free ticket + item ticket
|
||||||
|
int remainTicket = freeTicket + remainItemTicket;
|
||||||
|
|
||||||
|
Logging.WriteLine($"GetTicket FreeTicket: {freeTicket}, ItemTicket: {remainItemTicket}, RemainTicket: {remainTicket}", LogType.Debug);
|
||||||
|
return remainTicket;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Subtract user remaining ticket by event id and value
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user"></param>
|
||||||
|
/// <param name="eventId"></param>
|
||||||
|
/// <param name="val"></param>
|
||||||
|
/// <returns>remaining ticket</returns>
|
||||||
|
public static int SubtractTicket(User user, int eventId, int val)
|
||||||
|
{
|
||||||
|
int freeTicket = 5; // Default ticket is 5
|
||||||
|
|
||||||
|
// Get user event data, if exists, get free ticket
|
||||||
|
if (user.EventInfo.TryGetValue(eventId, out var eventData))
|
||||||
|
{
|
||||||
|
freeTicket = eventData.FreeTicket;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get item ticket information
|
||||||
|
(ItemData itemTicket, _) = GetItemTicket(user, eventId);
|
||||||
|
// Get remain item ticket
|
||||||
|
int remainItemTicket = itemTicket?.Count ?? 0;
|
||||||
|
|
||||||
|
// If free ticket is enough to subtract
|
||||||
|
if (freeTicket >= val)
|
||||||
|
{
|
||||||
|
freeTicket -= val;
|
||||||
|
user.EventInfo[eventId].FreeTicket = freeTicket;
|
||||||
|
|
||||||
|
int remainTicket = freeTicket + remainItemTicket;
|
||||||
|
Logging.WriteLine($"SubtractTicket Value: {val}, FreeTicket: {freeTicket}, ItemTicket: {remainItemTicket}, RemainTicket: {remainTicket}", LogType.Debug);
|
||||||
|
return remainTicket;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If free ticket is not enough to subtract, subtract free ticket and subtract item ticket
|
||||||
|
int SubtractItemTicket = val - freeTicket;
|
||||||
|
user.EventInfo[eventId].FreeTicket = 0;
|
||||||
|
if (itemTicket is not null)
|
||||||
|
{
|
||||||
|
user.RemoveItemBySerialNumber(itemTicket.Isn, SubtractItemTicket);
|
||||||
|
}
|
||||||
|
|
||||||
|
freeTicket = 0;
|
||||||
|
// Remain ticket is free ticket + item ticket
|
||||||
|
int remainTicket = freeTicket + remainItemTicket;
|
||||||
|
Logging.WriteLine($"SubtractTicket Value: {val}, FreeTicket: {freeTicket}, ItemTicket: {remainItemTicket}, RemainTicket: {remainTicket}", LogType.Debug);
|
||||||
|
return remainTicket;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get user item ticket and free ticket max by event id
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user"></param>
|
||||||
|
/// <param name="eventId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static (ItemData itemTicket, int freeTicketMax) GetItemTicket(User user, int eventId)
|
||||||
|
{
|
||||||
|
int freeTicketMax = 5; // Default free ticket max is 5
|
||||||
|
// Get event story data
|
||||||
|
var eventStory = GameData.Instance.EventStoryTable.Values.FirstOrDefault(x => x.EventId == eventId);
|
||||||
|
|
||||||
|
// If event story data is null or auto charge id is 0, return null and default free ticket max
|
||||||
|
if (eventStory is null || eventStory.AutoChargeId == 0) return (null, freeTicketMax);
|
||||||
|
log.Debug($"GetItemTicket EventId: {eventId}, EventStory: {JsonConvert.SerializeObject(eventStory)}");
|
||||||
|
|
||||||
|
// If auto charge data is null, return null and default free ticket max
|
||||||
|
if (!GameData.Instance.AutoChargeTable.TryGetValue(eventStory.AutoChargeId, out var autoCharge)) return (null, 5);
|
||||||
|
log.Debug($"GetItemTicket AutoChargeId: {eventStory.AutoChargeId}, AutoCharge: {JsonConvert.SerializeObject(autoCharge)}");
|
||||||
|
|
||||||
|
// If auto charge max is 0, return null and default free ticket max
|
||||||
|
if (autoCharge.AutoChargeMax == 0) return (null, freeTicketMax);
|
||||||
|
|
||||||
|
freeTicketMax = autoCharge.AutoChargeMax; // Set free ticket max to auto charge max
|
||||||
|
|
||||||
|
// Get user item
|
||||||
|
var userItem = user.Items.FirstOrDefault(x => x.ItemType == autoCharge.ItemId);
|
||||||
|
log.Debug($"GetItemTicket UserItem: {(userItem is not null ? JsonConvert.SerializeObject(userItem) : null)}");
|
||||||
|
return (userItem, freeTicketMax); // Return user item and free ticket max
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,7 +19,15 @@ namespace EpinelPS.LobbyServer.Event.StoryEvent
|
|||||||
ClearEventStageHelper.ClearStage(user, req.StageId, ref reward, ref bonusReward, 1, req.ClearCount); // always battleResult = 1 for fast clear
|
ClearEventStageHelper.ClearStage(user, req.StageId, ref reward, ref bonusReward, 1, req.ClearCount); // always battleResult = 1 for fast clear
|
||||||
|
|
||||||
user.AddTrigger(Trigger.EventDungeonStageClear, req.ClearCount, req.EventId);
|
user.AddTrigger(Trigger.EventDungeonStageClear, req.ClearCount, req.EventId);
|
||||||
response.RemainTicket = 4;
|
response.RemainTicket = EventStoryHelper.SubtractTicket(user, req.EventId, req.ClearCount);
|
||||||
|
|
||||||
|
if (bonusReward.Item.Count > 0)
|
||||||
|
{
|
||||||
|
bonusReward.Item.ToList().ForEach(item =>
|
||||||
|
{
|
||||||
|
user.AddTrigger(Trigger.ObtainEventCurrencyMaterial, item.Count, item.Tid);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
response.Reward = reward;
|
response.Reward = reward;
|
||||||
response.BonusReward = bonusReward;
|
response.BonusReward = bonusReward;
|
||||||
|
|||||||
@@ -12,16 +12,17 @@ namespace EpinelPS.LobbyServer.Event.StoryEvent
|
|||||||
ReqStoryDungeonEventData req = await ReadData<ReqStoryDungeonEventData>();
|
ReqStoryDungeonEventData req = await ReadData<ReqStoryDungeonEventData>();
|
||||||
User user = GetUser();
|
User user = GetUser();
|
||||||
|
|
||||||
|
// Get user event data, if not exist, create new one
|
||||||
if (!user.EventInfo.TryGetValue(req.EventId, out EventData? eventData))
|
if (!user.EventInfo.TryGetValue(req.EventId, out EventData? eventData))
|
||||||
{
|
{
|
||||||
eventData = new();
|
eventData = new() { LastDay = user.GetDateDay(), FreeTicket = 5};
|
||||||
|
user.EventInfo.TryAdd(req.EventId, eventData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ResStoryDungeonEventData response = new()
|
ResStoryDungeonEventData response = new()
|
||||||
{
|
{
|
||||||
RemainTicket = 5,
|
RemainTicket = EventStoryHelper.GetTicket(user, req.EventId),
|
||||||
TeamData = new NetUserTeamData
|
TeamData = new NetUserTeamData
|
||||||
{
|
{
|
||||||
Type = (int)TeamType.StoryEvent
|
Type = (int)TeamType.StoryEvent
|
||||||
|
|||||||
@@ -60,6 +60,9 @@ namespace EpinelPS.LobbyServer.Inventory
|
|||||||
// we NEED to make sure the target item itself is in the delta list, or the UI won't update!
|
// we NEED to make sure the target item itself is in the delta list, or the UI won't update!
|
||||||
response.Items.Add(NetUtils.ToNet(destItem));
|
response.Items.Add(NetUtils.ToNet(destItem));
|
||||||
|
|
||||||
|
// Add trigger for equipment level count - 升级装备次数
|
||||||
|
user.AddTrigger(Trigger.EquipItemLevelCount, 1);
|
||||||
|
|
||||||
JsonDb.Save();
|
JsonDb.Save();
|
||||||
|
|
||||||
await WriteDataAsync(response);
|
await WriteDataAsync(response);
|
||||||
|
|||||||
@@ -85,6 +85,8 @@ namespace EpinelPS.Models
|
|||||||
public List<int> ClearedStages = []; // List of cleared stage IDs
|
public List<int> ClearedStages = []; // List of cleared stage IDs
|
||||||
public int Diff = 0; // Default value for Diff
|
public int Diff = 0; // Default value for Diff
|
||||||
public int LastStage = 0; // Default value for LastStage
|
public int LastStage = 0; // Default value for LastStage
|
||||||
|
public int LastDay = 0;
|
||||||
|
public int FreeTicket = 0;
|
||||||
}
|
}
|
||||||
public class LoginEventData
|
public class LoginEventData
|
||||||
{
|
{
|
||||||
@@ -92,6 +94,26 @@ namespace EpinelPS.Models
|
|||||||
public int LastDay = 0; // Default value for LastDay
|
public int LastDay = 0; // Default value for LastDay
|
||||||
public long LastDate = 0; // Default value for LastDate
|
public long LastDate = 0; // Default value for LastDate
|
||||||
}
|
}
|
||||||
|
// EventMissionData
|
||||||
|
public class EventMissionData
|
||||||
|
{
|
||||||
|
public List<int> MissionIdList = [];
|
||||||
|
public List<int> DailyMissionIdList = [];
|
||||||
|
public int LastDay = 0; // yyyyMMdd
|
||||||
|
public long LastDate = 0; // Default value for LastDate
|
||||||
|
}
|
||||||
|
// EventShopBuyCountData
|
||||||
|
public class EventShopProductData
|
||||||
|
{
|
||||||
|
public int ProductTid = 0;
|
||||||
|
public int BuyCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EventShopBuyCountData
|
||||||
|
{
|
||||||
|
public int EventId = 0;
|
||||||
|
public List<EventShopProductData> datas = [];
|
||||||
|
}
|
||||||
|
|
||||||
public class SynchroSlot
|
public class SynchroSlot
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -116,6 +116,8 @@ public class User
|
|||||||
// Event data
|
// Event data
|
||||||
public Dictionary<int, EventData> EventInfo = [];
|
public Dictionary<int, EventData> EventInfo = [];
|
||||||
public Dictionary<int, LoginEventData> LoginEventInfo = [];
|
public Dictionary<int, LoginEventData> LoginEventInfo = [];
|
||||||
|
public Dictionary<int, EventMissionData> EventMissionInfo = []; // key: eventId
|
||||||
|
public Dictionary<int, EventShopBuyCountData> EventShopBuyCountInfo = []; // key: eventId
|
||||||
public MogMinigameInfo MogInfo = new();
|
public MogMinigameInfo MogInfo = new();
|
||||||
public List<NetPlaySodaEachGameInfo> ArcadePlaySodaInfoList = [];
|
public List<NetPlaySodaEachGameInfo> ArcadePlaySodaInfoList = [];
|
||||||
public NetArcadeMvgData ArcadeInTheMirrorData = new();
|
public NetArcadeMvgData ArcadeInTheMirrorData = new();
|
||||||
@@ -500,4 +502,10 @@ public class User
|
|||||||
JsonDb.Save();
|
JsonDb.Save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public int GetDateDay()
|
||||||
|
{
|
||||||
|
// +4 每天4点重新计算 yyyyMMdd
|
||||||
|
DateTime dateTime = DateTime.UtcNow.AddHours(4);
|
||||||
|
return dateTime.Year * 10000 + dateTime.Month * 100 + dateTime.Day;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -278,7 +278,7 @@ namespace EpinelPS.Utils
|
|||||||
{
|
{
|
||||||
// Update the character's Tid and Grade
|
// Update the character's Tid and Grade
|
||||||
character.Tid = newCharData.Id;
|
character.Tid = newCharData.Id;
|
||||||
character.Grade = inputGrade;
|
character.Grade = newGradeCoreId;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -299,7 +299,7 @@ namespace EpinelPS.Utils
|
|||||||
{
|
{
|
||||||
// Update the character's Tid and Grade
|
// Update the character's Tid and Grade
|
||||||
character.Tid = newCharData.Id;
|
character.Tid = newCharData.Id;
|
||||||
character.Grade = inputGrade;
|
character.Grade = newGradeCoreId;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,17 @@ namespace EpinelPS.Utils
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static NetItemData ItemDataToNet(ItemData item)
|
||||||
|
{
|
||||||
|
return new NetItemData()
|
||||||
|
{
|
||||||
|
Count = item.Count,
|
||||||
|
Tid = item.ItemType,
|
||||||
|
Corporation = item.Corp,
|
||||||
|
Isn = item.Isn,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static List<NetUserItemData> GetUserItems(User user)
|
public static List<NetUserItemData> GetUserItems(User user)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ namespace EpinelPS.Utils
|
|||||||
{
|
{
|
||||||
AddSingleCurrencyObject(user, ref ret, (CurrencyType)rewardId, rewardCount);
|
AddSingleCurrencyObject(user, ref ret, (CurrencyType)rewardId, rewardCount);
|
||||||
}
|
}
|
||||||
else if (rewardType == RewardType.Item ||rewardType.ToString().StartsWith("Equipment"))
|
else if (rewardType == RewardType.Item || rewardType.ToString().StartsWith("Equipment"))
|
||||||
{
|
{
|
||||||
|
|
||||||
int corpId = 0; // Default to 0 (None)
|
int corpId = 0; // Default to 0 (None)
|
||||||
@@ -211,11 +211,8 @@ namespace EpinelPS.Utils
|
|||||||
}
|
}
|
||||||
else if (rewardType == RewardType.Bgm)
|
else if (rewardType == RewardType.Bgm)
|
||||||
{
|
{
|
||||||
if (!user.JukeboxBgm.Contains(rewardId))
|
if (!user.JukeboxBgm.Contains(rewardId)) user.JukeboxBgm.Add(rewardId);
|
||||||
{
|
ret.JukeboxBgm.Add(rewardId);
|
||||||
ret.JukeboxBgm.Add(rewardId);
|
|
||||||
user.JukeboxBgm.Add(rewardId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (rewardType == RewardType.InfraCoreExp)
|
else if (rewardType == RewardType.InfraCoreExp)
|
||||||
{
|
{
|
||||||
|
|||||||
99
ServerSelector/PathUtil.cs
Normal file
99
ServerSelector/PathUtil.cs
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace ServerSelector;
|
||||||
|
|
||||||
|
public class PathUtil
|
||||||
|
{
|
||||||
|
public bool LauncherExists { get; private set; }
|
||||||
|
public string? LauncherBasePath { get; set; }
|
||||||
|
public string GameBasePath { get; set; }
|
||||||
|
public string? SystemHostsFile
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "Drivers\\etc\\hosts");
|
||||||
|
}
|
||||||
|
else if (OperatingSystem.IsLinux())
|
||||||
|
{
|
||||||
|
return "/etc/hosts";
|
||||||
|
}
|
||||||
|
else throw new NotImplementedException("Unsupported operating system");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string? WineHostsFile => GameBasePath + "../../wine_prefix/drive_c/windows/system32/drivers/etc/hosts";
|
||||||
|
|
||||||
|
public string? LauncherCertificatePath
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (LauncherBasePath == null) return null;
|
||||||
|
|
||||||
|
string path = Path.Combine(LauncherBasePath, "intl_service/intl_cacert.pem");
|
||||||
|
|
||||||
|
if (!File.Exists(path))
|
||||||
|
{
|
||||||
|
// Older game/SDK version
|
||||||
|
path = Path.Combine(LauncherBasePath, "intl_service/cacert.pem");
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GamePluginsDirectory => Path.Combine(GameBasePath ?? throw new InvalidOperationException("Game path not assigned"), "nikke_Data/Plugins/x86_64/");
|
||||||
|
public string? GameCertificatePath
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
string path = Path.Combine(GamePluginsDirectory, "intl_cacert.pem");
|
||||||
|
|
||||||
|
if (!File.Exists(path))
|
||||||
|
{
|
||||||
|
// Older game/SDK version
|
||||||
|
path = Path.Combine(GamePluginsDirectory, "cacert.pem");
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string? GameSodiumPath => Path.Combine(GamePluginsDirectory, "sodium.dll");
|
||||||
|
public string? GameSodiumBackupPath => Path.Combine(GamePluginsDirectory, "sodium.dll.bak");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the directory where the (game name) and Launcher directories are located
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="basePath">directory where the (game name) and Launcher directories are located</param>
|
||||||
|
/// <returns>Return (bool, string) where if the operation is successful, true is returned. If it fails, the string contains more details.</returns>
|
||||||
|
public (bool, string?) SetBasePath(string basePath)
|
||||||
|
{
|
||||||
|
GameBasePath = Path.Combine(basePath, "NIKKE", "game");
|
||||||
|
LauncherBasePath = Path.Combine(basePath, "Launcher");
|
||||||
|
|
||||||
|
// Various sanity checks
|
||||||
|
if (!Directory.Exists(GameBasePath))
|
||||||
|
{
|
||||||
|
return (false, $"Directory \"{GameBasePath}\" does not exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
LauncherExists = Directory.Exists(LauncherBasePath);
|
||||||
|
|
||||||
|
if (LauncherExists)
|
||||||
|
{
|
||||||
|
if (!File.Exists(Path.Combine(LauncherBasePath, "nikke_launcher.exe")))
|
||||||
|
{
|
||||||
|
return (false, "Game path is invalid. Make sure that nikke_launcher.exe exists in the <Game Path>/launcher folder");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!File.Exists(GameCertificatePath))
|
||||||
|
{
|
||||||
|
return (false, $"Path is invalid. File \"{GameCertificatePath}\" does not exist.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (true, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,164 +0,0 @@
|
|||||||
// From: https://github.com/Ninka-Rex/CSharp-Search-and-Replace/blob/main/SearchReplace.cs
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace ServerSelector;
|
|
||||||
|
|
||||||
public static class PatchUtility
|
|
||||||
{
|
|
||||||
public static bool CanFindOffset(string filePath, string[] searchPatterns)
|
|
||||||
{
|
|
||||||
// Check if the file exists
|
|
||||||
if (!File.Exists(filePath)) { Console.WriteLine("[ERROR] File not found: " + filePath); return false; }
|
|
||||||
|
|
||||||
// Read the binary data from the file as an array of bytes
|
|
||||||
byte[] fileData = File.ReadAllBytes(filePath);
|
|
||||||
|
|
||||||
for (int k = 0; k < searchPatterns.Length; k++)
|
|
||||||
{
|
|
||||||
// Convert the hexadecimal strings to byte arrays using a helper method
|
|
||||||
byte[] searchBytes = HexStringToBytes(searchPatterns[k]);
|
|
||||||
|
|
||||||
// Find the index of the first occurrence of the search pattern in the file data using another helper method
|
|
||||||
List<int> results = FindPatternIndex(fileData, searchBytes);
|
|
||||||
if (results.Count <= 1) return false;
|
|
||||||
|
|
||||||
Console.WriteLine("offset: " + results[1].ToString("X"));
|
|
||||||
|
|
||||||
// If the index is -1, it means the pattern was not found, so we return false and log an error message
|
|
||||||
if (results[1] == -1)
|
|
||||||
{
|
|
||||||
Console.WriteLine("[ERROR] Search pattern not found: " + searchPatterns[k]);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// This method searches and replaces binary patterns in a given file
|
|
||||||
// It takes three parameters:
|
|
||||||
// - filePath: the path of the file to be patched
|
|
||||||
// - searchPatterns: an array of hexadecimal strings representing the patterns to be searched for
|
|
||||||
// - replacePatterns: an array of hexadecimal strings representing the patterns to be replaced with
|
|
||||||
// It returns true if the patching was successful, or false if there was an error or a pattern was not found
|
|
||||||
public static bool SearchAndReplace(string filePath, string[] searchPatterns, string[] replacePatterns)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Check if the file exists
|
|
||||||
if (!File.Exists(filePath)) { Console.WriteLine("[ERROR] File not found: " + filePath); return false; }
|
|
||||||
|
|
||||||
// Read the binary data from the file as an array of bytes
|
|
||||||
byte[] fileData = File.ReadAllBytes(filePath);
|
|
||||||
|
|
||||||
// Backup the original file by copying it to a new file with a .bak extension
|
|
||||||
string backupFilePath = filePath + ".bak";
|
|
||||||
if (!File.Exists(backupFilePath))
|
|
||||||
{
|
|
||||||
File.Copy(filePath, backupFilePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loop through each pair of search and replace patterns and apply them to the file data
|
|
||||||
for (int k = 0; k < searchPatterns.Length; k++)
|
|
||||||
{
|
|
||||||
// Convert the hexadecimal strings to byte arrays using a helper method
|
|
||||||
byte[] searchBytes = HexStringToBytes(searchPatterns[k]);
|
|
||||||
byte[] replaceBytes = HexStringToBytes(replacePatterns[k]);
|
|
||||||
|
|
||||||
// Find the index of the first occurrence of the search pattern in the file data using another helper method
|
|
||||||
|
|
||||||
// Note: game versions 124 - 133 have 2 matches, 2nd one is the correct one
|
|
||||||
// game version 134 - ? has 1 match which is correct
|
|
||||||
int index = -1;
|
|
||||||
List<int> indexes = FindPatternIndex(fileData, searchBytes);
|
|
||||||
if (indexes.Count == 1)
|
|
||||||
index = indexes[0];
|
|
||||||
else index = indexes[1];
|
|
||||||
|
|
||||||
Console.WriteLine("Found offset: " + index.ToString("X"));
|
|
||||||
|
|
||||||
// If the index is -1, it means the pattern was not found, so we return false and log an error message
|
|
||||||
if (index == -1)
|
|
||||||
{
|
|
||||||
Console.WriteLine("[ERROR] Search pattern not found: " + searchPatterns[k]);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace the pattern at the found index with the replace pattern, preserving original values when wildcards are encountered
|
|
||||||
// A wildcard is represented by either 00 or FF in the replace pattern, meaning that we keep the original value at that position
|
|
||||||
for (int i = 0; i < replaceBytes.Length; i++)
|
|
||||||
{
|
|
||||||
if (replaceBytes[i] != 0x00 && replaceBytes[i] != 0xFF)
|
|
||||||
{
|
|
||||||
fileData[index + i] = replaceBytes[i];
|
|
||||||
}
|
|
||||||
else if (replaceBytes[i] == 0x00)
|
|
||||||
{
|
|
||||||
fileData[index + i] = 0x00;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log a success message with the offset and file name where the patch was applied
|
|
||||||
string exeName = Path.GetFileName(filePath);
|
|
||||||
Console.WriteLine($"[Patch] Apply patch success at 0x{index:X} in {exeName}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the modified data back to the file, overwriting the original content
|
|
||||||
File.WriteAllBytes(filePath, fileData);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// If any exception occurs during the patching process, we return false and log an error message with the exception details
|
|
||||||
Console.WriteLine("[ERROR] An error occurred while writing the file: " + ex.Message);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This helper method converts a hexadecimal string to a byte array
|
|
||||||
// It takes one parameter:
|
|
||||||
// - hex: a string of hexadecimal digits, optionally separated by spaces or question marks
|
|
||||||
// It returns a byte array corresponding to the hexadecimal values in the string
|
|
||||||
private static byte[] HexStringToBytes(string hex)
|
|
||||||
{
|
|
||||||
hex = hex.Replace(" ", "").Replace("??", "FF"); // Replace ?? with FF for wildcards
|
|
||||||
return [.. Enumerable.Range(0, hex.Length)
|
|
||||||
.Where(x => x % 2 == 0) // Take every second character in the string
|
|
||||||
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))]; // Convert the result to an array of bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
// This helper method finds the index of the first occurrence of a pattern in a data array
|
|
||||||
// It takes two parameters:
|
|
||||||
// - data: an array of bytes representing the data to be searched in
|
|
||||||
// - pattern: an array of bytes representing the pattern to be searched for
|
|
||||||
// It returns an integer representing the index where the pattern was found, or -1 if it was not found
|
|
||||||
private static List<int> FindPatternIndex(byte[] data, byte[] pattern)
|
|
||||||
{
|
|
||||||
List<int> points = [];
|
|
||||||
// Loop through each possible position in the data array where the pattern could start
|
|
||||||
for (int i = 0; i < data.Length - pattern.Length + 1; i++)
|
|
||||||
{
|
|
||||||
bool found = true; // Assume that the pattern is found until proven otherwise
|
|
||||||
// Loop through each byte in the pattern and compare it with the corresponding byte in the data array
|
|
||||||
for (int j = 0; j < pattern.Length; j++)
|
|
||||||
{
|
|
||||||
// If the pattern byte is not FF (wildcard) and it does not match the data byte, then the pattern is not found at this position
|
|
||||||
if (pattern[j] != 0xFF && data[i + j] != pattern[j])
|
|
||||||
{
|
|
||||||
found = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If the pattern was found at this position, return the index
|
|
||||||
if (found)
|
|
||||||
{
|
|
||||||
points.Add(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If the pattern was not found in the entire data array, return -1
|
|
||||||
return points;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,210 +4,174 @@ using System.IO;
|
|||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace ServerSelector
|
namespace ServerSelector;
|
||||||
|
|
||||||
|
public class ServerSwitcher
|
||||||
{
|
{
|
||||||
public class ServerSwitcher
|
private const string HostsStartMarker = "# begin ServerSelector entries";
|
||||||
|
private const string HostsEndMarker = "# end ServerSelector entries";
|
||||||
|
|
||||||
|
private static PathUtil util = new();
|
||||||
|
|
||||||
|
public static bool IsUsingLocalServer()
|
||||||
{
|
{
|
||||||
private const string HostsStartMarker = "# begin ServerSelector entries";
|
return File.ReadAllText(util.SystemHostsFile).Contains("global-lobby.nikke-kr.com");
|
||||||
private const string HostsEndMarker = "# end ServerSelector entries";
|
}
|
||||||
|
|
||||||
public static bool IsUsingOfficalServer()
|
public static bool IsOffline()
|
||||||
|
{
|
||||||
|
return File.ReadAllText(util.SystemHostsFile).Contains("cloud.nikke-kr.com");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static (bool, string?) SetBasePath(string basePath)
|
||||||
|
{
|
||||||
|
return util.SetBasePath(basePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<string> CheckIntegrity()
|
||||||
|
{
|
||||||
|
if (!IsUsingLocalServer())
|
||||||
|
return "Official server";
|
||||||
|
|
||||||
|
if (File.Exists(util.LauncherCertificatePath))
|
||||||
{
|
{
|
||||||
string hostsFile = File.ReadAllText(OperatingSystem.IsWindows() ? "C:\\Windows\\System32\\drivers\\etc\\hosts" : "/etc/hosts");
|
string certList1 = await File.ReadAllTextAsync(util.LauncherCertificatePath);
|
||||||
return !hostsFile.Contains("global-lobby.nikke-kr.com");
|
|
||||||
|
if (!certList1.Contains("Good SSL Ca"))
|
||||||
|
return "SSL Cert Patch missing Launcher";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsOffline()
|
if (File.Exists(util.GameCertificatePath))
|
||||||
{
|
{
|
||||||
string hostsFile = File.ReadAllText(OperatingSystem.IsWindows() ? "C:\\Windows\\System32\\drivers\\etc\\hosts" : "/etc/hosts");
|
string certList2 = await File.ReadAllTextAsync(util.GameCertificatePath);
|
||||||
return hostsFile.Contains("cloud.nikke-kr.com");
|
|
||||||
|
if (!certList2.Contains("Good SSL Ca"))
|
||||||
|
return "SSL Cert Patch missing Game";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<string> CheckIntegrity(string gamePath, string launcherPath)
|
// TODO: Check sodium lib
|
||||||
|
// TODO: check hosts file
|
||||||
|
|
||||||
|
return "OK";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task RevertHostsFile(string hostsFilePath)
|
||||||
|
{
|
||||||
|
string txt = await File.ReadAllTextAsync(hostsFilePath);
|
||||||
|
|
||||||
|
// remove stuff
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (IsUsingOfficalServer())
|
|
||||||
return "Official server";
|
|
||||||
|
|
||||||
if (!Directory.Exists(gamePath))
|
int startIdx = txt.IndexOf(HostsStartMarker);
|
||||||
|
int endIdx;
|
||||||
|
if (startIdx == -1)
|
||||||
{
|
{
|
||||||
return "Game path does not exist";
|
startIdx = txt.IndexOf("cloud.nikke-kr.com");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Directory.Exists(launcherPath))
|
string endIndexStr = HostsEndMarker;
|
||||||
|
if (!txt.Contains(endIndexStr))
|
||||||
{
|
{
|
||||||
return "Launcher path does not exist";
|
// old code, find new line character before start index
|
||||||
}
|
for (int i = startIdx - 1; i >= 0; i--)
|
||||||
|
|
||||||
if (!File.Exists(Path.Combine(launcherPath, "nikke_launcher.exe")))
|
|
||||||
{
|
|
||||||
return "Launcher path is invalid. Make sure that the game executable exists in the launcher folder";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// TODO fix this mess
|
|
||||||
string launcherCertList = launcherPath + "/intl_service/intl_cacert.pem";
|
|
||||||
if (!File.Exists(launcherCertList))
|
|
||||||
launcherCertList = launcherPath + "/intl_service/cacert.pem"; // older INTL sdk versions
|
|
||||||
string gameCertList = gamePath + "/nikke_Data/Plugins/x86_64/intl_cacert.pem";
|
|
||||||
if (!File.Exists(gameCertList))
|
|
||||||
gameCertList = gamePath + "/nikke_Data/Plugins/x86_64/cacert.pem"; // older INTL sdk versions
|
|
||||||
|
|
||||||
if (File.Exists(launcherCertList))
|
|
||||||
{
|
|
||||||
string certList1 = await File.ReadAllTextAsync(launcherCertList);
|
|
||||||
|
|
||||||
if (!certList1.Contains("Good SSL Ca"))
|
|
||||||
return "SSL Cert Patch missing";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (File.Exists(gameCertList))
|
|
||||||
{
|
|
||||||
string certList2 = await File.ReadAllTextAsync(gameCertList);
|
|
||||||
|
|
||||||
if (!certList2.Contains("Good SSL Ca"))
|
|
||||||
return "SSL Cert Patch missing";
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Check sodium lib
|
|
||||||
// TODO: check hosts file
|
|
||||||
|
|
||||||
return "OK";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task RevertHostsFile(string hostsFilePath)
|
|
||||||
{
|
|
||||||
string txt = await File.ReadAllTextAsync(hostsFilePath);
|
|
||||||
|
|
||||||
// remove stuff
|
|
||||||
try
|
|
||||||
{
|
|
||||||
|
|
||||||
int startIdx = txt.IndexOf(HostsStartMarker);
|
|
||||||
int endIdx;
|
|
||||||
if (startIdx == -1)
|
|
||||||
{
|
{
|
||||||
startIdx = txt.IndexOf("cloud.nikke-kr.com");
|
char c = txt[i];
|
||||||
}
|
if (c == '\n')
|
||||||
|
|
||||||
string endIndexStr = HostsEndMarker;
|
|
||||||
if (!txt.Contains(endIndexStr))
|
|
||||||
{
|
|
||||||
// old code, find new line character before start index
|
|
||||||
for (int i = startIdx - 1; i >= 0; i--)
|
|
||||||
{
|
{
|
||||||
char c = txt[i];
|
startIdx = i + 1;
|
||||||
if (c == '\n')
|
break;
|
||||||
{
|
|
||||||
startIdx = i + 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
endIndexStr = "y.io";
|
|
||||||
endIdx = txt.IndexOf(endIndexStr) + endIndexStr.Length;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// add/subtract 2 to take into account newline
|
|
||||||
startIdx = txt.IndexOf(HostsStartMarker) - 2;
|
|
||||||
endIdx = txt.IndexOf(endIndexStr) + endIndexStr.Length;
|
|
||||||
}
|
|
||||||
|
|
||||||
txt = string.Concat(txt.AsSpan(0, startIdx), txt.AsSpan(endIdx));
|
|
||||||
|
|
||||||
|
|
||||||
await File.WriteAllTextAsync(hostsFilePath, txt);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<ServerSwitchResult> SaveCfg(bool useOffical, string gamePath, string? launcherPath, string ip, bool offlineMode)
|
|
||||||
{
|
|
||||||
string sodiumLib = AppDomain.CurrentDomain.BaseDirectory + "sodium.dll";
|
|
||||||
string gameSodium = gamePath + "/nikke_Data/Plugins/x86_64/sodium.dll";
|
|
||||||
string gameAssembly = gamePath + "/GameAssembly.dll";
|
|
||||||
string sodiumBackup = gameSodium + ".bak";
|
|
||||||
string hostsFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "drivers/etc/hosts");
|
|
||||||
string CAcert = await File.ReadAllTextAsync(AppDomain.CurrentDomain.BaseDirectory + "myCA.pem");
|
|
||||||
|
|
||||||
string launcherCertList = launcherPath + "/intl_service/intl_cacert.pem";
|
|
||||||
if (!File.Exists(launcherCertList))
|
|
||||||
launcherCertList = launcherPath + "/intl_service/cacert.pem"; // older INTL sdk versions
|
|
||||||
string gameCertList = gamePath + "/nikke_Data/Plugins/x86_64/intl_cacert.pem";
|
|
||||||
if (!File.Exists(gameCertList))
|
|
||||||
gameCertList = gamePath + "/nikke_Data/Plugins/x86_64/cacert.pem"; // older INTL sdk versions
|
|
||||||
bool supported = true;
|
|
||||||
|
|
||||||
if (OperatingSystem.IsLinux())
|
|
||||||
{
|
|
||||||
// for wine
|
|
||||||
hostsFilePath = gamePath + "/../../../windows/system32/drivers/etc/hosts";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (useOffical)
|
|
||||||
{
|
|
||||||
await RevertHostsFile(hostsFilePath);
|
|
||||||
if (OperatingSystem.IsLinux())
|
|
||||||
{
|
|
||||||
await RevertHostsFile("/etc/hosts");
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// remove cert
|
|
||||||
if (OperatingSystem.IsWindows())
|
|
||||||
{
|
|
||||||
X509Store store = new(StoreName.Root, StoreLocation.LocalMachine);
|
|
||||||
store.Open(OpenFlags.ReadWrite);
|
|
||||||
store.Remove(new X509Certificate2(X509Certificate.CreateFromCertFile(AppDomain.CurrentDomain.BaseDirectory + "myCA.pfx")));
|
|
||||||
store.Close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
|
||||||
{
|
|
||||||
// may not be installed
|
|
||||||
}
|
|
||||||
|
|
||||||
// restore sodium
|
endIndexStr = "y.io";
|
||||||
if (!File.Exists(sodiumBackup))
|
endIdx = txt.IndexOf(endIndexStr) + endIndexStr.Length;
|
||||||
{
|
|
||||||
throw new Exception("sodium backup does not exist. Repair the game in the launcher and switch to local server and back to official.");
|
|
||||||
}
|
|
||||||
File.Copy(sodiumBackup, gameSodium, true);
|
|
||||||
|
|
||||||
if (File.Exists(launcherCertList))
|
|
||||||
{
|
|
||||||
string certList = await File.ReadAllTextAsync(launcherCertList);
|
|
||||||
|
|
||||||
int goodSslIndex1 = certList.IndexOf("Good SSL Ca");
|
|
||||||
if (goodSslIndex1 != -1)
|
|
||||||
await File.WriteAllTextAsync(launcherCertList, certList[..goodSslIndex1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (File.Exists(gameCertList))
|
|
||||||
{
|
|
||||||
string certList = await File.ReadAllTextAsync(gameCertList);
|
|
||||||
|
|
||||||
int newCertIndex = certList.IndexOf("Good SSL Ca");
|
|
||||||
if (newCertIndex != -1)
|
|
||||||
await File.WriteAllTextAsync(gameCertList, certList[..newCertIndex]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// add to hosts file
|
// add/subtract 2 to take into account newline
|
||||||
string hosts = $@"{HostsStartMarker}
|
startIdx = txt.IndexOf(HostsStartMarker) - 2;
|
||||||
|
endIdx = txt.IndexOf(endIndexStr) + endIndexStr.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
txt = string.Concat(txt.AsSpan(0, startIdx), txt.AsSpan(endIdx));
|
||||||
|
|
||||||
|
|
||||||
|
await File.WriteAllTextAsync(hostsFilePath, txt);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<ServerSwitchResult> SaveCfg(bool useOffical, string ip, bool offlineMode)
|
||||||
|
{
|
||||||
|
string CAcert = await File.ReadAllTextAsync(AppDomain.CurrentDomain.BaseDirectory + "myCA.pem");
|
||||||
|
string sodiumLib = AppDomain.CurrentDomain.BaseDirectory + "sodium.dll";
|
||||||
|
|
||||||
|
bool supported = true;
|
||||||
|
if (useOffical)
|
||||||
|
{
|
||||||
|
await RevertHostsFile(util.SystemHostsFile);
|
||||||
|
if (OperatingSystem.IsLinux())
|
||||||
|
{
|
||||||
|
await RevertHostsFile(util.WineHostsFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// remove cert
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
X509Store store = new(StoreName.Root, StoreLocation.LocalMachine);
|
||||||
|
store.Open(OpenFlags.ReadWrite);
|
||||||
|
store.Remove(new X509Certificate2(X509Certificate.CreateFromCertFile(AppDomain.CurrentDomain.BaseDirectory + "myCA.pfx")));
|
||||||
|
store.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// may not be installed
|
||||||
|
}
|
||||||
|
|
||||||
|
// restore sodium
|
||||||
|
if (!File.Exists(util.GameSodiumBackupPath))
|
||||||
|
{
|
||||||
|
throw new Exception("sodium backup does not exist. Repair the game in the launcher and switch to local server and back to official.");
|
||||||
|
}
|
||||||
|
File.Copy(util.GameSodiumBackupPath, util.GameSodiumPath, true);
|
||||||
|
|
||||||
|
if (util.LauncherCertificatePath != null && File.Exists(util.LauncherCertificatePath))
|
||||||
|
{
|
||||||
|
string certList = await File.ReadAllTextAsync(util.LauncherCertificatePath);
|
||||||
|
|
||||||
|
int goodSslIndex1 = certList.IndexOf("Good SSL Ca");
|
||||||
|
if (goodSslIndex1 != -1)
|
||||||
|
await File.WriteAllTextAsync(util.LauncherCertificatePath, certList[..goodSslIndex1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (File.Exists(util.GameCertificatePath))
|
||||||
|
{
|
||||||
|
string certList = await File.ReadAllTextAsync(util.GameCertificatePath);
|
||||||
|
|
||||||
|
int newCertIndex = certList.IndexOf("Good SSL Ca");
|
||||||
|
if (newCertIndex != -1)
|
||||||
|
await File.WriteAllTextAsync(util.GameCertificatePath, certList[..newCertIndex]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// add to hosts file
|
||||||
|
string hosts = $@"{HostsStartMarker}
|
||||||
{ip} global-lobby.nikke-kr.com
|
{ip} global-lobby.nikke-kr.com
|
||||||
";
|
";
|
||||||
if (offlineMode)
|
if (offlineMode)
|
||||||
{
|
{
|
||||||
hosts += $"{ip} cloud.nikke-kr.com" + Environment.NewLine;
|
hosts += $"{ip} cloud.nikke-kr.com" + Environment.NewLine;
|
||||||
}
|
}
|
||||||
|
|
||||||
hosts += $@"{ip} jp-lobby.nikke-kr.com
|
hosts += $@"{ip} jp-lobby.nikke-kr.com
|
||||||
{ip} us-lobby.nikke-kr.com
|
{ip} us-lobby.nikke-kr.com
|
||||||
{ip} kr-lobby.nikke-kr.com
|
{ip} kr-lobby.nikke-kr.com
|
||||||
{ip} sea-lobby.nikke-kr.com
|
{ip} sea-lobby.nikke-kr.com
|
||||||
@@ -224,94 +188,92 @@ namespace ServerSelector
|
|||||||
255.255.221.21 sentry.io
|
255.255.221.21 sentry.io
|
||||||
{HostsEndMarker}";
|
{HostsEndMarker}";
|
||||||
|
|
||||||
await RevertHostsFile(hostsFilePath);
|
await RevertHostsFile(util.SystemHostsFile);
|
||||||
|
|
||||||
try
|
try
|
||||||
|
{
|
||||||
|
FileInfo fi = new(util.SystemHostsFile);
|
||||||
|
if (fi.IsReadOnly)
|
||||||
{
|
{
|
||||||
FileInfo fi = new(hostsFilePath);
|
// try to remove readonly flag
|
||||||
if (fi.IsReadOnly)
|
fi.IsReadOnly = false;
|
||||||
{
|
|
||||||
// try to remove readonly flag
|
|
||||||
fi.IsReadOnly = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(await File.ReadAllTextAsync(hostsFilePath)).Contains("global-lobby.nikke-kr.com"))
|
|
||||||
{
|
|
||||||
using StreamWriter w = File.AppendText(hostsFilePath);
|
|
||||||
w.WriteLine();
|
|
||||||
w.Write(hosts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
throw new Exception("cannot modify C:\\Windows\\System32\\drivers\\etc\\hosts file to redirect to server, check your antivirus software");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also change /etc/hosts if running on linux
|
if (!(await File.ReadAllTextAsync(util.SystemHostsFile)).Contains("global-lobby.nikke-kr.com"))
|
||||||
if (OperatingSystem.IsLinux())
|
|
||||||
{
|
{
|
||||||
hostsFilePath = "/etc/hosts";
|
using StreamWriter w = File.AppendText(util.SystemHostsFile);
|
||||||
await RevertHostsFile(hostsFilePath);
|
w.WriteLine();
|
||||||
if (!(await File.ReadAllTextAsync(hostsFilePath)).Contains("global-lobby.nikke-kr.com"))
|
w.Write(hosts);
|
||||||
{
|
|
||||||
using StreamWriter w = File.AppendText(hostsFilePath);
|
|
||||||
w.WriteLine();
|
|
||||||
w.Write(hosts);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
throw new Exception($"cannot modify \"{util.SystemHostsFile}\" file to redirect to server, check your antivirus software");
|
||||||
|
}
|
||||||
|
|
||||||
// trust CA. TODO is this needed?
|
// Also change hosts file in wineprefix if running on linux
|
||||||
try
|
if (OperatingSystem.IsLinux())
|
||||||
|
{
|
||||||
|
await RevertHostsFile(util.WineHostsFile);
|
||||||
|
if (!(await File.ReadAllTextAsync(util.WineHostsFile)).Contains("global-lobby.nikke-kr.com"))
|
||||||
{
|
{
|
||||||
if (OperatingSystem.IsWindows())
|
using StreamWriter w = File.AppendText(util.WineHostsFile);
|
||||||
{
|
w.WriteLine();
|
||||||
X509Store store = new(StoreName.Root, StoreLocation.LocalMachine);
|
w.Write(hosts);
|
||||||
store.Open(OpenFlags.ReadWrite);
|
|
||||||
store.Add(new X509Certificate2(X509Certificate2.CreateFromCertFile(AppDomain.CurrentDomain.BaseDirectory + "myCA.pfx")));
|
|
||||||
store.Close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch { }
|
}
|
||||||
|
|
||||||
if (!File.Exists(gameSodium))
|
// trust CA. TODO is this needed?
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
{
|
{
|
||||||
throw new Exception("expected sodium library to exist at path " + gameSodium);
|
X509Store store = new(StoreName.Root, StoreLocation.LocalMachine);
|
||||||
|
store.Open(OpenFlags.ReadWrite);
|
||||||
|
store.Add(new X509Certificate2(X509Certificate2.CreateFromCertFile(AppDomain.CurrentDomain.BaseDirectory + "myCA.pfx")));
|
||||||
|
store.Close();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
// copy backup if sodium size is correct
|
if (!File.Exists(util.GameSodiumPath))
|
||||||
byte[] sod = await File.ReadAllBytesAsync(gameSodium);
|
{
|
||||||
if (sod.Length <= 307200) // TODO this is awful
|
throw new Exception("expected sodium library to exist at path " + util.GameSodiumPath);
|
||||||
{
|
}
|
||||||
// orignal file size, copy backup
|
|
||||||
await File.WriteAllBytesAsync(sodiumBackup, sod);
|
|
||||||
}
|
|
||||||
|
|
||||||
// write new sodium library
|
// copy backup if sodium size is correct
|
||||||
await File.WriteAllBytesAsync(gameSodium, await File.ReadAllBytesAsync(sodiumLib));
|
byte[] sod = await File.ReadAllBytesAsync(util.GameSodiumPath);
|
||||||
|
if (sod.Length <= 307200) // TODO this is awful
|
||||||
|
{
|
||||||
|
// orignal file size, copy backup
|
||||||
|
await File.WriteAllBytesAsync(util.GameSodiumBackupPath, sod);
|
||||||
|
}
|
||||||
|
|
||||||
// Add generated CA certificate to launcher/game curl certificate list
|
// write new sodium library
|
||||||
if (launcherPath != null)
|
await File.WriteAllBytesAsync(util.GameSodiumPath, await File.ReadAllBytesAsync(sodiumLib));
|
||||||
{
|
|
||||||
await File.WriteAllTextAsync(launcherCertList,
|
|
||||||
await File.ReadAllTextAsync(launcherCertList)
|
|
||||||
+ "\nGood SSL Ca\n===============================\n"
|
|
||||||
+ CAcert);
|
|
||||||
}
|
|
||||||
|
|
||||||
await File.WriteAllTextAsync(gameCertList,
|
// Add generated CA certificate to launcher/game curl certificate list
|
||||||
await File.ReadAllTextAsync(gameCertList)
|
if (util.LauncherCertificatePath != null)
|
||||||
|
{
|
||||||
|
await File.WriteAllTextAsync(util.LauncherCertificatePath,
|
||||||
|
await File.ReadAllTextAsync(util.LauncherCertificatePath)
|
||||||
+ "\nGood SSL Ca\n===============================\n"
|
+ "\nGood SSL Ca\n===============================\n"
|
||||||
+ CAcert);
|
+ CAcert);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ServerSwitchResult(true, null, supported);
|
await File.WriteAllTextAsync(util.GameCertificatePath,
|
||||||
|
await File.ReadAllTextAsync(util.GameCertificatePath)
|
||||||
|
+ "\nGood SSL Ca\n===============================\n"
|
||||||
|
+ CAcert);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public class ServerSwitchResult(bool ok, Exception? exception, bool isSupported)
|
return new ServerSwitchResult(true, null, supported);
|
||||||
{
|
|
||||||
public bool Ok { get; set; } = ok;
|
|
||||||
public Exception? Exception { get; set; } = exception;
|
|
||||||
public bool IsSupported { get; set; } = isSupported;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class ServerSwitchResult(bool ok, Exception? exception, bool isSupported)
|
||||||
|
{
|
||||||
|
public bool Ok { get; set; } = ok;
|
||||||
|
public Exception? Exception { get; set; } = exception;
|
||||||
|
public bool IsSupported { get; set; } = isSupported;
|
||||||
|
}
|
||||||
|
|||||||
@@ -116,7 +116,7 @@
|
|||||||
|
|
||||||
<TextBlock VerticalAlignment="Center" Margin="5" Grid.Row="0" Grid.Column="0" ToolTip.Tip="The folder with the game and launcher folders.">Game root path: </TextBlock>
|
<TextBlock VerticalAlignment="Center" Margin="5" Grid.Row="0" Grid.Column="0" ToolTip.Tip="The folder with the game and launcher folders.">Game root path: </TextBlock>
|
||||||
<TextBox x:Name="txtGamePath" Grid.Row="0" Grid.Column="1" TextChanged="GamePath_TextChanged" ToolTip.Tip="The folder with the game and launcher folders.">C:\NIKKE\</TextBox>
|
<TextBox x:Name="txtGamePath" Grid.Row="0" Grid.Column="1" TextChanged="GamePath_TextChanged" ToolTip.Tip="The folder with the game and launcher folders.">C:\NIKKE\</TextBox>
|
||||||
<Button x:Name="btnSelectGamePath" Grid.Row="0" Grid.Column="2" Classes="utility" Click="BtnSelectGamePath_Click">...</Button>
|
<Button x:Name="btnSelectGamePath" Grid.Row="0" Grid.Column="2" Classes="utility" Click="BtnSelectGamePath_Click" Margin="5,0,0,0">...</Button>
|
||||||
|
|
||||||
<TextBlock VerticalAlignment="Center" Margin="5" Grid.Row="4" Grid.Column="0">Server: </TextBlock>
|
<TextBlock VerticalAlignment="Center" Margin="5" Grid.Row="4" Grid.Column="0">Server: </TextBlock>
|
||||||
|
|
||||||
@@ -144,7 +144,7 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
<TabItem Header="Android">
|
<TabItem Header="Android" IsVisible="False">
|
||||||
<Grid Margin="5">
|
<Grid Margin="5">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="auto"></ColumnDefinition>
|
<ColumnDefinition Width="auto"></ColumnDefinition>
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ public partial class MainView : UserControl
|
|||||||
public MainView()
|
public MainView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
CmbServerSelection.SelectedIndex = ServerSwitcher.IsUsingOfficalServer() ? 0 : 1;
|
CmbServerSelection.SelectedIndex = ServerSwitcher.IsUsingLocalServer() ? 1 : 0;
|
||||||
|
|
||||||
TxtIpAddress.IsEnabled = !ServerSwitcher.IsUsingOfficalServer();
|
TxtIpAddress.IsEnabled = !ServerSwitcher.IsUsingLocalServer();
|
||||||
ChkOffline.IsChecked = ServerSwitcher.IsOffline();
|
ChkOffline.IsChecked = ServerSwitcher.IsOffline();
|
||||||
|
|
||||||
if (OperatingSystem.IsWindows() && !new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator))
|
if (OperatingSystem.IsWindows() && !new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator))
|
||||||
@@ -57,25 +57,17 @@ public partial class MainView : UserControl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string? GamePath
|
private string? BasePath
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (txtGamePath.Text == null) return null;
|
if (txtGamePath.Text == null) return null;
|
||||||
return Path.Combine(txtGamePath.Text, "NIKKE", "game");
|
return txtGamePath.Text;
|
||||||
}
|
|
||||||
}
|
|
||||||
private string? LauncherPath
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (txtGamePath.Text == null) return null;
|
|
||||||
return Path.Combine(txtGamePath.Text, "Launcher");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private bool ValidatePaths(bool showMessage)
|
private bool ValidatePaths(bool showMessage)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(txtGamePath.Text) || LauncherPath == null)
|
if (string.IsNullOrEmpty(BasePath))
|
||||||
{
|
{
|
||||||
SetGamePathValid(false);
|
SetGamePathValid(false);
|
||||||
if (showMessage)
|
if (showMessage)
|
||||||
@@ -83,20 +75,21 @@ public partial class MainView : UserControl
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Directory.Exists(GamePath))
|
if (!Directory.Exists(BasePath))
|
||||||
{
|
{
|
||||||
SetGamePathValid(false);
|
SetGamePathValid(false);
|
||||||
if (showMessage)
|
if (showMessage)
|
||||||
ShowWarningMsg("game folder does not exist in the game root folder", "Error");
|
ShowWarningMsg("Game path does not exist", "Error");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!File.Exists(Path.Combine(LauncherPath, "nikke_launcher.exe")))
|
var result = ServerSwitcher.SetBasePath(BasePath);
|
||||||
|
|
||||||
|
if (!result.Item1)
|
||||||
{
|
{
|
||||||
SetGamePathValid(false);
|
SetGamePathValid(false);
|
||||||
if (showMessage)
|
if (showMessage)
|
||||||
ShowWarningMsg("Game path is invalid. Make sure that nikke_launcher.exe exists in the <Game Path>/launcher folder", "Error");
|
ShowWarningMsg(result.Item2, "Error");
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,11 +100,11 @@ public partial class MainView : UserControl
|
|||||||
|
|
||||||
private async void UpdateIntegrityLabel()
|
private async void UpdateIntegrityLabel()
|
||||||
{
|
{
|
||||||
if (!ValidatePaths(false) || txtGamePath.Text == null || GamePath == null || LauncherPath == null)
|
if (!ValidatePaths(false) || txtGamePath.Text == null || BasePath == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
SetLoadingScreenVisible(true);
|
SetLoadingScreenVisible(true);
|
||||||
LblStatus.Text = "Status: " + await ServerSwitcher.CheckIntegrity(GamePath, LauncherPath);
|
LblStatus.Text = "Status: " + await ServerSwitcher.CheckIntegrity();
|
||||||
SetLoadingScreenVisible(false);
|
SetLoadingScreenVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +124,7 @@ public partial class MainView : UserControl
|
|||||||
|
|
||||||
private async void Save_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
|
private async void Save_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (!ValidatePaths(true) || txtGamePath.Text == null || GamePath == null || LauncherPath == null)
|
if (!ValidatePaths(true) || txtGamePath.Text == null || BasePath == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (CmbServerSelection.SelectedIndex == 1 && !IPAddress.TryParse(TxtIpAddress.Text, out _))
|
if (CmbServerSelection.SelectedIndex == 1 && !IPAddress.TryParse(TxtIpAddress.Text, out _))
|
||||||
@@ -150,7 +143,7 @@ public partial class MainView : UserControl
|
|||||||
SetLoadingScreenVisible(true);
|
SetLoadingScreenVisible(true);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ServerSwitchResult res = await ServerSwitcher.SaveCfg(CmbServerSelection.SelectedIndex == 0, GamePath, LauncherPath, TxtIpAddress.Text, ChkOffline.IsChecked ?? false);
|
ServerSwitchResult res = await ServerSwitcher.SaveCfg(CmbServerSelection.SelectedIndex == 0, TxtIpAddress.Text, ChkOffline.IsChecked ?? false);
|
||||||
|
|
||||||
if (!res.IsSupported)
|
if (!res.IsSupported)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user