09-13-2013, 05:58 PM
Думаю все в курсе, что в Lindvior ввели Beauty Shop (салон красоты), предназначенный для изменения внешнего вида персонажей (прически, цвета волос, лица).
Ну и собственно в данном руководстве я хочу более-менее подробно описать, как реализовать данное дело в сервере, в частности на сервере основанном на оверах.
Итак, поехали:
Первым делом создадим 3 новых поля в таблице characters, аналоги полей face, hairStyle и hairColor - назовем их к примеру newFace, newHairStyle и newHairColor. Объяснять как читать и писать в эти поля, а так же как добавить новые переменные и геттеры/сеттеры для них в класс Player я не буду - это самые основы знания Java, не знать такое просто грешно
Может сразу возникнуть вполне закономерный вопрос - а почему бы не использовать существующие поля для хранения информации о внешности? Ответ прост - в некоторых ситуациях нам потребуется знать и то, каким была внешность до посещения салона красоты, к примеру для функции отмены изменений в том же самом салоне, или же для фестиваля хаоса - новые прически на фестивале не отображаются (правда насчет этого информация неподтвержденная, но так написано в патчноутах).
Далее озаботимся хранилищем данных о допустимых прическах и т.д., а так же загрузкой этих данных при запуске сервера.
Хранить данные мы будем в xml-файле следующей структуры:
[SRC="xml"]
<?xml version="1.0" encoding="utf-8"?>
<!ELEMENT list (config|set)*>
<!ELEMENT config (#PCDATA)*>
<!ATTLIST config
coin_item_id CDATA #REQUIRED
def_reset_price CDATA #REQUIRED>
<!ELEMENT set (hair|face)*>
<!ATTLIST set
id CDATA #REQUIRED>
<!ELEMENT hair (color)*>
<!ATTLIST hair
id CDATA #REQUIRED
adena CDATA #REQUIRED
coins CDATA #REQUIRED
reset_price CDATA #IMPLIED>
<!ELEMENT color (#PCDATA)>
<!ATTLIST color
id CDATA #REQUIRED
adena CDATA #REQUIRED
coins CDATA #REQUIRED>
<!ELEMENT face (#PCDATA)>
<!ATTLIST face
id CDATA #REQUIRED
adena CDATA #REQUIRED
coins CDATA #REQUIRED
reset_price CDATA #IMPLIED>
[/SRC]
И для примера фрагмент xml с данными
[SRC="xml"]
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE list SYSTEM "beauty_shop.dtd">
<list>
<config coin_item_id="36308" def_reset_price="1000000" />
<!-- Human Male Fighter -->
<set id="0">
<!-- Прически -->
<hair id="10001" adena="15000000" coins="0" /> <!-- Настоящий мачо A -->
<hair id="10002" adena="15000000" coins="0" /> <!-- Прекрасный юноша A -->
<hair id="10003" adena="0" coins="4"> <!-- Пират -->
<color id="101" adena="3000000" coins="0" /> <!-- Рекомендация дизайнера -->
<color id="102" adena="0" coins="1" /> <!-- Светло-кобальтовое небо -->
<color id="103" adena="0" coins="1" /> <!-- Лиловый виноград -->
<color id="104" adena="0" coins="1" /> <!-- Розовый карандаш -->
<color id="105" adena="0" coins="1" /> <!-- Черная бездна -->
<color id="106" adena="0" coins="1" /> <!-- Изумрудное украшение -->
<color id="107" adena="0" coins="1" /> <!-- Желтая бабочка -->
<color id="108" adena="0" coins="1" /> <!-- Вечнозеленый -->
<color id="109" adena="0" coins="1" /> <!-- Волшебная метель -->
<color id="110" adena="0" coins="1" /> <!-- Молчание серого ветра -->
</hair>
<hair id="10004" adena="0" coins="4"> <!-- Настоящий смельчак -->
<color id="101" adena="3000000" coins="0" /> <!-- Рекомендация дизайнера -->
<color id="102" adena="0" coins="1" /> <!-- Светло-кобальтовое небо -->
<color id="103" adena="0" coins="1" /> <!-- Лиловый виноград -->
<color id="104" adena="0" coins="1" /> <!-- Розовый карандаш -->
<color id="105" adena="0" coins="1" /> <!-- Черная бездна -->
<color id="106" adena="0" coins="1" /> <!-- Изумрудное украшение -->
<color id="107" adena="0" coins="1" /> <!-- Желтая бабочка -->
<color id="108" adena="0" coins="1" /> <!-- Вечнозеленый -->
<color id="109" adena="0" coins="1" /> <!-- Волшебная метель -->
<color id="110" adena="0" coins="1" /> <!-- Молчание серого ветра -->
</hair>
<hair id="10005" adena="15000000" coins="0" /> <!-- Потому что она - моя женщина A -->
<hair id="10006" adena="15000000" coins="0" /> <!-- Честь джентльмена A -->
<hair id="10007" adena="0" coins="3" /> <!-- Настоящий мачо B -->
<hair id="10008" adena="0" coins="3" /> <!-- Прекрасный юноша B -->
<hair id="10009" adena="0" coins="3" /> <!-- Потому что она - моя женщина B -->
<hair id="10010" adena="0" coins="3" /> <!-- Честь джентльмена B -->
<!-- Лица -->
<face id="20001" adena="2000000" coins="0" reset_price="500000" /> <!-- Стиль A -->
<face id="20002" adena="2000000" coins="0" reset_price="500000" /> <!-- Стиль B -->
<face id="20003" adena="2000000" coins="0" reset_price="500000" /> <!-- Стиль C -->
<face id="20004" adena="0" coins="2" /> <!-- Стиль Орена -->
<face id="20005" adena="0" coins="2" /> <!-- Стиль Годдарда -->
</set>
<!-- Human Female Fighter -->
<set id="1">
<!-- Прически -->
<hair id="10001" adena="15000000" coins="0" /> <!-- Холодная городская девушка A -->
<hair id="10002" adena="15000000" coins="0" /> <!-- Грибочек A -->
<hair id="10003" adena="0" coins="4"> <!-- Пират -->
<color id="101" adena="3000000" coins="0" /> <!-- Рекомендация дизайнера -->
<color id="102" adena="0" coins="1" /> <!-- Светло-кобальтовое небо -->
<color id="103" adena="0" coins="1" /> <!-- Лиловый виноград -->
<color id="104" adena="0" coins="1" /> <!-- Розовый карандаш -->
<color id="105" adena="0" coins="1" /> <!-- Черная бездна -->
<color id="106" adena="0" coins="1" /> <!-- Изумрудное украшение -->
<color id="107" adena="0" coins="1" /> <!-- Желтая бабочка -->
<color id="108" adena="0" coins="1" /> <!-- Вечнозеленый -->
<color id="109" adena="0" coins="1" /> <!-- Волшебная метель -->
<color id="110" adena="0" coins="1" /> <!-- Молчание серого ветра -->
</hair>
<hair id="10004" adena="0" coins="4"> <!-- Конский хвост -->
<color id="101" adena="3000000" coins="0" /> <!-- Рекомендация дизайнера -->
<color id="102" adena="0" coins="1" /> <!-- Светло-кобальтовое небо -->
<color id="103" adena="0" coins="1" /> <!-- Лиловый виноград -->
<color id="104" adena="0" coins="1" /> <!-- Розовый карандаш -->
<color id="105" adena="0" coins="1" /> <!-- Черная бездна -->
<color id="106" adena="0" coins="1" /> <!-- Изумрудное украшение -->
<color id="107" adena="0" coins="1" /> <!-- Желтая бабочка -->
<color id="108" adena="0" coins="1" /> <!-- Вечнозеленый -->
<color id="109" adena="0" coins="1" /> <!-- Волшебная метель -->
<color id="110" adena="0" coins="1" /> <!-- Молчание серого ветра -->
</hair>
<hair id="10005" adena="15000000" coins="0" /> <!-- Харизматичная амазонка A -->
<hair id="10006" adena="15000000" coins="0" /> <!-- Унесенная ветром A -->
<hair id="10007" adena="0" coins="3" /> <!-- Холодная городская девушка B -->
<hair id="10008" adena="0" coins="3" /> <!-- Грибочек B -->
<hair id="10009" adena="0" coins="3" /> <!-- Харизматичная амазонка B -->
<hair id="10010" adena="0" coins="3" /> <!-- Унесенная ветром B -->
<!-- Лица -->
<face id="20001" adena="2000000" coins="0" reset_price="500000" /> <!-- Стиль A -->
<face id="20002" adena="2000000" coins="0" reset_price="500000" /> <!-- Стиль B -->
<face id="20003" adena="2000000" coins="0" reset_price="500000" /> <!-- Стиль C -->
<face id="20004" adena="0" coins="2" /> <!-- Стиль Орена -->
<face id="20005" adena="0" coins="2" /> <!-- Стиль Годдарда -->
</set>
...
</list>
[/SRC]
Внимательные могут сразу же заметить избыточность данных в файле - для каждой расы/пола в общем-то данные одни и те же. Я это знаю, просто обычно предпочитаю реализовывать многое с учетом того, что может быть сейчас и все одинаково для любой расы/пола, но кто их корейцев знает - могут и изменить в будущем эту ситуацию. Так что в принципе если вам такая избыточность не нужна - это легко все дорабатывается до одного единственного набора данных для любой расы/пола. Опять же это делать вам самим, тем более что это легко - ломать не строить .
Далее добавляем в ядро сервера парсер этих данных и хранилище для них.
Парсер:
[SRC="java"]
package l2p.gameserver.data.xml.parser;
import l2p.commons.data.xml.AbstractFileParser;
import l2p.gameserver.Config;
import l2p.gameserver.data.xml.holder.BeautyShopHolder;
import l2p.gameserver.model.beautyshop.BeautyShopFace;
import l2p.gameserver.model.beautyshop.BeautyShopSet;
import l2p.gameserver.model.beautyshop.BeautyShopHairColor;
import l2p.gameserver.model.beautyshop.BeautyShopHairStyle;
import l2p.gameserver.utils.Util;
import org.dom4j.Element;
import gnu.trove.map.hash.TIntObjectHashMap;
import java.io.File;
import java.util.Iterator;
public final class BeautyShopParser extends AbstractFileParser<BeautyShopHolder>
{
private static final BeautyShopParser _instance = new BeautyShopParser();
public static BeautyShopParser getInstance()
{
return _instance;
}
private BeautyShopParser()
{
super(BeautyShopHolder.getInstance());
}
@Override
public File getXMLFile()
{
return new File(Config.DATAPACK_ROOT, "data/beauty_shop.xml");
}
@Override
public String getDTDFileName()
{
return "beauty_shop.dtd";
}
@Override
protected void readData(Element rootElement) throws Exception
{
long defResetPrice = 0L;
for (Iterator iterator = rootElement.elementIterator("config"); iterator.hasNext()
{
Element element = (Element) iterator.next();
Config.BS_COIN_ITEM_ID = Util.parseValue(element.attributeValue("coin_item_id"), 36308);
defResetPrice = Util.parseValue(element.attributeValue("def_reset_price"), 1000000L);
}
for (Iterator iterator = rootElement.elementIterator("set"); iterator.hasNext()
{
Element element = (Element) iterator.next();
int setId = Util.parseValue(element.attributeValue("id"), 0);
TIntObjectHashMap<BeautyShopHairStyle> hairStyles = new TIntObjectHashMap<BeautyShopHairStyle>();
TIntObjectHashMap<BeautyShopFace> faces = new TIntObjectHashMap<BeautyShopFace>();
for (Iterator<Element> subIterator = element.elementIterator("hair"); subIterator.hasNext()
{
Element subElement = subIterator.next();
int id = Util.parseValue(subElement.attributeValue("id"), 0);
long adena = Util.parseValue(subElement.attributeValue("adena"), 0L);
long coins = Util.parseValue(subElement.attributeValue("coins"), 0L);
long resetPrice = Util.parseValue(subElement.attributeValue("reset_price"), defResetPrice);
TIntObjectHashMap<BeautyShopHairColor> colors = new TIntObjectHashMap<BeautyShopHairColor>();
for (Iterator<Element> colorIterator = subElement.elementIterator("color"); colorIterator.hasNext()
{
Element colorElement = colorIterator.next();
int cId = Util.parseValue(colorElement.attributeValue("id"), 0);
long cAdena = Util.parseValue(colorElement.attributeValue("adena"), 0L);
long cCoins = Util.parseValue(colorElement.attributeValue("coins"), 0L);
colors.put(cId, new BeautyShopHairColor(cId, cAdena, cCoins));
}
if (colors.isEmpty())
colors.put(101, new BeautyShopHairColor(101, 0, 0));
hairStyles.put(id, new BeautyShopHairStyle(id, adena, coins, resetPrice, colors));
}
for (Iterator<Element> subIterator = element.elementIterator("face"); subIterator.hasNext()
{
Element subElement = subIterator.next();
int id = Util.parseValue(subElement.attributeValue("id"), 0);
long adena = Util.parseValue(subElement.attributeValue("adena"), 0L);
long coins = Util.parseValue(subElement.attributeValue("coins"), 0L);
long resetPrice = Util.parseValue(subElement.attributeValue("reset_price"), defResetPrice);
faces.put(id, new BeautyShopFace(id, adena, coins, resetPrice));
}
getHolder().addSet(new BeautyShopSet(setId, hairStyles, faces));
}
}
}
[/SRC]
Хранилище:
[SRC="java"]
package l2p.gameserver.data.xml.holder;
import gnu.trove.map.hash.TIntObjectHashMap;
import l2p.commons.data.xml.AbstractHolder;
import l2p.gameserver.model.Player;
import l2p.gameserver.model.base.Race;
import l2p.gameserver.model.base.Sex;
import l2p.gameserver.model.beautyshop.BeautyShopSet;
public final class BeautyShopHolder extends AbstractHolder
{
private static final BeautyShopHolder _instance = new BeautyShopHolder();
private static final TIntObjectHashMap<BeautyShopSet> _sets = new TIntObjectHashMap<BeautyShopSet>();
public static BeautyShopHolder getInstance()
{
return _instance;
}
public void addSet(BeautyShopSet set)
{
if (_sets.containsKey(set.getId()))
warn("Duplicate set declaration, set id - " + set.getId());
_sets.put(set.getId(), set);
}
public BeautyShopSet getSet(int id)
{
return _sets.get(id);
}
public BeautyShopSet getSet(Player player)
{
int id = -1;
if (player.getRace() == Race.HUMAN)
{
if (!player.getClassId().isMage())
id = player.getSex() == Sex.MALE ? 0 : 1;
else
id = player.getSex() == Sex.MALE ? 8 : 9;
}
else if (player.getRace() == Race.DARK_ELF)
id = player.getSex() == Sex.MALE ? 2 : 3;
else if (player.getRace() == Race.DWARF)
id = player.getSex() == Sex.MALE ? 4 : 5;
else if (player.getRace() == Race.ELF)
id = player.getSex() == Sex.MALE ? 6 : 7;
else if (player.getRace() == Race.ORC)
{
if (!player.getClassId().isMage())
id = player.getSex() == Sex.MALE ? 10 : 11;
else
id = player.getSex() == Sex.MALE ? 12 : 13;
}
else if (player.getRace() == Race.KAMAEL)
id = player.getSex() == Sex.MALE ? 14 : 15;
return getSet(id);
}
@Override
public int size()
{
return _sets.size();
}
@Override
public void clear()
{
_sets.clear();
}
@Override
public void log()
{
info(String.format("loaded %d beauty shop set(s) count.", size()));
}
}
[/SRC]
Структуры для хранения данных:
[SRC="java"]
package l2p.gameserver.model.beautyshop;
import gnu.trove.map.hash.TIntObjectHashMap;
public class BeautyShopSet
{
private final int _id;
private final TIntObjectHashMap<BeautyShopHairStyle> _hairStyles;
private final TIntObjectHashMap<BeautyShopFace> _faces;
public BeautyShopSet(int id, TIntObjectHashMap<BeautyShopHairStyle> hairStyles, TIntObjectHashMap<BeautyShopFace> faces)
{
_id = id;
_hairStyles = hairStyles;
_faces = faces;
}
public int getId()
{
return _id;
}
public TIntObjectHashMap<BeautyShopHairStyle> getHairStyles()
{
return _hairStyles;
}
public BeautyShopHairStyle getHairStyle(int id)
{
return _hairStyles.get(id);
}
public TIntObjectHashMap<BeautyShopFace> getFaces()
{
return _faces;
}
public BeautyShopFace getFace(int id)
{
return _faces.get(id);
}
}
package l2p.gameserver.model.beautyshop;
public class BeautyShopFace
{
private final int _id;
private final long _adena;
private final long _coins;
private final long _resetPrice;
public BeautyShopFace(int id, long adena, long coins, long resetPrice)
{
_id = id;
_adena = adena;
_coins = coins;
_resetPrice = resetPrice;
}
public int getId()
{
return _id;
}
public long getAdena()
{
return _adena;
}
public long getCoins()
{
return _coins;
}
public long getResetPrice()
{
return _resetPrice;
}
}
package l2p.gameserver.model.beautyshop;
import gnu.trove.map.hash.TIntObjectHashMap;
public class BeautyShopHairStyle
{
private final int _id;
private final long _adena;
private final long _coins;
private final long _resetPrice;
private final TIntObjectHashMap<BeautyShopHairColor> _hairColors;
public BeautyShopHairStyle(int id, long adena, long coins, long resetPrice, TIntObjectHashMap<BeautyShopHairColor> hairColors)
{
_id = id;
_adena = adena;
_coins = coins;
_resetPrice = resetPrice;
_hairColors = hairColors;
}
public int getId()
{
return _id;
}
public long getAdena()
{
return _adena;
}
public long getCoins()
{
return _coins;
}
public long getResetPrice()
{
return _resetPrice;
}
public TIntObjectHashMap<BeautyShopHairColor> getHairColors()
{
return _hairColors;
}
public BeautyShopHairColor getHairColor(int id)
{
return _hairColors.get(id);
}
}
package l2p.gameserver.model.beautyshop;
public class BeautyShopHairColor
{
private final int _id;
private final long _adena;
private final long _coins;
public BeautyShopHairColor(int id, long adena, long coins)
{
_id = id;
_adena = adena;
_coins = coins;
}
public int getId()
{
return _id;
}
public long getAdena()
{
return _adena;
}
public long getCoins()
{
return _coins;
}
}
[/SRC]
В парсере вы можете заметить для чтения значений используется некие методы c названием Util.parseValue( ... ) - приводить их код я не буду, т.к. и так понятно как и что они делают - можете просто написать свою реализацию их или же использовать уже что-то готовое (методы, выполняющие примерно те же функции, насколько помню вроде как были и в оригинальном овере).
Далее нам необходимо внести мелкие изменения в пакеты UserInfo, CharInfo и GMViewCharacterInfo, для корректного отображения новой внешности.
Изменения простейшие, типа таких:
[SRC="java"]
_hairStyle = player.getNewHairStyle() > 0 ? player.getNewHairStyle() : player.getHairStyle();
_hairColor = player.getNewHairColor() > 0 ? player.getNewHairColor() : player.getHairColor();
_face = player.getNewFace() > 0 ? player.getNewFace() : player.getFace();
[/SRC]
Ну и напоследок самое главное - все пакеты, связанные с салоном красоты.
Объяснять их содержимое думаю нет смысла - они достаточно просты и по их коду и так все понятно.
Но сначала не забудем добавить в GamePacketHandler обработку необходимых клиентских пакетов:
[SRC="java"]
case 0xD7:
msg = new RequestShowBeautyList();
break;
case 0xD8:
msg = new RequestRegistBeauty();
break;
case 0xDA:
msg = new RequestShowResetShopList();
break;
case 0xEE:
msg = new NotifyExitBeautyShop();
break;
[/SRC]
Ну и непосредственно сами пакеты.
Клиентские:
[SRC="java"]
package l2p.gameserver.network.clientpackets;
import l2p.gameserver.model.Player;
import l2p.gameserver.network.serverpackets.ExResponseBeautyList;
public class RequestShowBeautyList extends L2GameClientPacket
{
private int _type;
@Override
protected void readImpl()
{
_type = readD();
}
@Override
protected void runImpl()
{
Player player = getClient().getActiveChar();
if (player == null)
return;
player.sendPacket(new ExResponseBeautyList(player, _type));
}
}
package l2p.gameserver.network.clientpackets;
import l2p.gameserver.Config;
import l2p.gameserver.data.xml.holder.BeautyShopHolder;
import l2p.gameserver.model.Player;
import l2p.gameserver.model.beautyshop.BeautyShopSet;
import l2p.gameserver.network.serverpackets.ExResponseBeautyList;
import l2p.gameserver.network.serverpackets.ExResponseBeautyRegistReset;
public class RequestRegistBeauty extends L2GameClientPacket
{
private int _hairStyle, _face, _hairColor;
@Override
protected void readImpl()
{
_hairStyle = readD();
_face = readD();
_hairColor = readD();
}
@Override
protected void runImpl()
{
Player player = getClient().getActiveChar();
if (player == null)
return;
long reqAdena = 0;
long reqCoins = 0;
boolean change = false;
BeautyShopSet set = BeautyShopHolder.getInstance().getSet(player);
if (set == null)
{
player.sendPacket(new ExResponseBeautyRegistReset(player, 0, 0), new ExResponseBeautyList(player, 0));
return;
}
if (_hairStyle > 0 && _hairColor > 0 && (_hairStyle != player.getNewHairStyle() || _hairColor != player.getNewHairColor()))
{
if (set.getHairStyle(_hairStyle) == null || set.getHairStyle(_hairStyle).getHairColor(_hairColor) == null)
{
player.sendPacket(new ExResponseBeautyRegistReset(player, 0, 0), new ExResponseBeautyList(player, 0));
return;
}
if (_hairStyle != player.getNewHairStyle())
{
reqAdena += set.getHairStyle(_hairStyle).getAdena() + set.getHairStyle(_hairStyle).getHairColor(_hairColor).getAdena();
reqCoins += set.getHairStyle(_hairStyle).getCoins() + set.getHairStyle(_hairStyle).getHairColor(_hairColor).getCoins();
}
else
{
reqAdena += set.getHairStyle(_hairStyle).getHairColor(_hairColor).getAdena();
reqCoins += set.getHairStyle(_hairStyle).getHairColor(_hairColor).getCoins();
}
change = true;
}
if (_face > 0 && _face != player.getNewFace())
{
if (set.getFace(_face) == null)
{
player.sendPacket(new ExResponseBeautyRegistReset(player, 0, 0), new ExResponseBeautyList(player, 0));
return;
}
reqAdena += set.getFace(_face).getAdena();
reqCoins += set.getFace(_face).getCoins();
change = true;
}
if (!change || player.getItemCount(57) < reqAdena || player.getItemCount(Config.BS_COIN_ITEM_ID) < reqCoins)
{
player.sendPacket(new ExResponseBeautyRegistReset(player, 0, 0), new ExResponseBeautyList(player, 0));
return;
}
if (reqAdena > 0)
player.getInventory().destroyItemByItemId(57, reqAdena);
if (reqCoins > 0)
player.getInventory().destroyItemByItemId(Config.BS_COIN_ITEM_ID, reqCoins);
if (_hairStyle > 0)
{
player.setNewHairStyle(_hairStyle);
player.setNewHairColor(_hairColor);
}
if (_face > 0)
player.setNewFace(_face);
player.sendPacket(new ExResponseBeautyRegistReset(player, 0, 1), new ExResponseBeautyList(player, 0));
}
}
package l2p.gameserver.network.clientpackets;
import l2p.gameserver.data.xml.holder.BeautyShopHolder;
import l2p.gameserver.model.Player;
import l2p.gameserver.model.beautyshop.BeautyShopSet;
import l2p.gameserver.network.serverpackets.ExResponseBeautyRegistReset;
public class RequestShowResetShopList extends L2GameClientPacket
{
private int _hairStyle, _face, _hairColor;
@Override
protected void readImpl()
{
_hairStyle = readD();
_face = readD();
_hairColor = readD();
}
@Override
protected void runImpl()
{
Player player = getClient().getActiveChar();
if (player == null)
return;
long reqAdena = 0;
boolean reset = false;
BeautyShopSet set = BeautyShopHolder.getInstance().getSet(player);
if (set == null)
{
player.sendPacket(new ExResponseBeautyRegistReset(player, 1, 0));
return;
}
if (_hairStyle > 0 && _hairColor > 0)
{
if (set.getHairStyle(_hairStyle) == null)
{
player.sendPacket(new ExResponseBeautyRegistReset(player, 1, 0));
return;
}
reqAdena += set.getHairStyle(_hairStyle).getResetPrice();
reset = true;
}
if (_face > 0)
{
if (set.getFace(_face) == null)
{
player.sendPacket(new ExResponseBeautyRegistReset(player, 1, 0));
return;
}
reqAdena += set.getFace(_face).getResetPrice();
reset = true;
}
if (!reset || player.getAdena() < reqAdena)
{
player.sendPacket(new ExResponseBeautyRegistReset(player, 1, 0));
return;
}
player.getInventory().destroyItemByItemId(57, reqAdena);
if (_hairStyle > 0)
{
player.setNewHairStyle(0);
player.setNewHairColor(0);
}
if (_face > 0)
player.setNewFace(0);
player.sendPacket(new ExResponseBeautyRegistReset(player, 1, 1));
}
}
package l2p.gameserver.network.clientpackets;
import l2p.gameserver.model.Player;
public class NotifyExitBeautyShop extends L2GameClientPacket
{
@Override
protected void readImpl()
{}
@Override
protected void runImpl()
{
Player player = getClient().getActiveChar();
if (player == null)
return;
player.broadcastCharInfo();
}
}
[/SRC]
И серверные пакеты:
[SRC="java"]
package l2p.gameserver.network.serverpackets;
import l2p.gameserver.network.serverpackets.components.ServerPacket;
public class ExShowBeautyMenu extends L2GameServerPacket
{
public int _type; // 0 - выбор нового внешнего вида, 1 - возврат старого
public ExShowBeautyMenu(int type)
{
_type = type;
}
@Override
protected void writeImpl()
{
writeEx(ServerPacket.ExShowBeautyMenu);
writeD(_type);
}
}
package l2p.gameserver.network.serverpackets;
import l2p.gameserver.Config;
import l2p.gameserver.data.xml.holder.BeautyShopHolder;
import l2p.gameserver.model.Player;
import l2p.gameserver.model.beautyshop.BeautyShopFace;
import l2p.gameserver.model.beautyshop.BeautyShopHairStyle;
import l2p.gameserver.model.beautyshop.BeautyShopSet;
import l2p.gameserver.network.serverpackets.components.ServerPacket;
public class ExResponseBeautyList extends L2GameServerPacket
{
private int _type;
private long _adena;
private long _coins;
private int[][] _data;
private boolean _send = false;
public ExResponseBeautyList(Player player, int type)
{
BeautyShopSet set = BeautyShopHolder.getInstance().getSet(player);
if (set == null)
return;
int i = 0;
if (type == 0)
{
_data = new int[set.getHairStyles().size()][2];
for (BeautyShopHairStyle style : set.getHairStyles().valueCollection())
{
_data[i][0] = style.getId();
_data[i][1] = 0;
i++;
}
}
else
{
_data = new int[set.getFaces().size()][2];
for (BeautyShopFace face : set.getFaces().valueCollection())
{
_data[i][0] = face.getId();
_data[i][1] = 0;
i++;
}
}
_type = type;
_adena = player.getAdena();
_coins = player.getItemCount(Config.BS_COIN_ITEM_ID);
_send = true;
}
@Override
protected void writeImpl()
{
if (!_send)
return;
writeEx(ServerPacket.ExResponseBeautyList);
writeQ(_adena);
writeQ(_coins);
writeD(_type);
writeD(_data.length);
for (int[] element : _data)
{
writeD(element[0]);
writeD(element[1]);
}
}
}
package l2p.gameserver.network.serverpackets;
import l2p.gameserver.Config;
import l2p.gameserver.model.Player;
import l2p.gameserver.network.serverpackets.components.ServerPacket;
public class ExResponseBeautyRegistReset extends L2GameServerPacket
{
private int _type;
private int _result;
private int _hairStyle;
private int _hairColor;
private int _face;
private long _adena;
private long _coins;
public ExResponseBeautyRegistReset(Player player, int type, int result)
{
_type = type;
_result = result;
_hairStyle = player.getNewHairStyle() > 0 ? player.getNewHairStyle() : player.getHairStyle();
_hairColor = player.getNewHairColor() > 0 ? player.getNewHairColor() : player.getHairColor();
_face = player.getNewFace() > 0 ? player.getNewFace() : player.getFace();
_adena = player.getAdena();
_coins = player.getItemCount(Config.BS_COIN_ITEM_ID);
}
@Override
protected void writeImpl()
{
writeEx(ServerPacket.ExResponseBeautyRegistReset);
writeQ(_adena);
writeQ(_coins);
writeD(_type);
writeD(_result);
writeD(_hairStyle);
writeD(_face);
writeD(_hairColor);
}
}
package l2p.gameserver.network.serverpackets;
import l2p.gameserver.Config;
import l2p.gameserver.model.Player;
import l2p.gameserver.network.serverpackets.components.ServerPacket;
public class ExResponseResetList extends L2GameServerPacket
{
private int _hairStyle;
private int _hairColor;
private int _face;
private long _adena;
private long _coins;
public ExResponseResetList(Player player)
{
_hairStyle = player.getHairStyle();
_hairColor = player.getHairColor();
_face = player.getFace();
_adena = player.getAdena();
_coins = player.getItemCount(Config.BS_COIN_ITEM_ID);
}
@Override
protected void writeImpl()
{
writeEx(ServerPacket.ExResponseResetList);
writeQ(_adena);
writeQ(_coins);
writeD(_hairStyle);
writeD(_face);
writeD(_hairColor);
}
}
[/SRC]
И опкоды этих пакетов:
[SRC="java"]
public static final int ExShowBeautyMenu = 0x13E;
public static final int ExResponseBeautyList = 0x13F;
public static final int ExResponseBeautyRegistReset = 0x140;
public static final int ExResponseResetList = 0x141;
[/SRC]
Ну вот собственно и все. Если я что и не описал или не пояснил - это в основном мелочи, до которых вы сами сможете без проблем додуматься.
Ну и собственно в данном руководстве я хочу более-менее подробно описать, как реализовать данное дело в сервере, в частности на сервере основанном на оверах.
Итак, поехали:
Первым делом создадим 3 новых поля в таблице characters, аналоги полей face, hairStyle и hairColor - назовем их к примеру newFace, newHairStyle и newHairColor. Объяснять как читать и писать в эти поля, а так же как добавить новые переменные и геттеры/сеттеры для них в класс Player я не буду - это самые основы знания Java, не знать такое просто грешно
Может сразу возникнуть вполне закономерный вопрос - а почему бы не использовать существующие поля для хранения информации о внешности? Ответ прост - в некоторых ситуациях нам потребуется знать и то, каким была внешность до посещения салона красоты, к примеру для функции отмены изменений в том же самом салоне, или же для фестиваля хаоса - новые прически на фестивале не отображаются (правда насчет этого информация неподтвержденная, но так написано в патчноутах).
Далее озаботимся хранилищем данных о допустимых прическах и т.д., а так же загрузкой этих данных при запуске сервера.
Хранить данные мы будем в xml-файле следующей структуры:
[SRC="xml"]
<?xml version="1.0" encoding="utf-8"?>
<!ELEMENT list (config|set)*>
<!ELEMENT config (#PCDATA)*>
<!ATTLIST config
coin_item_id CDATA #REQUIRED
def_reset_price CDATA #REQUIRED>
<!ELEMENT set (hair|face)*>
<!ATTLIST set
id CDATA #REQUIRED>
<!ELEMENT hair (color)*>
<!ATTLIST hair
id CDATA #REQUIRED
adena CDATA #REQUIRED
coins CDATA #REQUIRED
reset_price CDATA #IMPLIED>
<!ELEMENT color (#PCDATA)>
<!ATTLIST color
id CDATA #REQUIRED
adena CDATA #REQUIRED
coins CDATA #REQUIRED>
<!ELEMENT face (#PCDATA)>
<!ATTLIST face
id CDATA #REQUIRED
adena CDATA #REQUIRED
coins CDATA #REQUIRED
reset_price CDATA #IMPLIED>
[/SRC]
И для примера фрагмент xml с данными
[SRC="xml"]
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE list SYSTEM "beauty_shop.dtd">
<list>
<config coin_item_id="36308" def_reset_price="1000000" />
<!-- Human Male Fighter -->
<set id="0">
<!-- Прически -->
<hair id="10001" adena="15000000" coins="0" /> <!-- Настоящий мачо A -->
<hair id="10002" adena="15000000" coins="0" /> <!-- Прекрасный юноша A -->
<hair id="10003" adena="0" coins="4"> <!-- Пират -->
<color id="101" adena="3000000" coins="0" /> <!-- Рекомендация дизайнера -->
<color id="102" adena="0" coins="1" /> <!-- Светло-кобальтовое небо -->
<color id="103" adena="0" coins="1" /> <!-- Лиловый виноград -->
<color id="104" adena="0" coins="1" /> <!-- Розовый карандаш -->
<color id="105" adena="0" coins="1" /> <!-- Черная бездна -->
<color id="106" adena="0" coins="1" /> <!-- Изумрудное украшение -->
<color id="107" adena="0" coins="1" /> <!-- Желтая бабочка -->
<color id="108" adena="0" coins="1" /> <!-- Вечнозеленый -->
<color id="109" adena="0" coins="1" /> <!-- Волшебная метель -->
<color id="110" adena="0" coins="1" /> <!-- Молчание серого ветра -->
</hair>
<hair id="10004" adena="0" coins="4"> <!-- Настоящий смельчак -->
<color id="101" adena="3000000" coins="0" /> <!-- Рекомендация дизайнера -->
<color id="102" adena="0" coins="1" /> <!-- Светло-кобальтовое небо -->
<color id="103" adena="0" coins="1" /> <!-- Лиловый виноград -->
<color id="104" adena="0" coins="1" /> <!-- Розовый карандаш -->
<color id="105" adena="0" coins="1" /> <!-- Черная бездна -->
<color id="106" adena="0" coins="1" /> <!-- Изумрудное украшение -->
<color id="107" adena="0" coins="1" /> <!-- Желтая бабочка -->
<color id="108" adena="0" coins="1" /> <!-- Вечнозеленый -->
<color id="109" adena="0" coins="1" /> <!-- Волшебная метель -->
<color id="110" adena="0" coins="1" /> <!-- Молчание серого ветра -->
</hair>
<hair id="10005" adena="15000000" coins="0" /> <!-- Потому что она - моя женщина A -->
<hair id="10006" adena="15000000" coins="0" /> <!-- Честь джентльмена A -->
<hair id="10007" adena="0" coins="3" /> <!-- Настоящий мачо B -->
<hair id="10008" adena="0" coins="3" /> <!-- Прекрасный юноша B -->
<hair id="10009" adena="0" coins="3" /> <!-- Потому что она - моя женщина B -->
<hair id="10010" adena="0" coins="3" /> <!-- Честь джентльмена B -->
<!-- Лица -->
<face id="20001" adena="2000000" coins="0" reset_price="500000" /> <!-- Стиль A -->
<face id="20002" adena="2000000" coins="0" reset_price="500000" /> <!-- Стиль B -->
<face id="20003" adena="2000000" coins="0" reset_price="500000" /> <!-- Стиль C -->
<face id="20004" adena="0" coins="2" /> <!-- Стиль Орена -->
<face id="20005" adena="0" coins="2" /> <!-- Стиль Годдарда -->
</set>
<!-- Human Female Fighter -->
<set id="1">
<!-- Прически -->
<hair id="10001" adena="15000000" coins="0" /> <!-- Холодная городская девушка A -->
<hair id="10002" adena="15000000" coins="0" /> <!-- Грибочек A -->
<hair id="10003" adena="0" coins="4"> <!-- Пират -->
<color id="101" adena="3000000" coins="0" /> <!-- Рекомендация дизайнера -->
<color id="102" adena="0" coins="1" /> <!-- Светло-кобальтовое небо -->
<color id="103" adena="0" coins="1" /> <!-- Лиловый виноград -->
<color id="104" adena="0" coins="1" /> <!-- Розовый карандаш -->
<color id="105" adena="0" coins="1" /> <!-- Черная бездна -->
<color id="106" adena="0" coins="1" /> <!-- Изумрудное украшение -->
<color id="107" adena="0" coins="1" /> <!-- Желтая бабочка -->
<color id="108" adena="0" coins="1" /> <!-- Вечнозеленый -->
<color id="109" adena="0" coins="1" /> <!-- Волшебная метель -->
<color id="110" adena="0" coins="1" /> <!-- Молчание серого ветра -->
</hair>
<hair id="10004" adena="0" coins="4"> <!-- Конский хвост -->
<color id="101" adena="3000000" coins="0" /> <!-- Рекомендация дизайнера -->
<color id="102" adena="0" coins="1" /> <!-- Светло-кобальтовое небо -->
<color id="103" adena="0" coins="1" /> <!-- Лиловый виноград -->
<color id="104" adena="0" coins="1" /> <!-- Розовый карандаш -->
<color id="105" adena="0" coins="1" /> <!-- Черная бездна -->
<color id="106" adena="0" coins="1" /> <!-- Изумрудное украшение -->
<color id="107" adena="0" coins="1" /> <!-- Желтая бабочка -->
<color id="108" adena="0" coins="1" /> <!-- Вечнозеленый -->
<color id="109" adena="0" coins="1" /> <!-- Волшебная метель -->
<color id="110" adena="0" coins="1" /> <!-- Молчание серого ветра -->
</hair>
<hair id="10005" adena="15000000" coins="0" /> <!-- Харизматичная амазонка A -->
<hair id="10006" adena="15000000" coins="0" /> <!-- Унесенная ветром A -->
<hair id="10007" adena="0" coins="3" /> <!-- Холодная городская девушка B -->
<hair id="10008" adena="0" coins="3" /> <!-- Грибочек B -->
<hair id="10009" adena="0" coins="3" /> <!-- Харизматичная амазонка B -->
<hair id="10010" adena="0" coins="3" /> <!-- Унесенная ветром B -->
<!-- Лица -->
<face id="20001" adena="2000000" coins="0" reset_price="500000" /> <!-- Стиль A -->
<face id="20002" adena="2000000" coins="0" reset_price="500000" /> <!-- Стиль B -->
<face id="20003" adena="2000000" coins="0" reset_price="500000" /> <!-- Стиль C -->
<face id="20004" adena="0" coins="2" /> <!-- Стиль Орена -->
<face id="20005" adena="0" coins="2" /> <!-- Стиль Годдарда -->
</set>
...
</list>
[/SRC]
Внимательные могут сразу же заметить избыточность данных в файле - для каждой расы/пола в общем-то данные одни и те же. Я это знаю, просто обычно предпочитаю реализовывать многое с учетом того, что может быть сейчас и все одинаково для любой расы/пола, но кто их корейцев знает - могут и изменить в будущем эту ситуацию. Так что в принципе если вам такая избыточность не нужна - это легко все дорабатывается до одного единственного набора данных для любой расы/пола. Опять же это делать вам самим, тем более что это легко - ломать не строить .
Далее добавляем в ядро сервера парсер этих данных и хранилище для них.
Парсер:
[SRC="java"]
package l2p.gameserver.data.xml.parser;
import l2p.commons.data.xml.AbstractFileParser;
import l2p.gameserver.Config;
import l2p.gameserver.data.xml.holder.BeautyShopHolder;
import l2p.gameserver.model.beautyshop.BeautyShopFace;
import l2p.gameserver.model.beautyshop.BeautyShopSet;
import l2p.gameserver.model.beautyshop.BeautyShopHairColor;
import l2p.gameserver.model.beautyshop.BeautyShopHairStyle;
import l2p.gameserver.utils.Util;
import org.dom4j.Element;
import gnu.trove.map.hash.TIntObjectHashMap;
import java.io.File;
import java.util.Iterator;
public final class BeautyShopParser extends AbstractFileParser<BeautyShopHolder>
{
private static final BeautyShopParser _instance = new BeautyShopParser();
public static BeautyShopParser getInstance()
{
return _instance;
}
private BeautyShopParser()
{
super(BeautyShopHolder.getInstance());
}
@Override
public File getXMLFile()
{
return new File(Config.DATAPACK_ROOT, "data/beauty_shop.xml");
}
@Override
public String getDTDFileName()
{
return "beauty_shop.dtd";
}
@Override
protected void readData(Element rootElement) throws Exception
{
long defResetPrice = 0L;
for (Iterator iterator = rootElement.elementIterator("config"); iterator.hasNext()
{
Element element = (Element) iterator.next();
Config.BS_COIN_ITEM_ID = Util.parseValue(element.attributeValue("coin_item_id"), 36308);
defResetPrice = Util.parseValue(element.attributeValue("def_reset_price"), 1000000L);
}
for (Iterator iterator = rootElement.elementIterator("set"); iterator.hasNext()
{
Element element = (Element) iterator.next();
int setId = Util.parseValue(element.attributeValue("id"), 0);
TIntObjectHashMap<BeautyShopHairStyle> hairStyles = new TIntObjectHashMap<BeautyShopHairStyle>();
TIntObjectHashMap<BeautyShopFace> faces = new TIntObjectHashMap<BeautyShopFace>();
for (Iterator<Element> subIterator = element.elementIterator("hair"); subIterator.hasNext()
{
Element subElement = subIterator.next();
int id = Util.parseValue(subElement.attributeValue("id"), 0);
long adena = Util.parseValue(subElement.attributeValue("adena"), 0L);
long coins = Util.parseValue(subElement.attributeValue("coins"), 0L);
long resetPrice = Util.parseValue(subElement.attributeValue("reset_price"), defResetPrice);
TIntObjectHashMap<BeautyShopHairColor> colors = new TIntObjectHashMap<BeautyShopHairColor>();
for (Iterator<Element> colorIterator = subElement.elementIterator("color"); colorIterator.hasNext()
{
Element colorElement = colorIterator.next();
int cId = Util.parseValue(colorElement.attributeValue("id"), 0);
long cAdena = Util.parseValue(colorElement.attributeValue("adena"), 0L);
long cCoins = Util.parseValue(colorElement.attributeValue("coins"), 0L);
colors.put(cId, new BeautyShopHairColor(cId, cAdena, cCoins));
}
if (colors.isEmpty())
colors.put(101, new BeautyShopHairColor(101, 0, 0));
hairStyles.put(id, new BeautyShopHairStyle(id, adena, coins, resetPrice, colors));
}
for (Iterator<Element> subIterator = element.elementIterator("face"); subIterator.hasNext()
{
Element subElement = subIterator.next();
int id = Util.parseValue(subElement.attributeValue("id"), 0);
long adena = Util.parseValue(subElement.attributeValue("adena"), 0L);
long coins = Util.parseValue(subElement.attributeValue("coins"), 0L);
long resetPrice = Util.parseValue(subElement.attributeValue("reset_price"), defResetPrice);
faces.put(id, new BeautyShopFace(id, adena, coins, resetPrice));
}
getHolder().addSet(new BeautyShopSet(setId, hairStyles, faces));
}
}
}
[/SRC]
Хранилище:
[SRC="java"]
package l2p.gameserver.data.xml.holder;
import gnu.trove.map.hash.TIntObjectHashMap;
import l2p.commons.data.xml.AbstractHolder;
import l2p.gameserver.model.Player;
import l2p.gameserver.model.base.Race;
import l2p.gameserver.model.base.Sex;
import l2p.gameserver.model.beautyshop.BeautyShopSet;
public final class BeautyShopHolder extends AbstractHolder
{
private static final BeautyShopHolder _instance = new BeautyShopHolder();
private static final TIntObjectHashMap<BeautyShopSet> _sets = new TIntObjectHashMap<BeautyShopSet>();
public static BeautyShopHolder getInstance()
{
return _instance;
}
public void addSet(BeautyShopSet set)
{
if (_sets.containsKey(set.getId()))
warn("Duplicate set declaration, set id - " + set.getId());
_sets.put(set.getId(), set);
}
public BeautyShopSet getSet(int id)
{
return _sets.get(id);
}
public BeautyShopSet getSet(Player player)
{
int id = -1;
if (player.getRace() == Race.HUMAN)
{
if (!player.getClassId().isMage())
id = player.getSex() == Sex.MALE ? 0 : 1;
else
id = player.getSex() == Sex.MALE ? 8 : 9;
}
else if (player.getRace() == Race.DARK_ELF)
id = player.getSex() == Sex.MALE ? 2 : 3;
else if (player.getRace() == Race.DWARF)
id = player.getSex() == Sex.MALE ? 4 : 5;
else if (player.getRace() == Race.ELF)
id = player.getSex() == Sex.MALE ? 6 : 7;
else if (player.getRace() == Race.ORC)
{
if (!player.getClassId().isMage())
id = player.getSex() == Sex.MALE ? 10 : 11;
else
id = player.getSex() == Sex.MALE ? 12 : 13;
}
else if (player.getRace() == Race.KAMAEL)
id = player.getSex() == Sex.MALE ? 14 : 15;
return getSet(id);
}
@Override
public int size()
{
return _sets.size();
}
@Override
public void clear()
{
_sets.clear();
}
@Override
public void log()
{
info(String.format("loaded %d beauty shop set(s) count.", size()));
}
}
[/SRC]
Структуры для хранения данных:
[SRC="java"]
package l2p.gameserver.model.beautyshop;
import gnu.trove.map.hash.TIntObjectHashMap;
public class BeautyShopSet
{
private final int _id;
private final TIntObjectHashMap<BeautyShopHairStyle> _hairStyles;
private final TIntObjectHashMap<BeautyShopFace> _faces;
public BeautyShopSet(int id, TIntObjectHashMap<BeautyShopHairStyle> hairStyles, TIntObjectHashMap<BeautyShopFace> faces)
{
_id = id;
_hairStyles = hairStyles;
_faces = faces;
}
public int getId()
{
return _id;
}
public TIntObjectHashMap<BeautyShopHairStyle> getHairStyles()
{
return _hairStyles;
}
public BeautyShopHairStyle getHairStyle(int id)
{
return _hairStyles.get(id);
}
public TIntObjectHashMap<BeautyShopFace> getFaces()
{
return _faces;
}
public BeautyShopFace getFace(int id)
{
return _faces.get(id);
}
}
package l2p.gameserver.model.beautyshop;
public class BeautyShopFace
{
private final int _id;
private final long _adena;
private final long _coins;
private final long _resetPrice;
public BeautyShopFace(int id, long adena, long coins, long resetPrice)
{
_id = id;
_adena = adena;
_coins = coins;
_resetPrice = resetPrice;
}
public int getId()
{
return _id;
}
public long getAdena()
{
return _adena;
}
public long getCoins()
{
return _coins;
}
public long getResetPrice()
{
return _resetPrice;
}
}
package l2p.gameserver.model.beautyshop;
import gnu.trove.map.hash.TIntObjectHashMap;
public class BeautyShopHairStyle
{
private final int _id;
private final long _adena;
private final long _coins;
private final long _resetPrice;
private final TIntObjectHashMap<BeautyShopHairColor> _hairColors;
public BeautyShopHairStyle(int id, long adena, long coins, long resetPrice, TIntObjectHashMap<BeautyShopHairColor> hairColors)
{
_id = id;
_adena = adena;
_coins = coins;
_resetPrice = resetPrice;
_hairColors = hairColors;
}
public int getId()
{
return _id;
}
public long getAdena()
{
return _adena;
}
public long getCoins()
{
return _coins;
}
public long getResetPrice()
{
return _resetPrice;
}
public TIntObjectHashMap<BeautyShopHairColor> getHairColors()
{
return _hairColors;
}
public BeautyShopHairColor getHairColor(int id)
{
return _hairColors.get(id);
}
}
package l2p.gameserver.model.beautyshop;
public class BeautyShopHairColor
{
private final int _id;
private final long _adena;
private final long _coins;
public BeautyShopHairColor(int id, long adena, long coins)
{
_id = id;
_adena = adena;
_coins = coins;
}
public int getId()
{
return _id;
}
public long getAdena()
{
return _adena;
}
public long getCoins()
{
return _coins;
}
}
[/SRC]
В парсере вы можете заметить для чтения значений используется некие методы c названием Util.parseValue( ... ) - приводить их код я не буду, т.к. и так понятно как и что они делают - можете просто написать свою реализацию их или же использовать уже что-то готовое (методы, выполняющие примерно те же функции, насколько помню вроде как были и в оригинальном овере).
Далее нам необходимо внести мелкие изменения в пакеты UserInfo, CharInfo и GMViewCharacterInfo, для корректного отображения новой внешности.
Изменения простейшие, типа таких:
[SRC="java"]
_hairStyle = player.getNewHairStyle() > 0 ? player.getNewHairStyle() : player.getHairStyle();
_hairColor = player.getNewHairColor() > 0 ? player.getNewHairColor() : player.getHairColor();
_face = player.getNewFace() > 0 ? player.getNewFace() : player.getFace();
[/SRC]
Ну и напоследок самое главное - все пакеты, связанные с салоном красоты.
Объяснять их содержимое думаю нет смысла - они достаточно просты и по их коду и так все понятно.
Но сначала не забудем добавить в GamePacketHandler обработку необходимых клиентских пакетов:
[SRC="java"]
case 0xD7:
msg = new RequestShowBeautyList();
break;
case 0xD8:
msg = new RequestRegistBeauty();
break;
case 0xDA:
msg = new RequestShowResetShopList();
break;
case 0xEE:
msg = new NotifyExitBeautyShop();
break;
[/SRC]
Ну и непосредственно сами пакеты.
Клиентские:
[SRC="java"]
package l2p.gameserver.network.clientpackets;
import l2p.gameserver.model.Player;
import l2p.gameserver.network.serverpackets.ExResponseBeautyList;
public class RequestShowBeautyList extends L2GameClientPacket
{
private int _type;
@Override
protected void readImpl()
{
_type = readD();
}
@Override
protected void runImpl()
{
Player player = getClient().getActiveChar();
if (player == null)
return;
player.sendPacket(new ExResponseBeautyList(player, _type));
}
}
package l2p.gameserver.network.clientpackets;
import l2p.gameserver.Config;
import l2p.gameserver.data.xml.holder.BeautyShopHolder;
import l2p.gameserver.model.Player;
import l2p.gameserver.model.beautyshop.BeautyShopSet;
import l2p.gameserver.network.serverpackets.ExResponseBeautyList;
import l2p.gameserver.network.serverpackets.ExResponseBeautyRegistReset;
public class RequestRegistBeauty extends L2GameClientPacket
{
private int _hairStyle, _face, _hairColor;
@Override
protected void readImpl()
{
_hairStyle = readD();
_face = readD();
_hairColor = readD();
}
@Override
protected void runImpl()
{
Player player = getClient().getActiveChar();
if (player == null)
return;
long reqAdena = 0;
long reqCoins = 0;
boolean change = false;
BeautyShopSet set = BeautyShopHolder.getInstance().getSet(player);
if (set == null)
{
player.sendPacket(new ExResponseBeautyRegistReset(player, 0, 0), new ExResponseBeautyList(player, 0));
return;
}
if (_hairStyle > 0 && _hairColor > 0 && (_hairStyle != player.getNewHairStyle() || _hairColor != player.getNewHairColor()))
{
if (set.getHairStyle(_hairStyle) == null || set.getHairStyle(_hairStyle).getHairColor(_hairColor) == null)
{
player.sendPacket(new ExResponseBeautyRegistReset(player, 0, 0), new ExResponseBeautyList(player, 0));
return;
}
if (_hairStyle != player.getNewHairStyle())
{
reqAdena += set.getHairStyle(_hairStyle).getAdena() + set.getHairStyle(_hairStyle).getHairColor(_hairColor).getAdena();
reqCoins += set.getHairStyle(_hairStyle).getCoins() + set.getHairStyle(_hairStyle).getHairColor(_hairColor).getCoins();
}
else
{
reqAdena += set.getHairStyle(_hairStyle).getHairColor(_hairColor).getAdena();
reqCoins += set.getHairStyle(_hairStyle).getHairColor(_hairColor).getCoins();
}
change = true;
}
if (_face > 0 && _face != player.getNewFace())
{
if (set.getFace(_face) == null)
{
player.sendPacket(new ExResponseBeautyRegistReset(player, 0, 0), new ExResponseBeautyList(player, 0));
return;
}
reqAdena += set.getFace(_face).getAdena();
reqCoins += set.getFace(_face).getCoins();
change = true;
}
if (!change || player.getItemCount(57) < reqAdena || player.getItemCount(Config.BS_COIN_ITEM_ID) < reqCoins)
{
player.sendPacket(new ExResponseBeautyRegistReset(player, 0, 0), new ExResponseBeautyList(player, 0));
return;
}
if (reqAdena > 0)
player.getInventory().destroyItemByItemId(57, reqAdena);
if (reqCoins > 0)
player.getInventory().destroyItemByItemId(Config.BS_COIN_ITEM_ID, reqCoins);
if (_hairStyle > 0)
{
player.setNewHairStyle(_hairStyle);
player.setNewHairColor(_hairColor);
}
if (_face > 0)
player.setNewFace(_face);
player.sendPacket(new ExResponseBeautyRegistReset(player, 0, 1), new ExResponseBeautyList(player, 0));
}
}
package l2p.gameserver.network.clientpackets;
import l2p.gameserver.data.xml.holder.BeautyShopHolder;
import l2p.gameserver.model.Player;
import l2p.gameserver.model.beautyshop.BeautyShopSet;
import l2p.gameserver.network.serverpackets.ExResponseBeautyRegistReset;
public class RequestShowResetShopList extends L2GameClientPacket
{
private int _hairStyle, _face, _hairColor;
@Override
protected void readImpl()
{
_hairStyle = readD();
_face = readD();
_hairColor = readD();
}
@Override
protected void runImpl()
{
Player player = getClient().getActiveChar();
if (player == null)
return;
long reqAdena = 0;
boolean reset = false;
BeautyShopSet set = BeautyShopHolder.getInstance().getSet(player);
if (set == null)
{
player.sendPacket(new ExResponseBeautyRegistReset(player, 1, 0));
return;
}
if (_hairStyle > 0 && _hairColor > 0)
{
if (set.getHairStyle(_hairStyle) == null)
{
player.sendPacket(new ExResponseBeautyRegistReset(player, 1, 0));
return;
}
reqAdena += set.getHairStyle(_hairStyle).getResetPrice();
reset = true;
}
if (_face > 0)
{
if (set.getFace(_face) == null)
{
player.sendPacket(new ExResponseBeautyRegistReset(player, 1, 0));
return;
}
reqAdena += set.getFace(_face).getResetPrice();
reset = true;
}
if (!reset || player.getAdena() < reqAdena)
{
player.sendPacket(new ExResponseBeautyRegistReset(player, 1, 0));
return;
}
player.getInventory().destroyItemByItemId(57, reqAdena);
if (_hairStyle > 0)
{
player.setNewHairStyle(0);
player.setNewHairColor(0);
}
if (_face > 0)
player.setNewFace(0);
player.sendPacket(new ExResponseBeautyRegistReset(player, 1, 1));
}
}
package l2p.gameserver.network.clientpackets;
import l2p.gameserver.model.Player;
public class NotifyExitBeautyShop extends L2GameClientPacket
{
@Override
protected void readImpl()
{}
@Override
protected void runImpl()
{
Player player = getClient().getActiveChar();
if (player == null)
return;
player.broadcastCharInfo();
}
}
[/SRC]
И серверные пакеты:
[SRC="java"]
package l2p.gameserver.network.serverpackets;
import l2p.gameserver.network.serverpackets.components.ServerPacket;
public class ExShowBeautyMenu extends L2GameServerPacket
{
public int _type; // 0 - выбор нового внешнего вида, 1 - возврат старого
public ExShowBeautyMenu(int type)
{
_type = type;
}
@Override
protected void writeImpl()
{
writeEx(ServerPacket.ExShowBeautyMenu);
writeD(_type);
}
}
package l2p.gameserver.network.serverpackets;
import l2p.gameserver.Config;
import l2p.gameserver.data.xml.holder.BeautyShopHolder;
import l2p.gameserver.model.Player;
import l2p.gameserver.model.beautyshop.BeautyShopFace;
import l2p.gameserver.model.beautyshop.BeautyShopHairStyle;
import l2p.gameserver.model.beautyshop.BeautyShopSet;
import l2p.gameserver.network.serverpackets.components.ServerPacket;
public class ExResponseBeautyList extends L2GameServerPacket
{
private int _type;
private long _adena;
private long _coins;
private int[][] _data;
private boolean _send = false;
public ExResponseBeautyList(Player player, int type)
{
BeautyShopSet set = BeautyShopHolder.getInstance().getSet(player);
if (set == null)
return;
int i = 0;
if (type == 0)
{
_data = new int[set.getHairStyles().size()][2];
for (BeautyShopHairStyle style : set.getHairStyles().valueCollection())
{
_data[i][0] = style.getId();
_data[i][1] = 0;
i++;
}
}
else
{
_data = new int[set.getFaces().size()][2];
for (BeautyShopFace face : set.getFaces().valueCollection())
{
_data[i][0] = face.getId();
_data[i][1] = 0;
i++;
}
}
_type = type;
_adena = player.getAdena();
_coins = player.getItemCount(Config.BS_COIN_ITEM_ID);
_send = true;
}
@Override
protected void writeImpl()
{
if (!_send)
return;
writeEx(ServerPacket.ExResponseBeautyList);
writeQ(_adena);
writeQ(_coins);
writeD(_type);
writeD(_data.length);
for (int[] element : _data)
{
writeD(element[0]);
writeD(element[1]);
}
}
}
package l2p.gameserver.network.serverpackets;
import l2p.gameserver.Config;
import l2p.gameserver.model.Player;
import l2p.gameserver.network.serverpackets.components.ServerPacket;
public class ExResponseBeautyRegistReset extends L2GameServerPacket
{
private int _type;
private int _result;
private int _hairStyle;
private int _hairColor;
private int _face;
private long _adena;
private long _coins;
public ExResponseBeautyRegistReset(Player player, int type, int result)
{
_type = type;
_result = result;
_hairStyle = player.getNewHairStyle() > 0 ? player.getNewHairStyle() : player.getHairStyle();
_hairColor = player.getNewHairColor() > 0 ? player.getNewHairColor() : player.getHairColor();
_face = player.getNewFace() > 0 ? player.getNewFace() : player.getFace();
_adena = player.getAdena();
_coins = player.getItemCount(Config.BS_COIN_ITEM_ID);
}
@Override
protected void writeImpl()
{
writeEx(ServerPacket.ExResponseBeautyRegistReset);
writeQ(_adena);
writeQ(_coins);
writeD(_type);
writeD(_result);
writeD(_hairStyle);
writeD(_face);
writeD(_hairColor);
}
}
package l2p.gameserver.network.serverpackets;
import l2p.gameserver.Config;
import l2p.gameserver.model.Player;
import l2p.gameserver.network.serverpackets.components.ServerPacket;
public class ExResponseResetList extends L2GameServerPacket
{
private int _hairStyle;
private int _hairColor;
private int _face;
private long _adena;
private long _coins;
public ExResponseResetList(Player player)
{
_hairStyle = player.getHairStyle();
_hairColor = player.getHairColor();
_face = player.getFace();
_adena = player.getAdena();
_coins = player.getItemCount(Config.BS_COIN_ITEM_ID);
}
@Override
protected void writeImpl()
{
writeEx(ServerPacket.ExResponseResetList);
writeQ(_adena);
writeQ(_coins);
writeD(_hairStyle);
writeD(_face);
writeD(_hairColor);
}
}
[/SRC]
И опкоды этих пакетов:
[SRC="java"]
public static final int ExShowBeautyMenu = 0x13E;
public static final int ExResponseBeautyList = 0x13F;
public static final int ExResponseBeautyRegistReset = 0x140;
public static final int ExResponseResetList = 0x141;
[/SRC]
Ну вот собственно и все. Если я что и не описал или не пояснил - это в основном мелочи, до которых вы сами сможете без проблем додуматься.