Парсинг PTS скиллов

HostMan

Выдающийся
Участник
Старожил I степени
Сообщения
226
Розыгрыши
0
Решения
1
Репутация
5
Реакции
79
Баллы
1 370
Всем привет.
Пытаюсь научить Java читать скиллы с ПТС сборок.
При парсинге столкнулся с тем, что не знаю как красивее реализовать разбор скилла из txt в L2Skill. Ранее работал с json и xml, поэтому вопрос десериализация строки заставляет жестко тупить.
Пример скилла:
skill_begin skill_name=[s_power_strike11] skill_id=3 level=1 operate_type=A1 magic_level=3 effect={{i_p_attack_over_hit;25;0}} operate_cond={{equip_weapon;{sword;blunt}}} is_magic=0 mp_consume2=10 cast_range=40 effective_range=400 skill_hit_time=1.08 skill_cool_time=0.72 skill_hit_cancel_time=0.5 reuse_delay=13 attribute=attr_none effect_point=-52 target_type=enemy affect_scope=single affect_limit={0;0} next_action=attack ride_state={@ride_none} skill_end
Параметры отделены табуляцией, поэтому первое что изначально пришло в голову - сплитить по \t, но этот метод далеко меня не завел.
В общем, с радостью выслушаю ваши идеи, советы, может кто сталкивался.
Заранее спасибо
 

Примерно так:
1) Для начала определяешь полную структуру файла. То есть из каких характеристик состоит каждый скилл.
2) Для каждой характеристики задаешь метод, который парсит значение. Например, [s_power_strike11] - считываем как строку или что-то другое, как душе угодно. Или enemy - парсим как значение enum`а.
3) Помимо сплита по \t (допустим String[] tabSplitted) еще сплитить потом каждый элемент по '=' (допустим String[] element). Ну и потом в зависимости от element[0] соответственно парсим значение из element[1] для каждого tabSplitted.

Возможно это проще сделать с помощью регулярных выражений, но я особо ими не пользовался, поэтому не знаю.
 
Последнее редактирование:
Примерно так:
1) Для начала определяешь полную структуру файла. То есть из каких характеристик состоит каждый скилл.
2) Для каждой характеристики задаешь метод, который парсит значение. Например, [s_power_strike11] - считываем как строку или что-то другое, как душе угодно. Или enemy - парсим как значение enum`а.
3) Помимо сплита по \t (допустим String[] tabSplitted) еще сплитить потом каждый элемент по '=' (допустим String[] element). Ну и потом в зависимости от element[0] соответственно парсим значение из element[1] для каждого tabSplitted.

Возможно это проще сделать с помощью регулярных выражений, но я особо ими не пользовался, поэтому не знаю.
Спасибо за ответ. По началу я таким же способом и хотел парсить:) потом понял, что скиллы имеют разное количество параметров: от 7 до 37. соответственно привязываться по номеру элемента уже нельзя. Здесь нужно как-то сопоставить имя параметра и переменную.
п.с. с регулярными та же фигня:)
 
Спасибо за ответ. По началу я таким же способом и хотел парсить:) потом понял, что скиллы имеют разное количество параметров: от 7 до 37. соответственно привязываться по номеру элемента уже нельзя. Здесь нужно как-то сопоставить имя параметра и переменную.
п.с. с регулярными та же фигня:)
Вот из-за того, что количество параметров периодически меняется, и нужно сплитить по =. А в структуре задаем обязательные и необязательные для считывания поля (с дефолтным значением). Например:
Код:
public class Skill {
   @Required
   private String _name;
   @Required
   private int _id;
   ...
   private RideState _rideState = RideState.NONE;
   ...
}
А потом проводим верификацию: если хотя бы одно из полей @Required имеет заданное по дефолту значение, тогда не добавляем скилл в список и выдаем предупреждение.
А считывание примерно так (синтаксис может быть не соблюден, ибо черкал в блокноте).
Код:
Map<String, Skill> (где идентификатор - skill_name) или Map<Integer, Skill> loadedSkills;

Files.lines(Paths.get(SKILLDATA_FILENAME)).forEach((el) -> {
   String[] splittedLine = el.split("\t");
   if (splittedLine == null || splittedLine.length() <= 2)
       throw new DataParseException(...);
   Skill newSkill = new Skill();
   for (int i = 1; i < el.length() - 1; i++) {
       if (splittedLine[i].trim().isEmpty())
           throw new DataParseException(...);
       String[] skillParameter = splittedLine[i].split("=");
       switch (skillParameter[0]) {
           case "skill_name":
               newSkill.parseName(skillParameter[1]) или newSkill.setId(SkillParser.parseName(skillParameter[1])), тут как удобней
               break;
           ...
           default:
               log.warn(...); // что-то, что не можем прочитать
       }
   }
   if (newSkill.isValid())
       loadedSkills.put(newSkill.getName() или newSkill.getId(), newSkill);
   else
       log.warn(...);
});
 
Впрочем, если нужно считывать все файлы, то оптимальней будет создать какой-то PtsDataReader, который парсит имена и значения параметров. Судя по строке из skilldata в топике, можно такие типы данных как:
- строка или параметр из *pch - [текст]
- число
- enum - A1, enemy, single, возможно и @ride_none, но не уверен, что значит этот @
- объект - {{i_p_attack_over_hit;25;0}}
- массив - ? (по идее он должен где-то всплыть и возможно будет что-то вроде [1;2;3]
ну и т.д.
 
  • Мне нравится
Реакции: kick
Впрочем, если нужно считывать все файлы, то оптимальней будет создать какой-то PtsDataReader, который парсит имена и значения параметров. Судя по строке из skilldata в топике, можно такие типы данных как:
- строка или параметр из *pch - [текст]
- число
- enum - A1, enemy, single, возможно и @ride_none, но не уверен, что значит этот @
- объект - {{i_p_attack_over_hit;25;0}}
- массив - ? (по идее он должен где-то всплыть и возможно будет что-то вроде [1;2;3]
ну и т.д.
Почва в виде вспомогательных классов уже подготовлена. Вопрос пока стоит лишь в том, как оптимальнее заполнить этими данными наш объект :)
 
Спасибо за ответ. По началу я таким же способом и хотел парсить:) потом понял, что скиллы имеют разное количество параметров: от 7 до 37. соответственно привязываться по номеру элемента уже нельзя. Здесь нужно как-то сопоставить имя параметра и переменную.
п.с. с регулярными та же фигня:)
Вернитесь к изначальному варианту, только вспомните про чудесный "startsWith", может быть более опытные программисты посчитаю и не правильным, но на практике это самый быстрый и простой способ, работает быстрей чем вариант у JTS(не знаю что в этих сорсах, сравнивалось много лет назад в паке который делал камелион).
db29c54a7c4df4cb8caefb5c09d3af06.png
 
Смотрю я на ваш код и аж стало интересно как вы npcdata будете парсить, где для некоторых токенов = нет :loltt0:
 
И да, говорю сразу: птс принимает не \t, а например 5 пробелов. В итоге ваши парсеры нахер сломаются ;)
 
Смотрю я на ваш код и аж стало интересно как вы npcdata будете парсить, где для некоторых токенов = нет :loltt0:
Думаю очевидно, что каждый парсер требует индивидуального подхода при совершенно любой его реализации, скилы парсятся таким образом, можно еще через Field парсить, нпсдат парсится в сто раз проще скилов...скилы и нпспос самые громоздкие...если бы я этот код разбил на три десятка нелепых классов, тогда вы наверно бы точно заценили и поставили класс/лайк/сердечко:)
И да, говорю сразу: птс принимает не \t, а например 5 пробелов. В итоге ваши парсеры нахер сломаются ;)
Повезло ПТС, особенно с учетом того, что он сто лет парсит эту датку, смею предположить что он просто find field по строке, но это сомнительное решение...

Защита от дурака, дело самого дурака, по этому кто поставил пробелы, это его проблем, по умолчанию датка имеет ИСКЛЮЧИТЕЛЬНО табуляции, вот на них оно и рассчитано, ну и думаю самое основное то, что сборка будет уже иметь в комплекте необходимые файлы, с необходимым форматом.
 

Программист - это автоматизация любого ручного процесса.
TO GLORY друзья!
 
Повезло ПТС, особенно с учетом того, что он сто лет парсит эту датку, смею предположить что он просто find field по строке, но это сомнительное решение...
Целых 0.0001 секунды при старте сервера мало, что дадут.
Насколько я помню, он просто идет по строке.
 
В общем, после нескольких суток танцев с бубном, когда вроде бы как все должно было заработать, выяснилось - что некоторые скиллы записаны без соблюдения шаблона. То есть, например 1298 (Масс Слоу) имеет параметр cast_range=500. только вот перед знаком "=" стоит \t. Соответственно мой ̶с̶у̶п̶е̶р̶_̶к̶л̶а̶с̶с̶н̶ы̶й̶_̶п̶а̶р̶с̶е̶р̶ ломается к е**ням. Как корейцы парсят это дерьмо?
 
да даже 30 секунд на старте сервера игрокам погоды не сделают.
ну, такое...если 30 секунд грузить скиллы, 10 минут нпс, 5 минут геодату, то старта сервера придется ждать час
 
Вернитесь к изначальному варианту, только вспомните про чудесный "startsWith", может быть более опытные программисты посчитаю и не правильным, но на практике это самый быстрый и простой способ, работает быстрей чем вариант у JTS(не знаю что в этих сорсах, сравнивалось много лет назад в паке который делал камелион).
db29c54a7c4df4cb8caefb5c09d3af06.png
Круто, быстрее зато каждый файл нести ахинею, и описывать каждое поле для каждого файла. Логика достойная. Но да ладно, судя по всему кто то гонится за быстрым стартом сервера, тогда вопрос почему все поля типа int, имеют примитив Integer.parseInt(val)? Ведь мы гонимся за оптимизацией, и быстрой загрузкой сервера, ведь большинство параметров значения типа "уровня" 0-127, есть конечно же и больше. Хм... Может стоит использовать valueOf? Ведь он умеет ложить значения -128 127 в кэш, не копируя каждый раз.
 
Запускаем весь сервер + нпц сервер за 10-30 секунд :pandaredlol:
 

Вложения

  • photo_2018-01-19_23-57-03.jpg
    photo_2018-01-19_23-57-03.jpg
    66 КБ · Просмотры: 219
Круто, быстрее зато каждый файл нести ахинею, и описывать каждое поле для каждого файла. Логика достойная. Но да ладно, судя по всему кто то гонится за быстрым стартом сервера, тогда вопрос почему все поля типа int, имеют примитив Integer.parseInt(val)? Ведь мы гонимся за оптимизацией, и быстрой загрузкой сервера, ведь большинство параметров значения типа "уровня" 0-127, есть конечно же и больше. Хм... Может стоит использовать valueOf? Ведь он умеет ложить значения -128 127 в кэш, не копируя каждый раз.
Ой, давай не будем про ахинею, хоть палец в жопу, хоть куда, нужно описывать каждый файл персонально, только в случае с решением камаэля, нужно еще сделать 100 классов, а потом с их помощью описывать "не ахинею"...Никто, ни за чем не гонится, это было просто к слову сказано...Конкретно на скрине, левел в int ибо в конечном итоге он всеравно будет преобразован в него, данные которые помещаются в byte/short и не переходят в int, сидят в byte/short...Использование кеша здесь мало того что беспощадно бестолковое, так еще и код писался 8 лет назад, когда я понятия не имел о многих вещах...
 
Последнее редактирование:
Назад
Сверху Снизу