397 lines
No EOL
17 KiB
C#
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;
|
|
}
|
|
}
|
|
} |