Skip to content

Commit

Permalink
Look up ITEMTYPE.2DA for item categories in EE games
Browse files Browse the repository at this point in the history
- use specialized data type to display item categories
- use ITEMTYPE.2DA definitions for item category list in EE games
- add associated slot type to category label if available
- minor improvements to symbol lookup in IdsMapEntry class
  • Loading branch information
Argent77 committed Jul 30, 2024
1 parent 7bf073f commit f18ceab
Show file tree
Hide file tree
Showing 9 changed files with 212 additions and 47 deletions.
139 changes: 139 additions & 0 deletions src/org/infinity/datatype/ItemTypeBitmap.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Near Infinity - An Infinity Engine Browser and Editor
// Copyright (C) 2001 Jon Olav Hauglid
// See LICENSE.txt for license information

package org.infinity.datatype;

import java.nio.ByteBuffer;
import java.util.Locale;
import java.util.TreeMap;

import org.infinity.resource.Profile;
import org.infinity.resource.ResourceFactory;
import org.infinity.util.IdsMap;
import org.infinity.util.IdsMapCache;
import org.infinity.util.IdsMapEntry;
import org.infinity.util.Misc;
import org.infinity.util.Table2da;
import org.infinity.util.Table2daCache;

/**
* Specialized {@link HashBitmap} that uses a mix of hardcoded entries and custom entries from ITEMTYPE.2DA
* if available.
*/
public class ItemTypeBitmap extends HashBitmap {
private static final String TABLE_NAME = "ITEMTYPE.2DA";

public static final String[] CATEGORIES_ARRAY = { "Miscellaneous", "Amulets and necklaces", "Armor",
"Belts and girdles", "Boots", "Arrows", "Bracers and gauntlets", "Headgear", "Keys", "Potions", "Rings",
"Scrolls", "Shields", "Food", "Bullets", "Bows", "Daggers", "Maces", "Slings", "Small swords", "Large swords",
"Hammers", "Morning stars", "Flails", "Darts", "Axes", "Quarterstaves", "Crossbows", "Hand-to-hand weapons",
"Spears", "Halberds", "Bolts", "Cloaks and robes", "Gold pieces", "Gems", "Wands", "Containers", "Books",
"Familiars", "Tattoos", "Lenses", "Bucklers", "Candles", "Child bodies", "Clubs", "Female bodies", "Keys (old)",
"Large shields", "Male bodies", "Medium shields", "Notes", "Rods", "Skulls", "Small shields", "Spider bodies",
"Telescopes", "Bottles", "Greatswords", "Bags", "Furs and pelts", "Leather armor", "Studded leather",
"Chain mail", "Splint mail", "Plate mail", "Full plate", "Hide armor", "Robes", "Scale mail", "Bastard swords",
"Scarves", "Rations", "Hats", "Gloves", "Eyeballs", "Earrings", "Teeth", "Bracelets" };

public static final String[] CATEGORIES11_ARRAY = { "Miscellaneous", "Amulets and necklaces", "Armor",
"Belts and girdles", "Boots", "Arrows", "Bracers and gauntlets", "Headgear", "Keys", "Potions", "Rings",
"Scrolls", "Shields", "Spells", "Bullets", "Bows", "Daggers", "Maces", "Slings", "Small swords", "Large swords",
"Hammers", "Morning stars", "Flails", "Darts", "Axes", "Quarterstaves", "Crossbows", "Hand-to-hand weapons",
"Greatswords", "Halberds", "Bolts", "Cloaks and robes", "Copper commons", "Gems", "Wands", "Eyeballs",
"Bracelets", "Earrings", "Tattoos", "Lenses", "Teeth" };

private static TreeMap<Long, String> CATEGORIES = null;

public ItemTypeBitmap(ByteBuffer buffer, int offset, int length, String name) {
super(buffer, offset, length, name, getItemCategories(), false, false);
}

/**
* Returns a list of available item categories. List entries depend on the detected game and may include
* static and dynamic elements.
*
* @return Array of strings with item categories.
*/
public static TreeMap<Long, String> getItemCategories() {
synchronized (TABLE_NAME) {
if (Profile.isEnhancedEdition() && !Table2daCache.isCached(TABLE_NAME)) {
CATEGORIES = null;
}
if (CATEGORIES == null) {
CATEGORIES = buildCategories();
}
}
return CATEGORIES;
}

/** Rebuilds the list of item categories. */
private static TreeMap<Long, String> buildCategories() {
final TreeMap<Long, String> retVal = new TreeMap<>();
if (Profile.isEnhancedEdition() && ResourceFactory.resourceExists(TABLE_NAME)) {
final IdsMap slots = IdsMapCache.get("SLOTS.IDS");
final Table2da table = Table2daCache.get(TABLE_NAME);
final String baseName = "Extra category ";
int baseIndex = 1;
for (int row = 0, count = table.getRowCount(); row < count; row++) {
final String idxValue = table.get(row, 0);
final int radix = idxValue.startsWith("0x") ? 16 : 10;
String catName = "";
try {
int idx = Integer.parseInt(idxValue, radix);
if (idx >= 0 && idx < CATEGORIES_ARRAY.length) {
// looking up hardcoded category name
catName = CATEGORIES_ARRAY[idx];
} else {
// generating custom category name
catName = baseName + baseIndex;
baseIndex++;
}

// adding slot restriction if available
int slot = Misc.toNumber(table.get(row, 3), -1);
if (slot >= 0) {
final IdsMapEntry slotEntry = slots.get(slot);
if (slotEntry != null) {
final String slotName = beautifyString(slotEntry.getSymbol(), "SLOT");
if (slotName != null && !slotName.isEmpty()) {
catName = catName + " [" + slotName + " slot" + "]";
}
}
}

retVal.put((long) idx, catName);
} catch (NumberFormatException e) {
// skip entry with log message
System.err.printf("%s: Invalid index at row=%d (value=%s)\n", TABLE_NAME, row, idxValue);
}
}
} else if (Profile.getEngine() == Profile.Engine.PST) {
// PST
for (long idx = 0, count = CATEGORIES11_ARRAY.length; idx < count; idx++) {
retVal.put(idx, CATEGORIES11_ARRAY[(int) idx]);
}
} else {
// Any non-EE games except PST
for (long idx = 0, count = CATEGORIES_ARRAY.length; idx < count; idx++) {
retVal.put(idx, CATEGORIES_ARRAY[(int) idx]);
}
}

return retVal;
}

private static String beautifyString(String s, String removedPrefix) {
String retVal = s;
if (retVal != null) {
retVal = retVal.replaceFirst(removedPrefix + "_?", "");
final String[] words = retVal.split("[ _]+");
for (int i = 0; i < words.length; i++) {
words[i] = words[i].charAt(0) + words[i].substring(1).toLowerCase(Locale.ENGLISH);
}
retVal = String.join(" ", words);
retVal = retVal.replaceFirst("(\\D)(\\d)", "$1 $2");
retVal = retVal.trim();
}
return retVal;
}
}
6 changes: 3 additions & 3 deletions src/org/infinity/resource/effects/Opcode181.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@

import org.infinity.datatype.Bitmap;
import org.infinity.datatype.Datatype;
import org.infinity.datatype.ItemTypeBitmap;
import org.infinity.datatype.StringRef;
import org.infinity.resource.Profile;
import org.infinity.resource.StructEntry;
import org.infinity.resource.itm.ItmResource;

/**
* Implemention of opcode 181.
Expand Down Expand Up @@ -41,14 +41,14 @@ public Opcode181() {
protected String makeEffectParamsGeneric(Datatype parent, ByteBuffer buffer, int offset, List<StructEntry> list,
boolean isVersion1) {
list.add(new StringRef(buffer, offset, EFFECT_DESC_NOTE));
list.add(new Bitmap(buffer, offset + 4, 4, EFFECT_ITEM_TYPE, ItmResource.CATEGORIES_ARRAY));
list.add(new ItemTypeBitmap(buffer, offset + 4, 4, EFFECT_ITEM_TYPE));
return null;
}

@Override
protected String makeEffectParamsEE(Datatype parent, ByteBuffer buffer, int offset, List<StructEntry> list,
boolean isVersion1) {
list.add(new Bitmap(buffer, offset, 4, EFFECT_ITEM_TYPE, ItmResource.CATEGORIES_ARRAY));
list.add(new ItemTypeBitmap(buffer, offset, 4, EFFECT_ITEM_TYPE));
list.add(new Bitmap(buffer, offset + 4, 4, EFFECT_RESTRICTION, RESTRICTION_TYPES_EE));
return null;
}
Expand Down
7 changes: 3 additions & 4 deletions src/org/infinity/resource/effects/Opcode183.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,13 @@
import java.nio.ByteBuffer;
import java.util.List;

import org.infinity.datatype.Bitmap;
import org.infinity.datatype.Datatype;
import org.infinity.datatype.DecNumber;
import org.infinity.datatype.ItemTypeBitmap;
import org.infinity.datatype.StringRef;
import org.infinity.resource.AbstractStruct;
import org.infinity.resource.Profile;
import org.infinity.resource.StructEntry;
import org.infinity.resource.itm.ItmResource;

/**
* Implemention of opcode 183.
Expand All @@ -41,15 +40,15 @@ public Opcode183() {
protected String makeEffectParamsGeneric(Datatype parent, ByteBuffer buffer, int offset, List<StructEntry> list,
boolean isVersion1) {
list.add(new DecNumber(buffer, offset, 4, AbstractStruct.COMMON_UNUSED));
list.add(new Bitmap(buffer, offset + 4, 4, EFFECT_ITEM_TYPE, ItmResource.CATEGORIES_ARRAY));
list.add(new ItemTypeBitmap(buffer, offset + 4, 4, EFFECT_ITEM_TYPE));
return RES_TYPE;
}

@Override
protected String makeEffectParamsIWD2(Datatype parent, ByteBuffer buffer, int offset, List<StructEntry> list,
boolean isVersion1) {
list.add(new StringRef(buffer, offset, EFFECT_STRING));
list.add(new Bitmap(buffer, offset + 4, 4, EFFECT_ITEM_TYPE, ItmResource.CATEGORIES_ARRAY));
list.add(new ItemTypeBitmap(buffer, offset + 4, 4, EFFECT_ITEM_TYPE));
return RES_TYPE;
}

Expand Down
26 changes: 4 additions & 22 deletions src/org/infinity/resource/itm/ItmResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@
import javax.swing.JComponent;
import javax.swing.JScrollPane;

import org.infinity.datatype.Bitmap;
import org.infinity.datatype.ColorValue;
import org.infinity.datatype.DecNumber;
import org.infinity.datatype.Flag;
import org.infinity.datatype.IdsBitmap;
import org.infinity.datatype.IsNumeric;
import org.infinity.datatype.ItemTypeBitmap;
import org.infinity.datatype.ResourceRef;
import org.infinity.datatype.SectionCount;
import org.infinity.datatype.SectionOffset;
Expand Down Expand Up @@ -111,24 +111,6 @@ public final class ItmResource extends AbstractStruct implements Resource, HasCh
public static final String ITM_SPEAKER_NAME = "Speaker name";
public static final String ITM_WEAPON_COLOR = "Weapon color";

public static final String[] CATEGORIES_ARRAY = { "Miscellaneous", "Amulets and necklaces", "Armor",
"Belts and girdles", "Boots", "Arrows", "Bracers and gauntlets", "Headgear", "Keys", "Potions", "Rings",
"Scrolls", "Shields", "Food", "Bullets", "Bows", "Daggers", "Maces", "Slings", "Small swords", "Large swords",
"Hammers", "Morning stars", "Flails", "Darts", "Axes", "Quarterstaves", "Crossbows", "Hand-to-hand weapons",
"Spears", "Halberds", "Bolts", "Cloaks and robes", "Gold pieces", "Gems", "Wands", "Containers", "Books",
"Familiars", "Tattoos", "Lenses", "Bucklers", "Candles", "Child bodies", "Clubs", "Female bodies", "Keys (old)",
"Large shields", "Male bodies", "Medium shields", "Notes", "Rods", "Skulls", "Small shields", "Spider bodies",
"Telescopes", "Bottles", "Greatswords", "Bags", "Furs and pelts", "Leather armor", "Studded leather",
"Chain mail", "Splint mail", "Plate mail", "Full plate", "Hide armor", "Robes", "Scale mail", "Bastard swords",
"Scarves", "Rations", "Hats", "Gloves", "Eyeballs", "Earrings", "Teeth", "Bracelets" };

public static final String[] CATEGORIES11_ARRAY = { "Miscellaneous", "Amulets and necklaces", "Armor",
"Belts and girdles", "Boots", "Arrows", "Bracers and gauntlets", "Headgear", "Keys", "Potions", "Rings",
"Scrolls", "Shields", "Spells", "Bullets", "Bows", "Daggers", "Maces", "Slings", "Small swords", "Large swords",
"Hammers", "Morning stars", "Flails", "Darts", "Axes", "Quarterstaves", "Crossbows", "Hand-to-hand weapons",
"Greatswords", "Halberds", "Bolts", "Cloaks and robes", "Copper commons", "Gems", "Wands", "Eyeballs",
"Bracelets", "Earrings", "Tattoos", "Lenses", "Teeth" };

public static final String[] FLAGS_ARRAY = { "None", "Critical item", "Two-handed", "Droppable", "Displayable",
"Cursed", "Not copyable", "Magical", "Left-handed", "Silver", "Cold iron", "Off-handed", "Conversable",
"EE: Fake two-handed", "EE: Forbid off-hand weapon", "", "EE: Adamantine", null, null, null, null, null, null,
Expand Down Expand Up @@ -352,18 +334,18 @@ public int read(ByteBuffer buffer, int offset) throws Exception {
addField(new ResourceRef(buffer, 16, ITM_DROP_SOUND, "WAV"));
if (Profile.getGame() == Profile.Game.PSTEE) {
addField(new Flag(buffer, 24, 4, ITM_FLAGS, FLAGS_PSTEE_ARRAY));
addField(new Bitmap(buffer, 28, 2, ITM_CATEGORY, CATEGORIES_ARRAY));
addField(new ItemTypeBitmap(buffer, 28, 2, ITM_CATEGORY));
} else {
addField(new Flag(buffer, 24, 4, ITM_FLAGS, FLAGS11_ARRAY));
addField(new Bitmap(buffer, 28, 2, ITM_CATEGORY, CATEGORIES11_ARRAY));
addField(new ItemTypeBitmap(buffer, 28, 2, ITM_CATEGORY));
}
addField(new Flag(buffer, 30, 4, ITM_UNUSABLE_BY, USABILITY11_ARRAY));
addField(new TextBitmap(buffer, 34, 2, ITM_EQUIPPED_APPEARANCE, Profile.getEquippedAppearanceMap()));
} else {
addField(new ResourceRef(buffer, 16, ITM_USED_UP_ITEM, "ITM"));
addField(
new Flag(buffer, 24, 4, ITM_FLAGS, IdsMapCache.getUpdatedIdsFlags(FLAGS_ARRAY, "ITEMFLAG.IDS", 4, false, false)));
addField(new Bitmap(buffer, 28, 2, ITM_CATEGORY, CATEGORIES_ARRAY));
addField(new ItemTypeBitmap(buffer, 28, 2, ITM_CATEGORY));
if (isV20) {
addField(new Flag(buffer, 30, 4, ITM_UNUSABLE_BY, USABILITY20_ARRAY));
} else {
Expand Down
12 changes: 4 additions & 8 deletions src/org/infinity/resource/sto/Purchases.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,20 @@

import java.nio.ByteBuffer;

import org.infinity.datatype.Bitmap;
import org.infinity.datatype.ItemTypeBitmap;
import org.infinity.resource.AddRemovable;
import org.infinity.resource.Profile;
import org.infinity.resource.itm.ItmResource;
import org.infinity.util.io.StreamUtils;

public final class Purchases extends Bitmap implements AddRemovable {
public final class Purchases extends ItemTypeBitmap implements AddRemovable {
// STO/Purchases-specific field labels
public static final String STO_PURCHASES = "Store purchases";

Purchases() {
super(StreamUtils.getByteBuffer(4), 0, 4, STO_PURCHASES,
(Profile.getEngine() == Profile.Engine.PST) ? ItmResource.CATEGORIES11_ARRAY : ItmResource.CATEGORIES_ARRAY);
super(StreamUtils.getByteBuffer(4), 0, 4, STO_PURCHASES);
}

Purchases(ByteBuffer buffer, int offset, int number) {
super(buffer, offset, 4, STO_PURCHASES + " " + number,
(Profile.getEngine() == Profile.Engine.PST) ? ItmResource.CATEGORIES11_ARRAY : ItmResource.CATEGORIES_ARRAY);
super(buffer, offset, 4, STO_PURCHASES + " " + number);
}

// --------------------- Begin Interface AddRemovable ---------------------
Expand Down
12 changes: 7 additions & 5 deletions src/org/infinity/search/SearchResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
import org.infinity.NearInfinity;
import org.infinity.datatype.IdsBitmap;
import org.infinity.datatype.IsNumeric;
import org.infinity.datatype.ItemTypeBitmap;
import org.infinity.datatype.KitIdsBitmap;
import org.infinity.datatype.PriTypeBitmap;
import org.infinity.datatype.ProRef;
Expand Down Expand Up @@ -2030,17 +2031,17 @@ private void init() {
String[] sCat;
if ((Boolean) Profile.getProperty(Profile.Key.IS_SUPPORTED_ITM_V11)) {
sFlags = ItmResource.FLAGS11_ARRAY;
sCat = ItmResource.CATEGORIES11_ARRAY;
sCat = ItemTypeBitmap.CATEGORIES11_ARRAY;
} else if ((Boolean) Profile.getProperty(Profile.Key.IS_SUPPORTED_ITM_V20)) {
sFlags = ItmResource.FLAGS_ARRAY;
sCat = ItmResource.CATEGORIES_ARRAY;
sCat = ItemTypeBitmap.CATEGORIES_ARRAY;
} else {
if (Profile.getGame() == Profile.Game.PSTEE) {
sFlags = ItmResource.FLAGS_PSTEE_ARRAY;
} else {
sFlags = ItmResource.FLAGS_ARRAY;
}
sCat = ItmResource.CATEGORIES_ARRAY;
sCat = ItemTypeBitmap.CATEGORIES_ARRAY;
}

pFlags = new FlagsPanel(4, sFlags);
Expand Down Expand Up @@ -5425,8 +5426,9 @@ private void init() {
cbLabel[i] = new JCheckBox(String.format("Category %d:", i + 1));
cbLabel[i].addActionListener(this);

String[] cat = ((Boolean) Profile.getProperty(Profile.Key.IS_SUPPORTED_STO_V11)) ? ItmResource.CATEGORIES11_ARRAY
: ItmResource.CATEGORIES_ARRAY;
String[] cat = ((Boolean) Profile.getProperty(Profile.Key.IS_SUPPORTED_STO_V11))
? ItemTypeBitmap.CATEGORIES11_ARRAY
: ItemTypeBitmap.CATEGORIES_ARRAY;
cbCategory[i] = new AutoComboBox<>(IndexedString.createArray(cat, 0, 0));
}

Expand Down
33 changes: 30 additions & 3 deletions src/org/infinity/util/IdsMapEntry.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,36 @@ public int getNumSymbols() {
return symbols.size();
}

/** Returns the most recently added symbolic name. */
/** Returns the first available symbolic name. */
public String getSymbol() {
return symbols.peek();
return symbols.peekFirst();
}

/** Returns the most recently added symbolic name. */
public String getLastSymbol() {
return symbols.peekLast();
}

/**
* Returns the symbolic name at the specified index. Call {@link #getNumSymbols()} to get the number of available
* symbolic names.
*
* @param index Index of the symbolic name.
* @return Symbolic name as string.
* @throws IndexOutOfBoundsException if {@code index} is out of bounds.
*/
public String getSymbol(int index) throws IndexOutOfBoundsException {
if (index < 0 || index >= symbols.size()) {
throw new IndexOutOfBoundsException("Index out of bounds: " + index);
}

final Iterator<String> iter = symbols.iterator();
String retVal = null;
while (index >= 0) {
retVal = iter.next();
index--;
}
return retVal;
}

/** Returns an iterator over the whole collection of available symbols. */
Expand All @@ -51,7 +78,7 @@ public void addSymbol(String symbol) {
}

if (!symbol.isEmpty() && !symbols.contains(symbol)) {
symbols.push(symbol);
symbols.add(symbol);
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/org/infinity/util/Table2da.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public void reload() {
init(entry);
}

/** Returns total number of columns, including header column. */
/** Returns total number of data columns. */
public int getColCount() {
return table.isEmpty() ? header.size() : columnCount;
}
Expand All @@ -66,7 +66,7 @@ public int getColCount(int row) {
return 0;
}

/** Returns number of rows, including header row. */
/** Returns number of data rows. */
public int getRowCount() {
return table.size();
}
Expand Down
Loading

0 comments on commit f18ceab

Please sign in to comment.