mirror of
https://github.com/EpinelPS/EpinelPS.git
synced 2025-12-12 15:04:36 +01:00
implementation for the Favorite Item ,Character Counse and Harmony Cube systems (#51)
* feat: Implement Favorite Item and Harmony Cube systems
This commit introduces the core implementation for the Favorite Item and Harmony Cube systems.
Features:
- Added data structures and loading for Favorite Items, Harmony Cubes, and Attractive Levels.
- Implemented lobby handlers for all related actions:
- Favorite Items: equip, increase exp, quests, rewards, etc.
- Harmony Cubes: get, clear, increase exp, level up, management, etc.
- Character Counsel: check, present, quick counsel.
- Updated user data models to store related progression.
- Switched JSON deserialization for db.json to Newtonsoft.Json to handle protobuf models correctly.
* fix InfraCoreExp
* fix UserFavoriteItems and present count
---------
Co-authored-by: Mikhail Tyukin <mishakeys20@gmail.com>
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
using System.Data;
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.Json;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using MemoryPack;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace EpinelPS.Data
|
||||
{
|
||||
@@ -154,6 +154,9 @@ namespace EpinelPS.Data
|
||||
[LoadRecord("AttractiveLevelRewardTable.json", "id")]
|
||||
public readonly Dictionary<int, AttractiveLevelRewardRecord> AttractiveLevelReward = [];
|
||||
|
||||
[LoadRecord("AttractiveLevelTable.json", "id")]
|
||||
public readonly Dictionary<int, AttractiveLevelRecord> AttractiveLevelTable = [];
|
||||
|
||||
[LoadRecord("SubQuestTable.json", "id")]
|
||||
public readonly Dictionary<int, SubquestRecord> Subquests = [];
|
||||
|
||||
@@ -200,6 +203,32 @@ namespace EpinelPS.Data
|
||||
[LoadRecord("RecycleResearchLevelTable.json", "id")]
|
||||
public readonly Dictionary<int, RecycleResearchLevelRecord> RecycleResearchLevels = [];
|
||||
|
||||
// Harmony Cube Data Tables
|
||||
[LoadRecord("ItemHarmonyCubeTable.json", "id")]
|
||||
public readonly Dictionary<int, ItemHarmonyCubeRecord> ItemHarmonyCubeTable = [];
|
||||
|
||||
[LoadRecord("ItemHarmonyCubeLevelTable.json", "id")]
|
||||
public readonly Dictionary<int, ItemHarmonyCubeLevelRecord> ItemHarmonyCubeLevelTable = [];
|
||||
|
||||
// Favorite Item Data Tables
|
||||
[LoadRecord("FavoriteItemTable.json", "id")]
|
||||
public readonly Dictionary<int, FavoriteItemRecord> FavoriteItemTable = [];
|
||||
|
||||
[LoadRecord("FavoriteItemExpTable.json", "id")]
|
||||
public readonly Dictionary<int, FavoriteItemExpRecord> FavoriteItemExpTable = [];
|
||||
|
||||
[LoadRecord("FavoriteItemLevelTable.json", "id")]
|
||||
public readonly Dictionary<int, FavoriteItemLevelRecord> FavoriteItemLevelTable = [];
|
||||
|
||||
[LoadRecord("FavoriteItemProbabilityTable.json", "id")]
|
||||
public readonly Dictionary<int, FavoriteItemProbabilityRecord> FavoriteItemProbabilityTable = [];
|
||||
|
||||
[LoadRecord("FavoriteItemQuestTable.json", "id")]
|
||||
public readonly Dictionary<int, FavoriteItemQuestRecord> FavoriteItemQuestTable = [];
|
||||
|
||||
[LoadRecord("FavoriteItemQuestStageTable.json", "id")]
|
||||
public readonly Dictionary<int, FavoriteItemQuestStageRecord> FavoriteItemQuestStageTable = [];
|
||||
|
||||
|
||||
static async Task<GameData> BuildAsync()
|
||||
{
|
||||
@@ -392,7 +421,9 @@ namespace EpinelPS.Data
|
||||
}
|
||||
else
|
||||
{
|
||||
DataTable<X> obj = await JsonSerializer.DeserializeAsync<DataTable<X>>(MainZip.GetInputStream(fileEntry), JsonDb.IndentedJson) ?? throw new Exception("deserializeobject failed");
|
||||
using var streamReader = new System.IO.StreamReader(MainZip.GetInputStream(fileEntry));
|
||||
var json = await streamReader.ReadToEndAsync();
|
||||
DataTable<X> obj = JsonConvert.DeserializeObject<DataTable<X>>(json) ?? throw new Exception("deserializeobject failed");
|
||||
deserializedObject = [.. obj.records];
|
||||
}
|
||||
|
||||
@@ -560,7 +591,20 @@ namespace EpinelPS.Data
|
||||
|
||||
public string? GetItemSubType(int itemType)
|
||||
{
|
||||
return ItemEquipTable[itemType].item_sub_type;
|
||||
// Check if it's an equipment item
|
||||
if (ItemEquipTable.TryGetValue(itemType, out ItemEquipRecord? equipRecord))
|
||||
{
|
||||
return equipRecord.item_sub_type;
|
||||
}
|
||||
|
||||
// Check if it's a harmony cube item
|
||||
if (ItemHarmonyCubeTable.TryGetValue(itemType, out ItemHarmonyCubeRecord? harmonyCubeRecord))
|
||||
{
|
||||
return harmonyCubeRecord.item_sub_type;
|
||||
}
|
||||
|
||||
// Return null if item type not found
|
||||
return null;
|
||||
}
|
||||
|
||||
internal IEnumerable<int> GetStageIdsForChapter(int chapterNumber, bool normal)
|
||||
@@ -669,5 +713,17 @@ namespace EpinelPS.Data
|
||||
return results.FirstOrDefault().Value.reward_id;
|
||||
else return 0;
|
||||
}
|
||||
|
||||
public FavoriteItemQuestRecord? GetFavoriteItemQuestTableData(int questId)
|
||||
{
|
||||
FavoriteItemQuestTable.TryGetValue(questId, out FavoriteItemQuestRecord?data);
|
||||
return data;
|
||||
}
|
||||
|
||||
public FavoriteItemQuestStageRecord? GetFavoriteItemQuestStageData(int stageId)
|
||||
{
|
||||
FavoriteItemQuestStageTable.TryGetValue(stageId, out FavoriteItemQuestStageRecord? data);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Data;
|
||||
using System.Data;
|
||||
using MemoryPack;
|
||||
|
||||
namespace EpinelPS.Data
|
||||
@@ -685,7 +685,7 @@ namespace EpinelPS.Data
|
||||
{
|
||||
public int id;
|
||||
public int grade;
|
||||
public int reard_id;
|
||||
public int reward_id;
|
||||
public int infra_core_exp;
|
||||
public List<InfracoreFunction> function_list = [];
|
||||
}
|
||||
@@ -706,6 +706,32 @@ namespace EpinelPS.Data
|
||||
public int attractive_level;
|
||||
public int costume;
|
||||
}
|
||||
|
||||
[MemoryPackable]
|
||||
public partial class AttractiveLevelRecord
|
||||
{
|
||||
public int id;
|
||||
public int attractive_level;
|
||||
public int attractive_point;
|
||||
public int attacker_hp_rate;
|
||||
public int attacker_attack_rate;
|
||||
public int attacker_defence_rate;
|
||||
public int attacker_energy_resist_rate;
|
||||
public int attacker_metal_resist_rate;
|
||||
public int attacker_bio_resist_rate;
|
||||
public int defender_hp_rate;
|
||||
public int defender_attack_rate;
|
||||
public int defender_defence_rate;
|
||||
public int defender_energy_resist_rate;
|
||||
public int defender_metal_resist_rate;
|
||||
public int defender_bio_resist_rate;
|
||||
public int supporter_hp_rate;
|
||||
public int supporter_attack_rate;
|
||||
public int supporter_defence_rate;
|
||||
public int supporter_energy_resist_rate;
|
||||
public int supporter_metal_resist_rate;
|
||||
public int supporter_bio_resist_rate;
|
||||
}
|
||||
[MemoryPackable]
|
||||
public partial class SubquestRecord
|
||||
{
|
||||
@@ -731,9 +757,23 @@ namespace EpinelPS.Data
|
||||
public partial class MessengerMsgConditionRecord
|
||||
{
|
||||
public int id;
|
||||
public List<MessengerConditionTriggerList> trigger_list = [];
|
||||
public string message_type = "";
|
||||
public string tid = "";
|
||||
public int resource_id;
|
||||
public int stamina_value;
|
||||
public int reward_id;
|
||||
}
|
||||
|
||||
[MemoryPackable]
|
||||
public partial class MessengerConditionTriggerList
|
||||
{
|
||||
public string trigger = "";
|
||||
public int condition_id;
|
||||
public int condition_value;
|
||||
}
|
||||
|
||||
|
||||
public enum ScenarioRewardCondition
|
||||
{
|
||||
MainScenario,
|
||||
@@ -889,4 +929,165 @@ namespace EpinelPS.Data
|
||||
public List<StageSpawner> StageSpawner { get; set; } = [];
|
||||
}
|
||||
|
||||
// Harmony Cube Data Structures
|
||||
[MemoryPackable]
|
||||
public partial class ItemHarmonyCubeRecord
|
||||
{
|
||||
public int id;
|
||||
public string name_localkey = "";
|
||||
public string description_localkey = "";
|
||||
public int location_id;
|
||||
public string location_localkey = "";
|
||||
public int order;
|
||||
public int resource_id;
|
||||
public string bg = "";
|
||||
public string bg_color = "";
|
||||
public string item_type = "";
|
||||
public string item_sub_type = "";
|
||||
public string item_rare = "";
|
||||
public string @class = "";
|
||||
public int level_enhance_id;
|
||||
public List<HarmonyCubeSkillGroup> harmonycube_skill_group = [];
|
||||
}
|
||||
|
||||
[MemoryPackable]
|
||||
public partial class ItemHarmonyCubeLevelRecord
|
||||
{
|
||||
public int id;
|
||||
public int level_enhance_id;
|
||||
public int level;
|
||||
public List<HarmonyCubeSkillLevel> skill_levels = [];
|
||||
public int material_id;
|
||||
public int material_value;
|
||||
public int gold_value;
|
||||
public int slot;
|
||||
public List<HarmonyCubeStat> harmonycube_stats = [];
|
||||
}
|
||||
|
||||
public class HarmonyCubeSkillGroup
|
||||
{
|
||||
public int skill_group_id;
|
||||
}
|
||||
|
||||
public class HarmonyCubeSkillLevel
|
||||
{
|
||||
public int skill_level;
|
||||
}
|
||||
|
||||
public class HarmonyCubeStat
|
||||
{
|
||||
public string stat_type = "";
|
||||
public int stat_rate;
|
||||
}
|
||||
|
||||
[MemoryPackable]
|
||||
public partial class FavoriteItemRecord
|
||||
{
|
||||
public int id;
|
||||
public string name_localkey = "";
|
||||
public string description_localkey = "";
|
||||
public string icon_resource_id = "";
|
||||
public string img_resource_id = "";
|
||||
public string prop_resource_id = "";
|
||||
public int order;
|
||||
public string favorite_rare = "";
|
||||
public string favorite_type = "";
|
||||
public string weapon_type = "";
|
||||
public int name_code;
|
||||
public int max_level;
|
||||
public int level_enhance_id;
|
||||
public int probability_group;
|
||||
public int albumcategory_id;
|
||||
}
|
||||
|
||||
[MemoryPackable]
|
||||
public partial class FavoriteItemExpRecord
|
||||
{
|
||||
public int id;
|
||||
public string favorite_rare = "";
|
||||
public int level;
|
||||
public int need_exp;
|
||||
}
|
||||
|
||||
[MemoryPackable]
|
||||
public partial class FavoriteItemLevelRecord
|
||||
{
|
||||
public int id;
|
||||
public int level_enhance_id;
|
||||
public int grade;
|
||||
public int level;
|
||||
public List<FavoriteItemStatData> favoriteitem_stat_data = [];
|
||||
public List<CollectionSkillLevelData> collection_skill_level_data = [];
|
||||
}
|
||||
|
||||
[MemoryPackable]
|
||||
public partial class FavoriteItemProbabilityRecord
|
||||
{
|
||||
public int id;
|
||||
public int probability_group;
|
||||
public int level_min;
|
||||
public int level_max;
|
||||
public int need_item_id;
|
||||
public int need_item_count;
|
||||
public int exp;
|
||||
public int great_success_rate;
|
||||
public int great_success_level;
|
||||
}
|
||||
|
||||
[MemoryPackable]
|
||||
public partial class FavoriteItemQuestRecord
|
||||
{
|
||||
public int id;
|
||||
public int name_code;
|
||||
public string condition_type = "";
|
||||
public int condition_value;
|
||||
public string quest_thumbnail_resource_id = "";
|
||||
public string name_localkey = "";
|
||||
public string description_localkey = "";
|
||||
public int next_quest_id;
|
||||
public string end_scenario_id = "";
|
||||
public int reward_id;
|
||||
}
|
||||
|
||||
public class FavoriteItemStatData
|
||||
{
|
||||
public string stat_type = "";
|
||||
public int stat_value;
|
||||
}
|
||||
|
||||
public class CollectionSkillLevelData
|
||||
{
|
||||
public int collection_skill_level;
|
||||
}
|
||||
|
||||
[MemoryPackable]
|
||||
public partial class FavoriteItemQuestStageRecord
|
||||
{
|
||||
public int id;
|
||||
public int group_id;
|
||||
public int chapter_id;
|
||||
public string chapter_mod = "";
|
||||
public int name_code;
|
||||
public int spawn_condition_favoriteitem_quest_id;
|
||||
public int spawn_condition_campaign_stage_id;
|
||||
public int enter_condition_favoriteitem_quest_id;
|
||||
public int enter_condition_campaign_stage_id;
|
||||
public string name_localkey = "";
|
||||
public string stage_category = "";
|
||||
public bool spot_autocontrol;
|
||||
public int monster_stage_lv;
|
||||
public int dynamic_object_stage_lv;
|
||||
public int standard_battle_power;
|
||||
public int stage_stat_increase_group_id;
|
||||
public bool is_use_quick_battle;
|
||||
public int field_monster_id;
|
||||
public int spot_id;
|
||||
public int state_effect_function_id;
|
||||
public int reward_id;
|
||||
public string enter_scenario_type = "";
|
||||
public string enter_scenario = "";
|
||||
public int fixed_play_character_id;
|
||||
public int spawn_condition_favoriteitem_quest_stage_id;
|
||||
public int enter_condition_favoriteitem_quest_stage_id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Globalization;
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Utils;
|
||||
using Google.Protobuf;
|
||||
using Newtonsoft.Json;
|
||||
using Paseto;
|
||||
using Paseto.Builder;
|
||||
|
||||
@@ -12,7 +11,6 @@ namespace EpinelPS.Database
|
||||
internal class JsonDb
|
||||
{
|
||||
public static CoreInfo Instance { get; internal set; }
|
||||
public static readonly JsonSerializerOptions IndentedJson = new() { WriteIndented = true, IncludeFields = true };
|
||||
|
||||
// Note: change this in sodium
|
||||
public static byte[] ServerPrivateKey = Convert.FromBase64String("FSUY8Ohd942n5LWAfxn6slK3YGwc8OqmyJoJup9nNos=");
|
||||
@@ -20,7 +18,6 @@ namespace EpinelPS.Database
|
||||
|
||||
static JsonDb()
|
||||
{
|
||||
IndentedJson.Converters.Add(new JsonStringEnumConverter());
|
||||
if (!File.Exists(AppDomain.CurrentDomain.BaseDirectory + "/db.json"))
|
||||
{
|
||||
Console.WriteLine("users: warning: configuration not found, writing default data");
|
||||
@@ -28,7 +25,7 @@ namespace EpinelPS.Database
|
||||
Save();
|
||||
}
|
||||
|
||||
var j = JsonSerializer.Deserialize<CoreInfo>(File.ReadAllText(AppDomain.CurrentDomain.BaseDirectory + "/db.json"), IndentedJson);
|
||||
var j = JsonConvert.DeserializeObject<CoreInfo>(File.ReadAllText(AppDomain.CurrentDomain.BaseDirectory + "/db.json"));
|
||||
if (j != null)
|
||||
{
|
||||
Instance = j;
|
||||
@@ -80,7 +77,7 @@ namespace EpinelPS.Database
|
||||
Save();
|
||||
}
|
||||
|
||||
var j = JsonSerializer.Deserialize<CoreInfo>(File.ReadAllText(AppDomain.CurrentDomain.BaseDirectory + "/db.json"));
|
||||
var j = JsonConvert.DeserializeObject<CoreInfo>(File.ReadAllText(AppDomain.CurrentDomain.BaseDirectory + "/db.json"));
|
||||
if (j != null)
|
||||
{
|
||||
Instance = j;
|
||||
@@ -112,7 +109,7 @@ namespace EpinelPS.Database
|
||||
{
|
||||
if (Instance != null)
|
||||
{
|
||||
File.WriteAllText(AppDomain.CurrentDomain.BaseDirectory + "/db.json", JsonSerializer.Serialize(Instance, IndentedJson));
|
||||
File.WriteAllText(AppDomain.CurrentDomain.BaseDirectory + "/db.json", JsonConvert.SerializeObject(Instance, Formatting.Indented));
|
||||
}
|
||||
}
|
||||
public static int CurrentJukeboxBgm(int position)
|
||||
|
||||
@@ -36,7 +36,8 @@
|
||||
<PackageReference Include="PeterO.Cbor" Version="4.5.5" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
||||
<PackageReference Include="Sodium.Core" Version="1.4.0" />
|
||||
<PackageReference Include="System.Text.Json" Version="9.0.8" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
18
EpinelPS/LobbyServer/Character/Counsel/CheckCounsel.cs
Normal file
18
EpinelPS/LobbyServer/Character/Counsel/CheckCounsel.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character.Counsel;
|
||||
|
||||
[PacketPath("/character/counsel/check")]
|
||||
public class CheckCounsel : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqCounseledBefore req = await ReadData<ReqCounseledBefore>();
|
||||
|
||||
ResCounseledBefore response = new();
|
||||
|
||||
response.IsCounseledBefore = false;
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
@@ -2,49 +2,98 @@ using EpinelPS.Data;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character.Counsel;
|
||||
|
||||
[PacketPath("/character/attractive/counsel")]
|
||||
public class DoCounsel : LobbyMsgHandler
|
||||
namespace EpinelPS.LobbyServer.Character.Counsel
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
[PacketPath("/character/attractive/counsel")]
|
||||
public class DoCounsel : LobbyMsgHandler
|
||||
{
|
||||
ReqCharacterCounsel req = await ReadData<ReqCharacterCounsel>();
|
||||
User user = GetUser();
|
||||
|
||||
ResCharacterCounsel response = new();
|
||||
|
||||
foreach (KeyValuePair<CurrencyType, long> currency in user.Currency)
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
response.Currencies.Add(new NetUserCurrencyData() { Type = (int)currency.Key, Value = currency.Value });
|
||||
}
|
||||
ReqCharacterCounsel req = await ReadData<ReqCharacterCounsel>();
|
||||
User user = GetUser();
|
||||
|
||||
IEnumerable<NetUserAttractiveData> currentBondInfo = user.BondInfo.Where(x => x.NameCode == req.NameCode);
|
||||
ResCharacterCounsel response = new();
|
||||
|
||||
NetUserAttractiveData data;
|
||||
|
||||
if (currentBondInfo.Any())
|
||||
{
|
||||
data = currentBondInfo.First();
|
||||
|
||||
// TODO update
|
||||
response.Attractive = data;
|
||||
}
|
||||
else
|
||||
{
|
||||
data = new()
|
||||
foreach (KeyValuePair<CurrencyType, long> currency in user.Currency)
|
||||
{
|
||||
NameCode = req.NameCode,
|
||||
// TODO
|
||||
};
|
||||
response.Currencies.Add(new NetUserCurrencyData() { Type = (int)currency.Key, Value = currency.Value });
|
||||
}
|
||||
|
||||
response.Attractive = data;
|
||||
user.BondInfo.Add(data);
|
||||
NetUserAttractiveData? currentBondInfo = user.BondInfo.FirstOrDefault(x => x.NameCode == req.NameCode);
|
||||
|
||||
if (currentBondInfo != null)
|
||||
{
|
||||
int beforeLv = currentBondInfo.Lv;
|
||||
int beforeExp = currentBondInfo.Exp;
|
||||
|
||||
currentBondInfo.Exp += 100;
|
||||
currentBondInfo.CounseledCount++;
|
||||
currentBondInfo.CanCounselToday = true; // Always allow counseling
|
||||
UpdateAttractiveLevel(currentBondInfo);
|
||||
|
||||
response.Attractive = currentBondInfo;
|
||||
response.Exp = new NetIncreaseExpData
|
||||
{
|
||||
NameCode = currentBondInfo.NameCode,
|
||||
BeforeLv = beforeLv,
|
||||
BeforeExp = beforeExp,
|
||||
CurrentLv = currentBondInfo.Lv,
|
||||
CurrentExp = currentBondInfo.Exp,
|
||||
GainExp = 100
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
NetUserAttractiveData data = new NetUserAttractiveData()
|
||||
{
|
||||
NameCode = req.NameCode,
|
||||
Exp = 100,
|
||||
CounseledCount = 1,
|
||||
IsFavorites = false,
|
||||
CanCounselToday = true,
|
||||
Lv = 1
|
||||
};
|
||||
UpdateAttractiveLevel(data);
|
||||
user.BondInfo.Add(data);
|
||||
response.Attractive = data;
|
||||
response.Exp = new NetIncreaseExpData
|
||||
{
|
||||
NameCode = data.NameCode,
|
||||
BeforeLv = 1,
|
||||
BeforeExp = 0,
|
||||
CurrentLv = 1,
|
||||
CurrentExp = 100,
|
||||
GainExp = 100
|
||||
};
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
private void UpdateAttractiveLevel(NetUserAttractiveData attractiveData)
|
||||
{
|
||||
while (attractiveData.Lv < 40)
|
||||
{
|
||||
AttractiveLevelRecord? levelInfo = GameData.Instance.AttractiveLevelTable.FirstOrDefault(x => x.Value.attractive_level == attractiveData.Lv).Value;
|
||||
|
||||
// TODO: Validate response from real server and pull info from user info
|
||||
await WriteDataAsync(response);
|
||||
if (levelInfo == null)
|
||||
{
|
||||
// No more level data
|
||||
break;
|
||||
}
|
||||
|
||||
if (attractiveData.Exp >= levelInfo.attractive_point)
|
||||
{
|
||||
attractiveData.Exp -= levelInfo.attractive_point;
|
||||
attractiveData.Lv++;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
114
EpinelPS/LobbyServer/Character/Counsel/Present.cs
Normal file
114
EpinelPS/LobbyServer/Character/Counsel/Present.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character.Counsel
|
||||
{
|
||||
[PacketPath("/character/attractive/present")]
|
||||
public class Present : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqCharacterPresent req = await ReadData<ReqCharacterPresent>();
|
||||
User user = GetUser();
|
||||
|
||||
ResCharacterPresent response = new ResCharacterPresent();
|
||||
|
||||
NetUserAttractiveData? bondInfo = user.BondInfo.FirstOrDefault(x => x.NameCode == req.NameCode);
|
||||
if (bondInfo == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int totalExpGained = 0;
|
||||
CharacterRecord? characterRecord = GameData.Instance.CharacterTable.Values.FirstOrDefault(x => x.name_code == req.NameCode);
|
||||
|
||||
foreach (NetItemData item in req.Items)
|
||||
{
|
||||
ItemMaterialRecord? materialInfo = GameData.Instance.itemMaterialTable.GetValueOrDefault(item.Tid);
|
||||
if (materialInfo != null && materialInfo.item_sub_type == "AttractiveMaterial")
|
||||
{
|
||||
int expGained = materialInfo.item_value * (int)item.Count;
|
||||
|
||||
if (characterRecord != null)
|
||||
{
|
||||
if (materialInfo.material_type == "Corporation")
|
||||
{
|
||||
string corporation = materialInfo.name_localkey.Split('_')[2];
|
||||
if (corporation.Equals(characterRecord.corporation, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
expGained *= 5;
|
||||
}
|
||||
}
|
||||
else if (materialInfo.material_type == "Squad")
|
||||
{
|
||||
string squad = materialInfo.name_localkey.Split('_')[2];
|
||||
if (squad.Equals(characterRecord.squad, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
expGained *= 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
totalExpGained += expGained;
|
||||
|
||||
ItemData? userItem = user.Items.FirstOrDefault(x => x.ItemType == item.Tid);
|
||||
if (userItem != null)
|
||||
{
|
||||
userItem.Count -= (int)item.Count;
|
||||
if (userItem.Count <= 0)
|
||||
{
|
||||
user.Items.Remove(userItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int beforeLv = bondInfo.Lv;
|
||||
int beforeExp = bondInfo.Exp;
|
||||
|
||||
bondInfo.Exp += totalExpGained;
|
||||
UpdateAttractiveLevel(bondInfo);
|
||||
|
||||
response.Attractive = bondInfo;
|
||||
response.Exp = new NetIncreaseExpData
|
||||
{
|
||||
NameCode = bondInfo.NameCode,
|
||||
BeforeLv = beforeLv,
|
||||
BeforeExp = beforeExp,
|
||||
CurrentLv = bondInfo.Lv,
|
||||
CurrentExp = bondInfo.Exp,
|
||||
GainExp = totalExpGained
|
||||
};
|
||||
|
||||
response.Items.AddRange(NetUtils.GetUserItems(user));
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
|
||||
private void UpdateAttractiveLevel(NetUserAttractiveData attractiveData)
|
||||
{
|
||||
while (attractiveData.Lv < 40)
|
||||
{
|
||||
AttractiveLevelRecord? levelInfo = GameData.Instance.AttractiveLevelTable.Values.FirstOrDefault(x => x.attractive_level == attractiveData.Lv);
|
||||
|
||||
if (levelInfo == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (attractiveData.Exp >= levelInfo.attractive_point)
|
||||
{
|
||||
attractiveData.Exp -= levelInfo.attractive_point;
|
||||
attractiveData.Lv++;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
99
EpinelPS/LobbyServer/Character/Counsel/QuickCounsel.cs
Normal file
99
EpinelPS/LobbyServer/Character/Counsel/QuickCounsel.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character.Counsel
|
||||
{
|
||||
[PacketPath("/character/counsel/quick")]
|
||||
public class QuickCounsel : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqCharacterQuickCounsel req = await ReadData<ReqCharacterQuickCounsel>();
|
||||
User user = GetUser();
|
||||
|
||||
ResCharacterQuickCounsel response = new ResCharacterQuickCounsel();
|
||||
|
||||
foreach (KeyValuePair<CurrencyType, long> currency in user.Currency)
|
||||
{
|
||||
response.Currencies.Add(new NetUserCurrencyData() { Type = (int)currency.Key, Value = currency.Value });
|
||||
}
|
||||
|
||||
NetUserAttractiveData? bondInfo = user.BondInfo.FirstOrDefault(x => x.NameCode == req.NameCode);
|
||||
|
||||
if (bondInfo != null)
|
||||
{
|
||||
int beforeLv = bondInfo.Lv;
|
||||
int beforeExp = bondInfo.Exp;
|
||||
|
||||
bondInfo.Exp += 100;
|
||||
bondInfo.CounseledCount++;
|
||||
bondInfo.CanCounselToday = true; // Always allow counseling
|
||||
UpdateAttractiveLevel(bondInfo);
|
||||
|
||||
response.Attractive = bondInfo;
|
||||
response.Exp = new NetIncreaseExpData
|
||||
{
|
||||
NameCode = bondInfo.NameCode,
|
||||
BeforeLv = beforeLv,
|
||||
BeforeExp = beforeExp,
|
||||
CurrentLv = bondInfo.Lv,
|
||||
CurrentExp = bondInfo.Exp,
|
||||
GainExp = 100
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
NetUserAttractiveData data = new NetUserAttractiveData()
|
||||
{
|
||||
NameCode = req.NameCode,
|
||||
Exp = 100,
|
||||
CounseledCount = 1,
|
||||
IsFavorites = false,
|
||||
CanCounselToday = true,
|
||||
Lv = 1
|
||||
};
|
||||
UpdateAttractiveLevel(data);
|
||||
user.BondInfo.Add(data);
|
||||
response.Attractive = data;
|
||||
response.Exp = new NetIncreaseExpData
|
||||
{
|
||||
NameCode = data.NameCode,
|
||||
BeforeLv = 1,
|
||||
BeforeExp = 0,
|
||||
CurrentLv = 1,
|
||||
CurrentExp = 100,
|
||||
GainExp = 100
|
||||
};
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
|
||||
private void UpdateAttractiveLevel(NetUserAttractiveData attractiveData)
|
||||
{
|
||||
while (attractiveData.Lv < 40)
|
||||
{
|
||||
AttractiveLevelRecord? levelInfo = GameData.Instance.AttractiveLevelTable.Values.FirstOrDefault(x => x.attractive_level == attractiveData.Lv);
|
||||
|
||||
if (levelInfo == null)
|
||||
{
|
||||
// No more level data
|
||||
break;
|
||||
}
|
||||
|
||||
if (attractiveData.Exp >= levelInfo.attractive_point)
|
||||
{
|
||||
attractiveData.Exp -= levelInfo.attractive_point;
|
||||
attractiveData.Lv++;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
@@ -19,10 +19,8 @@ namespace EpinelPS.LobbyServer.Character
|
||||
{
|
||||
response.Attractives.Add(item);
|
||||
item.CanCounselToday = true;
|
||||
item.Exp = 9999; // TODO
|
||||
item.Lv = 10;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TODO: Validate response from real server and pull info from user info
|
||||
await WriteDataAsync(response);
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data;
|
||||
using System.Linq;
|
||||
|
||||
namespace EpinelPS.LobbyServer.FavoriteItem
|
||||
{
|
||||
[PacketPath("/favoriteitem/quest/stage/clear")]
|
||||
public class ClearFavoriteItemQuestStage : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqClearFavoriteItemQuestStage req = await ReadData<ReqClearFavoriteItemQuestStage>();
|
||||
User user = GetUser();
|
||||
ResClearFavoriteItemQuestStage response = new();
|
||||
|
||||
FavoriteItemQuestStageRecord? stageData = GameData.Instance.GetFavoriteItemQuestStageData(req.StageId);
|
||||
if (stageData == null)
|
||||
{
|
||||
await WriteDataAsync(response);
|
||||
return;
|
||||
}
|
||||
|
||||
FavoriteItemQuestRecord? questData = GameData.Instance.GetFavoriteItemQuestTableData(req.FavoriteItemQuestId);
|
||||
if (questData != null)
|
||||
{
|
||||
NetUserFavoriteItemQuestData? existingQuest = user.FavoriteItemQuests.FirstOrDefault(q => q.QuestId == req.FavoriteItemQuestId);
|
||||
if (existingQuest != null) existingQuest.Clear = true;
|
||||
else user.FavoriteItemQuests.Add(new NetUserFavoriteItemQuestData { QuestId = req.FavoriteItemQuestId, Clear = true });
|
||||
|
||||
if (questData.next_quest_id > 0 && user.FavoriteItemQuests.All(q => q.QuestId != questData.next_quest_id))
|
||||
{
|
||||
user.FavoriteItemQuests.Add(new NetUserFavoriteItemQuestData { QuestId = questData.next_quest_id, Clear = false, Received = false });
|
||||
}
|
||||
}
|
||||
|
||||
string stageMapId = GameData.Instance.GetMapIdFromChapter(stageData.chapter_id, stageData.chapter_mod);
|
||||
if (!user.FieldInfoNew.ContainsKey(stageMapId))
|
||||
{
|
||||
user.FieldInfoNew.Add(stageMapId, new FieldInfoNew());
|
||||
}
|
||||
if (!user.FieldInfoNew[stageMapId].CompletedStages.Contains(req.StageId))
|
||||
{
|
||||
user.FieldInfoNew[stageMapId].CompletedStages.Add(req.StageId);
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data;
|
||||
|
||||
namespace EpinelPS.LobbyServer.FavoriteItem
|
||||
{
|
||||
[PacketPath("/favoriteitem/quest/stage/enter")]
|
||||
public class EnterFavoriteItemQuestStage : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqEnterFavoriteItemQuestStage req = await ReadData<ReqEnterFavoriteItemQuestStage>();
|
||||
User user = GetUser();
|
||||
|
||||
user.AddTrigger(TriggerType.CampaignStart, 1, req.StageId);
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
ResEnterFavoriteItemQuestStage response = new();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
46
EpinelPS/LobbyServer/FavoriteItem/EquipFavoriteItem.cs
Normal file
46
EpinelPS/LobbyServer/FavoriteItem/EquipFavoriteItem.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
namespace EpinelPS.LobbyServer.FavoriteItem
|
||||
{
|
||||
[PacketPath("/favoriteitem/equip")]
|
||||
public class EquipFavoriteItem : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqEquipFavoriteItem req = await ReadData<ReqEquipFavoriteItem>();
|
||||
User user = GetUser();
|
||||
|
||||
ResEquipFavoriteItem response = new();
|
||||
|
||||
NetUserFavoriteItemData? favoriteItemToEquip = user.FavoriteItems.FirstOrDefault(f => f.FavoriteItemId == req.FavoriteItemId);
|
||||
if (favoriteItemToEquip == null)
|
||||
{
|
||||
throw new BadHttpRequestException($"FavoriteItem with ID {req.FavoriteItemId} not found", 404);
|
||||
}
|
||||
|
||||
CharacterModel? character = user.Characters.FirstOrDefault(c => c.Csn == req.Csn);
|
||||
if (character == null)
|
||||
{
|
||||
throw new BadHttpRequestException($"Character with CSN {req.Csn} not found", 404);
|
||||
}
|
||||
|
||||
NetUserFavoriteItemData? existingEquippedItem = user.FavoriteItems.FirstOrDefault(f => f.Csn == req.Csn);
|
||||
if (existingEquippedItem != null)
|
||||
{
|
||||
existingEquippedItem.Csn = 0; // Unequip
|
||||
}
|
||||
|
||||
favoriteItemToEquip.Csn = req.Csn;
|
||||
|
||||
foreach (NetUserFavoriteItemData item in user.FavoriteItems)
|
||||
{
|
||||
response.FavoriteItems.Add(item);
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
83
EpinelPS/LobbyServer/FavoriteItem/FinishFavoriteItemQuest.cs
Normal file
83
EpinelPS/LobbyServer/FavoriteItem/FinishFavoriteItemQuest.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data;
|
||||
|
||||
namespace EpinelPS.LobbyServer.FavoriteItem
|
||||
{
|
||||
[PacketPath("/favoriteitem/quest/finish")]
|
||||
public class FinishFavoriteItemQuest : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqFinishFavoriteItemQuest req = await ReadData<ReqFinishFavoriteItemQuest>();
|
||||
User user = GetUser();
|
||||
|
||||
FavoriteItemQuestRecord? questData = GetQuestDataFromGameData(req.FavoriteItemQuestId);
|
||||
if (questData == null)
|
||||
{
|
||||
ResFinishFavoriteItemQuest errorResponse = new();
|
||||
await WriteDataAsync(errorResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
NetUserFavoriteItemQuestData? existingQuest = user.FavoriteItemQuests.FirstOrDefault(q => q.QuestId == req.FavoriteItemQuestId);
|
||||
|
||||
if (existingQuest != null)
|
||||
{
|
||||
if (existingQuest.Clear)
|
||||
{
|
||||
ResFinishFavoriteItemQuest errorResponse = new();
|
||||
await WriteDataAsync(errorResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
existingQuest.Clear = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
NetUserFavoriteItemQuestData newQuest = new NetUserFavoriteItemQuestData
|
||||
{
|
||||
QuestId = req.FavoriteItemQuestId,
|
||||
Clear = true,
|
||||
Received = false
|
||||
};
|
||||
user.FavoriteItemQuests.Add(newQuest);
|
||||
}
|
||||
|
||||
|
||||
if (questData.next_quest_id > 0)
|
||||
{
|
||||
NetUserFavoriteItemQuestData? nextQuest = user.FavoriteItemQuests.FirstOrDefault(q => q.QuestId == questData.next_quest_id);
|
||||
if (nextQuest == null)
|
||||
{
|
||||
NetUserFavoriteItemQuestData newQuest = new NetUserFavoriteItemQuestData
|
||||
{
|
||||
QuestId = questData.next_quest_id,
|
||||
Clear = false,
|
||||
Received = false
|
||||
};
|
||||
user.FavoriteItemQuests.Add(newQuest);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
ResFinishFavoriteItemQuest response = new();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
|
||||
private FavoriteItemQuestRecord? GetQuestDataFromGameData(int questId)
|
||||
{
|
||||
if (GameData.Instance.FavoriteItemQuestTable.TryGetValue(questId, out FavoriteItemQuestRecord? questRecord))
|
||||
{
|
||||
return questRecord;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,15 @@ namespace EpinelPS.LobbyServer.FavoriteItem
|
||||
ResGetFavoriteItemLibrary response = new();
|
||||
User user = GetUser();
|
||||
|
||||
foreach (NetUserFavoriteItemData favoriteItem in user.FavoriteItems)
|
||||
{
|
||||
NetFavoriteItemLibraryElement libraryElement = new NetFavoriteItemLibraryElement
|
||||
{
|
||||
Tid = favoriteItem.Tid,
|
||||
ReceivedAt = DateTime.UtcNow.Ticks // Use current time as received time
|
||||
};
|
||||
response.FavoriteItemLibrary.Add(libraryElement);
|
||||
}
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
|
||||
214
EpinelPS/LobbyServer/FavoriteItem/IncreaseExpFavoriteItem.cs
Normal file
214
EpinelPS/LobbyServer/FavoriteItem/IncreaseExpFavoriteItem.cs
Normal file
@@ -0,0 +1,214 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data;
|
||||
|
||||
namespace EpinelPS.LobbyServer.FavoriteItem
|
||||
{
|
||||
[PacketPath("/favoriteitem/increaseexp")]
|
||||
public class IncreaseExpFavoriteItem : LobbyMsgHandler
|
||||
{
|
||||
// Favorite item experience materials mapping
|
||||
private static readonly Dictionary<int, int> favoriteExpTable = new()
|
||||
{
|
||||
{ 7150001, 20 }, // 20 exp
|
||||
{ 7150002, 50 }, // 50 exp
|
||||
{ 7150003, 100 }, // 100 exp
|
||||
};
|
||||
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqIncreaseExpFavoriteItem req = await ReadData<ReqIncreaseExpFavoriteItem>();
|
||||
User user = GetUser();
|
||||
|
||||
ResIncreaseExpFavoriteItem response = new();
|
||||
|
||||
NetUserFavoriteItemData? favoriteItem = user.FavoriteItems.FirstOrDefault(f => f.FavoriteItemId == req.FavoriteItemId);
|
||||
if (favoriteItem == null)
|
||||
{
|
||||
throw new BadHttpRequestException($"FavoriteItem with ID {req.FavoriteItemId} not found", 404);
|
||||
}
|
||||
|
||||
if (req.ItemData == null)
|
||||
{
|
||||
throw new BadHttpRequestException($"No material item provided", 400);
|
||||
}
|
||||
|
||||
ItemData? userItem = user.Items.FirstOrDefault(x => x.Isn == req.ItemData.Isn);
|
||||
if (userItem == null)
|
||||
{
|
||||
throw new BadHttpRequestException($"Material item with ISN {req.ItemData.Isn} not found", 404);
|
||||
}
|
||||
|
||||
int useCount = req.ItemData.Count * req.LoopCount;
|
||||
if (userItem.Count < useCount)
|
||||
{
|
||||
throw new BadHttpRequestException($"Insufficient material. Required: {useCount}, Available: {userItem.Count}", 400);
|
||||
}
|
||||
|
||||
FavoriteItemProbabilityRecord? probabilityData = GetProbabilityData(favoriteItem.Lv, req.ItemData.Tid);
|
||||
if (probabilityData == null)
|
||||
{
|
||||
throw new BadHttpRequestException($"Cannot upgrade at current level with this material", 400);
|
||||
}
|
||||
|
||||
int baseExp = probabilityData.exp * req.LoopCount;
|
||||
bool isGreatSuccess = CheckGreatSuccess(probabilityData.great_success_rate);
|
||||
|
||||
int totalExpGained = baseExp;
|
||||
int targetLevel = favoriteItem.Lv;
|
||||
|
||||
if (isGreatSuccess)
|
||||
{
|
||||
targetLevel = probabilityData.great_success_level;
|
||||
}
|
||||
|
||||
int goldCost = baseExp * 10;
|
||||
|
||||
userItem.Count -= useCount;
|
||||
|
||||
if (user.GetCurrencyVal(CurrencyType.Gold) < goldCost)
|
||||
{
|
||||
throw new BadHttpRequestException($"Insufficient gold. Required: {goldCost}, Available: {user.GetCurrencyVal(CurrencyType.Gold)}", 400);
|
||||
}
|
||||
|
||||
int originalLevel = favoriteItem.Lv;
|
||||
int originalExp = favoriteItem.Exp;
|
||||
|
||||
if (isGreatSuccess)
|
||||
{
|
||||
favoriteItem.Lv = targetLevel;
|
||||
favoriteItem.Exp = 0; // Reset exp at target level
|
||||
}
|
||||
else
|
||||
{
|
||||
favoriteItem.Exp += totalExpGained;
|
||||
ProcessLevelUp(favoriteItem);
|
||||
}
|
||||
|
||||
user.AddCurrency(CurrencyType.Gold, -goldCost);
|
||||
|
||||
|
||||
response.FavoriteItem = favoriteItem;
|
||||
response.Result = isGreatSuccess ? FavoriteItemGreatSuccessResult.GreatSuccess : FavoriteItemGreatSuccessResult.Success;
|
||||
response.ItemData = NetUtils.ToNet(userItem);
|
||||
response.LoopCount = req.LoopCount;
|
||||
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
|
||||
private FavoriteItemProbabilityRecord? GetProbabilityData(int currentLevel, int materialId)
|
||||
{
|
||||
foreach (var record in GameData.Instance.FavoriteItemProbabilityTable.Values)
|
||||
{
|
||||
if (record.need_item_id == materialId &&
|
||||
currentLevel >= record.level_min &&
|
||||
currentLevel <= record.level_max)
|
||||
{
|
||||
return record;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool CheckGreatSuccess(int successRate)
|
||||
{
|
||||
Random random = new Random();
|
||||
int roll = random.Next(0, 10000);
|
||||
return roll < successRate;
|
||||
}
|
||||
|
||||
private void ProcessLevelUp(NetUserFavoriteItemData favoriteItem)
|
||||
{
|
||||
|
||||
if (!GameData.Instance.FavoriteItemTable.TryGetValue(favoriteItem.Tid, out FavoriteItemRecord? favoriteRecord))
|
||||
{
|
||||
var sampleTids = GameData.Instance.FavoriteItemTable.Keys.Take(5).ToList();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
string itemRarity = favoriteRecord.favorite_rare;
|
||||
if (string.IsNullOrEmpty(itemRarity))
|
||||
{
|
||||
if (favoriteItem.Tid >= 100102 && favoriteItem.Tid <= 100602 && favoriteItem.Tid % 100 == 2)
|
||||
{
|
||||
itemRarity = "SR";
|
||||
}
|
||||
else if (favoriteItem.Tid >= 100101 && favoriteItem.Tid <= 100601 && favoriteItem.Tid % 100 == 1)
|
||||
{
|
||||
itemRarity = "R";
|
||||
}
|
||||
else if (favoriteItem.Tid >= 200101 && favoriteItem.Tid <= 201301 && favoriteItem.Tid % 100 == 1)
|
||||
{
|
||||
itemRarity = "SSR";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (itemRarity == "SSR")
|
||||
{
|
||||
int ssrMaxLevel = 2; // SSR has levels 0, 1, 2
|
||||
|
||||
if (favoriteItem.Lv < ssrMaxLevel)
|
||||
{
|
||||
favoriteItem.Lv++;
|
||||
favoriteItem.Exp = 0; // SSR items don't use exp system
|
||||
}
|
||||
|
||||
if (favoriteItem.Lv >= ssrMaxLevel)
|
||||
{
|
||||
favoriteItem.Exp = 0;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var expRecords = GameData.Instance.FavoriteItemExpTable.Values
|
||||
.Where(x => x.favorite_rare == itemRarity)
|
||||
.OrderBy(x => x.level)
|
||||
.ToList();
|
||||
|
||||
|
||||
if (!expRecords.Any())
|
||||
{
|
||||
var allRarities = GameData.Instance.FavoriteItemExpTable.Values.Select(x => x.favorite_rare).Distinct();
|
||||
return;
|
||||
}
|
||||
|
||||
int maxLevel = 15;
|
||||
|
||||
while (favoriteItem.Lv < maxLevel)
|
||||
{
|
||||
int expRequired = GetExpRequiredForLevel(favoriteItem.Lv + 1, expRecords);
|
||||
|
||||
if (favoriteItem.Exp >= expRequired && expRequired > 0)
|
||||
{
|
||||
favoriteItem.Exp -= expRequired;
|
||||
favoriteItem.Lv++;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Cap excess exp if at max level
|
||||
if (favoriteItem.Lv >= maxLevel)
|
||||
{
|
||||
favoriteItem.Exp = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private int GetExpRequiredForLevel(int level, List<FavoriteItemExpRecord> expRecords)
|
||||
{
|
||||
var record = expRecords.FirstOrDefault(x => x.level == level);
|
||||
return record?.need_exp ?? 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,12 @@ namespace EpinelPS.LobbyServer.FavoriteItem
|
||||
|
||||
ResListFavoriteItem response = new();
|
||||
|
||||
// Add all user's favorite items to the response
|
||||
foreach (NetUserFavoriteItemData favoriteItem in user.FavoriteItems)
|
||||
{
|
||||
response.FavoriteItems.Add(favoriteItem);
|
||||
}
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data;
|
||||
|
||||
namespace EpinelPS.LobbyServer.FavoriteItem
|
||||
{
|
||||
@@ -12,6 +13,16 @@ namespace EpinelPS.LobbyServer.FavoriteItem
|
||||
|
||||
ResListFavoriteItemQuest response = new();
|
||||
|
||||
if (user.FavoriteItemQuests == null)
|
||||
{
|
||||
user.FavoriteItemQuests = new List<NetUserFavoriteItemQuestData>();
|
||||
}
|
||||
|
||||
foreach (NetUserFavoriteItemQuestData quest in user.FavoriteItemQuests)
|
||||
{
|
||||
response.FavoriteItemQuests.Add(quest);
|
||||
}
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data;
|
||||
|
||||
namespace EpinelPS.LobbyServer.FavoriteItem
|
||||
{
|
||||
[PacketPath("/favoriteitem/quest/obtain")]
|
||||
public class ObtainFavoriteItemQuestReward : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqObtainFavoriteItemQuestReward req = await ReadData<ReqObtainFavoriteItemQuestReward>();
|
||||
User user = GetUser();
|
||||
|
||||
FavoriteItemQuestRecord? questData = GameData.Instance.GetFavoriteItemQuestTableData(req.QuestId);
|
||||
if (questData == null)
|
||||
{
|
||||
throw new BadHttpRequestException("Quest not found");
|
||||
}
|
||||
|
||||
NetUserFavoriteItemQuestData? userQuest = user.FavoriteItemQuests.FirstOrDefault(q => q.QuestId == req.QuestId);
|
||||
if (userQuest == null || !userQuest.Clear || userQuest.Received)
|
||||
{
|
||||
throw new BadHttpRequestException("Quest not cleared or reward already received");
|
||||
}
|
||||
|
||||
List<CharacterRecord> characterRecords = GameData.Instance.CharacterTable.Values.Where(c => c.name_code == questData.name_code).ToList();
|
||||
if (!characterRecords.Any())
|
||||
{
|
||||
throw new Exception($"Failed to find character record with name_code: {questData.name_code}");
|
||||
}
|
||||
|
||||
HashSet<int> characterTids = characterRecords.Select(c => c.id).ToHashSet();
|
||||
CharacterModel? character = user.Characters.FirstOrDefault(c => characterTids.Contains(c.Tid));
|
||||
|
||||
int characterCsn = 0;
|
||||
if (character != null)
|
||||
{
|
||||
characterCsn = character.Csn;
|
||||
}
|
||||
|
||||
RewardTableRecord ? reward = GameData.Instance.GetRewardTableEntry(questData.reward_id);
|
||||
if (reward?.rewards == null || reward.rewards.Length == 0 || reward.rewards[0].reward_type != "FavoriteItem")
|
||||
{
|
||||
if (questData.reward_id > 0 && reward != null)
|
||||
{
|
||||
NetRewardData rewardData = RewardUtils.RegisterRewardsForUser(user, reward);
|
||||
ResObtainFavoriteItemQuestReward genericResponse = new ResObtainFavoriteItemQuestReward { UserReward = rewardData };
|
||||
userQuest.Received = true;
|
||||
JsonDb.Save();
|
||||
await WriteDataAsync(genericResponse);
|
||||
return;
|
||||
}
|
||||
throw new Exception("FavoriteItem reward data not found for quest");
|
||||
}
|
||||
int newItemTid = reward.rewards[0].reward_id;
|
||||
|
||||
if (character != null)
|
||||
{
|
||||
NetUserFavoriteItemData? existingEquippedItem = user.FavoriteItems.FirstOrDefault(f => f.Csn == characterCsn);
|
||||
if (existingEquippedItem != null)
|
||||
{
|
||||
user.FavoriteItems.Remove(existingEquippedItem);
|
||||
}
|
||||
}
|
||||
|
||||
NetRewardData finalRewardData = RewardUtils.RegisterRewardsForUser(user, reward);
|
||||
|
||||
if (character != null && finalRewardData.UserFavoriteItems.Count > 0)
|
||||
{
|
||||
var newFavoriteItem = user.FavoriteItems.LastOrDefault(f => f.Tid == newItemTid);
|
||||
if (newFavoriteItem != null)
|
||||
{
|
||||
newFavoriteItem.Csn = characterCsn; // Equip item by setting Csn
|
||||
}
|
||||
}
|
||||
|
||||
userQuest.Received = true;
|
||||
|
||||
ResObtainFavoriteItemQuestReward response = new ResObtainFavoriteItemQuestReward { UserReward = finalRewardData };
|
||||
|
||||
JsonDb.Save();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
32
EpinelPS/LobbyServer/FavoriteItem/StartFavoriteItemQuest.cs
Normal file
32
EpinelPS/LobbyServer/FavoriteItem/StartFavoriteItemQuest.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data;
|
||||
|
||||
namespace EpinelPS.LobbyServer.FavoriteItem
|
||||
{
|
||||
[PacketPath("/favoriteitem/quest/start")]
|
||||
public class StartFavoriteItemQuest : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqStartFavoriteItemQuest req = await ReadData<ReqStartFavoriteItemQuest>();
|
||||
User user = GetUser();
|
||||
|
||||
var newQuest = new NetUserFavoriteItemQuestData
|
||||
{
|
||||
QuestId = req.FavoriteItemQuestId,
|
||||
Clear = false,
|
||||
Received = false
|
||||
};
|
||||
|
||||
user.FavoriteItemQuests.Add(newQuest);
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
ResStartFavoriteItemQuest response = new();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
96
EpinelPS/LobbyServer/FavoriteItem/UpgradeFavoriteItem.cs
Normal file
96
EpinelPS/LobbyServer/FavoriteItem/UpgradeFavoriteItem.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data;
|
||||
|
||||
namespace EpinelPS.LobbyServer.FavoriteItem
|
||||
{
|
||||
[PacketPath("/favoriteitem/exchange")]
|
||||
public class UpgradeFavoriteItem : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqIncreaseExpFavoriteItem req = await ReadData<ReqIncreaseExpFavoriteItem>();
|
||||
User user = GetUser();
|
||||
|
||||
ResEquipFavoriteItem response = new();
|
||||
|
||||
NetUserFavoriteItemData? rFavoriteItem = user.FavoriteItems.FirstOrDefault(f => f.FavoriteItemId == req.FavoriteItemId);
|
||||
if (rFavoriteItem == null)
|
||||
{
|
||||
throw new BadHttpRequestException("Favorite item not found", 400);
|
||||
}
|
||||
|
||||
int srItemTid = rFavoriteItem.Tid + 1;
|
||||
|
||||
|
||||
NetUserFavoriteItemData? srInventoryItem = user.FavoriteItems.FirstOrDefault(f => f.Tid == srItemTid && f.Csn == 0);
|
||||
if (srInventoryItem == null)
|
||||
{
|
||||
throw new BadHttpRequestException($"No SR-grade favorite item (TID: {srItemTid}) available in inventory for exchange", 400);
|
||||
}
|
||||
|
||||
(int NewLevel, int RemainingExp, double ConversionRate) expConversion = CalculateExpConversion(rFavoriteItem.Lv, rFavoriteItem.Exp);
|
||||
|
||||
int exchangeCost = 100000; // 100k gold for exchange
|
||||
long goldBefore = user.GetCurrencyVal(CurrencyType.Gold);
|
||||
if (goldBefore < exchangeCost)
|
||||
{
|
||||
throw new BadHttpRequestException($"Insufficient gold for exchange. Required: {exchangeCost}, Available: {goldBefore}", 400);
|
||||
}
|
||||
|
||||
long equippedCharacterCsn = rFavoriteItem.Csn;
|
||||
|
||||
NetUserFavoriteItemData newSrFavoriteItem = new NetUserFavoriteItemData
|
||||
{
|
||||
FavoriteItemId = user.GenerateUniqueItemId(),
|
||||
Tid = srItemTid,
|
||||
Csn = equippedCharacterCsn, // Maintain equipment status
|
||||
Lv = expConversion.NewLevel,
|
||||
Exp = expConversion.RemainingExp
|
||||
};
|
||||
|
||||
|
||||
int itemCountBefore = user.FavoriteItems.Count;
|
||||
user.FavoriteItems.Remove(rFavoriteItem);
|
||||
user.FavoriteItems.Remove(srInventoryItem);
|
||||
int itemCountAfter = user.FavoriteItems.Count;
|
||||
user.FavoriteItems.Add(newSrFavoriteItem);
|
||||
int finalItemCount = user.FavoriteItems.Count;
|
||||
|
||||
user.AddCurrency(CurrencyType.Gold, -exchangeCost);
|
||||
long goldAfter = user.GetCurrencyVal(CurrencyType.Gold);
|
||||
|
||||
|
||||
foreach (NetUserFavoriteItemData item in user.FavoriteItems)
|
||||
{
|
||||
response.FavoriteItems.Add(item);
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
|
||||
private static (int NewLevel, int RemainingExp, double ConversionRate) CalculateExpConversion(int rLevel, int rExp)
|
||||
{
|
||||
int totalRExp = (rLevel * 1000) + rExp;
|
||||
|
||||
int totalSRExp = totalRExp;
|
||||
|
||||
int srLevel = totalSRExp / 3000; // Each SR level needs 3000 exp
|
||||
int remainingExp = totalSRExp % 3000;
|
||||
|
||||
if (srLevel > 15)
|
||||
{
|
||||
srLevel = 15;
|
||||
remainingExp = 0;
|
||||
}
|
||||
|
||||
double conversionRate = totalRExp > 0 ? (double)totalSRExp / totalRExp : 1.0;
|
||||
|
||||
return (srLevel, remainingExp, conversionRate);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
76
EpinelPS/LobbyServer/Inventory/ClearHarmonyCube.cs
Normal file
76
EpinelPS/LobbyServer/Inventory/ClearHarmonyCube.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Inventory
|
||||
{
|
||||
[PacketPath("/inventory/clearharmonycube")]
|
||||
public class ClearHarmonyCube : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqClearHarmonyCube req = await ReadData<ReqClearHarmonyCube>();
|
||||
User user = GetUser();
|
||||
|
||||
|
||||
ResClearHarmonyCube response = new();
|
||||
|
||||
foreach (ItemData item in user.Items.ToArray())
|
||||
{
|
||||
if (item.Isn == req.Isn)
|
||||
{
|
||||
if (req.Csn > 0)
|
||||
{
|
||||
if (item.CsnList.Contains(req.Csn))
|
||||
{
|
||||
item.CsnList.Remove(req.Csn);
|
||||
}
|
||||
|
||||
if (item.CsnList.Count > 0)
|
||||
{
|
||||
item.Csn = item.CsnList[0];
|
||||
if (GameData.Instance.ItemHarmonyCubeTable.TryGetValue(item.ItemType, out var harmonyCubeData))
|
||||
{
|
||||
item.Position = harmonyCubeData.location_id;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
item.Csn = 0;
|
||||
item.Position = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
item.CsnList.Clear();
|
||||
item.Csn = 0;
|
||||
item.Position = 0;
|
||||
}
|
||||
|
||||
NetUserHarmonyCubeData netHarmonyCube = new()
|
||||
{
|
||||
Isn = item.Isn,
|
||||
Tid = item.ItemType,
|
||||
Lv = item.Level
|
||||
};
|
||||
|
||||
foreach (long csn in item.CsnList)
|
||||
{
|
||||
netHarmonyCube.CsnList.Add(csn);
|
||||
}
|
||||
|
||||
if (item.Csn > 0 && !item.CsnList.Contains(item.Csn))
|
||||
{
|
||||
netHarmonyCube.CsnList.Add(item.Csn);
|
||||
}
|
||||
|
||||
response.HarmonyCube = netHarmonyCube;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
49
EpinelPS/LobbyServer/Inventory/GetHarmonyCube.cs
Normal file
49
EpinelPS/LobbyServer/Inventory/GetHarmonyCube.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Inventory
|
||||
{
|
||||
[PacketPath("/inventory/getharmonycube")]
|
||||
public class GetHarmonyCube : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqGetHarmonyCube req = await ReadData<ReqGetHarmonyCube>();
|
||||
User user = GetUser();
|
||||
|
||||
ResGetHarmonyCube response = new();
|
||||
|
||||
List<ItemData> harmonyCubes = user.Items.Where(item =>
|
||||
GameData.Instance.ItemHarmonyCubeTable.ContainsKey(item.ItemType)).ToList();
|
||||
|
||||
foreach (ItemData harmonyCube in harmonyCubes)
|
||||
{
|
||||
if (GameData.Instance.ItemHarmonyCubeTable.TryGetValue(harmonyCube.ItemType, out ItemHarmonyCubeRecord? harmonyCubeData))
|
||||
{
|
||||
NetUserHarmonyCubeData netHarmonyCube = new()
|
||||
{
|
||||
Isn = harmonyCube.Isn,
|
||||
Tid = harmonyCube.ItemType,
|
||||
Lv = harmonyCube.Level
|
||||
};
|
||||
|
||||
foreach (long csn in harmonyCube.CsnList)
|
||||
{
|
||||
netHarmonyCube.CsnList.Add(csn);
|
||||
}
|
||||
|
||||
if (harmonyCube.Csn > 0 && !harmonyCube.CsnList.Contains(harmonyCube.Csn))
|
||||
{
|
||||
netHarmonyCube.CsnList.Add(harmonyCube.Csn);
|
||||
}
|
||||
|
||||
response.HarmonyCubes.Add(netHarmonyCube);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
101
EpinelPS/LobbyServer/Inventory/LevelUpHarmonyCube.cs
Normal file
101
EpinelPS/LobbyServer/Inventory/LevelUpHarmonyCube.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Utils;
|
||||
using static EpinelPS.Data.TriggerType;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Inventory
|
||||
{
|
||||
[PacketPath("/inventory/levelupharmonycube")]
|
||||
public class LevelUpHarmonyCube : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqLevelUpHarmonyCube req = await ReadData<ReqLevelUpHarmonyCube>();
|
||||
User user = GetUser();
|
||||
|
||||
ResLevelUpHarmonyCube response = new();
|
||||
|
||||
ItemData? harmonyCubeItem = user.Items.FirstOrDefault(x => x.Isn == req.Isn);
|
||||
if (harmonyCubeItem == null)
|
||||
{
|
||||
throw new BadHttpRequestException("Harmony cube not found", 404);
|
||||
}
|
||||
|
||||
if (!GameData.Instance.ItemHarmonyCubeTable.TryGetValue(harmonyCubeItem.ItemType, out ItemHarmonyCubeRecord? harmonyCubeData))
|
||||
{
|
||||
throw new BadHttpRequestException("Item is not a harmony cube", 400);
|
||||
}
|
||||
|
||||
List<ItemHarmonyCubeLevelRecord> levelData = GameData.Instance.ItemHarmonyCubeLevelTable.Values
|
||||
.Where(x => x.level_enhance_id == harmonyCubeData.level_enhance_id)
|
||||
.OrderBy(x => x.level)
|
||||
.ToList();
|
||||
|
||||
if (levelData.Count == 0)
|
||||
{
|
||||
throw new BadHttpRequestException("No level data found for this harmony cube", 400);
|
||||
}
|
||||
|
||||
ItemHarmonyCubeLevelRecord? currentLevelData = levelData.FirstOrDefault(x => x.level == harmonyCubeItem.Level);
|
||||
if (currentLevelData == null)
|
||||
{
|
||||
throw new BadHttpRequestException("Current level data not found", 400);
|
||||
}
|
||||
|
||||
ItemHarmonyCubeLevelRecord? nextLevelData = levelData.FirstOrDefault(x => x.level == harmonyCubeItem.Level + 1);
|
||||
if (nextLevelData == null)
|
||||
{
|
||||
throw new BadHttpRequestException("Harmony cube is already at max level", 400);
|
||||
}
|
||||
|
||||
int requiredMaterialCount = nextLevelData.material_value;
|
||||
int requiredMaterialId = nextLevelData.material_id;
|
||||
int requiredGold = nextLevelData.gold_value;
|
||||
|
||||
ItemData? materialItem = user.Items.FirstOrDefault(x => x.ItemType == requiredMaterialId && x.Count >= requiredMaterialCount);
|
||||
if (materialItem == null)
|
||||
{
|
||||
throw new BadHttpRequestException($"Not enough materials. Required: {requiredMaterialCount} of item {requiredMaterialId}", 400);
|
||||
}
|
||||
|
||||
if (user.GetCurrencyVal(CurrencyType.Gold) < requiredGold)
|
||||
{
|
||||
throw new BadHttpRequestException($"Not enough gold. Required: {requiredGold}", 400);
|
||||
}
|
||||
|
||||
materialItem.Count -= requiredMaterialCount;
|
||||
if (materialItem.Count <= 0)
|
||||
{
|
||||
user.Items.Remove(materialItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
response.Items.Add(NetUtils.ToNet(materialItem));
|
||||
}
|
||||
|
||||
user.Currency[CurrencyType.Gold] -= requiredGold;
|
||||
|
||||
int originalLevel = harmonyCubeItem.Level;
|
||||
harmonyCubeItem.Level++;
|
||||
harmonyCubeItem.Exp = 0; // Reset exp for the new level
|
||||
|
||||
user.AddTrigger(TriggerType.HarmonyCubeLevel, harmonyCubeItem.Level, harmonyCubeItem.ItemType);
|
||||
|
||||
if (harmonyCubeItem.Level >= levelData.Count)
|
||||
{
|
||||
user.AddTrigger(TriggerType.HarmonyCubeLevelMax, 1, harmonyCubeItem.ItemType);
|
||||
}
|
||||
|
||||
response.Items.Add(NetUtils.ToNet(harmonyCubeItem));
|
||||
|
||||
response.Currency = new NetUserCurrencyData
|
||||
{
|
||||
Type = (int)CurrencyType.Gold,
|
||||
Value = user.GetCurrencyVal(CurrencyType.Gold)
|
||||
};
|
||||
|
||||
JsonDb.Save();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
190
EpinelPS/LobbyServer/Inventory/ManagementHarmonyCube.cs
Normal file
190
EpinelPS/LobbyServer/Inventory/ManagementHarmonyCube.cs
Normal file
@@ -0,0 +1,190 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Inventory
|
||||
{
|
||||
[PacketPath("/inventory/managementharmonycube")]
|
||||
public class ManagementHarmonyCube : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqManagementHarmonyCube req = await ReadData<ReqManagementHarmonyCube>();
|
||||
User user = GetUser();
|
||||
|
||||
ResManagementHarmonyCube response = new();
|
||||
|
||||
ItemData? harmonyCubeItem = user.Items.FirstOrDefault(x => x.Isn == req.Isn);
|
||||
if (harmonyCubeItem == null)
|
||||
{
|
||||
throw new BadHttpRequestException("Harmony cube not found", 404);
|
||||
}
|
||||
|
||||
if (!GameData.Instance.ItemHarmonyCubeTable.TryGetValue(harmonyCubeItem.ItemType, out ItemHarmonyCubeRecord? harmonyCubeData))
|
||||
{
|
||||
throw new BadHttpRequestException("Item is not a harmony cube", 400);
|
||||
}
|
||||
|
||||
ItemHarmonyCubeLevelRecord? currentLevelData = GetCurrentLevelData(harmonyCubeItem, harmonyCubeData);
|
||||
int maxSlots = currentLevelData?.slot ?? 1;
|
||||
|
||||
foreach (long clearCsn in req.Clears)
|
||||
{
|
||||
if (harmonyCubeItem.CsnList.Contains(clearCsn))
|
||||
{
|
||||
harmonyCubeItem.CsnList.Remove(clearCsn);
|
||||
}
|
||||
|
||||
if (harmonyCubeItem.Csn == clearCsn)
|
||||
{
|
||||
harmonyCubeItem.Csn = 0;
|
||||
harmonyCubeItem.Position = 0;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (NetWearHarmonyCubeData wearData in req.Wears)
|
||||
{
|
||||
long targetCsn = wearData.Csn;
|
||||
long swapCsn = wearData.SwapCsn;
|
||||
|
||||
if (swapCsn > 0)
|
||||
{
|
||||
if (harmonyCubeItem.CsnList.Contains(swapCsn))
|
||||
{
|
||||
harmonyCubeItem.CsnList.Remove(swapCsn);
|
||||
}
|
||||
|
||||
if (harmonyCubeItem.Csn == swapCsn)
|
||||
{
|
||||
harmonyCubeItem.Csn = 0;
|
||||
harmonyCubeItem.Position = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (targetCsn > 0)
|
||||
{
|
||||
EquipHarmonyCubeToCharacter(user, harmonyCubeItem, harmonyCubeData, targetCsn, maxSlots);
|
||||
}
|
||||
}
|
||||
|
||||
List<ItemData> allHarmonyCubes = user.Items.Where(item =>
|
||||
GameData.Instance.ItemHarmonyCubeTable.ContainsKey(item.ItemType)).ToList();
|
||||
|
||||
foreach (ItemData harmonyCube in allHarmonyCubes)
|
||||
{
|
||||
NetUserHarmonyCubeData netHarmonyCube = new()
|
||||
{
|
||||
Isn = harmonyCube.Isn,
|
||||
Tid = harmonyCube.ItemType,
|
||||
Lv = harmonyCube.Level
|
||||
};
|
||||
|
||||
foreach (long csn in harmonyCube.CsnList)
|
||||
{
|
||||
netHarmonyCube.CsnList.Add(csn);
|
||||
}
|
||||
|
||||
if (harmonyCube.Csn > 0 && !harmonyCube.CsnList.Contains(harmonyCube.Csn))
|
||||
{
|
||||
netHarmonyCube.CsnList.Add(harmonyCube.Csn);
|
||||
}
|
||||
|
||||
response.HarmonyCubes.Add(netHarmonyCube);
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
|
||||
private void EquipHarmonyCubeToCharacter(User user, ItemData harmonyCubeItem, ItemHarmonyCubeRecord harmonyCubeData, long targetCsn, int maxSlots)
|
||||
{
|
||||
if (harmonyCubeItem.CsnList.Contains(targetCsn))
|
||||
{
|
||||
return; // Already equipped, skip
|
||||
}
|
||||
|
||||
if (harmonyCubeItem.CsnList.Count >= maxSlots)
|
||||
{
|
||||
throw new BadHttpRequestException($"Harmony cube slot limit reached. Current level allows {maxSlots} characters.", 400);
|
||||
}
|
||||
|
||||
CharacterModel? character = user.GetCharacterBySerialNumber(targetCsn);
|
||||
if (character == null)
|
||||
{
|
||||
throw new BadHttpRequestException($"Character {targetCsn} not found", 404);
|
||||
}
|
||||
|
||||
if (!IsClassCompatible(character, harmonyCubeData))
|
||||
{
|
||||
throw new BadHttpRequestException($"Character class incompatible with harmony cube", 400);
|
||||
}
|
||||
|
||||
CleanupCharacterFromAllHarmonyCubes(user, targetCsn, harmonyCubeData.location_id, harmonyCubeItem.Isn);
|
||||
|
||||
harmonyCubeItem.CsnList.Add(targetCsn);
|
||||
|
||||
if (harmonyCubeItem.CsnList.Count == 1)
|
||||
{
|
||||
harmonyCubeItem.Csn = targetCsn;
|
||||
harmonyCubeItem.Position = harmonyCubeData.location_id;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void CleanupCharacterFromAllHarmonyCubes(User user, long targetCsn, int position, long excludeIsn)
|
||||
{
|
||||
foreach (ItemData item in user.Items.ToArray())
|
||||
{
|
||||
if (!GameData.Instance.ItemHarmonyCubeTable.ContainsKey(item.ItemType) ||
|
||||
item.Isn == excludeIsn)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!GameData.Instance.ItemHarmonyCubeTable.TryGetValue(item.ItemType, out ItemHarmonyCubeRecord? existingHarmonyCubeData))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bool wasInCsnList = item.CsnList.Contains(targetCsn);
|
||||
bool wasInLegacyCsn = item.Csn == targetCsn;
|
||||
|
||||
if (wasInCsnList || wasInLegacyCsn)
|
||||
{
|
||||
item.CsnList.Remove(targetCsn);
|
||||
|
||||
if (item.CsnList.Count > 0)
|
||||
{
|
||||
item.Csn = item.CsnList[0];
|
||||
item.Position = existingHarmonyCubeData.location_id;
|
||||
}
|
||||
else
|
||||
{
|
||||
item.Csn = 0;
|
||||
item.Position = 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ItemHarmonyCubeLevelRecord? GetCurrentLevelData(ItemData harmonyCubeItem, ItemHarmonyCubeRecord harmonyCubeData)
|
||||
{
|
||||
List<ItemHarmonyCubeLevelRecord> levelData = GameData.Instance.ItemHarmonyCubeLevelTable.Values
|
||||
.Where(x => x.level_enhance_id == harmonyCubeData.level_enhance_id)
|
||||
.OrderBy(x => x.level)
|
||||
.ToList();
|
||||
|
||||
return levelData.FirstOrDefault(x => x.level == harmonyCubeItem.Level);
|
||||
}
|
||||
|
||||
private bool IsClassCompatible(CharacterModel character, ItemHarmonyCubeRecord harmonyCubeData)
|
||||
{
|
||||
if (GameData.Instance.CharacterTable.TryGetValue(character.Tid, out CharacterRecord? characterData))
|
||||
{
|
||||
return harmonyCubeData.@class == "All" || harmonyCubeData.@class == characterData.character_class;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
241
EpinelPS/LobbyServer/Inventory/WearHarmonyCube.cs
Normal file
241
EpinelPS/LobbyServer/Inventory/WearHarmonyCube.cs
Normal file
@@ -0,0 +1,241 @@
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Inventory
|
||||
{
|
||||
[PacketPath("/inventory/wearharmonycube")]
|
||||
public class WearHarmonyCube : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqWearHarmonyCube req = await ReadData<ReqWearHarmonyCube>();
|
||||
User user = GetUser();
|
||||
ResWearHarmonyCube response = new();
|
||||
|
||||
ItemData? harmonyCubeItem = user.Items.FirstOrDefault(x => x.Isn == req.Isn);
|
||||
if (harmonyCubeItem == null)
|
||||
{
|
||||
throw new BadHttpRequestException("Harmony cube not found", 404);
|
||||
}
|
||||
|
||||
if (!GameData.Instance.ItemHarmonyCubeTable.TryGetValue(harmonyCubeItem.ItemType, out ItemHarmonyCubeRecord? harmonyCubeData))
|
||||
{
|
||||
throw new BadHttpRequestException("Item is not a harmony cube", 400);
|
||||
}
|
||||
|
||||
if (req.Wear == null)
|
||||
{
|
||||
throw new BadHttpRequestException("Wear data is required", 400);
|
||||
}
|
||||
|
||||
long targetCsn = req.Wear.Csn;
|
||||
long swapCsn = req.Wear.SwapCsn;
|
||||
|
||||
ItemHarmonyCubeLevelRecord? currentLevelData = GetCurrentLevelData(harmonyCubeItem, harmonyCubeData);
|
||||
int maxSlots = currentLevelData?.slot ?? 1;
|
||||
|
||||
if (swapCsn > 0)
|
||||
{
|
||||
if (harmonyCubeItem.CsnList.Contains(swapCsn))
|
||||
{
|
||||
harmonyCubeItem.CsnList.Remove(swapCsn);
|
||||
}
|
||||
|
||||
if (harmonyCubeItem.Csn == swapCsn)
|
||||
{
|
||||
harmonyCubeItem.Csn = 0;
|
||||
harmonyCubeItem.Position = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
List<ItemData> modifiedItems = new();
|
||||
if (targetCsn > 0)
|
||||
{
|
||||
modifiedItems = EquipHarmonyCubeToCharacter(user, harmonyCubeItem, harmonyCubeData, targetCsn, maxSlots);
|
||||
}
|
||||
else if (targetCsn == 0)
|
||||
{
|
||||
harmonyCubeItem.CsnList.Clear();
|
||||
harmonyCubeItem.Csn = 0;
|
||||
harmonyCubeItem.Position = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new BadHttpRequestException("Invalid character CSN", 400);
|
||||
}
|
||||
|
||||
foreach (ItemData modifiedItem in modifiedItems)
|
||||
{
|
||||
NetUserHarmonyCubeData netModifiedHarmonyCube = new()
|
||||
{
|
||||
Isn = modifiedItem.Isn,
|
||||
Tid = modifiedItem.ItemType,
|
||||
Lv = modifiedItem.Level
|
||||
};
|
||||
|
||||
foreach (long csn in modifiedItem.CsnList)
|
||||
{
|
||||
netModifiedHarmonyCube.CsnList.Add(csn);
|
||||
}
|
||||
|
||||
if (modifiedItem.Csn > 0 && !modifiedItem.CsnList.Contains(modifiedItem.Csn))
|
||||
{
|
||||
netModifiedHarmonyCube.CsnList.Add(modifiedItem.Csn);
|
||||
}
|
||||
|
||||
response.HarmonyCubes.Add(netModifiedHarmonyCube);
|
||||
}
|
||||
|
||||
NetUserHarmonyCubeData netHarmonyCube = new()
|
||||
{
|
||||
Isn = harmonyCubeItem.Isn,
|
||||
Tid = harmonyCubeItem.ItemType,
|
||||
Lv = harmonyCubeItem.Level
|
||||
};
|
||||
|
||||
foreach (long csn in harmonyCubeItem.CsnList)
|
||||
{
|
||||
netHarmonyCube.CsnList.Add(csn);
|
||||
}
|
||||
|
||||
if (harmonyCubeItem.Csn > 0 && !harmonyCubeItem.CsnList.Contains(harmonyCubeItem.Csn))
|
||||
{
|
||||
netHarmonyCube.CsnList.Add(harmonyCubeItem.Csn);
|
||||
}
|
||||
|
||||
response.HarmonyCubes.Add(netHarmonyCube);
|
||||
|
||||
JsonDb.Save();
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
|
||||
private List<ItemData> EquipHarmonyCubeToCharacter(User user, ItemData harmonyCubeItem, ItemHarmonyCubeRecord harmonyCubeData, long targetCsn, int maxSlots)
|
||||
{
|
||||
|
||||
// Check if already equipped to this character
|
||||
if (harmonyCubeItem.CsnList.Contains(targetCsn))
|
||||
{
|
||||
Console.WriteLine($"Harmony cube {harmonyCubeItem.ItemType} already equipped to character {targetCsn}");
|
||||
return new List<ItemData>(); // Already equipped, no need to do anything
|
||||
}
|
||||
|
||||
// Check slot limit
|
||||
if (harmonyCubeItem.CsnList.Count >= maxSlots)
|
||||
{
|
||||
throw new BadHttpRequestException($"Harmony cube slot limit reached. Current level allows {maxSlots} characters.", 400);
|
||||
}
|
||||
|
||||
// Check if the character exists
|
||||
CharacterModel? character = user.GetCharacterBySerialNumber(targetCsn);
|
||||
if (character == null)
|
||||
{
|
||||
throw new BadHttpRequestException($"Character {targetCsn} not found", 404);
|
||||
}
|
||||
|
||||
// Check class compatibility
|
||||
if (!IsClassCompatible(character, harmonyCubeData))
|
||||
{
|
||||
throw new BadHttpRequestException($"Character class incompatible with harmony cube", 400);
|
||||
}
|
||||
|
||||
// CRITICAL: Remove this character from ALL harmony cubes at the same position
|
||||
// This fixes any existing data inconsistency where a character might be in multiple CsnLists
|
||||
List<ItemData> modifiedItems = CleanupCharacterFromAllHarmonyCubes(user, targetCsn, harmonyCubeData.location_id, harmonyCubeItem.Isn);
|
||||
|
||||
// Add to CsnList
|
||||
harmonyCubeItem.CsnList.Add(targetCsn);
|
||||
|
||||
// For backward compatibility, also set legacy fields if this is the first character
|
||||
if (harmonyCubeItem.CsnList.Count == 1)
|
||||
{
|
||||
harmonyCubeItem.Csn = targetCsn;
|
||||
harmonyCubeItem.Position = harmonyCubeData.location_id;
|
||||
}
|
||||
|
||||
Console.WriteLine($"Equipped harmony cube {harmonyCubeItem.ItemType} to character {targetCsn} for user {user.Username} (slot {harmonyCubeItem.CsnList.Count}/{maxSlots})");
|
||||
|
||||
return modifiedItems;
|
||||
}
|
||||
|
||||
private List<ItemData> CleanupCharacterFromAllHarmonyCubes(User user, long targetCsn, int position, long excludeIsn)
|
||||
{
|
||||
// Remove this character from ALL harmony cubes (all positions)
|
||||
// This ensures one character can only have one harmony cube equipped at any time
|
||||
List<ItemData> modifiedItems = new();
|
||||
|
||||
foreach (ItemData item in user.Items.ToArray())
|
||||
{
|
||||
// Skip if it's not a harmony cube or it's the item we're about to equip
|
||||
if (!GameData.Instance.ItemHarmonyCubeTable.ContainsKey(item.ItemType) ||
|
||||
item.Isn == excludeIsn)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the harmony cube data
|
||||
if (!GameData.Instance.ItemHarmonyCubeTable.TryGetValue(item.ItemType, out ItemHarmonyCubeRecord? existingHarmonyCubeData))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check ALL harmony cubes (not just same position) - ONE CHARACTER, ONE HARMONY CUBE RULE
|
||||
// Check if this character is in the CsnList or legacy Csn field
|
||||
bool wasInCsnList = item.CsnList.Contains(targetCsn);
|
||||
bool wasInLegacyCsn = (item.Csn == targetCsn);
|
||||
|
||||
if (wasInCsnList || wasInLegacyCsn)
|
||||
{
|
||||
// Remove from CsnList
|
||||
item.CsnList.Remove(targetCsn);
|
||||
|
||||
// Update legacy fields
|
||||
if (item.CsnList.Count > 0)
|
||||
{
|
||||
// Set legacy fields to the first remaining character
|
||||
item.Csn = item.CsnList[0];
|
||||
item.Position = existingHarmonyCubeData.location_id;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No characters left, clear legacy fields
|
||||
item.Csn = 0;
|
||||
item.Position = 0;
|
||||
}
|
||||
|
||||
// Add to modified items list for response
|
||||
modifiedItems.Add(item);
|
||||
|
||||
Console.WriteLine($"[ONE HARMONY CUBE RULE] Removed character {targetCsn} from harmony cube {item.ItemType} (position {existingHarmonyCubeData.location_id}) - one character can only have one harmony cube");
|
||||
}
|
||||
}
|
||||
|
||||
return modifiedItems;
|
||||
}
|
||||
|
||||
private ItemHarmonyCubeLevelRecord? GetCurrentLevelData(ItemData harmonyCubeItem, ItemHarmonyCubeRecord harmonyCubeData)
|
||||
{
|
||||
// Get level data for this harmony cube
|
||||
List<ItemHarmonyCubeLevelRecord> levelData = GameData.Instance.ItemHarmonyCubeLevelTable.Values
|
||||
.Where(x => x.level_enhance_id == harmonyCubeData.level_enhance_id)
|
||||
.OrderBy(x => x.level)
|
||||
.ToList();
|
||||
|
||||
// Find current level data
|
||||
return levelData.FirstOrDefault(x => x.level == harmonyCubeItem.Level);
|
||||
}
|
||||
|
||||
private bool IsClassCompatible(CharacterModel character, ItemHarmonyCubeRecord harmonyCubeData)
|
||||
{
|
||||
// Get character data to check class
|
||||
if (GameData.Instance.CharacterTable.TryGetValue(character.Tid, out CharacterRecord? characterData))
|
||||
{
|
||||
// Check if harmony cube class restriction matches character class
|
||||
return harmonyCubeData.@class == "All" || harmonyCubeData.@class == characterData.character_class;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Utils;
|
||||
|
||||
@@ -67,18 +67,47 @@ namespace EpinelPS.LobbyServer.LobbyUser
|
||||
response.TypeTeams.Add(teamInfo.Value);
|
||||
}
|
||||
|
||||
// TODO: Save outpost data
|
||||
response.Outposts.Add(new NetUserOutpostData() { SlotId = 1, BuildingId = 22401, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 });
|
||||
response.Outposts.Add(new NetUserOutpostData() { SlotId = 4, BuildingId = 22701, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 });
|
||||
response.Outposts.Add(new NetUserOutpostData() { SlotId = 5, BuildingId = 22801, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 });
|
||||
response.Outposts.Add(new NetUserOutpostData() { SlotId = 6, BuildingId = 22901, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 });
|
||||
response.Outposts.Add(new NetUserOutpostData() { SlotId = 7, BuildingId = 23001, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 });
|
||||
response.Outposts.Add(new NetUserOutpostData() { SlotId = 3, BuildingId = 23101, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 });
|
||||
response.Outposts.Add(new NetUserOutpostData() { SlotId = 2, BuildingId = 23201, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 });
|
||||
response.Outposts.Add(new NetUserOutpostData() { SlotId = 9, BuildingId = 23301, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 });
|
||||
response.Outposts.Add(new NetUserOutpostData() { SlotId = 8, BuildingId = 23401, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 });
|
||||
response.Outposts.Add(new NetUserOutpostData() { SlotId = 10, BuildingId = 23501, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 });
|
||||
response.Outposts.Add(new NetUserOutpostData() { SlotId = 38, BuildingId = 33601, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 });
|
||||
if (user.OutpostBuildings != null && user.OutpostBuildings.Count > 0)
|
||||
{
|
||||
bool needsSave = false;
|
||||
foreach (NetUserOutpostData building in user.OutpostBuildings)
|
||||
{
|
||||
|
||||
if (!building.IsDone && DateTime.UtcNow.Ticks >= building.CompleteAt)
|
||||
{
|
||||
building.IsDone = true;
|
||||
needsSave = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (needsSave)
|
||||
{
|
||||
JsonDb.Save();
|
||||
}
|
||||
|
||||
response.Outposts.AddRange(user.OutpostBuildings);
|
||||
}
|
||||
else
|
||||
{
|
||||
List<NetUserOutpostData> defaultBuildings = new List<NetUserOutpostData>
|
||||
{
|
||||
new() { SlotId = 1, BuildingId = 22401, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 },
|
||||
new() { SlotId = 4, BuildingId = 22701, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 },
|
||||
new() { SlotId = 5, BuildingId = 22801, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 },
|
||||
new() { SlotId = 6, BuildingId = 22901, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 },
|
||||
new() { SlotId = 7, BuildingId = 23001, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 },
|
||||
new() { SlotId = 3, BuildingId = 23101, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 },
|
||||
new() { SlotId = 2, BuildingId = 23201, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 },
|
||||
new() { SlotId = 9, BuildingId = 23301, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 },
|
||||
new() { SlotId = 8, BuildingId = 23401, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 },
|
||||
new() { SlotId = 10, BuildingId = 23501, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 },
|
||||
new() { SlotId = 38, BuildingId = 33601, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 }
|
||||
};
|
||||
|
||||
response.Outposts.AddRange(defaultBuildings);
|
||||
user.OutpostBuildings = defaultBuildings;
|
||||
JsonDb.Save();
|
||||
}
|
||||
|
||||
response.LastClearedNormalMainStageId = user.LastNormalStageCleared;
|
||||
response.TimeRewardBuffs.AddRange(NetUtils.GetOutpostTimeReward(user));
|
||||
|
||||
@@ -14,8 +14,24 @@ namespace EpinelPS.LobbyServer.Messenger
|
||||
|
||||
ResEnterMessengerDialog response = new();
|
||||
|
||||
MessengerMsgConditionRecord opener = GameData.Instance.MessageConditions[req.Tid];
|
||||
KeyValuePair<string, MessengerDialogRecord> conversation = GameData.Instance.Messages.Where(x => x.Value.conversation_id == opener.tid && x.Value.is_opener).First();
|
||||
if (!GameData.Instance.MessageConditions.TryGetValue(req.Tid, out MessengerMsgConditionRecord? opener))
|
||||
{
|
||||
throw new BadHttpRequestException($"Message condition {req.Tid} not found", 404);
|
||||
}
|
||||
|
||||
KeyValuePair<string, MessengerDialogRecord> conversation = GameData.Instance.Messages.FirstOrDefault(x =>
|
||||
x.Value.conversation_id == opener.tid && x.Value.is_opener);
|
||||
|
||||
if (conversation.Value == null)
|
||||
{
|
||||
conversation = GameData.Instance.Messages.FirstOrDefault(x =>
|
||||
x.Value.conversation_id == opener.tid);
|
||||
|
||||
if (conversation.Value == null)
|
||||
{
|
||||
throw new BadHttpRequestException($"No conversation found for {opener.tid}", 404);
|
||||
}
|
||||
}
|
||||
|
||||
response.Message = user.CreateMessage(conversation.Value);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Messenger
|
||||
{
|
||||
@@ -10,6 +11,8 @@ namespace EpinelPS.LobbyServer.Messenger
|
||||
ReqGetMessages req = await ReadData<ReqGetMessages>();
|
||||
User user = GetUser();
|
||||
|
||||
CheckAndCreateAvailableMessages(user);
|
||||
|
||||
ResGetMessages response = new();
|
||||
|
||||
IEnumerable<NetMessage> newMessages = user.MessengerData.Where(x => x.Seq >= req.Seq);
|
||||
@@ -21,5 +24,71 @@ namespace EpinelPS.LobbyServer.Messenger
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
|
||||
private static void CheckAndCreateAvailableMessages(User user)
|
||||
{
|
||||
foreach (KeyValuePair<int, MessengerMsgConditionRecord> messageCondition in GameData.Instance.MessageConditions)
|
||||
{
|
||||
int conditionId = messageCondition.Key;
|
||||
MessengerMsgConditionRecord msgCondition = messageCondition.Value;
|
||||
|
||||
if (IsMessageConditionSatisfied(user, conditionId))
|
||||
{
|
||||
bool messageExists = user.MessengerData.Any(m => m.ConversationId == msgCondition.tid);
|
||||
if (!messageExists)
|
||||
{
|
||||
KeyValuePair<string, MessengerDialogRecord> conversation = GameData.Instance.Messages.FirstOrDefault(x =>
|
||||
x.Value.conversation_id == msgCondition.tid && x.Value.is_opener);
|
||||
|
||||
if (conversation.Value != null)
|
||||
{
|
||||
user.CreateMessage(conversation.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsMessageConditionSatisfied(User user, int conditionId)
|
||||
{
|
||||
if (!GameData.Instance.MessageConditions.TryGetValue(conditionId, out MessengerMsgConditionRecord? msgCondition))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (MessengerConditionTriggerList trigger in msgCondition.trigger_list)
|
||||
{
|
||||
if (trigger.trigger == "None" || trigger.condition_id == 0)
|
||||
continue;
|
||||
|
||||
if (!CheckTriggerCondition(user, trigger))
|
||||
{
|
||||
return false; // All conditions must be satisfied
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool CheckTriggerCondition(User user, MessengerConditionTriggerList trigger)
|
||||
{
|
||||
TriggerType triggerType = ParseTriggerType(trigger.trigger);
|
||||
|
||||
return user.Triggers.Any(t =>
|
||||
t.Type == triggerType &&
|
||||
t.ConditionId == trigger.condition_id &&
|
||||
t.Value >= trigger.condition_value);
|
||||
}
|
||||
|
||||
private static TriggerType ParseTriggerType(string triggerString)
|
||||
{
|
||||
return triggerString switch
|
||||
{
|
||||
"ObtainCharacter" => TriggerType.ObtainCharacter,
|
||||
"MainQuestClear" => TriggerType.MainQuestClear,
|
||||
"MessageClear" => TriggerType.MessageClear,
|
||||
_ => TriggerType.None
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Database;
|
||||
using EpinelPS.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -10,17 +12,132 @@ namespace EpinelPS.LobbyServer.Outpost
|
||||
[PacketPath("/outpost/building")]
|
||||
public class BuildBuilding : LobbyMsgHandler
|
||||
{
|
||||
private static BuildingCost GetBuildingCost(int buildingId)
|
||||
{
|
||||
// TODO: get building cost from data
|
||||
// test data
|
||||
return buildingId switch
|
||||
{
|
||||
// bulidding (22xxx)
|
||||
22401 => new BuildingCost { Gold = 1000, BuildTimeMinutes = 5 },
|
||||
22701 => new BuildingCost { Gold = 2000, BuildTimeMinutes = 10 },
|
||||
22801 => new BuildingCost { Gold = 1500, BuildTimeMinutes = 8 },
|
||||
22901 => new BuildingCost { Gold = 3000, BuildTimeMinutes = 15 },
|
||||
23001 => new BuildingCost { Gold = 5000, BuildTimeMinutes = 20 },
|
||||
23100 => new BuildingCost { Gold = 800, BuildTimeMinutes = 6 },
|
||||
23200 => new BuildingCost { Gold = 1200, BuildTimeMinutes = 8 },
|
||||
23300 => new BuildingCost { Gold = 2500, BuildTimeMinutes = 12 },
|
||||
23400 => new BuildingCost { Gold = 2000, BuildTimeMinutes = 10 },
|
||||
23500 => new BuildingCost { Gold = 1800, BuildTimeMinutes = 9 },
|
||||
|
||||
// bulidding (10xxx-12xxx)
|
||||
10100 => new BuildingCost { Gold = 500, BuildTimeMinutes = 3 },
|
||||
10200 => new BuildingCost { Gold = 800, BuildTimeMinutes = 5 },
|
||||
10300 => new BuildingCost { Gold = 1200, BuildTimeMinutes = 7 },
|
||||
10400 => new BuildingCost { Gold = 900, BuildTimeMinutes = 6 },
|
||||
10500 => new BuildingCost { Gold = 700, BuildTimeMinutes = 4 },
|
||||
10600 => new BuildingCost { Gold = 1500, BuildTimeMinutes = 8 },
|
||||
10700 => new BuildingCost { Gold = 1100, BuildTimeMinutes = 7 },
|
||||
10800 => new BuildingCost { Gold = 1300, BuildTimeMinutes = 8 },
|
||||
10900 => new BuildingCost { Gold = 1600, BuildTimeMinutes = 9 },
|
||||
11000 => new BuildingCost { Gold = 1000, BuildTimeMinutes = 6 },
|
||||
11100 => new BuildingCost { Gold = 1400, BuildTimeMinutes = 8 },
|
||||
11200 => new BuildingCost { Gold = 1800, BuildTimeMinutes = 10 },
|
||||
11300 => new BuildingCost { Gold = 800, BuildTimeMinutes = 5 },
|
||||
11400 => new BuildingCost { Gold = 2000, BuildTimeMinutes = 11 },
|
||||
11500 => new BuildingCost { Gold = 1200, BuildTimeMinutes = 7 },
|
||||
11600 => new BuildingCost { Gold = 1700, BuildTimeMinutes = 9 },
|
||||
11700 => new BuildingCost { Gold = 1300, BuildTimeMinutes = 8 },
|
||||
11800 => new BuildingCost { Gold = 900, BuildTimeMinutes = 6 },
|
||||
11900 => new BuildingCost { Gold = 2200, BuildTimeMinutes = 12 },
|
||||
12000 => new BuildingCost { Gold = 1500, BuildTimeMinutes = 9 },
|
||||
12100 => new BuildingCost { Gold = 3000, BuildTimeMinutes = 15 },
|
||||
12200 => new BuildingCost { Gold = 2800, BuildTimeMinutes = 14 },
|
||||
12300 => new BuildingCost { Gold = 1600, BuildTimeMinutes = 9 },
|
||||
|
||||
// default building cost
|
||||
_ => new BuildingCost { Gold = 1000, BuildTimeMinutes = 5 }
|
||||
};
|
||||
}
|
||||
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqBuilding req = await ReadData<ReqBuilding>();
|
||||
User user = GetUser();
|
||||
|
||||
|
||||
BuildingCost cost = GetBuildingCost(req.BuildingId);
|
||||
|
||||
if (!user.CanSubtractCurrency(CurrencyType.Gold, cost.Gold))
|
||||
{
|
||||
|
||||
ResBuilding errorResponse = new()
|
||||
{
|
||||
StartAt = 0,
|
||||
CompleteAt = 0
|
||||
};
|
||||
await WriteDataAsync(errorResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
bool goldDeducted = user.SubtractCurrency(CurrencyType.Gold, cost.Gold);
|
||||
if (!goldDeducted)
|
||||
{
|
||||
ResBuilding errorResponse = new()
|
||||
{
|
||||
StartAt = 0,
|
||||
CompleteAt = 0
|
||||
};
|
||||
await WriteDataAsync(errorResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
DateTime startTime = DateTime.UtcNow;
|
||||
DateTime completeTime = startTime.AddMinutes(cost.BuildTimeMinutes);
|
||||
|
||||
NetUserOutpostData newBuilding = new NetUserOutpostData()
|
||||
{
|
||||
SlotId = req.PositionId,
|
||||
BuildingId = req.BuildingId,
|
||||
IsDone = false,
|
||||
StartAt = startTime.Ticks,
|
||||
CompleteAt = completeTime.Ticks
|
||||
};
|
||||
|
||||
bool found = false;
|
||||
for (int i = 0; i < user.OutpostBuildings.Count; i++)
|
||||
{
|
||||
if (user.OutpostBuildings[i].SlotId == req.PositionId)
|
||||
{
|
||||
user.OutpostBuildings[i] = newBuilding;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
user.OutpostBuildings.Add(newBuilding);
|
||||
}
|
||||
|
||||
JsonDb.Save();
|
||||
|
||||
ResBuilding response = new()
|
||||
{
|
||||
StartAt = DateTime.UtcNow.Ticks,
|
||||
CompleteAt = DateTime.UtcNow.AddDays(1).Ticks
|
||||
StartAt = newBuilding.StartAt,
|
||||
CompleteAt = newBuilding.CompleteAt
|
||||
};
|
||||
// TODO
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
|
||||
public class BuildingCost
|
||||
{
|
||||
public long Gold { get; set; } = 0;
|
||||
public int BuildTimeMinutes { get; set; } = 5;
|
||||
// public long Materials { get; set; } = 0;
|
||||
// public long SpecialCurrency { get; set; } = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Outpost
|
||||
{
|
||||
@@ -10,7 +11,22 @@ namespace EpinelPS.LobbyServer.Outpost
|
||||
ReqCheckReceiveInfraCoreReward req = await ReadData<ReqCheckReceiveInfraCoreReward>();
|
||||
ResCheckReceiveInfraCoreReward response = new();
|
||||
|
||||
// TODO
|
||||
User user = GetUser();
|
||||
|
||||
bool isReceived = false;
|
||||
|
||||
int currentLevel = user.InfraCoreLvl;
|
||||
|
||||
Dictionary<int, InfracoreRecord> gradeTable = GameData.Instance.InfracoreTable;
|
||||
if (gradeTable.TryGetValue(currentLevel, out var gradeData))
|
||||
{
|
||||
if (gradeData.reward_id > 0)
|
||||
{
|
||||
isReceived = user.InfraCoreRewardReceived.ContainsKey(currentLevel) && user.InfraCoreRewardReceived[currentLevel];
|
||||
}
|
||||
}
|
||||
|
||||
response.IsReceived = isReceived;
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data;
|
||||
using EpinelPS.Database;
|
||||
namespace EpinelPS.LobbyServer.Outpost
|
||||
{
|
||||
[PacketPath("/outpost/getoutpostdata")]
|
||||
@@ -37,17 +38,41 @@ namespace EpinelPS.LobbyServer.Outpost
|
||||
response.OutpostBattleLevel = user.OutpostBattleLevel;
|
||||
response.OutpostBattleTime = new NetOutpostBattleTime() { MaxBattleTime = 864000000000, MaxOverBattleTime = 12096000000000, BattleTime = battleTimeMs, OverBattleTime = overBattleTime };
|
||||
|
||||
response.Data.Add(new NetUserOutpostData() { SlotId = 1, BuildingId = 22401, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 });
|
||||
response.Data.Add(new NetUserOutpostData() { SlotId = 4, BuildingId = 22701, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 });
|
||||
response.Data.Add(new NetUserOutpostData() { SlotId = 5, BuildingId = 22801, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 });
|
||||
response.Data.Add(new NetUserOutpostData() { SlotId = 6, BuildingId = 22901, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 });
|
||||
response.Data.Add(new NetUserOutpostData() { SlotId = 7, BuildingId = 23001, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 });
|
||||
response.Data.Add(new NetUserOutpostData() { SlotId = 3, BuildingId = 23101, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 });
|
||||
response.Data.Add(new NetUserOutpostData() { SlotId = 2, BuildingId = 23201, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 });
|
||||
response.Data.Add(new NetUserOutpostData() { SlotId = 9, BuildingId = 23301, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 });
|
||||
response.Data.Add(new NetUserOutpostData() { SlotId = 8, BuildingId = 23401, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 });
|
||||
response.Data.Add(new NetUserOutpostData() { SlotId = 10, BuildingId = 23501, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 });
|
||||
response.Data.Add(new NetUserOutpostData() { SlotId = 38, BuildingId = 33601, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 });
|
||||
// defult building
|
||||
if (user.OutpostBuildings == null || user.OutpostBuildings.Count == 0)
|
||||
{
|
||||
var defaultBuildings = new List<NetUserOutpostData>
|
||||
{
|
||||
new() { SlotId = 1, BuildingId = 22401, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 },
|
||||
new() { SlotId = 4, BuildingId = 22701, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 },
|
||||
new() { SlotId = 5, BuildingId = 22801, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 },
|
||||
new() { SlotId = 6, BuildingId = 22901, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 },
|
||||
new() { SlotId = 7, BuildingId = 23001, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 },
|
||||
new() { SlotId = 3, BuildingId = 23101, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 },
|
||||
new() { SlotId = 2, BuildingId = 23201, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 },
|
||||
new() { SlotId = 9, BuildingId = 23301, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 },
|
||||
new() { SlotId = 8, BuildingId = 23401, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 },
|
||||
new() { SlotId = 10, BuildingId = 23501, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 },
|
||||
new() { SlotId = 38, BuildingId = 33601, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 }
|
||||
};
|
||||
|
||||
response.Data.AddRange(defaultBuildings);
|
||||
user.OutpostBuildings = defaultBuildings;
|
||||
JsonDb.Save();
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var building in user.OutpostBuildings)
|
||||
{
|
||||
|
||||
if (!building.IsDone && DateTime.UtcNow.Ticks >= building.CompleteAt)
|
||||
{
|
||||
building.IsDone = true;
|
||||
}
|
||||
}
|
||||
response.Data.AddRange(user.OutpostBuildings);
|
||||
JsonDb.Save();
|
||||
}
|
||||
|
||||
response.TimeRewardBuffs.AddRange(NetUtils.GetOutpostTimeReward(user));
|
||||
|
||||
|
||||
38
EpinelPS/LobbyServer/Outpost/ObtainInfracoreReward.cs
Normal file
38
EpinelPS/LobbyServer/Outpost/ObtainInfracoreReward.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using EpinelPS.Utils;
|
||||
using EpinelPS.Data;
|
||||
|
||||
namespace EpinelPS.LobbyServer.Outpost
|
||||
{
|
||||
[PacketPath("/infracore/reward")]
|
||||
public class ObtainInfracoreReward : LobbyMsgHandler
|
||||
{
|
||||
protected override async Task HandleAsync()
|
||||
{
|
||||
ReqObtainInfraCoreReward req = await ReadData<ReqObtainInfraCoreReward>();
|
||||
ResObtainInfraCoreReward response = new();
|
||||
|
||||
User user = GetUser();
|
||||
|
||||
int currentLevel = user.InfraCoreLvl;
|
||||
|
||||
Dictionary<int, InfracoreRecord> gradeTable = GameData.Instance.InfracoreTable;
|
||||
if (gradeTable.TryGetValue(currentLevel, out var gradeData))
|
||||
{
|
||||
if (gradeData.reward_id > 0)
|
||||
{
|
||||
bool isReceived = user.InfraCoreRewardReceived.ContainsKey(currentLevel) && user.InfraCoreRewardReceived[currentLevel];
|
||||
|
||||
if (!isReceived)
|
||||
{
|
||||
user.InfraCoreRewardReceived[currentLevel] = true;
|
||||
|
||||
var reward = RewardUtils.RegisterRewardsForUser(user, gradeData.reward_id);
|
||||
response.Reward = reward;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,10 @@ namespace EpinelPS.LobbyServer.Stage
|
||||
|
||||
public static ResClearStage CompleteStage(User user, int StageId, bool forceCompleteScenarios = false)
|
||||
{
|
||||
ResClearStage response = new();
|
||||
ResClearStage response = new()
|
||||
{
|
||||
OutpostTimeRewardBuff = new()
|
||||
};
|
||||
CampaignStageRecord clearedStage = GameData.Instance.GetStageData(StageId) ?? throw new Exception("cleared stage cannot be null");
|
||||
|
||||
string stageMapId = GameData.Instance.GetMapIdFromChapter(clearedStage.chapter_id, clearedStage.chapter_mod);
|
||||
|
||||
@@ -21,10 +21,14 @@ namespace EpinelPS.LobbyServer.Stage
|
||||
OutpostBattle = rsp.OutpostBattle,
|
||||
OutpostBattleLevelReward = rsp.OutpostBattleLevelReward,
|
||||
StageClearReward = rsp.StageClearReward,
|
||||
UserLevelUpReward = rsp.UserLevelUpReward
|
||||
UserLevelUpReward = rsp.UserLevelUpReward,
|
||||
OutpostTimeRewardBuff = new()
|
||||
};
|
||||
|
||||
response.OutpostTimeRewardBuff.TimeRewardBuffs.AddRange(rsp.OutpostTimeRewardBuff.TimeRewardBuffs);
|
||||
if (rsp.OutpostTimeRewardBuff != null)
|
||||
{
|
||||
response.OutpostTimeRewardBuff.TimeRewardBuffs.AddRange(rsp.OutpostTimeRewardBuff.TimeRewardBuffs);
|
||||
}
|
||||
|
||||
await WriteDataAsync(response);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ using EpinelPS.Utils;
|
||||
namespace EpinelPS.Models;
|
||||
public class CoreInfo
|
||||
{
|
||||
public int DbVersion = 3;
|
||||
public int DbVersion = 5;
|
||||
public List<User> Users = [];
|
||||
|
||||
public List<AccessToken> LauncherAccessTokens = [];
|
||||
|
||||
@@ -61,6 +61,9 @@ namespace EpinelPS.Models
|
||||
public int Position;
|
||||
public int Corp;
|
||||
public long Isn;
|
||||
|
||||
// For harmony cubes that can be equipped to multiple characters
|
||||
public List<long> CsnList = [];
|
||||
}
|
||||
public class EventData
|
||||
{
|
||||
@@ -106,6 +109,9 @@ namespace EpinelPS.Models
|
||||
public List<int> CompletedDailyMissions = [];
|
||||
public int DailyMissionPoints;
|
||||
public SimroomData SimRoomData = new();
|
||||
|
||||
public bool UnlimitedCounseling = false;
|
||||
public Dictionary<int, int> DailyCounselCount = [];
|
||||
}
|
||||
public class WeeklyResetableData
|
||||
{
|
||||
|
||||
@@ -48,6 +48,9 @@ public class User
|
||||
public long[] RepresentationTeamDataNew = [];
|
||||
public Dictionary<int, ClearedTutorialData> ClearedTutorialData = [];
|
||||
|
||||
// Outpost buildings data
|
||||
public List<NetUserOutpostData> OutpostBuildings = [];
|
||||
|
||||
public NetWallpaperData[] WallpaperList = [];
|
||||
public NetWallpaperBackground[] WallpaperBackground = [];
|
||||
public NetWallpaperJukeboxFavorite[] WallpaperFavoriteList = [];
|
||||
@@ -61,6 +64,7 @@ public class User
|
||||
public Dictionary<int, bool> SubQuestData = [];
|
||||
public int InfraCoreExp = 0;
|
||||
public int InfraCoreLvl = 1;
|
||||
public Dictionary<int, bool> InfraCoreRewardReceived = [];
|
||||
public UserPointData userPointData = new();
|
||||
public DateTime LastLogin = DateTime.UtcNow;
|
||||
public DateTime BattleTime = DateTime.UtcNow;
|
||||
@@ -72,7 +76,9 @@ public class User
|
||||
|
||||
public List<int> Memorial = [];
|
||||
public List<int> JukeboxBgm = [];
|
||||
public List<NetUserFavoriteItemData> FavoriteItems = [];
|
||||
|
||||
public List<NetUserFavoriteItemQuestData> FavoriteItemQuests = [];
|
||||
public Dictionary<int, int> TowerProgress = [];
|
||||
|
||||
public JukeBoxSetting LobbyMusic = new() { Location = NetJukeboxLocation.Lobby, TableId = 2, Type = NetJukeboxBgmType.JukeboxTableId };
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Text.Json;
|
||||
using EpinelPS.Database;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace EpinelPS.Utils
|
||||
{
|
||||
@@ -51,7 +51,7 @@ namespace EpinelPS.Utils
|
||||
Console.WriteLine("Loaded game config");
|
||||
|
||||
|
||||
_root = JsonSerializer.Deserialize<GameConfigRoot>(File.ReadAllText(AppDomain.CurrentDomain.BaseDirectory + "/gameconfig.json"));
|
||||
_root = JsonConvert.DeserializeObject<GameConfigRoot>(File.ReadAllText(AppDomain.CurrentDomain.BaseDirectory + "/gameconfig.json"));
|
||||
|
||||
if (_root == null)
|
||||
{
|
||||
@@ -67,7 +67,7 @@ namespace EpinelPS.Utils
|
||||
{
|
||||
if (Root != null)
|
||||
{
|
||||
File.WriteAllText(AppDomain.CurrentDomain.BaseDirectory + "/gameconfig.json", JsonSerializer.Serialize(Root, JsonDb.IndentedJson));
|
||||
File.WriteAllText(AppDomain.CurrentDomain.BaseDirectory + "/gameconfig.json", JsonConvert.SerializeObject(Root, Formatting.Indented));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,6 +104,8 @@ namespace EpinelPS.Utils
|
||||
return 2;
|
||||
case "Module_D":
|
||||
return 3;
|
||||
case "HarmonyCube":
|
||||
return GetHarmonyCubePosition(item.ItemType);
|
||||
default:
|
||||
Console.WriteLine("Unknown item subtype: " + subType);
|
||||
break;
|
||||
@@ -114,6 +116,15 @@ namespace EpinelPS.Utils
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int GetHarmonyCubePosition(int itemType)
|
||||
{
|
||||
if (GameData.Instance.ItemHarmonyCubeTable.TryGetValue(itemType, out ItemHarmonyCubeRecord? harmonyCube))
|
||||
{
|
||||
return harmonyCube.location_id;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
/// <summary>
|
||||
/// Takes multiple NetRewardData objects and merges it into one. Note that this function expects that rewards are already applied to user object.
|
||||
/// </summary>
|
||||
@@ -391,6 +402,8 @@ namespace EpinelPS.Utils
|
||||
{
|
||||
RandomItemRecord winningRecord = Rng.PickWeightedItem(probabilityEntries);
|
||||
|
||||
Logging.WriteLine($"LootBox {boxId}: Won item - Type: {winningRecord.reward_type}, ID: {winningRecord.reward_id}, Value: {winningRecord.reward_value_min}", LogType.Info);
|
||||
|
||||
if (winningRecord.reward_value_min != winningRecord.reward_value_max)
|
||||
{
|
||||
Logging.WriteLine("TODO: reward_value_max", LogType.Warning);
|
||||
|
||||
@@ -191,11 +191,39 @@ namespace EpinelPS.Utils
|
||||
}
|
||||
else if (rewardType == "InfraCoreExp")
|
||||
{
|
||||
int beforeLv = user.InfraCoreLvl;
|
||||
int beforeExp = user.InfraCoreExp;
|
||||
|
||||
user.InfraCoreExp += rewardCount;
|
||||
|
||||
// Check for level ups
|
||||
Dictionary<int, InfracoreRecord> gradeTable = GameData.Instance.InfracoreTable;
|
||||
int newLevel = user.InfraCoreLvl;
|
||||
|
||||
foreach (InfracoreRecord grade in gradeTable.Values.OrderBy(g => g.grade))
|
||||
{
|
||||
if (user.InfraCoreExp >= grade.infra_core_exp)
|
||||
{
|
||||
newLevel = grade.grade + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (newLevel > user.InfraCoreLvl)
|
||||
{
|
||||
user.InfraCoreLvl = newLevel;
|
||||
}
|
||||
|
||||
ret.InfraCoreExp = new NetIncreaseExpData()
|
||||
{
|
||||
BeforeLv = user.InfraCoreLvl,
|
||||
BeforeExp = user.InfraCoreExp,
|
||||
// TODO
|
||||
BeforeLv = beforeLv,
|
||||
BeforeExp = beforeExp,
|
||||
CurrentLv = user.InfraCoreLvl,
|
||||
CurrentExp = user.InfraCoreExp,
|
||||
GainExp = rewardCount
|
||||
};
|
||||
}
|
||||
else if (rewardType == "ItemRandomBox")
|
||||
@@ -221,6 +249,32 @@ namespace EpinelPS.Utils
|
||||
user.Items.Add(new ItemData() { Count = rewardCount, Isn = itm.Isn, ItemType = itm.Tid });
|
||||
}
|
||||
}
|
||||
else if (rewardType == "FavoriteItem")
|
||||
{
|
||||
|
||||
NetUserFavoriteItemData newFavoriteItem = new NetUserFavoriteItemData
|
||||
{
|
||||
FavoriteItemId = user.GenerateUniqueItemId(),
|
||||
Tid = rewardId,
|
||||
Csn = 0,
|
||||
Lv = 0,
|
||||
Exp = 0
|
||||
};
|
||||
user.FavoriteItems.Add(newFavoriteItem);
|
||||
|
||||
ret.UserFavoriteItems.Add(newFavoriteItem);
|
||||
|
||||
NetFavoriteItemData favoriteItemData = new NetFavoriteItemData
|
||||
{
|
||||
FavoriteItemId = newFavoriteItem.FavoriteItemId,
|
||||
Tid = newFavoriteItem.Tid,
|
||||
Csn = newFavoriteItem.Csn,
|
||||
Lv = newFavoriteItem.Lv,
|
||||
Exp = newFavoriteItem.Exp
|
||||
};
|
||||
ret.FavoriteItems.Add(favoriteItemData);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
Logging.WriteLine("TODO: Reward type " + rewardType, LogType.Warning);
|
||||
|
||||
Reference in New Issue
Block a user