server-master/srcs/_plugins/Plugin.ResourceLoader/Loaders/SkillResourceFileLoader.cs

397 lines
No EOL
17 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Npgsql;
using PhoenixLib.Logging;
using WingsAPI.Data.GameData;
using WingsEmu.DTOs.BCards;
using WingsEmu.DTOs.Skills;
using WingsEmu.Game._enum;
using WingsEmu.Packets.Enums.Battle;
namespace Plugin.ResourceLoader.Loaders
{
public class SkillResourceFileLoader : IResourceLoader<SkillDTO>
{
private readonly ResourceLoadingConfiguration _config;
private readonly ILogger<SkillResourceFileLoader> _logger;
private readonly List<SkillDTO> _skills = new();
public SkillResourceFileLoader(ResourceLoadingConfiguration config, ILogger<SkillResourceFileLoader> logger)
{
_config = config;
_logger = logger;
}
public async Task<IReadOnlyList<SkillDTO>> LoadAsync()
{
if (_skills.Any())
{
return _skills;
}
bool dbFirst = string.Equals(Environment.GetEnvironmentVariable("DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase)
|| string.Equals(Environment.GetEnvironmentVariable("RESOURCE_DB_FIRST"), "true", StringComparison.OrdinalIgnoreCase);
bool strictDbOnly = string.Equals(Environment.GetEnvironmentVariable("STRICT_DB_ONLY"), "true", StringComparison.OrdinalIgnoreCase);
if (dbFirst)
{
int loadedFromDb = LoadSkillsFromDatabase();
if (loadedFromDb > 0)
{
Log.Info($"[RESOURCE_LOADER] {loadedFromDb} Skills loaded from database");
return _skills;
}
if (strictDbOnly)
{
throw new InvalidOperationException("DB_FIRST/STRICT_DB_ONLY enabled but no skills were loaded from database.");
}
}
string filePath = Path.Combine(_config.GameDataPath, "Skill.dat");
if (!File.Exists(filePath))
{
throw new FileNotFoundException($"{filePath} should be present");
}
var skill = new SkillDTO();
int counter = 0;
using var skillIdStream = new StreamReader(filePath, Encoding.GetEncoding(1252));
string line;
while ((line = await skillIdStream.ReadLineAsync()) != null)
{
string[] currentLine = line.Split('\t');
switch (currentLine.Length)
{
case > 2 when currentLine[1] == "VNUM":
skill = new SkillDTO
{
Id = short.Parse(currentLine[2])
};
break;
case > 2 when currentLine[1] == "NAME":
skill.Name = currentLine[2];
break;
case > 2 when currentLine[1] == "TYPE":
skill.SkillType = (SkillType)byte.Parse(currentLine[2]);
skill.CastId = short.Parse(currentLine[3]);
skill.Class = byte.Parse(currentLine[4]);
skill.AttackType = (AttackType)byte.Parse(currentLine[5]);
skill.IsUsingSecondWeapon = currentLine[6] == "1";
skill.Element = byte.Parse(currentLine[7]);
break;
case > 3 when currentLine[1] == "COST":
skill.CPCost = currentLine[2] == "-1" ? (byte)0 : byte.Parse(currentLine[2]);
skill.Price = int.Parse(currentLine[3]);
skill.SpecialCost = int.Parse(currentLine[4]);
break;
case > 2 when currentLine[1] == "LEVEL":
FillLevelInformation(skill, currentLine, _skills);
break;
case > 2 when currentLine[1] == "EFFECT":
skill.CtEffect = short.Parse(currentLine[3]);
skill.CtAnimation = short.Parse(currentLine[4]);
skill.SuEffect = short.Parse(currentLine[5]);
skill.SuAnimation = short.Parse(currentLine[6]);
break;
case > 2 when currentLine[1] == "TARGET":
skill.TargetType = (TargetType)byte.Parse(currentLine[2]);
skill.HitType = (TargetHitType)byte.Parse(currentLine[3]);
skill.Range = byte.Parse(currentLine[4]);
skill.AoERange = short.Parse(currentLine[5]);
skill.TargetAffectedEntities = (TargetAffectedEntities)byte.Parse(currentLine[6]);
break;
case > 2 when currentLine[1] == "DATA":
skill.UpgradeSkill = short.Parse(currentLine[2]);
skill.UpgradeType = short.Parse(currentLine[3]);
skill.CastTime = short.Parse(currentLine[6]);
skill.Cooldown = short.Parse(currentLine[7]);
skill.MpCost = short.Parse(currentLine[10]);
skill.DashSpeed = short.Parse(currentLine[11]);
skill.ItemVNum = short.Parse(currentLine[12]);
break;
case > 2 when currentLine[1] == "BASIC":
{
byte type = (byte)int.Parse(currentLine[3]);
if (type is 0 or 255)
{
continue;
}
int first = int.Parse(currentLine[5]);
int second = int.Parse(currentLine[6]);
int firstModulo = first % 4;
firstModulo = firstModulo switch
{
-1 => 1,
-2 => 2,
-3 => 1,
_ => firstModulo
};
int secondModulo = second % 4;
secondModulo = secondModulo switch
{
-1 => 1,
-2 => 2,
-3 => 1,
_ => secondModulo
};
var bcard = new BCardDTO
{
SkillVNum = skill.Id,
Type = type,
SubType = (byte)((int.Parse(currentLine[4]) + 1) * 10 + 1 + (first < 0 ? 1 : 0)),
FirstDataScalingType = (BCardScalingType)firstModulo,
SecondDataScalingType = (BCardScalingType)secondModulo,
FirstData = (int)Math.Abs(Math.Floor(first / 4.0)),
SecondData = (int)Math.Abs(Math.Floor(second / 4.0)),
CastType = byte.Parse(currentLine[7])
};
skill.BCards.Add(bcard);
break;
}
case > 2 when currentLine[1] == "FCOMBO":
{
if (int.Parse(currentLine[2]) == 0)
{
continue;
}
for (int i = 3; i < currentLine.Length - 4; i += 3)
{
var comb = new ComboDTO
{
SkillVNum = skill.Id,
Hit = short.Parse(currentLine[i]),
Animation = short.Parse(currentLine[i + 1]),
Effect = short.Parse(currentLine[i + 2])
};
if (comb.Hit == 0 && comb.Animation == 0 && comb.Effect == 0)
{
continue;
}
/*
* Idk if it's preferable to load them all instead of one by one (idk if there is lot of combo skills)
*/
skill.Combos.Add(comb);
}
break;
}
case > 2 when currentLine[1] == "CELL":
// investigate
break;
case > 1 when currentLine[1] == "Z_DESC":
_skills.Add(skill);
counter++;
break;
}
}
Log.Info($"[RESOURCE_LOADER] {counter.ToString()} Skills loaded");
return _skills;
}
private int LoadSkillsFromDatabase()
{
try
{
string host = Environment.GetEnvironmentVariable("DATABASE_IP")
?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_IP")
?? "127.0.0.1";
string port = Environment.GetEnvironmentVariable("DATABASE_PORT")
?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_PORT")
?? "5432";
string db = Environment.GetEnvironmentVariable("DATABASE_NAME")
?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_NAME")
?? "game";
string user = Environment.GetEnvironmentVariable("DATABASE_USER")
?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_USER")
?? "postgres";
string pass = Environment.GetEnvironmentVariable("DATABASE_PASSWORD")
?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_PASSWORD")
?? "postgres";
using var conn = new NpgsqlConnection($"Host={host};Port={port};Database={db};Username={user};Password={pass}");
conn.Open();
using var cmd = new NpgsqlCommand(@"SELECT id, name, class, cast_id, cast_time, cooldown, mp_cost, cp_cost, skill_type, attack_type, hit_type, target_type, range, aoe_range, level_minimum, element
FROM skills ORDER BY id;", conn);
using var reader = cmd.ExecuteReader();
while (reader.Read())
{
var skill = new SkillDTO
{
Id = reader.GetInt32(0),
Name = reader.IsDBNull(1) ? string.Empty : reader.GetString(1),
Class = Convert.ToByte(reader.GetInt32(2)),
CastId = Convert.ToInt16(reader.GetInt32(3)),
CastTime = Convert.ToInt16(reader.GetInt32(4)),
Cooldown = Convert.ToInt16(reader.GetInt32(5)),
MpCost = Convert.ToInt16(reader.GetInt32(6)),
CPCost = Convert.ToByte(reader.GetInt32(7)),
SkillType = (SkillType)reader.GetInt32(8),
AttackType = (AttackType)reader.GetInt32(9),
HitType = (TargetHitType)reader.GetInt32(10),
TargetType = (TargetType)reader.GetInt32(11),
Range = Convert.ToByte(reader.GetInt32(12)),
AoERange = Convert.ToInt16(reader.GetInt32(13)),
LevelMinimum = Convert.ToByte(reader.GetInt32(14)),
Element = Convert.ToByte(reader.GetInt32(15))
};
_skills.Add(skill);
}
return _skills.Count;
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Could not load skills from database, falling back to file parser");
return 0;
}
}
private static void FillLevelInformation(SkillDTO skill, IReadOnlyList<string> currentLine, IReadOnlyCollection<SkillDTO> skills)
{
skill.LevelMinimum = currentLine[2] != "-1" ? byte.Parse(currentLine[2]) : (byte)0;
if (skill.Class > 31)
{
SkillDTO firstSkill = skills.FirstOrDefault(s => s.Class == skill.Class);
if (firstSkill == null || skill.Id <= firstSkill.Id + 10)
{
switch (skill.Class)
{
case 8:
switch (skills.Count(s => s.Class == skill.Class))
{
case 3:
skill.LevelMinimum = 20;
break;
case 2:
skill.LevelMinimum = 10;
break;
default:
skill.LevelMinimum = 0;
break;
}
break;
case 9:
switch (skills.Count(s => s.Class == skill.Class))
{
case 9:
skill.LevelMinimum = 20;
break;
case 8:
skill.LevelMinimum = 16;
break;
case 7:
skill.LevelMinimum = 12;
break;
case 6:
skill.LevelMinimum = 8;
break;
case 5:
skill.LevelMinimum = 4;
break;
default:
skill.LevelMinimum = 0;
break;
}
break;
case 16:
switch (skills.Count(s => s.Class == skill.Class))
{
case 6:
skill.LevelMinimum = 20;
break;
case 5:
skill.LevelMinimum = 15;
break;
case 4:
skill.LevelMinimum = 10;
break;
case 3:
skill.LevelMinimum = 5;
break;
case 2:
skill.LevelMinimum = 3;
break;
default:
skill.LevelMinimum = 0;
break;
}
break;
default:
switch (skills.Count(s => s.Class == skill.Class))
{
case 10:
skill.LevelMinimum = 20;
break;
case 9:
skill.LevelMinimum = 16;
break;
case 8:
skill.LevelMinimum = 12;
break;
case 7:
skill.LevelMinimum = 8;
break;
case 6:
skill.LevelMinimum = 4;
break;
default:
skill.LevelMinimum = 0;
break;
}
break;
}
}
}
skill.MinimumAdventurerLevel = currentLine[3] != "-1" ? byte.Parse(currentLine[3]) : (byte)0;
skill.MinimumSwordmanLevel = currentLine[4] != "-1" ? byte.Parse(currentLine[4]) : (byte)0;
skill.MinimumArcherLevel = currentLine[5] != "-1" ? byte.Parse(currentLine[5]) : (byte)0;
skill.MinimumMagicianLevel = currentLine[6] != "-1" ? byte.Parse(currentLine[6]) : (byte)0;
}
}
}