Committing first iteration of CharData redesign

Changed API to work with item indexes (should simplify networking code)
Created ItemData abstraction to separate player and merc items
Moved set item fields to ItemData since they should eventually be distinct
Created EnumIntMap as a better primitive container for enum ordinals
D2S is now a container for saves, not the operational data itself
D2S CharData changes are intended to decouple them both and make D2S specifically to serialize and deserialize the data
This commit is contained in:
Collin Smith 2020-01-10 17:48:58 -08:00
parent 9b78c010e9
commit 997c57ad5e
5 changed files with 905 additions and 0 deletions

View File

@ -0,0 +1,512 @@
package com.riiablo.save;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.IntArray;
import com.badlogic.gdx.utils.IntIntMap;
import com.riiablo.CharacterClass;
import com.riiablo.Riiablo;
import com.riiablo.codec.excel.DifficultyLevels;
import com.riiablo.item.Attributes;
import com.riiablo.item.BodyLoc;
import com.riiablo.item.Item;
import com.riiablo.item.Location;
import com.riiablo.item.PropertyList;
import com.riiablo.item.Stat;
import com.riiablo.item.StoreLoc;
import com.riiablo.item.Type;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.Validate;
import java.nio.ByteBuffer;
import java.util.Arrays;
public class CharData {
private static final int attack = 0;
private static final int kick = 1;
private static final int throw_ = 2;
private static final int unsummon = 3;
private static final int left_hand_throw = 4;
private static final int left_hand_swing = 5;
private static final int scroll_of_identify = 217;
private static final int book_of_identify = 218;
private static final int scroll_of_townportal = 219;
private static final int book_of_townportal = 220;
private static final IntIntMap defaultSkills = new IntIntMap();
static {
defaultSkills.put(attack, 1);
defaultSkills.put(kick, 1);
//defaultSkills.put(throw_, 1);
defaultSkills.put(unsummon, 1);
//defaultSkills.put(left_hand_throw, 1);
defaultSkills.put(left_hand_swing, 1);
}
public String name;
public byte charClass;
public int alternate;
public int flags;
public byte level;
public final int hotkeys[] = new int[D2S.NUM_HOTKEYS];
public final int actions[][] = new int[D2S.NUM_ACTIONS][D2S.NUM_BUTTONS];
public final byte towns[] = new byte[D2S.NUM_DIFFS];
public int mapSeed;
public final byte realmData[] = new byte[144];
final MercData mercData = new MercData();
final short questData[][][] = new short[Riiablo.MAX_DIFFS][Riiablo.MAX_ACTS][8];
final int waypointData[][] = new int[Riiablo.MAX_DIFFS][Riiablo.MAX_ACTS];
final long npcIntroData[] = new long[Riiablo.MAX_DIFFS];
final long npcReturnData[] = new long[Riiablo.MAX_DIFFS];
final Attributes statData = new Attributes();
final IntIntMap skillData = new IntIntMap();
final ItemData itemData = new ItemData();
Item golemItemData;
public int diff;
public boolean managed;
public CharacterClass classId;
private byte[] data; // TODO: replace this reference with D2S.serialize(CharData)
final IntIntMap skills = new IntIntMap();
final Array<Stat> chargedSkills = new Array<>(false, 16);
final Array<SkillListener> skillListeners = new Array<>(false, 16);
public static CharData loadFromD2S(int diff, D2S d2s) {
CharData charData = new CharData(diff, true).load(d2s);
if (d2s.file != null) charData.data = d2s.file.readBytes();
return charData;
}
public static CharData loadFromBuffer(int diff, ByteBuffer buffer) {
D2S d2s = D2S.loadFromBuffer(buffer, true);
return new CharData(diff, false).load(d2s);
}
/**
* @param managed whether or not this data is backed by a file
*/
private CharData(int diff, boolean managed) {
this.diff = diff;
this.managed = managed;
}
// created via new character or external player
public CharData(int diff, boolean managed, String name, byte charClass) {
this(diff, managed);
this.name = name;
this.charClass = charClass;
classId = CharacterClass.get(charClass);
alternate = D2S.PRIMARY;
flags = D2S.FLAG_EXPANSION;
level = 1;
Arrays.fill(hotkeys, D2S.HOTKEY_UNASSIGNED);
for (int[] actions : actions) Arrays.fill(actions, 0);
// TODO: check and set town against saved town
mapSeed = 0;
}
public CharData load(D2S d2s) {
d2s.copyTo(this);
preprocessItems();
return this;
}
private void preprocessItems() {
itemData.preprocessItems();
mercData.itemData.preprocessItems();
}
public void reset() {
softReset();
name = null;
charClass = -1;
classId = null;
alternate = 0;
flags = 0;
level = 0;
Arrays.fill(hotkeys, D2S.HOTKEY_UNASSIGNED);
for (int i = 0, s = D2S.NUM_ACTIONS; i < s; i++) Arrays.fill(actions[i], 0);
Arrays.fill(towns, (byte) 0);
mapSeed = 0;
Arrays.fill(realmData, (byte) 0);
mercData.flags = 0;
mercData.seed = 0;
mercData.name = 0;
mercData.type = 0;
mercData.xp = 0;
for (int i = 0, i0 = Riiablo.MAX_DIFFS; i < i0; i++) {
for (int a = 0; a < Riiablo.MAX_ACTS; a++) Arrays.fill(questData[i][a], (short) 0);
Arrays.fill(waypointData[i], 0);
npcIntroData[i] = 0;
npcReturnData[i] = 0;
}
}
void softReset() {
statData.base().clear();
statData.reset();
skillData.clear();
itemData.clear();
mercData.itemData.clear();
golemItemData = null;
skills.clear();
chargedSkills.clear();
skillListeners.clear();
}
public boolean isManaged() {
return managed;
}
public byte[] serialize() {
Validate.isTrue(isManaged(), "Cannot serialize unmanaged data"); // TODO: replace temp check with D2S.serialize(CharData)
return ArrayUtils.nullToEmpty(data);
}
public int getHotkey(int button, int skill) {
return ArrayUtils.indexOf(hotkeys, button == Input.Buttons.LEFT ? skill | D2S.HOTKEY_LEFT_MASK : skill);
}
public void setHotkey(int button, int skill, int index) {
hotkeys[index] = button == Input.Buttons.LEFT ? skill | D2S.HOTKEY_LEFT_MASK : skill;
}
public int getAction(int button) {
return getAction(alternate, button);
}
public int getAction(int alternate, int button) {
return actions[alternate][button];
}
public void setAction(int alternate, int button, int skill) {
actions[alternate][button] = skill;
}
public boolean hasMerc() {
return mercData.seed != 0;
}
public MercData getMerc() {
return mercData;
}
public short[] getQuests(int act) {
return questData[diff][act];
}
public int getWaypoints(int act) {
return waypointData[diff][act];
}
public long getNpcIntro() {
return npcIntroData[diff];
}
public long getNpcReturn() {
return npcReturnData[diff];
}
public boolean hasGolemItem() {
return golemItemData != null;
}
public Item getGolemItem() {
return golemItemData;
}
public Attributes getStats() {
return statData;
}
public void updateStats() {
DifficultyLevels.Entry diff = Riiablo.files.DifficultyLevels.get(this.diff);
PropertyList base = statData.base();
base.put(Stat.armorclass, 0);
base.put(Stat.damageresist, 0);
base.put(Stat.magicresist, 0);
base.put(Stat.fireresist, diff.ResistPenalty);
base.put(Stat.lightresist, diff.ResistPenalty);
base.put(Stat.coldresist, diff.ResistPenalty);
base.put(Stat.poisonresist, diff.ResistPenalty);
base.put(Stat.maxfireresist, 75);
base.put(Stat.maxlightresist, 75);
base.put(Stat.maxcoldresist, 75);
base.put(Stat.maxpoisonresist, 75);
statData.reset();
int[] equippedItems = itemData.equipped.values();
for (int i = 0, s = equippedItems.length, j; i < s; i++) {
j = equippedItems[i];
if (j == ItemData.INVALID_ITEM) continue;
Item item = itemData.getItem(j);
if (isActive(item)) {
statData.add(item.props.remaining());
Stat stat;
if ((stat = item.props.get(Stat.armorclass)) != null) {
statData.aggregate().addCopy(stat);
}
}
}
IntArray inventoryItems = itemData.getStore(StoreLoc.INVENTORY);
int[] inventoryItemsCache = inventoryItems.items;
for (int i = 0, s = inventoryItems.size, j; i < s; i++) {
j = inventoryItemsCache[i];
if (j == ItemData.INVALID_ITEM) continue;
Item item = itemData.getItem(j);
if (item.type.is(Type.CHAR)) {
statData.add(item.props.remaining());
}
}
// statData.update(this); // TODO: uncomment
// FIXME: This corrects a mismatch between max and current, algorithm should be tested later for correctness in other cases
statData.get(Stat.maxstamina).set(statData.get(Stat.stamina));
statData.get(Stat.maxhp).set(statData.get(Stat.hitpoints));
statData.get(Stat.maxmana).set(statData.get(Stat.mana));
// This appears to be hard-coded in the original client
int dex = statData.get(Stat.dexterity).value();
Stat armorclass = statData.get(Stat.armorclass);
armorclass.add(dex / 4);
armorclass.modified = false;
skills.clear();
skills.putAll(skillData);
skills.putAll(defaultSkills);
Item LARM = getEquipped(BodyLoc.LARM);
Item RARM = getEquipped(BodyLoc.RARM);
if ((LARM != null && LARM.typeEntry.Throwable)
|| (RARM != null && RARM.typeEntry.Throwable)) {
skills.put(throw_, 1);
if (classId == CharacterClass.BARBARIAN) {
skills.put(left_hand_throw, 1);
}
}
for (int i = 0, s = inventoryItems.size; i < s; i++) {
Item item = itemData.getItem(inventoryItemsCache[i]);
if (item.type.is(Type.BOOK) || item.type.is(Type.SCRO)) {
if (item.base.code.equalsIgnoreCase("ibk")) {
skills.getAndIncrement(book_of_identify, 0, item.props.get(Stat.quantity).value());
} else if (item.base.code.equalsIgnoreCase("isc")) {
skills.getAndIncrement(scroll_of_identify, 0, 1);
} else if (item.base.code.equalsIgnoreCase("tbk")) {
skills.getAndIncrement(book_of_townportal, 0, item.props.get(Stat.quantity).value());
} else if (item.base.code.equalsIgnoreCase("tsc")) {
skills.getAndIncrement(scroll_of_townportal, 0, 1);
}
}
}
chargedSkills.clear();
for (Stat stat : statData.remaining()) {
switch (stat.id) {
case Stat.item_nonclassskill:
skills.getAndIncrement(stat.param(), 0, stat.value());
break;
case Stat.item_charged_skill:
chargedSkills.add(stat);
break;
default:
// do nothing
}
}
}
public int getSkill(int skill) {
return skills.get(skill, 0);
}
public ItemData getItems() {
return itemData;
}
public Item getItem(int i) {
return itemData.getItem(i);
}
public Item getCursor() {
return itemData.getCursor();
}
public Item getSlot(BodyLoc bodyLoc) {
return itemData.getSlot(bodyLoc);
}
public Item getEquipped(BodyLoc bodyLoc) {
return getEquipped(bodyLoc, alternate);
}
public Item getEquipped(BodyLoc bodyLoc, int alternate) {
return itemData.getEquipped(bodyLoc, alternate);
}
public boolean isActive(Item item) {
if (item == null) return false;
return item.bodyLoc == BodyLoc.getAlternate(item.bodyLoc, alternate);
}
public void itemToCursor(int i) {
assert itemData.cursor == ItemData.INVALID_ITEM;
itemData.cursor = i;
Item item = itemData.getItem(i);
item.location = Location.CURSOR;
}
public void locationToCursor(int i) {
itemToCursor(i);
}
public void cursorToLocation(StoreLoc storeLoc, int x, int y) {
assert itemData.cursor != ItemData.INVALID_ITEM;
Item item = itemData.getItem(itemData.cursor);
item.location = Location.STORED;
item.storeLoc = storeLoc;
item.gridX = (byte) x;
item.gridY = (byte) y;
itemData.cursor = ItemData.INVALID_ITEM;
}
public void swapLocationItem(int i, StoreLoc storeLoc, int x, int y) {
cursorToLocation(storeLoc, x, y);
locationToCursor(i);
}
public void bodyToCursor(BodyLoc bodyLoc) {
bodyToCursor(bodyLoc, false);
}
public void cursorToBody(BodyLoc bodyLoc) {
cursorToBody(bodyLoc, false);
}
public void swapBodyItem(BodyLoc bodyLoc) {
swapBodyItem(bodyLoc, false);
}
public void bodyToCursor(BodyLoc bodyLoc, boolean merc) {
assert itemData.cursor == ItemData.INVALID_ITEM;
Item item;
if (merc) {
int i = mercData.itemData.equipped.get(bodyLoc);
itemData.cursor = itemData.add(item = mercData.itemData.remove(i));
mercData.itemData.equipped.remove(item.bodyLoc);
} else {
itemData.cursor = itemData.equipped.get(bodyLoc);
item = itemData.getItem(itemData.cursor);
itemData.equipped.put(item.bodyLoc, ItemData.INVALID_ITEM);
}
item.location = Location.CURSOR;
}
public void cursorToBody(BodyLoc bodyLoc, boolean merc) {
assert itemData.cursor != ItemData.INVALID_ITEM;
Item item = itemData.getItem(itemData.cursor);
item.location = Location.EQUIPPED;
item.bodyLoc = bodyLoc;
if (merc) {
itemData.remove(itemData.cursor);
int i = mercData.itemData.add(item);
mercData.itemData.equipped.put(bodyLoc, i);
} else {
itemData.equipped.put(bodyLoc, itemData.cursor);
}
itemData.cursor = ItemData.INVALID_ITEM;
}
public void swapBodyItem(BodyLoc bodyLoc, boolean merc) {
assert itemData.cursor != ItemData.INVALID_ITEM;
int oldCursor = itemData.cursor;
itemData.cursor = ItemData.INVALID_ITEM;
bodyToCursor(bodyLoc, merc);
int newCursor = itemData.cursor;
itemData.cursor = oldCursor;
cursorToBody(bodyLoc, merc);
itemData.cursor = newCursor;
}
public void beltToCursor(int i) {
itemToCursor(i);
}
public void cursorToBelt(int x, int y) {
assert itemData.cursor != ItemData.INVALID_ITEM;
Item item = itemData.getItem(itemData.cursor);
item.location = Location.BELT;
item.gridX = (byte) x;
item.gridY = (byte) y;
itemData.cursor = ItemData.INVALID_ITEM;
}
public void swapBeltItem(int i) {
assert itemData.cursor != ItemData.INVALID_ITEM;
int oldCursor = itemData.cursor;
itemData.cursor = ItemData.INVALID_ITEM;
beltToCursor(i);
int newCursor = itemData.cursor;
itemData.cursor = oldCursor;
Item oldItem = itemData.getItem(oldCursor);
cursorToBelt(oldItem.gridX, oldItem.gridY);
itemData.cursor = newCursor;
}
public int getAlternate() {
return alternate;
}
public void setAlternate(int alternate) {
if (this.alternate != alternate) {
this.alternate = alternate;
Item LH = getEquipped(BodyLoc.LARM);
Item RH = getEquipped(BodyLoc.RARM);
updateStats();
itemData.notifyEquipmentAlternated(alternate, LH, RH);
}
}
public int alternate() {
int alt = getAlternate() > D2S.PRIMARY ? D2S.PRIMARY : D2S.SECONDARY;
setAlternate(alt);
return alt;
}
public static class MercData {
public int flags;
public int seed;
public short name;
public short type;
public int xp;
public final ItemData itemData = new ItemData();
public String getName() {
return String.format("0x%04X", name);
}
}
public void clearListeners() {
itemData.equipListeners.clear();
mercData.itemData.equipListeners.clear();
skillListeners.clear();
}
public boolean addSkillListener(SkillListener l) {
skillListeners.add(l);
return true;
}
private void notifySkillChanged(IntIntMap skills, Array<Stat> chargedSkills) {
for (SkillListener l : skillListeners) l.onChanged(this, skills, chargedSkills);
}
public interface SkillListener {
void onChanged(CharData client, IntIntMap skills, Array<Stat> chargedSkills);
}
}

View File

@ -6,11 +6,14 @@ import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.riiablo.CharacterClass;
import com.riiablo.Riiablo;
import com.riiablo.codec.COF;
import com.riiablo.codec.util.BitStream;
import com.riiablo.item.Item;
import com.riiablo.item.Location;
import com.riiablo.item.PropertyList;
import com.riiablo.item.Stat;
import com.riiablo.util.BufferUtils;
import com.riiablo.util.DebugUtils;
@ -143,6 +146,78 @@ public class D2S {
return d2s;
}
CharData copyTo(CharData data) {
data.softReset();
data.name = header.name;
data.charClass = header.charClass;
data.classId = CharacterClass.get(header.charClass);
data.alternate = header.alternate;
data.flags = header.flags;
data.level = header.level;
System.arraycopy(header.hotkeys, 0, data.hotkeys, 0, D2S.NUM_HOTKEYS);
for (int i = 0, s = D2S.NUM_ACTIONS; i < s; i++) System.arraycopy(header.actions[i], 0, data.actions[i], 0, D2S.NUM_BUTTONS);
System.arraycopy(header.towns, 0, data.towns, 0, D2S.NUM_DIFFS);
data.mapSeed = header.mapSeed;
System.arraycopy(header.realmData, 0, data.realmData, 0, header.realmData.length);
data.mercData.flags = header.merc.flags;
data.mercData.seed = header.merc.seed;
data.mercData.name = header.merc.name;
data.mercData.type = header.merc.type;
data.mercData.xp = header.merc.xp;
data.mercData.itemData.clear();
if (header.merc.seed != 0) data.mercData.itemData.addAll(header.merc.items.items.items);
BitStream bitStream;
for (int i = 0, i0 = Riiablo.MAX_DIFFS; i < i0; i++) {
bitStream = new BitStream(quests.data[i]);
for (int q = 0, q0 = 8; q < q0; q++) data.questData[i][0][q] = (short) bitStream.readUnsigned31OrLess(16);
for (int q = 0, q0 = 8; q < q0; q++) data.questData[i][1][q] = (short) bitStream.readUnsigned31OrLess(16);
for (int q = 0, q0 = 8; q < q0; q++) data.questData[i][2][q] = (short) bitStream.readUnsigned31OrLess(16);
for (int q = 0, q0 = 8; q < q0; q++) data.questData[i][3][q] = (short) bitStream.readUnsigned31OrLess(16);
for (int q = 0, q0 = 8; q < q0; q++) data.questData[i][4][q] = (short) bitStream.readUnsigned31OrLess(16);
bitStream = new BitStream(waypoints.diff[i].data);
data.waypointData[i][0] = bitStream.readUnsigned31OrLess(9);
data.waypointData[i][1] = bitStream.readUnsigned31OrLess(9);
data.waypointData[i][2] = bitStream.readUnsigned31OrLess(9);
data.waypointData[i][3] = bitStream.readUnsigned31OrLess(3);
data.waypointData[i][4] = bitStream.readUnsigned31OrLess(9);
bitStream = new BitStream(npcs.data[NPCData.GREETING_INTRO][i]);
data.npcIntroData[i] = bitStream.readUnsigned(64);
bitStream = new BitStream(npcs.data[NPCData.GREETING_RETURN][i]);
data.npcReturnData[i] = bitStream.readUnsigned(64);
}
PropertyList base = data.statData.base();
base.put(Stat.strength, stats.strength);
base.put(Stat.energy, stats.energy);
base.put(Stat.dexterity, stats.dexterity);
base.put(Stat.vitality, stats.vitality);
base.put(Stat.statpts, stats.statpts);
base.put(Stat.newskills, stats.newskills);
base.put(Stat.hitpoints, stats.hitpoints);
base.put(Stat.maxhp, stats.maxhp);
base.put(Stat.mana, stats.mana);
base.put(Stat.maxmana, stats.maxmana);
base.put(Stat.stamina, stats.stamina);
base.put(Stat.maxstamina, stats.maxstamina);
base.put(Stat.level, stats.level);
base.put(Stat.experience, (int) stats.experience);
base.put(Stat.gold, stats.gold);
base.put(Stat.goldbank, stats.goldbank);
for (int spellId = data.classId.firstSpell, s = data.classId.lastSpell, i = 0; spellId < s; spellId++, i++) {
data.skillData.put(spellId, skills.data[i]);
}
data.itemData.clear();
data.itemData.addAll(items.items);
data.golemItemData = golem.item;
return data;
}
public static class Header {
static final int SIZE = 0x14F;

View File

@ -0,0 +1,9 @@
package com.riiablo.save;
import com.riiablo.item.BodyLoc;
import com.riiablo.item.Item;
public class EquipAdapter implements ItemData.EquipListener {
@Override public void onChanged(ItemData items, BodyLoc bodyLoc, Item oldItem, Item item) {}
@Override public void onAlternated(ItemData items, int alternate, Item LH, Item RH) {}
}

View File

@ -0,0 +1,161 @@
package com.riiablo.save;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.IntArray;
import com.badlogic.gdx.utils.IntIntMap;
import com.riiablo.Riiablo;
import com.riiablo.codec.excel.SetItems;
import com.riiablo.item.BodyLoc;
import com.riiablo.item.Item;
import com.riiablo.item.Location;
import com.riiablo.item.Quality;
import com.riiablo.item.StoreLoc;
import com.riiablo.util.EnumIntMap;
public class ItemData {
public static final int INVALID_ITEM = -1;
final Array<Item> itemData = new Array<>(Item.class);
int cursor;
final EnumIntMap<BodyLoc> equipped = new EnumIntMap<>(BodyLoc.class, INVALID_ITEM);
final Array<EquipListener> equipListeners = new Array<>(false, 16);
final IntIntMap equippedSets = new IntIntMap(); // Indexed using set id
final IntIntMap setItemsOwned = new IntIntMap(); // Indexed using set item id
public void clear() {
cursor = INVALID_ITEM;
itemData.clear();
equipped.clear();
equipListeners.clear();
equippedSets.clear();
setItemsOwned.clear();
}
void preprocessItems() {
cursor = ItemData.INVALID_ITEM;
Item[] items = itemData.items;
for (int i = 0, s = itemData.size; i < s; i++) {
Item item = items[i];
switch (item.location) {
case EQUIPPED:
equipped.put(item.bodyLoc, i);
break;
case BELT:
item.gridY = (byte) -(item.gridX >>> 2);
item.gridX &= 0x3;
break;
case CURSOR:
assert cursor == INVALID_ITEM : "Only one item should be marked as cursor";
cursor = i;
break;
case STORED:
case UNK3:
case UNK5:
case SOCKET:
default:
}
if (item.quality == Quality.SET) setItemsOwned.getAndIncrement(item.qualityId, 0, 1);
}
}
public Item getItem(int i) {
return itemData.get(i);
}
public Item getCursor() {
return cursor == INVALID_ITEM ? null : getItem(cursor);
}
public Item getSlot(BodyLoc bodyLoc) {
int i = equipped.get(bodyLoc);
return i == ItemData.INVALID_ITEM ? null : getItem(i);
}
public Item getEquipped(BodyLoc bodyLoc, int alternate) {
return getSlot(BodyLoc.getAlternate(bodyLoc, alternate));
}
public int add(Item item) {
int i = itemData.size;
itemData.add(item);
if (item.quality == Quality.SET) setItemsOwned.getAndIncrement(item.qualityId, 0, 1);
return i;
}
public Item remove(int i) {
Item item = getItem(i);
itemData.removeIndex(i);
int[] vals = equipped.values();
for (int j = 0, s = vals.length; j < s; j++) if (vals[j] > i) vals[j]--;
if (item.quality == Quality.SET) setItemsOwned.getAndIncrement(item.qualityId, 0, -1);
return item;
}
public void addAll(Array<? extends Item> items) {
itemData.addAll(items);
}
public IntArray getLocation(Location location) {
return getLocation(location, StoreLoc.NONE);
}
public IntArray getLocation(Location location, StoreLoc storeLoc) {
Item[] items = itemData.items;
IntArray copy = new IntArray(items.length);
for (int i = 0, s = itemData.size; i < s; i++) {
Item item = items[i];
if (item.location != location) continue;
if (location == Location.STORED && item.storeLoc != storeLoc) continue;
copy.add(i);
}
return copy;
}
public IntArray getStore(StoreLoc storeLoc) {
return getLocation(Location.STORED, storeLoc);
}
// public Item equip(BodyLoc bodyLoc, Item item) {
// Item oldItem = equipped.put(bodyLoc, item);
//// if (item != null) item.update(this);
// updateSets(oldItem, item);
// updateStats();
// notifyEquipmentChanged(bodyLoc, oldItem, item);
// return oldItem;
// }
private void updateSets(Item oldItem, Item item) {
if (oldItem != null && oldItem.quality == Quality.SET) {
SetItems.Entry setItem = (SetItems.Entry) oldItem.qualityData;
int id = Riiablo.files.Sets.index(setItem.set);
equippedSets.getAndIncrement(id, 0, -1);
}
if (item != null && item.quality == Quality.SET) {
SetItems.Entry setItem = (SetItems.Entry) item.qualityData;
int id = Riiablo.files.Sets.index(setItem.set);
equippedSets.getAndIncrement(id, 0, 1);
}
}
public boolean addEquipmentListener(EquipListener l) {
equipListeners.add(l);
return true;
}
void notifyEquipmentChanged(BodyLoc bodyLoc, Item oldItem, Item item) {
for (EquipListener l : equipListeners) l.onChanged(this, bodyLoc, oldItem, item);
}
void notifyEquipmentAlternated(int alternate, Item LH, Item RH) {
for (EquipListener l : equipListeners) l.onAlternated(this, alternate, LH, RH);
}
public interface EquipListener {
void onChanged(ItemData items, BodyLoc bodyLoc, Item oldItem, Item item);
void onAlternated(ItemData items, int alternate, Item LH, Item RH);
}
}

View File

@ -0,0 +1,148 @@
package com.riiablo.util;
import com.badlogic.gdx.utils.Collections;
import com.badlogic.gdx.utils.GdxRuntimeException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.NoSuchElementException;
public class EnumIntMap<K extends Enum<K>> implements Iterable<EnumIntMap.Entry<K>> {
private final Class<K> keyType;
private final int defaultValue;
private K[] keyUniverse;
private int[] vals;
private Entries<K> entries1, entries2;
public EnumIntMap(Class<K> keyType, int defaultValue) {
this.keyType = keyType;
this.defaultValue = defaultValue;
keyUniverse = keyType.getEnumConstants();
vals = new int[keyUniverse.length];
Arrays.fill(vals, defaultValue);
}
public void clear() {
Arrays.fill(vals, defaultValue);
}
public int get(K key) {
return vals[key.ordinal()];
}
public int remove(K key) {
return put(key, defaultValue);
}
public int put(K key, int value) {
int i = key.ordinal();
int val = vals[i];
vals[i] = value;
return val;
}
public int[] values() {
return vals;
}
@Override
public Entries<K> iterator() {
return entries();
}
public Entries<K> entries () {
if (Collections.allocateIterators) return new Entries<>(this);
if (entries1 == null) {
entries1 = new Entries<>(this);
entries2 = new Entries<>(this);
}
if (!entries1.valid) {
entries1.reset();
entries1.valid = true;
entries2.valid = false;
return entries1;
}
entries2.reset();
entries2.valid = true;
entries1.valid = false;
return entries2;
}
public static class Entry<K extends Enum<K>> {
public K key;
public int value;
public String toString () {
return key + "=" + value;
}
}
private static class MapIterator<K extends Enum<K>> {
public boolean hasNext;
final EnumIntMap<K> map;
int nextIndex, currentIndex;
boolean valid = true;
public MapIterator(EnumIntMap<K> map) {
this.map = map;
reset();
}
public void reset() {
currentIndex = -1;
nextIndex = -1;
findNextIndex();
}
void findNextIndex() {
hasNext = false;
int[] vals = map.vals;
int defaultValue = map.defaultValue;
for (int n = map.keyUniverse.length; ++nextIndex < n;) {
if (vals[nextIndex] != defaultValue) {
hasNext = true;
break;
}
}
}
public void remove() {
throw new UnsupportedOperationException("#remove() not supported.");
}
}
public static class Entries<K extends Enum<K>> extends EnumIntMap.MapIterator<K> implements Iterable<Entry<K>>, Iterator<Entry<K>> {
private Entry<K> entry = new Entry<>();
public Entries(EnumIntMap<K> map) {
super(map);
}
/** Note the same entry instance is returned each time this method is called. */
public Entry<K> next() {
if (!hasNext) throw new NoSuchElementException();
if (!valid) throw new GdxRuntimeException("#iterator() cannot be used nested.");
entry.key = map.keyUniverse[nextIndex];
entry.value = map.vals[nextIndex];
currentIndex = nextIndex;
findNextIndex();
return entry;
}
public boolean hasNext() {
if (!valid) throw new GdxRuntimeException("#iterator() cannot be used nested.");
return hasNext;
}
public Entries<K> iterator() {
return this;
}
public void remove() {
super.remove();
}
}
}