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.Diagnostics;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text.Json;
|
|
||||||
using EpinelPS.Database;
|
using EpinelPS.Database;
|
||||||
using EpinelPS.Utils;
|
using EpinelPS.Utils;
|
||||||
using ICSharpCode.SharpZipLib.Zip;
|
using ICSharpCode.SharpZipLib.Zip;
|
||||||
using MemoryPack;
|
using MemoryPack;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace EpinelPS.Data
|
namespace EpinelPS.Data
|
||||||
{
|
{
|
||||||
@@ -154,6 +154,9 @@ namespace EpinelPS.Data
|
|||||||
[LoadRecord("AttractiveLevelRewardTable.json", "id")]
|
[LoadRecord("AttractiveLevelRewardTable.json", "id")]
|
||||||
public readonly Dictionary<int, AttractiveLevelRewardRecord> AttractiveLevelReward = [];
|
public readonly Dictionary<int, AttractiveLevelRewardRecord> AttractiveLevelReward = [];
|
||||||
|
|
||||||
|
[LoadRecord("AttractiveLevelTable.json", "id")]
|
||||||
|
public readonly Dictionary<int, AttractiveLevelRecord> AttractiveLevelTable = [];
|
||||||
|
|
||||||
[LoadRecord("SubQuestTable.json", "id")]
|
[LoadRecord("SubQuestTable.json", "id")]
|
||||||
public readonly Dictionary<int, SubquestRecord> Subquests = [];
|
public readonly Dictionary<int, SubquestRecord> Subquests = [];
|
||||||
|
|
||||||
@@ -200,6 +203,32 @@ namespace EpinelPS.Data
|
|||||||
[LoadRecord("RecycleResearchLevelTable.json", "id")]
|
[LoadRecord("RecycleResearchLevelTable.json", "id")]
|
||||||
public readonly Dictionary<int, RecycleResearchLevelRecord> RecycleResearchLevels = [];
|
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()
|
static async Task<GameData> BuildAsync()
|
||||||
{
|
{
|
||||||
@@ -244,7 +273,7 @@ namespace EpinelPS.Data
|
|||||||
|
|
||||||
#region Data loading
|
#region Data loading
|
||||||
private static byte[] PresharedValue = [0xCB, 0xC2, 0x1C, 0x6F, 0xF3, 0xF5, 0x07, 0xF5, 0x05, 0xBA, 0xCA, 0xD4, 0x98, 0x28, 0x84, 0x1F, 0xF0, 0xD1, 0x38, 0xC7, 0x61, 0xDF, 0xD6, 0xE6, 0x64, 0x9A, 0x85, 0x13, 0x3E, 0x1A, 0x6A, 0x0C, 0x68, 0x0E, 0x2B, 0xC4, 0xDF, 0x72, 0xF8, 0xC6, 0x55, 0xE4, 0x7B, 0x14, 0x36, 0x18, 0x3B, 0xA7, 0xD1, 0x20, 0x81, 0x22, 0xD1, 0xA9, 0x18, 0x84, 0x65, 0x13, 0x0B, 0xED, 0xA3, 0x00, 0xE5, 0xD9];
|
private static byte[] PresharedValue = [0xCB, 0xC2, 0x1C, 0x6F, 0xF3, 0xF5, 0x07, 0xF5, 0x05, 0xBA, 0xCA, 0xD4, 0x98, 0x28, 0x84, 0x1F, 0xF0, 0xD1, 0x38, 0xC7, 0x61, 0xDF, 0xD6, 0xE6, 0x64, 0x9A, 0x85, 0x13, 0x3E, 0x1A, 0x6A, 0x0C, 0x68, 0x0E, 0x2B, 0xC4, 0xDF, 0x72, 0xF8, 0xC6, 0x55, 0xE4, 0x7B, 0x14, 0x36, 0x18, 0x3B, 0xA7, 0xD1, 0x20, 0x81, 0x22, 0xD1, 0xA9, 0x18, 0x84, 0x65, 0x13, 0x0B, 0xED, 0xA3, 0x00, 0xE5, 0xD9];
|
||||||
private static RSAParameters LoadParameters = new()
|
private static RSAParameters LoadParameters = new()
|
||||||
{
|
{
|
||||||
Exponent = [0x01, 0x00, 0x01],
|
Exponent = [0x01, 0x00, 0x01],
|
||||||
Modulus = [0x89, 0xD6, 0x66, 0x00, 0x7D, 0xFC, 0x7D, 0xCE, 0x83, 0xA6, 0x62, 0xE3, 0x1A, 0x5E, 0x9A, 0x53, 0xC7, 0x8A, 0x27, 0xF3, 0x67, 0xC1, 0xF3, 0xD4, 0x37, 0xFE, 0x50, 0x6D, 0x38, 0x45, 0xDF, 0x7E, 0x73, 0x5C, 0xF4, 0x9D, 0x40, 0x4C, 0x8C, 0x63, 0x21, 0x97, 0xDF, 0x46, 0xFF, 0xB2, 0x0D, 0x0E, 0xDB, 0xB2, 0x72, 0xB4, 0xA8, 0x42, 0xCD, 0xEE, 0x48, 0x06, 0x74, 0x4F, 0xE9, 0x56, 0x6E, 0x9A, 0xB1, 0x60, 0x18, 0xBC, 0x86, 0x0B, 0xB6, 0x32, 0xA7, 0x51, 0x00, 0x85, 0x7B, 0xC8, 0x72, 0xCE, 0x53, 0x71, 0x3F, 0x64, 0xC2, 0x25, 0x58, 0xEF, 0xB0, 0xC9, 0x1D, 0xE3, 0xB3, 0x8E, 0xFC, 0x55, 0xCF, 0x8B, 0x02, 0xA5, 0xC8, 0x1E, 0xA7, 0x0E, 0x26, 0x59, 0xA8, 0x33, 0xA5, 0xF1, 0x11, 0xDB, 0xCB, 0xD3, 0xA7, 0x1F, 0xB1, 0xC6, 0x10, 0x39, 0xC8, 0x31, 0x1D, 0x60, 0xDB, 0x0D, 0xA4, 0x13, 0x4B, 0x2B, 0x0E, 0xF3, 0x6F, 0x69, 0xCB, 0xA8, 0x62, 0x03, 0x69, 0xE6, 0x95, 0x6B, 0x8D, 0x11, 0xF6, 0xAF, 0xD9, 0xC2, 0x27, 0x3A, 0x32, 0x12, 0x05, 0xC3, 0xB1, 0xE2, 0x81, 0x4B, 0x40, 0xF8, 0x8B, 0x8D, 0xBA, 0x1F, 0x55, 0x60, 0x2C, 0x09, 0xC6, 0xED, 0x73, 0x96, 0x32, 0xAF, 0x5F, 0xEE, 0x8F, 0xEB, 0x5B, 0x93, 0xCF, 0x73, 0x13, 0x15, 0x6B, 0x92, 0x7B, 0x27, 0x0A, 0x13, 0xF0, 0x03, 0x4D, 0x6F, 0x5E, 0x40, 0x7B, 0x9B, 0xD5, 0xCE, 0xFC, 0x04, 0x97, 0x7E, 0xAA, 0xA3, 0x53, 0x2A, 0xCF, 0xD2, 0xD5, 0xCF, 0x52, 0xB2, 0x40, 0x61, 0x28, 0xB1, 0xA6, 0xF6, 0x78, 0xFB, 0x69, 0x9A, 0x85, 0xD6, 0xB9, 0x13, 0x14, 0x6D, 0xC4, 0x25, 0x36, 0x17, 0xDB, 0x54, 0x0C, 0xD8, 0x77, 0x80, 0x9A, 0x00, 0x62, 0x83, 0xDD, 0xB0, 0x06, 0x64, 0xD0, 0x81, 0x5B, 0x0D, 0x23, 0x9E, 0x88, 0xBD],
|
Modulus = [0x89, 0xD6, 0x66, 0x00, 0x7D, 0xFC, 0x7D, 0xCE, 0x83, 0xA6, 0x62, 0xE3, 0x1A, 0x5E, 0x9A, 0x53, 0xC7, 0x8A, 0x27, 0xF3, 0x67, 0xC1, 0xF3, 0xD4, 0x37, 0xFE, 0x50, 0x6D, 0x38, 0x45, 0xDF, 0x7E, 0x73, 0x5C, 0xF4, 0x9D, 0x40, 0x4C, 0x8C, 0x63, 0x21, 0x97, 0xDF, 0x46, 0xFF, 0xB2, 0x0D, 0x0E, 0xDB, 0xB2, 0x72, 0xB4, 0xA8, 0x42, 0xCD, 0xEE, 0x48, 0x06, 0x74, 0x4F, 0xE9, 0x56, 0x6E, 0x9A, 0xB1, 0x60, 0x18, 0xBC, 0x86, 0x0B, 0xB6, 0x32, 0xA7, 0x51, 0x00, 0x85, 0x7B, 0xC8, 0x72, 0xCE, 0x53, 0x71, 0x3F, 0x64, 0xC2, 0x25, 0x58, 0xEF, 0xB0, 0xC9, 0x1D, 0xE3, 0xB3, 0x8E, 0xFC, 0x55, 0xCF, 0x8B, 0x02, 0xA5, 0xC8, 0x1E, 0xA7, 0x0E, 0x26, 0x59, 0xA8, 0x33, 0xA5, 0xF1, 0x11, 0xDB, 0xCB, 0xD3, 0xA7, 0x1F, 0xB1, 0xC6, 0x10, 0x39, 0xC8, 0x31, 0x1D, 0x60, 0xDB, 0x0D, 0xA4, 0x13, 0x4B, 0x2B, 0x0E, 0xF3, 0x6F, 0x69, 0xCB, 0xA8, 0x62, 0x03, 0x69, 0xE6, 0x95, 0x6B, 0x8D, 0x11, 0xF6, 0xAF, 0xD9, 0xC2, 0x27, 0x3A, 0x32, 0x12, 0x05, 0xC3, 0xB1, 0xE2, 0x81, 0x4B, 0x40, 0xF8, 0x8B, 0x8D, 0xBA, 0x1F, 0x55, 0x60, 0x2C, 0x09, 0xC6, 0xED, 0x73, 0x96, 0x32, 0xAF, 0x5F, 0xEE, 0x8F, 0xEB, 0x5B, 0x93, 0xCF, 0x73, 0x13, 0x15, 0x6B, 0x92, 0x7B, 0x27, 0x0A, 0x13, 0xF0, 0x03, 0x4D, 0x6F, 0x5E, 0x40, 0x7B, 0x9B, 0xD5, 0xCE, 0xFC, 0x04, 0x97, 0x7E, 0xAA, 0xA3, 0x53, 0x2A, 0xCF, 0xD2, 0xD5, 0xCF, 0x52, 0xB2, 0x40, 0x61, 0x28, 0xB1, 0xA6, 0xF6, 0x78, 0xFB, 0x69, 0x9A, 0x85, 0xD6, 0xB9, 0x13, 0x14, 0x6D, 0xC4, 0x25, 0x36, 0x17, 0xDB, 0x54, 0x0C, 0xD8, 0x77, 0x80, 0x9A, 0x00, 0x62, 0x83, 0xDD, 0xB0, 0x06, 0x64, 0xD0, 0x81, 0x5B, 0x0D, 0x23, 0x9E, 0x88, 0xBD],
|
||||||
@@ -392,7 +421,9 @@ namespace EpinelPS.Data
|
|||||||
}
|
}
|
||||||
else
|
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];
|
deserializedObject = [.. obj.records];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -560,7 +591,20 @@ namespace EpinelPS.Data
|
|||||||
|
|
||||||
public string? GetItemSubType(int itemType)
|
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)
|
internal IEnumerable<int> GetStageIdsForChapter(int chapterNumber, bool normal)
|
||||||
@@ -669,5 +713,17 @@ namespace EpinelPS.Data
|
|||||||
return results.FirstOrDefault().Value.reward_id;
|
return results.FirstOrDefault().Value.reward_id;
|
||||||
else return 0;
|
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;
|
using MemoryPack;
|
||||||
|
|
||||||
namespace EpinelPS.Data
|
namespace EpinelPS.Data
|
||||||
@@ -685,7 +685,7 @@ namespace EpinelPS.Data
|
|||||||
{
|
{
|
||||||
public int id;
|
public int id;
|
||||||
public int grade;
|
public int grade;
|
||||||
public int reard_id;
|
public int reward_id;
|
||||||
public int infra_core_exp;
|
public int infra_core_exp;
|
||||||
public List<InfracoreFunction> function_list = [];
|
public List<InfracoreFunction> function_list = [];
|
||||||
}
|
}
|
||||||
@@ -706,6 +706,32 @@ namespace EpinelPS.Data
|
|||||||
public int attractive_level;
|
public int attractive_level;
|
||||||
public int costume;
|
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]
|
[MemoryPackable]
|
||||||
public partial class SubquestRecord
|
public partial class SubquestRecord
|
||||||
{
|
{
|
||||||
@@ -731,9 +757,23 @@ namespace EpinelPS.Data
|
|||||||
public partial class MessengerMsgConditionRecord
|
public partial class MessengerMsgConditionRecord
|
||||||
{
|
{
|
||||||
public int id;
|
public int id;
|
||||||
|
public List<MessengerConditionTriggerList> trigger_list = [];
|
||||||
|
public string message_type = "";
|
||||||
public string tid = "";
|
public string tid = "";
|
||||||
|
public int resource_id;
|
||||||
|
public int stamina_value;
|
||||||
public int reward_id;
|
public int reward_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MemoryPackable]
|
||||||
|
public partial class MessengerConditionTriggerList
|
||||||
|
{
|
||||||
|
public string trigger = "";
|
||||||
|
public int condition_id;
|
||||||
|
public int condition_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public enum ScenarioRewardCondition
|
public enum ScenarioRewardCondition
|
||||||
{
|
{
|
||||||
MainScenario,
|
MainScenario,
|
||||||
@@ -888,5 +928,166 @@ namespace EpinelPS.Data
|
|||||||
public List<ItemSpawner> ItemSpawner { get; set; } = [];
|
public List<ItemSpawner> ItemSpawner { get; set; } = [];
|
||||||
public List<StageSpawner> StageSpawner { get; set; } = [];
|
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.Globalization;
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
using EpinelPS.Data;
|
using EpinelPS.Data;
|
||||||
using EpinelPS.Utils;
|
using EpinelPS.Utils;
|
||||||
using Google.Protobuf;
|
using Google.Protobuf;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using Paseto;
|
using Paseto;
|
||||||
using Paseto.Builder;
|
using Paseto.Builder;
|
||||||
|
|
||||||
@@ -12,7 +11,6 @@ namespace EpinelPS.Database
|
|||||||
internal class JsonDb
|
internal class JsonDb
|
||||||
{
|
{
|
||||||
public static CoreInfo Instance { get; internal set; }
|
public static CoreInfo Instance { get; internal set; }
|
||||||
public static readonly JsonSerializerOptions IndentedJson = new() { WriteIndented = true, IncludeFields = true };
|
|
||||||
|
|
||||||
// Note: change this in sodium
|
// Note: change this in sodium
|
||||||
public static byte[] ServerPrivateKey = Convert.FromBase64String("FSUY8Ohd942n5LWAfxn6slK3YGwc8OqmyJoJup9nNos=");
|
public static byte[] ServerPrivateKey = Convert.FromBase64String("FSUY8Ohd942n5LWAfxn6slK3YGwc8OqmyJoJup9nNos=");
|
||||||
@@ -20,7 +18,6 @@ namespace EpinelPS.Database
|
|||||||
|
|
||||||
static JsonDb()
|
static JsonDb()
|
||||||
{
|
{
|
||||||
IndentedJson.Converters.Add(new JsonStringEnumConverter());
|
|
||||||
if (!File.Exists(AppDomain.CurrentDomain.BaseDirectory + "/db.json"))
|
if (!File.Exists(AppDomain.CurrentDomain.BaseDirectory + "/db.json"))
|
||||||
{
|
{
|
||||||
Console.WriteLine("users: warning: configuration not found, writing default data");
|
Console.WriteLine("users: warning: configuration not found, writing default data");
|
||||||
@@ -28,7 +25,7 @@ namespace EpinelPS.Database
|
|||||||
Save();
|
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)
|
if (j != null)
|
||||||
{
|
{
|
||||||
Instance = j;
|
Instance = j;
|
||||||
@@ -80,7 +77,7 @@ namespace EpinelPS.Database
|
|||||||
Save();
|
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)
|
if (j != null)
|
||||||
{
|
{
|
||||||
Instance = j;
|
Instance = j;
|
||||||
@@ -112,7 +109,7 @@ namespace EpinelPS.Database
|
|||||||
{
|
{
|
||||||
if (Instance != null)
|
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)
|
public static int CurrentJukeboxBgm(int position)
|
||||||
@@ -154,4 +151,4 @@ namespace EpinelPS.Database
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -36,7 +36,8 @@
|
|||||||
<PackageReference Include="PeterO.Cbor" Version="4.5.5" />
|
<PackageReference Include="PeterO.Cbor" Version="4.5.5" />
|
||||||
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
||||||
<PackageReference Include="Sodium.Core" Version="1.4.0" />
|
<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>
|
||||||
|
|
||||||
<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.Database;
|
||||||
using EpinelPS.Utils;
|
using EpinelPS.Utils;
|
||||||
|
|
||||||
namespace EpinelPS.LobbyServer.Character.Counsel;
|
namespace EpinelPS.LobbyServer.Character.Counsel
|
||||||
|
|
||||||
[PacketPath("/character/attractive/counsel")]
|
|
||||||
public class DoCounsel : LobbyMsgHandler
|
|
||||||
{
|
{
|
||||||
protected override async Task HandleAsync()
|
[PacketPath("/character/attractive/counsel")]
|
||||||
|
public class DoCounsel : LobbyMsgHandler
|
||||||
{
|
{
|
||||||
ReqCharacterCounsel req = await ReadData<ReqCharacterCounsel>();
|
protected override async Task HandleAsync()
|
||||||
User user = GetUser();
|
|
||||||
|
|
||||||
ResCharacterCounsel response = new();
|
|
||||||
|
|
||||||
foreach (KeyValuePair<CurrencyType, long> currency in user.Currency)
|
|
||||||
{
|
{
|
||||||
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;
|
foreach (KeyValuePair<CurrencyType, long> currency in user.Currency)
|
||||||
|
|
||||||
if (currentBondInfo.Any())
|
|
||||||
{
|
|
||||||
data = currentBondInfo.First();
|
|
||||||
|
|
||||||
// TODO update
|
|
||||||
response.Attractive = data;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
data = new()
|
|
||||||
{
|
{
|
||||||
NameCode = req.NameCode,
|
response.Currencies.Add(new NetUserCurrencyData() { Type = (int)currency.Key, Value = currency.Value });
|
||||||
// TODO
|
}
|
||||||
};
|
|
||||||
|
|
||||||
response.Attractive = data;
|
NetUserAttractiveData? currentBondInfo = user.BondInfo.FirstOrDefault(x => x.NameCode == req.NameCode);
|
||||||
user.BondInfo.Add(data);
|
|
||||||
|
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
|
if (levelInfo == null)
|
||||||
await WriteDataAsync(response);
|
{
|
||||||
|
// 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
|
namespace EpinelPS.LobbyServer.Character
|
||||||
{
|
{
|
||||||
@@ -19,13 +19,11 @@ namespace EpinelPS.LobbyServer.Character
|
|||||||
{
|
{
|
||||||
response.Attractives.Add(item);
|
response.Attractives.Add(item);
|
||||||
item.CanCounselToday = true;
|
item.CanCounselToday = true;
|
||||||
item.Exp = 9999; // TODO
|
|
||||||
item.Lv = 10;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO: Validate response from real server and pull info from user info
|
// TODO: Validate response from real server and pull info from user info
|
||||||
await WriteDataAsync(response);
|
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();
|
ResGetFavoriteItemLibrary response = new();
|
||||||
User user = GetUser();
|
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);
|
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();
|
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);
|
await WriteDataAsync(response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using EpinelPS.Utils;
|
using EpinelPS.Utils;
|
||||||
|
using EpinelPS.Data;
|
||||||
|
|
||||||
namespace EpinelPS.LobbyServer.FavoriteItem
|
namespace EpinelPS.LobbyServer.FavoriteItem
|
||||||
{
|
{
|
||||||
@@ -9,10 +10,20 @@ namespace EpinelPS.LobbyServer.FavoriteItem
|
|||||||
{
|
{
|
||||||
ReqListFavoriteItemQuest req = await ReadData<ReqListFavoriteItemQuest>();
|
ReqListFavoriteItemQuest req = await ReadData<ReqListFavoriteItemQuest>();
|
||||||
User user = GetUser();
|
User user = GetUser();
|
||||||
|
|
||||||
ResListFavoriteItemQuest response = new();
|
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);
|
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.Database;
|
||||||
using EpinelPS.Utils;
|
using EpinelPS.Utils;
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ namespace EpinelPS.LobbyServer.LobbyUser
|
|||||||
User user = GetUser();
|
User user = GetUser();
|
||||||
|
|
||||||
TimeSpan battleTime = DateTime.UtcNow - user.BattleTime;
|
TimeSpan battleTime = DateTime.UtcNow - user.BattleTime;
|
||||||
long battleTimeMs = (long)(battleTime.TotalNanoseconds / 100);
|
long battleTimeMs = (long)(battleTime.TotalNanoseconds / 100);
|
||||||
|
|
||||||
// NOTE: Keep this in sync with GetUser code
|
// NOTE: Keep this in sync with GetUser code
|
||||||
|
|
||||||
@@ -67,18 +67,47 @@ namespace EpinelPS.LobbyServer.LobbyUser
|
|||||||
response.TypeTeams.Add(teamInfo.Value);
|
response.TypeTeams.Add(teamInfo.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Save outpost data
|
if (user.OutpostBuildings != null && user.OutpostBuildings.Count > 0)
|
||||||
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 });
|
bool needsSave = false;
|
||||||
response.Outposts.Add(new NetUserOutpostData() { SlotId = 5, BuildingId = 22801, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 });
|
foreach (NetUserOutpostData building in user.OutpostBuildings)
|
||||||
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 });
|
if (!building.IsDone && DateTime.UtcNow.Ticks >= building.CompleteAt)
|
||||||
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 });
|
building.IsDone = true;
|
||||||
response.Outposts.Add(new NetUserOutpostData() { SlotId = 8, BuildingId = 23401, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 });
|
needsSave = true;
|
||||||
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 (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.LastClearedNormalMainStageId = user.LastNormalStageCleared;
|
||||||
response.TimeRewardBuffs.AddRange(NetUtils.GetOutpostTimeReward(user));
|
response.TimeRewardBuffs.AddRange(NetUtils.GetOutpostTimeReward(user));
|
||||||
@@ -90,4 +119,4 @@ namespace EpinelPS.LobbyServer.LobbyUser
|
|||||||
await WriteDataAsync(response);
|
await WriteDataAsync(response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14,16 +14,32 @@ namespace EpinelPS.LobbyServer.Messenger
|
|||||||
|
|
||||||
ResEnterMessengerDialog response = new();
|
ResEnterMessengerDialog response = new();
|
||||||
|
|
||||||
MessengerMsgConditionRecord opener = GameData.Instance.MessageConditions[req.Tid];
|
if (!GameData.Instance.MessageConditions.TryGetValue(req.Tid, out MessengerMsgConditionRecord? opener))
|
||||||
KeyValuePair<string, MessengerDialogRecord> conversation = GameData.Instance.Messages.Where(x => x.Value.conversation_id == opener.tid && x.Value.is_opener).First();
|
{
|
||||||
|
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);
|
response.Message = user.CreateMessage(conversation.Value);
|
||||||
|
|
||||||
user.AddTrigger(TriggerType.MessageClear, 1, req.Tid); // TODO check if this is correct
|
user.AddTrigger(TriggerType.MessageClear, 1, req.Tid); // TODO check if this is correct
|
||||||
|
|
||||||
JsonDb.Save();
|
JsonDb.Save();
|
||||||
|
|
||||||
await WriteDataAsync(response);
|
await WriteDataAsync(response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using EpinelPS.Utils;
|
using EpinelPS.Utils;
|
||||||
|
using EpinelPS.Data;
|
||||||
|
|
||||||
namespace EpinelPS.LobbyServer.Messenger
|
namespace EpinelPS.LobbyServer.Messenger
|
||||||
{
|
{
|
||||||
@@ -10,6 +11,8 @@ namespace EpinelPS.LobbyServer.Messenger
|
|||||||
ReqGetMessages req = await ReadData<ReqGetMessages>();
|
ReqGetMessages req = await ReadData<ReqGetMessages>();
|
||||||
User user = GetUser();
|
User user = GetUser();
|
||||||
|
|
||||||
|
CheckAndCreateAvailableMessages(user);
|
||||||
|
|
||||||
ResGetMessages response = new();
|
ResGetMessages response = new();
|
||||||
|
|
||||||
IEnumerable<NetMessage> newMessages = user.MessengerData.Where(x => x.Seq >= req.Seq);
|
IEnumerable<NetMessage> newMessages = user.MessengerData.Where(x => x.Seq >= req.Seq);
|
||||||
@@ -21,5 +24,71 @@ namespace EpinelPS.LobbyServer.Messenger
|
|||||||
|
|
||||||
await WriteDataAsync(response);
|
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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -10,17 +12,132 @@ namespace EpinelPS.LobbyServer.Outpost
|
|||||||
[PacketPath("/outpost/building")]
|
[PacketPath("/outpost/building")]
|
||||||
public class BuildBuilding : LobbyMsgHandler
|
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()
|
protected override async Task HandleAsync()
|
||||||
{
|
{
|
||||||
ReqBuilding req = await ReadData<ReqBuilding>();
|
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()
|
ResBuilding response = new()
|
||||||
{
|
{
|
||||||
StartAt = DateTime.UtcNow.Ticks,
|
StartAt = newBuilding.StartAt,
|
||||||
CompleteAt = DateTime.UtcNow.AddDays(1).Ticks
|
CompleteAt = newBuilding.CompleteAt
|
||||||
};
|
};
|
||||||
// TODO
|
|
||||||
await WriteDataAsync(response);
|
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.Utils;
|
||||||
|
using EpinelPS.Data;
|
||||||
|
|
||||||
namespace EpinelPS.LobbyServer.Outpost
|
namespace EpinelPS.LobbyServer.Outpost
|
||||||
{
|
{
|
||||||
@@ -10,7 +11,22 @@ namespace EpinelPS.LobbyServer.Outpost
|
|||||||
ReqCheckReceiveInfraCoreReward req = await ReadData<ReqCheckReceiveInfraCoreReward>();
|
ReqCheckReceiveInfraCoreReward req = await ReadData<ReqCheckReceiveInfraCoreReward>();
|
||||||
ResCheckReceiveInfraCoreReward response = new();
|
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);
|
await WriteDataAsync(response);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using EpinelPS.Utils;
|
using EpinelPS.Utils;
|
||||||
using EpinelPS.Data;
|
using EpinelPS.Data;
|
||||||
|
using EpinelPS.Database;
|
||||||
namespace EpinelPS.LobbyServer.Outpost
|
namespace EpinelPS.LobbyServer.Outpost
|
||||||
{
|
{
|
||||||
[PacketPath("/outpost/getoutpostdata")]
|
[PacketPath("/outpost/getoutpostdata")]
|
||||||
@@ -37,17 +38,41 @@ namespace EpinelPS.LobbyServer.Outpost
|
|||||||
response.OutpostBattleLevel = user.OutpostBattleLevel;
|
response.OutpostBattleLevel = user.OutpostBattleLevel;
|
||||||
response.OutpostBattleTime = new NetOutpostBattleTime() { MaxBattleTime = 864000000000, MaxOverBattleTime = 12096000000000, BattleTime = battleTimeMs, OverBattleTime = overBattleTime };
|
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 });
|
// defult building
|
||||||
response.Data.Add(new NetUserOutpostData() { SlotId = 4, BuildingId = 22701, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 });
|
if (user.OutpostBuildings == null || user.OutpostBuildings.Count == 0)
|
||||||
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 });
|
var defaultBuildings = new List<NetUserOutpostData>
|
||||||
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 });
|
new() { SlotId = 1, BuildingId = 22401, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 },
|
||||||
response.Data.Add(new NetUserOutpostData() { SlotId = 2, BuildingId = 23201, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 });
|
new() { SlotId = 4, BuildingId = 22701, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 },
|
||||||
response.Data.Add(new NetUserOutpostData() { SlotId = 9, BuildingId = 23301, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 });
|
new() { SlotId = 5, BuildingId = 22801, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 },
|
||||||
response.Data.Add(new NetUserOutpostData() { SlotId = 8, BuildingId = 23401, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 });
|
new() { SlotId = 6, BuildingId = 22901, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 },
|
||||||
response.Data.Add(new NetUserOutpostData() { SlotId = 10, BuildingId = 23501, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 });
|
new() { SlotId = 7, BuildingId = 23001, IsDone = true, StartAt = 638549982076760660, CompleteAt = 638549982076760660 },
|
||||||
response.Data.Add(new NetUserOutpostData() { SlotId = 38, BuildingId = 33601, 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));
|
response.TimeRewardBuffs.AddRange(NetUtils.GetOutpostTimeReward(user));
|
||||||
|
|
||||||
@@ -55,4 +80,4 @@ namespace EpinelPS.LobbyServer.Outpost
|
|||||||
await WriteDataAsync(response);
|
await WriteDataAsync(response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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)
|
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");
|
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);
|
string stageMapId = GameData.Instance.GetMapIdFromChapter(clearedStage.chapter_id, clearedStage.chapter_mod);
|
||||||
|
|||||||
@@ -21,10 +21,14 @@ namespace EpinelPS.LobbyServer.Stage
|
|||||||
OutpostBattle = rsp.OutpostBattle,
|
OutpostBattle = rsp.OutpostBattle,
|
||||||
OutpostBattleLevelReward = rsp.OutpostBattleLevelReward,
|
OutpostBattleLevelReward = rsp.OutpostBattleLevelReward,
|
||||||
StageClearReward = rsp.StageClearReward,
|
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);
|
await WriteDataAsync(response);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using EpinelPS.Utils;
|
|||||||
namespace EpinelPS.Models;
|
namespace EpinelPS.Models;
|
||||||
public class CoreInfo
|
public class CoreInfo
|
||||||
{
|
{
|
||||||
public int DbVersion = 3;
|
public int DbVersion = 5;
|
||||||
public List<User> Users = [];
|
public List<User> Users = [];
|
||||||
|
|
||||||
public List<AccessToken> LauncherAccessTokens = [];
|
public List<AccessToken> LauncherAccessTokens = [];
|
||||||
|
|||||||
@@ -61,6 +61,9 @@ namespace EpinelPS.Models
|
|||||||
public int Position;
|
public int Position;
|
||||||
public int Corp;
|
public int Corp;
|
||||||
public long Isn;
|
public long Isn;
|
||||||
|
|
||||||
|
// For harmony cubes that can be equipped to multiple characters
|
||||||
|
public List<long> CsnList = [];
|
||||||
}
|
}
|
||||||
public class EventData
|
public class EventData
|
||||||
{
|
{
|
||||||
@@ -106,6 +109,9 @@ namespace EpinelPS.Models
|
|||||||
public List<int> CompletedDailyMissions = [];
|
public List<int> CompletedDailyMissions = [];
|
||||||
public int DailyMissionPoints;
|
public int DailyMissionPoints;
|
||||||
public SimroomData SimRoomData = new();
|
public SimroomData SimRoomData = new();
|
||||||
|
|
||||||
|
public bool UnlimitedCounseling = false;
|
||||||
|
public Dictionary<int, int> DailyCounselCount = [];
|
||||||
}
|
}
|
||||||
public class WeeklyResetableData
|
public class WeeklyResetableData
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ public class User
|
|||||||
public long[] RepresentationTeamDataNew = [];
|
public long[] RepresentationTeamDataNew = [];
|
||||||
public Dictionary<int, ClearedTutorialData> ClearedTutorialData = [];
|
public Dictionary<int, ClearedTutorialData> ClearedTutorialData = [];
|
||||||
|
|
||||||
|
// Outpost buildings data
|
||||||
|
public List<NetUserOutpostData> OutpostBuildings = [];
|
||||||
|
|
||||||
public NetWallpaperData[] WallpaperList = [];
|
public NetWallpaperData[] WallpaperList = [];
|
||||||
public NetWallpaperBackground[] WallpaperBackground = [];
|
public NetWallpaperBackground[] WallpaperBackground = [];
|
||||||
public NetWallpaperJukeboxFavorite[] WallpaperFavoriteList = [];
|
public NetWallpaperJukeboxFavorite[] WallpaperFavoriteList = [];
|
||||||
@@ -61,6 +64,7 @@ public class User
|
|||||||
public Dictionary<int, bool> SubQuestData = [];
|
public Dictionary<int, bool> SubQuestData = [];
|
||||||
public int InfraCoreExp = 0;
|
public int InfraCoreExp = 0;
|
||||||
public int InfraCoreLvl = 1;
|
public int InfraCoreLvl = 1;
|
||||||
|
public Dictionary<int, bool> InfraCoreRewardReceived = [];
|
||||||
public UserPointData userPointData = new();
|
public UserPointData userPointData = new();
|
||||||
public DateTime LastLogin = DateTime.UtcNow;
|
public DateTime LastLogin = DateTime.UtcNow;
|
||||||
public DateTime BattleTime = DateTime.UtcNow;
|
public DateTime BattleTime = DateTime.UtcNow;
|
||||||
@@ -72,7 +76,9 @@ public class User
|
|||||||
|
|
||||||
public List<int> Memorial = [];
|
public List<int> Memorial = [];
|
||||||
public List<int> JukeboxBgm = [];
|
public List<int> JukeboxBgm = [];
|
||||||
|
public List<NetUserFavoriteItemData> FavoriteItems = [];
|
||||||
|
|
||||||
|
public List<NetUserFavoriteItemQuestData> FavoriteItemQuests = [];
|
||||||
public Dictionary<int, int> TowerProgress = [];
|
public Dictionary<int, int> TowerProgress = [];
|
||||||
|
|
||||||
public JukeBoxSetting LobbyMusic = new() { Location = NetJukeboxLocation.Lobby, TableId = 2, Type = NetJukeboxBgmType.JukeboxTableId };
|
public JukeBoxSetting LobbyMusic = new() { Location = NetJukeboxLocation.Lobby, TableId = 2, Type = NetJukeboxBgmType.JukeboxTableId };
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using System.Text.Json;
|
|
||||||
using EpinelPS.Database;
|
using EpinelPS.Database;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace EpinelPS.Utils
|
namespace EpinelPS.Utils
|
||||||
{
|
{
|
||||||
@@ -51,7 +51,7 @@ namespace EpinelPS.Utils
|
|||||||
Console.WriteLine("Loaded game config");
|
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)
|
if (_root == null)
|
||||||
{
|
{
|
||||||
@@ -67,8 +67,8 @@ namespace EpinelPS.Utils
|
|||||||
{
|
{
|
||||||
if (Root != null)
|
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;
|
return 2;
|
||||||
case "Module_D":
|
case "Module_D":
|
||||||
return 3;
|
return 3;
|
||||||
|
case "HarmonyCube":
|
||||||
|
return GetHarmonyCubePosition(item.ItemType);
|
||||||
default:
|
default:
|
||||||
Console.WriteLine("Unknown item subtype: " + subType);
|
Console.WriteLine("Unknown item subtype: " + subType);
|
||||||
break;
|
break;
|
||||||
@@ -114,6 +116,15 @@ namespace EpinelPS.Utils
|
|||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int GetHarmonyCubePosition(int itemType)
|
||||||
|
{
|
||||||
|
if (GameData.Instance.ItemHarmonyCubeTable.TryGetValue(itemType, out ItemHarmonyCubeRecord? harmonyCube))
|
||||||
|
{
|
||||||
|
return harmonyCube.location_id;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Takes multiple NetRewardData objects and merges it into one. Note that this function expects that rewards are already applied to user object.
|
/// Takes multiple NetRewardData objects and merges it into one. Note that this function expects that rewards are already applied to user object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -391,6 +402,8 @@ namespace EpinelPS.Utils
|
|||||||
{
|
{
|
||||||
RandomItemRecord winningRecord = Rng.PickWeightedItem(probabilityEntries);
|
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)
|
if (winningRecord.reward_value_min != winningRecord.reward_value_max)
|
||||||
{
|
{
|
||||||
Logging.WriteLine("TODO: reward_value_max", LogType.Warning);
|
Logging.WriteLine("TODO: reward_value_max", LogType.Warning);
|
||||||
|
|||||||
@@ -191,11 +191,39 @@ namespace EpinelPS.Utils
|
|||||||
}
|
}
|
||||||
else if (rewardType == "InfraCoreExp")
|
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()
|
ret.InfraCoreExp = new NetIncreaseExpData()
|
||||||
{
|
{
|
||||||
BeforeLv = user.InfraCoreLvl,
|
BeforeLv = beforeLv,
|
||||||
BeforeExp = user.InfraCoreExp,
|
BeforeExp = beforeExp,
|
||||||
// TODO
|
CurrentLv = user.InfraCoreLvl,
|
||||||
|
CurrentExp = user.InfraCoreExp,
|
||||||
|
GainExp = rewardCount
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else if (rewardType == "ItemRandomBox")
|
else if (rewardType == "ItemRandomBox")
|
||||||
@@ -221,6 +249,32 @@ namespace EpinelPS.Utils
|
|||||||
user.Items.Add(new ItemData() { Count = rewardCount, Isn = itm.Isn, ItemType = itm.Tid });
|
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
|
else
|
||||||
{
|
{
|
||||||
Logging.WriteLine("TODO: Reward type " + rewardType, LogType.Warning);
|
Logging.WriteLine("TODO: Reward type " + rewardType, LogType.Warning);
|
||||||
|
|||||||
Reference in New Issue
Block a user