Skill upgrade, more commands, fixes (#21)

* add BuyWallpaper

* added partial stage clear info handling, no longer fails get check

the creation of the clear info still has imperfections, so I've left it disabled for now. no point in letting it clutter the DB until the info is at least accurate in the UI.

* unfinished CP calculation stuff

still need to impl other tables (equipment), need a better way to capture outputs and compare them to retail, not really worth the excess work currently since CP calc isn't used anywhere critical right now

* AddItem command

* AddCharacter command

* finishalltutorials command

* addallmaterials command

also load the material items table

* skill upgrade impl

* update README

* potential fix for bodies not being removed properly on limit breaks and core upgrades

the client seems to be smart enough to deal with zero count item entries, so there's no real reason to remove zero count item entries from the DB

* check to make sure we have the character before adding a body

* use CreateWholeUserDataFromDbUser instead of doing a manual copy
This commit is contained in:
Kyle873
2024-12-20 14:39:21 -05:00
committed by GitHub
parent 6c58b3e137
commit b022eb688c
15 changed files with 947 additions and 440 deletions

View File

@@ -183,6 +183,7 @@ namespace EpinelPS.Database
public NetWallpaperJukeboxFavorite[] WallpaperFavoriteList = [];
public NetWallpaperPlaylist[] WallpaperPlaylistList = [];
public NetWallpaperJukebox[] WallpaperJukeboxList = [];
public List<int> LobbyDecoBackgroundList = [];
public Dictionary<int, NetUserTeamData> UserTeams = new Dictionary<int, NetUserTeamData>();
@@ -208,6 +209,8 @@ namespace EpinelPS.Database
public OutpostBuffs OutpostBuffs = new();
public Dictionary<int, UnlockData> ContentsOpenUnlocked = new();
public List<NetStageClearInfo> StageClearHistorys = [];
// Event data
public Dictionary<int, EventData> EventInfo = new();
public MogMinigameInfo MogInfo = new();
@@ -382,24 +385,12 @@ namespace EpinelPS.Database
if (item.Isn == isn)
{
if (item.Count == 1)
removed++;
item.Count -= count;
if (item.Count < 0)
{
Items.Remove(item);
count--;
}
else
{
// TODO test this
if (item.Count >= count)
{
removed++;
item.Count -= count;
}
else
{
removed += item.Count;
Items.Remove(item);
}
item.Count = 0;
}
}
}

View File

@@ -32,8 +32,9 @@ namespace EpinelPS.StaticInfo
private Dictionary<int, CampaignChapterRecord> chapterCampaignData;
private JArray characterCostumeTable;
public Dictionary<int, CharacterRecord> characterTable;
private Dictionary<int, ClearedTutorialData> tutorialTable;
private Dictionary<int, ItemEquipRecord> itemEquipTable;
public Dictionary<int, ClearedTutorialData> tutorialTable;
public Dictionary<int, ItemEquipRecord> itemEquipTable;
public Dictionary<int, ItemMaterialRecord> itemMaterialTable;
private Dictionary<string, JArray> FieldMapData = new Dictionary<string, JArray>(); // Fixed initialization
private Dictionary<int, CharacterLevelData> LevelData = new Dictionary<int, CharacterLevelData>(); // Fixed initialization
private Dictionary<int, TacticAcademyLessonRecord> TacticAcademyLessons = new Dictionary<int, TacticAcademyLessonRecord>(); // Fixed initialization
@@ -54,6 +55,9 @@ namespace EpinelPS.StaticInfo
public Dictionary<int, ArchiveEventDungeonStageRecord> archiveEventDungeonStageRecords = new Dictionary<int, ArchiveEventDungeonStageRecord>();
public Dictionary<int, UserTitleRecord> userTitleRecords = new Dictionary<int, UserTitleRecord>();
public Dictionary<int, ArchiveMessengerConditionRecord> archiveMessengerConditionRecords;
public Dictionary<int, CharacterStatRecord> characterStatTable;
public Dictionary<int, SkillInfoRecord> skillInfoTable;
public Dictionary<int, CostRecord> costTable;
@@ -90,6 +94,10 @@ namespace EpinelPS.StaticInfo
ZipStream = new();
tutorialTable = new();
itemEquipTable = new();
itemMaterialTable = new();
characterStatTable = new();
skillInfoTable = new();
costTable = new();
// Initialize Jukebox data dictionaries
jukeboxListDataRecords = new Dictionary<int, JukeboxListRecord>();
@@ -320,6 +328,12 @@ namespace EpinelPS.StaticInfo
this.itemEquipTable.Add(obj.id, obj);
}
var itemMaterialTable = await LoadZip<ItemMaterialTable>("ItemMaterialTable.json", progress);
foreach (var obj in itemMaterialTable.records)
{
this.itemMaterialTable.Add(obj.id, obj);
}
var characterLevelTable = await LoadZip("CharacterLevelTable.json", progress);
foreach (JToken item in characterLevelTable)
@@ -460,6 +474,24 @@ namespace EpinelPS.StaticInfo
// Load Jukebox data
await LoadJukeboxListData(progress);
await LoadJukeboxThemeData(progress);
var characterStatTable = await LoadZip<CharacterStatTable>("CharacterStatTable.json", progress);
foreach (var obj in characterStatTable.records)
{
this.characterStatTable.Add(obj.id, obj);
}
var skillinfoTable = await LoadZip<SkillInfoTable>("SkillInfoTable.json", progress);
foreach (var obj in skillinfoTable.records)
{
this.skillInfoTable.Add(obj.id, obj);
}
var costTable = await LoadZip<CostTable>("CostTable.json", progress);
foreach (var obj in costTable.records)
{
this.costTable.Add(obj.id, obj);
}
}
public async Task LoadJukeboxListData(ProgressBar bar)

View File

@@ -131,11 +131,38 @@
{
public int id;
public int piece_id;
public string original_rare;
public string corporation;
public int grade_core_id;
public int name_code;
public int grow_grade;
public string original_rare;
public string corporation;
public int grade_core_id;
public int name_code;
public int grow_grade;
public int stat_enhance_id;
public string character_class;
public List<int> element_id;
public int critical_ratio;
public int critical_damage;
public int shot_id;
public int bonusrange_min;
public int bonusrange_max;
public string use_burst_skill;
public string change_burst_step;
public int burst_apply_delay;
public int burst_duration;
public int ulti_skill_id;
public int skill1_id;
public string skill1_table;
public int skill2_id;
public string skill2_table;
public string eff_category_type;
public int eff_category_value;
public string category_type_1;
public string category_type_2;
public string category_type_3;
public string cv_localkey;
public string squad;
public bool is_visible;
public bool prism_is_active;
public bool is_detail_close;
}
public class CharacterTable
{
@@ -427,4 +454,82 @@
public List<ArchiveMessengerConditionRecord> records;
}
public class CharacterStatRecord
{
public int id;
public int group;
public int level;
public int level_hp;
public int level_attack;
public int level_defence;
public int level_energy_resist;
public int level_bio_resist;
}
public class CharacterStatTable
{
public List<CharacterStatRecord> records;
}
public class ItemMaterialRecord
{
public int id;
public string name_localkey;
public string description_localkey;
public string resource_id;
public string item_type;
public string item_sub_type;
public string item_rare;
public int item_value;
public string material_type;
public int material_value;
public int stack_max;
}
public class ItemMaterialTable
{
public List<ItemMaterialRecord> records;
}
public class SkillInfoRecord
{
public int id;
public int group_id;
public int skill_level;
public int next_level_id;
public int level_up_cost_id;
public string icon;
public string name_localkey;
public string description_localkey;
public string info_description_localkey;
public List<DescriptionValue> description_value_list;
}
public class DescriptionValue
{
public string description_value;
}
public class SkillInfoTable
{
public List<SkillInfoRecord> records;
}
public class CostRecord
{
public int id;
public List<CostData> costs;
}
public class CostData
{
public string item_type;
public int item_id;
public int item_value;
}
public class CostTable
{
public List<CostRecord> records;
}
}

View File

@@ -55,12 +55,9 @@ namespace EpinelPS.LobbyServer.Msgs.Character
};
// remove spare body item
var bodyItem = user.Items.FirstOrDefault(i => i.Isn == req.Isn);
user.RemoveItemBySerialNumber(req.Isn, 1);
foreach (var item in user.Items)
{
response.Items.Add(NetUtils.ToNet(item));
}
response.Items.Add(NetUtils.ToNet(bodyItem));
// replace any reference to the old character to the new TID
// Check if RepresentationTeamData exists and has slots

View File

@@ -0,0 +1,87 @@
using EpinelPS.Database;
using EpinelPS.StaticInfo;
using EpinelPS.Utils;
namespace EpinelPS.LobbyServer.Msgs.Character
{
[PacketPath("/character/skill/levelup")]
public class SkillLevelUp : LobbyMsgHandler
{
protected override async Task HandleAsync()
{
var req = await ReadData<ReqCharacterSkillLevelUp>();
var user = GetUser();
var response = new ResCharacterSkillLevelUp();
var character = user.Characters.FirstOrDefault(c => c.Csn == req.Csn);
var charRecord = GameData.Instance.characterTable.Values.FirstOrDefault(c => c.id == character.Tid);
var skillIdMap = new Dictionary<int, int>
{
{ 1, charRecord.ulti_skill_id },
{ 2, charRecord.skill1_id },
{ 3, charRecord.skill2_id }
};
var skillLevelMap = new Dictionary<int, int>
{
{ 1, character.UltimateLevel },
{ 2, character.Skill1Lvl },
{ 3, character.Skill2Lvl }
};
var skillRecord = GameData.Instance.skillInfoTable.Values.FirstOrDefault(s => s.id == skillIdMap[req.Category] + (skillLevelMap[req.Category] - 1));
var costRecord = GameData.Instance.costTable.Values.FirstOrDefault(c => c.id == skillRecord.level_up_cost_id);
foreach (var cost in costRecord.costs.Where(i => i.item_type != "None"))
{
var item = user.Items.FirstOrDefault(i => i.ItemType == cost.item_id);
item.Count -= cost.item_value;
response.Items.Add(new NetUserItemData
{
Isn = item.Isn,
Tid = cost.item_id,
Count = item.Count,
Csn = item.Csn,
Corporation = item.Corp,
Level = item.Level,
Exp = item.Exp,
Position = item.Position
});
}
var newChar = new NetUserCharacterDefaultData
{
CostumeId = character.CostumeId,
Csn = character.Csn,
Level = character.Level,
Grade = character.Grade,
Tid = character.Tid,
DispatchTid = character.Tid,
Skill1Lv = character.Skill1Lvl,
Skill2Lv = character.Skill2Lvl,
UltiSkillLv = character.UltimateLevel,
};
if (req.Category == 1)
{
character.UltimateLevel++;
newChar.UltiSkillLv++;
}
else if (req.Category == 2)
{
character.Skill1Lvl++;
newChar.Skill1Lv++;
}
else if (req.Category == 3)
{
character.Skill2Lvl++;
newChar.Skill2Lv++;
}
response.Character = newChar;
JsonDb.Save();
await WriteDataAsync(response);
}
}
}

View File

@@ -56,12 +56,9 @@ namespace EpinelPS.LobbyServer.Msgs.Character
};
// remove spare body item
var bodyItem = user.Items.FirstOrDefault(i => i.Isn == req.Isn);
user.RemoveItemBySerialNumber(req.Isn, 1);
foreach (var item in user.Items)
{
response.Items.Add(NetUtils.ToNet(item));
}
response.Items.Add(NetUtils.ToNet(bodyItem));
// replace any reference to the old character to the new TID
// Check if RepresentationTeamData exists and has slots

View File

@@ -64,55 +64,58 @@ namespace EpinelPS.LobbyServer.Msgs.Gacha
// Add each character's item to user.Items if the character exists in user.Characters
foreach (var characterData in selectedCharacters)
{
// Check if the item for this character already exists in user.Items based on ItemType
var existingItem = user.Items.FirstOrDefault(item => item.ItemType == characterData.piece_id);
if (user.HasCharacter(characterData.id))
{
// Check if the item for this character already exists in user.Items based on ItemType
var existingItem = user.Items.FirstOrDefault(item => item.ItemType == characterData.piece_id);
if (existingItem != null)
{
// If the item exists, increment the count
existingItem.Count += 1;
if (existingItem != null)
{
// If the item exists, increment the count
existingItem.Count += 1;
// Send the updated item in the response
response.Items.Add(new NetUserItemData()
{
Tid = existingItem.ItemType,
Csn = existingItem.Csn,
Count = existingItem.Count,
Level = existingItem.Level,
Exp = existingItem.Exp,
Position = existingItem.Position,
Isn = existingItem.Isn
});
}
else
{
// If the item does not exist, create a new item entry
var newItem = new ItemData()
{
ItemType = characterData.piece_id,
Csn = 0,
Count = 1, // or any relevant count
Level = 0,
Exp = 0,
Position = 0,
Corp = 0,
Isn = user.GenerateUniqueItemId()
};
user.Items.Add(newItem);
// Send the updated item in the response
response.Items.Add(new NetUserItemData()
{
Tid = existingItem.ItemType,
Csn = existingItem.Csn,
Count = existingItem.Count,
Level = existingItem.Level,
Exp = existingItem.Exp,
Position = existingItem.Position,
Isn = existingItem.Isn
});
}
else
{
// If the item does not exist, create a new item entry
var newItem = new ItemData()
{
ItemType = characterData.piece_id,
Csn = 0,
Count = 1, // or any relevant count
Level = 0,
Exp = 0,
Position = 0,
Corp = 0,
Isn = user.GenerateUniqueItemId()
};
user.Items.Add(newItem);
// Add the new item to response
response.Items.Add(new NetUserItemData()
{
Tid = newItem.ItemType,
Csn = newItem.Csn,
Count = newItem.Count,
Level = newItem.Level,
Exp = newItem.Exp,
Position = newItem.Position,
Isn = newItem.Isn
});
}
}
// Add the new item to response
response.Items.Add(new NetUserItemData()
{
Tid = newItem.ItemType,
Csn = newItem.Csn,
Count = newItem.Count,
Level = newItem.Level,
Exp = newItem.Exp,
Position = newItem.Position,
Isn = newItem.Isn
});
}
}
}
// Populate the 2D array with characterId and pieceId for each selected character
foreach (var characterData in selectedCharacters)

View File

@@ -104,6 +104,8 @@ namespace EpinelPS.LobbyServer.Msgs.Stage
}
}
// CreateClearInfo(user);
var key = (clearedStage.chapter_id - 1) + "_" + clearedStage.chapter_mod;
if (!user.FieldInfoNew.ContainsKey(key))
user.FieldInfoNew.Add(key, new FieldInfoNew());
@@ -309,5 +311,29 @@ namespace EpinelPS.LobbyServer.Msgs.Stage
}
// TODO: add neon
}
private static void CreateClearInfo(Database.User user)
{
NetStageClearInfo clearInfo = new NetStageClearInfo
{
User = LobbyHandler.CreateWholeUserDataFromDbUser(user),
TeamCombat = user.RepresentationTeamData.TeamCombat,
ClearedAt = DateTimeOffset.UtcNow.Ticks
};
foreach (var character in user.RepresentationTeamData.Slots)
{
clearInfo.Slots.Add(new NetStageClearInfoTeam()
{
Slot = character.Slot,
Tid = character.Tid,
Level = character.Level,
Combat = FormulaUtils.CalculateCP(user, character.Csn),
CharacterType = StageClearInfoTeamCharacterType.StageClearInfoTeamCharacterTypeOwnedCharacter // TODO: how do we get this?
});
}
user.StageClearHistorys.Add(clearInfo);
}
}
}

View File

@@ -0,0 +1,19 @@
using EpinelPS.Utils;
namespace EpinelPS.LobbyServer.Msgs.Stage
{
[PacketPath("/stageclearinfo/get")]
public class GetStageClearInfo : LobbyMsgHandler
{
protected override async Task HandleAsync()
{
var req = await ReadData<ReqGetStageClearInfo>();
var response = new ResGetStageClearInfo();
var user = GetUser();
response.Historys.AddRange(user.StageClearHistorys);
await WriteDataAsync(response);
}
}
}

View File

@@ -0,0 +1,24 @@
using EpinelPS.Database;
using EpinelPS.Utils;
namespace EpinelPS.LobbyServer.Msgs.User
{
[PacketPath("/user/wallpaper/buy")]
public class BuyWallpaper : LobbyMsgHandler
{
protected override async Task HandleAsync()
{
var req = await ReadData<ReqBuyLobbyDecoBackground>();
var response = new ResBuyLobbyDecoBackground();
var user = GetUser();
user.LobbyDecoBackgroundList.Add(req.LobbyDecoBackgroundId);
response.OwnedLobbyDecoBackgroundIdList.Add(user.LobbyDecoBackgroundList);
JsonDb.Save();
await WriteDataAsync(response);
}
}
}

View File

@@ -85,6 +85,8 @@ namespace EpinelPS.LobbyServer.Msgs.User
response.LastClearedNormalMainStageId = user.LastNormalStageCleared;
response.TimeRewardBuffs.AddRange(NetUtils.GetOutpostTimeReward(user));
response.OwnedLobbyDecoBackgroundIdList.AddRange(user.LobbyDecoBackgroundList);
await WriteDataAsync(response);
}
}

View File

@@ -17,6 +17,7 @@ namespace EpinelPS.LobbyServer.Msgs.User
response.WallpaperJukeboxList.AddRange(user.WallpaperJukeboxList);
response.WallpaperBackgroundList.AddRange(user.WallpaperBackground);
response.WallpaperFavoriteList.AddRange(user.WallpaperFavoriteList);
response.OwnedLobbyDecoBackgroundIdList.AddRange(user.LobbyDecoBackgroundList);
// TODO: JukeboxIdList

View File

@@ -217,13 +217,16 @@ namespace EpinelPS
Console.WriteLine(" unban - unban selected user from game");
Console.WriteLine(" exit - exit server application");
Console.WriteLine(" completestage (chapter num)-(stage number) - complete selected stage and get rewards (and all previous ones). Example completestage 15-1. Note that the exact stage number cleared may not be exact.");
Console.WriteLine(" sickpulls (requires selecting user first) allows for all characters to have equal chances of getting pulled");
Console.WriteLine(" sickpulls (requires selecting user first) allows for all characters to have equal chances of getting pulled");
Console.WriteLine(" SetLevel (level) - Set all characters' level (between 1 and 999 takes effect on game and server restart)");
Console.WriteLine(" SetSkillLevel (level) - Set all characters' skill levels between 1 and 10 (takes effect on game and server restart)");
Console.WriteLine(" addallcharacters - Add all missing characters to the selected user with default levels and skills (takes effect on game and server restart)");
Console.WriteLine(" addallmaterials (amount) - Add all materials to the selected user with default levels and skills (takes effect on game and server restart)");
Console.WriteLine(" finishalltutorials - finish all tutorials for the selected user (takes effect on game and server restart)");
Console.WriteLine(" SetCoreLevel (core level / 0-3 sets stars) - Set all characters' grades based on the input (from 0 to 11)");
}
Console.WriteLine(" AddItem (id) (amount) - Adds an item to the selected user (takes effect on game and server restart)");
Console.WriteLine(" AddCharacter (id) - Adds a character to the selected user (takes effect on game and server restart)");
}
else if (input == "ls /users")
{
Console.WriteLine("Id,Username,Nickname");
@@ -263,299 +266,378 @@ namespace EpinelPS
Console.WriteLine("Usage: chroot (user id)");
}
}
else if (input == "addallcharacters")
{
if (selectedUser == 0)
{
Console.WriteLine("No user selected");
}
else
{
var user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == selectedUser);
if (user == null)
{
Console.WriteLine("Selected user does not exist");
selectedUser = 0;
prompt = "# ";
}
else
{
// Group characters by name_code and always add those with grade_core_id == 11, 103, and include grade_core_id == 201
var allCharacters = GameData.Instance.characterTable.Values
.GroupBy(c => c.name_code) // Group by name_code to treat same name_code as one character 3999 = marian
.SelectMany(g => g.Where(c => c.grade_core_id == 1 || c.grade_core_id == 101 || c.grade_core_id == 201 || c.name_code == 3999)) // Always add characters with grade_core_id == 11 and 103
.ToList();
else if (input == "addallcharacters")
{
if (selectedUser == 0)
{
Console.WriteLine("No user selected");
}
else
{
var user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == selectedUser);
if (user == null)
{
Console.WriteLine("Selected user does not exist");
selectedUser = 0;
prompt = "# ";
}
else
{
// Group characters by name_code and always add those with grade_core_id == 11, 103, and include grade_core_id == 201
var allCharacters = GameData.Instance.characterTable.Values
.GroupBy(c => c.name_code) // Group by name_code to treat same name_code as one character 3999 = marian
.SelectMany(g => g.Where(c => c.grade_core_id == 1 || c.grade_core_id == 101 || c.grade_core_id == 201 || c.name_code == 3999)) // Always add characters with grade_core_id == 11 and 103
.ToList();
foreach (var character in allCharacters)
{
if (!user.HasCharacter(character.id))
{
user.Characters.Add(new Database.Character()
{
CostumeId = 0,
Csn = user.GenerateUniqueCharacterId(),
Grade = 0,
Level = 1,
Skill1Lvl = 1,
Skill2Lvl = 1,
Tid = character.id, // Tid is the character ID
UltimateLevel = 1
});
}
}
foreach (var character in allCharacters)
{
if (!user.HasCharacter(character.id))
{
user.Characters.Add(new Database.Character()
{
CostumeId = 0,
Csn = user.GenerateUniqueCharacterId(),
Grade = 0,
Level = 1,
Skill1Lvl = 1,
Skill2Lvl = 1,
Tid = character.id, // Tid is the character ID
UltimateLevel = 1
});
}
}
Console.WriteLine("Added all missing characters to user " + user.Username);
JsonDb.Save();
}
}
}
else if (input.StartsWith("SetCoreLevel"))
{
if (selectedUser == 0)
{
Console.WriteLine("No user selected");
}
else
{
var user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == selectedUser);
if (user == null)
{
Console.WriteLine("Selected user does not exist");
selectedUser = 0;
prompt = "# ";
}
else if (args.Length == 2 && int.TryParse(args[1], out int inputGrade) && inputGrade >= 0 && inputGrade <= 11)
{
foreach (var character in user.Characters)
{
// Get current character's Tid
int tid = character.Tid;
Console.WriteLine("Added all missing characters to user " + user.Username);
JsonDb.Save();
}
}
}
else if (input == "addallmaterials")
{
if (selectedUser == 0)
{
Console.WriteLine("No user selected");
}
else
{
var user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == selectedUser);
if (user == null)
{
Console.WriteLine("Selected user does not exist");
selectedUser = 0;
prompt = "# ";
}
else
{
int amount = 1000000;
if (args.Length >= 2)
{
int.TryParse(args[1], out amount);
}
// Get the character data from the character table
if (!GameData.Instance.characterTable.TryGetValue(tid, out var charData))
{
Console.WriteLine($"Character data not found for Tid {tid}");
continue;
}
foreach (var tableItem in GameData.Instance.itemMaterialTable.Values)
{
ItemData? item = user.Items.FirstOrDefault(i => i.ItemType == tableItem.id);
int currentGradeCoreId = charData.grade_core_id;
int nameCode = charData.name_code;
string originalRare = charData.original_rare;
if (item == null)
{
user.Items.Add(new ItemData
{
Isn = user.GenerateUniqueItemId(),
ItemType = tableItem.id,
Level = 1,
Exp = 1,
Count = amount
});
}
else
{
item.Count += amount;
}
}
// Skip characters with original_rare == "R"
if (originalRare == "R" || nameCode == 3999)
{
continue;
}
Console.WriteLine($"Added {amount} of all materials to user " + user.Username);
JsonDb.Save();
}
}
}
else if (input == "finishalltutorials")
{
if (selectedUser == 0)
{
Console.WriteLine("No user selected");
}
else
{
var user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == selectedUser);
if (user == null)
{
Console.WriteLine("Selected user does not exist");
selectedUser = 0;
prompt = "# ";
}
else
{
foreach (var tutorial in GameData.Instance.tutorialTable.Values)
{
if (!user.ClearedTutorialData.ContainsKey(tutorial.id))
{
user.ClearedTutorialData.Add(tutorial.id, tutorial);
}
}
// Now handle normal SR and SSR characters
int maxGradeCoreId = 0;
Console.WriteLine("Finished all tutorials for user " + user.Username);
JsonDb.Save();
}
}
}
else if (input.StartsWith("SetCoreLevel"))
{
if (selectedUser == 0)
{
Console.WriteLine("No user selected");
}
else
{
var user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == selectedUser);
if (user == null)
{
Console.WriteLine("Selected user does not exist");
selectedUser = 0;
prompt = "# ";
}
else if (args.Length == 2 && int.TryParse(args[1], out int inputGrade) && inputGrade >= 0 && inputGrade <= 11)
{
foreach (var character in user.Characters)
{
// Get current character's Tid
int tid = character.Tid;
// If the character is "SSR", it can have a grade_core_id from 1 to 11
if (originalRare == "SSR")
{
maxGradeCoreId = 11; // SSR characters can go from 1 to 11
// Get the character data from the character table
if (!GameData.Instance.characterTable.TryGetValue(tid, out var charData))
{
Console.WriteLine($"Character data not found for Tid {tid}");
continue;
}
// Calculate the new grade_core_id within the bounds
int newGradeCoreId = Math.Min(inputGrade + 1, maxGradeCoreId); // +1 because inputGrade starts from 0 for SSRs
int currentGradeCoreId = charData.grade_core_id;
int nameCode = charData.name_code;
string originalRare = charData.original_rare;
// Find the character with the same name_code and new grade_core_id
var newCharData = GameData.Instance.characterTable.Values.FirstOrDefault(c =>
c.name_code == nameCode && c.grade_core_id == newGradeCoreId);
// Skip characters with original_rare == "R"
if (originalRare == "R" || nameCode == 3999)
{
continue;
}
if (newCharData != null)
{
// Update the character's Tid and Grade
character.Tid = newCharData.id;
character.Grade = inputGrade;
}
// Now handle normal SR and SSR characters
int maxGradeCoreId = 0;
}
// If the character is "SSR", it can have a grade_core_id from 1 to 11
if (originalRare == "SSR")
{
maxGradeCoreId = 11; // SSR characters can go from 1 to 11
// If the character is "SR", it can have a grade_core_id from 101 to 103
else if (originalRare == "SR")
{
maxGradeCoreId = 103; // SR characters can go from 101 to 103
// Calculate the new grade_core_id within the bounds
int newGradeCoreId = Math.Min(inputGrade + 1, maxGradeCoreId); // +1 because inputGrade starts from 0 for SSRs
// Start from 101 and increment based on inputGrade (inputGrade 0 -> grade_core_id 101)
int newGradeCoreId = Math.Min(101 + inputGrade, maxGradeCoreId); // Starts at 101
// Find the character with the same name_code and new grade_core_id
var newCharData = GameData.Instance.characterTable.Values.FirstOrDefault(c =>
c.name_code == nameCode && c.grade_core_id == newGradeCoreId);
// Find the character with the same name_code and new grade_core_id
var newCharData = GameData.Instance.characterTable.Values.FirstOrDefault(c =>
c.name_code == nameCode && c.grade_core_id == newGradeCoreId);
if (newCharData != null)
{
// Update the character's Tid and Grade
character.Tid = newCharData.id;
character.Grade = inputGrade;
}
if (newCharData != null)
{
// Update the character's Tid and Grade
character.Tid = newCharData.id;
character.Grade = inputGrade;
}
}
}
// If the character is "SR", it can have a grade_core_id from 101 to 103
else if (originalRare == "SR")
{
maxGradeCoreId = 103; // SR characters can go from 101 to 103
}
Console.WriteLine($"Core level of all characters have been set to {inputGrade}");
JsonDb.Save();
}
else
{
Console.WriteLine("Invalid argument. Core level must be between 0 and 11.");
}
}
//code above WILL change tids in user.characters so this will update them in representation team
foreach (var user in JsonDb.Instance.Users)
{
// Check if RepresentationTeamData exists and has slots
if (user.RepresentationTeamData != null && user.RepresentationTeamData.Slots != null)
{
// Iterate through RepresentationTeamData slots
foreach (var slot in user.RepresentationTeamData.Slots)
{
// Find the character in user's character list that matches the slot's Csn
var correspondingCharacter = user.Characters.FirstOrDefault(c => c.Csn == slot.Csn);
// Start from 101 and increment based on inputGrade (inputGrade 0 -> grade_core_id 101)
int newGradeCoreId = Math.Min(101 + inputGrade, maxGradeCoreId); // Starts at 101
if (correspondingCharacter != null)
{
// Update the Tid value if it differs
if (slot.Tid != correspondingCharacter.Tid)
{
slot.Tid = correspondingCharacter.Tid;
}
}
}
}
}
// Find the character with the same name_code and new grade_core_id
var newCharData = GameData.Instance.characterTable.Values.FirstOrDefault(c =>
c.name_code == nameCode && c.grade_core_id == newGradeCoreId);
// Save the updated data
JsonDb.Save();
if (newCharData != null)
{
// Update the character's Tid and Grade
character.Tid = newCharData.id;
character.Grade = inputGrade;
}
}
}
}
Console.WriteLine($"Core level of all characters have been set to {inputGrade}");
JsonDb.Save();
}
else
{
Console.WriteLine("Invalid argument. Core level must be between 0 and 11.");
}
}
//code above WILL change tids in user.characters so this will update them in representation team
foreach (var user in JsonDb.Instance.Users)
{
// Check if RepresentationTeamData exists and has slots
if (user.RepresentationTeamData != null && user.RepresentationTeamData.Slots != null)
{
// Iterate through RepresentationTeamData slots
foreach (var slot in user.RepresentationTeamData.Slots)
{
// Find the character in user's character list that matches the slot's Csn
var correspondingCharacter = user.Characters.FirstOrDefault(c => c.Csn == slot.Csn);
if (correspondingCharacter != null)
{
// Update the Tid value if it differs
if (slot.Tid != correspondingCharacter.Tid)
{
slot.Tid = correspondingCharacter.Tid;
}
}
}
}
}
// Save the updated data
JsonDb.Save();
}
else if (input == "sickpulls")
{
if (selectedUser == 0)
{
Console.WriteLine("No user selected");
}
else
{
var user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == selectedUser);
if (user == null)
{
Console.WriteLine("Selected user does not exist");
selectedUser = 0;
prompt = "# ";
}
else
{
// Check current value of sickpulls and toggle it
bool currentSickPulls = EpinelPS.Database.JsonDb.IsSickPulls(user);
if (currentSickPulls)
{
user.sickpulls = false;
Console.WriteLine("sickpulls is now set to false for user " + user.Username);
}
else
{
user.sickpulls = true;
Console.WriteLine("sickpulls is now set to true for user " + user.Username);
}
else if (input == "sickpulls")
{
if (selectedUser == 0)
{
Console.WriteLine("No user selected");
}
else
{
var user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == selectedUser);
if (user == null)
{
Console.WriteLine("Selected user does not exist");
selectedUser = 0;
prompt = "# ";
}
else
{
// Check current value of sickpulls and toggle it
bool currentSickPulls = EpinelPS.Database.JsonDb.IsSickPulls(user);
if (currentSickPulls)
{
user.sickpulls = false;
Console.WriteLine("sickpulls is now set to false for user " + user.Username);
}
else
{
user.sickpulls = true;
Console.WriteLine("sickpulls is now set to true for user " + user.Username);
}
// Save the changes to the database
JsonDb.Save();
}
}
}
else if (input.StartsWith("SetLevel"))
{
if (selectedUser == 0)
{
Console.WriteLine("No user selected");
}
else
{
var user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == selectedUser);
if (user == null)
{
Console.WriteLine("Selected user does not exist");
selectedUser = 0;
prompt = "# ";
}
else if (args.Length == 2 && int.TryParse(args[1], out int level) && level >= 1 && level <= 999)
{
foreach (var character in user.Characters)
{
character.Level = level;
}
Console.WriteLine("Set all characters' level to " + level);
JsonDb.Save();
}
else
{
Console.WriteLine("Invalid argument. Level must be between 1 and 999.");
}
}
//code above WILL change levels in user.characters so this will update them in representation team
foreach (var user in JsonDb.Instance.Users)
{
// Check if RepresentationTeamData exists and has slots
if (user.RepresentationTeamData != null && user.RepresentationTeamData.Slots != null)
{
// Iterate through RepresentationTeamData slots
foreach (var slot in user.RepresentationTeamData.Slots)
{
// Find the character in user's character list that matches the slot's Csn
var correspondingCharacter = user.Characters.FirstOrDefault(c => c.Csn == slot.Csn);
// Save the changes to the database
JsonDb.Save();
}
}
}
else if (input.StartsWith("SetLevel"))
{
if (selectedUser == 0)
{
Console.WriteLine("No user selected");
}
else
{
var user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == selectedUser);
if (user == null)
{
Console.WriteLine("Selected user does not exist");
selectedUser = 0;
prompt = "# ";
}
else if (args.Length == 2 && int.TryParse(args[1], out int level) && level >= 1 && level <= 999)
{
foreach (var character in user.Characters)
{
character.Level = level;
}
Console.WriteLine("Set all characters' level to " + level);
JsonDb.Save();
}
else
{
Console.WriteLine("Invalid argument. Level must be between 1 and 999.");
}
}
//code above WILL change levels in user.characters so this will update them in representation team
foreach (var user in JsonDb.Instance.Users)
{
// Check if RepresentationTeamData exists and has slots
if (user.RepresentationTeamData != null && user.RepresentationTeamData.Slots != null)
{
// Iterate through RepresentationTeamData slots
foreach (var slot in user.RepresentationTeamData.Slots)
{
// Find the character in user's character list that matches the slot's Csn
var correspondingCharacter = user.Characters.FirstOrDefault(c => c.Csn == slot.Csn);
if (correspondingCharacter != null)
{
// Update the Level value if it differs
if (slot.Level != correspondingCharacter.Level)
{
slot.Level = correspondingCharacter.Level;
}
}
}
}
}
if (correspondingCharacter != null)
{
// Update the Level value if it differs
if (slot.Level != correspondingCharacter.Level)
{
slot.Level = correspondingCharacter.Level;
}
}
}
}
}
// Save the updated data
JsonDb.Save();
}
else if (input.StartsWith("SetSkillLevel"))
{
if (selectedUser == 0)
{
Console.WriteLine("No user selected");
}
else
{
var user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == selectedUser);
if (user == null)
{
Console.WriteLine("Selected user does not exist");
selectedUser = 0;
prompt = "# ";
}
else if (args.Length == 2 && int.TryParse(args[1], out int skillLevel) && skillLevel >= 1 && skillLevel <= 10)
{
foreach (var character in user.Characters)
{
character.UltimateLevel = skillLevel;
character.Skill1Lvl = skillLevel;
character.Skill2Lvl = skillLevel;
}
Console.WriteLine("Set all characters' skill levels to " + skillLevel);
JsonDb.Save();
}
else
{
Console.WriteLine("Invalid argument. Skill level must be between 1 and 10.");
}
}
}
// Save the updated data
JsonDb.Save();
}
else if (input.StartsWith("SetSkillLevel"))
{
if (selectedUser == 0)
{
Console.WriteLine("No user selected");
}
else
{
var user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == selectedUser);
if (user == null)
{
Console.WriteLine("Selected user does not exist");
selectedUser = 0;
prompt = "# ";
}
else if (args.Length == 2 && int.TryParse(args[1], out int skillLevel) && skillLevel >= 1 && skillLevel <= 10)
{
foreach (var character in user.Characters)
{
character.UltimateLevel = skillLevel;
character.Skill1Lvl = skillLevel;
character.Skill2Lvl = skillLevel;
}
Console.WriteLine("Set all characters' skill levels to " + skillLevel);
JsonDb.Save();
}
else
{
Console.WriteLine("Invalid argument. Skill level must be between 1 and 10.");
}
}
}
else if (input.StartsWith("rmuser"))
{
@@ -591,108 +673,218 @@ namespace EpinelPS
}
}
}
else if (input.StartsWith("completestage"))
{
if (selectedUser == 0)
{
Console.WriteLine("No user selected");
}
else
{
var user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == selectedUser);
if (user == null)
{
Console.WriteLine("Selected user does not exist");
selectedUser = 0;
prompt = "# ";
}
else
{
if (args.Length == 2)
{
var input2 = args[1];
try
{
var chapterParsed = int.TryParse(input2.Split('-')[0], out int chapterNumber);
var stageParsed = int.TryParse(input2.Split('-')[1], out int stageNumber);
else if (input.StartsWith("completestage"))
{
if (selectedUser == 0)
{
Console.WriteLine("No user selected");
}
else
{
var user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == selectedUser);
if (user == null)
{
Console.WriteLine("Selected user does not exist");
selectedUser = 0;
prompt = "# ";
}
else
{
if (args.Length == 2)
{
var input2 = args[1];
try
{
var chapterParsed = int.TryParse(input2.Split('-')[0], out int chapterNumber);
var stageParsed = int.TryParse(input2.Split('-')[1], out int stageNumber);
if (chapterParsed && stageParsed)
{
Console.WriteLine($"Chapter number: {chapterNumber}, Stage number: {stageNumber}");
if (chapterParsed && stageParsed)
{
Console.WriteLine($"Chapter number: {chapterNumber}, Stage number: {stageNumber}");
// Complete main stages
for (int i = 0; i <= chapterNumber; i++)
{
var stages = GameData.Instance.GetStageIdsForChapter(i, true);
int target = 1;
foreach (var item in stages)
{
if (!user.IsStageCompleted(item, true))
{
Console.WriteLine("Completing stage " + item);
ClearStage.CompleteStage(user, item, true);
}
// Complete main stages
for (int i = 0; i <= chapterNumber; i++)
{
var stages = GameData.Instance.GetStageIdsForChapter(i, true);
int target = 1;
foreach (var item in stages)
{
if (!user.IsStageCompleted(item, true))
{
Console.WriteLine("Completing stage " + item);
ClearStage.CompleteStage(user, item, true);
}
if (i == chapterNumber && target == stageNumber)
{
break;
}
if (i == chapterNumber && target == stageNumber)
{
break;
}
target++;
}
}
target++;
}
}
// Process scenario and regular stages
Console.WriteLine($"Processing stages for chapters 0 to {chapterNumber}");
// Process scenario and regular stages
Console.WriteLine($"Processing stages for chapters 0 to {chapterNumber}");
for (int chapter = 0; chapter <= chapterNumber; chapter++)
{
Console.WriteLine($"Processing chapter: {chapter}");
for (int chapter = 0; chapter <= chapterNumber; chapter++)
{
Console.WriteLine($"Processing chapter: {chapter}");
var stages = GameData.Instance.GetScenarioStageIdsForChapter(chapter)
.Where(stageId => GameData.Instance.IsValidScenarioStage(stageId, chapterNumber, stageNumber))
.ToList();
var stages = GameData.Instance.GetScenarioStageIdsForChapter(chapter)
.Where(stageId => GameData.Instance.IsValidScenarioStage(stageId, chapterNumber, stageNumber))
.ToList();
Console.WriteLine($"Found {stages.Count} stages for chapter {chapter}");
Console.WriteLine($"Found {stages.Count} stages for chapter {chapter}");
foreach (var stage in stages)
{
if (!user.CompletedScenarios.Contains(stage))
{
user.CompletedScenarios.Add(stage);
Console.WriteLine($"Added stage {stage} to CompletedScenarios");
}
else
{
Console.WriteLine($"Stage {stage} is already completed");
}
}
}
foreach (var stage in stages)
{
if (!user.CompletedScenarios.Contains(stage))
{
user.CompletedScenarios.Add(stage);
Console.WriteLine($"Added stage {stage} to CompletedScenarios");
}
else
{
Console.WriteLine($"Stage {stage} is already completed");
}
}
}
// Save changes to user data
JsonDb.Save();
}
else
{
Console.WriteLine("Chapter and stage number must be valid integers");
}
}
catch (Exception ex)
{
Console.WriteLine("Exception: " + ex.ToString());
}
}
else
{
Console.WriteLine("Invalid argument length, must be 1");
}
}
}
}
// Save changes to user data
JsonDb.Save();
}
else
{
Console.WriteLine("Chapter and stage number must be valid integers");
}
}
catch (Exception ex)
{
Console.WriteLine("Exception: " + ex.ToString());
}
}
else
{
Console.WriteLine("Invalid argument length, must be 1");
}
}
}
}
else if (input.StartsWith("AddItem"))
{
if (selectedUser == 0)
{
Console.WriteLine("No user selected");
}
else
{
var user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == selectedUser);
if (user == null)
{
Console.WriteLine("Selected user does not exist");
selectedUser = 0;
prompt = "# ";
}
else
{
if (args.Length == 3)
{
if (int.TryParse(args[1], out int itemId) && int.TryParse(args[2], out int amount))
{
ItemData? item = user.Items.FirstOrDefault(i => i.ItemType == itemId);
if (item == null)
{
user.Items.Add(new ItemData
{
Isn = user.GenerateUniqueItemId(),
ItemType = itemId,
Level = 1,
Exp = 1,
Count = amount
});
}
else
{
item.Count += amount;
if (item.Count < 0)
{
item.Count = 0;
}
}
Console.WriteLine($"Added {amount} of item {itemId} to user {user.Username}");
JsonDb.Save();
}
else
{
Console.WriteLine("Invalid item ID or amount");
}
}
else
{
Console.WriteLine("Invalid argument length, must be 2");
}
}
}
}
else if (input.StartsWith("AddCharacter"))
{
if (selectedUser == 0)
{
Console.WriteLine("No user selected");
}
else
{
var user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == selectedUser);
if (user == null)
{
Console.WriteLine("Selected user does not exist");
selectedUser = 0;
prompt = "# ";
}
else
{
if (args.Length == 2)
{
if (int.TryParse(args[1], out int characterId))
{
if (!user.HasCharacter(characterId))
{
user.Characters.Add(new Database.Character()
{
CostumeId = 0,
Csn = user.GenerateUniqueCharacterId(),
Grade = 0,
Level = 1,
Skill1Lvl = 1,
Skill2Lvl = 1,
Tid = characterId,
UltimateLevel = 1
});
Console.WriteLine($"Added character {characterId} to user {user.Username}");
JsonDb.Save();
}
else
{
Console.WriteLine($"User {user.Username} already has character {characterId}");
}
}
else
{
Console.WriteLine("Invalid character ID");
}
}
else
{
Console.WriteLine("Invalid argument length, must be 1");
}
}
}
}
else if (input == "exit")
{
Environment.Exit(0);

View File

@@ -0,0 +1,32 @@
using EpinelPS.StaticInfo;
namespace EpinelPS.Utils
{
public class FormulaUtils
{
public static int CalculateCP(Database.User user, long csn)
{
var character = user.Characters.FirstOrDefault(c => c.Csn == csn);
var charRecord = GameData.Instance.characterTable.Values.FirstOrDefault(c => c.id == character.Tid);
var statRecord = GameData.Instance.characterStatTable.Values.FirstOrDefault(s => charRecord.stat_enhance_id == s.group + (character.Level - 1));
float coreMult = 1f + character.Grade * 0.02f;
float hp = statRecord.level_hp * coreMult;
float atk = statRecord.level_attack * coreMult;
float def = statRecord.level_defence * coreMult;
float critRate = charRecord.critical_ratio;
float critDamage = charRecord.critical_damage;
float skill1Level = character.Skill1Lvl;
float skill2Level = character.Skill2Lvl;
float ultSkillLevel = character.UltimateLevel;
float critResult = 1 + ((critRate / 10000f) * (critDamage / 10000f - 1));
float effHealthResult = (def * 100) + hp;
float skillResult = (skill1Level * 0.01f) + (skill2Level * 0.01f) + (ultSkillLevel * 0.02f);
float bondResult = 0f; // TODO
float equipResult = 0f; // TOD
float overloadResult = 0; // TODO
float finalResult = (((critResult * atk * 18) + (effHealthResult * 0.7f)) * (1.3f + skillResult) + bondResult + equipResult + overloadResult) / 100f;
return (int)Math.Round(finalResult);
}
}
}

View File

@@ -45,7 +45,7 @@ To skip stages, a basic command line interface is implemented.
- [X] Outpost Rewards
- [ ] Admin Panel
- [ ] Simulation Room
- [ ] Skill level up
- [X] Skill level up
- [ ] Outpost jukebox
- [ ] Event system
- [ ] Download all game assets ahead of time
@@ -57,7 +57,6 @@ To skip stages, a basic command line interface is implemented.
## What is not working:
- Events
- Skill upgrade
- Mission reward, daily/weekly missions
- Side quests
- Lots of things in the outpost