- 1,159
- 38
- 544
Здравствуйте, почти неделю убил на устранение ошибки, но все безрезультатно
У меня есть два инвентаря - StatsInventory и SkillsInventory. Если навести мышку на какой-либо слот в StatsInventory, то SkillsInventory заполнится только теми блоками, которые подходят для выбранного в StatsInventory. Т.е. сделано что-то вроде вкладок.
Вот так это выглядит:
Баг заключается в расчете параметра "Защита", который расчитывается на основе меты предмета FightSkillItem (навык драки). Т.е., когда GUI открывается, мод начинает искать в инвентаре предмет FightSkillItem и применяет алгоритм расчета (не думаю, что алгоритм имеет значение). Если предмет не найдет - тупо пишет в стойкости 2. В штатных ситуациях все работает хорошо и расчет происходит корректно...
Проблемы начинаются тогда, когда пользователь заходит в игру и открывает GUI. И тут мы видим, что Стойкость (зависит от выносливости) рассчиталась верно, а Защита - не расчиталась вообще (тупо написана двойка, мол мета FightSkillItem = 0). Но как мы помним, на момент выхода пользователя из игры его FightSkillItem был != 0. И в тот момент, когда мы наведем мышку на второй слот в StatsInventory, в SkillsInventory появится FightSkillItem (иконка кулака) с той метой, которая была у пользователя на момент выхода. И в тот же момент Защита расчитается верно.
Поначалу я думал, что это проблема синхронизации инвентаря на сервере и на клиенте. Опытным путем установил, что это не так - проблема в стартовой инициализации ExtendedPlayer (по крайней мере, мне так кажется)
код
Что я делаю не так и почему этот баг возникает? Заранее спасибо и простите за костыли и говнокод ибо вообще без понятия как этот баг победить.
У меня есть два инвентаря - StatsInventory и SkillsInventory. Если навести мышку на какой-либо слот в StatsInventory, то SkillsInventory заполнится только теми блоками, которые подходят для выбранного в StatsInventory. Т.е. сделано что-то вроде вкладок.
Вот так это выглядит:
Баг заключается в расчете параметра "Защита", который расчитывается на основе меты предмета FightSkillItem (навык драки). Т.е., когда GUI открывается, мод начинает искать в инвентаре предмет FightSkillItem и применяет алгоритм расчета (не думаю, что алгоритм имеет значение). Если предмет не найдет - тупо пишет в стойкости 2. В штатных ситуациях все работает хорошо и расчет происходит корректно...
Проблемы начинаются тогда, когда пользователь заходит в игру и открывает GUI. И тут мы видим, что Стойкость (зависит от выносливости) рассчиталась верно, а Защита - не расчиталась вообще (тупо написана двойка, мол мета FightSkillItem = 0). Но как мы помним, на момент выхода пользователя из игры его FightSkillItem был != 0. И в тот момент, когда мы наведем мышку на второй слот в StatsInventory, в SkillsInventory появится FightSkillItem (иконка кулака) с той метой, которая была у пользователя на момент выхода. И в тот же момент Защита расчитается верно.
Поначалу я думал, что это проблема синхронизации инвентаря на сервере и на клиенте. Опытным путем установил, что это не так - проблема в стартовой инициализации ExtendedPlayer (по крайней мере, мне так кажется)
код
ExtendedPlayer - мой IEEP:
StatInventory:
SkillsInventory:
Мой GUI:
контейнер
Java:
package rsstats.data;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.world.World;
import net.minecraftforge.common.IExtendedEntityProperties;
import org.apache.commons.logging.Log;
import rsstats.common.RSStats;
import rsstats.inventory.SkillsInventory;
import rsstats.inventory.StatsInventory;
import rsstats.inventory.WearableInventory;
import rsstats.items.SkillItem;
import rsstats.items.StatItem;
import java.util.logging.Logger;
/**
*
* @author rares
*/
public class ExtendedPlayer implements IExtendedEntityProperties {
/** Каждый наследник {@link IExtendedEntityProperties} должен иметь индивидуальное имя */
private static final String EXTENDED_ENTITY_TAG = RSStats.MODID;
private final EntityPlayer entityPlayer;
/** Основной параметр игрока - Шаг */
private int step = 6;
/** Основной параметр игрока - Защита */
private int protection;
/** Основной параметр игрока - Стойкость */
private int persistence;
/** Основной параметр игрока - Харизма */
private int charisma = 0;
private int exp = 0;
private int lvl = 0;
private int tiredness = 0;
private int tirednessLimit = 25;
/** Инвентарь для статов */
public final StatsInventory statsInventory;
/** Инвентарь для скиллов */
public final SkillsInventory skillsInventory;
/** Инвентарь для носимых предметов */
public final WearableInventory wearableInventory;
/*
Тут в виде полей можно хранить дополнительную информацию о Entity: мана,
золото, хп, переносимый вес, уровень радиации, репутацию и т.д. Т.е. все то,
что нельзя хранить в виде блоков
*/
private ExtendedPlayer(EntityPlayer player) {
this.entityPlayer = player;
statsInventory = new StatsInventory(player);
skillsInventory = new SkillsInventory(player);
wearableInventory = new WearableInventory();
}
/**
* Used to register these extended properties for the entityPlayer during EntityConstructing event
* This method is for convenience only; it will make your code look nicer
* @param player
*/
public static final void register(EntityPlayer player) {
player.registerExtendedProperties(ExtendedPlayer.EXTENDED_ENTITY_TAG, new ExtendedPlayer(player));
}
/**
* Returns ExtendedPlayer properties for entityPlayer
* This method is for convenience only; it will make your code look nicer
*/
public static final ExtendedPlayer get(EntityPlayer player) {
return (ExtendedPlayer) player.getExtendedProperties(EXTENDED_ENTITY_TAG);
}
public boolean isServerSide() {
return this.entityPlayer instanceof EntityPlayerMP;
}
@Override
public void saveNBTData(NBTTagCompound properties) {
properties.setInteger("exp", exp);
properties.setInteger("lvl", lvl);
properties.setInteger("tiredness", tiredness);
properties.setInteger("tirednessLimit", tirednessLimit);
this.statsInventory.writeToNBT(properties);
this.skillsInventory.writeToNBT(properties);
this.wearableInventory.writeToNBT(properties);
System.out.println("LOG: saveNBTData(), NBTTagCompound" + properties.toString());
}
// TODO: Почему-то когда открывается GUI - Отображается категорий скиллов ловкости
@Override
public void loadNBTData(NBTTagCompound properties) {
this.statsInventory.totalClear();
this.skillsInventory.totalClear();
exp = properties.getInteger("exp");
lvl = properties.getInteger("lvl");
tiredness = properties.getInteger("tiredness");
tirednessLimit = properties.getInteger("tirednessLimit");
System.out.println("LOG: loadNBTData(), NBTTagCompound" + properties.toString());
this.statsInventory.readFromNBT(properties);
this.skillsInventory.readFromNBT(properties);
this.wearableInventory.readFromNBT(properties);
}
/**
* Used to initialize the extended properties with the entity that this is attached to, as well
* as the world object.
* Called automatically if you register with the EntityConstructing event.
* May be called multiple times if the extended properties is moved over to a new entity.
* Such as when a player switches dimension {Minecraft re-creates the player entity}
* @param entity The entity that this extended properties is attached to
* @param world The world in which the entity exists
*/
@Override
public void init(Entity entity, World world) {
/* Крайне интересный хак. Дело в том, что init() используется для инициализации самого ExtendedPlayer'а,
* а не Compound'а, который передается в loadNBTData(). TODO: Я все еще не разобрался как связана сущность, создаваемая тут и compound в loadNBTData
* Я проивожу "инициализацию нового игрока" тут, т.к. если игрок зашел в игру первый раз - loadNBTData никогда не
* вызовется при логине. Подозреваю это из-за того, что на сервере нет NBT записи об этом игроке.
* Зато когда она заходит во второй раз - loadNBTData() точно вызовется, но т.к. перед ним вызовется и init(), то
* в loadNBTData() нужно предварительно очистить инициализацию нового игрока, которая выполнилась тут.
*/
ExtendedPlayer.get((EntityPlayer) entity).statsInventory.initItems();
ExtendedPlayer.get((EntityPlayer) entity).skillsInventory.initItems();
/*if (entity.toString() != null) {
System.out.print("LOG: init(), Entity" + entity.toString());
} else {*/
System.out.println("LOG: init()");
//}
// Инициализируем основные параметры
//loadNBTData(entity.getEntityData());
try { // КОСТЫЛЬ
ItemStack itemStack = skillsInventory.getSkill("item.FightingSkillItem");
if (itemStack.getItem().getDamage(itemStack) == 0) {
this.protection = 2;
} else {
this.protection = 2 + ((SkillItem) itemStack.getItem()).getRollLevel(itemStack) / 2;
}
itemStack = statsInventory.getStat("item.EnduranceStatItem");
this.persistence = 2 + ((StatItem) itemStack.getItem()).getRollLevel(itemStack) / 2;
} catch (Exception e) {}
}
public int getProtection() {
return protection;
}
public int getStep() {
return step;
}
public int getPersistence() {
return persistence;
}
public int getCharisma() {
return charisma;
}
public int getExp() {
return exp;
}
public int getLvl() {
return lvl;
}
public int getTiredness() {
return tiredness;
}
public int getTirednessLimit() {
return tirednessLimit;
}
public void setLvl(int lvl) {
this.lvl = lvl;
}
public void updateParams() {
try { // КОСТЫЛЬ
ItemStack itemStack = skillsInventory.getSkill("item.FightingSkillItem");
if (itemStack.getItem().getDamage(itemStack) == 0) {
this.protection = 2;
} else {
this.protection = 2 + ((SkillItem) itemStack.getItem()).getRollLevel(itemStack) / 2;
}
} catch (Exception e) {}
try {
ItemStack itemStack = statsInventory.getStat("item.EnduranceStatItem");
this.persistence = 2 + ((StatItem) itemStack.getItem()).getRollLevel(itemStack) / 2;
} catch (Exception e) {}
}
}
StatInventory:
Java:
package rsstats.inventory;
import cpw.mods.fml.common.registry.GameRegistry;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.util.StatCollector;
import net.minecraftforge.common.util.Constants;
import rsstats.common.CommonProxy;
import rsstats.common.RSStats;
import rsstats.items.SkillItem;
import rsstats.items.StatItem;
/**
* Инвентарь для статов игрока (сила, ловкость, выносливость и т.д.)
* @author RareScrap
*/
public class StatsInventory implements IInventory {
/** The key used to store and retrieve the inventory from NBT */
private static final String NBT_TAG = "stats";
/** Define the inventory size here for easy reference */
/* This is also the place to define which slot is which if you have different types,
* for example SLOT_SHIELD = 0, SLOT_AMULET = 1; */
private static final int INV_SIZE = 9;
/** Масимальный размер стака для предметов в инвенторе {@link #inventory} */
private static final int STACK_LIMIT = 1;
/** Структура, хранящая предметы инвентаря в стаках.
* Inventory's size must be same as number of slots you add to the Container class. */
private ItemStack[] inventory = new ItemStack[INV_SIZE];
/** Игрок, к которому привязан инвентарь */
private EntityPlayer entityPlayer;
/**
* Необходимый публичный контсруктор
*/
public StatsInventory(EntityPlayer entityPlayer) {
this.entityPlayer = entityPlayer;
}
/**
* Геттер для {@link #inventory}
* @return Размер массива {@link #inventory}
*/
@Override
public int getSizeInventory() {
return inventory.length;
}
/**
* Геттер для получения элементов из инвентаря {@link #inventory}
* @param slotIndex Индекс слота в инвенторе, из которого нужно получить предмет
* @return Стак предметов под индексом slotIndex в инвенторе
*/
@Override
public ItemStack getStackInSlot(int slotIndex) {
return inventory[slotIndex];
}
/**
* Удаляет предмет из слота инвентаря до определенного количества элементов и возвращает их в новый стак.
* @param slotIndex Слот в инвенторе, где лежит предмет, стак которого нужно уменьшить
* @param amount До скольки нужно уменьший стак
* @return Предмет с уменьшенным стаком
*/
@Override
public ItemStack decrStackSize(int slotIndex, int amount) {
ItemStack stack = getStackInSlot(slotIndex);
if (stack != null) {
if (stack.stackSize > amount) {
stack = stack.splitStack(amount);
markDirty(); // Аналог onInventoryChanged()
} else {
setInventorySlotContents(slotIndex, null);
}
}
return stack;
}
/**
* Clears a slot and returns it's previous content. Аналог removeStackFromSlot() в более новых версиях.
* @param slotIndex
* @return
*/
@Override
public ItemStack getStackInSlotOnClosing(int slotIndex) {
ItemStack stack = getStackInSlot(slotIndex);
setInventorySlotContents(slotIndex, null);
return stack;
}
@Override
public void setInventorySlotContents(int slotIndex, ItemStack itemStack) {
this.inventory[slotIndex] = itemStack;
if (itemStack != null && itemStack.stackSize > this.getInventoryStackLimit()) {
itemStack.stackSize = this.getInventoryStackLimit();
}
markDirty();
}
/**
* Возвращает локализованное имя инвентаря
* @return Имя инвентаря
*/
@Override
public String getInventoryName() {
return StatCollector.translateToLocal("inventory." + NBT_TAG);
}
/*
// Было в туториале но зачем - хз
@Override
public boolean isInvNameLocalized() {
return name.length() > 0;
}*/
@Override
public boolean hasCustomInventoryName() {
return false;
}
/**
* Возвращает масимальный размер стака для предметов в этом инвенторе
* @return {@link #STACK_LIMIT}
*/
@Override
public int getInventoryStackLimit() {
return STACK_LIMIT;
}
@Override
public void markDirty() {
// ТОDO: ХЗ что это мы делаем. Удаляем мусор? Гарантированно очищаем слоты?
for (int i = 0; i < getSizeInventory(); ++i) {
if (getStackInSlot(i) != null && getStackInSlot(i).stackSize == 0) {
inventory[i] = null;
}
}
// TODO: Проверить при помощи NBTEdit как мод ведет себя без этой строки
writeToNBT(entityPlayer.getEntityData());
}
/**
* Returns true if the given player has access to the inventory. The default implementation just checks
* the player's distance to the TileEntity and returns true if it is less than 8 blocks. The method uses
* the player's method getDistanceSq which returns the squared distance to the given point. If this is
* less than 64, the real distance is less than 8.
*
* Инвентарь может использоваться игроком?
* @param entityPlayer Сущность игрока, взаимодействующая с инвентарем
* @return false
*/
@Override
public boolean isUseableByPlayer(EntityPlayer entityPlayer) {
// TODO: Без понятия как это работает и зачем нужно
return entityPlayer.capabilities.isCreativeMode;
// return true;
}
@Override
public void openInventory() {}
@Override
public void closeInventory() {}
/**
* Проверяет, можно ли поместить предмет в данный слот инвентаря {@link #inventory}
*
* This method doesn't seem to do what it claims to do, as
* items can still be left-clicked and placed in the inventory
* even when this returns false
* @param slotIndex TODO
* @param itemStack Предмет, который хочет поместиться в инвентарь
* @return Итог проверки: возвращает true, если предмет можно поместить в инвентарь.
* Иначе - false.
*/
@Override
public boolean isItemValidForSlot(int slotIndex, ItemStack itemStack) {
// If you have different kinds of slots, then check them here:
// if (slot == SLOT_SHIELD && itemstack.getItem() instanceof ItemShield) return true;
// TODO: Хочу использовать уже реализованную проверку в StatSlot, но не нзнаю как
return itemStack.getItem() instanceof StatItem && !(itemStack.getItem() instanceof SkillItem);
}
/**
* Записывает состояние инвентаря в NBT
* @param compound TODO
*/
public void writeToNBT(NBTTagCompound compound) {
NBTTagList items = new NBTTagList();
for (int i = 0; i < getSizeInventory(); ++i) {
if (getStackInSlot(i) != null) {
NBTTagCompound item = new NBTTagCompound();
item.setByte("Slot", (byte) i);
getStackInSlot(i).writeToNBT(item);
items.appendTag(item);
}
}
// We're storing our items in a custom tag list using our 'NBT_TAG' from above
// to prevent potential conflicts
compound.setTag(NBT_TAG, items);
}
/**
* Читает данные из NBT, восстанавливая состояние инвентаря
* @param compound TODO
*/
public void readFromNBT(NBTTagCompound compound) {
NBTTagList items = compound.getTagList(NBT_TAG, Constants.NBT.TAG_COMPOUND);
/* Если инвентарь статов пустой или не содержвится в пришедшем compound'е (а он скорее всего содержится, см init())
* - добавляем стандартный набор статов */
if (items.tagCount() == 0) {
initItems();
return;
}
// Штатное чтение из NBT
for (int i = 0; i < items.tagCount(); ++i) {
NBTTagCompound item = items.getCompoundTagAt(i);
byte slot = item.getByte("Slot");
if (slot >= 0 && slot < getSizeInventory()) {
inventory[slot] = ItemStack.loadItemStackFromNBT(item);
}
}
}
/**
* Инициализирует начальные статы
*/
public void initItems() {
for (int i = 0; i < CommonProxy.Stats.values().length; i++) {
inventory[i] = new ItemStack(GameRegistry.findItem(RSStats.MODID, CommonProxy.Stats.values()[i].toString()));
}
markDirty();
}
/**
* Получаем стак из {@link #inventory} по указанному UnlocalizedName
* @param unlocalizedSkillName UnlocalizedName нужного стака скилла
* @return Стак {@link StatItem}'а
*/
public ItemStack getStat(String unlocalizedSkillName) {
for (ItemStack stat : inventory) {
if (stat.getUnlocalizedName().equals(unlocalizedSkillName)) {
return stat;
}
}
return null;
}
/**
* Очищает все имеющиеся хранилища {@link ItemStack}'ов
*/
public void totalClear() {
for (int i = 0; i < getSizeInventory(); ++i) {
inventory[i] = null;
}
}
}
SkillsInventory:
Java:
package rsstats.inventory;
import cpw.mods.fml.common.registry.GameRegistry;
import net.minecraft.client.Minecraft;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.util.StatCollector;
import net.minecraftforge.common.util.Constants;
import rsstats.client.gui.MainMenuGUI;
import rsstats.common.CommonProxy;
import rsstats.common.RSStats;
import rsstats.common.network.PacketUpdateParams;
import rsstats.items.SkillItem;
import java.util.ArrayList;
public class SkillsInventory extends StatsInventory {
/** The key used to store and retrieve the inventory from NBT */
private static final String NBT_TAG = "skills";
/** Define the inventory size here for easy reference */
/* This is also the place to define which slot is which if you have different types,
* for example SLOT_SHIELD = 0, SLOT_AMULET = 1; */
private static final int INV_SIZE = 27;
/** Структура, хранящая только те предметы, которые будут отображены в инвентаре пользователя.
* Inventory's size must be same as number of slots you add to the Container class. */
private ItemStack[] inventory = new ItemStack[INV_SIZE];
/** Структура, хранящая все предметы инвентаря в стаках */
private ArrayList<ItemStack> skills = new ArrayList<ItemStack>();
private EntityPlayer entityPlayer;
/**
* Необходимый публичный контсруктор
*/
public SkillsInventory(EntityPlayer entityPlayer) {
super(entityPlayer);
this.entityPlayer = entityPlayer;
}
/**
* Геттер для размера {@link #inventory}
* @return Размер массива {@link #inventory}
*/
@Override
public int getSizeInventory() {
return inventory.length;
}
/**
* Геттер для получения элементов из инвентаря {@link #inventory}
* @param slotIndex Индекс слота в инвенторе, из которого нужно получить предмет
* @return Стак предметов под индексом slotIndex в инвенторе
*/
@Override
public ItemStack getStackInSlot(int slotIndex) {
return inventory[slotIndex];
}
@Override
public void setInventorySlotContents(int slotIndex, ItemStack itemStack) {
//super.setInventorySlotContents(slotIndex, itemStack);
if (itemStack == null) { // Очистить слот
if (inventory[slotIndex] != null) { // Был ли слот очищенным до этого?
// Если нет - удаляем то, что есть сейчас в слоте из хранилища скиллов
removeSkill(inventory[slotIndex].getUnlocalizedName());
}
// Обновляем сам слот
this.inventory[slotIndex] = itemStack;
} else { // Добавить новый стак в слот
if (/*inventory[slotIndex] *!* == null &&*/ containSkill(itemStack.getUnlocalizedName())) {
removeSkill(itemStack.getUnlocalizedName());
} // TODO: !!!!!!!!!!!!!!!!!!!!!!!!
this.inventory[slotIndex] = itemStack;
skills.add(itemStack);
}
//CommonProxy.INSTANCE.sendTo(new PacketUpdateParams(skills), (EntityPlayerMP) entityPlayer);
if (itemStack != null && itemStack.stackSize > this.getInventoryStackLimit()) {
itemStack.stackSize = this.getInventoryStackLimit();
}
// TODO: ВАЖНО this.onInventoryChanged();
}
/**
* Проверяет, можно ли поместить предмет в данный слот инвентаря {@link #inventory}
* <p>
* This method doesn't seem to do what it claims to do, as
* items can still be left-clicked and placed in the inventory
* even when this returns false
*
* @param slotIndex TODO
* @param itemStack Предмет, который хочет поместиться в инвентарь
* @return Итог проверки: возвращает true, если предмет можно поместить в инвентарь.
* Иначе - false.
*/
@Override
public boolean isItemValidForSlot(int slotIndex, ItemStack itemStack) {
return itemStack.getItem() instanceof SkillItem;
}
/**
* Возвращает локализованное имя инвентаря
* @return Имя инвентаря
*/
@Override
public String getInventoryName() {
return StatCollector.translateToLocal("inventory." + NBT_TAG);
}
/**
* Записывает состояние инвентаря в NBT
* @param compound TODO
*/
@Override
public void writeToNBT(NBTTagCompound compound) {
NBTTagList items = new NBTTagList();
for (ItemStack skill : skills) {
if (skill != null) {
NBTTagCompound item = new NBTTagCompound();
//item.setByte("Slot", (byte) i);
skill.writeToNBT(item);
items.appendTag(item);
}
}
// We're storing our items in a custom tag list using our 'NBT_TAG' from above
// to prevent potential conflicts
compound.setTag(NBT_TAG, items);
}
/**
* Читает данные из NBT, восстанавливая состояние инвентаря
*
* @param compound TODO
*/
@Override
public void readFromNBT(NBTTagCompound compound) {
NBTTagList items = compound.getTagList(NBT_TAG, Constants.NBT.TAG_COMPOUND);
/* Сравнивать compound'ы через строки понадобвится того, когда потребуется чтобы статы не
* добавлялись когда их нет, но в compound есть тег инвентаря статов */
//String a = compound.toString();
//String b = a.replaceFirst(NBT_TAG, "");
if (items.tagCount() == 0) {
initItems();
return;
}
// Штатное чтение из NBT
byte slot = 0;
String asd = ((SkillItem) ItemStack.loadItemStackFromNBT(items.getCompoundTagAt(0)).getItem()).parentStat.getUnlocalizedName();
for (int i = 0; i < items.tagCount(); ++i) {
NBTTagCompound NBTItem = items.getCompoundTagAt(i);
//byte slot = NBTItem.getByte("Slot");
SkillItem item = (SkillItem) ItemStack.loadItemStackFromNBT(NBTItem).getItem();
if (!containSkill(item.getUnlocalizedName()))
skills.add(ItemStack.loadItemStackFromNBT(NBTItem));
if (slot >= 0 && slot < getSizeInventory() && item.parentStat.getUnlocalizedName().equals(asd)) {
ItemStack itemstack = skills.get(skills.size()-1);
inventory[slot++] = itemstack;
}
}
}
/**
* Инициализирует начальные скиллы
*/
@Override
public void initItems() {
// Заполняем общее хранилище
for (CommonProxy.Skills skill : CommonProxy.Skills.values()) {
skills.add(new ItemStack(GameRegistry.findItem(RSStats.MODID, skill.toString())));
}
// Заполняем хранилище отображения
for (int i = 0, slot = 0; i < skills.size() && slot < inventory.length; i++) {
SkillItem item = (SkillItem) skills.get(i).getItem();
if (item.parentStat.getUnlocalizedName().equals("item.StrengthStatItem")) {
inventory[slot++] = skills.get(i);
}
}
markDirty();
}
/**
* Очищает {@link #inventory}, выставляя все его элементы null
*/
private void clearInventory() {
for (int i = 0; i < getSizeInventory(); i++) {
inventory[i] = null;
}
}
/**
* Проверяет, содержится ли в {@link #skills} стак с предметом по имение skillName
* @param skillName UnlocalizedName поискового скилла
* @return True, если элемент есть в {@link #skills}, иначе - false.
*/
private boolean containSkill(String skillName) {
for (ItemStack skill : skills) {
if (skill.getUnlocalizedName().equals(skillName)) {
return true;
}
}
return false;
}
/**
* Находит и удаляет стак из {@link #skills}
* @param unlocalizedSkillName UnlocalizedName, по которому будет произведен поиск.
* Если элемент найтен - он удалится из {@link #skills}
*/
private void removeSkill(String unlocalizedSkillName) {
for (ItemStack skill : skills) {
if (skill.getUnlocalizedName().equals(unlocalizedSkillName)) {
skills.remove(skill);
return;
}
}
}
/**
* Получаем стак из {@link #skills} по указанному UnlocalizedName
* @param unlocalizedSkillName UnlocalizedName нужного стака скилла
* @return Стак {@link SkillItem}
*/
public ItemStack getSkill(String unlocalizedSkillName) {
for (ItemStack skill : skills) {
if (skill.getUnlocalizedName().equals(unlocalizedSkillName)) {
return skill;
}
}
return null;
}
/**
* Заполняет {@link #inventory} подходящими элементами из {@link #skills}
* @param parentStatName {@link #inventory} будет заполнен только теми элентами, которые
* имеют данный parentStat.UnlocalizedName
*/
public void setSkillsFor(String parentStatName) {
clearInventory();
int slot = 0;
for (ItemStack skill : skills) {
SkillItem item = (SkillItem) skill.getItem();
if (parentStatName.equals(item.parentStat.getUnlocalizedName()))
this.inventory[slot++] = skill;
//setInventorySlotContents(slot++, skill);
}
}
/**
* Очищает все имеющиеся хранилища {@link ItemStack}'ов
*/
@Override
public void totalClear() {
// TODO: Почему нельзя вызвать супер?
skills.clear();
clearInventory();
}
public void setNewSkills(ArrayList<ItemStack> list) {
this.skills = list;
Minecraft.getMinecraft().currentScreen.updateScreen();
}
}
Мой GUI:
Java:
package rsstats.client.gui;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.renderer.InventoryEffectRenderer;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.RenderHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import rsstats.common.CommonProxy;
import rsstats.common.RSStats;
import rsstats.common.network.PacketShowSkillsByStat;
import rsstats.data.ExtendedPlayer;
import rsstats.inventory.container.MainContainer;
import rsstats.items.SkillItem;
import java.util.Timer;
import java.util.TimerTask;
/**
* GUI для основного окна мода, содержащее информацию о персонаже (имя, уровень, здоровье, защита, харизма,
* стойкость), панель предметов и панели статов, навыков и перков.
* @author RareScrap
*/
public class MainMenuGUI extends InventoryEffectRenderer {
/** Расположение фона GUI */
private static final ResourceLocation background =
new ResourceLocation(RSStats.MODID,"textures/gui/StatsAndInvTab_FIT.png");
public ExtendedPlayer player;
/** UnlocalozedName текущей выбранно статы */
private String currentStat = "";
/** Инвентарь для статов */
// Could use IInventory type to be more generic, but this way will save an import...
// Нужно для запроса кастомного имени инвентаря для отрисоки названия инвентаря
//private final StatsInventory statsInventory;
private Timer timer;
public MainMenuGUI(ExtendedPlayer player, MainContainer mainContainer) {
super(mainContainer);
this.allowUserInput = true;
this.player = player;
// Высталяем размеры контейнера. Соответствует размерам GUI на текстуре.
this.xSize = 340;
this.ySize = 211;
// Выставляем края контейнера (верхний и левый)
this.guiLeft = this.width/2 - xSize/2;
this.guiTop = this.height/2 - ySize/2;
}
/**
* Свой аналог {@link #drawTexturedModalRect(int, int, int, int, int, int)}, способный работать с текстурами
* разрешением более чем 256x256.
* @param x Координата начала отрисовки относительно левого-верхнего угла экрана игрока
* @param y Координата начала отрисовки относительно левого-верхнего угла экрана игрока
* @param u Координата начала текстуры по оси X относительно левого-верхнего угла текстуры
* @param v Координата начала текстуры по оси Y относительно левого-верхнего угла текстуры
* @param width Ширина текстуры, которую нужно отрисовать
* @param height Высота текстуры, которую нужно отрисовать
* @param textureWidth Общая ширина текстуры (кол-во пикселей в файле)
* @param textureHeight Общая высота текстуры (кол-во пикселей в файле)
*/
// Взято отсюда: http://www.minecraftforge.net/forum/topic/20177-172-gui-cant-more-than-256256/
private void drawTexturedRect(int x, int y, int u, int v, int width, int height, int textureWidth, int textureHeight) {
float f = 1F / (float)textureWidth;
float f1 = 1F / (float)textureHeight;
Tessellator tessellator = Tessellator.instance;
tessellator.startDrawingQuads();
tessellator.addVertexWithUV((double)(x), (double)(y + height), 0, (double)((float)(u) * f), (double)((float)(v + height) * f1));
tessellator.addVertexWithUV((double)(x + width), (double)(y + height), 0, (double)((float)(u + width) * f), (double)((float)(v + height) * f1));
tessellator.addVertexWithUV((double)(x + width), (double)(y), 0, (double)((float)(u + width) * f), (double)((float)(v) * f1));
tessellator.addVertexWithUV((double)(x), (double)(y), 0, (double)((float)(u) * f), (double)((float)(v) * f1));
tessellator.draw();
}
/**
* Draw the background layer for the GuiContainer (everything behind the items)
* @param partialTicks
* @param mouseX
* @param mouseY
*/
@Override
protected void drawGuiContainerBackgroundLayer(float partialTicks, int mouseX, int mouseY) {
GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
//GL11.glScalef(2.0F, 2.0F, 1.0F);
this.mc.getTextureManager().bindTexture(background);
// Отрисовываем текстуру GUI
drawTexturedRect(this.guiLeft, this.guiTop, 0, 0, xSize, ySize, xSize, ySize);
// Орисовываем превью игрока
drawPlayerModel(this.guiLeft+30, this.guiTop+90, /*17*/ 40, (float)(this.guiLeft + 51) - mouseX, (float)(this.guiTop + 75 - 50) - mouseY, this.mc.thePlayer);
// Это было в туторах, но я хз на что это влияет. Слоты и рендер предметов работают и без этого
/*for (int i1 = 0; i1 < this.inventorySlots.inventorySlots.size(); ++i1)
{
Slot slot = (Slot)this.inventorySlots.inventorySlots.get(i1);
//if (slot.getHasStack() && slot.getSlotStackLimit()==1)
//{
this.drawTexturedModalRect(k+slot.xDisplayPosition, l+slot.yDisplayPosition, 200, 0, 16, 16);
//}
}*/
}
/**
* Draw the foreground layer for the GuiContainer (everything in front of the items)
*
* @param p_146979_1_
* @param p_146979_2_
*/
@Override
protected void drawGuiContainerForegroundLayer(int p_146979_1_, int p_146979_2_) {
int textY = 123;
mc.fontRenderer.drawString("Шаг: " + player.getStep(), 8, textY, 0x444444, false);
mc.fontRenderer.drawString("Уровень: " + player.getLvl(), 60, textY, 0x444444, false);
mc.fontRenderer.drawString("Защита: " + player.getProtection(), 8, textY+=10, 0x444444, false);
mc.fontRenderer.drawString("Очки опыта: " + player.getExp(), 60, textY, 0x444444, false);
mc.fontRenderer.drawString("Стойкость: " + player.getPersistence(), 8, textY+=10, 0x444444, false);
mc.fontRenderer.drawString("Усталость: " + player.getTiredness(), 60, textY, 0x444444, false);
mc.fontRenderer.drawString("Харизма: " + player.getCharisma(), 8, textY+=10, 0x444444, false);
super.drawGuiContainerForegroundLayer(p_146979_1_, p_146979_2_);
}
/**
* Отрисовывает превью игрока
* @param x TODO
* @param y TODO
* @param scale Маштаб модели
* @param yaw TODO
* @param pitch TODO
* @param playerdrawn TODO
*/
private static void drawPlayerModel(int x, int y, int scale, float yaw, float pitch, EntityLivingBase playerdrawn) {
GL11.glEnable(GL11.GL_COLOR_MATERIAL);
GL11.glPushMatrix();
GL11.glTranslatef((float)x, (float)y, 50.0F);
GL11.glScalef((float)(-scale), (float)scale, (float)scale);
GL11.glRotatef(180.0F, 0.0F, 0.0F, 1.0F);
float f2 = playerdrawn.renderYawOffset;
float f3 = playerdrawn.rotationYaw;
float f4 = playerdrawn.rotationPitch;
float f5 = playerdrawn.prevRotationYawHead;
float f6 = playerdrawn.rotationYawHead;
GL11.glRotatef(135.0F, 0.0F, 1.0F, 0.0F);
RenderHelper.enableStandardItemLighting();
GL11.glRotatef(-135.0F, 0.0F, 1.0F, 0.0F);
GL11.glRotatef(-((float)Math.atan((double)(pitch / 40.0F))) * 20.0F, 1.0F, 0.0F, 0.0F);
playerdrawn.renderYawOffset = (float)Math.atan((double)(yaw / 40.0F)) * 20.0F;
playerdrawn.rotationYaw = (float)Math.atan((double)(yaw / 40.0F)) * 40.0F;
playerdrawn.rotationPitch = -((float)Math.atan((double)(pitch / 40.0F))) * 20.0F;
playerdrawn.rotationYawHead = playerdrawn.rotationYaw;
playerdrawn.prevRotationYawHead = playerdrawn.rotationYaw;
GL11.glTranslatef(0.0F, playerdrawn.yOffset, 0.0F);
RenderManager.instance.playerViewY = 180.0F;
RenderManager.instance.renderEntityWithPosYaw(playerdrawn, 0.0D, 0.0D, 0.0D, 0.0F, 1.0F);
playerdrawn.renderYawOffset = f2;
playerdrawn.rotationYaw = f3;
playerdrawn.rotationPitch = f4;
playerdrawn.prevRotationYawHead = f5;
playerdrawn.rotationYawHead = f6;
GL11.glPopMatrix();
RenderHelper.disableStandardItemLighting();
GL11.glDisable(GL12.GL_RESCALE_NORMAL);
OpenGlHelper.setActiveTexture(OpenGlHelper.lightmapTexUnit);
GL11.glDisable(GL11.GL_TEXTURE_2D);
OpenGlHelper.setActiveTexture(OpenGlHelper.defaultTexUnit);
}
@Override
protected void renderToolTip(ItemStack itemStack, int p_146285_2_, int p_146285_3_) {
Item item = itemStack.getItem();
if (!item.getUnlocalizedName().equals(currentStat) && !(item instanceof SkillItem)) {
PacketShowSkillsByStat packet = new PacketShowSkillsByStat(itemStack.getItem().getUnlocalizedName());
CommonProxy.INSTANCE.sendToServer(packet);
currentStat = itemStack.getItem().getUnlocalizedName();
}
super.renderToolTip(itemStack, p_146285_2_, p_146285_3_);
}
/**
* Fired when a key is typed. This is the equivalent of KeyListener.keyTyped(KeyEvent e).
*
* @param p_73869_1_
* @param p_73869_2_
*/
@Override
protected void keyTyped(char p_73869_1_, int p_73869_2_) {
// TODO: Добавь отображение скиллов по нажатой цифре
super.keyTyped(p_73869_1_, p_73869_2_);
}
/**
* Called from the main game loop to update the screen.
*/
@Override
public void updateScreen() {
super.updateScreen();
}
/**
* Called when the screen is unloaded. Used to disable keyboard repeat events
*/
@Override
public void onGuiClosed() {
super.onGuiClosed();
timer.cancel();
timer.purge();
}
/**
* Adds the buttons (and other controls) to the screen in question.
*/
@Override
public void initGui() {
super.initGui();
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
//System.out.println("timer");
player.updateParams();
updateScreen();
}
}, 0, 100);
}
}
контейнер
Java:
package rsstats.inventory.container;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.InventoryPlayer;
import net.minecraft.inventory.Container;
import net.minecraft.inventory.Slot;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import rsstats.common.CommonProxy;
import rsstats.common.network.PacketUpdateParams;
import rsstats.inventory.SkillsInventory;
import rsstats.inventory.StatsInventory;
import rsstats.inventory.WearableInventory;
import rsstats.inventory.slots.SkillSlot;
import rsstats.inventory.slots.StatSlot;
import rsstats.items.SkillItem;
import rsstats.items.StatItem;
import java.util.ArrayList;
import java.util.List;
/**
*
* @author rares
*/
public class MainContainer extends Container {
private final EntityPlayer player;
private final InventoryPlayer inventoryPlayer;
private final StatsInventory statsInventory;
private final WearableInventory wearableInventory;
private final SkillsInventory skillsInventory;
public MainContainer(EntityPlayer player, InventoryPlayer inventoryPlayer, StatsInventory statsInventory, SkillsInventory skillsInventory, WearableInventory wearableInventory) {
this.player = player;
this.inventoryPlayer = inventoryPlayer;
this.statsInventory = statsInventory;
this.skillsInventory = skillsInventory;
this.wearableInventory = wearableInventory;
addSlots();
}
public MainContainer() {
this.player = null;
this.inventoryPlayer = null;
this.statsInventory = null;
this.skillsInventory = null;
this.wearableInventory = null;
}
private void addSlots() {
/*if (inventoryPlayer != null)
for (int y = 0; y < 3; ++y) {
for (int x = 0; x < 9; ++x) {
this.addSlotToContainer(new Slot(inventoryPlayer, x + y * 9 + 9, 8 + x * 18, 84 + y * 18));
}
}*/
// Расставляем слоты на панели руки
for (int i = 0; i < 9; i++) {
this.addSlotToContainer(new Slot(inventoryPlayer, i, (i*18 -3) +8, 188));
}
// Расставляем слоты на панели статов
for (int i = 0, slotIndex = 0; i < statsInventory.getSizeInventory(); ++i, slotIndex++) {
this.addSlotToContainer(new StatSlot(statsInventory, i, (i*18 +167) +8, /*-24*/8));
//this.addSlotToContainer(new StatSlot(statsInventory, slotIndex, i*9, 0));
}
// Расставляем слоты на панели скиллов
for (int y = 0; y < 3; ++y) {
for (int x = 0; x < 9; ++x) {
this.addSlotToContainer(new SkillSlot(skillsInventory, x + y * 9 /*+ 9*/, (x*18 +167) +8, (y * 18) + 26));
}
}
// Расставляем слоты на панели носимых вещей
for (int y = 0; y < 5; ++y) {
for (int x = 0; x < 4; ++x) {
this.addSlotToContainer(new Slot(wearableInventory, x + y * 4 /*+ 9*/, (x*18 + 51) +8, (y * 18) + 8));
}
}
}
/**
* This should always return true, since custom inventory can be accessed from anywhere
* @param player TODO
* @return TODO
*/
@Override
public boolean canInteractWith(EntityPlayer player) {
return true;
}
/**
* Called when a entityPlayer shift-clicks on a slot. You must override this or you will crash when someone does that.
* Basically the same as every other container I make, since I define the same constant indices for all of them
* @param player TODO
* @param par2 TODO
* @return TODO
*/
@Override
public ItemStack transferStackInSlot(EntityPlayer player, int par2) {
ItemStack itemstack = null;
Slot slot = (Slot) this.inventorySlots.get(par2);
return itemstack;
}
@Override
public ItemStack slotClick(int slotId, int clickedButton, int mode, EntityPlayer playerIn) {
Slot slot;
try {
slot = getSlot(slotId);
} catch(Exception e) {
return super.slotClick(slotId, clickedButton, mode, playerIn);
//return null; // костыль
}
Item itemInSlot;
if (slot.getStack() != null && slot.getStack().getItem() != null) {
itemInSlot = slot.getStack().getItem();
} else {
return super.slotClick(slotId, clickedButton, mode, playerIn);
//return null;
}
// Прокачка навыков
List subitems = new ArrayList();
itemInSlot.getSubItems(itemInSlot, CreativeTabs.tabMaterials, subitems);
if (clickedButton == 1) { // ПКМ
int damage = itemInSlot.getDamage(slot.getStack());
itemInSlot.setDamage(slot.getStack(), damage < subitems.size()-1 ? damage+1 : subitems.size()-1);
return null;
}
if (clickedButton == 2) { // СКМ
int damage = itemInSlot.getDamage(slot.getStack());
itemInSlot.setDamage(slot.getStack(), damage > 0 ? damage-1 : 0);
return null;
}
if ((slot.inventory == statsInventory || slot.inventory == skillsInventory) && (itemInSlot instanceof SkillItem || itemInSlot instanceof StatItem)) {
ItemStack itemStack = getSlot(slotId).getStack();
// Защита от дублирующихся сообщений в чате
if (!playerIn.worldObj.isRemote) {
( (StatItem) itemStack.getItem() ).roll(itemStack, playerIn);
}
return null;
}
return super.slotClick(slotId, clickedButton, mode, playerIn);
}
public SkillsInventory getSkillsInventory() {
return skillsInventory;
}
}
Что я делаю не так и почему этот баг возникает? Заранее спасибо и простите за костыли и говнокод ибо вообще без понятия как этот баг победить.