From d64b4186a31fc067f9ac8f350c31c9305c0f62c7 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sat, 23 Jan 2021 15:11:14 +0100 Subject: [PATCH 001/158] Mark several SPL fields as unused by the game engine --- src/org/infinity/resource/AbstractStruct.java | 1 + src/org/infinity/resource/spl/Ability.java | 21 ++++++++++++------- .../infinity/resource/spl/SplResource.java | 4 ++-- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/org/infinity/resource/AbstractStruct.java b/src/org/infinity/resource/AbstractStruct.java index ebeb9a367..681a11434 100644 --- a/src/org/infinity/resource/AbstractStruct.java +++ b/src/org/infinity/resource/AbstractStruct.java @@ -52,6 +52,7 @@ public abstract class AbstractStruct extends AbstractTableModel implements Struc public static final String COMMON_UNKNOWN = "Unknown"; public static final String COMMON_UNUSED = "Unused"; public static final String COMMON_UNUSED_BYTES = "Unused bytes?"; + public static final String SUFFIX_UNUSED = " (unused)"; // Commonly used string arrays public static final String[] OPTION_NOYES = {"No", "Yes"}; diff --git a/src/org/infinity/resource/spl/Ability.java b/src/org/infinity/resource/spl/Ability.java index 7a3977684..795a245d8 100644 --- a/src/org/infinity/resource/spl/Ability.java +++ b/src/org/infinity/resource/spl/Ability.java @@ -32,6 +32,13 @@ public final class Ability extends AbstractAbility implements AddRemovable, HasC public static final String SPL_ABIL = "Spell ability"; public static final String SPL_ABIL_MIN_LEVEL = "Minimum level"; public static final String SPL_ABIL_CASTING_SPEED = "Casting speed"; + public static final String SPL_ABIL_TIMES_PER_DAY = "Times per day"; + public static final String SPL_ABIL_DICE_SIZE = AbstractAbility.ABILITY_DICE_SIZE + SUFFIX_UNUSED; + public static final String SPL_ABIL_DICE_COUNT = AbstractAbility.ABILITY_DICE_COUNT + SUFFIX_UNUSED; + public static final String SPL_ABIL_DAMAGE_BONUS = AbstractAbility.ABILITY_DAMAGE_BONUS + SUFFIX_UNUSED; + public static final String SPL_ABIL_DAMAGE_TYPE = AbstractAbility.ABILITY_DAMAGE_TYPE + SUFFIX_UNUSED; + public static final String SPL_ABIL_NUM_CHARGES = AbstractAbility.ABILITY_NUM_CHARGES + SUFFIX_UNUSED; + public static final String SPL_ABIL_WHEN_DRAINED = AbstractAbility.ABILITY_WHEN_DRAINED + SUFFIX_UNUSED; Ability() throws Exception { @@ -104,15 +111,15 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new DecNumber(buffer, offset + 14, 2, ABILITY_RANGE)); addField(new DecNumber(buffer, offset + 16, 2, SPL_ABIL_MIN_LEVEL)); addField(new DecNumber(buffer, offset + 18, 2, SPL_ABIL_CASTING_SPEED)); - addField(new DecNumber(buffer, offset + 20, 2, ABILITY_HIT_BONUS)); - addField(new DecNumber(buffer, offset + 22, 2, ABILITY_DICE_SIZE)); - addField(new DecNumber(buffer, offset + 24, 2, ABILITY_DICE_COUNT)); - addField(new DecNumber(buffer, offset + 26, 2, ABILITY_DAMAGE_BONUS)); - addField(new Bitmap(buffer, offset + 28, 2, ABILITY_DAMAGE_TYPE, s_dmgtype)); + addField(new DecNumber(buffer, offset + 20, 2, SPL_ABIL_TIMES_PER_DAY)); + addField(new DecNumber(buffer, offset + 22, 2, SPL_ABIL_DICE_SIZE)); + addField(new DecNumber(buffer, offset + 24, 2, SPL_ABIL_DICE_COUNT)); + addField(new DecNumber(buffer, offset + 26, 2, SPL_ABIL_DAMAGE_BONUS)); + addField(new DecNumber(buffer, offset + 28, 2, SPL_ABIL_DAMAGE_TYPE)); addField(new SectionCount(buffer, offset + 30, 2, ABILITY_NUM_EFFECTS, Effect.class)); addField(new DecNumber(buffer, offset + 32, 2, ABILITY_FIRST_EFFECT_INDEX)); - addField(new DecNumber(buffer, offset + 34, 2, ABILITY_NUM_CHARGES)); - addField(new Bitmap(buffer, offset + 36, 2, ABILITY_WHEN_DRAINED, s_drain)); + addField(new DecNumber(buffer, offset + 34, 2, SPL_ABIL_NUM_CHARGES)); + addField(new DecNumber(buffer, offset + 36, 2, SPL_ABIL_WHEN_DRAINED)); if (ResourceFactory.resourceExists("PROJECTL.IDS")) { addField(new ProRef(buffer, offset + 38, ABILITY_PROJECTILE)); } else if (Profile.getEngine() == Profile.Engine.PST) { diff --git a/src/org/infinity/resource/spl/SplResource.java b/src/org/infinity/resource/spl/SplResource.java index 5e20b4136..cc368fc3c 100644 --- a/src/org/infinity/resource/spl/SplResource.java +++ b/src/org/infinity/resource/spl/SplResource.java @@ -67,7 +67,7 @@ public final class SplResource extends AbstractStruct implements Resource, HasCh { // SPL-specific field labels public static final String SPL_NAME = "Spell name"; - public static final String SPL_NAME_IDENTIFIED = org.infinity.resource.itm.ItmResource.ITM_NAME_IDENTIFIED; + public static final String SPL_NAME_IDENTIFIED = org.infinity.resource.itm.ItmResource.ITM_NAME_IDENTIFIED + SUFFIX_UNUSED; public static final String SPL_CASTING_SOUND = "Casting sound"; public static final String SPL_FLAGS = "Flags"; public static final String SPL_TYPE = "Spell type"; @@ -80,7 +80,7 @@ public final class SplResource extends AbstractStruct implements Resource, HasCh public static final String SPL_ICON = "Spell icon"; public static final String SPL_ICON_GROUND = "Ground icon"; public static final String SPL_DESCRIPTION = "Spell description"; - public static final String SPL_DESCRIPTION_IDENTIFIED = org.infinity.resource.itm.ItmResource.ITM_DESCRIPTION_IDENTIFIED; + public static final String SPL_DESCRIPTION_IDENTIFIED = org.infinity.resource.itm.ItmResource.ITM_DESCRIPTION_IDENTIFIED + SUFFIX_UNUSED; public static final String SPL_DESCRIPTION_IMAGE = org.infinity.resource.itm.ItmResource.ITM_DESCRIPTION_IMAGE; public static final String SPL_OFFSET_ABILITIES = org.infinity.resource.itm.ItmResource.ITM_OFFSET_ABILITIES; public static final String SPL_NUM_ABILITIES = org.infinity.resource.itm.ItmResource.ITM_NUM_ABILITIES; From cd75ee3fb8329dba3bb44005e414a3f42f64d063 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sat, 23 Jan 2021 19:58:22 +0100 Subject: [PATCH 002/158] Improve effect opcode 12 special flags descriptions --- src/org/infinity/resource/EffectFactory.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/org/infinity/resource/EffectFactory.java b/src/org/infinity/resource/EffectFactory.java index 30925d30c..7ee1b8fb5 100644 --- a/src/org/infinity/resource/EffectFactory.java +++ b/src/org/infinity/resource/EffectFactory.java @@ -5539,12 +5539,17 @@ private int makeEffectParam25(Datatype parent, ByteBuffer buffer, int offset, Li boolean isEEex = Profile.getProperty(Profile.Key.IS_GAME_EEEX); switch (effectType) { case 12: // Damage - s.add(new Flag(buffer, offset, 4, EFFECT_SPECIAL, new String[] { - "Default", "Drain HP to caster", "Transfer HP to target", - "Fist damage only", "Drain HP to caster (non-cumulative)", null, - "Suppress damage feedback", "Drain HP (limited to cur. HP of target)", - "Drain HP (limited to max. HP of caster)", "Save for half", - "Made save", "Does not wake sleepers"})); + s.add(new Flag(buffer, offset, 4, "Flags", new String[] { + "Default", + "Transfer HP to caster (cumulative)*;Bits 0, 1, 3 and 4 are mutually exclusive. Cumulative temporary extra HP.", + "Transfer HP to target (cumulative)*;Bits 0, 1, 3 and 4 are mutually exclusive. Cumulative temporary extra HP.", + "Fist damage only", + "Transfer HP to caster (non-cumulative)*;Bits 0, 1, 3 and 4 are mutually exclusive. Non-cumulative temporary extra HP.", + "Transfer HP to target (non-cumulative)*;Bits 0, 1, 3 and 4 are mutually exclusive. Non-cumulative temporary extra HP.", + "Suppress damage feedback", + "Limit to cur. HP of target minus MINHP*;Bits 1 and 4 switch target -> caster.", + "Limit to cur./max. HP difference of caster*;Bits 1 and 4 switch caster -> target.", + "Save for half", "Made save", "Does not wake sleepers"})); break; case 18: // Maximum HP bonus From 344b0d79866e9d22379f9f2472fad617af286296 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sat, 23 Jan 2021 20:01:24 +0100 Subject: [PATCH 003/158] Update PRO area effect fields related to "Ray count" --- src/org/infinity/datatype/DecNumber.java | 7 ++- .../infinity/resource/pro/ProAreaType.java | 59 +++++++++++++++++-- 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/src/org/infinity/datatype/DecNumber.java b/src/org/infinity/datatype/DecNumber.java index 742ed46fa..dbcc6fa1e 100644 --- a/src/org/infinity/datatype/DecNumber.java +++ b/src/org/infinity/datatype/DecNumber.java @@ -45,7 +45,12 @@ protected DecNumber(ByteBuffer buffer, int offset, int length, String name, bool public boolean update(Object value) { try { - setValue(parseNumber(value, getSize(), signed, true)); + long oldVal = getLongValue(); + long newVal = parseNumber(value, getSize(), signed, true); + setValue(newVal); + if (oldVal != newVal) { + fireValueUpdated(new UpdateEvent(this, getParent())); + } return true; } catch (Exception e) { e.printStackTrace(); diff --git a/src/org/infinity/resource/pro/ProAreaType.java b/src/org/infinity/resource/pro/ProAreaType.java index 696cb8bcc..5a7d8435b 100644 --- a/src/org/infinity/resource/pro/ProAreaType.java +++ b/src/org/infinity/resource/pro/ProAreaType.java @@ -15,13 +15,16 @@ import org.infinity.datatype.ResourceRef; import org.infinity.datatype.Unknown; import org.infinity.datatype.UnsignDecNumber; +import org.infinity.datatype.UpdateEvent; +import org.infinity.datatype.UpdateListener; import org.infinity.resource.AbstractStruct; import org.infinity.resource.AddRemovable; import org.infinity.resource.Profile; +import org.infinity.resource.StructEntry; import org.infinity.util.LongIntegerHashMap; import org.infinity.util.io.StreamUtils; -public final class ProAreaType extends AbstractStruct implements AddRemovable +public final class ProAreaType extends AbstractStruct implements AddRemovable, UpdateListener { // PRO/Area-specific field labels public static final String PRO_AREA = "Area effect info"; @@ -39,6 +42,8 @@ public final class ProAreaType extends AbstractStruct implements AddRemovable public static final String PRO_AREA_EXPLOSION_PROJECTILE = "Explosion projectile"; public static final String PRO_AREA_EXPLOSION_ANIMATION = "Explosion animation"; public static final String PRO_AREA_CONE_WIDTH = "Cone width"; + public static final String PRO_AREA_ANGLE_BETWEEN_RAYS = "Angle between rays"; + public static final String PRO_AREA_ROTATE_RAYS = "Rotate rays"; public static final String PRO_AREA_SPREAD_ANIMATION = "Spread animation"; public static final String PRO_AREA_RING_ANIMATION = "Ring animation"; public static final String PRO_AREA_SOUND = "Area sound"; @@ -105,11 +110,49 @@ public boolean canRemove() } // + @Override + public boolean valueUpdated(UpdateEvent event) + { + boolean retVal = false; + if (event.getSource() instanceof DecNumber && ((DecNumber)event.getSource()).getName().equals(PRO_AREA_RAY_COUNT)) { + int rayCount = ((DecNumber)event.getSource()).getValue(); + AbstractStruct struct = event.getStructure(); + if (rayCount > 0) { + StructEntry field = struct.getField(null, 0x224); + if (field instanceof DecNumber && !field.getName().equals(PRO_AREA_ANGLE_BETWEEN_RAYS)) { + field.setName(PRO_AREA_ANGLE_BETWEEN_RAYS); + retVal = true; + } + field = struct.getField(null, 0x226); + if (field instanceof DecNumber && !field.getName().equals(PRO_AREA_ROTATE_RAYS)) { + field.setName(PRO_AREA_ROTATE_RAYS); + retVal = true; + } + } else { + StructEntry field = struct.getField(null, 0x224); + if (field instanceof DecNumber && !field.getName().equals(PRO_AREA_CONE_WIDTH)) { + field.setName(PRO_AREA_CONE_WIDTH); + retVal = true; + } + field = struct.getField(null, 0x226); + if (field instanceof DecNumber && !field.getName().equals(COMMON_UNUSED)) { + field.setName(COMMON_UNUSED); + retVal = true; + } + } + } + return retVal; + } + @Override public int read(ByteBuffer buffer, int offset) throws Exception { addField(new Flag(buffer, offset, 2, PRO_AREA_FLAGS, s_areaflags)); - addField(new DecNumber(buffer, offset + 2, 2, PRO_AREA_RAY_COUNT)); + DecNumber rayCount = new DecNumber(buffer, offset + 2, 2, PRO_AREA_RAY_COUNT); + if (Profile.isEnhancedEdition()) { + rayCount.addUpdateListener(this); + } + addField(rayCount); addField(new DecNumber(buffer, offset + 4, 2, PRO_AREA_TRAP_SIZE)); addField(new DecNumber(buffer, offset + 6, 2, PRO_AREA_EXPLOSION_SIZE)); addField(new ResourceRef(buffer, offset + 8, PRO_AREA_EXPLOSION_SOUND, "WAV")); @@ -122,9 +165,17 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new Unknown(buffer, offset + 25, 1, COMMON_UNUSED)); addField(new ProRef(buffer, offset + 26, PRO_AREA_EXPLOSION_PROJECTILE)); addField(new ResourceRef(buffer, offset + 28, PRO_AREA_EXPLOSION_ANIMATION, "VEF", "VVC", "BAM")); - addField(new DecNumber(buffer, offset + 36, 2, PRO_AREA_CONE_WIDTH)); + if (Profile.isEnhancedEdition() && rayCount.getValue() > 0) { + addField(new DecNumber(buffer, offset + 36, 2, PRO_AREA_ANGLE_BETWEEN_RAYS)); + } else { + addField(new DecNumber(buffer, offset + 36, 2, PRO_AREA_CONE_WIDTH)); + } if (Profile.isEnhancedEdition()) { - addField(new Unknown(buffer, offset + 38, 2)); + if (rayCount.getValue() > 0) { + addField(new DecNumber(buffer, offset + 38, 2, PRO_AREA_ROTATE_RAYS)); + } else { + addField(new DecNumber(buffer, offset + 38, 2, COMMON_UNUSED)); + } addField(new ResourceRef(buffer, offset + 40, PRO_AREA_SPREAD_ANIMATION, "BAM")); addField(new ResourceRef(buffer, offset + 48, PRO_AREA_RING_ANIMATION, "BAM")); addField(new ResourceRef(buffer, offset + 56, PRO_AREA_SOUND, "WAV")); From 68796e84427e090fd4d4912ea44a6e3c3c161c0f Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sat, 23 Jan 2021 23:06:32 +0100 Subject: [PATCH 004/158] Update SPL V2.0 flags --- src/org/infinity/resource/spl/SplResource.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/org/infinity/resource/spl/SplResource.java b/src/org/infinity/resource/spl/SplResource.java index cc368fc3c..33bd44a25 100644 --- a/src/org/infinity/resource/spl/SplResource.java +++ b/src/org/infinity/resource/spl/SplResource.java @@ -112,6 +112,11 @@ public final class SplResource extends AbstractStruct implements Resource, HasCh "Allow spotting", "Outdoors only", "Ignore dead/wild magic", "Ignore wild surge", "Non-combat ability", "", "", "", "", "", "", "", "EE/Ex: Can target invisible", "EE/Ex: Castable when silenced"}; + public static final String[] s_spellflag2 = {"No flags set", "", "", "", "", "", "", "", "", + "", "", "Hostile", "No LOS required", + "Allow spotting", "Outdoors only", "Simplified duration", + "Trigger/Contingency", "", "", "Non-combat ability (?)", "", "", "", + "", "", "", ""}; public static final String[] s_exclude = { "None", "Berserker", "Wizard slayer", "Kensai", "Cavalier", "Inquisitor", "Undead hunter", @@ -331,7 +336,11 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new StringRef(buffer, offset + 8, SPL_NAME)); addField(new StringRef(buffer, offset + 12, SPL_NAME_IDENTIFIED)); addField(new ResourceRef(buffer, offset + 16, SPL_CASTING_SOUND, "WAV")); - addField(new Flag(buffer, offset + 24, 4, SPL_FLAGS, s_spellflag)); + if (version.getText().equalsIgnoreCase("V2.0")) { + addField(new Flag(buffer, offset + 24, 4, SPL_FLAGS, s_spellflag2)); + } else { + addField(new Flag(buffer, offset + 24, 4, SPL_FLAGS, s_spellflag)); + } Bitmap spellType = new Bitmap(buffer, offset + 28, 2, SPL_TYPE, s_spelltype); // 0x1c spellType.addUpdateListener(this); addField(spellType); From a3fcabf1c571ea756c8a38586a146f07b2b87418 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sat, 23 Jan 2021 23:06:54 +0100 Subject: [PATCH 005/158] Fix ITM V2.0 kit usability flags --- .../infinity/resource/itm/ItmResource.java | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/org/infinity/resource/itm/ItmResource.java b/src/org/infinity/resource/itm/ItmResource.java index 176e74596..082d79ad3 100644 --- a/src/org/infinity/resource/itm/ItmResource.java +++ b/src/org/infinity/resource/itm/ItmResource.java @@ -189,7 +189,7 @@ public final class ItmResource extends AbstractStruct implements Resource, HasCh "Lawful;Includes Lawful Good, Lawful Neutral and Lawful Evil", "Neutral ...;Includes Neutral Good, True Neutral and Neutral Evil", null, null, null, null, null, "Elf", - "Dwarf", "Half-elf", "Halfling", "Human", "Gnome" + "Dwarf", "Half-elf", "Halfling", "Human", "Gnome", "Half-Orc" }; public static final String[] s_kituse1 = {"None", "Cleric of Talos", "Cleric of Helm", "Cleric of Lathander", @@ -203,6 +203,17 @@ public final class ItmResource extends AbstractStruct implements Resource, HasCh public static final String[] s_kituse4 = {"None", "Berserker", "Wizard slayer", "Kensai", "Cavalier", "Inquisitor", "Undead hunter", "Abjurer", "Conjurer"}; + public static final String[] s_kituse1_v2 = {"None"}; + public static final String[] s_kituse2_v2 = + {"None", "Cleric of Lathander", "Cleric of Selune", "Cleric of Helm", "Cleric of Oghma", + "Cleric of Tempus", "Cleric of Bane", "Cleric of Mask", "Cleric of Talos"}; + public static final String[] s_kituse3_v2 = + {"None", "Diviner", "Enchanter", "Illusionist", "Invoker", "Necromancer", "Transmuter", + "Generalist", "Cleric of Ilmater"}; + public static final String[] s_kituse4_v2 = + {"None", "Paladin of Ilmater", "Paladin of Helm", "Paladin of Mystra", + "Monk of the Old Order", "Monk of the Broken Ones", "Monk of the Dark Moon", + "Abjurer", "Conjurer"}; private StructHexViewer hexViewer; @@ -374,11 +385,13 @@ public int read(ByteBuffer buffer, int offset) throws Exception { addField(new TextString(buffer, 0, 4, COMMON_SIGNATURE)); TextString version = new TextString(buffer, 4, 4, COMMON_VERSION); + boolean isV10 = version.getText().equalsIgnoreCase("V1 "); + boolean isV11 = version.getText().equalsIgnoreCase("V1.1"); + boolean isV20 = version.getText().equalsIgnoreCase("V2.0"); addField(version); addField(new StringRef(buffer, 8, ITM_NAME_GENERAL)); addField(new StringRef(buffer, 12, ITM_NAME_IDENTIFIED)); - if (version.getText().equalsIgnoreCase("V1.1") || - (version.getText().equalsIgnoreCase("V1 ") && Profile.getGame() == Profile.Game.PSTEE)) { + if (isV11 || (isV10 && Profile.getGame() == Profile.Game.PSTEE)) { addField(new ResourceRef(buffer, 16, ITM_DROP_SOUND, "WAV")); if (Profile.getGame() == Profile.Game.PSTEE) { addField(new Flag(buffer, 24, 4, ITM_FLAGS, s_flags_pstee)); @@ -394,7 +407,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new ResourceRef(buffer, 16, ITM_USED_UP_ITEM, "ITM")); addField(new Flag(buffer, 24, 4, ITM_FLAGS, IdsMapCache.getUpdatedIdsFlags(s_flags, "ITEMFLAG.IDS", 4, false, false))); addField(new Bitmap(buffer, 28, 2, ITM_CATEGORY, s_categories)); - if (version.toString().equalsIgnoreCase("V2.0")) { + if (isV20) { addField(new Flag(buffer, 30, 4, ITM_UNUSABLE_BY, s_usability20)); } else { addField(new Flag(buffer, 30, 4, ITM_UNUSABLE_BY, s_usability)); @@ -405,13 +418,13 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new DecNumber(buffer, 38, 2, ITM_MIN_STRENGTH)); if (ResourceFactory.resourceExists("KIT.IDS")) { addField(new DecNumber(buffer, 40, 1, ITM_MIN_STRENGTH_BONUS)); - addField(new Flag(buffer, 41, 1, ITM_UNUSABLE_BY_1, s_kituse1)); + addField(new Flag(buffer, 41, 1, ITM_UNUSABLE_BY_1, isV20 ? s_kituse1_v2 : s_kituse1)); addField(new DecNumber(buffer, 42, 1, ITM_MIN_INTELLIGENCE)); - addField(new Flag(buffer, 43, 1, ITM_UNUSABLE_BY_2, s_kituse2)); + addField(new Flag(buffer, 43, 1, ITM_UNUSABLE_BY_2, isV20 ? s_kituse2_v2 : s_kituse2)); addField(new DecNumber(buffer, 44, 1, ITM_MIN_DEXTERITY)); - addField(new Flag(buffer, 45, 1, ITM_UNUSABLE_BY_3, s_kituse3)); + addField(new Flag(buffer, 45, 1, ITM_UNUSABLE_BY_3, isV20 ? s_kituse3_v2 : s_kituse3)); addField(new DecNumber(buffer, 46, 1, ITM_MIN_WISDOM)); - addField(new Flag(buffer, 47, 1, ITM_UNUSABLE_BY_4, s_kituse4)); + addField(new Flag(buffer, 47, 1, ITM_UNUSABLE_BY_4, isV20 ? s_kituse4_v2 : s_kituse4)); addField(new DecNumber(buffer, 48, 1, ITM_MIN_CONSTITUTION)); if (ResourceFactory.resourceExists("PROFTYPE.IDS")) { addField(new IdsBitmap(buffer, 49, 1, ITM_WEAPON_PROFICIENCY, "PROFTYPE.IDS")); @@ -435,7 +448,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new DecNumber(buffer, 76, 4, ITM_WEIGHT)); addField(new StringRef(buffer, 80, ITM_DESCRIPTION_GENERAL)); addField(new StringRef(buffer, 84, ITM_DESCRIPTION_IDENTIFIED)); - if (version.toString().equalsIgnoreCase("V1.1")) { + if (isV11) { addField(new ResourceRef(buffer, 88, ITM_PICK_UP_SOUND, "WAV")); } else { addField(new ResourceRef(buffer, 88, ITM_DESCRIPTION_IMAGE, "BAM")); @@ -455,13 +468,13 @@ public int read(ByteBuffer buffer, int offset) throws Exception Effect.class); addField(global_count); - if (version.toString().equalsIgnoreCase("V1.1")) { + if (isV11) { addField(new ResourceRef(buffer, 114, ITM_DIALOG, "DLG")); addField(new StringRef(buffer, 122, ITM_SPEAKER_NAME)); addField(new IdsBitmap(buffer, 126, 2, ITM_WEAPON_COLOR, "CLOWNCLR.IDS")); addField(new Unknown(buffer, 128, 26)); } - else if (version.toString().equalsIgnoreCase("V2.0")) { + else if (isV20) { addField(new Unknown(buffer, 114, 16)); } From a3a6fad1f5d1ba370b5b3686bb6bf8b537ae7c29 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sun, 24 Jan 2021 00:47:47 +0100 Subject: [PATCH 006/158] ITM V2.0 kit usability1 flags: generate dynamically (experimental) --- .../infinity/resource/itm/ItmResource.java | 61 ++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/src/org/infinity/resource/itm/ItmResource.java b/src/org/infinity/resource/itm/ItmResource.java index 082d79ad3..f0351bebd 100644 --- a/src/org/infinity/resource/itm/ItmResource.java +++ b/src/org/infinity/resource/itm/ItmResource.java @@ -42,8 +42,13 @@ import org.infinity.resource.key.ResourceEntry; import org.infinity.resource.spl.SplResource; import org.infinity.search.SearchOptions; +import org.infinity.util.IdsMap; import org.infinity.util.IdsMapCache; +import org.infinity.util.IdsMapEntry; +import org.infinity.util.Misc; import org.infinity.util.StringTable; +import org.infinity.util.Table2da; +import org.infinity.util.Table2daCache; import org.infinity.util.io.StreamUtils; /** @@ -203,7 +208,7 @@ public final class ItmResource extends AbstractStruct implements Resource, HasCh public static final String[] s_kituse4 = {"None", "Berserker", "Wizard slayer", "Kensai", "Cavalier", "Inquisitor", "Undead hunter", "Abjurer", "Conjurer"}; - public static final String[] s_kituse1_v2 = {"None"}; + public static final String[] s_kituse1_v2 = {"None", "", "", "", "", "", "", "", ""}; public static final String[] s_kituse2_v2 = {"None", "Cleric of Lathander", "Cleric of Selune", "Cleric of Helm", "Cleric of Oghma", "Cleric of Tempus", "Cleric of Bane", "Cleric of Mask", "Cleric of Talos"}; @@ -418,6 +423,9 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new DecNumber(buffer, 38, 2, ITM_MIN_STRENGTH)); if (ResourceFactory.resourceExists("KIT.IDS")) { addField(new DecNumber(buffer, 40, 1, ITM_MIN_STRENGTH_BONUS)); + if (isV20) { + updateKitUsability(s_kituse1_v2, 24, 8, true); + } addField(new Flag(buffer, 41, 1, ITM_UNUSABLE_BY_1, isV20 ? s_kituse1_v2 : s_kituse1)); addField(new DecNumber(buffer, 42, 1, ITM_MIN_INTELLIGENCE)); addField(new Flag(buffer, 43, 1, ITM_UNUSABLE_BY_2, isV20 ? s_kituse2_v2 : s_kituse2)); @@ -517,6 +525,57 @@ private void incAbilityEffects(StructEntry child, AddRemovable datatype, int val } } + /** + * Updates the specified string array with kit names from KIT.IDS. + * @param kits The string array for kit names. (First slot is reserved for empty selection string.) + * @param offset bit position to start. + * @param count number of bits to update. + * @param fillMissing whether only empty slots in the string array should be updated. + */ + private void updateKitUsability(String[] kits, int offset, int count, boolean fillMissing) + { + if (kits != null && offset >= 0 && offset < 32 && count > 0) { + IdsMap map = IdsMapCache.get("KIT.IDS"); + Table2da table = Table2daCache.get("KITLIST.2DA"); + if (map != null) { + for (int i = 0; i < count; i++) { + long value = 1L << (offset + i); + IdsMapEntry entry = map.get(value); + if (entry != null) { + if (i + 1 < kits.length && (!fillMissing || kits[i + 1] == null || kits[i + 1].isEmpty())) { + int strref = -1; + if (table != null) { + // try getting proper kit name from kitlist.2da + for (int row = 3, rowCount = table.getRowCount(); row < rowCount; row++) { + if (entry.getSymbol().equalsIgnoreCase(table.get(row, 0))) { + strref = Misc.toNumber(table.get(row, 2), -1); // mixed + if (strref < 0) { + strref = Misc.toNumber(table.get(row, 1), -1); // lowercase + } + } + if (strref > 0) { + break; + } + } + } + String desc = null; + if (strref > 0) { + try { + desc = StringTable.getStringRef(strref); + } catch (IndexOutOfBoundsException e) { + } + } + if (desc == null || desc.isEmpty()) { + desc = entry.getSymbol(); + } + kits[i + 1] = desc; + } + } + } + } + } + } + /** * Called by "Extended Search" * Checks whether the specified resource entry matches all available search options. From a01a49a57f8eec93dfa40895af8c5d621617d722 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sun, 24 Jan 2021 15:32:29 +0100 Subject: [PATCH 007/158] Opcodes 15 and 44: fix param1 handling when param2 = 3 --- src/org/infinity/resource/EffectFactory.java | 22 +++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/org/infinity/resource/EffectFactory.java b/src/org/infinity/resource/EffectFactory.java index 7ee1b8fb5..a2f94470a 100644 --- a/src/org/infinity/resource/EffectFactory.java +++ b/src/org/infinity/resource/EffectFactory.java @@ -817,24 +817,27 @@ private static boolean updateOpcode1(AbstractStruct struct) throws Exception // Effect type "Dexterity bonus" (15) private static boolean updateOpcode15(AbstractStruct struct) throws Exception { + boolean retVal = false; if (struct != null) { if (Profile.getEngine() == Profile.Engine.IWD || Profile.getEngine() == Profile.Engine.IWD2 || Profile.isEnhancedEdition() || (boolean)Profile.getProperty(Profile.Key.IS_GAME_TOBEX)) { + boolean isV1 = (getEntry(struct, EffectEntry.IDX_OPCODE).getSize() == 2); int opcode = ((EffectType)getEntry(struct, EffectEntry.IDX_OPCODE)).getValue(); int param2 = ((Bitmap)getEntry(struct, EffectEntry.IDX_PARAM2)).getValue(); if (opcode == 15) { - if (param2 == 3) { + if (isV1 && param2 == 3) { replaceEntry(struct, EffectEntry.IDX_PARAM1, EffectEntry.OFS_PARAM1, new DecNumber(getEntryData(struct, EffectEntry.IDX_PARAM1), 0, 4, AbstractStruct.COMMON_UNUSED)); + retVal = true; } else { replaceEntry(struct, EffectEntry.IDX_PARAM1, EffectEntry.OFS_PARAM1, new DecNumber(getEntryData(struct, EffectEntry.IDX_PARAM1), 0, 4, "Value")); + retVal = true; } - return true; } } } - return false; + return retVal; } // Effect type "Reset morale" (23) @@ -868,24 +871,27 @@ private static boolean updateOpcode23(AbstractStruct struct) throws Exception // Effect type "Strength bonus" (44) private static boolean updateOpcode44(AbstractStruct struct) throws Exception { + boolean retVal = false; if (struct != null) { if (Profile.getEngine() == Profile.Engine.IWD || Profile.getEngine() == Profile.Engine.IWD2 || Profile.isEnhancedEdition() || (boolean)Profile.getProperty(Profile.Key.IS_GAME_TOBEX)) { + boolean isV1 = (getEntry(struct, EffectEntry.IDX_OPCODE).getSize() == 2); int opcode = ((EffectType)getEntry(struct, EffectEntry.IDX_OPCODE)).getValue(); int param2 = ((Bitmap)getEntry(struct, EffectEntry.IDX_PARAM2)).getValue(); if (opcode == 44) { - if (param2 == 3) { + if (isV1 && param2 == 3) { replaceEntry(struct, EffectEntry.IDX_PARAM1, EffectEntry.OFS_PARAM1, new DecNumber(getEntryData(struct, EffectEntry.IDX_PARAM1), 0, 4, AbstractStruct.COMMON_UNUSED)); + retVal = true; } else { replaceEntry(struct, EffectEntry.IDX_PARAM1, EffectEntry.OFS_PARAM1, new DecNumber(getEntryData(struct, EffectEntry.IDX_PARAM1), 0, 4, "Value")); + retVal = true; } - return true; } } } - return false; + return retVal; } // Effect type "Disease" (78) @@ -2218,7 +2224,7 @@ private String makeEffectParamsGeneric(Datatype parent, ByteBuffer buffer, int o if (Profile.getEngine() == Profile.Engine.IWD || Profile.getEngine() == Profile.Engine.IWD2 || Profile.isEnhancedEdition() || isTobEx) { int type = buffer.getInt(offset + 4); - s.add(new DecNumber(buffer, offset, 4, (type == 3) ? AbstractStruct.COMMON_UNUSED : "Value")); + s.add(new DecNumber(buffer, offset, 4, (isV1 && type == 3) ? AbstractStruct.COMMON_UNUSED : "Value")); Bitmap item = new Bitmap(buffer, offset + 4, 4, "Modifier type", new String[]{"Increment", "Set", "Set % of", "Cat's grace"}); s.add(item); @@ -2451,7 +2457,7 @@ private String makeEffectParamsGeneric(Datatype parent, ByteBuffer buffer, int o if (Profile.getEngine() == Profile.Engine.IWD || Profile.getEngine() == Profile.Engine.IWD2 || Profile.isEnhancedEdition()) { int type = buffer.getInt(offset + 4); - s.add(new DecNumber(buffer, offset, 4, (type == 3) ? AbstractStruct.COMMON_UNUSED : "Value")); + s.add(new DecNumber(buffer, offset, 4, (isV1 && type == 3) ? AbstractStruct.COMMON_UNUSED : "Value")); item = new Bitmap(buffer, offset + 4, 4, "Modifier type", new String[]{"Increment", "Set", "Set % of", "Bull's Strength"}); s.add(item); From d692adaa7e1177cd5c9b7e5d60672a6316229a20 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Mon, 25 Jan 2021 13:28:57 +0100 Subject: [PATCH 008/158] Notify updates of datatypes only if data actually changes --- src/org/infinity/datatype/ColorValue.java | 5 ++++- src/org/infinity/datatype/DecNumber.java | 8 +++++--- src/org/infinity/datatype/Flag.java | 7 ++++++- src/org/infinity/datatype/FloatNumber.java | 7 +++++++ src/org/infinity/datatype/HashBitmap.java | 5 ++++- src/org/infinity/datatype/MultiNumber.java | 5 ++++- src/org/infinity/datatype/ResourceBitmap.java | 6 +++++- src/org/infinity/datatype/ResourceRef.java | 9 +++++++-- src/org/infinity/datatype/StringRef.java | 5 ++++- src/org/infinity/datatype/TextBitmap.java | 5 ++++- src/org/infinity/datatype/TextEdit.java | 5 ++++- src/org/infinity/datatype/TextString.java | 14 +++++++++++--- src/org/infinity/datatype/Unknown.java | 6 +++++- 13 files changed, 70 insertions(+), 17 deletions(-) diff --git a/src/org/infinity/datatype/ColorValue.java b/src/org/infinity/datatype/ColorValue.java index 4efdabae2..179c6e8ba 100644 --- a/src/org/infinity/datatype/ColorValue.java +++ b/src/org/infinity/datatype/ColorValue.java @@ -167,10 +167,13 @@ public boolean updateValue(AbstractStruct struct) { if (colorList.getSelectedIndex() >= 0) { if (number != colorList.getSelectedIndex()) { + long oldValue = getLongValue(); setValue(colorList.getSelectedIndex()); // notifying listeners - fireValueUpdated(new UpdateEvent(this, struct)); + if (getLongValue() != oldValue) { + fireValueUpdated(new UpdateEvent(this, struct)); + } } return true; diff --git a/src/org/infinity/datatype/DecNumber.java b/src/org/infinity/datatype/DecNumber.java index dbcc6fa1e..bb931157e 100644 --- a/src/org/infinity/datatype/DecNumber.java +++ b/src/org/infinity/datatype/DecNumber.java @@ -46,15 +46,17 @@ public boolean update(Object value) { try { long oldVal = getLongValue(); - long newVal = parseNumber(value, getSize(), signed, true); - setValue(newVal); - if (oldVal != newVal) { + setValue(parseNumber(value, getSize(), signed, true)); + + // notifying listeners + if (getLongValue() != oldVal) { fireValueUpdated(new UpdateEvent(this, getParent())); } return true; } catch (Exception e) { e.printStackTrace(); } + return false; } diff --git a/src/org/infinity/datatype/Flag.java b/src/org/infinity/datatype/Flag.java index 5173dd02f..7cecb179a 100644 --- a/src/org/infinity/datatype/Flag.java +++ b/src/org/infinity/datatype/Flag.java @@ -156,10 +156,15 @@ public void select() @Override public boolean updateValue(AbstractStruct struct) { + long oldValue = getLongValue(); + // updating value setValue(calcValue()); + // notifying listeners - fireValueUpdated(new UpdateEvent(this, struct)); + if (getLongValue() != oldValue) { + fireValueUpdated(new UpdateEvent(this, struct)); + } return true; } diff --git a/src/org/infinity/datatype/FloatNumber.java b/src/org/infinity/datatype/FloatNumber.java index 2d0b799e1..a68153c61 100644 --- a/src/org/infinity/datatype/FloatNumber.java +++ b/src/org/infinity/datatype/FloatNumber.java @@ -44,7 +44,14 @@ public boolean update(Object value) if (getSize() == 4) { newValue = Double.valueOf(newValue).floatValue(); } + double oldValue = getValue(); setValue(newValue); + + // notifying listeners + if (getValue() != oldValue) { + fireValueUpdated(new UpdateEvent(this, getParent())); + } + return true; } catch (NumberFormatException e) { e.printStackTrace(); diff --git a/src/org/infinity/datatype/HashBitmap.java b/src/org/infinity/datatype/HashBitmap.java index 86ce1165b..c4e133c7a 100644 --- a/src/org/infinity/datatype/HashBitmap.java +++ b/src/org/infinity/datatype/HashBitmap.java @@ -159,6 +159,7 @@ public void select() @Override public boolean updateValue(AbstractStruct struct) { + long oldValue = getLongValue(); // updating value Long number = getValueOfItem(list.getSelectedValue()); if (number != null) { @@ -168,7 +169,9 @@ public boolean updateValue(AbstractStruct struct) } // notifying listeners - fireValueUpdated(new UpdateEvent(this, struct)); + if (getLongValue() != oldValue) { + fireValueUpdated(new UpdateEvent(this, struct)); + } return true; } diff --git a/src/org/infinity/datatype/MultiNumber.java b/src/org/infinity/datatype/MultiNumber.java index eb6b4022d..c9a0c100d 100644 --- a/src/org/infinity/datatype/MultiNumber.java +++ b/src/org/infinity/datatype/MultiNumber.java @@ -148,10 +148,13 @@ public void select() @Override public boolean updateValue(AbstractStruct struct) { + long oldValue = getLongValue(); setValueImpl(mValues.getValue()); // notifying listeners - fireValueUpdated(new UpdateEvent(this, struct)); + if (getLongValue() != oldValue) { + fireValueUpdated(new UpdateEvent(this, struct)); + } return true; } diff --git a/src/org/infinity/datatype/ResourceBitmap.java b/src/org/infinity/datatype/ResourceBitmap.java index 71d8f2dc7..818f33acc 100644 --- a/src/org/infinity/datatype/ResourceBitmap.java +++ b/src/org/infinity/datatype/ResourceBitmap.java @@ -204,9 +204,13 @@ public boolean updateValue(AbstractStruct struct) return false; } + long oldValue = getLongValue(); setValue(selected.getValue()); + // notifying listeners - fireValueUpdated(new UpdateEvent(this, struct)); + if (getLongValue() != oldValue) { + fireValueUpdated(new UpdateEvent(this, struct)); + } return true; } diff --git a/src/org/infinity/datatype/ResourceRef.java b/src/org/infinity/datatype/ResourceRef.java index 089283ab7..4387d58c1 100644 --- a/src/org/infinity/datatype/ResourceRef.java +++ b/src/org/infinity/datatype/ResourceRef.java @@ -212,12 +212,15 @@ public void select() @Override public boolean updateValue(AbstractStruct struct) { + String oldString = getText(); final ResourceRefEntry selected = list.getSelectedValue(); if (selected == NONE) { setValue(NONE.name);//FIXME: use null instead of this // notifying listeners - fireValueUpdated(new UpdateEvent(this, struct)); + if (!getText().equals(oldString)) { + fireValueUpdated(new UpdateEvent(this, struct)); + } return true; } @@ -240,7 +243,9 @@ public boolean updateValue(AbstractStruct struct) } // notifying listeners - fireValueUpdated(new UpdateEvent(this, struct)); + if (!getText().equals(oldString)) { + fireValueUpdated(new UpdateEvent(this, struct)); + } return true; } diff --git a/src/org/infinity/datatype/StringRef.java b/src/org/infinity/datatype/StringRef.java index f2237b059..20655322a 100644 --- a/src/org/infinity/datatype/StringRef.java +++ b/src/org/infinity/datatype/StringRef.java @@ -261,10 +261,13 @@ public void select() @Override public boolean updateValue(AbstractStruct struct) { + long oldValue = getLongValue(); setValue(getValueFromEditor()); // notifying listeners - fireValueUpdated(new UpdateEvent(this, struct)); + if (getLongValue() != oldValue) { + fireValueUpdated(new UpdateEvent(this, struct)); + } return true; } diff --git a/src/org/infinity/datatype/TextBitmap.java b/src/org/infinity/datatype/TextBitmap.java index 5bbcd28d6..9bcf79fb2 100644 --- a/src/org/infinity/datatype/TextBitmap.java +++ b/src/org/infinity/datatype/TextBitmap.java @@ -121,10 +121,13 @@ public boolean updateValue(AbstractStruct struct) if (index == -1) { return false; } + String oldString = getText(); setValue(ids[index]); // notifying listeners - fireValueUpdated(new UpdateEvent(this, struct)); + if (getText().equals(oldString)) { + fireValueUpdated(new UpdateEvent(this, struct)); + } return true; } diff --git a/src/org/infinity/datatype/TextEdit.java b/src/org/infinity/datatype/TextEdit.java index e32bef4c5..279111763 100644 --- a/src/org/infinity/datatype/TextEdit.java +++ b/src/org/infinity/datatype/TextEdit.java @@ -126,10 +126,13 @@ public void select() @Override public boolean updateValue(AbstractStruct struct) { + String oldString = getText(); setValue(textArea.getText()); // notifying listeners - fireValueUpdated(new UpdateEvent(this, struct)); + if (!getText().equals(oldString)) { + fireValueUpdated(new UpdateEvent(this, struct)); + } return true; } diff --git a/src/org/infinity/datatype/TextString.java b/src/org/infinity/datatype/TextString.java index e303baa6c..269b7b6dd 100644 --- a/src/org/infinity/datatype/TextString.java +++ b/src/org/infinity/datatype/TextString.java @@ -46,11 +46,19 @@ public TextString(ByteBuffer buffer, int offset, int length, String name) @Override public boolean update(Object value) { - String newstring = (String)value; - if (newstring.length() > getSize()) { + String newString = value.toString(); + if (newString.length() > getSize()) { return false; } - setValue(newstring); + + String oldString = getText(); + setValue(newString); + + // notifying listeners + if (!getText().equals(oldString)) { + fireValueUpdated(new UpdateEvent(this, getParent())); + } + return true; } diff --git a/src/org/infinity/datatype/Unknown.java b/src/org/infinity/datatype/Unknown.java index 28c96a438..f9bf7290e 100644 --- a/src/org/infinity/datatype/Unknown.java +++ b/src/org/infinity/datatype/Unknown.java @@ -117,10 +117,13 @@ public boolean updateValue(AbstractStruct struct) if (newData == null) { return false; } + ByteBuffer oldBuffer = getData(); setValue(newData); // notifying listeners - fireValueUpdated(new UpdateEvent(this, struct)); + if (getData().compareTo(oldBuffer) != 0) { + fireValueUpdated(new UpdateEvent(this, struct)); + } return true; } @@ -159,6 +162,7 @@ public ByteBuffer getData() buffer.position(0); ByteBuffer bb = StreamUtils.getByteBuffer(buffer.remaining()); buffer.put(bb); + bb.position(0); return bb; } From 8612f70d0e7e7121d4e13132068d39797dc7a95e Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 29 Jan 2021 17:50:58 +0100 Subject: [PATCH 009/158] Show both general and identified description in ITM view panel --- src/org/infinity/gui/ViewerUtil.java | 20 +++++++++++++++++++- src/org/infinity/resource/itm/Viewer.java | 20 +++++++++++++++----- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/org/infinity/gui/ViewerUtil.java b/src/org/infinity/gui/ViewerUtil.java index b25b0dfa3..ec407e73a 100644 --- a/src/org/infinity/gui/ViewerUtil.java +++ b/src/org/infinity/gui/ViewerUtil.java @@ -308,7 +308,23 @@ public static JPanel makeListPanel(String title, AbstractStruct struct, return new StructListPanel(title, struct, listClass, attrName, renderer, listener); } + /** + * Creates a panel with a text area control and a title with the {@code StructEntry} name. + * @param entry the {@code StructEntry} instance used to derive data and title from. + * @return a {@code JPanel} instance. + */ public static JPanel makeTextAreaPanel(StructEntry entry) + { + return makeTextAreaPanel(entry, true); + } + + /** + * Creates a panel with a text area control and an optional title with the {@code StructEntry} name. + * @param entry the {@code StructEntry} instance used to derive data and title from. + * @param showTitle whether to show the entry title. + * @return a {@code JPanel} instance. + */ + public static JPanel makeTextAreaPanel(StructEntry entry, boolean showTitle) { String text; if (entry instanceof StringRef) { @@ -329,7 +345,9 @@ public static JPanel makeTextAreaPanel(StructEntry entry) scroll.setLineNumbersEnabled(false); ta.setMargin(new Insets(3, 3, 3, 3)); JPanel panel = new JPanel(new BorderLayout()); - panel.add(new JLabel(entry.getName()), BorderLayout.NORTH); + if (showTitle) { + panel.add(new JLabel(entry.getName()), BorderLayout.NORTH); + } panel.add(scroll, BorderLayout.CENTER); panel.setPreferredSize(new Dimension(5, 5)); return panel; diff --git a/src/org/infinity/resource/itm/Viewer.java b/src/org/infinity/resource/itm/Viewer.java index 29ae538ba..56815b39d 100644 --- a/src/org/infinity/resource/itm/Viewer.java +++ b/src/org/infinity/resource/itm/Viewer.java @@ -16,15 +16,18 @@ import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; +import javax.swing.JTabbedPane; import org.infinity.datatype.EffectType; import org.infinity.datatype.Flag; +import org.infinity.datatype.IsNumeric; import org.infinity.datatype.ResourceRef; import org.infinity.gui.ViewerUtil; import org.infinity.resource.AbstractAbility; import org.infinity.resource.Effect; import org.infinity.resource.Profile; import org.infinity.resource.StructEntry; +import org.infinity.util.StringTable; import org.infinity.util.Table2da; import org.infinity.util.Table2daCache; @@ -38,10 +41,17 @@ final class Viewer extends JPanel JPanel abilitiesPanel = ViewerUtil.makeListPanel("Abilities", itm, Ability.class, AbstractAbility.ABILITY_TYPE); JPanel fieldPanel = makeFieldPanel(itm); JPanel boxPanel = ViewerUtil.makeCheckPanel((Flag)itm.getAttribute(ItmResource.ITM_FLAGS), 1); - StructEntry desc = itm.getAttribute(ItmResource.ITM_DESCRIPTION_IDENTIFIED); - if (desc.toString().equalsIgnoreCase("No such index")) - desc = itm.getAttribute(ItmResource.ITM_DESCRIPTION_GENERAL); - JPanel descPanel = ViewerUtil.makeTextAreaPanel(desc); + + StructEntry descGeneral = itm.getAttribute(ItmResource.ITM_DESCRIPTION_GENERAL); + StructEntry descIdentified = itm.getAttribute(ItmResource.ITM_DESCRIPTION_IDENTIFIED); + JTabbedPane tabbedDescPanel = new JTabbedPane(JTabbedPane.TOP); + tabbedDescPanel.addTab(descGeneral.getName(), ViewerUtil.makeTextAreaPanel(descGeneral, false)); + tabbedDescPanel.setEnabledAt(0, StringTable.isValidStringRef(((IsNumeric)descGeneral).getValue())); + tabbedDescPanel.addTab(descIdentified.getName(), ViewerUtil.makeTextAreaPanel(descIdentified, false)); + tabbedDescPanel.setEnabledAt(1, StringTable.isValidStringRef(((IsNumeric)descIdentified).getValue())); + if (tabbedDescPanel.isEnabledAt(1)) { + tabbedDescPanel.setSelectedIndex(1); + } JPanel iconPanel = new JPanel(new GridLayout(2, 1, 0, 6)); iconPanel.add(iconPanel1); @@ -56,7 +66,7 @@ final class Viewer extends JPanel panel2.add(globaleffectsPanel); JPanel panel3 = new JPanel(new GridLayout(2, 1, 6, 6)); - panel3.add(descPanel); + panel3.add(tabbedDescPanel); panel3.add(panel2); JPanel panel4 = new JPanel(new BorderLayout()); From a3500d1ab884bc9f505fa2f74511be6f1a11c88d Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Mon, 1 Feb 2021 10:51:00 +0100 Subject: [PATCH 010/158] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 5961b438c..a9637d53b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +[![GitHub release (latest by date)](https://img.shields.io/github/v/release/Argent77/NearInfinity?color=darkred&include_prereleases&label=latest%20release)](https://GitHub.com/Argent77/NearInfinity/releases/latest) +[![GitHub release date (latest by date)](https://img.shields.io/github/release-date/Argent77/NearInfinity?color=gold)](https://GitHub.com/Argent77/NearInfinity/releases/latest) +[![Github downloads (total)](https://img.shields.io/github/downloads/Argent77/NearInfinity/total.svg?color=blueviolet)](https://GitHub.com/Argent77/NearInfinity/releases) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/b5d10ca2684c4e30b6632e34584f1241)](https://www.codacy.com/manual/Mingun/NearInfinity) # Near Infinity From c50ce1703e095d9d072765037dc34a0f42876f0c Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sat, 13 Feb 2021 15:43:14 +0100 Subject: [PATCH 011/158] Consider offset argument in ByteBufferStream methods --- src/org/infinity/util/io/ByteBufferInputStream.java | 2 +- src/org/infinity/util/io/ByteBufferOutputStream.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/infinity/util/io/ByteBufferInputStream.java b/src/org/infinity/util/io/ByteBufferInputStream.java index 2700dce42..c78a18853 100644 --- a/src/org/infinity/util/io/ByteBufferInputStream.java +++ b/src/org/infinity/util/io/ByteBufferInputStream.java @@ -80,7 +80,7 @@ public int read(byte[] bytes, int off, int len) throws IOException if (remaining <= 0) { break; } - buf.get(bytes, read, remaining); + buf.get(bytes, off + read, remaining); read += remaining; } diff --git a/src/org/infinity/util/io/ByteBufferOutputStream.java b/src/org/infinity/util/io/ByteBufferOutputStream.java index f60896cdf..34f3a3709 100644 --- a/src/org/infinity/util/io/ByteBufferOutputStream.java +++ b/src/org/infinity/util/io/ByteBufferOutputStream.java @@ -77,7 +77,7 @@ public void write(byte[] bytes, int off, int len) throws IOException if (remaining <= 0) { throw new IndexOutOfBoundsException(); } - buf.put(bytes, written, remaining); + buf.put(bytes, off + written, remaining); written += remaining; } } From dce8654b75bd6a1c435244879553134255c91a80 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sat, 13 Feb 2021 15:45:14 +0100 Subject: [PATCH 012/158] ColorConvert: Add methods for loading palette data from resource entries --- .../resource/graphics/ColorConvert.java | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/org/infinity/resource/graphics/ColorConvert.java b/src/org/infinity/resource/graphics/ColorConvert.java index a5ba46378..1bec5e26c 100644 --- a/src/org/infinity/resource/graphics/ColorConvert.java +++ b/src/org/infinity/resource/graphics/ColorConvert.java @@ -33,6 +33,8 @@ import javax.imageio.ImageReader; import javax.imageio.stream.ImageInputStream; +import org.infinity.resource.key.FileResourceEntry; +import org.infinity.resource.key.ResourceEntry; import org.infinity.util.DynamicArray; import org.infinity.util.io.FileEx; import org.infinity.util.io.StreamUtils; @@ -438,8 +440,19 @@ public static boolean medianCut(int[] pixels, int desiredColors, int[] palette, */ public static int[] loadPaletteBMP(Path file) throws Exception { - if (file != null && FileEx.create(file).isFile()) { - try (InputStream is = StreamUtils.getInputStream(file)) { + return loadPaletteBMP(new FileResourceEntry(file)); + } + + /** + * Attempts to load a palette from the specified Windows BMP file. + * @param entry The BMP resource entry to extract the palette from. + * @return The palette as ARGB integers. + * @throws Exception on error. + */ + public static int[] loadPaletteBMP(ResourceEntry entry) throws Exception + { + if (entry != null) { + try (InputStream is = entry.getResourceDataAsStream()) { byte[] signature = new byte[8]; is.read(signature); if ("BM".equals(new String(signature, 0, 2))) { @@ -463,14 +476,14 @@ public static int[] loadPaletteBMP(Path file) throws Exception } return retVal; } else { - throw new Exception("Error loading palette from BMP file " + file.getFileName()); + throw new Exception("Error loading palette: " + entry.getResourceName()); } } else { - throw new Exception("Invalid BMP file " + file.getFileName()); + throw new Exception("Invalid BMP resource: " + entry.getResourceName()); } } catch (IOException e) { e.printStackTrace(); - throw new Exception("Unable to read BMP file " + file.getFileName()); + throw new Exception("Unable to read BMP resource: " + entry.getResourceName()); } } else { throw new Exception("File does not exist."); @@ -612,13 +625,18 @@ public static int[] loadPaletteACT(Path file) throws Exception */ public static int[] loadPaletteBAM(Path file, boolean preserveAlpha) throws Exception { - if (file != null && FileEx.create(file).isFile()) { - try (InputStream is = StreamUtils.getInputStream(file)) { + return loadPaletteBAM(new FileResourceEntry(file), preserveAlpha); + } + + public static int[] loadPaletteBAM(ResourceEntry entry, boolean preserveAlpha) throws Exception + { + if (entry != null) { + try (InputStream is = entry.getResourceDataAsStream()) { byte[] signature = new byte[8]; is.read(signature); String s = new String(signature); if ("BAM V1 ".equals(s) || "BAMCV1 ".equals(s)) { - byte[] bamData = new byte[(int)Files.size(file)]; + byte[] bamData = new byte[(int)entry.getResourceSize()]; System.arraycopy(signature, 0, bamData, 0, signature.length); is.read(bamData, signature.length, bamData.length - signature.length); if ("BAMCV1 ".equals(s)) { @@ -636,14 +654,14 @@ public static int[] loadPaletteBAM(Path file, boolean preserveAlpha) throws Exce } return retVal; } else { - throw new Exception("Error loading palette from BAM file " + file.getFileName()); + throw new Exception("Error loading palette: " + entry.getResourceName()); } } else { throw new Exception("Unsupport file type."); } } catch (IOException e) { e.printStackTrace(); - throw new Exception("Unable to read BAM file " + file.getFileName()); + throw new Exception("Unable to read BAM resource: " + entry.getResourceName()); } } else { throw new Exception("File does not exist."); From e6250319dde9647e78697facf23da5c2ad6fe889 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sat, 13 Feb 2021 15:48:02 +0100 Subject: [PATCH 013/158] Add tuple classes for one to six elements --- src/org/infinity/util/tuples/Couple.java | 153 ++++++++++++ src/org/infinity/util/tuples/Monuple.java | 136 ++++++++++ src/org/infinity/util/tuples/Quadruple.java | 196 +++++++++++++++ src/org/infinity/util/tuples/Quintuple.java | 216 ++++++++++++++++ src/org/infinity/util/tuples/Sextuple.java | 236 ++++++++++++++++++ src/org/infinity/util/tuples/Triple.java | 174 +++++++++++++ src/org/infinity/util/tuples/Tuple.java | 233 +++++++++++++++++ src/org/infinity/util/tuples/TupleValue0.java | 24 ++ src/org/infinity/util/tuples/TupleValue1.java | 24 ++ src/org/infinity/util/tuples/TupleValue2.java | 24 ++ src/org/infinity/util/tuples/TupleValue3.java | 24 ++ src/org/infinity/util/tuples/TupleValue4.java | 24 ++ src/org/infinity/util/tuples/TupleValue5.java | 24 ++ 13 files changed, 1488 insertions(+) create mode 100644 src/org/infinity/util/tuples/Couple.java create mode 100644 src/org/infinity/util/tuples/Monuple.java create mode 100644 src/org/infinity/util/tuples/Quadruple.java create mode 100644 src/org/infinity/util/tuples/Quintuple.java create mode 100644 src/org/infinity/util/tuples/Sextuple.java create mode 100644 src/org/infinity/util/tuples/Triple.java create mode 100644 src/org/infinity/util/tuples/Tuple.java create mode 100644 src/org/infinity/util/tuples/TupleValue0.java create mode 100644 src/org/infinity/util/tuples/TupleValue1.java create mode 100644 src/org/infinity/util/tuples/TupleValue2.java create mode 100644 src/org/infinity/util/tuples/TupleValue3.java create mode 100644 src/org/infinity/util/tuples/TupleValue4.java create mode 100644 src/org/infinity/util/tuples/TupleValue5.java diff --git a/src/org/infinity/util/tuples/Couple.java b/src/org/infinity/util/tuples/Couple.java new file mode 100644 index 000000000..3ef353563 --- /dev/null +++ b/src/org/infinity/util/tuples/Couple.java @@ -0,0 +1,153 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.util.tuples; + +import java.util.Collection; +import java.util.Iterator; + +/** + * A tuple class that can store two elements. + */ +public class Couple extends Tuple implements TupleValue0, TupleValue1 +{ + private static final int SIZE = 2; + + private A value0; + private B value1; + + /** + * Creates a new tuple instance with the specified elements. + * @param the tuple element type. + * @param value0 The first element to store in the tuple. + * @param value1 The second element to store in the tuple. + * @return A new tuple instance. + */ + public static Couple with(A value0, B value1) + { + return new Couple(value0, value1); + } + + /** + * Creates a new tuple from the array. The array must contain at least 2 elements. + * @param the tuple element type. + * @param arr The array to be used as source for the tuple. + * @return A new tuple instance. + */ + public static Couple fromArray(T[] arr) + { + if (arr == null) { + throw new IllegalArgumentException("Array cannot be null"); + } + if (arr.length < SIZE) { + throw new IllegalArgumentException(String.format("Array must contain at least %d elements", SIZE)); + } + return new Couple(arr[0], arr[1]); + } + + /** + * Creates a new tuple from the collection. The collection must contain at least 2 elements. + * @param the tuple element type. + * @param col the collection to be used as source for the tuple. + * @return a new tuple instance. + */ + public static Couple fromCollection(Collection col) + { + if (col == null) { + throw new IllegalArgumentException("Collection cannot be null"); + } + if (col.size() < SIZE) { + throw new IllegalArgumentException(String.format("Collection must contain at least %d elements", SIZE)); + } + Iterator iter = col.iterator(); + T el0 = iter.next(); + T el1 = iter.next(); + return new Couple(el0, el1); + } + + /** + * Creates a new tuple from the {@code Iterable} object. + * @param the tuple element type. + * @param iterator the {@code Iterable} object to be used as source for the tuple. + * @return a new tuple instance. + */ + public static Couple fromIterable(Iterable iterator) + { + return fromIterable(iterator, 0); + } + + /** + * Creates a new tuple from the {@code Iterable} object, starting the specified index. + * @param the tuple element type. + * @param iterator the {@code Iterable} object to be used as source for the tuple. + * @param index start index in {@code Iterable} object. + * @return A new tuple instance. + */ + public static Couple fromIterable(Iterable iterator, int index) + { + if (iterator == null) { + throw new IllegalArgumentException("Iterator cannot be null"); + } + + Iterator iter = iterator.iterator(); + for (int i = 0; i < index; i++) { + if (iter.hasNext()) { + iter.next(); + } + i++; + } + + T el0 = iter.hasNext() ? iter.next() : null; + T el1 = iter.hasNext() ? iter.next() : null; + return new Couple(el0, el1); + } + + /** + * Constructs a new Couple instance and initializes it with the specified arguments. + * @param value0 the first value of the Couple. + * @param value1 the second value of the Couple. + */ + public Couple(A value0, B value1) + { + super(value0, value1); + this.value0 = value0; + this.value1 = value1; + } + + @Override + public int size() + { + return SIZE; + } + + @Override + public A getValue0() + { + return value0; + } + + @Override + public A setValue0(A newValue) + { + A retVal = value0; + setValue(0, newValue); + value0 = newValue; + return retVal; + } + + @Override + public B getValue1() + { + return value1; + } + + @Override + public B setValue1(B newValue) + { + B retVal = value1; + setValue(1, newValue); + value1 = newValue; + return retVal; + } +} diff --git a/src/org/infinity/util/tuples/Monuple.java b/src/org/infinity/util/tuples/Monuple.java new file mode 100644 index 000000000..50e770cee --- /dev/null +++ b/src/org/infinity/util/tuples/Monuple.java @@ -0,0 +1,136 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.util.tuples; + +import java.util.Collection; +import java.util.Iterator; + +/** + * A tuple class that can store one element. + */ +public class Monuple extends Tuple implements TupleValue0 +{ + private static final int SIZE = 1; + + private A value0; + + /** + * Creates a new tuple instance with the specified element. + * @param the tuple element type. + * @param value0 The element to store in the tuple. + * @return A new tuple instance. + */ + public static Monuple with(A value0) + { + return new Monuple(value0); + } + + /** + * Creates a new tuple from the array. The array must contain at least 1 element. + * @param the tuple element type. + * @param arr the array to be used as source for the tuple. + * @return a new tuple instance. + */ + public static Monuple fromArray(T[] arr) + { + if (arr == null) { + throw new IllegalArgumentException("Array cannot be null"); + } + if (arr.length < SIZE) { + throw new IllegalArgumentException("Array must contain at least 1 element"); + } + return new Monuple(arr[0]); + } + + /** + * Creates a new tuple from the collection. The collection must contain at least 1 element. + * @param the tuple element type. + * @param col the collection to be used as source for the tuple. + * @return a new tuple instance. + */ + public static Monuple fromCollection(Collection col) + { + if (col == null) { + throw new IllegalArgumentException("Collection cannot be null"); + } + if (col.size() < SIZE) { + throw new IllegalArgumentException("Collection must contain at least 1 element"); + } + Iterator iter = col.iterator(); + return new Monuple(iter.next()); + } + + /** + * Creates a new tuple from the {@code Iterable} object. + * @param the tuple element type. + * @param iterator the {@code Iterable} object to be used as source for the tuple. + * @return a new tuple instance. + */ + public static Monuple fromIterable(Iterable iterator) + { + return fromIterable(iterator, 0); + } + + /** + * Creates a new tuple from the {@code Iterable} object, starting the specified index. + * @param the tuple element type. + * @param iterator the {@code Iterable} object to be used as source for the tuple. + * @param index start index in {@code Iterable} object. + * @return A new tuple instance. + */ + public static Monuple fromIterable(Iterable iterator, int index) + { + if (iterator == null) { + throw new IllegalArgumentException("Iterator cannot be null"); + } + + Iterator iter = iterator.iterator(); + for (int i = 0; i < index; i++) { + if (iter.hasNext()) { + iter.next(); + } + i++; + } + + T el0; + if (iter.hasNext()) { + el0 = iter.next(); + } else { + el0 = null; + } + return new Monuple(el0); + } + + /** + * Constructs a new Monuple instance and initializes it with the specified arguments. + * @param value0 the value of the Monuple. + */ + public Monuple(A value0) + { + super(value0); + this.value0 = value0; + } + + @Override + public int size() + { + return SIZE; + } + + @Override + public A getValue0() + { + return value0; + } + + @Override + public A setValue0(A newValue) + { + A retVal = value0; + setValue(0, newValue); + value0 = newValue; + return retVal; + } +} diff --git a/src/org/infinity/util/tuples/Quadruple.java b/src/org/infinity/util/tuples/Quadruple.java new file mode 100644 index 000000000..37efcb4ae --- /dev/null +++ b/src/org/infinity/util/tuples/Quadruple.java @@ -0,0 +1,196 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.util.tuples; + +import java.util.Collection; +import java.util.Iterator; + +/** + * A tuple class that can store four elements. + */ +public class Quadruple extends Tuple + implements TupleValue0, TupleValue1, TupleValue2, TupleValue3 +{ + private static final int SIZE = 4; + + private A value0; + private B value1; + private C value2; + private D value3; + + /** + * Creates a new tuple instance with the specified elements. + * @param the tuple element type. + * @param value0 The first element to store in the tuple. + * @param value2 The second element to store in the tuple. + * @param value3 The third element to store in the tuple. + * @param value4 The fourth element to store in the tuple. + * @return A new tuple instance. + */ + public static Quadruple with(A value0, B value1, C value2, D value3) + { + return new Quadruple(value0, value1, value2, value3); + } + + /** + * Creates a new tuple from the array. The array must contain at least 4 elements. + * @param the tuple element type. + * @param arr The array to be used as source for the tuple. + * @return A new tuple instance. + */ + public static Quadruple fromArray(T[] arr) + { + if (arr == null) { + throw new IllegalArgumentException("Array cannot be null"); + } + if (arr.length < SIZE) { + throw new IllegalArgumentException(String.format("Array must contain at least %d elements", SIZE)); + } + return new Quadruple(arr[0], arr[1], arr[2], arr[3]); + } + + /** + * Creates a new tuple from the collection. The collection must contain at least 4 elements. + * @param the tuple element type. + * @param col the collection to be used as source for the tuple. + * @return a new tuple instance. + */ + public static Quadruple fromCollection(Collection col) + { + if (col == null) { + throw new IllegalArgumentException("Collection cannot be null"); + } + if (col.size() < SIZE) { + throw new IllegalArgumentException(String.format("Collection must contain at least %d elements", SIZE)); + } + Iterator iter = col.iterator(); + T el0 = iter.next(); + T el1 = iter.next(); + T el2 = iter.next(); + T el3 = iter.next(); + return new Quadruple(el0, el1, el2, el3); + } + + /** + * Creates a new tuple from the {@code Iterable} object. + * @param the tuple element type. + * @param iterator the {@code Iterable} object to be used as source for the tuple. + * @return a new tuple instance. + */ + public static Quadruple fromIterable(Iterable iterator) + { + return fromIterable(iterator, 0); + } + + /** + * Creates a new tuple from the {@code Iterable} object, starting the specified index. + * @param the tuple element type. + * @param iterator the {@code Iterable} object to be used as source for the tuple. + * @param index start index in {@code Iterable} object. + * @return A new tuple instance. + */ + public static Quadruple fromIterable(Iterable iterator, int index) + { + if (iterator == null) { + throw new IllegalArgumentException("Iterator cannot be null"); + } + + Iterator iter = iterator.iterator(); + for (int i = 0; i < index; i++) { + if (iter.hasNext()) { + iter.next(); + } + i++; + } + + T el0 = iter.hasNext() ? iter.next() : null; + T el1 = iter.hasNext() ? iter.next() : null; + T el2 = iter.hasNext() ? iter.next() : null; + T el3 = iter.hasNext() ? iter.next() : null; + return new Quadruple(el0, el1, el2, el3); + } + + /** + * Constructs a new Triple instance and initializes it with the specified arguments. + * @param value0 the first value of the Quadruple. + * @param value1 the second value of the Quadruple. + * @param value2 the third value of the Quadruple. + * @param value3 the fourth value of the Quadruple. + */ + public Quadruple(A value0, B value1, C value2, D value3) + { + super(value0, value1, value2); + this.value0 = value0; + this.value1 = value1; + this.value2 = value2; + this.value3 = value3; + } + + @Override + public int size() + { + return SIZE; + } + + @Override + public A getValue0() + { + return value0; + } + + @Override + public A setValue0(A newValue) + { + A retVal = value0; + setValue(0, newValue); + value0 = newValue; + return retVal; + } + + @Override + public B getValue1() + { + return value1; + } + + @Override + public B setValue1(B newValue) + { + B retVal = value1; + setValue(1, newValue); + value1 = newValue; + return retVal; + } + + @Override + public C getValue2() + { + return value2; + } + + @Override + public C setValue2(C newValue) + { + C retVal = value2; + setValue(2, newValue); + value2 = newValue; + return retVal; + } + + @Override + public D getValue3() + { + return value3; + } + + @Override + public D setValue3(D newValue) + { + D retVal = value3; + setValue(3, newValue); + value3 = newValue; + return retVal; + } +} diff --git a/src/org/infinity/util/tuples/Quintuple.java b/src/org/infinity/util/tuples/Quintuple.java new file mode 100644 index 000000000..18d2381dd --- /dev/null +++ b/src/org/infinity/util/tuples/Quintuple.java @@ -0,0 +1,216 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.util.tuples; + +import java.util.Collection; +import java.util.Iterator; + +/** + * A tuple class that can store five elements. + */ +public class Quintuple extends Tuple + implements TupleValue0, TupleValue1, TupleValue2, TupleValue3, TupleValue4 +{ + private static final int SIZE = 5; + + private A value0; + private B value1; + private C value2; + private D value3; + private E value4; + + /** + * Creates a new tuple instance with the specified elements. + * @param the tuple element type. + * @param value0 The first element to store in the tuple. + * @param value2 The second element to store in the tuple. + * @param value3 The third element to store in the tuple. + * @param value4 The fourth element to store in the tuple. + * @return A new tuple instance. + */ + public static Quintuple with(A value0, B value1, C value2, D value3, E value4) + { + return new Quintuple(value0, value1, value2, value3, value4); + } + + /** + * Creates a new tuple from the array. The array must contain at least 5 elements. + * @param the tuple element type. + * @param arr The array to be used as source for the tuple. + * @return A new tuple instance. + */ + public static Quintuple fromArray(T[] arr) + { + if (arr == null) { + throw new IllegalArgumentException("Array cannot be null"); + } + if (arr.length < SIZE) { + throw new IllegalArgumentException(String.format("Array must contain at least %d elements", SIZE)); + } + return new Quintuple(arr[0], arr[1], arr[2], arr[3], arr[4]); + } + + /** + * Creates a new tuple from the collection. The collection must contain at least 5 elements. + * @param the tuple element type. + * @param col the collection to be used as source for the tuple. + * @return a new tuple instance. + */ + public static Quintuple fromCollection(Collection col) + { + if (col == null) { + throw new IllegalArgumentException("Collection cannot be null"); + } + if (col.size() < SIZE) { + throw new IllegalArgumentException(String.format("Collection must contain at least %d elements", SIZE)); + } + Iterator iter = col.iterator(); + T el0 = iter.next(); + T el1 = iter.next(); + T el2 = iter.next(); + T el3 = iter.next(); + T el4 = iter.next(); + return new Quintuple(el0, el1, el2, el3, el4); + } + + /** + * Creates a new tuple from the {@code Iterable} object. + * @param the tuple element type. + * @param iterator the {@code Iterable} object to be used as source for the tuple. + * @return a new tuple instance. + */ + public static Quintuple fromIterable(Iterable iterator) + { + return fromIterable(iterator, 0); + } + + /** + * Creates a new tuple from the {@code Iterable} object, starting the specified index. + * @param the tuple element type. + * @param iterator the {@code Iterable} object to be used as source for the tuple. + * @param index start index in {@code Iterable} object. + * @return A new tuple instance. + */ + public static Quintuple fromIterable(Iterable iterator, int index) + { + if (iterator == null) { + throw new IllegalArgumentException("Iterator cannot be null"); + } + + Iterator iter = iterator.iterator(); + for (int i = 0; i < index; i++) { + if (iter.hasNext()) { + iter.next(); + } + i++; + } + + T el0 = iter.hasNext() ? iter.next() : null; + T el1 = iter.hasNext() ? iter.next() : null; + T el2 = iter.hasNext() ? iter.next() : null; + T el3 = iter.hasNext() ? iter.next() : null; + T el4 = iter.hasNext() ? iter.next() : null; + return new Quintuple(el0, el1, el2, el3, el4); + } + + /** + * Constructs a new Triple instance and initializes it with the specified arguments. + * @param value0 the first value of the Quintuple. + * @param value1 the second value of the Quintuple. + * @param value2 the third value of the Quintuple. + * @param value3 the fourth value of the Quintuple. + * @param value4 the fifth value of the Quintuple. + */ + public Quintuple(A value0, B value1, C value2, D value3, E value4) + { + super(value0, value1, value2); + this.value0 = value0; + this.value1 = value1; + this.value2 = value2; + this.value3 = value3; + this.value4 = value4; + } + + @Override + public int size() + { + return SIZE; + } + + @Override + public A getValue0() + { + return value0; + } + + @Override + public A setValue0(A newValue) + { + A retVal = value0; + setValue(0, newValue); + value0 = newValue; + return retVal; + } + + @Override + public B getValue1() + { + return value1; + } + + @Override + public B setValue1(B newValue) + { + B retVal = value1; + setValue(1, newValue); + value1 = newValue; + return retVal; + } + + @Override + public C getValue2() + { + return value2; + } + + @Override + public C setValue2(C newValue) + { + C retVal = value2; + setValue(2, newValue); + value2 = newValue; + return retVal; + } + + @Override + public D getValue3() + { + return value3; + } + + @Override + public D setValue3(D newValue) + { + D retVal = value3; + setValue(3, newValue); + value3 = newValue; + return retVal; + } + + @Override + public E getValue4() + { + return value4; + } + + @Override + public E setValue4(E newValue) + { + E retVal = value4; + setValue(4, newValue); + value4 = newValue; + return retVal; + } +} diff --git a/src/org/infinity/util/tuples/Sextuple.java b/src/org/infinity/util/tuples/Sextuple.java new file mode 100644 index 000000000..890eae308 --- /dev/null +++ b/src/org/infinity/util/tuples/Sextuple.java @@ -0,0 +1,236 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.util.tuples; + +import java.util.Collection; +import java.util.Iterator; + +/** + * A tuple class that can store six elements. + */ +public class Sextuple extends Tuple + implements TupleValue0, TupleValue1, TupleValue2, TupleValue3, TupleValue4, TupleValue5 +{ + private static final int SIZE = 6; + + private A value0; + private B value1; + private C value2; + private D value3; + private E value4; + private F value5; + + /** + * Creates a new tuple instance with the specified elements. + * @param the tuple element type. + * @param value0 The first element to store in the tuple. + * @param value2 The second element to store in the tuple. + * @param value3 The third element to store in the tuple. + * @param value4 The fourth element to store in the tuple. + * @return A new tuple instance. + */ + public static Sextuple with(A value0, B value1, C value2, D value3, E value4, F value5) + { + return new Sextuple(value0, value1, value2, value3, value4, value5); + } + + /** + * Creates a new tuple from the array. The array must contain at least 6 elements. + * @param the tuple element type. + * @param arr The array to be used as source for the tuple. + * @return A new tuple instance. + */ + public static Sextuple fromArray(T[] arr) + { + if (arr == null) { + throw new IllegalArgumentException("Array cannot be null"); + } + if (arr.length < SIZE) { + throw new IllegalArgumentException(String.format("Array must contain at least %d elements", SIZE)); + } + return new Sextuple(arr[0], arr[1], arr[2], arr[3], arr[4], arr[5]); + } + + /** + * Creates a new tuple from the collection. The collection must contain at least 6 elements. + * @param the tuple element type. + * @param col the collection to be used as source for the tuple. + * @return a new tuple instance. + */ + public static Sextuple fromCollection(Collection col) + { + if (col == null) { + throw new IllegalArgumentException("Collection cannot be null"); + } + if (col.size() < SIZE) { + throw new IllegalArgumentException(String.format("Collection must contain at least %d elements", SIZE)); + } + Iterator iter = col.iterator(); + T el0 = iter.next(); + T el1 = iter.next(); + T el2 = iter.next(); + T el3 = iter.next(); + T el4 = iter.next(); + T el5 = iter.next(); + return new Sextuple(el0, el1, el2, el3, el4, el5); + } + + /** + * Creates a new tuple from the {@code Iterable} object. + * @param the tuple element type. + * @param iterator the {@code Iterable} object to be used as source for the tuple. + * @return a new tuple instance. + */ + public static Sextuple fromIterable(Iterable iterator) + { + return fromIterable(iterator, 0); + } + + /** + * Creates a new tuple from the {@code Iterable} object, starting the specified index. + * @param the tuple element type. + * @param iterator the {@code Iterable} object to be used as source for the tuple. + * @param index start index in {@code Iterable} object. + * @return A new tuple instance. + */ + public static Sextuple fromIterable(Iterable iterator, int index) + { + if (iterator == null) { + throw new IllegalArgumentException("Iterator cannot be null"); + } + + Iterator iter = iterator.iterator(); + for (int i = 0; i < index; i++) { + if (iter.hasNext()) { + iter.next(); + } + i++; + } + + T el0 = iter.hasNext() ? iter.next() : null; + T el1 = iter.hasNext() ? iter.next() : null; + T el2 = iter.hasNext() ? iter.next() : null; + T el3 = iter.hasNext() ? iter.next() : null; + T el4 = iter.hasNext() ? iter.next() : null; + T el5 = iter.hasNext() ? iter.next() : null; + return new Sextuple(el0, el1, el2, el3, el4, el5); + } + + /** + * Constructs a new Triple instance and initializes it with the specified arguments. + * @param value0 the first value of the Sextuple. + * @param value1 the second value of the Sextuple. + * @param value2 the third value of the Sextuple. + * @param value3 the fourth value of the Sextuple. + * @param value4 the fifth value of the Sextuple. + * @param value5 the sixth value of the Sextuple. + */ + public Sextuple(A value0, B value1, C value2, D value3, E value4, F value5) + { + super(value0, value1, value2); + this.value0 = value0; + this.value1 = value1; + this.value2 = value2; + this.value3 = value3; + this.value4 = value4; + this.value5 = value5; + } + + @Override + public int size() + { + return SIZE; + } + + @Override + public A getValue0() + { + return value0; + } + + @Override + public A setValue0(A newValue) + { + A retVal = value0; + setValue(0, newValue); + value0 = newValue; + return retVal; + } + + @Override + public B getValue1() + { + return value1; + } + + @Override + public B setValue1(B newValue) + { + B retVal = value1; + setValue(1, newValue); + value1 = newValue; + return retVal; + } + + @Override + public C getValue2() + { + return value2; + } + + @Override + public C setValue2(C newValue) + { + C retVal = value2; + setValue(2, newValue); + value2 = newValue; + return retVal; + } + + @Override + public D getValue3() + { + return value3; + } + + @Override + public D setValue3(D newValue) + { + D retVal = value3; + setValue(3, newValue); + value3 = newValue; + return retVal; + } + + @Override + public E getValue4() + { + return value4; + } + + @Override + public E setValue4(E newValue) + { + E retVal = value4; + setValue(4, newValue); + value4 = newValue; + return retVal; + } + + @Override + public F getValue5() + { + return value5; + } + + @Override + public F setValue5(F newValue) + { + F retVal = value5; + setValue(5, newValue); + value5 = newValue; + return retVal; + } +} diff --git a/src/org/infinity/util/tuples/Triple.java b/src/org/infinity/util/tuples/Triple.java new file mode 100644 index 000000000..5b7b89916 --- /dev/null +++ b/src/org/infinity/util/tuples/Triple.java @@ -0,0 +1,174 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.util.tuples; + +import java.util.Collection; +import java.util.Iterator; + +/** + * A tuple class that can store three elements. + */ +public class Triple extends Tuple implements TupleValue0, TupleValue1, TupleValue2 +{ + private static final int SIZE = 3; + + private A value0; + private B value1; + private C value2; + + /** + * Creates a new tuple instance with the specified elements. + * @param the tuple element type. + * @param value0 The first element to store in the tuple. + * @param value2 The second element to store in the tuple. + * @param value3 The third element to store in the tuple. + * @return A new tuple instance. + */ + public static Triple with(A value0, B value1, C value2) + { + return new Triple(value0, value1, value2); + } + + /** + * Creates a new tuple from the array. The array must contain at least 3 elements. + * @param the tuple element type. + * @param arr The array to be used as source for the tuple. + * @return A new tuple instance. + */ + public static Triple fromArray(T[] arr) + { + if (arr == null) { + throw new IllegalArgumentException("Array cannot be null"); + } + if (arr.length < SIZE) { + throw new IllegalArgumentException(String.format("Array must contain at least %d elements", SIZE)); + } + return new Triple(arr[0], arr[1], arr[2]); + } + + /** + * Creates a new tuple from the collection. The collection must contain at least 3 elements. + * @param the tuple element type. + * @param col the collection to be used as source for the tuple. + * @return a new tuple instance. + */ + public static Triple fromCollection(Collection col) + { + if (col == null) { + throw new IllegalArgumentException("Collection cannot be null"); + } + if (col.size() < SIZE) { + throw new IllegalArgumentException(String.format("Collection must contain at least %d elements", SIZE)); + } + Iterator iter = col.iterator(); + T el0 = iter.next(); + T el1 = iter.next(); + T el2 = iter.next(); + return new Triple(el0, el1, el2); + } + + /** + * Creates a new tuple from the {@code Iterable} object. + * @param the tuple element type. + * @param iterator the {@code Iterable} object to be used as source for the tuple. + * @return a new tuple instance. + */ + public static Triple fromIterable(Iterable iterator) + { + return fromIterable(iterator, 0); + } + + /** + * Creates a new tuple from the {@code Iterable} object, starting the specified index. + * @param the tuple element type. + * @param iterator the {@code Iterable} object to be used as source for the tuple. + * @param index start index in {@code Iterable} object. + * @return A new tuple instance. + */ + public static Triple fromIterable(Iterable iterator, int index) + { + if (iterator == null) { + throw new IllegalArgumentException("Iterator cannot be null"); + } + + Iterator iter = iterator.iterator(); + for (int i = 0; i < index; i++) { + if (iter.hasNext()) { + iter.next(); + } + i++; + } + + T el0 = iter.hasNext() ? iter.next() : null; + T el1 = iter.hasNext() ? iter.next() : null; + T el2 = iter.hasNext() ? iter.next() : null; + return new Triple(el0, el1, el2); + } + + /** + * Constructs a new Triple instance and initializes it with the specified arguments. + * @param value0 the first value of the Triple. + * @param value1 the second value of the Triple. + * @param value2 the third value of the Triple. + */ + public Triple(A value0, B value1, C value2) + { + super(value0, value1, value2); + this.value0 = value0; + this.value1 = value1; + this.value2 = value2; + } + + @Override + public int size() + { + return SIZE; + } + + @Override + public A getValue0() + { + return value0; + } + + @Override + public A setValue0(A newValue) + { + A retVal = value0; + setValue(0, newValue); + value0 = newValue; + return retVal; + } + + @Override + public B getValue1() + { + return value1; + } + + @Override + public B setValue1(B newValue) + { + B retVal = value1; + setValue(1, newValue); + value1 = newValue; + return retVal; + } + + @Override + public C getValue2() + { + return value2; + } + + @Override + public C setValue2(C newValue) + { + C retVal = value2; + setValue(2, newValue); + value2 = newValue; + return retVal; + } +} diff --git a/src/org/infinity/util/tuples/Tuple.java b/src/org/infinity/util/tuples/Tuple.java new file mode 100644 index 000000000..fca3a962f --- /dev/null +++ b/src/org/infinity/util/tuples/Tuple.java @@ -0,0 +1,233 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.util.tuples; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +public abstract class Tuple implements Iterable, Comparable +{ + private final Object[] values; + private final List valueList; + + protected Tuple(Object... values) + { + this.values = values; + this.valueList = Arrays.asList(this.values); + } + + /** + * Returns the number of elements that can be stored in the tuple. + * @return size of the tuple. + */ + public abstract int size(); + + /** + * Returns the value at the specified element position. + * @param pos the element position in the tuple. + * @return the value. + */ + public Object getValue(int pos) + { + if (pos < 0 || pos >= size()) { + throw new IllegalArgumentException(String.format("Invalid position: %d. Valid range: [0, %d]", pos, size() - 1)); + } + return values[pos]; + } + + /** + * Assigns a new value to specified element position. + * @param pos the element position in the tuple. + * @param value the new value. + * @return the previous value. + */ + public Object setValue(int pos, Object value) + { + if (pos < 0 || pos >= size()) { + throw new IllegalArgumentException(String.format("Invalid position: %d. Valid range: [0, %d]", pos, size() - 1)); + } + Object retVal = values[pos]; + values[pos] = pos; + valueList.set(pos, value); + return retVal; + } + + /** + * Returns an iterator over the elements in this tuple in proper sequence. + */ + public Iterator iterator() + { + return this.valueList.iterator(); + } + + @Override + public String toString() + { + return valueList.toString(); + } + + /** + * Returns {@code true} if this tuple contains the specified element. + * More formally, returns {@code true} if and only if this tuple contains at least one element {@code e} + * such that {@code (o==null ? e==null : o.equals(e))}. + * @param o element whose presence in this tuple is to be tested. + * @return {@code true} if this list contains the specified element. + */ + public boolean contains(Object o) + { + return indexOf(o) >= 0; + } + + /** + * Returns {@code true} if this tuple contains all of the elements of the specified collection. + * @param collection collection to be checked for containment in this tuple. + * @return {@code true} if this tuple contains all of the elements of the specified collection. + */ + public boolean containsAll(Collection collection) + { + for (final Object o : collection) { + if (!contains(o)) { + return false; + } + } + return true; + } + + /** + * Returns {@code true} if this tuple contains all of the elements of the specified array. + * @param values array to be checked for containment in this tuple. + * @return {@code true} if this tuple contains all of the elements of the specified array. + */ + public boolean containsAll(Object... values) + { + if (values == null) { + throw new IllegalArgumentException("Specified argument cannot be null"); + } + for (final Object o : values) { + if (!contains(o)) { + return false; + } + } + return true; + } + + /** + * Returns the index of the first occurrence of the specified element in this tuple, or -1 if this tuple + * does not contain the element. + * More formally, returns the lowest index {@code i} such that {@code (o==null ? get(i)==null : o.equals(get(i)))}, + * or -1 if there is no such index. + * @param o element to search for. + * @return the index of the first occurrence of the specified element in this tuple, + * or -1 if this tuple does not contain the element. + */ + public int indexOf(Object o) + { + int retVal = 0; + for (final Object v : valueList) { + if (v == null) { + if (o == null) { + return retVal; + } + } else { + if (v.equals(o)) { + return retVal; + } + } + retVal++; + } + return -1; + } + + /** + * Returns the index of the last occurrence of the specified element in this tuple, or -1 if this tuple + * does not contain the element. + * More formally, returns the highest index {@code i} such that {@code (o==null ? get(i)==null : o.equals(get(i)))}, + * or -1 if there is no such index. + * @param o element to search for. + * @return the index of the last occurrence of the specified element in this tuple, + * or -1 if this tuple does not contain the element. + */ + public int lastIndexOf(Object o) + { + for (int i = size() - 1; i >= 0; i--) { + final Object v = valueList.get(i); + if (v == null) { + if (o == null) { + return i; + } + } else { + if (v.equals(o)) { + return i; + } + } + } + return -1; + } + + /** + * Returns an unmodifiable list of the elements stored in this tuple. + * @return unmodifiable list with the elements of this tuple. + */ + public List toList() + { + return Collections.unmodifiableList(new ArrayList(valueList)); + } + + /** + * Returns an array with the elements stored in this tuple. + * @return array with the elements of this tuple. + */ + public Object[] toArray() + { + return values.clone(); + } + + @Override + public boolean equals(Object o) + { + if (this == o) { + return true; + } + if (o == null) { + return false; + } + if (getClass() != o.getClass()) { + return false; + } + Tuple other = (Tuple)o; + return valueList.equals(other.valueList); + } + + @Override + public int hashCode() + { + return valueList.hashCode(); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public int compareTo(Tuple o) + { + int len = values.length; + Object[] values2 = o.values; + int len2 = values2.length; + + for (int i = 0; i < len && i < len2; i++) { + Comparable el = (Comparable)values[i]; + Comparable el2 = (Comparable)values2[i]; + + int cmp = el.compareTo(el2); + if (cmp != 0) { + return cmp; + } + } + + return (len - len2); + } +} diff --git a/src/org/infinity/util/tuples/TupleValue0.java b/src/org/infinity/util/tuples/TupleValue0.java new file mode 100644 index 000000000..f16ece6cc --- /dev/null +++ b/src/org/infinity/util/tuples/TupleValue0.java @@ -0,0 +1,24 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.util.tuples; + +/** + * Allows read/write access to the first tuple element. + */ +public interface TupleValue0 +{ + /** + * Returns the first element of the tuple. + * @return first element of the tuple. + */ + public T getValue0(); + + /** + * Assigns a new value to the first element of the tuple. + * @param newValue the new value to assign. + * @return the previously assigned value of the first tuple element. + */ + public T setValue0(T newValue); +} diff --git a/src/org/infinity/util/tuples/TupleValue1.java b/src/org/infinity/util/tuples/TupleValue1.java new file mode 100644 index 000000000..fc2a6b329 --- /dev/null +++ b/src/org/infinity/util/tuples/TupleValue1.java @@ -0,0 +1,24 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.util.tuples; + +/** + * Allows read/write access to the second tuple element. + */ +public interface TupleValue1 +{ + /** + * Returns the second element of the tuple. + * @return second element of the tuple. + */ + public T getValue1(); + + /** + * Assigns a new value to the second element of the tuple. + * @param newValue the new value to assign. + * @return the previously assigned value of the second tuple element. + */ + public T setValue1(T newValue); +} diff --git a/src/org/infinity/util/tuples/TupleValue2.java b/src/org/infinity/util/tuples/TupleValue2.java new file mode 100644 index 000000000..8e4f91f78 --- /dev/null +++ b/src/org/infinity/util/tuples/TupleValue2.java @@ -0,0 +1,24 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.util.tuples; + +/** + * Allows read/write access to the third tuple element. + */ +public interface TupleValue2 +{ + /** + * Returns the third element of the tuple. + * @return third element of the tuple. + */ + public T getValue2(); + + /** + * Assigns a new value to the third element of the tuple. + * @param newValue the new value to assign. + * @return the previously assigned value of the third tuple element. + */ + public T setValue2(T newValue); +} diff --git a/src/org/infinity/util/tuples/TupleValue3.java b/src/org/infinity/util/tuples/TupleValue3.java new file mode 100644 index 000000000..239cacbff --- /dev/null +++ b/src/org/infinity/util/tuples/TupleValue3.java @@ -0,0 +1,24 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.util.tuples; + +/** + * Allows read/write access to the fourth tuple element. + */ +public interface TupleValue3 +{ + /** + * Returns the fourth element of the tuple. + * @return fourth element of the tuple. + */ + public T getValue3(); + + /** + * Assigns a new value to the fourth element of the tuple. + * @param newValue the new value to assign. + * @return the previously assigned value of the fourth tuple element. + */ + public T setValue3(T newValue); +} diff --git a/src/org/infinity/util/tuples/TupleValue4.java b/src/org/infinity/util/tuples/TupleValue4.java new file mode 100644 index 000000000..10b8fa6a7 --- /dev/null +++ b/src/org/infinity/util/tuples/TupleValue4.java @@ -0,0 +1,24 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.util.tuples; + +/** + * Allows read/write access to the fifth tuple element. + */ +public interface TupleValue4 +{ + /** + * Returns the fifth element of the tuple. + * @return fifth element of the tuple. + */ + public T getValue4(); + + /** + * Assigns a new value to the fifth element of the tuple. + * @param newValue the new value to assign. + * @return the previously assigned value of the fifth tuple element. + */ + public T setValue4(T newValue); +} diff --git a/src/org/infinity/util/tuples/TupleValue5.java b/src/org/infinity/util/tuples/TupleValue5.java new file mode 100644 index 000000000..4daa9acc9 --- /dev/null +++ b/src/org/infinity/util/tuples/TupleValue5.java @@ -0,0 +1,24 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.util.tuples; + +/** + * Allows read/write access to the sixth tuple element. + */ +public interface TupleValue5 +{ + /** + * Returns the sixth element of the tuple. + * @return sixth element of the tuple. + */ + public T getValue5(); + + /** + * Assigns a new value to the sixth element of the tuple. + * @param newValue the new value to assign. + * @return the previously assigned value of the sixth tuple element. + */ + public T setValue5(T newValue); +} From 230e4564ebf8e67bc68dfeee68d0b388e4981f75 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 26 Feb 2021 20:18:00 +0100 Subject: [PATCH 014/158] Implement helper methods for throwing conditional exceptions --- src/org/infinity/util/Misc.java | 70 +++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/src/org/infinity/util/Misc.java b/src/org/infinity/util/Misc.java index b4af02055..c3a326964 100644 --- a/src/org/infinity/util/Misc.java +++ b/src/org/infinity/util/Misc.java @@ -8,6 +8,7 @@ import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; +import java.lang.reflect.Constructor; import java.nio.charset.Charset; import java.util.Comparator; import java.util.concurrent.ArrayBlockingQueue; @@ -540,6 +541,75 @@ public static String safeToString(Object o) return (o != null) ? o.toString() : ""; } + /** + * This method throws a general {@link Exception} without message if the specified condition isn't met. + * @param cond the condition to meet. + * @throws Exception + */ + public static void requireCondition(boolean cond) throws Exception + { + requireCondition(cond, null, null); + } + + /** + * This method throws a general {@link Exception} with associated message if the specified condition isn't met. + * @param cond the condition to meet. + * @param message the exception message. Can be {@code null}. + * @throws Exception + */ + public static void requireCondition(boolean cond, String message) throws Exception + { + requireCondition(cond, message, null); + } + + /** + * This method throws a specialized exception without message if the specified condition isn't met. + * @param cond the condition to meet. + * @param classEx the exception class to throw. + * @throws Exception + */ + public static void requireCondition(boolean cond, Class classEx) throws Exception + { + requireCondition(cond, null, classEx); + } + + /** + * This method throws a specialized exception with associated message if the specified condition isn't met. + * @param cond the condition to meet. + * @param message the exception message. Can be {@code null}. + * @param classEx the exception class to throw. + * @throws Exception + */ + public static void requireCondition(boolean cond, String message, Class classEx) throws Exception + { + if (!cond) { + if (message != null && message.isEmpty()) + { + message = null; + } + + if (classEx == null) { + classEx = Exception.class; + } + + for (final Class cls : new Class[] { classEx, Exception.class }) { + Object ex = null; + if (message != null) { + Constructor ctor = cls.getConstructor(String.class); + ex = ctor.newInstance(message); + } else { + Constructor ctor = cls.getConstructor(); + ex = ctor.newInstance(); + } + + if (ex instanceof Exception) { + throw (Exception)ex; + } + } + } + } + + // Contains static functions only private Misc() {} } From dc365c1dde8ecdcf0eb909031b6ea0c183200f0b Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 26 Feb 2021 20:19:13 +0100 Subject: [PATCH 015/158] Expand functionality of IniMap classes --- src/org/infinity/util/IniMap.java | 34 ++++++++++++++++++-- src/org/infinity/util/IniMapEntry.java | 3 ++ src/org/infinity/util/IniMapSection.java | 40 ++++++++++++++++++++++-- 3 files changed, 72 insertions(+), 5 deletions(-) diff --git a/src/org/infinity/util/IniMap.java b/src/org/infinity/util/IniMap.java index 244eaea9f..ade9eda19 100644 --- a/src/org/infinity/util/IniMap.java +++ b/src/org/infinity/util/IniMap.java @@ -26,6 +26,27 @@ public class IniMap implements Iterable private static final Pattern LINE_SPLIT = Pattern.compile("\r?\n"); private final List entries = new ArrayList<>(); + /** + * Creates a {@code IniMap} instance from the specified collection of strings. + * Each string is considered as a separate line. + * @param lines Collection of strings + * @return an {@code IniMap} object with the content of the strings. Returns {@code null} if no strings were provided. + */ + public static IniMap from(List lines) + { + IniMap ini = null; + if (lines != null) { + StringBuilder sb = new StringBuilder(); + for(final String line : lines) { + sb.append(line != null ? line : "").append('\n'); + } + if (sb.length() > 0) { + ini = new IniMap(sb.toString(), true); + } + } + return ini; + } + /** * Parses specified text content as {@code ini} file with comments (comment * starts from {@code //} and continues to end of string). @@ -67,7 +88,10 @@ public IniMap(CharSequence content, boolean ignoreComments) curSectionLine = i; section.clear(); } else { // potential section entry - section.add(parseEntry(line, i, ignoreComments)); + IniMapEntry entry = parseEntry(line, i, ignoreComments); + if (entry != null) { + section.add(entry); + } } } @@ -143,7 +167,7 @@ public String toString() * to end of line) will not be treated specially (i.e. will not be considered * as comments) * - * @return New object, that represent entry in INI. Never {@code null} + * @return New object, that represent entry in INI. Returns {@code null} if entry is not valid. */ private IniMapEntry parseEntry(String line, int lineNr, boolean ignoreComments) { @@ -167,7 +191,11 @@ private IniMapEntry parseEntry(String line, int lineNr, boolean ignoreComments) value = line.substring(start, pos).trim(); } - return new IniMapEntry(key, value, lineNr); + if (key != null || value != null) { + return new IniMapEntry(key, value, lineNr); + } else { + return null; + } } private static String readResource(ResourceEntry entry) diff --git a/src/org/infinity/util/IniMapEntry.java b/src/org/infinity/util/IniMapEntry.java index 2c6127902..9ea5739e8 100644 --- a/src/org/infinity/util/IniMapEntry.java +++ b/src/org/infinity/util/IniMapEntry.java @@ -40,6 +40,9 @@ public IniMapEntry(String key, String value, int line) public Integer getIntValue() { return value == null ? null : Integer.valueOf(value); } public int getIntValue(int defValue) { return value == null ? defValue : Integer.valueOf(value); } + public Double getDoubleValue() { return value == null ? null : Double.valueOf(value); } + public double getDoubleValue(double defValue) { return value == null ? defValue : Double.valueOf(value); } + public StringRef getStringRefValue() { return value == null ? null : new StringRef(key, Integer.valueOf(value)); } diff --git a/src/org/infinity/util/IniMapSection.java b/src/org/infinity/util/IniMapSection.java index 02ae35345..758d8216d 100644 --- a/src/org/infinity/util/IniMapSection.java +++ b/src/org/infinity/util/IniMapSection.java @@ -109,13 +109,29 @@ public IniMapEntry getEntry(String key) * {@code key}, or {@code null}, if no such pair exists */ public String getAsString(String key) + { + return getAsString(key, null); + } + /** + * Returns value for specified key as string. Returns a default value if key or + * value are not present. + * + * @param key Name of value in key-value pair. This key must not contain spaces + * because all spaces are cut off during parsing if ini-file, so key with + * leading or trailing spaces never will be found + * + * @return Value of first key-value pair, which key equals (ignoring case) parameter + * {@code key}, or {@code defValue}, if no such pair exists or value is + * not defined. + */ + public String getAsString(String key, String defValue) { final IniMapEntry entry = getEntry(key); - return entry == null ? null : entry.getValue(); + return entry == null ? defValue : entry.getValue(); } /** * Returns value for specified key as integer or returns default value, if key - * or valus is not presented in the file. + * or value is not presented in the file. * * @param key Name of value in key-value pair. This key must not contain spaces * because all spaces are cut off during parsing if ini-file, so key with @@ -133,6 +149,26 @@ public int getAsInteger(String key, int defValue) final IniMapEntry entry = getEntry(key); return entry == null ? defValue : entry.getIntValue(defValue); } + /** + * Returns value for specified key as double or returns default value, if key + * or value is not presented in the file. + * + * @param key Name of value in key-value pair. This key must not contain spaces + * because all spaces are cut off during parsing if ini-file, so key with + * leading or trailing spaces never will be found + * @param defValue Default value that will be returned, if key or value does not exist + * + * @return Value of first key-value pair, which key equals (ignoring case) parameter + * {@code key}, converted to integer, or {@code defValue}, if no such pair + * exists or value is not defined + * + * @throws NumberFormatException If value is not a double + */ + public double getAsDouble(String key, double defValue) + { + final IniMapEntry entry = getEntry(key); + return entry == null ? defValue : entry.getDoubleValue(defValue); + } /** * Returns value for specified key as string reference. {@link StringRef#getName} * will return {@code key} as its name. From e590c3d6a0e8f2e73eb44be8d6d144740974ad50 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 26 Feb 2021 20:19:46 +0100 Subject: [PATCH 016/158] Various improvements and optimizations in BamDecoder classes --- .../resource/graphics/BamDecoder.java | 72 +++++++++--- .../resource/graphics/BamV1Decoder.java | 33 ++++++ .../resource/graphics/BamV2Decoder.java | 36 ++++++ .../resource/graphics/PseudoBamDecoder.java | 107 +++++++++++++----- 4 files changed, 202 insertions(+), 46 deletions(-) diff --git a/src/org/infinity/resource/graphics/BamDecoder.java b/src/org/infinity/resource/graphics/BamDecoder.java index e7ea8eac8..6e1e7305b 100644 --- a/src/org/infinity/resource/graphics/BamDecoder.java +++ b/src/org/infinity/resource/graphics/BamDecoder.java @@ -168,6 +168,39 @@ protected void setType(Type type) this.type = type; } + @Override + public int hashCode() + { + int hash = 7; + hash = 31 * hash + ((type == null) ? 0 : type.hashCode()); + hash = 31 * hash + ((bamEntry == null) ? 0 : bamEntry.hashCode()); + return hash; + } + + @Override + public boolean equals(Object o) + { + if (o == this) { + return true; + } + if (!(o instanceof BamDecoder)) { + return false; + } + BamDecoder other = (BamDecoder)o; + boolean retVal = (this.type == null && other.type == null) || + (this.type != null && this.type.equals(other.type)); + retVal &= (this.bamEntry == null && other.bamEntry == null) || + (this.bamEntry != null && this.bamEntry.equals(other.bamEntry)); + return retVal; + } + + @Override + public String toString() + { + String retVal = "entry=" + ((bamEntry != null) ? bamEntry.toString() : "(null)"); + retVal += ", Type=" + ((type != null) ? type.toString() : "(null)"); + return retVal; + } //-------------------------- INNER CLASSES -------------------------- @@ -188,14 +221,19 @@ public interface FrameEntry */ public static abstract class BamControl { - /** - * Definitions of how to render BAM frames.
- * Individual: Each frame is drawn individually. The resulting image dimension is defined - * by the drawn frame. Does not take frame centers into account.
- * Shared: Each frame is drawn onto a canvas of fixed dimension that is big enough to hold - * every single frame without cropping or resizing. Takes frame centers into account. - */ - public enum Mode { INDIVIDUAL, SHARED } + /** Definitions of how to render BAM frames. */ + public enum Mode { + /** + * Each frame is drawn individually. The resulting image dimension is defined by the + * drawn frame. Does not take frame centers into account. + */ + INDIVIDUAL, + /** + * Each frame is drawn onto a canvas of fixed dimension that is big enough to hold + * every single frame without cropping or resizing. Takes frame centers into account. + */ + SHARED + } private final BamDecoder parent; @@ -376,19 +414,21 @@ protected Rectangle calculateSharedBamSize(Rectangle rect, boolean cycleBased, b if (cycleBased) { for (int i = 0; i < cycleFrameCount(); i++) { int frame = cycleGetFrameIndexAbsolute(i); - int cx = isMirrored ? (parent.getFrameInfo(frame).getWidth() - parent.getFrameInfo(frame).getCenterX() - 1) : parent.getFrameInfo(frame).getCenterX(); + FrameEntry fe = parent.getFrameInfo(frame); + int cx = isMirrored ? (fe.getWidth() - fe.getCenterX() - 1) : fe.getCenterX(); x1 = Math.min(x1, -cx); - y1 = Math.min(y1, -parent.getFrameInfo(frame).getCenterY()); - x2 = Math.max(x2, parent.getFrameInfo(frame).getWidth() - cx); - y2 = Math.max(y2, parent.getFrameInfo(frame).getHeight() - parent.getFrameInfo(frame).getCenterY()); + y1 = Math.min(y1, -fe.getCenterY()); + x2 = Math.max(x2, fe.getWidth() - cx); + y2 = Math.max(y2, fe.getHeight() - fe.getCenterY()); } } else { for (int i = 0; i < parent.frameCount(); i++) { - int cx = isMirrored ? (parent.getFrameInfo(i).getWidth() - parent.getFrameInfo(i).getCenterX() - 1) : parent.getFrameInfo(i).getCenterX(); + FrameEntry fe = parent.getFrameInfo(i); + int cx = isMirrored ? (fe.getWidth() - fe.getCenterX() - 1) : fe.getCenterX(); x1 = Math.min(x1, -cx); - y1 = Math.min(y1, -parent.getFrameInfo(i).getCenterY()); - x2 = Math.max(x2, parent.getFrameInfo(i).getWidth() - cx); - y2 = Math.max(y2, parent.getFrameInfo(i).getHeight() - parent.getFrameInfo(i).getCenterY()); + y1 = Math.min(y1, -fe.getCenterY()); + x2 = Math.max(x2, fe.getWidth() - cx); + y2 = Math.max(y2, fe.getHeight() - fe.getCenterY()); } } if (x1 == Integer.MAX_VALUE) x1 = 0; diff --git a/src/org/infinity/resource/graphics/BamV1Decoder.java b/src/org/infinity/resource/graphics/BamV1Decoder.java index 2b6ff9466..3aed6b2b1 100644 --- a/src/org/infinity/resource/graphics/BamV1Decoder.java +++ b/src/org/infinity/resource/graphics/BamV1Decoder.java @@ -338,6 +338,39 @@ private void decodeFrame(BamControl control, int frameIdx, Image canvas) } } + @Override + public int hashCode() + { + int hash = super.hashCode(); + hash = 31 * hash + ((listFrames == null) ? 0 : listFrames.hashCode()); + hash = 31 * hash + ((listCycles == null) ? 0 : listCycles.hashCode()); + hash = 31 * hash + ((bamBuffer == null) ? 0 : bamBuffer.hashCode()); + hash = 31 * hash + ((bamPalette == null) ? 0 : bamPalette.hashCode()); + hash = 31 * hash + rleIndex; + return hash; + } + + @Override + public boolean equals(Object o) + { + if (!(o instanceof BamV1Decoder)) { + return false; + } + boolean retVal = super.equals(o); + if (retVal) { + BamV1Decoder other = (BamV1Decoder)o; + retVal &= (this.listFrames == null && other.listFrames == null) || + (this.listFrames != null && this.listFrames.equals(other.listFrames)); + retVal &= (this.listCycles == null && other.listCycles == null) || + (this.listCycles != null && this.listCycles.equals(other.listCycles)); + retVal &= (this.bamBuffer == null && other.bamBuffer == null) || + (this.bamBuffer != null && this.bamBuffer.equals(other.bamBuffer)); + retVal &= (this.bamPalette == null && other.bamPalette == null) || + (this.bamPalette != null && this.bamPalette.equals(other.bamPalette)); + retVal &= (this.rleIndex == other.rleIndex); + } + return retVal; + } //-------------------------- INNER CLASSES -------------------------- diff --git a/src/org/infinity/resource/graphics/BamV2Decoder.java b/src/org/infinity/resource/graphics/BamV2Decoder.java index bf061f011..6f8ad9a40 100644 --- a/src/org/infinity/resource/graphics/BamV2Decoder.java +++ b/src/org/infinity/resource/graphics/BamV2Decoder.java @@ -323,6 +323,42 @@ private void renderFrame(BamControl control, int frameIdx, Image canvas) } } + @Override + public int hashCode() + { + int hash = super.hashCode(); + hash = 31 * hash + ((pvrIndices == null) ? 0 : pvrIndices.hashCode()); + hash = 31 * hash + ((listFrames == null) ? 0 : listFrames.hashCode()); + hash = 31 * hash + ((listCycles == null) ? 0 : listCycles.hashCode()); + hash = 31 * hash + ((bamBuffer == null) ? 0 : bamBuffer.hashCode()); + hash = 31 * hash + ((bamPath == null) ? 0 : bamPath.hashCode()); + hash = 31 * hash + numDataBlocks; + return hash; + } + + @Override + public boolean equals(Object o) + { + if (!(o instanceof BamV2Decoder)) { + return false; + } + boolean retVal = super.equals(o); + if (retVal) { + BamV2Decoder other = (BamV2Decoder)o; + retVal &= (this.pvrIndices == null && other.pvrIndices == null) || + (this.pvrIndices != null && this.pvrIndices.equals(other.pvrIndices)); + retVal &= (this.listFrames == null && other.listFrames == null) || + (this.listFrames != null && this.listFrames.equals(other.listFrames)); + retVal &= (this.listCycles == null && other.listCycles == null) || + (this.listCycles != null && this.listCycles.equals(other.listCycles)); + retVal &= (this.bamBuffer == null && other.bamBuffer == null) || + (this.bamBuffer != null && this.bamBuffer.equals(other.bamBuffer)); + retVal &= (this.bamPath == null && other.bamPath == null) || + (this.bamPath != null && this.bamPath.equals(other.bamPath)); + retVal &= (this.numDataBlocks == other.numDataBlocks); + } + return retVal; + } //-------------------------- INNER CLASSES -------------------------- diff --git a/src/org/infinity/resource/graphics/PseudoBamDecoder.java b/src/org/infinity/resource/graphics/PseudoBamDecoder.java index 23cd43e6f..0c9d6def1 100644 --- a/src/org/infinity/resource/graphics/PseudoBamDecoder.java +++ b/src/org/infinity/resource/graphics/PseudoBamDecoder.java @@ -194,49 +194,54 @@ public void setCyclesList(List cyclesList) /** * Adds a new frame to the end of the frame list. Center position defaults to (0, 0). * @param image The image to add. + * @return The index of the added frame or -1 if frame could not be added. */ - public void frameAdd(BufferedImage image) + public int frameAdd(BufferedImage image) { - frameInsert(listFrames.size(), new BufferedImage[]{image}, new Point[0]); + return frameInsert(listFrames.size(), new BufferedImage[]{image}, new Point[0]); } /** * Adds a new frame to the end of the frame list. * @param image The image to add. * @param center The center position of the image. + * @return The index of the added frame or -1 if frame could not be added. */ - public void frameAdd(BufferedImage image, Point center) + public int frameAdd(BufferedImage image, Point center) { - frameInsert(listFrames.size(), new BufferedImage[]{image}, new Point[]{center}); + return frameInsert(listFrames.size(), new BufferedImage[]{image}, new Point[]{center}); } /** * Adds the list of frames to the end of the frame list. Center positions default to (0, 0). * @param images An array containing the images to add. + * @return The index of the first added frame or -1 if frames could not be added. */ - public void frameAdd(BufferedImage[] images) + public int frameAdd(BufferedImage[] images) { - frameInsert(listFrames.size(), images, new Point[0]); + return frameInsert(listFrames.size(), images, new Point[0]); } /** * Adds the list of frames to the end of the frame list. * @param images An array containing the images to add. * @param centers An array of center positions corresponding with the images. + * @return The index of the first added frame or -1 if frames could not be added. */ - public void frameAdd(BufferedImage[] images, Point[] centers) + public int frameAdd(BufferedImage[] images, Point[] centers) { - frameInsert(listFrames.size(), images, centers); + return frameInsert(listFrames.size(), images, centers); } /** * Inserts a frame at the specified position. Center position defaults to (0, 0). * @param frameIdx The position for the frame to insert. * @param image The image to insert. + * @return The index of the inserted frame or -1 if frame could not be inserted. */ - public void frameInsert(int frameIdx, BufferedImage image) + public int frameInsert(int frameIdx, BufferedImage image) { - frameInsert(frameIdx, new BufferedImage[]{image}, new Point[0]); + return frameInsert(frameIdx, new BufferedImage[]{image}, new Point[0]); } /** @@ -244,20 +249,22 @@ public void frameInsert(int frameIdx, BufferedImage image) * @param frameIdx The position for the frame to insert. * @param image The image to insert. * @param center The center position of the image. + * @return The index of the inserted frame or -1 if frame could not be inserted. */ - public void frameInsert(int frameIdx, BufferedImage image, Point center) + public int frameInsert(int frameIdx, BufferedImage image, Point center) { - frameInsert(frameIdx, new BufferedImage[]{image}, new Point[]{center}); + return frameInsert(frameIdx, new BufferedImage[]{image}, new Point[]{center}); } /** * Inserts an array of frames at the specified position. Center positions default to (0, 0). * @param frameIdx The position for the frames to insert. * @param images An array containing the images to insert. + * @return The index of the first inserted frame or -1 if frames could not be inserted. */ - public void frameInsert(int frameIdx, BufferedImage[] images) + public int frameInsert(int frameIdx, BufferedImage[] images) { - frameInsert(frameIdx, images, new Point[0]); + return frameInsert(frameIdx, images, new Point[0]); } /** @@ -265,8 +272,9 @@ public void frameInsert(int frameIdx, BufferedImage[] images) * @param frameIdx The position for the frames to insert. * @param images An array containing the images to insert. * @param centers An array of center positions corresponding with the images. + * @return The index of the first inserted frame or -1 if frames could not be inserted. */ - public void frameInsert(int frameIdx, BufferedImage[] images, Point[] centers) + public int frameInsert(int frameIdx, BufferedImage[] images, Point[] centers) { if (frameIdx >= 0 && frameIdx <= listFrames.size() && images != null) { for (int i = 0; i < images.length; i++) { @@ -277,7 +285,9 @@ public void frameInsert(int frameIdx, BufferedImage[] images, Point[] centers) } listFrames.add(frameIdx+i, new PseudoBamFrameEntry(images[i], x, y)); } + return frameIdx; } + return -1; } /** @@ -1354,6 +1364,34 @@ private boolean createPvrzPages(Path path, DxtEncoder.DxtType dxtType, List= 0 && cycleIdx <= getDecoder().listCycles.size()) { PseudoBamCycleEntry ce = new PseudoBamCycleEntry(indices); getDecoder().listCycles.add(cycleIdx, ce); update(); + return cycleIdx; } + return -1; } /** Removes the cycle at the specified position. */ @@ -1607,20 +1650,24 @@ public int cycleMove(int cycleIdx, int offset) /** Adds frame indices to the specified cycle. */ - public void cycleAddFrames(int cycleIdx, int[] indices) + public int cycleAddFrames(int cycleIdx, int[] indices) { if (cycleIdx >= 0 && cycleIdx < getDecoder().listCycles.size()) { - cycleInsertFrames(cycleIdx, getDecoder().listCycles.get(cycleIdx).size(), indices); + return cycleInsertFrames(cycleIdx, getDecoder().listCycles.get(cycleIdx).size(), indices); } + return -1; } /** Inserts frame indices to the cycle at the specified position. */ - public void cycleInsertFrames(int cycleIdx, int pos, int[] indices) + public int cycleInsertFrames(int cycleIdx, int pos, int[] indices) { if (cycleIdx >= 0 && cycleIdx < getDecoder().listCycles.size()) { - getDecoder().listCycles.get(cycleIdx).insert(pos, indices); - update(); + if (getDecoder().listCycles.get(cycleIdx).insert(pos, indices)) { + update(); + return pos; + } } + return -1; } /** Removes one frame index from the cycle at the specified position. */ From 7f9abe9590fbea5f99cb16bd753d647d6d24ff68 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sat, 27 Feb 2021 20:32:34 +0100 Subject: [PATCH 017/158] Improve STO flags labels --- src/org/infinity/resource/sto/StoResource.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/org/infinity/resource/sto/StoResource.java b/src/org/infinity/resource/sto/StoResource.java index 92d63e06e..7ed6894ff 100644 --- a/src/org/infinity/resource/sto/StoResource.java +++ b/src/org/infinity/resource/sto/StoResource.java @@ -79,11 +79,11 @@ public final class StoResource extends AbstractStruct implements Resource, HasCh // private static final String[] s_flag = {"Can't do anything", "Can buy", "Can sell", "Can identify", // "Can steal", "Can buy cures", "Can donate", // "Can buy drinks", "", "", "Quality Bit 0 (BAM)", "Quality Bit 1 (BAM)"}; - public static final String[] s_flag_bg2 = {"Can only rest", "Can buy", "Can sell", "Can identify", - "Can steal", "Can donate;Unused in Enhanced Editions", "Can buy cures", - "Can buy drinks", null, "EE: Disable donation screen;Disables donation screen in temple stores", - "Tavern quality 1", "Tavern quality 2", null, "Fence", "EE: Ignore reputation", - "Ex: Toggle recharge", "EE: Can sell critical"}; + public static final String[] s_flag_bg2 = {"User can only rest", "User can buy", "User can sell", "User can identify", + "User can steal", "User can donate;Unused in Enhanced Editions", "User can purchase cures", + "User can purchase drinks", null, "EE: Disable donation screen;Disables donation screen in temple stores", + "Tavern quality 1", "Tavern quality 2", null, "User can sell stolen goods", "EE: Ignore reputation", + "Ex: Toggle item recharge", "EE: User can sell critical items"}; public static final String[] s_rooms = {"No rooms available", "Peasant", "Merchant", "Noble", "Royal"}; private StructHexViewer hexViewer; From cdfbb89b3d3002a6c5eda28a1df916ea99a5c0d3 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sat, 27 Feb 2021 20:52:16 +0100 Subject: [PATCH 018/158] Add new feature: Creature animation decoder and quick viewer - added sprite decoder: - support for all available animation types - detection based on available INI definitions (EE 2.0+), internal lookup tables and autodetection (via ANISND.IDS and ANIMATE.SRC) - added CRE quick viewer: accessible from CRE viewer panel - added Infinity Animations detection to Profile class --- src/org/infinity/NearInfinity.java | 2 + src/org/infinity/resource/Profile.java | 59 +- .../infinity/resource/cre/CreResource.java | 11 + src/org/infinity/resource/cre/Viewer.java | 26 + .../resource/cre/ViewerAnimation.java | 517 + .../resource/cre/decoder/AmbientDecoder.java | 163 + .../cre/decoder/AmbientStaticDecoder.java | 141 + .../cre/decoder/ArmoredBaseDecoder.java | 249 + .../cre/decoder/CharacterDecoder.java | 461 + .../cre/decoder/CharacterOldDecoder.java | 385 + .../resource/cre/decoder/EffectDecoder.java | 257 + .../resource/cre/decoder/FlyingDecoder.java | 116 + .../cre/decoder/MonsterAnkhegDecoder.java | 178 + .../resource/cre/decoder/MonsterDecoder.java | 336 + .../cre/decoder/MonsterIcewindDecoder.java | 221 + .../cre/decoder/MonsterLarge16Decoder.java | 144 + .../cre/decoder/MonsterLargeDecoder.java | 137 + .../cre/decoder/MonsterLayeredDecoder.java | 214 + .../decoder/MonsterLayeredSpellDecoder.java | 246 + .../cre/decoder/MonsterMultiDecoder.java | 323 + .../cre/decoder/MonsterMultiNewDecoder.java | 205 + .../cre/decoder/MonsterOldDecoder.java | 150 + .../cre/decoder/MonsterPlanescapeDecoder.java | 503 + .../cre/decoder/MonsterQuadrantDecoder.java | 203 + .../cre/decoder/QuadrantsBaseDecoder.java | 39 + .../resource/cre/decoder/SpriteDecoder.java | 2615 +++++ .../resource/cre/decoder/SpriteUtils.java | 1256 +++ .../cre/decoder/TownStaticDecoder.java | 126 + .../cre/decoder/internal/ColorInfo.java | 118 + .../cre/decoder/internal/CreatureInfo.java | 818 ++ .../cre/decoder/internal/CycleDef.java | 183 + .../decoder/internal/DecoderAttribute.java | 118 + .../resource/cre/decoder/internal/DirDef.java | 106 + .../cre/decoder/internal/FrameInfo.java | 85 + .../cre/decoder/internal/ItemInfo.java | 293 + .../cre/decoder/internal/SegmentDef.java | 357 + .../resource/cre/decoder/internal/SeqDef.java | 233 + .../cre/decoder/tables/InfinityTables.java | 255 + .../cre/decoder/tables/SpriteTables.java | 409 + .../cre/decoder/tables/avatars-bg1.2da | 212 + .../cre/decoder/tables/avatars-bg2ee.2da | 515 + .../cre/decoder/tables/avatars-bg2soa.2da | 357 + .../cre/decoder/tables/avatars-bg2tob.2da | 373 + .../cre/decoder/tables/avatars-bgee.2da | 519 + .../cre/decoder/tables/avatars-eet.2da | 519 + .../cre/decoder/tables/avatars-iwd.2da | 273 + .../cre/decoder/tables/avatars-iwd2.2da | 384 + .../cre/decoder/tables/avatars-iwdee.2da | 453 + .../cre/decoder/tables/avatars-iwdhow.2da | 303 + .../cre/decoder/tables/avatars-pst.2da | 110 + .../cre/decoder/tables/avatars-pstee.2da | 105 + .../decoder/tables/infinityanimations-v5.ids | 8724 +++++++++++++++++ .../decoder/tables/infinityanimations-v6.ids | 8724 +++++++++++++++++ .../resource/cre/decoder/tables/notes.txt | 73 + .../resource/key/BufferedResourceEntry.java | 136 + 55 files changed, 34037 insertions(+), 1 deletion(-) create mode 100644 src/org/infinity/resource/cre/ViewerAnimation.java create mode 100644 src/org/infinity/resource/cre/decoder/AmbientDecoder.java create mode 100644 src/org/infinity/resource/cre/decoder/AmbientStaticDecoder.java create mode 100644 src/org/infinity/resource/cre/decoder/ArmoredBaseDecoder.java create mode 100644 src/org/infinity/resource/cre/decoder/CharacterDecoder.java create mode 100644 src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java create mode 100644 src/org/infinity/resource/cre/decoder/EffectDecoder.java create mode 100644 src/org/infinity/resource/cre/decoder/FlyingDecoder.java create mode 100644 src/org/infinity/resource/cre/decoder/MonsterAnkhegDecoder.java create mode 100644 src/org/infinity/resource/cre/decoder/MonsterDecoder.java create mode 100644 src/org/infinity/resource/cre/decoder/MonsterIcewindDecoder.java create mode 100644 src/org/infinity/resource/cre/decoder/MonsterLarge16Decoder.java create mode 100644 src/org/infinity/resource/cre/decoder/MonsterLargeDecoder.java create mode 100644 src/org/infinity/resource/cre/decoder/MonsterLayeredDecoder.java create mode 100644 src/org/infinity/resource/cre/decoder/MonsterLayeredSpellDecoder.java create mode 100644 src/org/infinity/resource/cre/decoder/MonsterMultiDecoder.java create mode 100644 src/org/infinity/resource/cre/decoder/MonsterMultiNewDecoder.java create mode 100644 src/org/infinity/resource/cre/decoder/MonsterOldDecoder.java create mode 100644 src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java create mode 100644 src/org/infinity/resource/cre/decoder/MonsterQuadrantDecoder.java create mode 100644 src/org/infinity/resource/cre/decoder/QuadrantsBaseDecoder.java create mode 100644 src/org/infinity/resource/cre/decoder/SpriteDecoder.java create mode 100644 src/org/infinity/resource/cre/decoder/SpriteUtils.java create mode 100644 src/org/infinity/resource/cre/decoder/TownStaticDecoder.java create mode 100644 src/org/infinity/resource/cre/decoder/internal/ColorInfo.java create mode 100644 src/org/infinity/resource/cre/decoder/internal/CreatureInfo.java create mode 100644 src/org/infinity/resource/cre/decoder/internal/CycleDef.java create mode 100644 src/org/infinity/resource/cre/decoder/internal/DecoderAttribute.java create mode 100644 src/org/infinity/resource/cre/decoder/internal/DirDef.java create mode 100644 src/org/infinity/resource/cre/decoder/internal/FrameInfo.java create mode 100644 src/org/infinity/resource/cre/decoder/internal/ItemInfo.java create mode 100644 src/org/infinity/resource/cre/decoder/internal/SegmentDef.java create mode 100644 src/org/infinity/resource/cre/decoder/internal/SeqDef.java create mode 100644 src/org/infinity/resource/cre/decoder/tables/InfinityTables.java create mode 100644 src/org/infinity/resource/cre/decoder/tables/SpriteTables.java create mode 100644 src/org/infinity/resource/cre/decoder/tables/avatars-bg1.2da create mode 100644 src/org/infinity/resource/cre/decoder/tables/avatars-bg2ee.2da create mode 100644 src/org/infinity/resource/cre/decoder/tables/avatars-bg2soa.2da create mode 100644 src/org/infinity/resource/cre/decoder/tables/avatars-bg2tob.2da create mode 100644 src/org/infinity/resource/cre/decoder/tables/avatars-bgee.2da create mode 100644 src/org/infinity/resource/cre/decoder/tables/avatars-eet.2da create mode 100644 src/org/infinity/resource/cre/decoder/tables/avatars-iwd.2da create mode 100644 src/org/infinity/resource/cre/decoder/tables/avatars-iwd2.2da create mode 100644 src/org/infinity/resource/cre/decoder/tables/avatars-iwdee.2da create mode 100644 src/org/infinity/resource/cre/decoder/tables/avatars-iwdhow.2da create mode 100644 src/org/infinity/resource/cre/decoder/tables/avatars-pst.2da create mode 100644 src/org/infinity/resource/cre/decoder/tables/avatars-pstee.2da create mode 100644 src/org/infinity/resource/cre/decoder/tables/infinityanimations-v5.ids create mode 100644 src/org/infinity/resource/cre/decoder/tables/infinityanimations-v6.ids create mode 100644 src/org/infinity/resource/cre/decoder/tables/notes.txt create mode 100644 src/org/infinity/resource/key/BufferedResourceEntry.java diff --git a/src/org/infinity/NearInfinity.java b/src/org/infinity/NearInfinity.java index d09bf4eea..549d2d871 100644 --- a/src/org/infinity/NearInfinity.java +++ b/src/org/infinity/NearInfinity.java @@ -91,6 +91,7 @@ import org.infinity.resource.Viewable; import org.infinity.resource.ViewableContainer; import org.infinity.resource.bcs.Signatures; +import org.infinity.resource.cre.decoder.SpriteUtils; import org.infinity.resource.key.FileResourceEntry; import org.infinity.resource.key.ResourceEntry; import org.infinity.resource.key.ResourceTreeModel; @@ -1043,6 +1044,7 @@ private static void clearCache(boolean refreshOnly) StringTable.resetAll(); ProRef.clearCache(); Signatures.clearCache(); + SpriteUtils.clearCache(); } private static void showProgress(String msg, int max) diff --git a/src/org/infinity/resource/Profile.java b/src/org/infinity/resource/Profile.java index 57b74ebd8..5a56af799 100644 --- a/src/org/infinity/resource/Profile.java +++ b/src/org/infinity/resource/Profile.java @@ -7,7 +7,10 @@ import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; +import java.io.EOFException; +import java.io.File; import java.io.IOException; +import java.io.RandomAccessFile; import java.nio.charset.StandardCharsets; import java.nio.file.DirectoryStream; import java.nio.file.FileSystem; @@ -237,7 +240,7 @@ public enum Key { * (Sorted by priority in descending order for Enhanced Editions, * sorted by entries found in ini file for non-enhanced games) */ GET_GAME_BIFF_FOLDERS, - /** Property: {@code Map} Map of "Equipped appearance" codes with associated + /** Property: ({@code Map}) Map of "Equipped appearance" codes with associated * descriptions. Map is generated on first call of {@code getEquippedAppearanceMap()}. */ GET_GAME_EQUIPPED_APPEARANCES, @@ -249,6 +252,14 @@ public enum Key { IS_GAME_EEEX, /** Property: ({@code Boolean}) Has type of current game been forcibly set? */ IS_FORCED_GAME, + /** Property: ({@code Integer}) Returns the Infinity Animations installed version: + *
+     *            0: not installed
+     *            1: old IA format (v5 or earlier)
+     *            2: new format (v6 or later)
+     * 
+ */ + GET_INFINITY_ANIMATIONS, /** Property: ({@code Boolean}) Are {@code 2DA} resources supported? */ IS_SUPPORTED_2DA, @@ -2291,6 +2302,52 @@ private void initFeatures() addEntry(Key.IS_GAME_EEEX, Type.BOOLEAN, Boolean.FALSE); } + // Is Infinity Animations installed? + boolean isIAv1 = false; + boolean isIAv2 = false; + if (engine == Engine.BG2) { + Path exe = FileManager.queryExisting(getGameRoot(), "bgmain.exe"); + if (exe != null) { + File exeFile = exe.toFile(); + if (exeFile != null && exeFile.length() == 7839790L) { + try (RandomAccessFile raf = new RandomAccessFile(exeFile, "r")) { + // checking key signatures + final int[] sigCheckV1 = { 0x3db6d84, 0xc6004c48, 0x54464958, 0x004141de, 0xf9 }; + final int[] sigCheckV2 = { 0x3db6d84, 0xc6004c48, 0x54464958, 0x0041412d, 0xf9 }; + long ofs[] = { 0x40742cL, 0x40a8daL, 0x7536e7L, 0x407713L }; + int sig[] = new int[ofs.length + 1]; + for (int i = 0; i < ofs.length; i++) { + // reading int signatures + raf.seek(ofs[i]); + int b1 = raf.read(); + int b2 = raf.read(); + int b3 = raf.read(); + int b4 = raf.read(); + if ((b1 | b2 | b3 | b4) < 0) { + throw new EOFException(); + } + sig[i] = b1 | (b2 << 8) | (b3 << 16) | (b4 << 24); + } + + // reading byte signature + raf.seek(0x4595c9L); + sig[ofs.length] = raf.read(); + + isIAv1 = Arrays.equals(sig, sigCheckV1); + isIAv2 = Arrays.equals(sig, sigCheckV2); + } catch (IOException e) { + } + } + } + } + if (isIAv1) { + addEntry(Key.GET_INFINITY_ANIMATIONS, Type.INTEGER, Integer.valueOf(1)); // v5 or earlier + } else if (isIAv2) { + addEntry(Key.GET_INFINITY_ANIMATIONS, Type.INTEGER, Integer.valueOf(2)); // v6 or later + } else { + addEntry(Key.GET_INFINITY_ANIMATIONS, Type.INTEGER, Integer.valueOf(0)); // not installed + } + // Add campaign-specific extra folders initCampaigns(); } diff --git a/src/org/infinity/resource/cre/CreResource.java b/src/org/infinity/resource/cre/CreResource.java index fe0478276..fbe6990a2 100644 --- a/src/org/infinity/resource/cre/CreResource.java +++ b/src/org/infinity/resource/cre/CreResource.java @@ -48,6 +48,7 @@ import org.infinity.datatype.UpdateListener; import org.infinity.gui.ButtonPanel; import org.infinity.gui.ButtonPopupMenu; +import org.infinity.gui.ChildFrame; import org.infinity.gui.StructViewer; import org.infinity.gui.hexview.BasicColorMap; import org.infinity.gui.hexview.StructHexViewer; @@ -729,6 +730,16 @@ public CreResource(AbstractStruct superStruct, String name, ByteBuffer data, int isChr = StreamUtils.readString(data, startoffset, 4).equalsIgnoreCase("CHR "); } + @Override + public void close() throws Exception + { + ViewerAnimation va = ChildFrame.getFirstFrame(ViewerAnimation.class); + if (va != null) { + va.close(); + } + super.close(); + } + // @Override public AddRemovable[] getPrototypes() throws Exception diff --git a/src/org/infinity/resource/cre/Viewer.java b/src/org/infinity/resource/cre/Viewer.java index f52ca07be..2df8e902c 100644 --- a/src/org/infinity/resource/cre/Viewer.java +++ b/src/org/infinity/resource/cre/Viewer.java @@ -11,9 +11,12 @@ import java.awt.GridBagLayout; import java.awt.GridLayout; import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import javax.swing.BorderFactory; import javax.swing.DefaultListCellRenderer; +import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JList; @@ -25,8 +28,10 @@ import org.infinity.datatype.Flag; import org.infinity.datatype.IsNumeric; import org.infinity.datatype.ResourceRef; +import org.infinity.gui.ChildFrame; import org.infinity.gui.ViewerUtil; import org.infinity.gui.ViewerUtil.ListValueRenderer; +import org.infinity.icon.Icons; import org.infinity.resource.AbstractStruct; import org.infinity.resource.Effect; import org.infinity.resource.Effect2; @@ -96,6 +101,7 @@ private static JPanel makeMiscPanelIWD2(CreResource cre) ViewerUtil.addLabelFieldPair(panel, cre.getAttribute(CreResource.CRE_GENDER), gbl, gbc, true); ViewerUtil.addLabelFieldPair(panel, cre.getAttribute(CreResource.CRE_ALIGNMENT), gbl, gbc, true); ViewerUtil.addLabelFieldPair(panel, cre.getAttribute(CreResource.CRE_KIT), gbl, gbc, true); + ViewerUtil.addLabelFieldPair(panel, cre.getAttribute(CreResource.CRE_ANIMATION), gbl, gbc, true); ViewerUtil.addLabelFieldPair(panel, cre.getAttribute(CreResource.CRE_CHALLENGE_RATING), gbl, gbc, true); ViewerUtil.addLabelFieldPair(panel, cre.getAttribute(CreResource.CRE_SAVE_FORTITUDE), gbl, gbc, true); ViewerUtil.addLabelFieldPair(panel, cre.getAttribute(CreResource.CRE_SAVE_REFLEX), gbl, gbc, true); @@ -191,6 +197,24 @@ private JPanel makeMainPanel(CreResource cre) imagePanel = ViewerUtil.makeImagePanel((ResourceRef)cre.getAttribute(CreResource.CRE_PORTRAIT_SMALL), true); } + JButton bViewAnimation = new JButton("View creature animation", Icons.getIcon(Icons.ICON_VOLUME_16)); + bViewAnimation.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) + { + ViewerAnimation va = ChildFrame.getFirstFrame(ViewerAnimation.class); + if (va == null) { + va = new ViewerAnimation(cre); + } else if (!va.isVisible()) { + va.setVisible(true); + va.toFront(); + } else { + va.toFront(); + } + } + }); + bViewAnimation.setMargin(new Insets(8, 8, 8, 4)); + GridBagLayout gbl = new GridBagLayout(); GridBagConstraints gbc = new GridBagConstraints(); JPanel leftPanel = new JPanel(gbl); @@ -201,6 +225,8 @@ private JPanel makeMainPanel(CreResource cre) gbc.gridwidth = GridBagConstraints.REMAINDER; gbl.setConstraints(imagePanel, gbc); leftPanel.add(imagePanel); + gbl.setConstraints(bViewAnimation, gbc); + leftPanel.add(bViewAnimation); gbc.weighty = 1.0; gbl.setConstraints(effectPanel, gbc); leftPanel.add(effectPanel); diff --git a/src/org/infinity/resource/cre/ViewerAnimation.java b/src/org/infinity/resource/cre/ViewerAnimation.java new file mode 100644 index 000000000..67f88d8e2 --- /dev/null +++ b/src/org/infinity/resource/cre/ViewerAnimation.java @@ -0,0 +1,517 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre; + +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import javax.swing.BorderFactory; +import javax.swing.DefaultComboBoxModel; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JScrollPane; +import javax.swing.JToggleButton; +import javax.swing.ScrollPaneConstants; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; +import javax.swing.Timer; + +import org.infinity.NearInfinity; +import org.infinity.gui.ButtonPanel; +import org.infinity.gui.Center; +import org.infinity.gui.ChildFrame; +import org.infinity.gui.RenderCanvas; +import org.infinity.gui.ViewerUtil; +import org.infinity.gui.WindowBlocker; +import org.infinity.icon.Icons; +import org.infinity.resource.cre.decoder.SpriteDecoder; +import org.infinity.resource.cre.decoder.SpriteUtils; +import org.infinity.resource.graphics.PseudoBamDecoder.PseudoBamControl; + +/** + * A basic creature animation viewer. + */ +public class ViewerAnimation extends ChildFrame implements ActionListener +{ + private static final Color TransparentColor = new Color(0, true); + private static final int ANIM_DELAY = 1000 / 15; // 15 fps in milliseconds + + private static final ButtonPanel.Control CtrlNextCycle = ButtonPanel.Control.CUSTOM_1; + private static final ButtonPanel.Control CtrlPrevCycle = ButtonPanel.Control.CUSTOM_2; + private static final ButtonPanel.Control CtrlNextFrame = ButtonPanel.Control.CUSTOM_3; + private static final ButtonPanel.Control CtrlPrevFrame = ButtonPanel.Control.CUSTOM_4; + private static final ButtonPanel.Control CtrlPlay = ButtonPanel.Control.CUSTOM_5; + private static final ButtonPanel.Control CtrlCycleLabel = ButtonPanel.Control.CUSTOM_6; + private static final ButtonPanel.Control CtrlFrameLabel = ButtonPanel.Control.CUSTOM_7; + private static final ButtonPanel.Control CtrlSequenceLabel = ButtonPanel.Control.CUSTOM_8; + private static final ButtonPanel.Control CtrlSequenceList = ButtonPanel.Control.CUSTOM_9; + private static final ButtonPanel.Control CtrlShowCircle = ButtonPanel.Control.CUSTOM_10; + private static final ButtonPanel.Control CtrlShowSpace = ButtonPanel.Control.CUSTOM_11; + private static final ButtonPanel.Control CtrlZoom = ButtonPanel.Control.CUSTOM_12; + + // List of potential sequences to display when loading a new creature + private static final List InitialSequences = new ArrayList() {{ + add(SpriteDecoder.Sequence.STAND); + add(SpriteDecoder.Sequence.STAND2); + add(SpriteDecoder.Sequence.STAND3); + add(SpriteDecoder.Sequence.STAND_EMERGED); + add(SpriteDecoder.Sequence.PST_STAND); + add(SpriteDecoder.Sequence.STANCE); + add(SpriteDecoder.Sequence.STANCE2); + add(SpriteDecoder.Sequence.PST_STANCE); + add(SpriteDecoder.Sequence.WALK); + add(SpriteDecoder.Sequence.PST_WALK); + }}; + + private static boolean zoom = false; + private static boolean showSelectionCircle = false; + private static boolean showPersonalSpace = false; + + private final ButtonPanel buttonControlPanel = new ButtonPanel(); + private final CreResource cre; + + private SpriteDecoder decoder; + private PseudoBamControl bamControl; + private RenderCanvas rcDisplay; + private int curCycle, curFrame; + private Timer timer; + private SpriteDecoder.Sequence sequence; + + public ViewerAnimation(CreResource cre) + { + super("", true); + this.cre = Objects.requireNonNull(cre); + try { + this.decoder = SpriteDecoder.importSprite(getCre()); + + init(); + } catch (Exception e) { + e.printStackTrace(); + JOptionPane.showMessageDialog(this, "Creature animation could not be loaded.\nError message: " + e.getMessage(), + "Error", JOptionPane.ERROR_MESSAGE); + this.bamControl = null; + this.decoder = null; + close(); + return; + } + } + + public CreResource getCre() + { + return cre; + } + + /** Returns the associated {@code SpriteDecoder} instance. */ + public SpriteDecoder getDecoder() + { + return decoder; + } + + /** Returns the {@code BamControl} instance linked to the {@code SpriteDecoder}. */ + public PseudoBamControl getController() + { + return bamControl; + } + + private void setController(PseudoBamControl ctrl) + { + this.bamControl = Objects.requireNonNull(ctrl, "BamControl cannot be null"); + } + + /** Returns the selected animation sequence. */ + public SpriteDecoder.Sequence getAnimationSequence() + { + return sequence; + } + + /** Loads a new animation sequence. */ + private void setAnimationSequence(SpriteDecoder.Sequence seq) throws Exception + { + if (seq != null && seq != getAnimationSequence()) { + sequence = seq; + curFrame = 0; + getDecoder().loadSequence(seq); + resetAnimationSequence(); + showFrame(); + } + } + + private void resetAnimationSequence() throws Exception + { + setController(getDecoder().createControl()); + getController().setMode(PseudoBamControl.Mode.SHARED); + getController().setSharedPerCycle(false); + if (curCycle < getController().cycleCount()) { + getController().cycleSet(curCycle); + if (curFrame < getController().cycleFrameCount()) { + getController().cycleSetFrameIndex(curFrame); + } + } + curCycle = getController().cycleGet(); + curFrame = getController().cycleGetFrameIndex(); + updateCanvasSize(); + } + + public void updateCanvasSize() + { + int zoom = isZoomed() ? 2 : 1; + Dimension dim = getController().getSharedDimension(); + Dimension dimDisplay = new Dimension(dim.width * zoom, dim.height * zoom); + boolean imageChanged = !dim.equals(new Dimension(rcDisplay.getImage().getWidth(null), rcDisplay.getImage().getHeight(null))); + boolean sizeChanged = !dimDisplay.equals(rcDisplay.getPreferredSize()); + if (imageChanged || sizeChanged) { + rcDisplay.setImage(new BufferedImage(dim.width, dim.height, BufferedImage.TYPE_INT_ARGB)); + if (sizeChanged) { + rcDisplay.setPreferredSize(dimDisplay); + Container c = SwingUtilities.getAncestorOfClass(JScrollPane.class, rcDisplay); + if (c != null) { + c.setMinimumSize(rcDisplay.getPreferredSize()); + c.invalidate(); + c.getParent().validate(); + } + } + } + updateCanvas(); + } + + public void updateCanvas() + { + BufferedImage image = (BufferedImage)rcDisplay.getImage(); + Graphics2D g = image.createGraphics(); + try { + g.setComposite(AlphaComposite.Src); + g.setColor(TransparentColor); + g.fillRect(0, 0, image.getWidth(), image.getHeight()); + } finally { + g.dispose(); + g = null; + } + + // rendering new frame + getController().cycleGetFrame(image); + rcDisplay.repaint(); + } + + /** Returns whether animation is zoomed. */ + public boolean isZoomed() + { + return ((JCheckBox)buttonControlPanel.getControlByType(CtrlZoom)).isSelected(); + } + + /** Returns whether the animation is played back. */ + public boolean isPlaying() + { + if (timer == null) { + timer = new Timer(ANIM_DELAY, this); + } + return timer.isRunning(); + } + + /** Toggles playback between "play" and "pause". */ + public void togglePlay() + { + if (isPlaying()) { + pause(); + } else { + play(); + } + } + + /** Starts playback. Does nothing if animation is already played back. */ + public void play() + { + if (!isPlaying()) { + timer.restart(); + } + } + + /** Stops playback. Does nothing if animation is already stopped. */ + public void pause() + { + if (isPlaying()) { + timer.stop(); + } + } + + /** Rewinds animation of current cycle to first frame. */ + public void rewind() + { + curFrame = 0; + showFrame(); + } + +//--------------------- Begin Class ChildFrame --------------------- + + @Override + protected boolean windowClosing(boolean forced) throws Exception + { + SpriteUtils.clearCache(); + return true; + } + +//--------------------- End Class ChildFrame --------------------- + +//--------------------- Begin Interface ActionListener --------------------- + + @Override + public void actionPerformed(ActionEvent event) + { + if (buttonControlPanel.getControlByType(CtrlSequenceList) == event.getSource()) { + JComboBox cb = (JComboBox)buttonControlPanel.getControlByType(CtrlSequenceList); + SpriteDecoder.Sequence seq = (SpriteDecoder.Sequence)(cb).getSelectedItem(); + try { + WindowBlocker.blockWindow(this, true); + setAnimationSequence(seq); + updateControls(); + } catch (Exception e) { + e.printStackTrace(); + JOptionPane.showMessageDialog(this, e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); + cb.setSelectedItem(getAnimationSequence()); + } finally { + WindowBlocker.blockWindow(this, false); + } + } + else if (buttonControlPanel.getControlByType(CtrlZoom) == event.getSource()) { + try { + WindowBlocker.blockWindow(this, true); + zoom = ((JCheckBox)buttonControlPanel.getControlByType(CtrlZoom)).isSelected(); + updateCanvasSize(); + } finally { + WindowBlocker.blockWindow(this, false); + } + } + else if (buttonControlPanel.getControlByType(CtrlShowCircle) == event.getSource()) { + try { + WindowBlocker.blockWindow(this, true); + showSelectionCircle = ((JCheckBox)buttonControlPanel.getControlByType(CtrlShowCircle)).isSelected(); + getDecoder().setSelectionCircleEnabled(showSelectionCircle); + resetAnimationSequence(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + WindowBlocker.blockWindow(this, false); + } + } + else if (buttonControlPanel.getControlByType(CtrlShowSpace) == event.getSource()) { + try { + WindowBlocker.blockWindow(this, true); + showPersonalSpace = ((JCheckBox)buttonControlPanel.getControlByType(CtrlShowSpace)).isSelected(); + getDecoder().setPersonalSpaceVisible(showPersonalSpace); + resetAnimationSequence(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + WindowBlocker.blockWindow(this, false); + } + } + else if (buttonControlPanel.getControlByType(CtrlPrevCycle) == event.getSource()) { + if (curCycle > 0) { + curCycle--; + getController().cycleSet(curCycle); + if (isPlaying() && getController().cycleFrameCount() == 0) { + pause(); + ((JToggleButton)buttonControlPanel.getControlByType(CtrlPlay)).setSelected(false); + } + rewind(); + showFrame(); + } + } + else if (buttonControlPanel.getControlByType(CtrlNextCycle) == event.getSource()) { + if (curCycle < getController().cycleCount() - 1) { + curCycle++; + getController().cycleSet(curCycle); + if (isPlaying() && getController().cycleFrameCount() == 0) { + pause(); + ((JToggleButton)buttonControlPanel.getControlByType(CtrlPlay)).setSelected(false); + } + rewind(); + showFrame(); + } + } + else if (buttonControlPanel.getControlByType(CtrlPrevFrame) == event.getSource()) { + if (curFrame > 0) { + curFrame--; + showFrame(); + } + } + else if (buttonControlPanel.getControlByType(CtrlNextFrame) == event.getSource()) { + if (curFrame < getController().cycleFrameCount() - 1) { + curFrame++; + showFrame(); + } + } + else if (buttonControlPanel.getControlByType(CtrlPlay) == event.getSource()) { + if (((JToggleButton)buttonControlPanel.getControlByType(CtrlPlay)).isSelected()) { + play(); + } else { + pause(); + } + updateControls(); + } + else if (timer == event.getSource()) { + curFrame += 1; + curFrame %= getController().cycleFrameCount(); + showFrame(); + } + } + +//--------------------- End Interface ActionListener --------------------- + + private void init() throws Exception + { + Dimension dim = new Dimension(1, 1); + rcDisplay = new RenderCanvas(new BufferedImage(dim.width, dim.height, BufferedImage.TYPE_INT_ARGB)); + rcDisplay.setHorizontalAlignment(SwingConstants.CENTER); + rcDisplay.setVerticalAlignment(SwingConstants.CENTER); + rcDisplay.setInterpolationType(RenderCanvas.TYPE_NEAREST_NEIGHBOR); + rcDisplay.setScalingEnabled(true); + JScrollPane scrollDisplay = new JScrollPane(rcDisplay, ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + scrollDisplay.setBorder(BorderFactory.createEmptyBorder()); + + JToggleButton bPlay = new JToggleButton("Play", Icons.getIcon(Icons.ICON_PLAY_16)); + bPlay.addActionListener(this); + + JLabel lCycle = new JLabel("", JLabel.CENTER); + JButton bPrevCycle = new JButton(Icons.getIcon(Icons.ICON_BACK_16)); + bPrevCycle.setMargin(new Insets(bPrevCycle.getMargin().top, 2, bPrevCycle.getMargin().bottom, 2)); + bPrevCycle.addActionListener(this); + JButton bNextCycle = new JButton(Icons.getIcon(Icons.ICON_FORWARD_16)); + bNextCycle.setMargin(bPrevCycle.getMargin()); + bNextCycle.addActionListener(this); + + JLabel lFrame = new JLabel("", JLabel.CENTER); + lFrame.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0)); + JButton bPrevFrame = new JButton(Icons.getIcon(Icons.ICON_BACK_16)); + bPrevFrame.setMargin(new Insets(bPrevFrame.getMargin().top, 2, bPrevFrame.getMargin().bottom, 2)); + bPrevFrame.addActionListener(this); + JButton bNextFrame = new JButton(Icons.getIcon(Icons.ICON_FORWARD_16)); + bNextFrame.setMargin(bPrevFrame.getMargin()); + bNextFrame.addActionListener(this); + + JLabel lSequence = new JLabel("Sequence:"); + lSequence.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0)); + DefaultComboBoxModel modelSequences = new DefaultComboBoxModel<>(); + JComboBox cbSequences = new JComboBox<>(modelSequences); + cbSequences.addActionListener(this); + for (final SpriteDecoder.Sequence seq : SpriteDecoder.Sequence.values()) { + if (getDecoder().isSequenceAvailable(seq)) { + modelSequences.addElement(seq); + } + } + cbSequences.setEnabled(cbSequences.getItemCount() > 0); + + JCheckBox cbZoom = new JCheckBox("Zoom", zoom); + cbZoom.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0)); + cbZoom.addActionListener(this); + getDecoder().setSelectionCircleEnabled(showSelectionCircle); + JCheckBox cbShowCircle = new JCheckBox("Show selection circle", getDecoder().isSelectionCircleEnabled()); + cbShowCircle.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0)); + cbShowCircle.addActionListener(this); + getDecoder().setPersonalSpaceVisible(showPersonalSpace); + JCheckBox cbShowSpace = new JCheckBox("Show personal space", getDecoder().isPersonalSpaceVisible()); + cbShowSpace.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0)); + cbShowSpace.addActionListener(this); + + buttonControlPanel.addControl(lCycle, CtrlCycleLabel); + buttonControlPanel.addControl(bPrevCycle, CtrlPrevCycle); + buttonControlPanel.addControl(bNextCycle, CtrlNextCycle); + buttonControlPanel.addControl(lFrame, CtrlFrameLabel); + buttonControlPanel.addControl(bPrevFrame, CtrlPrevFrame); + buttonControlPanel.addControl(bNextFrame, CtrlNextFrame); + buttonControlPanel.addControl(bPlay, CtrlPlay); + buttonControlPanel.addControl(lSequence, CtrlSequenceLabel); + buttonControlPanel.addControl(cbSequences, CtrlSequenceList); + buttonControlPanel.addControl(cbZoom, CtrlZoom); + buttonControlPanel.addControl(cbShowCircle, CtrlShowCircle); + buttonControlPanel.addControl(cbShowSpace, CtrlShowSpace); + + setLayout(new GridBagLayout()); + GridBagConstraints c; + c = ViewerUtil.setGBC(null, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, + GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); + add(scrollDisplay, c); + c = ViewerUtil.setGBC(null, 0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 0, 8, 0), 0, 0); + add(buttonControlPanel, c); + + String name = getCre().getName(); + if (!name.isEmpty()) { + setTitle(String.format("%s (%s)", getCre().getName(), getCre().getAttribute(CreResource.CRE_NAME))); + } else { + setTitle(getCre().getName()); + } + setSize(NearInfinity.getInstance().getPreferredSize()); + Center.center(this, NearInfinity.getInstance().getBounds()); + setExtendedState(NearInfinity.getInstance().getExtendedState() & ~ICONIFIED); + setVisible(true); + + // loading animation sequence + if (cbSequences.isEnabled()) { + int seqIdx = 0; + for (final SpriteDecoder.Sequence sequence : InitialSequences) { + int idx = ((DefaultComboBoxModel)cbSequences.getModel()).getIndexOf(sequence); + if (idx >= 0) { + seqIdx = idx; + break; + } + } + SpriteDecoder.Sequence seq = cbSequences.getModel().getElementAt(seqIdx); + cbSequences.setSelectedItem(seq); + setAnimationSequence(seq); + } + } + + private void showFrame() + { + if (getController() == null) { + return; + } + + if (!getController().cycleSetFrameIndex(curFrame)) { + getController().cycleReset(); + curFrame = 0; + } + + updateCanvas(); + + ((JLabel)buttonControlPanel.getControlByType(CtrlCycleLabel)) + .setText("Cycle: " + curCycle + "/" + (getController().cycleCount() - 1)); + ((JLabel)buttonControlPanel.getControlByType(CtrlFrameLabel)) + .setText("Frame: " + curFrame + "/" + (getController().cycleFrameCount() - 1)); + updateControls(); + } + + private void updateControls() + { + if (getController() != null) { + buttonControlPanel.getControlByType(CtrlPrevFrame).setEnabled(curFrame > 0); + buttonControlPanel.getControlByType(CtrlPrevCycle).setEnabled(curCycle > 0); + buttonControlPanel.getControlByType(CtrlNextFrame).setEnabled(curFrame < getController().cycleFrameCount() - 1); + buttonControlPanel.getControlByType(CtrlNextCycle).setEnabled(curCycle < getController().cycleCount() - 1); + buttonControlPanel.getControlByType(CtrlPlay).setEnabled(getController().cycleFrameCount() > 0); + } else { + buttonControlPanel.getControlByType(CtrlPrevFrame).setEnabled(false); + buttonControlPanel.getControlByType(CtrlPrevCycle).setEnabled(false); + buttonControlPanel.getControlByType(CtrlNextFrame).setEnabled(false); + buttonControlPanel.getControlByType(CtrlNextCycle).setEnabled(false); + buttonControlPanel.getControlByType(CtrlPlay).setEnabled(false); + } + } +} diff --git a/src/org/infinity/resource/cre/decoder/AmbientDecoder.java b/src/org/infinity/resource/cre/decoder/AmbientDecoder.java new file mode 100644 index 000000000..762bc9027 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/AmbientDecoder.java @@ -0,0 +1,163 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.infinity.resource.ResourceFactory; +import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.DecoderAttribute; +import org.infinity.resource.cre.decoder.internal.DirDef; +import org.infinity.resource.cre.decoder.internal.SegmentDef; +import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.IniMap; +import org.infinity.util.IniMapSection; +import org.infinity.util.tuples.Couple; + +/** + * Creature animation decoder for processing type C000 (ambient) animations. + * Available ranges: [c000,cfff] + */ +public class AmbientDecoder extends SpriteDecoder +{ + /** The animation type associated with this class definition. */ + public static final AnimationType ANIMATION_TYPE = AnimationType.AMBIENT; + + public static final DecoderAttribute KEY_INVULNERABLE = DecoderAttribute.with("invulnerable", DecoderAttribute.DataType.BOOLEAN); + public static final DecoderAttribute KEY_PATH_SMOOTH = DecoderAttribute.with("path_smooth", DecoderAttribute.DataType.BOOLEAN); + public static final DecoderAttribute KEY_LIST_TYPE = DecoderAttribute.with("list_type", DecoderAttribute.DataType.INT); + + /** Creature behaves normally. */ + public static final int LISTTYPE_NORMAL = 0; + /** + * Creature behaves like a "flying" creatures: + * detected_by_infravision=0, {@link Sequence#WALK} transforms into {@link Sequence#STANCE}, + * hardcoded exceptions (e.g. polymorph, pathfinding, ...) + */ + public static final int LISTTYPE_FLYING = 2; + + private static final HashMap> suffixMap = new HashMap>() {{ + put(Sequence.WALK, Couple.with("G1", 0)); + put(Sequence.STANCE, Couple.with("G1", 8)); + put(Sequence.STAND, Couple.with("G1", 16)); + put(Sequence.GET_HIT, Couple.with("G1", 24)); + put(Sequence.DIE, Couple.with("G1", 32)); + put(Sequence.SLEEP, get(Sequence.DIE)); + put(Sequence.GET_UP, Couple.with("!G1", 32)); + put(Sequence.TWITCH, Couple.with("G1", 40)); + }}; + + /** + * A helper method that parses the specified data array and generates a {@link IniMap} instance out of it. + * @param data a String array containing table values for a specific table entry. + * @return a {@code IniMap} instance with the value derived from the specified data array. + * Returns {@code null} if no data could be derived. + */ + public static IniMap processTableData(String[] data) + { + IniMap retVal = null; + if (data == null || data.length < 16) { + return retVal; + } + + String resref = SpriteTables.valueToString(data, SpriteTables.COLUMN_RESREF, ""); + if (resref.isEmpty()) { + return retVal; + } + int falseColor = SpriteTables.valueToInt(data, SpriteTables.COLUMN_CLOWN, 0); + + List lines = processTableDataGeneral(data, ANIMATION_TYPE); + lines.add("[ambient]"); + lines.add("false_color=" + falseColor); + lines.add("resref=" + resref); + + retVal = IniMap.from(lines); + + return retVal; + } + + public AmbientDecoder(int animationId, IniMap ini) throws Exception + { + super(ANIMATION_TYPE, animationId, ini); + } + + public AmbientDecoder(CreResource cre) throws Exception + { + super(ANIMATION_TYPE, cre); + } + + /** Returns whether the creature is invulnerable by default. */ + public boolean isInvulnerable() { return getAttribute(KEY_INVULNERABLE); } + protected void setInvulnerable(boolean b) { setAttribute(KEY_INVULNERABLE, b); } + + /** ??? */ + public boolean isSmoothPath() { return getAttribute(KEY_PATH_SMOOTH); } + protected void setSmoothPath(boolean b) { setAttribute(KEY_PATH_SMOOTH, b); } + + /** ??? */ + public int getListType() { return getAttribute(KEY_LIST_TYPE); } + protected void setListType(int v) { setAttribute(KEY_LIST_TYPE, v); } + + @Override + public List getAnimationFiles(boolean essential) + { + String resref = getAnimationResref(); + ArrayList retVal = new ArrayList() {{ + add(resref + "G1.BAM"); + add(resref + "G1E.BAM"); + if (!essential) { + add(resref + "G2.BAM"); + add(resref + "G2E.BAM"); + } + }}; + return retVal; + } + + @Override + public boolean isSequenceAvailable(Sequence seq) + { + return (getSequenceDefinition(seq) != null); + } + + @Override + protected void init() throws Exception + { + // setting properties + initDefaults(getAnimationInfo()); + IniMapSection section = getSpecificIniSection(); + setFalseColor(section.getAsInteger(KEY_FALSE_COLOR.getName(), 0) != 0); + setInvulnerable(section.getAsInteger(KEY_INVULNERABLE.getName(), 0) != 0); + setSmoothPath(section.getAsInteger(KEY_PATH_SMOOTH.getName(), 0) != 0); + setListType(section.getAsInteger(KEY_LIST_TYPE.getName(), 0)); + } + + @Override + protected SeqDef getSequenceDefinition(Sequence seq) + { + SeqDef retVal = null; + String resref = getAnimationResref(); + Couple data = suffixMap.get(seq); + if (data != null) { + String suffix = data.getValue0(); + SegmentDef.Behavior behavior = SegmentDef.getBehaviorOf(suffix); + suffix = SegmentDef.fixBehaviorSuffix(suffix); + ResourceEntry entry = ResourceFactory.getResourceEntry(resref + suffix + ".BAM"); + ResourceEntry entryE = ResourceFactory.getResourceEntry(resref + suffix + "E.BAM"); + int cycle = data.getValue1().intValue(); + int cycleE = cycle + SeqDef.DIR_REDUCED_W.length; + if (SpriteUtils.bamCyclesExist(entry, cycle, SeqDef.DIR_REDUCED_W.length) && + SpriteUtils.bamCyclesExist(entryE, cycleE, SeqDef.DIR_REDUCED_E.length)) { + retVal = SeqDef.createSequence(seq, SeqDef.DIR_REDUCED_W, false, entry, cycle, null, behavior); + SeqDef tmp = SeqDef.createSequence(seq, SeqDef.DIR_REDUCED_E, false, entryE, cycleE, null, behavior); + retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); + } + } + return retVal; + } +} diff --git a/src/org/infinity/resource/cre/decoder/AmbientStaticDecoder.java b/src/org/infinity/resource/cre/decoder/AmbientStaticDecoder.java new file mode 100644 index 000000000..de51dd124 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/AmbientStaticDecoder.java @@ -0,0 +1,141 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.infinity.resource.ResourceFactory; +import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.DecoderAttribute; +import org.infinity.resource.cre.decoder.internal.DirDef; +import org.infinity.resource.cre.decoder.internal.SegmentDef; +import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.IniMap; +import org.infinity.util.IniMapSection; +import org.infinity.util.tuples.Couple; + +/** + * Creature animation decoder for processing type B000 (ambient_static) animations. + * Available ranges: [b000,bfff] + */ +public class AmbientStaticDecoder extends SpriteDecoder +{ + /** The animation type associated with this class definition. */ + public static final AnimationType ANIMATION_TYPE = AnimationType.AMBIENT_STATIC; + + public static final DecoderAttribute KEY_INVULNERABLE = DecoderAttribute.with("invulnerable", DecoderAttribute.DataType.BOOLEAN); + + private static final HashMap> suffixMap = new HashMap>() {{ + put(Sequence.STANCE, Couple.with("G1", 0)); + put(Sequence.STAND, Couple.with("G1", 8)); + put(Sequence.GET_HIT, Couple.with("G1", 16)); + put(Sequence.DIE, Couple.with("G1", 24)); + put(Sequence.SLEEP, get(Sequence.DIE)); + put(Sequence.GET_UP, Couple.with("!G1", 24)); + put(Sequence.TWITCH, Couple.with("G1", 32)); + }}; + + /** + * A helper method that parses the specified data array and generates a {@link IniMap} instance out of it. + * @param data a String array containing table values for a specific table entry. + * @return a {@code IniMap} instance with the value derived from the specified data array. + * Returns {@code null} if no data could be derived. + */ + public static IniMap processTableData(String[] data) + { + IniMap retVal = null; + if (data == null || data.length < 16) { + return retVal; + } + + String resref = SpriteTables.valueToString(data, SpriteTables.COLUMN_RESREF, ""); + if (resref.isEmpty()) { + return retVal; + } + int falseColor = SpriteTables.valueToInt(data, SpriteTables.COLUMN_CLOWN, 0); + + List lines = processTableDataGeneral(data, ANIMATION_TYPE); + lines.add("[ambient_static]"); + lines.add("false_color=" + falseColor); + lines.add("resref=" + resref); + + retVal = IniMap.from(lines); + + return retVal; + } + + public AmbientStaticDecoder(int animationId, IniMap ini) throws Exception + { + super(ANIMATION_TYPE, animationId, ini); + } + + public AmbientStaticDecoder(CreResource cre) throws Exception + { + super(ANIMATION_TYPE, cre); + } + + /** Returns whether the creature is invulnerable by default. */ + public boolean isInvulnerable() { return getAttribute(KEY_INVULNERABLE); } + protected void setInvulnerable(boolean b) { setAttribute(KEY_INVULNERABLE, b); } + + @Override + public List getAnimationFiles(boolean essential) + { + String resref = getAnimationResref(); + ArrayList retVal = new ArrayList() {{ + add(resref + "G1.BAM"); + add(resref + "G1E.BAM"); + if (!essential) { + add(resref + "G2.BAM"); + add(resref + "G2E.BAM"); + } + }}; + return retVal; + } + + @Override + public boolean isSequenceAvailable(Sequence seq) + { + return (getSequenceDefinition(seq) != null); + } + + @Override + protected void init() throws Exception + { + // setting properties + initDefaults(getAnimationInfo()); + IniMapSection section = getSpecificIniSection(); + setFalseColor(section.getAsInteger(KEY_FALSE_COLOR.getName(), 0) != 0); + setInvulnerable(section.getAsInteger(KEY_INVULNERABLE.getName(), 0) != 0); + } + + @Override + protected SeqDef getSequenceDefinition(Sequence seq) + { + SeqDef retVal = null; + String resref = getAnimationResref(); + Couple data = suffixMap.get(seq); + if (data != null) { + String suffix = data.getValue0(); + SegmentDef.Behavior behavior = SegmentDef.getBehaviorOf(suffix); + suffix = SegmentDef.fixBehaviorSuffix(suffix); + ResourceEntry entry = ResourceFactory.getResourceEntry(resref + suffix + ".BAM"); + int cycle = data.getValue1().intValue(); + ResourceEntry entryE = ResourceFactory.getResourceEntry(resref + suffix + "E.BAM"); + int cycleE = cycle + SeqDef.DIR_REDUCED_W.length; + if (SpriteUtils.bamCyclesExist(entry, cycle, SeqDef.DIR_REDUCED_W.length) && + SpriteUtils.bamCyclesExist(entryE, cycleE, SeqDef.DIR_REDUCED_E.length)) { + retVal = SeqDef.createSequence(seq, SeqDef.DIR_REDUCED_W, false, entry, cycle, null, behavior); + SeqDef tmp = SeqDef.createSequence(seq, SeqDef.DIR_REDUCED_E, false, entryE, cycleE, null, behavior); + retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); + } + } + return retVal; + } +} diff --git a/src/org/infinity/resource/cre/decoder/ArmoredBaseDecoder.java b/src/org/infinity/resource/cre/decoder/ArmoredBaseDecoder.java new file mode 100644 index 000000000..fda3411c7 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/ArmoredBaseDecoder.java @@ -0,0 +1,249 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder; + +import java.util.List; +import java.util.Locale; +import java.util.regex.Pattern; + +import org.infinity.datatype.IsNumeric; +import org.infinity.datatype.IsTextual; +import org.infinity.resource.Profile; +import org.infinity.resource.StructEntry; +import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.DecoderAttribute; +import org.infinity.resource.itm.Ability; +import org.infinity.resource.itm.ItmResource; +import org.infinity.util.IniMap; +import org.infinity.util.IniMapSection; + +/** + * Common base for processing creature animations with different armor levels. + */ +public abstract class ArmoredBaseDecoder extends SpriteDecoder +{ + public static final DecoderAttribute KEY_CAN_LIE_DOWN = DecoderAttribute.with("can_lie_down", DecoderAttribute.DataType.BOOLEAN); + public static final DecoderAttribute KEY_DOUBLE_BLIT = DecoderAttribute.with("double_blit", DecoderAttribute.DataType.BOOLEAN); + public static final DecoderAttribute KEY_EQUIP_HELMET = DecoderAttribute.with("equip_helmet", DecoderAttribute.DataType.BOOLEAN); + public static final DecoderAttribute KEY_ARMOR_MAX_CODE = DecoderAttribute.with("armor_max_code", DecoderAttribute.DataType.INT); + public static final DecoderAttribute KEY_HEIGHT_CODE = DecoderAttribute.with("height_code", DecoderAttribute.DataType.STRING); + public static final DecoderAttribute KEY_HEIGHT_CODE_HELMET = DecoderAttribute.with("height_code_helmet", DecoderAttribute.DataType.STRING); + + /** Available attack types associated with attack sequences. */ + public enum AttackType { + ONE_HANDED, TWO_HANDED, TWO_WEAPON, THROWING, + BOW, CROSSBOW, SLING + } + + public ArmoredBaseDecoder(AnimationType type, int animationId, IniMap ini) throws Exception + { + super(type, animationId, ini); + } + + public ArmoredBaseDecoder(AnimationType type, CreResource cre) throws Exception + { + super(type, cre); + } + + /** Returns whether the creature falls down when dead/unconscious. */ + public boolean canLieDown() { return getAttribute(KEY_CAN_LIE_DOWN); } + protected void setCanLieDown(boolean b) { setAttribute(KEY_CAN_LIE_DOWN, b); } + + /** unused */ + public boolean isDoubleBlit() { return getAttribute(KEY_DOUBLE_BLIT); } + protected void setDoubleBlit(boolean b) { setAttribute(KEY_DOUBLE_BLIT, b); } + + /** + * Returns the maximum armor code value used as suffix in animation filenames. + * Highest code value is usually used by ArmorSpecificResref(). + */ + public int getMaxArmorCode() { return getAttribute(KEY_ARMOR_MAX_CODE); } + protected void setMaxArmorCode(int v) { setAttribute(KEY_ARMOR_MAX_CODE, Math.max(0, v)); } + + /** Returns whether helmet overlay is shown. */ + public boolean isHelmetEquipped() { return getAttribute(KEY_EQUIP_HELMET); } + protected void setHelmetEquipped(boolean b) { setAttribute(KEY_EQUIP_HELMET, b); } + + /** Returns the height code prefix for helmet overlay sprites. Falls back to generic height code if needed. */ + public String getHelmetHeightCode() + { + String retVal = getAttribute(KEY_HEIGHT_CODE_HELMET); + if (retVal.isEmpty()) { + retVal = getHeightCode(); + } + return retVal; + } + + protected void setHelmetHeightCode(String s) { setAttribute(KEY_HEIGHT_CODE_HELMET, s); } + + /** Returns the creature animation height code prefix. */ + public String getHeightCode() { return getAttribute(KEY_HEIGHT_CODE); } + protected void setHeightCode(String s) + { + if (s == null || s.isEmpty()) { + // heuristically determine height code + s = guessHeightCode(); + } + setAttribute(KEY_HEIGHT_CODE, s); + } + + /** Returns the armor code based on equipped armor of the current creature. */ + public int getArmorCode() + { + int retVal = 1; + ItmResource itm = SpriteUtils.getEquippedArmor(getCreResource()); + if (itm != null) { + String code = ((IsTextual)itm.getAttribute(ItmResource.ITM_EQUIPPED_APPEARANCE)).getText(); + try { + retVal = Math.max(1, Math.min(getMaxArmorCode(), Integer.parseInt(code.substring(0, 1)))); + } catch (Exception e) { + e.printStackTrace(); + } + } + return retVal; + } + + /** + * Determines the attack type based on the specified item resource. + * @param itm the item resource. + * @param abilityIndex the item-specific ability to check (e.g. throwing or melee for throwing axes) + * @param preferTwoWeapon whether {@code AttackType.TwoWeapon} should be returned if a melee one-handed weapon is detected. + * @return attack type associated with the item resource. + */ + public AttackType getAttackType(ItmResource itm, int abilityIndex, boolean preferTwoWeapon) + { + AttackType retVal = AttackType.ONE_HANDED; + if (itm == null) { + return retVal; + } + + // collecting data + int flags = ((IsNumeric)itm.getAttribute(ItmResource.ITM_FLAGS)).getValue(); + boolean isTwoHanded = (flags & (1 << 1)) != 0; + if (Profile.isEnhancedEdition()) { + // include fake two-handed weapons (e.g. monk fists) + isTwoHanded |= (flags & (1 << 12)) != 0; + } + int cat = ((IsNumeric)itm.getAttribute(ItmResource.ITM_CATEGORY)).getValue(); + int abilType = -1; + int numAbil = ((IsNumeric)itm.getAttribute(ItmResource.ITM_NUM_ABILITIES)).getValue(); + abilityIndex = Math.max(0, Math.min(numAbil - 1, abilityIndex)); + if (abilityIndex < numAbil) { + List list = itm.getFields(Ability.class); + if (list != null && abilityIndex < list.size() && list.get(abilityIndex) instanceof Ability) { + Ability abil = (Ability)list.get(abilityIndex); + abilType = ((IsNumeric)abil.getAttribute(Ability.ABILITY_TYPE)).getValue(); + } + } + + switch (cat) { + case 15: // Bows + retVal = AttackType.BOW; + break; + case 27: // Crossbows + retVal = AttackType.CROSSBOW; + break; + case 18: // Slings + retVal = AttackType.SLING; + break; + default: + if (abilType == 1) { // melee + if (isTwoHanded) { + retVal = AttackType.TWO_HANDED; + } else { + retVal = (preferTwoWeapon) ? AttackType.TWO_WEAPON : AttackType.ONE_HANDED; + } + } else { // assume ranged + retVal = AttackType.THROWING; + } + } + + return retVal; + } + + /** + * Attempts to determine the correct height code. + * @return the "guessed" height code. Returns empty string if code could not be determined. + */ + protected String guessHeightCode() + { + String retVal = ""; + boolean isCharacter = (getAnimationType() == AnimationType.CHARACTER); + String c2 = isCharacter ? "Q" : "P"; + + // try resref naming scheme + String resref = getAnimationResref().toUpperCase(Locale.ENGLISH); + if (resref.length() >= 3 && Pattern.matches(".[DEGHIO][FM].?", resref)) { + char race = resref.charAt(1); + char gender = resref.charAt(2); + if (gender == 'M' || gender == 'F') { + switch (race) { + case 'H': // human + case 'O': // half-orc + if (isCharacter) { + retVal = "W" + c2 + ((gender == 'F') ? "N" : "L"); + } else { + retVal = "W" + c2 + "L"; + } + break; + case 'E': // elf/half-elf + retVal = "W" + c2 + "M"; + break; + case 'D': // dwarf/gnome + case 'G': // gnome (?) + case 'I': // halfling + retVal = "W" + c2 + "S"; + break; + } + } + } + + // try associated CRE data + if (retVal.isEmpty()) { + CreResource cre = getCreResource(); + if (cre != null) { + boolean isFemale = ((IsNumeric)cre.getAttribute(CreResource.CRE_GENDER)).getValue() == 2; + int race = ((IsNumeric)cre.getAttribute(CreResource.CRE_RACE)).getValue(); + switch (race) { + case 1: // human + case 7: // half-orc + if (isCharacter) { + retVal = "W" + c2 + (isFemale ? "N" : "L"); + } else { + retVal = "W" + c2 + "L"; + } + break; + case 2: // elf + case 3: // half-elf + retVal = "W" + c2 + "M"; + break; + case 4: // dwarf + case 5: // halfling + case 6: // gnome + retVal = "W" + c2 + "S"; + break; + } + } + } + + return retVal; + } + + @Override + protected void init() throws Exception + { + // setting properties + initDefaults(getAnimationInfo()); + IniMapSection section = getSpecificIniSection(); + setCanLieDown(section.getAsInteger(KEY_CAN_LIE_DOWN.getName(), 0) != 0); + setFalseColor(section.getAsInteger(KEY_FALSE_COLOR.getName(), 0) != 0); + setDetectedByInfravision(section.getAsInteger(KEY_DETECTED_BY_INFRAVISION.getName(), 0) != 0); + setDoubleBlit(section.getAsInteger(KEY_DOUBLE_BLIT.getName(), 0) != 0); + setMaxArmorCode(section.getAsInteger(KEY_ARMOR_MAX_CODE.getName(), 0)); + setHelmetEquipped(section.getAsInteger(KEY_EQUIP_HELMET.getName(), 0) != 0); + setHeightCode(section.getAsString(KEY_HEIGHT_CODE.getName(), "")); + setHelmetHeightCode(section.getAsString(KEY_HEIGHT_CODE_HELMET.getName(), "")); + } +} diff --git a/src/org/infinity/resource/cre/decoder/CharacterDecoder.java b/src/org/infinity/resource/cre/decoder/CharacterDecoder.java new file mode 100644 index 000000000..987305f22 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/CharacterDecoder.java @@ -0,0 +1,461 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; + +import org.infinity.resource.ResourceFactory; +import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.DecoderAttribute; +import org.infinity.resource.cre.decoder.internal.DirDef; +import org.infinity.resource.cre.decoder.internal.SegmentDef; +import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.itm.ItmResource; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.IniMap; +import org.infinity.util.IniMapSection; +import org.infinity.util.tuples.Couple; + +/** + * Creature animation decoder for processing type 5000/6000 (character) animations. + * Available ranges: [5000,53ff], [5500,55ff], [6000,63ff], [6500,65ff] + */ +public class CharacterDecoder extends ArmoredBaseDecoder +{ + /** The animation type associated with this class definition. */ + public static final AnimationType ANIMATION_TYPE = AnimationType.CHARACTER; + + public static final DecoderAttribute KEY_SPLIT_BAMS = DecoderAttribute.with("split_bams", DecoderAttribute.DataType.BOOLEAN); + public static final DecoderAttribute KEY_HEIGHT_CODE_SHIELD = DecoderAttribute.with("height_code_shield", DecoderAttribute.DataType.STRING); + public static final DecoderAttribute KEY_RESREF_PAPERDOLL = DecoderAttribute.with("resref_paperdoll", DecoderAttribute.DataType.STRING); + public static final DecoderAttribute KEY_RESREF_ARMOR_BASE = DecoderAttribute.with("resref_armor_base", DecoderAttribute.DataType.STRING); + public static final DecoderAttribute KEY_RESREF_ARMOR_SPECIFIC = DecoderAttribute.with("resref_armor_specific", DecoderAttribute.DataType.STRING); + + /** Assigns BAM suffix and cycle index to a specific animation sequence (unsplit version). */ + private static final HashMap> suffixMapUnsplit = + new HashMap>() {{ + put(Sequence.ATTACK_SLASH_1H, Couple.with("A1", 0)); + put(Sequence.ATTACK_SLASH_2H, Couple.with("A2", 0)); + put(Sequence.ATTACK_BACKSLASH_1H, Couple.with("A3", 0)); + put(Sequence.ATTACK_BACKSLASH_2H, Couple.with("A4", 0)); + put(Sequence.ATTACK_JAB_1H, Couple.with("A5", 0)); + put(Sequence.ATTACK_JAB_2H, Couple.with("A6", 0)); + put(Sequence.ATTACK_2WEAPONS1, Couple.with("A7", 0)); + put(Sequence.ATTACK_OVERHEAD, Couple.with("A8", 0)); + put(Sequence.ATTACK_2WEAPONS2, Couple.with("A9", 0)); + put(Sequence.ATTACK_BOW, Couple.with("SA", 0)); + put(Sequence.ATTACK_SLING, Couple.with("SS", 0)); + put(Sequence.ATTACK_CROSSBOW, Couple.with("SX", 0)); + put(Sequence.SPELL, Couple.with("CA", 0)); + put(Sequence.SPELL1, get(Sequence.SPELL)); + put(Sequence.SPELL2, Couple.with("CA", 18)); + put(Sequence.SPELL3, Couple.with("CA", 36)); + put(Sequence.SPELL4, Couple.with("CA", 54)); + put(Sequence.CAST, Couple.with("CA", 9)); + put(Sequence.CAST1, get(Sequence.CAST)); + put(Sequence.CAST2, Couple.with("CA", 27)); + put(Sequence.CAST3, Couple.with("CA", 45)); + put(Sequence.CAST4, Couple.with("CA", 63)); + put(Sequence.WALK, Couple.with("G1", 0)); + put(Sequence.STANCE, Couple.with("G1", 9)); + put(Sequence.STANCE2, Couple.with("G1", 27)); + put(Sequence.STAND, Couple.with("G1", 18)); + put(Sequence.STAND2, Couple.with("G1", 63)); + put(Sequence.STAND3, Couple.with("G1", 72)); + put(Sequence.GET_HIT, Couple.with("G1", 36)); + put(Sequence.DIE, Couple.with("G1", 45)); + put(Sequence.TWITCH, Couple.with("G1", 54)); + put(Sequence.SLEEP, Couple.with("G1", 81)); + put(Sequence.GET_UP, Couple.with("!G1", 81)); + put(Sequence.SLEEP2, Couple.with("G1", 90)); + put(Sequence.GET_UP2, Couple.with("!G1", 90)); + }}; + + /** Assigns BAM suffix and cycle index to a specific animation sequence (split version). */ + private static final HashMap> suffixMapSplit = + new HashMap>() {{ + put(Sequence.ATTACK_SLASH_1H, Couple.with("A1", 0)); + put(Sequence.ATTACK_SLASH_2H, Couple.with("A2", 0)); + put(Sequence.ATTACK_BACKSLASH_1H, Couple.with("A3", 0)); + put(Sequence.ATTACK_BACKSLASH_2H, Couple.with("A4", 0)); + put(Sequence.ATTACK_JAB_1H, Couple.with("A5", 0)); + put(Sequence.ATTACK_JAB_2H, Couple.with("A6", 0)); + put(Sequence.ATTACK_2WEAPONS1, Couple.with("A7", 0)); + put(Sequence.ATTACK_OVERHEAD, Couple.with("A8", 0)); + put(Sequence.ATTACK_2WEAPONS2, Couple.with("A9", 0)); + put(Sequence.ATTACK_BOW, Couple.with("SA", 0)); + put(Sequence.ATTACK_SLING, Couple.with("SS", 0)); + put(Sequence.ATTACK_CROSSBOW, Couple.with("SX", 0)); + put(Sequence.SPELL, Couple.with("CA", 0)); + put(Sequence.SPELL1, get(Sequence.SPELL)); + put(Sequence.SPELL2, Couple.with("CA", 18)); + put(Sequence.SPELL3, Couple.with("CA", 36)); + put(Sequence.SPELL4, Couple.with("CA", 54)); + put(Sequence.CAST, Couple.with("CA", 9)); + put(Sequence.CAST1, get(Sequence.CAST)); + put(Sequence.CAST2, Couple.with("CA", 27)); + put(Sequence.CAST3, Couple.with("CA", 45)); + put(Sequence.CAST4, Couple.with("CA", 63)); + put(Sequence.WALK, Couple.with("G11", 0)); + put(Sequence.STANCE, Couple.with("G1", 9)); + put(Sequence.STANCE2, Couple.with("G13", 27)); + put(Sequence.STAND, Couple.with("G12", 18)); + put(Sequence.STAND2, Couple.with("G17", 63)); + put(Sequence.STAND3, Couple.with("G18", 72)); + put(Sequence.GET_HIT, Couple.with("G15", 36)); + put(Sequence.DIE, Couple.with("G15", 45)); + put(Sequence.TWITCH, Couple.with("G16", 54)); + put(Sequence.SLEEP, Couple.with("G19", 81)); + put(Sequence.GET_UP, Couple.with("!G19", 81)); + put(Sequence.SLEEP2, Couple.with("G19", 90)); + put(Sequence.GET_UP2, Couple.with("!G19", 90)); + }}; + + /** Set of invalid attack type / animation sequence combinations. */ + private static final EnumMap> forbiddenSequences = + new EnumMap>(AttackType.class) {{ + put(AttackType.ONE_HANDED, EnumSet.of(Sequence.ATTACK_SLASH_2H, Sequence.ATTACK_BACKSLASH_2H, Sequence.ATTACK_JAB_2H, + Sequence.ATTACK_2WEAPONS1, Sequence.ATTACK_2WEAPONS2, Sequence.ATTACK_OVERHEAD, + Sequence.ATTACK_BOW, Sequence.ATTACK_SLING, Sequence.ATTACK_CROSSBOW, Sequence.STANCE2)); + put(AttackType.TWO_HANDED, EnumSet.of(Sequence.ATTACK_SLASH_1H, Sequence.ATTACK_BACKSLASH_1H, Sequence.ATTACK_JAB_1H, + Sequence.ATTACK_2WEAPONS1, Sequence.ATTACK_2WEAPONS2, Sequence.ATTACK_OVERHEAD, + Sequence.ATTACK_BOW, Sequence.ATTACK_SLING, Sequence.ATTACK_CROSSBOW, Sequence.STANCE)); + put(AttackType.TWO_WEAPON, EnumSet.of(Sequence.ATTACK_SLASH_1H, Sequence.ATTACK_BACKSLASH_1H, Sequence.ATTACK_JAB_1H, + Sequence.ATTACK_SLASH_2H, Sequence.ATTACK_BACKSLASH_2H, Sequence.ATTACK_JAB_2H, + Sequence.ATTACK_OVERHEAD, Sequence.ATTACK_BOW, Sequence.ATTACK_SLING, Sequence.ATTACK_CROSSBOW, + Sequence.STANCE2)); + put(AttackType.THROWING, EnumSet.of(Sequence.ATTACK_SLASH_1H, Sequence.ATTACK_BACKSLASH_1H, Sequence.ATTACK_JAB_1H, + Sequence.ATTACK_SLASH_2H, Sequence.ATTACK_BACKSLASH_2H, Sequence.ATTACK_JAB_2H, + Sequence.ATTACK_2WEAPONS1, Sequence.ATTACK_2WEAPONS2, Sequence.ATTACK_BOW, + Sequence.ATTACK_SLING, Sequence.ATTACK_CROSSBOW, Sequence.STANCE2)); + put(AttackType.BOW, EnumSet.of(Sequence.ATTACK_SLASH_1H, Sequence.ATTACK_BACKSLASH_1H, Sequence.ATTACK_JAB_1H, + Sequence.ATTACK_SLASH_2H, Sequence.ATTACK_BACKSLASH_2H, Sequence.ATTACK_JAB_2H, + Sequence.ATTACK_2WEAPONS1, Sequence.ATTACK_2WEAPONS2, Sequence.ATTACK_OVERHEAD, + Sequence.ATTACK_SLING, Sequence.ATTACK_CROSSBOW, Sequence.STANCE2)); + put(AttackType.SLING, EnumSet.of(Sequence.ATTACK_SLASH_1H, Sequence.ATTACK_BACKSLASH_1H, Sequence.ATTACK_JAB_1H, + Sequence.ATTACK_SLASH_2H, Sequence.ATTACK_BACKSLASH_2H, Sequence.ATTACK_JAB_2H, + Sequence.ATTACK_2WEAPONS1, Sequence.ATTACK_2WEAPONS2, Sequence.ATTACK_OVERHEAD, + Sequence.ATTACK_BOW, Sequence.ATTACK_CROSSBOW, Sequence.STANCE2)); + put(AttackType.CROSSBOW, EnumSet.of(Sequence.ATTACK_SLASH_1H, Sequence.ATTACK_BACKSLASH_1H, Sequence.ATTACK_JAB_1H, + Sequence.ATTACK_SLASH_2H, Sequence.ATTACK_BACKSLASH_2H, Sequence.ATTACK_JAB_2H, + Sequence.ATTACK_2WEAPONS1, Sequence.ATTACK_2WEAPONS2, Sequence.ATTACK_OVERHEAD, + Sequence.ATTACK_BOW, Sequence.ATTACK_SLING, Sequence.STANCE2)); + }}; + + /** + * A helper method that parses the specified data array and generates a {@link IniMap} instance out of it. + * @param data a String array containing table values for a specific table entry. + * @return a {@code IniMap} instance with the value derived from the specified data array. + * Returns {@code null} if no data could be derived. + */ + public static IniMap processTableData(String[] data) + { + IniMap retVal = null; + if (data == null || data.length < 16) { + return retVal; + } + + String resref = SpriteTables.valueToString(data, SpriteTables.COLUMN_RESREF, ""); + if (resref.isEmpty()) { + return retVal; + } + int equipHelmet = SpriteTables.valueToInt(data, SpriteTables.COLUMN_HELMET, 0); + int splitBams = SpriteTables.valueToInt(data, SpriteTables.COLUMN_SPLIT, 0); + int falseColor = SpriteTables.valueToInt(data, SpriteTables.COLUMN_CLOWN, 0); + String heightCode = SpriteTables.valueToString(data, SpriteTables.COLUMN_HEIGHT, ""); + String heightCodeHelmet = heightCode; + String heightCodeShield = SpriteTables.valueToString(data, SpriteTables.COLUMN_HEIGHT_SHIELD, ""); + String resrefSpecific = SpriteTables.valueToString(data, SpriteTables.COLUMN_RESREF2, ""); + + List lines = processTableDataGeneral(data, ANIMATION_TYPE); + lines.add("[character]"); + lines.add("equip_helmet=" + equipHelmet); + lines.add("split_bams=" + splitBams); + lines.add("false_color=" + falseColor); + lines.add("resref=" + resref); + if (!heightCode.isEmpty()) { + lines.add("height_code=" + heightCode); + } + if (!heightCodeHelmet.isEmpty()) { + lines.add("height_code_helmet=" + heightCodeHelmet); + } + if (!heightCodeShield.isEmpty()) { + lines.add("height_code_shield=" + heightCodeShield); + } + lines.add("resref_armor_base=" + resref.charAt(resref.length() - 1)); + if (!resrefSpecific.isEmpty()) { + lines.add("resref_armor_specific=" + resrefSpecific.charAt(resrefSpecific.length() - 1)); + } + + retVal = IniMap.from(lines); + + return retVal; + } + + public CharacterDecoder(int animationId, IniMap ini) throws Exception + { + super(ANIMATION_TYPE, animationId, ini); + } + + public CharacterDecoder(CreResource cre) throws Exception + { + super(ANIMATION_TYPE, cre); + } + + /** Returns the correct sequence map for the current settings. */ + private HashMap> getSuffixMap() + { + return isSplittedBams() ? suffixMapSplit : suffixMapUnsplit; + } + + /** Returns whether animations are spread over various subfiles. */ + public boolean isSplittedBams() { return getAttribute(KEY_SPLIT_BAMS); } + protected void setSplittedBams(boolean b) { setAttribute(KEY_SPLIT_BAMS, b); } + + /** Returns the height code prefix for shield overlay sprites. Falls back to generic height code if needed. */ + public String getShieldHeightCode() + { + String retVal = getAttribute(KEY_HEIGHT_CODE_SHIELD); + if (retVal.isEmpty()) { + retVal = getHeightCode(); + } + return retVal; + } + + protected void setShieldHeightCode(String s) { setAttribute(KEY_HEIGHT_CODE_SHIELD, s); } + + /** Returns the paperdoll resref. */ + public String getPaperdollResref() { return getAttribute(KEY_RESREF_PAPERDOLL); } + protected void setPaperdollResref(String s) { setAttribute(KEY_RESREF_PAPERDOLL, s); } + + /** + * Returns the animation resref for lesser armor types. + * Returns the same value as {@link #getAnimationResref()} if no base armor code is available. + */ + public String getArmorBaseResref() { return getAttribute(KEY_RESREF_ARMOR_BASE); } + protected void setArmorBaseResref(String s) + { + if (s.isEmpty()) { + s = getAnimationResref(); + } else { + s = getAnimationResref().substring(0, 3) + s.substring(0, 1); + } + setAttribute(KEY_RESREF_ARMOR_BASE, s); + } + + /** + * Returns the animation resref for greater armor types. + * Returns the same value as {@link #getAnimationResref()} if no specific armor code is available. + */ + public String getArmorSpecificResref() { return getAttribute(KEY_RESREF_ARMOR_SPECIFIC); } + protected void setArmorSpecificResref(String s) + { + if (s.isEmpty()) { + s = getAnimationResref(); + } else { + s = getAnimationResref().substring(0, 3) + s.substring(0, 1); + } + setAttribute(KEY_RESREF_ARMOR_SPECIFIC, s); + } + + /** + * Sets the maximum armor code value uses as suffix in animation filenames. + * Specify -1 to detect value automatically. + */ + @Override + protected void setMaxArmorCode(int v) + { + if (v < 0) { + // autodetection: requires fully initialized resref definitions + for (int i = 1; i < 10 && v < 0; i++) { + String resref = getArmorSpecificResref(); + if (!resref.isEmpty() && ResourceFactory.resourceExists(resref + i + "G1.BAM")) { + v = i; + } + } + } + super.setMaxArmorCode(v); + } + + @Override + public List getAnimationFiles(boolean essential) + { + ArrayList retVal = null; + String resref1 = getAnimationResref(); + String resref2 = getArmorSpecificResref(); + + if (essential) { + HashSet files = new HashSet<>(); + for (final HashMap.Entry> entry : getSuffixMap().entrySet()) { + String suffix = SegmentDef.fixBehaviorSuffix(entry.getValue().getValue0()); + if (suffix.startsWith("G")) { + for (int i = 1; i <= getMaxArmorCode(); i++) { + String resref = (i < getMaxArmorCode()) ? resref1 : resref2; + files.add(resref + i + suffix + ".BAM"); + } + } + } + retVal = new ArrayList<>(Arrays.asList(files.toArray(new String[files.size()]))); + } else { + // collecting suffixes + HashSet actionSet = new HashSet<>(); + for (final HashMap.Entry> entry : getSuffixMap().entrySet()) { + String suffix = SegmentDef.fixBehaviorSuffix(entry.getValue().getValue0()); + actionSet.add(suffix); + } + + // generating file list + retVal = new ArrayList() {{ + for (int i = 1; i <= getMaxArmorCode(); i++) { + String resref = (i < getMaxArmorCode()) ? resref1 : resref2; + for (final String a : actionSet) { + add(resref + i + a + ".BAM"); + } + } + }}; + } + + return retVal; + } + + @Override + public boolean isSequenceAvailable(Sequence seq) + { + return (getSequenceDefinition(seq) != null); + } + + @Override + protected void init() throws Exception + { + super.init(); + IniMapSection section = getSpecificIniSection(); + setSplittedBams(section.getAsInteger(KEY_SPLIT_BAMS.getName(), 0) != 0); + setShieldHeightCode(section.getAsString(KEY_HEIGHT_CODE_SHIELD.getName(), "")); + setPaperdollResref(section.getAsString(KEY_RESREF_PAPERDOLL.getName(), "")); + setArmorBaseResref(section.getAsString(KEY_RESREF_ARMOR_BASE.getName(), "")); + setArmorSpecificResref(section.getAsString(KEY_RESREF_ARMOR_SPECIFIC.getName(), "")); + if (getMaxArmorCode() == 0) { + setMaxArmorCode(-1); + } + } + + @Override + protected SeqDef getSequenceDefinition(Sequence seq) + { + SeqDef retVal = null; + + if (!getSuffixMap().containsKey(seq)) { + return retVal; + } + + // getting armor level + int armorCode = getArmorCode(); + if (armorCode > getMaxArmorCode()) { + return retVal; + } + + // preparing shield slot data + boolean isLefthandedWeapon = false; + String prefixShield = getShieldHeightCode(); + String codeShield = ""; + if (!prefixShield.isEmpty()) { + ItmResource itm = SpriteUtils.getEquippedShield(getCreResource()); + codeShield = SpriteUtils.getItemAppearance(itm).trim(); + isLefthandedWeapon = !codeShield.isEmpty() && SpriteUtils.isLeftHandedWeapon(itm); + } + + // getting attack type + ItmResource itmWeapon = SpriteUtils.getEquippedWeapon(getCreResource()); + int itmAbility = SpriteUtils.getEquippedWeaponAbility(getCreResource()); + AttackType attackType = getAttackType(itmWeapon, itmAbility, isLefthandedWeapon); + + EnumSet sequences = forbiddenSequences.get(attackType); + if (sequences != null && sequences.contains(seq)) { + // sequence not allowed for selected weapon + return retVal; + } + + ArrayList> resrefList = new ArrayList<>(); + + // adding creature resref + String creSuffix = getSuffixMap().get(seq).getValue0(); + SegmentDef.Behavior behavior = SegmentDef.getBehaviorOf(creSuffix); + creSuffix = SegmentDef.fixBehaviorSuffix(creSuffix); + String creResref = (armorCode > 1) ? getArmorSpecificResref() : getArmorBaseResref(); + if (!ResourceFactory.resourceExists(creResref + armorCode + creSuffix + ".BAM")) { + creResref = getArmorBaseResref(); + if (!ResourceFactory.resourceExists(creResref + armorCode + creSuffix + ".BAM")) { + creResref = getAnimationResref(); + } + } + creResref += armorCode; + resrefList.add(Couple.with(creResref, SegmentDef.SpriteType.AVATAR)); + + String prefix; + // adding helmet overlay + if (isHelmetEquipped()) { + prefix = getHelmetHeightCode(); + if (!prefix.isEmpty()) { + String code = SpriteUtils.getItemAppearance(SpriteUtils.getEquippedHelmet(getCreResource())).trim(); + if (code.length() == 2) { + resrefList.add(Couple.with(prefix + code, SegmentDef.SpriteType.HELMET)); + } + } + } + + // adding shield overlay + if (!prefixShield.isEmpty() && !codeShield.isEmpty()) { + resrefList.add(Couple.with(prefixShield + codeShield, SegmentDef.SpriteType.SHIELD)); + } + + // adding weapon overlay + prefix = getHeightCode(); + if (!prefix.isEmpty()) { + String code = SpriteUtils.getItemAppearance(itmWeapon).trim(); + if (code.length() == 2) { + resrefList.add(Couple.with(prefix + code, SegmentDef.SpriteType.WEAPON)); + } + } + + retVal = new SeqDef(seq); + for (final Couple data: resrefList) { + // getting BAM suffix and cycle index + prefix = data.getValue0(); + SegmentDef.SpriteType spriteType = data.getValue1(); + HashMap> curSuffixMap = (spriteType == SegmentDef.SpriteType.AVATAR) ? getSuffixMap() : suffixMapUnsplit; + String suffix = SegmentDef.fixBehaviorSuffix(curSuffixMap.get(seq).getValue0()); + int cycleIdx = curSuffixMap.get(seq).getValue1().intValue(); + + // enabling left-handed weapon overlay if available + if (spriteType == SegmentDef.SpriteType.SHIELD && isLefthandedWeapon) { + if (ResourceFactory.resourceExists(prefix + "O" + suffix + ".BAM")) { + prefix += "O"; + } + } + + // defining sequences + ResourceEntry entry = ResourceFactory.getResourceEntry(prefix + suffix + ".BAM"); + if (SpriteUtils.bamCyclesExist(entry, cycleIdx, SeqDef.DIR_FULL_W.length)) { + SeqDef tmp = SeqDef.createSequence(seq, SeqDef.DIR_FULL_W, false, entry, cycleIdx, spriteType, behavior); + retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); + tmp = SeqDef.createSequence(seq, SeqDef.DIR_FULL_E, true, entry, cycleIdx + 1, spriteType, behavior); + retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); + } + } + + if (retVal.isEmpty()) { + retVal = null; + } + + return retVal; + } +} diff --git a/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java b/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java new file mode 100644 index 000000000..dd6e054d0 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java @@ -0,0 +1,385 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; + +import org.infinity.resource.ResourceFactory; +import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.CycleDef; +import org.infinity.resource.cre.decoder.internal.DecoderAttribute; +import org.infinity.resource.cre.decoder.internal.DirDef; +import org.infinity.resource.cre.decoder.internal.SegmentDef; +import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.itm.ItmResource; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.IniMap; +import org.infinity.util.IniMapSection; +import org.infinity.util.tuples.Couple; + +/** + * Creature animation decoder for processing type 5000/6000 (character_old) animations. + * Available ranges: [5400,54ff], [5600,5fff], [6400,64ff], [6600,6fff] + */ +public class CharacterOldDecoder extends ArmoredBaseDecoder +{ + /** The animation type associated with this class definition. */ + public static final AnimationType ANIMATION_TYPE = AnimationType.CHARACTER_OLD; + + public static final DecoderAttribute KEY_HIDE_WEAPONS = DecoderAttribute.with("hide_weapons", DecoderAttribute.DataType.BOOLEAN); + public static final DecoderAttribute KEY_SHADOW = DecoderAttribute.with("shadow", DecoderAttribute.DataType.STRING); + + /** Assigns BAM suffix and cycle index to a specific animation sequence. */ + private static final HashMap> suffixMap = + new HashMap>() {{ + put(Sequence.ATTACK_SLASH_1H, Couple.with("A1", 0)); + put(Sequence.ATTACK_SLASH_2H, Couple.with("A2", 0)); + put(Sequence.ATTACK_BACKSLASH_1H, Couple.with("A3", 0)); + put(Sequence.ATTACK_BACKSLASH_2H, Couple.with("A4", 0)); + put(Sequence.ATTACK_JAB_1H, Couple.with("A5", 0)); + put(Sequence.ATTACK_JAB_2H, Couple.with("A6", 0)); + put(Sequence.ATTACK_BOW, Couple.with("SA", 0)); + put(Sequence.ATTACK_CROSSBOW, Couple.with("SX", 0)); + put(Sequence.SPELL, Couple.with("CA", 0)); + put(Sequence.SPELL1, get(Sequence.SPELL)); + put(Sequence.SPELL2, Couple.with("CA", 16)); + put(Sequence.SPELL3, Couple.with("CA", 32)); + put(Sequence.SPELL4, Couple.with("CA", 48)); + put(Sequence.CAST, Couple.with("CA", 8)); + put(Sequence.CAST1, get(Sequence.CAST)); + put(Sequence.CAST2, Couple.with("CA", 24)); + put(Sequence.CAST3, Couple.with("CA", 40)); + put(Sequence.CAST4, Couple.with("CA", 56)); + put(Sequence.WALK, Couple.with("G1", 0)); + put(Sequence.STANCE, Couple.with("G1", 8)); + put(Sequence.STANCE2, Couple.with("G1", 24)); + put(Sequence.STAND, Couple.with("G1", 16)); + put(Sequence.STAND2, Couple.with("G1", 32)); + put(Sequence.GET_HIT, Couple.with("G1", 40)); + put(Sequence.DIE, Couple.with("G1", 48)); + put(Sequence.SLEEP, get(Sequence.DIE)); + put(Sequence.GET_UP, Couple.with("!G1", 48)); + put(Sequence.TWITCH, Couple.with("G1", 56)); + }}; + + /** BAM suffix and cycle index for extended walk directions. */ + private static Couple walkExtra = Couple.with("W2", 0); + + /** Set of invalid attack type / animation sequence combinations. */ + private static final EnumMap> forbiddenSequences = + new EnumMap>(AttackType.class) {{ + put(AttackType.ONE_HANDED, EnumSet.of(Sequence.ATTACK_SLASH_2H, Sequence.ATTACK_BACKSLASH_2H, Sequence.ATTACK_JAB_2H, + Sequence.ATTACK_BOW, Sequence.ATTACK_CROSSBOW, Sequence.STANCE2)); + put(AttackType.TWO_HANDED, EnumSet.of(Sequence.ATTACK_SLASH_1H, Sequence.ATTACK_BACKSLASH_1H, Sequence.ATTACK_JAB_1H, + Sequence.ATTACK_BOW, Sequence.ATTACK_CROSSBOW, Sequence.STANCE)); + put(AttackType.TWO_WEAPON, EnumSet.allOf(Sequence.class)); + put(AttackType.THROWING, EnumSet.of(Sequence.ATTACK_BACKSLASH_1H, Sequence.ATTACK_JAB_1H, + Sequence.ATTACK_SLASH_2H, Sequence.ATTACK_BACKSLASH_2H, Sequence.ATTACK_JAB_2H, + Sequence.ATTACK_BOW, Sequence.ATTACK_CROSSBOW, Sequence.STANCE2)); + put(AttackType.BOW, EnumSet.of(Sequence.ATTACK_SLASH_1H, Sequence.ATTACK_BACKSLASH_1H, Sequence.ATTACK_JAB_1H, + Sequence.ATTACK_SLASH_2H, Sequence.ATTACK_BACKSLASH_2H, Sequence.ATTACK_JAB_2H, + Sequence.ATTACK_CROSSBOW, Sequence.STANCE2)); + put(AttackType.SLING, get(AttackType.THROWING)); + put(AttackType.CROSSBOW, EnumSet.of(Sequence.ATTACK_SLASH_1H, Sequence.ATTACK_BACKSLASH_1H, Sequence.ATTACK_JAB_1H, + Sequence.ATTACK_SLASH_2H, Sequence.ATTACK_BACKSLASH_2H, Sequence.ATTACK_JAB_2H, + Sequence.ATTACK_BOW, Sequence.STANCE2)); + }}; + + // the default shadow of the animation + private static final String SHADOW_RESREF = "CSHD"; + // special shadow for (armored) Sarevok + private static final String SHADOW_RESREF_SAREVOK = "SSHD"; + + /** + * A helper method that parses the specified data array and generates a {@link IniMap} instance out of it. + * @param data a String array containing table values for a specific table entry. + * @return a {@code IniMap} instance with the value derived from the specified data array. + * Returns {@code null} if no data could be derived. + */ + public static IniMap processTableData(String[] data) + { + IniMap retVal = null; + if (data == null || data.length < 16) { + return retVal; + } + + String resref = SpriteTables.valueToString(data, SpriteTables.COLUMN_RESREF, ""); + if (resref.isEmpty()) { + return retVal; + } + int equipHelmet = SpriteTables.valueToInt(data, SpriteTables.COLUMN_HELMET, 0); + int falseColor = SpriteTables.valueToInt(data, SpriteTables.COLUMN_CLOWN, 0); + int hideWeapons = SpriteTables.valueToInt(data, SpriteTables.COLUMN_WEAPON, 1) != 0 ? 0 : 1; + String heightCode = SpriteTables.valueToString(data, SpriteTables.COLUMN_HEIGHT, ""); + String heightCodeHelmet = heightCode; + String shadow = SpriteTables.valueToString(data, SpriteTables.COLUMN_RESREF2, ""); + + List lines = processTableDataGeneral(data, ANIMATION_TYPE); + lines.add("[character_old]"); + lines.add("equip_helmet=" + equipHelmet); + lines.add("false_color=" + falseColor); + lines.add("hide_weapons=" + hideWeapons); + lines.add("resref=" + resref); + if (!heightCode.isEmpty()) { + lines.add("height_code=" + heightCode); + } + if (!heightCodeHelmet.isEmpty()) { + lines.add("height_code_helmet=" + heightCodeHelmet); + } + if (!shadow.isEmpty()) { + lines.add("shadow=" + shadow); + } + + retVal = IniMap.from(lines); + + return retVal; + } + + public CharacterOldDecoder(int animationId, IniMap ini) throws Exception + { + super(ANIMATION_TYPE, animationId, ini); + } + + public CharacterOldDecoder(CreResource cre) throws Exception + { + super(ANIMATION_TYPE, cre); + } + + /** Returns whether weapon animation overlays are suppressed. */ + public boolean isWeaponsHidden() { return getAttribute(KEY_HIDE_WEAPONS); } + protected void setWeaponsHidden(boolean b) { setAttribute(KEY_HIDE_WEAPONS, b); } + + /** Returns a separate shadow sprite resref. */ + public String getShadowResref() { return getAttribute(KEY_SHADOW); } + protected void setShadowResref(String s) + { + String shadow; + // taking care of harcoded shadows + switch (getAnimationId()) { + case 0x6400: // Drizzt + shadow = SHADOW_RESREF; + break; + case 0x6404: // Sarevok + shadow = SHADOW_RESREF_SAREVOK; + break; + default: + shadow = !s.isEmpty() ? s : SHADOW_RESREF; + } + setAttribute(KEY_SHADOW, shadow); + } + + /** + * Sets the maximum armor code value uses as suffix in animation filenames. + * Specify -1 to detect value automatically. + */ + @Override + protected void setMaxArmorCode(int v) + { + if (v < 0) { + // autodetection + for (int i = 1; i < 10 && v < 0; i++) { + String resref = getAnimationResref(); + if (!resref.isEmpty() && !ResourceFactory.resourceExists(resref + i + "G1.BAM")) { + v = i - 1; + } + } + } + super.setMaxArmorCode(v); + } + + @Override + public List getAnimationFiles(boolean essential) + { + ArrayList retVal = null; + String resref = getAnimationResref(); + + if (essential) { + HashSet files = new HashSet<>(); + for (final HashMap.Entry> entry : suffixMap.entrySet()) { + String suffix = SegmentDef.fixBehaviorSuffix(entry.getValue().getValue0()); + if (suffix.startsWith("G")) { + for (int i = 1; i <= getMaxArmorCode(); i++) { + files.add(resref + i + suffix + ".BAM"); + } + } + } + retVal = new ArrayList<>(Arrays.asList(files.toArray(new String[files.size()]))); + } else { + // collecting suffixes + HashSet actionSet = new HashSet<>(); + for (final HashMap.Entry> entry : suffixMap.entrySet()) { + String suffix = SegmentDef.fixBehaviorSuffix(entry.getValue().getValue0()); + actionSet.add(suffix); + } + actionSet.add(walkExtra.getValue0()); + + // generating file list + retVal = new ArrayList() {{ + for (int i = 1; i <= getMaxArmorCode(); i++) { + for (final String a : actionSet) { + add(resref + i + a + ".BAM"); + add(resref + i + a + "E.BAM"); + } + } + }}; + } + + return retVal; + } + + @Override + public boolean isSequenceAvailable(Sequence seq) + { + return (getSequenceDefinition(seq) != null); + } + + @Override + protected void init() throws Exception + { + super.init(); + IniMapSection section = getSpecificIniSection(); + setWeaponsHidden(section.getAsInteger(KEY_HIDE_WEAPONS.getName(), 0) != 0); + setShadowResref(section.getAsString(KEY_SHADOW.getName(), "")); + if (getMaxArmorCode() == 0) { + setMaxArmorCode(-1); + } + } + + @Override + protected SeqDef getSequenceDefinition(Sequence seq) + { + SeqDef retVal = null; + + if (!suffixMap.containsKey(seq)) { + return retVal; + } + + // getting armor level + int armorCode = getArmorCode(); + if (armorCode > getMaxArmorCode()) { + return retVal; + } + + // getting attack type + ItmResource itmWeapon = SpriteUtils.getEquippedWeapon(getCreResource()); + int itmAbility = SpriteUtils.getEquippedWeaponAbility(getCreResource()); + AttackType attackType = getAttackType(itmWeapon, itmAbility, false); + + EnumSet sequences = forbiddenSequences.get(attackType); + if (sequences != null && sequences.contains(seq)) { + // sequence not allowed for selected weapon + return retVal; + } + + ArrayList> resrefList = new ArrayList<>(); + + // getting BAM suffix and cycle index + String suffix = suffixMap.get(seq).getValue0(); + SegmentDef.Behavior behavior = SegmentDef.getBehaviorOf(suffix); + suffix = SegmentDef.fixBehaviorSuffix(suffix); + int cycleIdx = suffixMap.get(seq).getValue1().intValue(); + + // adding creature shadow + if (!getShadowResref().isEmpty()) { + resrefList.add(Couple.with(getShadowResref(), SegmentDef.SpriteType.AVATAR)); + } + + // adding creature sprite + resrefList.add(Couple.with(getAnimationResref() + armorCode, SegmentDef.SpriteType.AVATAR)); + + // adding helmet overlay + if (isHelmetEquipped()) { + String prefix = getHelmetHeightCode(); + if (!prefix.isEmpty()) { + String code = SpriteUtils.getItemAppearance(SpriteUtils.getEquippedHelmet(getCreResource())); + if (code.length() == 2) { + resrefList.add(Couple.with(prefix + code, SegmentDef.SpriteType.HELMET)); + } + } + } + + if (!isWeaponsHidden()) { + // adding shield overlay + String prefix = getHeightCode(); + if (!prefix.isEmpty()) { + ItmResource itm = SpriteUtils.getEquippedShield(getCreResource()); + String code = SpriteUtils.getItemAppearance(itm); + if (!code.isEmpty()) { + resrefList.add(Couple.with(prefix + code, SegmentDef.SpriteType.SHIELD)); + } + } + + // adding weapon overlay + prefix = getHeightCode(); + if (!prefix.isEmpty()) { + String code = SpriteUtils.getItemAppearance(itmWeapon); + if (code.length() == 2) { + if (ResourceFactory.resourceExists(prefix + code + suffix + ".BAM")) { + resrefList.add(Couple.with(prefix + code, SegmentDef.SpriteType.WEAPON)); + } else { + // weapon animation is crucial + return retVal; + } + } + } + } + + retVal = new SeqDef(seq); + for (final Couple data : resrefList) { + String prefix = data.getValue0(); + SegmentDef.SpriteType spriteType = data.getValue1(); + // defining sequences + ResourceEntry entry = ResourceFactory.getResourceEntry(prefix + suffix + ".BAM"); + ResourceEntry entryE = ResourceFactory.getResourceEntry(prefix + suffix + "E.BAM"); + if (entry != null) { + if (seq == Sequence.WALK) { + // special: uses full set of directions spread over two BAM files + String suffix2 = walkExtra.getValue0(); + int cycleIdx2 = walkExtra.getValue1().intValue(); + ResourceEntry entry2 = ResourceFactory.getResourceEntry(prefix + suffix2 + ".BAM"); + ResourceEntry entry2E = ResourceFactory.getResourceEntry(prefix + suffix2 + "E.BAM"); + if (SpriteUtils.bamCyclesExist(entry, cycleIdx, SeqDef.DIR_REDUCED_W.length) && + SpriteUtils.bamCyclesExist(entry2, cycleIdx2, SeqDef.DIR_REDUCED_W.length) && + SpriteUtils.bamCyclesExist(entryE, cycleIdx + SeqDef.DIR_REDUCED_W.length, SeqDef.DIR_REDUCED_E.length) && + SpriteUtils.bamCyclesExist(entry2E, cycleIdx2 + SeqDef.DIR_REDUCED_W.length, SeqDef.DIR_REDUCED_E.length)) { + // defining western directions + Direction[] dirX = new Direction[] { Direction.SSW, Direction.WSW, Direction.WNW, Direction.NNW, Direction.NNE }; + for (int i = 0; i < SeqDef.DIR_REDUCED_W.length; i++) { + retVal.addDirections(new DirDef(SeqDef.DIR_REDUCED_W[i], false, new CycleDef(entry, cycleIdx + i, spriteType, behavior))); + retVal.addDirections(new DirDef(dirX[i], false, new CycleDef(entry2, cycleIdx2 + i, spriteType, behavior))); + } + // defining eastern directions + dirX = new Direction[] { Direction.ENE, Direction.ESE, Direction.SSE, }; + for (int i = 0; i < SeqDef.DIR_REDUCED_E.length; i++) { + retVal.addDirections(new DirDef(SeqDef.DIR_REDUCED_E[i], false, new CycleDef(entryE, cycleIdx + SeqDef.DIR_REDUCED_W.length + i, spriteType, behavior))); + retVal.addDirections(new DirDef(dirX[i], false, new CycleDef(entry2E, cycleIdx2 + SeqDef.DIR_REDUCED_W.length + i, spriteType, behavior))); + } + } + } else { + if (SpriteUtils.bamCyclesExist(entry, cycleIdx, SeqDef.DIR_REDUCED_W.length) && + SpriteUtils.bamCyclesExist(entry, cycleIdx + SeqDef.DIR_REDUCED_W.length, SeqDef.DIR_REDUCED_E.length)) { + SeqDef tmp = SeqDef.createSequence(seq, SeqDef.DIR_REDUCED_W, false, entry, cycleIdx, spriteType, behavior); + retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); + tmp = SeqDef.createSequence(seq, SeqDef.DIR_REDUCED_E, false, entryE, cycleIdx + SeqDef.DIR_REDUCED_W.length, spriteType, behavior); + retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); + } + } + } + } + + if (retVal.isEmpty()) { + retVal = null; + } + + return retVal; + } +} diff --git a/src/org/infinity/resource/cre/decoder/EffectDecoder.java b/src/org/infinity/resource/cre/decoder/EffectDecoder.java new file mode 100644 index 000000000..84aec65da --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/EffectDecoder.java @@ -0,0 +1,257 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import org.infinity.resource.ResourceFactory; +import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.CycleDef; +import org.infinity.resource.cre.decoder.internal.DecoderAttribute; +import org.infinity.resource.cre.decoder.internal.DirDef; +import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.IniMap; +import org.infinity.util.IniMapSection; +import org.infinity.util.tuples.Couple; + +/** + * Creature animation decoder for processing type 0000 (effect) animations. + * Available ranges: [0000,0fff] + */ +public class EffectDecoder extends SpriteDecoder +{ + /** The animation type associated with this class definition. */ + public static final AnimationType ANIMATION_TYPE = AnimationType.EFFECT; + + public static final DecoderAttribute KEY_SHADOW = DecoderAttribute.with("shadow", DecoderAttribute.DataType.STRING); + public static final DecoderAttribute KEY_PALLETIZED = DecoderAttribute.with("palletized", DecoderAttribute.DataType.BOOLEAN); + public static final DecoderAttribute KEY_RANDOM_RENDER = DecoderAttribute.with("random_render", DecoderAttribute.DataType.BOOLEAN); + public static final DecoderAttribute KEY_DELTA_Z = DecoderAttribute.with("delta_z", DecoderAttribute.DataType.INT); + public static final DecoderAttribute KEY_ALT_PALETTE = DecoderAttribute.with("alt_palette", DecoderAttribute.DataType.STRING); + public static final DecoderAttribute KEY_HEIGHT_CODE_SHIELD = DecoderAttribute.with("height_code_shield", DecoderAttribute.DataType.STRING); + public static final DecoderAttribute KEY_HEIGHT_CODE_HELMET = DecoderAttribute.with("height_code_helmet", DecoderAttribute.DataType.STRING); + public static final DecoderAttribute KEY_HEIGHT_CODE = DecoderAttribute.with("height_code", DecoderAttribute.DataType.STRING); + public static final DecoderAttribute KEY_RESREF_PAPERDOLL = DecoderAttribute.with("resref_paperdoll", DecoderAttribute.DataType.STRING); + public static final DecoderAttribute KEY_RESREF_ARMOR_BASE = DecoderAttribute.with("resref_armor_base", DecoderAttribute.DataType.STRING); + public static final DecoderAttribute KEY_RESREF_ARMOR_SPECIFIC = DecoderAttribute.with("resref_armor_specific", DecoderAttribute.DataType.STRING); + // Note: these attribute are artificial to store hardcoded information + // The cycle to play back (if >= 0) + public static final DecoderAttribute KEY_CYCLE = DecoderAttribute.with("cycle", DecoderAttribute.DataType.INT); + // A secondary resref to consider if random_render == 1 + public static final DecoderAttribute KEY_RESREF2 = DecoderAttribute.with("resref2", DecoderAttribute.DataType.STRING); + + /** + * A helper method that parses the specified data array and generates a {@link IniMap} instance out of it. + * @param data a String array containing table values for a specific table entry. + * @return a {@code IniMap} instance with the value derived from the specified data array. + * Returns {@code null} if no data could be derived. + */ + public static IniMap processTableData(String[] data) + { + IniMap retVal = null; + if (data == null || data.length < 16) { + return retVal; + } + + String resref = SpriteTables.valueToString(data, SpriteTables.COLUMN_RESREF, ""); + if (resref.isEmpty()) { + return retVal; + } + String shadow = SpriteTables.valueToString(data, SpriteTables.COLUMN_RESREF2, ""); + int translucent = SpriteTables.valueToInt(data, SpriteTables.COLUMN_TRANSLUCENT, 0); + int falseColor = SpriteTables.valueToInt(data, SpriteTables.COLUMN_CLOWN, 0); + int random = SpriteTables.valueToInt(data, SpriteTables.COLUMN_SPLIT, 0); + int cycle = SpriteTables.valueToInt(data, SpriteTables.COLUMN_HELMET, -1); + String altPalette = SpriteTables.valueToString(data, SpriteTables.COLUMN_PALETTE2, ""); + String resref2 = SpriteTables.valueToString(data, SpriteTables.COLUMN_HEIGHT, ""); + + List lines = processTableDataGeneral(data, ANIMATION_TYPE); + lines.add("[effect]"); + lines.add("resref=" + resref); + if (!shadow.isEmpty()) { + lines.add("shadow=" + shadow); + } + lines.add("translucent=" + translucent); + lines.add("false_color=" + falseColor); + lines.add("random_render=" + random); + if (cycle >= 0) { + lines.add("cycle=" + cycle); + } + if (!altPalette.isEmpty()) { + lines.add("alt_palette=" + altPalette); + } + if (!resref2.isEmpty()) { + lines.add("resref2=" + resref2); + } + + retVal = IniMap.from(lines); + + return retVal; + } + + public EffectDecoder(int animationId, IniMap ini) throws Exception + { + super(ANIMATION_TYPE, animationId, ini); + } + + public EffectDecoder(CreResource cre) throws Exception + { + super(ANIMATION_TYPE, cre); + } + + /** Returns a separate shadow sprite resref. */ + public String getShadowResref() { return getAttribute(KEY_SHADOW); } + protected void setShadowResref(String s) { setAttribute(KEY_SHADOW, s); } + + /** Returns a secondary sprite resref to consider if RenderRandom() is set. */ + public String getSecondaryResref() { return getAttribute(KEY_RESREF2); } + protected void setSecondaryResref(String s) { setAttribute(KEY_RESREF2, s); } + + /** Returns a replacement palette resref (BMP). */ + public String getAltPalette() { return getAttribute(KEY_ALT_PALETTE); } + protected void setAltPalette(String s) { setAttribute(KEY_ALT_PALETTE, s); } + + /** Returns the height code prefix for shield overlay sprites. */ + public String getShieldHeightCode() { return getAttribute(KEY_HEIGHT_CODE_SHIELD); } + protected void setShieldHeightCode(String s) { setAttribute(KEY_HEIGHT_CODE_SHIELD, s); } + + /** Returns the height code prefix for helmet overlay sprites. */ + public String getHelmetHeightCode() { return getAttribute(KEY_HEIGHT_CODE_HELMET); } + protected void setHelmetHeightCode(String s) { setAttribute(KEY_HEIGHT_CODE_HELMET, s); } + + /** Returns the creature animation height code prefix. */ + public String getHeightCode() { return getAttribute(KEY_HEIGHT_CODE); } + protected void setHeightCode(String s) { setAttribute(KEY_HEIGHT_CODE, s); } + + /** Returns the paperdoll resref. */ + public String getPaperdollResref() { return getAttribute(KEY_RESREF_PAPERDOLL); } + protected void setPaperdollResref(String s) { setAttribute(KEY_RESREF_PAPERDOLL, s); } + + /** Returns animation resref suffix (last letter) for lesser armor types. */ + public String getArmorBaseResref() { return getAttribute(KEY_RESREF_ARMOR_BASE); } + protected void setArmorBaseResref(String s) { setAttribute(KEY_RESREF_ARMOR_BASE, s); } + + /** Returns animation resref suffix (last letter) for greater armor types. */ + public String getArmorSpecificResref() { return getAttribute(KEY_RESREF_ARMOR_SPECIFIC); } + protected void setArmorSpecificResref(String s) { setAttribute(KEY_RESREF_ARMOR_SPECIFIC, s); } + + /** unused */ + public boolean isPalettized() { return getAttribute(KEY_PALLETIZED); } + protected void setPalettized(boolean b) { setAttribute(KEY_PALLETIZED, b); } + + /** Returns whether a randomly chosen animation cycle is drawn. */ + public boolean isRenderRandom() { return getAttribute(KEY_RANDOM_RENDER); } + protected void setRenderRandom(boolean b) { setAttribute(KEY_RANDOM_RENDER, b); } + + /** Returns the BAM cycle index to use. -1 indicates no specific BAM cycle. */ + public int getCycle() { return getAttribute(KEY_CYCLE); } + protected void setCycle(int v) { setAttribute(KEY_CYCLE, v); } + + /** ??? */ + public int getDeltaZ() { return getAttribute(KEY_DELTA_Z); } + protected void setDeltaZ(int v) { setAttribute(KEY_DELTA_Z, v); } + + @Override + public String getNewPalette() + { + String retVal = getAltPalette(); + if (retVal == null || retVal.isEmpty()) { + retVal = super.getNewPalette(); + } + return retVal; + } + + @Override + public List getAnimationFiles(boolean essential) + { + ArrayList retVal = new ArrayList<>(); + retVal.add(getAnimationResref() + ".BAM"); + return retVal; + } + + @Override + public boolean isSequenceAvailable(Sequence seq) + { + return (getSequenceDefinition(seq) != null); + } + + @Override + protected void init() throws Exception + { + // setting properties + initDefaults(getAnimationInfo()); + IniMapSection section = getSpecificIniSection(); + setShadowResref(section.getAsString(KEY_SHADOW.getName(), "")); + setPalettized(section.getAsInteger(KEY_PALLETIZED.getName(), 0) != 0); + setTranslucent(section.getAsInteger(KEY_TRANSLUCENT.getName(), 0) != 0); + setRenderRandom(section.getAsInteger(KEY_RANDOM_RENDER.getName(), 0) != 0); + setFalseColor(section.getAsInteger(KEY_FALSE_COLOR.getName(), 0) != 0); + setCycle(section.getAsInteger(KEY_CYCLE.getName(), -1)); + setDeltaZ(section.getAsInteger(KEY_DELTA_Z.getName(), 0)); + setAltPalette(section.getAsString(KEY_ALT_PALETTE.getName(), "")); + setShieldHeightCode(section.getAsString(KEY_HEIGHT_CODE_SHIELD.getName(), "")); + setHelmetHeightCode(section.getAsString(KEY_HEIGHT_CODE_HELMET.getName(), "")); + setHeightCode(section.getAsString(KEY_HEIGHT_CODE.getName(), "")); + setPaperdollResref(section.getAsString(KEY_RESREF_PAPERDOLL.getName(), "")); + setArmorBaseResref(section.getAsString(KEY_RESREF_ARMOR_BASE.getName(), "")); + setArmorSpecificResref(section.getAsString(KEY_RESREF_ARMOR_SPECIFIC.getName(), "")); + } + + @Override + protected SeqDef getSequenceDefinition(Sequence seq) + { + SeqDef retVal = null; + if (seq != Sequence.STAND) { + return retVal; + } + + ArrayList> creResList = new ArrayList<>(); + if (!getShadowResref().isEmpty()) { + ResourceEntry shdEntry = ResourceFactory.getResourceEntry(getShadowResref() + ".BAM"); + if (shdEntry != null) { + creResList.add(Couple.with(shdEntry, 0)); + } + } + + Random rnd = new Random(); + String resref = getAnimationResref(); + if (isRenderRandom()) { + if (!getSecondaryResref().isEmpty() && (Math.abs(rnd.nextInt()) % 3) == 0) { + resref = getSecondaryResref(); + } + } + + ResourceEntry resEntry = ResourceFactory.getResourceEntry(resref + ".BAM"); + BamControl ctrl = SpriteUtils.loadBamController(resEntry); + if (ctrl != null) { + int cycle = 0; + if (isRenderRandom()) { + cycle = Math.abs(rnd.nextInt()) % ctrl.cycleCount(); + } else if (getCycle() >= 0) { + cycle = getCycle(); + } + creResList.add(Couple.with(resEntry, 0)); + + retVal = new SeqDef(seq); + for (final Couple data : creResList) { + resEntry = data.getValue0(); + cycle = data.getValue1().intValue(); + if (SpriteUtils.bamCyclesExist(resEntry, cycle, 1)) { + retVal.addDirections(new DirDef(Direction.S, false, new CycleDef(resEntry, cycle))); + } + } + } + + if (retVal.isEmpty()) { + retVal = null; + } + + return retVal; + } +} diff --git a/src/org/infinity/resource/cre/decoder/FlyingDecoder.java b/src/org/infinity/resource/cre/decoder/FlyingDecoder.java new file mode 100644 index 000000000..d8d2b0a85 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/FlyingDecoder.java @@ -0,0 +1,116 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder; + +import java.util.ArrayList; +import java.util.List; + +import org.infinity.resource.ResourceFactory; +import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.DirDef; +import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.IniMap; +import org.infinity.util.IniMapSection; + +/** + * Creature animation decoder for processing type D000 (flying) animations. + * Available ranges: [d000,dfff] + */ +public class FlyingDecoder extends SpriteDecoder +{ + /** The animation type associated with this class definition. */ + public static final AnimationType ANIMATION_TYPE = AnimationType.FLYING; + + /** + * A helper method that parses the specified data array and generates a {@link IniMap} instance out of it. + * @param data a String array containing table values for a specific table entry. + * @return a {@code IniMap} instance with the value derived from the specified data array. + * Returns {@code null} if no data could be derived. + */ + public static IniMap processTableData(String[] data) + { + IniMap retVal = null; + if (data == null || data.length < 16) { + return retVal; + } + + String resref = SpriteTables.valueToString(data, SpriteTables.COLUMN_RESREF, ""); + if (resref.isEmpty()) { + return retVal; + } + int falseColor = SpriteTables.valueToInt(data, SpriteTables.COLUMN_CLOWN, 0); + + List lines = processTableDataGeneral(data, ANIMATION_TYPE); + lines.add("[flying]"); + lines.add("false_color=" + falseColor); + lines.add("resref=" + resref); + + retVal = IniMap.from(lines); + + return retVal; + } + + public FlyingDecoder(int animationId, IniMap ini) throws Exception + { + super(ANIMATION_TYPE, animationId, ini); + } + + public FlyingDecoder(CreResource cre) throws Exception + { + super(ANIMATION_TYPE, cre); + } + + @Override + public List getAnimationFiles(boolean essential) + { + ArrayList retVal = new ArrayList<>(); + retVal.add(getAnimationResref() + ".BAM"); + return retVal; + } + + @Override + public boolean isSequenceAvailable(Sequence seq) + { + return (getSequenceDefinition(seq) != null); + } + + @Override + protected void init() throws Exception + { + // setting properties + initDefaults(getAnimationInfo()); + IniMapSection section = getSpecificIniSection(); + setFalseColor(section.getAsInteger(KEY_FALSE_COLOR.getName(), 0) != 0); + } + + @Override + protected SeqDef getSequenceDefinition(Sequence seq) + { + SeqDef retVal = null; + + int cycleIndex = 0; + switch (seq) { + case STAND: + cycleIndex = 0; + break; + case WALK: + cycleIndex = 9; + break; + default: + return retVal; + } + + ResourceEntry entry = ResourceFactory.getResourceEntry(getAnimationResref() + ".BAM"); + if (SpriteUtils.bamCyclesExist(entry, cycleIndex, SeqDef.DIR_FULL_W.length)) { + retVal = SeqDef.createSequence(seq, SeqDef.DIR_FULL_W, false, entry, cycleIndex); + SeqDef tmp = SeqDef.createSequence(seq, SeqDef.DIR_FULL_E, true, entry, cycleIndex + 1); + retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); + } + + return retVal; + } +} diff --git a/src/org/infinity/resource/cre/decoder/MonsterAnkhegDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterAnkhegDecoder.java new file mode 100644 index 000000000..a34728a03 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/MonsterAnkhegDecoder.java @@ -0,0 +1,178 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.infinity.resource.ResourceFactory; +import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.DecoderAttribute; +import org.infinity.resource.cre.decoder.internal.DirDef; +import org.infinity.resource.cre.decoder.internal.SegmentDef; +import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.IniMap; +import org.infinity.util.IniMapSection; +import org.infinity.util.tuples.Couple; + +/** + * Creature animation decoder for processing type 3000 (monster_ankheg) animations. + * Available ranges: [3000,3fff] + */ +public class MonsterAnkhegDecoder extends SpriteDecoder +{ + /** The animation type associated with this class definition. */ + public static final AnimationType ANIMATION_TYPE = AnimationType.MONSTER_ANKHEG; + + public static final DecoderAttribute KEY_MIRROR = DecoderAttribute.with("mirror", DecoderAttribute.DataType.BOOLEAN); + public static final DecoderAttribute KEY_EXTEND_DIRECTION = DecoderAttribute.with("extend_direction", DecoderAttribute.DataType.BOOLEAN); + + private static final HashMap> suffixMap = new HashMap>() {{ + // Note: int value indicates direction segment multiplier + put(Sequence.DIE, Couple.with("G1", 1)); + put(Sequence.SLEEP, get(Sequence.DIE)); + put(Sequence.GET_UP, Couple.with("!G1", 1)); + put(Sequence.TWITCH, Couple.with("G1", 2)); + put(Sequence.STAND_EMERGED, Couple.with("G1", 3)); + put(Sequence.STAND_HIDDEN, Couple.with("G2", 0)); + put(Sequence.EMERGE, Couple.with("G2", 1)); + put(Sequence.HIDE, Couple.with("G2", 2)); + put(Sequence.ATTACK, Couple.with("G3", 0)); + put(Sequence.SPELL, Couple.with("G3", 1)); + }}; + + /** + * A helper method that parses the specified data array and generates a {@link IniMap} instance out of it. + * @param data a String array containing table values for a specific table entry. + * @return a {@code IniMap} instance with the value derived from the specified data array. + * Returns {@code null} if no data could be derived. + */ + public static IniMap processTableData(String[] data) + { + IniMap retVal = null; + if (data == null || data.length < 16) { + return retVal; + } + + String resref = SpriteTables.valueToString(data, SpriteTables.COLUMN_RESREF, ""); + if (resref.isEmpty()) { + return retVal; + } + int falseColor = SpriteTables.valueToInt(data, SpriteTables.COLUMN_CLOWN, 0); + + List lines = processTableDataGeneral(data, ANIMATION_TYPE); + lines.add("[monster_ankheg]"); + lines.add("false_color=" + falseColor); + lines.add("resref=" + resref); + + retVal = IniMap.from(lines); + + return retVal; + } + + public MonsterAnkhegDecoder(int animationId, IniMap ini) throws Exception + { + super(ANIMATION_TYPE, animationId, ini); + } + + public MonsterAnkhegDecoder(CreResource cre) throws Exception + { + super(ANIMATION_TYPE, cre); + } + + /** Returns whether eastern directions are calculated. */ + public boolean hasMirroredDirections() { return getAttribute(KEY_MIRROR); } + protected void setMirroredDirections(boolean b) { setAttribute(KEY_MIRROR, b); } + + /** Returns whether the animation provides the full set of directions. */ + public boolean hasExtendedDirections() { return getAttribute(KEY_EXTEND_DIRECTION); } + protected void setExtendedDirections(boolean b) { setAttribute(KEY_EXTEND_DIRECTION, b); } + + @Override + public List getAnimationFiles(boolean essential) + { + String resref = getAnimationResref(); + ArrayList retVal = new ArrayList() {{ + add(resref + "DG1.BAM"); + if (!hasMirroredDirections()) { add(resref + "DG1E.BAM"); } + add(resref + "DG2.BAM"); + if (!hasMirroredDirections()) { add(resref + "DG2E.BAM"); } + add(resref + "DG3.BAM"); + if (!hasMirroredDirections()) { add(resref + "DG3E.BAM"); } + add(resref + "G1.BAM"); + if (!hasMirroredDirections()) { add(resref + "G1E.BAM"); } + add(resref + "G2.BAM"); + if (!hasMirroredDirections()) { add(resref + "G2E.BAM"); } + add(resref + "G3.BAM"); + if (!hasMirroredDirections()) { add(resref + "G3E.BAM"); } + }}; + return retVal; + } + + @Override + public boolean isSequenceAvailable(Sequence seq) + { + return (getSequenceDefinition(seq) != null); + } + + @Override + protected void init() throws Exception + { + // setting properties + initDefaults(getAnimationInfo()); + IniMapSection section = getSpecificIniSection(); + setMirroredDirections(section.getAsInteger(KEY_MIRROR.getName(), 0) != 0); + setExtendedDirections(section.getAsInteger(KEY_EXTEND_DIRECTION.getName(), 0) != 0); + setDetectedByInfravision(section.getAsInteger(KEY_DETECTED_BY_INFRAVISION.getName(), 0) != 0); + setFalseColor(section.getAsInteger(KEY_FALSE_COLOR.getName(), 0) != 0); + } + + @Override + protected SeqDef getSequenceDefinition(Sequence seq) + { + SeqDef retVal = null; + String resref = getAnimationResref(); + + Direction[] dirWest = hasExtendedDirections() ? SeqDef.DIR_FULL_W : SeqDef.DIR_REDUCED_W; + Direction[] dirEast = hasExtendedDirections() ? SeqDef.DIR_FULL_E : SeqDef.DIR_REDUCED_E; + int seg = dirWest.length; + if (!hasMirroredDirections()) { + seg += dirEast.length; + } + + String suffixE = hasMirroredDirections() ? "" : "E"; + int eastOfs = hasMirroredDirections() ? 1 : dirWest.length; + Couple data = suffixMap.get(seq); + if (data == null) { + return retVal; + } + + retVal = new SeqDef(seq); + SegmentDef.Behavior behavior = SegmentDef.getBehaviorOf(data.getValue0()); + String suffix = SegmentDef.fixBehaviorSuffix(data.getValue0()); + for (final String type : new String[] {"D", ""}) { + ResourceEntry entry = ResourceFactory.getResourceEntry(resref + type + suffix + ".BAM"); + int cycle = data.getValue1().intValue() * seg; + ResourceEntry entryE = ResourceFactory.getResourceEntry(resref + type + suffix + suffixE + ".BAM"); + int cycleE = cycle + eastOfs; + + if (SpriteUtils.bamCyclesExist(entry, cycle, dirWest.length) && + SpriteUtils.bamCyclesExist(entryE, cycleE, dirEast.length)) { + SeqDef tmp = SeqDef.createSequence(seq, dirWest, false, entry, cycle, null, behavior); + retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); + tmp = SeqDef.createSequence(seq, dirEast, hasMirroredDirections(), entryE, cycleE, null, behavior); + retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); + } + } + + if (retVal.isEmpty()) { + retVal = null; + } + return retVal; + } +} diff --git a/src/org/infinity/resource/cre/decoder/MonsterDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterDecoder.java new file mode 100644 index 000000000..db90e6a78 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/MonsterDecoder.java @@ -0,0 +1,336 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; + +import org.infinity.resource.ResourceFactory; +import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.DecoderAttribute; +import org.infinity.resource.cre.decoder.internal.DirDef; +import org.infinity.resource.cre.decoder.internal.SegmentDef; +import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.itm.ItmResource; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.IniMap; +import org.infinity.util.IniMapSection; +import org.infinity.util.tuples.Couple; + +/** + * Creature animation decoder for processing type 7000 (monster) animations. + * Available ranges: (using notation slot/range where slot can be a formula with range definitions as [x,y]) + * (0x7002 | ([0x00,0x1f] << 4))/0xd + * (0x7004 | ([0x20,0x2f] << 4))/0xb + * (0x7000 | ([0x30,0x3f] << 4))/0xf + * (0x7003 | ([0x40,0x4f] << 4))/0xc + * (0x7002 | ([0x50,0x5f] << 4))/0xd + * (0x7003 | ([0x70,0x7f] << 4))/0xc + * (0x7005 | ([0x90,0xaf] << 4))/0xa + * (0x7007 | ([0xb0,0xbf] << 4))/0x8 + * (0x7002 | ([0xc0,0xcf] << 4))/0xd + * (0x7002 | ([0xe0,0xef] << 4))/0xd + * (0x7000 | ([0xf0,0xff] << 4))/0xf + */ +public class MonsterDecoder extends SpriteDecoder +{ + /** The animation type associated with this class definition. */ + public static final AnimationType ANIMATION_TYPE = AnimationType.MONSTER; + + public static final DecoderAttribute KEY_CAN_LIE_DOWN = DecoderAttribute.with("can_lie_down", DecoderAttribute.DataType.BOOLEAN); + public static final DecoderAttribute KEY_PATH_SMOOTH = DecoderAttribute.with("path_smooth", DecoderAttribute.DataType.BOOLEAN); + public static final DecoderAttribute KEY_SPLIT_BAMS = DecoderAttribute.with("split_bams", DecoderAttribute.DataType.BOOLEAN); + public static final DecoderAttribute KEY_GLOW_LAYER = DecoderAttribute.with("glow_layer", DecoderAttribute.DataType.STRING); + public static final DecoderAttribute KEY_PALETTE1 = DecoderAttribute.with("palette1", DecoderAttribute.DataType.STRING); + public static final DecoderAttribute KEY_PALETTE2 = DecoderAttribute.with("palette2", DecoderAttribute.DataType.STRING); + + /** Assigns BAM suffix and cycle index to a specific animation sequence (unsplit version). */ + private static final HashMap> suffixMapUnsplit = new HashMap>() {{ + put(Sequence.WALK, Couple.with("G1", 0)); + put(Sequence.STANCE, Couple.with("G1", 9)); + put(Sequence.STAND, Couple.with("G1", 18)); + put(Sequence.GET_HIT, Couple.with("G1", 27)); + put(Sequence.DIE, Couple.with("G1", 36)); + put(Sequence.TWITCH, Couple.with("G1", 45)); + put(Sequence.SLEEP, Couple.with("G1", 54)); + put(Sequence.GET_UP, Couple.with("G1", 63)); + put(Sequence.ATTACK, Couple.with("G2", 0)); + put(Sequence.ATTACK_2, Couple.with("G2", 9)); + put(Sequence.ATTACK_3, Couple.with("G2", 18)); + put(Sequence.ATTACK_4, Couple.with("G2", 27)); + put(Sequence.ATTACK_5, Couple.with("G2", 36)); + put(Sequence.SPELL, Couple.with("G2", 45)); + put(Sequence.CAST, Couple.with("G2", 54)); + }}; + + /** Assigns BAM suffix and cycle index to a specific animation sequence (split version). */ + private static final HashMap> suffixMapSplit = new HashMap>() {{ + put(Sequence.WALK, Couple.with("G11", 0)); + put(Sequence.STANCE, Couple.with("G1", 9)); + put(Sequence.STAND, Couple.with("G12", 18)); + put(Sequence.GET_HIT, Couple.with("G13", 27)); + put(Sequence.DIE, Couple.with("G14", 36)); + put(Sequence.SLEEP, get(Sequence.DIE)); + put(Sequence.GET_UP, Couple.with("!G14", 36)); + put(Sequence.TWITCH, Couple.with("G15", 45)); + put(Sequence.ATTACK, Couple.with("G2", 0)); + put(Sequence.ATTACK_2, Couple.with("G21", 9)); + put(Sequence.ATTACK_3, Couple.with("G22", 18)); + put(Sequence.ATTACK_4, Couple.with("G23", 27)); + put(Sequence.ATTACK_5, Couple.with("G24", 36)); + put(Sequence.SPELL, Couple.with("G25", 45)); + put(Sequence.CAST, Couple.with("G26", 54)); + }}; + + /** Replacement sequences if original sequence definition is missing (unsplit version). */ + private static final HashMap> replacementMapUnsplit = new HashMap>() {{ + put(Sequence.DIE, suffixMapUnsplit.get(Sequence.SLEEP)); + put(Sequence.SLEEP, suffixMapUnsplit.get(Sequence.DIE)); + put(Sequence.GET_UP, Couple.with("!" + suffixMapUnsplit.get(Sequence.DIE).getValue0(), suffixMapUnsplit.get(Sequence.DIE).getValue1())); + }}; + + /** Replacement sequences if original sequence definition is missing (split version). */ + private static final HashMap> replacementMapSplit = new HashMap>() {{ + // not needed + }}; + + + /** + * A helper method that parses the specified data array and generates a {@link IniMap} instance out of it. + * @param data a String array containing table values for a specific table entry. + * @return a {@code IniMap} instance with the value derived from the specified data array. + * Returns {@code null} if no data could be derived. + */ + public static IniMap processTableData(String[] data) + { + IniMap retVal = null; + if (data == null || data.length < 16) { + return retVal; + } + + String resref = SpriteTables.valueToString(data, SpriteTables.COLUMN_RESREF, ""); + if (resref.isEmpty()) { + return retVal; + } + int falseColor = SpriteTables.valueToInt(data, SpriteTables.COLUMN_CLOWN, 0); + int splitBams = SpriteTables.valueToInt(data, SpriteTables.COLUMN_SPLIT, 0); + int translucent = SpriteTables.valueToInt(data, SpriteTables.COLUMN_TRANSLUCENT, 0); + String palette1 = SpriteTables.valueToString(data, SpriteTables.COLUMN_PALETTE, ""); + String palette2 = SpriteTables.valueToString(data, SpriteTables.COLUMN_PALETTE2, ""); + + List lines = processTableDataGeneral(data, ANIMATION_TYPE); + lines.add("[monster]"); + lines.add("false_color=" + falseColor); + lines.add("split_bams=" + splitBams); + lines.add("translucent=" + translucent); + lines.add("resref=" + resref); + if (!palette1.isEmpty()) { + lines.add("palette1=" + palette1); + } + if (!palette2.isEmpty()) { + lines.add("palette2=" + palette2); + } + + retVal = IniMap.from(lines); + + return retVal; + } + + public MonsterDecoder(int animationId, IniMap ini) throws Exception + { + super(ANIMATION_TYPE, animationId, ini); + } + + public MonsterDecoder(CreResource cre) throws Exception + { + super(ANIMATION_TYPE, cre); + } + + /** Returns the correct sequence map for the current settings. */ + private HashMap> getSuffixMap() + { + return isSplittedBams() ? suffixMapSplit : suffixMapUnsplit; + } + + /** Returns whether the creature falls down when dead/unconscious. */ + public boolean canLieDown() { return getAttribute(KEY_CAN_LIE_DOWN); } + protected void setCanLieDown(boolean b) { setAttribute(KEY_CAN_LIE_DOWN, b); } + + /** ??? */ + public boolean isSmoothPath() { return getAttribute(KEY_PATH_SMOOTH); } + protected void setSmoothPath(boolean b) { setAttribute(KEY_PATH_SMOOTH, b); } + + /** Returns whether animations are spread over various subfiles. */ + public boolean isSplittedBams() { return getAttribute(KEY_SPLIT_BAMS); } + protected void setSplittedBams(boolean b) { setAttribute(KEY_SPLIT_BAMS, b); } + + /** + * Returns the solid background layer of blended/glowing creature animations. + * (Note: currently not supported by the engine.) + */ + public String getGlowLayer() { return getAttribute(KEY_GLOW_LAYER); } + protected void setGlowLayer(String s) { setAttribute(KEY_GLOW_LAYER, s); } + + /** + * Returns the first replacement palette (BMP) for the creature animation. + * Falls back to new palette from general attributes. + */ + public String getPalette1() + { + String retVal = getAttribute(KEY_PALETTE1); + if (retVal.isEmpty()) { + retVal = getNewPalette(); + } + return retVal; + } + + protected void setPalette1(String s) { setAttribute(KEY_PALETTE1, s); } + + /** + * Returns the second replacement palette (BMP) for the creature animation. + * Falls back to palette1 or new palette from general attributes. + */ + public String getPalette2() + { + String retVal = getAttribute(KEY_PALETTE2); + if (retVal.isEmpty()) { + retVal = getPalette1(); + } + return retVal; + } + + protected void setPalette2(String s) { setAttribute(KEY_PALETTE2, s); } + + @Override + public List getAnimationFiles(boolean essential) + { + // collecting suffixes + String resref = getAnimationResref(); + HashSet files = new HashSet<>(); + for (final HashMap.Entry> entry : getSuffixMap().entrySet()) { + String suffix = SegmentDef.fixBehaviorSuffix(entry.getValue().getValue0()); + files.add(resref + suffix + ".BAM"); + } + + // generating file list + ArrayList retVal = new ArrayList<>(Arrays.asList(files.toArray(new String[files.size()]))); + + return retVal; + } + + @Override + public boolean isSequenceAvailable(Sequence seq) + { + return (getSequenceDefinition(seq) != null); + } + + @Override + protected void init() throws Exception + { + // setting properties + initDefaults(getAnimationInfo()); + IniMapSection section = getSpecificIniSection(); + setCanLieDown(section.getAsInteger(KEY_CAN_LIE_DOWN.getName(), 0) != 0); + setFalseColor(section.getAsInteger(KEY_FALSE_COLOR.getName(), 0) != 0); + setDetectedByInfravision(section.getAsInteger(KEY_DETECTED_BY_INFRAVISION.getName(), 0) != 0); + setSmoothPath(section.getAsInteger(KEY_PATH_SMOOTH.getName(), 0) != 0); + setSplittedBams(section.getAsInteger(KEY_SPLIT_BAMS.getName(), 0) != 0); + setTranslucent(section.getAsInteger(KEY_TRANSLUCENT.getName(), 0) != 0); + String s = section.getAsString(KEY_GLOW_LAYER.getName(), ""); + if (s.isEmpty()) { + // KEY_GLOW_LAYER maybe incorrectly assigned to "general" section + s = getGeneralIniSection(getAnimationInfo()).getAsString(KEY_GLOW_LAYER.getName(), ""); + } + setGlowLayer(s); + setPalette1(section.getAsString(KEY_PALETTE1.getName(), "")); + setPalette2(section.getAsString(KEY_PALETTE2.getName(), "")); + } + + @Override + protected SeqDef getSequenceDefinition(Sequence seq) + { + SeqDef retVal = null; + + Couple data = getSuffixMap().get(seq); + if (data == null) { + return retVal; + } + + ArrayList> creResList = new ArrayList<>(); + + // processing creature sprite + String resref = getAnimationResref(); + String suffix = data.getValue0(); + if (!SpriteUtils.bamCyclesExist(ResourceFactory.getResourceEntry(resref + SegmentDef.fixBehaviorSuffix(suffix) + ".BAM"), + data.getValue1().intValue(), 9)) { + data = (isSplittedBams() ? replacementMapSplit: replacementMapUnsplit).get(seq); + if (data == null) { + return retVal; + } + suffix = data.getValue0(); + if (!ResourceFactory.resourceExists(resref + SegmentDef.fixBehaviorSuffix(suffix) + ".BAM")) { + return retVal; + } + } + SegmentDef.Behavior behavior = SegmentDef.getBehaviorOf(suffix); + suffix = SegmentDef.fixBehaviorSuffix(suffix); + int ofs = data.getValue1().intValue(); + creResList.add(Couple.with(resref + suffix + ".BAM", SegmentDef.SpriteType.AVATAR)); + + // processing weapon overlay + ItmResource itm = SpriteUtils.getEquippedWeapon(getCreResource()); + if (itm != null) { + String weapon = SpriteUtils.getItemAppearance(itm).trim(); + if (!weapon.isEmpty()) { + Couple wdata = suffixMapUnsplit.get(seq); + if (wdata != null) { + creResList.add(Couple.with(resref + wdata.getValue0() + weapon + ".BAM", SegmentDef.SpriteType.WEAPON)); + } + } + } + + retVal = new SeqDef(seq); + for (final Couple creInfo : creResList) { + ResourceEntry entry = ResourceFactory.getResourceEntry(creInfo.getValue0()); + if (SpriteUtils.bamCyclesExist(entry, ofs, SeqDef.DIR_FULL_W.length)) { + SeqDef tmp = SeqDef.createSequence(seq, SeqDef.DIR_FULL_W, false, entry, ofs, null, behavior); + retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); + tmp = SeqDef.createSequence(seq, SeqDef.DIR_FULL_E, true, entry, ofs + 1, null, behavior); + retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); + } else if (entry != null && SpriteUtils.getBamCycles(entry) == 1) { + // fallback solution: just use first bam cycle (required by a few animations) + SeqDef tmp = SeqDef.createSequence(seq, new Direction[] {Direction.S}, false, entry, 0, null, behavior); + retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); + } + } + + if (retVal.isEmpty()) { + retVal = null; + } + + return retVal; + } + + @Override + protected int[] getNewPaletteData(ResourceEntry bamRes) + { + if (bamRes != null) { + String resref = bamRes.getResourceRef(); + if (resref.length() >= 6) { + switch (resref.charAt(5)) { + case '1': + return SpriteUtils.loadReplacementPalette(getPalette1()); + case '2': + return SpriteUtils.loadReplacementPalette(getPalette2()); + } + } + } + return super.getNewPaletteData(bamRes); + } +} diff --git a/src/org/infinity/resource/cre/decoder/MonsterIcewindDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterIcewindDecoder.java new file mode 100644 index 000000000..0d5ca619d --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/MonsterIcewindDecoder.java @@ -0,0 +1,221 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.infinity.datatype.IsTextual; +import org.infinity.resource.ResourceFactory; +import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.DecoderAttribute; +import org.infinity.resource.cre.decoder.internal.DirDef; +import org.infinity.resource.cre.decoder.internal.SegmentDef; +import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.itm.ItmResource; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.IniMap; +import org.infinity.util.IniMapSection; + +/** + * Creature animation decoder for processing type E000 (monster_icewind) animations. + * Available ranges: [e000,efff] + */ +public class MonsterIcewindDecoder extends SpriteDecoder +{ + /** The animation type associated with this class definition. */ + public static final AnimationType ANIMATION_TYPE = AnimationType.MONSTER_ICEWIND; + + public static final DecoderAttribute KEY_WEAPON_LEFT_HAND = DecoderAttribute.with("weapon_left_hand", DecoderAttribute.DataType.BOOLEAN); + + private static final HashMap seqMap = new HashMap() {{ + put(Sequence.ATTACK, "A1"); + put(Sequence.ATTACK_2, "A2"); + put(Sequence.ATTACK_3, "A3"); + put(Sequence.ATTACK_4, "A4"); + put(Sequence.CAST, "CA"); + put(Sequence.DIE, "DE"); + put(Sequence.GET_HIT, "GH"); + put(Sequence.GET_UP, "GU"); + put(Sequence.STANCE, "SC"); + put(Sequence.STAND, "SD"); + put(Sequence.SLEEP, "SL"); + put(Sequence.SPELL, "SP"); + put(Sequence.TWITCH, "TW"); + put(Sequence.WALK, "WK"); + }}; + + private static final HashMap replacementMap = new HashMap() {{ + put(Sequence.DIE, seqMap.get(Sequence.SLEEP)); + put(Sequence.SLEEP, seqMap.get(Sequence.DIE)); + put(Sequence.GET_UP, "!" + seqMap.get(Sequence.DIE)); + }}; + + /** + * A helper method that parses the specified data array and generates a {@link IniMap} instance out of it. + * @param data a String array containing table values for a specific table entry. + * @return a {@code IniMap} instance with the value derived from the specified data array. + * Returns {@code null} if no data could be derived. + */ + public static IniMap processTableData(String[] data) + { + IniMap retVal = null; + if (data == null || data.length < 16) { + return retVal; + } + + String resref = SpriteTables.valueToString(data, SpriteTables.COLUMN_RESREF, ""); + if (resref.isEmpty()) { + return retVal; + } + int translucent = SpriteTables.valueToInt(data, SpriteTables.COLUMN_TRANSLUCENT, 0); + int leftHanded = SpriteTables.valueToInt(data, SpriteTables.COLUMN_WEAPON, 0); + + List lines = processTableDataGeneral(data, ANIMATION_TYPE); + lines.add("[monster_icewind]"); + lines.add("weapon_left_hand=" + leftHanded); + lines.add("translucent=" + translucent); + lines.add("resref=" + resref); + + retVal = IniMap.from(lines); + + return retVal; + } + + public MonsterIcewindDecoder(int animationId, IniMap ini) throws Exception + { + super(ANIMATION_TYPE, animationId, ini); + } + + public MonsterIcewindDecoder(CreResource cre) throws Exception + { + super(ANIMATION_TYPE, cre); + } + + /** ??? */ + public boolean isWeaponInLeftHand() { return getAttribute(KEY_WEAPON_LEFT_HAND); } + protected void setWeaponInLeftHand(boolean b) { setAttribute(KEY_WEAPON_LEFT_HAND, b); } + + @Override + public List getAnimationFiles(boolean essential) + { + ArrayList retVal = new ArrayList<>(); + String resref = getAnimationResref(); + + final String[] defOvls = essential ? new String[] { "" } + : new String[] { "", "A", "B", "C", "D", "F", "H", "M", "Q", "S", "W" }; + final String[] defSeqs = essential ? new String[] { "DE", "GH", "SD", "WK" } + : new String[] { "A1", "A2", "A3", "A4", "CA", "DE", "GH", "GU", "SC", "SD", "SL", "SP", "TW", "WK" }; + for (final String ovl : defOvls) { + for (final String seq : defSeqs) { + String bamFile = resref + ovl + seq + ".BAM"; + if (ResourceFactory.resourceExists(bamFile)) { + retVal.add(bamFile); + } + bamFile = resref + ovl + seq + "E.BAM"; + if (ResourceFactory.resourceExists(bamFile)) { + retVal.add(bamFile); + } + } + } +// final String[] wovl; +// final String[] seqs; +// if (essential) { +// wovl = new String[] { "" }; +// seqs = new String[] { "DE", "GH", "SD", "WK" }; +// } else { +// wovl = new String[] { "", "A", "B", "C", "D", "F", "H", "M", "Q", "S", "W" }; +// seqs = new String[] { "A1", "A2", "A3", "A4", "CA", "DE", "GH", "GU", "SC", "SD", "SL", "SP", "TW", "WK" }; +// } +// for (final String wpn : wovl) { +// for (final String seq : seqs) { +// String bamFile = resref + wpn + seq + ".BAM"; +// retVal.add(resref + wpn + seq + "E.BAM"); +// } +// } + + return retVal; + } + + @Override + public boolean isSequenceAvailable(Sequence seq) + { + return (getSequenceDefinition(seq) != null); + } + + @Override + protected void init() throws Exception + { + // setting properties + initDefaults(getAnimationInfo()); + IniMapSection section = getSpecificIniSection(); + setWeaponInLeftHand(section.getAsInteger(KEY_WEAPON_LEFT_HAND.getName(), 0) != 0); + setTranslucent(section.getAsInteger(KEY_TRANSLUCENT.getName(), 0) != 0); + setDetectedByInfravision(section.getAsInteger(KEY_DETECTED_BY_INFRAVISION.getName(), 0) != 0); + } + + @Override + protected SeqDef getSequenceDefinition(Sequence seq) + { + SeqDef retVal = null; + if (!seqMap.containsKey(seq)) { + return retVal; + } + + String resref = getAnimationResref(); + + // getting weapon code from CRE resource + String weapon = ""; + ItmResource itm = SpriteUtils.getEquippedWeapon(getCreResource()); + if (itm != null) { + weapon = ((IsTextual)itm.getAttribute(ItmResource.ITM_EQUIPPED_APPEARANCE)).getText(); + if (!weapon.isEmpty()) { + weapon = weapon.substring(0, 1).trim(); + } + weapon = weapon.trim(); + } + + // checking availability of sequence + String suffix = seqMap.get(seq); + if (!ResourceFactory.resourceExists(resref + SegmentDef.fixBehaviorSuffix(suffix) + ".BAM")) { + suffix = replacementMap.get(seq); + if (!ResourceFactory.resourceExists(resref + SegmentDef.fixBehaviorSuffix(suffix) + ".BAM")) { + return retVal; + } + } + + SegmentDef.Behavior behavior = SegmentDef.getBehaviorOf(suffix); + suffix = SegmentDef.fixBehaviorSuffix(suffix); + + retVal = new SeqDef(seq); + String[] ovls = weapon.isEmpty() ? new String[] {""} : new String[] {"", weapon}; + for (final String ovl : ovls) { + SegmentDef.SpriteType spriteType = (!weapon.isEmpty() && ovl.equals(weapon)) ? SegmentDef.SpriteType.WEAPON : SegmentDef.SpriteType.AVATAR; + ResourceEntry entry = ResourceFactory.getResourceEntry(resref + ovl + suffix + ".BAM"); + if (entry != null) { + ResourceEntry entryE = ResourceFactory.getResourceEntry(resref + ovl + suffix + "E.BAM"); + if (SpriteUtils.bamCyclesExist(entry, 0, SeqDef.DIR_REDUCED_W.length)) { + SeqDef tmp = SeqDef.createSequence(seq, SeqDef.DIR_REDUCED_W, false, entry, 0, spriteType, behavior); + retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); + if (SpriteUtils.bamCyclesExist(entryE, 0, SeqDef.DIR_REDUCED_E.length)) { + tmp = SeqDef.createSequence(seq, SeqDef.DIR_REDUCED_E, false, entryE, SeqDef.DIR_REDUCED_W.length, spriteType, behavior); + } else { + // fallback: mirror eastern directions + tmp = SeqDef.createSequence(seq, SeqDef.DIR_REDUCED_E, true, entry, 1, spriteType, behavior); + } + retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); + } + } + } + + if (retVal.isEmpty()) { + retVal = null; + } + + return retVal; + } +} diff --git a/src/org/infinity/resource/cre/decoder/MonsterLarge16Decoder.java b/src/org/infinity/resource/cre/decoder/MonsterLarge16Decoder.java new file mode 100644 index 000000000..5019f61be --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/MonsterLarge16Decoder.java @@ -0,0 +1,144 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.infinity.resource.ResourceFactory; +import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.DirDef; +import org.infinity.resource.cre.decoder.internal.SegmentDef; +import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.IniMap; +import org.infinity.util.IniMapSection; +import org.infinity.util.tuples.Couple; + +/** + * Creature animation decoder for processing type A000 (monster_large16) animations. + * Available ranges: [a000,afff] + */ +public class MonsterLarge16Decoder extends SpriteDecoder +{ + /** The animation type associated with this class definition. */ + public static final AnimationType ANIMATION_TYPE = AnimationType.MONSTER_LARGE_16; + + private static final HashMap> suffixMap = new HashMap>() {{ + put(Sequence.WALK, Couple.with("G1", 0)); + put(Sequence.STAND, Couple.with("G2", 0)); + put(Sequence.STANCE, Couple.with("G2", 16)); + put(Sequence.GET_HIT, Couple.with("G2", 32)); + put(Sequence.DIE, Couple.with("G2", 48)); + put(Sequence.SLEEP, get(Sequence.DIE)); + put(Sequence.GET_UP, Couple.with("!G2", 48)); + put(Sequence.TWITCH, Couple.with("G2", 64)); + put(Sequence.ATTACK, Couple.with("G3", 0)); + put(Sequence.ATTACK_2, Couple.with("G3", 16)); + put(Sequence.ATTACK_3, Couple.with("G3", 32)); + }}; + + /** + * A helper method that parses the specified data array and generates a {@link IniMap} instance out of it. + * @param data a String array containing table values for a specific table entry. + * @return a {@code IniMap} instance with the value derived from the specified data array. + * Returns {@code null} if no data could be derived. + */ + public static IniMap processTableData(String[] data) + { + IniMap retVal = null; + if (data == null || data.length < 16) { + return retVal; + } + + String resref = SpriteTables.valueToString(data, SpriteTables.COLUMN_RESREF, ""); + if (resref.isEmpty()) { + return retVal; + } + int falseColor = SpriteTables.valueToInt(data, SpriteTables.COLUMN_CLOWN, 0); + + List lines = processTableDataGeneral(data, ANIMATION_TYPE); + lines.add("[monster_large16]"); + lines.add("false_color=" + falseColor); + lines.add("resref=" + resref); + + retVal = IniMap.from(lines); + + return retVal; + } + + public MonsterLarge16Decoder(int animationId, IniMap ini) throws Exception + { + super(ANIMATION_TYPE, animationId, ini); + } + + public MonsterLarge16Decoder(CreResource cre) throws Exception + { + super(ANIMATION_TYPE, cre); + } + + @Override + public List getAnimationFiles(boolean essential) + { + String resref = getAnimationResref(); + ArrayList retVal = new ArrayList() {{ + add(resref + "G1.BAM"); + add(resref + "G1E.BAM"); + add(resref + "G2.BAM"); + add(resref + "G2E.BAM"); + add(resref + "G3.BAM"); + add(resref + "G3E.BAM"); + }}; + return retVal; + } + + @Override + public boolean isSequenceAvailable(Sequence seq) + { + return (getSequenceDefinition(seq) != null); + } + + @Override + protected void init() throws Exception + { + // setting properties + initDefaults(getAnimationInfo()); + IniMapSection section = getSpecificIniSection(); + setFalseColor(section.getAsInteger(KEY_FALSE_COLOR.getName(), 0) != 0); + setDetectedByInfravision(section.getAsInteger(KEY_DETECTED_BY_INFRAVISION.getName(), 0) != 0); + } + + @Override + protected SeqDef getSequenceDefinition(Sequence seq) + { + SeqDef retVal = null; + String resref = getAnimationResref(); + // special case: directions west include NNE + Direction[] dir_w = new Direction[SeqDef.DIR_FULL_W.length + 1]; + System.arraycopy(SeqDef.DIR_FULL_W, 0, dir_w, 0, SeqDef.DIR_FULL_W.length); + dir_w[SeqDef.DIR_FULL_W.length] = Direction.NNE; + Direction[] dir_e = new Direction[SeqDef.DIR_FULL_E.length - 1]; + System.arraycopy(SeqDef.DIR_FULL_E, 1, dir_e, 0, dir_e.length); + Couple data = suffixMap.get(seq); + if (data != null) { + SegmentDef.Behavior behavior = SegmentDef.getBehaviorOf(data.getValue0()); + String suffix = SegmentDef.fixBehaviorSuffix(data.getValue0()); + ResourceEntry entry = ResourceFactory.getResourceEntry(resref + suffix + ".BAM"); + int cycle = data.getValue1().intValue(); + ResourceEntry entryE = ResourceFactory.getResourceEntry(resref + suffix + "E.BAM"); + int cycleE = cycle + dir_w.length; + if (SpriteUtils.bamCyclesExist(entry, cycle, dir_w.length) && + SpriteUtils.bamCyclesExist(entryE, cycleE, dir_e.length)) { + retVal = SeqDef.createSequence(seq, dir_w, false, entry, cycle, null, behavior); + SeqDef tmp = SeqDef.createSequence(seq, dir_e, false, entryE, cycleE, null, behavior); + retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); + } + } + + return retVal; + } +} diff --git a/src/org/infinity/resource/cre/decoder/MonsterLargeDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterLargeDecoder.java new file mode 100644 index 000000000..407c706ab --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/MonsterLargeDecoder.java @@ -0,0 +1,137 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.infinity.resource.ResourceFactory; +import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.DirDef; +import org.infinity.resource.cre.decoder.internal.SegmentDef; +import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.IniMap; +import org.infinity.util.IniMapSection; +import org.infinity.util.tuples.Couple; + +/** + * Creature animation decoder for processing type 9000 (monster_large) animations. + * Available ranges: [9000,9fff] + */ +public class MonsterLargeDecoder extends SpriteDecoder +{ + /** The animation type associated with this class definition. */ + public static final AnimationType ANIMATION_TYPE = AnimationType.MONSTER_LARGE; + + private static final HashMap> suffixMap = new HashMap>() {{ + put(Sequence.STAND, Couple.with("G1", 0)); + put(Sequence.STANCE, Couple.with("G1", 8)); + put(Sequence.WALK, Couple.with("G1", 16)); + put(Sequence.ATTACK, Couple.with("G2", 0)); + put(Sequence.ATTACK_2, Couple.with("G2", 8)); + put(Sequence.ATTACK_3, Couple.with("G3", 0)); + put(Sequence.GET_HIT, Couple.with("G3", 8)); + put(Sequence.DIE, Couple.with("G3", 16)); + put(Sequence.SLEEP, get(Sequence.DIE)); + put(Sequence.GET_UP, Couple.with("!G3", 16)); + put(Sequence.TWITCH, Couple.with("G3", 24)); + }}; + + /** + * A helper method that parses the specified data array and generates a {@link IniMap} instance out of it. + * @param data a String array containing table values for a specific table entry. + * @return a {@code IniMap} instance with the value derived from the specified data array. + * Returns {@code null} if no data could be derived. + */ + public static IniMap processTableData(String[] data) + { + IniMap retVal = null; + if (data == null || data.length < 16) { + return retVal; + } + + String resref = SpriteTables.valueToString(data, SpriteTables.COLUMN_RESREF, ""); + if (resref.isEmpty()) { + return retVal; + } + int falseColor = SpriteTables.valueToInt(data, SpriteTables.COLUMN_CLOWN, 0); + + List lines = processTableDataGeneral(data, ANIMATION_TYPE); + lines.add("[monster_large]"); + lines.add("false_color=" + falseColor); + lines.add("resref=" + resref); + + retVal = IniMap.from(lines); + + return retVal; + } + + public MonsterLargeDecoder(int animationId, IniMap ini) throws Exception + { + super(ANIMATION_TYPE, animationId, ini); + } + + public MonsterLargeDecoder(CreResource cre) throws Exception + { + super(ANIMATION_TYPE, cre); + } + + @Override + public List getAnimationFiles(boolean essential) + { + String resref = getAnimationResref(); + ArrayList retVal = new ArrayList() {{ + add(resref + "G1.BAM"); + add(resref + "G1E.BAM"); + add(resref + "G2.BAM"); + add(resref + "G2E.BAM"); + add(resref + "G3.BAM"); + add(resref + "G3E.BAM"); + }}; + return retVal; + } + + @Override + public boolean isSequenceAvailable(Sequence seq) + { + return (getSequenceDefinition(seq) != null); + } + + @Override + protected void init() throws Exception + { + // setting properties + initDefaults(getAnimationInfo()); + IniMapSection section = getSpecificIniSection(); + setFalseColor(section.getAsInteger(KEY_FALSE_COLOR.getName(), 0) != 0); + setDetectedByInfravision(section.getAsInteger(KEY_DETECTED_BY_INFRAVISION.getName(), 0) != 0); + } + + @Override + protected SeqDef getSequenceDefinition(Sequence seq) + { + SeqDef retVal = null; + String resref = getAnimationResref(); + Couple data = suffixMap.get(seq); + if (data != null) { + SegmentDef.Behavior behavior = SegmentDef.getBehaviorOf(data.getValue0()); + String suffix = SegmentDef.fixBehaviorSuffix(data.getValue0()); + ResourceEntry entry = ResourceFactory.getResourceEntry(resref + suffix + ".BAM"); + int cycle = data.getValue1().intValue(); + ResourceEntry entryE = ResourceFactory.getResourceEntry(resref + suffix + "E.BAM"); + int cycleE = cycle + SeqDef.DIR_REDUCED_W.length; + if (SpriteUtils.bamCyclesExist(entry, cycle, SeqDef.DIR_REDUCED_W.length) && + SpriteUtils.bamCyclesExist(entryE, cycleE, SeqDef.DIR_REDUCED_E.length)) { + retVal = SeqDef.createSequence(seq, SeqDef.DIR_REDUCED_W, false, entry, cycle, null, behavior); + SeqDef tmp = SeqDef.createSequence(seq, SeqDef.DIR_REDUCED_E, false, entryE, cycleE, null, behavior); + retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); + } + } + return retVal; + } +} diff --git a/src/org/infinity/resource/cre/decoder/MonsterLayeredDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterLayeredDecoder.java new file mode 100644 index 000000000..4337fc92b --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/MonsterLayeredDecoder.java @@ -0,0 +1,214 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.infinity.resource.ResourceFactory; +import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.DecoderAttribute; +import org.infinity.resource.cre.decoder.internal.DirDef; +import org.infinity.resource.cre.decoder.internal.SegmentDef; +import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.itm.ItmResource; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.IniMap; +import org.infinity.util.IniMapSection; +import org.infinity.util.tuples.Couple; + +/** + * Creature animation decoder for processing type 8000 (monster_layered) animations. + * Available ranges: [8000,8fff] + */ +public class MonsterLayeredDecoder extends SpriteDecoder +{ + /** The animation type associated with this class definition. */ + public static final AnimationType ANIMATION_TYPE = AnimationType.MONSTER_LAYERED; + + public static final DecoderAttribute KEY_RESREF_WEAPON1 = DecoderAttribute.with("resref_weapon1", DecoderAttribute.DataType.STRING); + public static final DecoderAttribute KEY_RESREF_WEAPON2 = DecoderAttribute.with("resref_weapon2", DecoderAttribute.DataType.STRING); + + private static final HashMap> suffixMap = new HashMap>() {{ + put(Sequence.WALK, Couple.with("G1", 0)); + put(Sequence.STANCE, Couple.with("G1", 8)); + put(Sequence.STAND, Couple.with("G1", 16)); + put(Sequence.GET_HIT, Couple.with("G1", 24)); + put(Sequence.DIE, Couple.with("G1", 32)); + put(Sequence.SLEEP, get(Sequence.DIE)); + put(Sequence.GET_UP, Couple.with("!G1", 32)); + put(Sequence.TWITCH, Couple.with("G1", 40)); + put(Sequence.ATTACK, Couple.with("G2", 0)); + put(Sequence.ATTACK_2, Couple.with("G2", 8)); + put(Sequence.ATTACK_2H, Couple.with("G2", 16)); + }}; + + /** + * A helper method that parses the specified data array and generates a {@link IniMap} instance out of it. + * @param data a String array containing table values for a specific table entry. + * @return a {@code IniMap} instance with the value derived from the specified data array. + * Returns {@code null} if no data could be derived. + */ + public static IniMap processTableData(String[] data) + { + IniMap retVal = null; + if (data == null || data.length < 16) { + return retVal; + } + + String resref = SpriteTables.valueToString(data, SpriteTables.COLUMN_RESREF, ""); + if (resref.isEmpty()) { + return retVal; + } + String resrefWeapon1 = SpriteTables.valueToString(data, SpriteTables.COLUMN_HEIGHT, ""); + String resrefWeapon2 = SpriteTables.valueToString(data, SpriteTables.COLUMN_HEIGHT_SHIELD, ""); + + List lines = processTableDataGeneral(data, ANIMATION_TYPE); + lines.add("[monster_layered]"); + lines.add("resref=" + resref); + lines.add("resref_weapon1=" + resrefWeapon1); + lines.add("resref_weapon2=" + resrefWeapon2); + + retVal = IniMap.from(lines); + + return retVal; + } + + public MonsterLayeredDecoder(int animationId, IniMap ini) throws Exception + { + super(ANIMATION_TYPE, animationId, ini); + } + + public MonsterLayeredDecoder(CreResource cre) throws Exception + { + super(ANIMATION_TYPE, cre); + } + + /** Returns the two-letter weapon animation prefix for 1-handed weapons. */ + public String getWeapon1Overlay() { return getAttribute(KEY_RESREF_WEAPON1); } + protected void setWeapon1Overlay(String s) { setAttribute(KEY_RESREF_WEAPON1, s); } + + /** Returns the two-letter weapon animation prefix for 2-handed weapons. */ + public String getWeapon2Overlay() { return getAttribute(KEY_RESREF_WEAPON2); } + protected void setWeapon2Overlay(String s) { setAttribute(KEY_RESREF_WEAPON2, s); } + + @Override + public List getAnimationFiles(boolean essential) + { + String resref = getAnimationResref(); + final String w1 = !getWeapon1Overlay().isEmpty() ? getWeapon1Overlay().substring(0, 1) : ""; + final String w2 = !getWeapon2Overlay().isEmpty() ? getWeapon2Overlay().substring(0, 1) : ""; + final String[] suffix = { "G1", "G1E", "G2", "G2E" }; + ArrayList retVal = new ArrayList() {{ + for (final String s : suffix) { + add(resref + s + ".BAM"); + } + if (!w1.isEmpty()) { + for (final String s : suffix) { + add(resref + w1 + s + ".BAM"); + } + } + if (!w2.isEmpty()) { + for (final String s : suffix) { + add(resref + w2 + s + ".BAM"); + } + } + }}; + return retVal; + } + + @Override + public boolean isSequenceAvailable(Sequence seq) + { + return (getSequenceDefinition(seq) != null); + } + + @Override + protected void init() throws Exception + { + // setting properties + initDefaults(getAnimationInfo()); + IniMapSection section = getSpecificIniSection(); + setFalseColor(true); + setDetectedByInfravision(true); + setWeapon1Overlay(section.getAsString(KEY_RESREF_WEAPON1.getName(), "")); + setWeapon2Overlay(section.getAsString(KEY_RESREF_WEAPON2.getName(), "")); + } + + @Override + protected SeqDef getSequenceDefinition(Sequence seq) + { + SeqDef retVal = null; + + Couple data = suffixMap.get(seq); + if (data == null) { + return retVal; + } + + switch (seq) { + case ATTACK: + case ATTACK_2: + if (SpriteUtils.isWeaponTwoHanded(SpriteUtils.getEquippedWeapon(getCreResource()), false)) { + return retVal; + } + break; + case ATTACK_2H: + if (!SpriteUtils.isWeaponTwoHanded(SpriteUtils.getEquippedWeapon(getCreResource()), false)) { + return retVal; + } + break; + default: + } + + ArrayList> creResList = new ArrayList<>(); + + // defining creature resref prefix + String resref = getAnimationResref(); + SegmentDef.Behavior behavior = SegmentDef.getBehaviorOf(data.getValue0()); + String suffix = SegmentDef.fixBehaviorSuffix(data.getValue0()); + creResList.add(Couple.with(resref + suffix, SegmentDef.SpriteType.AVATAR)); + + // defining weapon overlay for current creature + ItmResource itm = SpriteUtils.getEquippedWeapon(getCreResource()); + if (itm != null) { + String weapon = SpriteUtils.getItemAppearance(itm); + if (!weapon.isEmpty()) { + weapon = weapon.substring(0, 1); + } + weapon = weapon.trim(); + if (!getWeapon1Overlay().startsWith(weapon) && !getWeapon2Overlay().startsWith(weapon)) { + weapon = ""; + } + + if (!weapon.isEmpty()) { + creResList.add(Couple.with(resref + weapon + suffix, SegmentDef.SpriteType.WEAPON)); + } + } + + int cycle = data.getValue1().intValue(); + int cycleE = cycle + SeqDef.DIR_REDUCED_W.length; + + retVal = new SeqDef(seq); + for (Couple resEntry : creResList) { + ResourceEntry entry = ResourceFactory.getResourceEntry(resEntry.getValue0() + ".BAM"); + ResourceEntry entryE = ResourceFactory.getResourceEntry(resEntry.getValue0() + "E.BAM"); + if (SpriteUtils.bamCyclesExist(entry, cycle, SeqDef.DIR_REDUCED_W.length) && + SpriteUtils.bamCyclesExist(entryE, cycleE, SeqDef.DIR_REDUCED_E.length)) { + SeqDef tmp = SeqDef.createSequence(seq, SeqDef.DIR_REDUCED_W, false, entry, cycle, resEntry.getValue1(), behavior); + retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); + tmp = SeqDef.createSequence(seq, SeqDef.DIR_REDUCED_E, false, entryE, cycleE, resEntry.getValue1(), behavior); + retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); + } + } + + if (retVal.isEmpty()) { + retVal = null; + } + + return retVal; + } +} diff --git a/src/org/infinity/resource/cre/decoder/MonsterLayeredSpellDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterLayeredSpellDecoder.java new file mode 100644 index 000000000..9b20122a2 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/MonsterLayeredSpellDecoder.java @@ -0,0 +1,246 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.infinity.resource.ResourceFactory; +import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.DecoderAttribute; +import org.infinity.resource.cre.decoder.internal.DirDef; +import org.infinity.resource.cre.decoder.internal.SegmentDef; +import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.itm.ItmResource; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.IniMap; +import org.infinity.util.IniMapSection; +import org.infinity.util.tuples.Couple; + +/** + * Creature animation decoder for processing type 2000 (monster_layered_spell) animations. + * Note: This type can be incorrectly labeled as "monster_layered" in INI files + * Available ranges: [2000,2fff] + */ +public class MonsterLayeredSpellDecoder extends SpriteDecoder +{ + /** The animation type associated with this class definition. */ + public static final AnimationType ANIMATION_TYPE = AnimationType.MONSTER_LAYERED_SPELL; + + public static final DecoderAttribute KEY_DUAL_ATTACK = DecoderAttribute.with("dual_attack", DecoderAttribute.DataType.BOOLEAN); + public static final DecoderAttribute KEY_INVULNERABLE = DecoderAttribute.with("invulnerable", DecoderAttribute.DataType.BOOLEAN); + public static final DecoderAttribute KEY_RESREF_WEAPON1 = DecoderAttribute.with("resref_weapon1", DecoderAttribute.DataType.STRING); + public static final DecoderAttribute KEY_RESREF_WEAPON2 = DecoderAttribute.with("resref_weapon2", DecoderAttribute.DataType.STRING); + + private static final HashMap> suffixMap = new HashMap>() {{ + put(Sequence.WALK, Couple.with("G1", 0)); + put(Sequence.STANCE, Couple.with("G1", 8)); + put(Sequence.STAND, Couple.with("G1", 16)); + put(Sequence.GET_HIT, Couple.with("G1", 24)); + put(Sequence.DIE, Couple.with("G1", 32)); + put(Sequence.SLEEP, get(Sequence.DIE)); + put(Sequence.GET_UP, Couple.with("!G1", 32)); + put(Sequence.TWITCH, Couple.with("G1", 40)); + put(Sequence.ATTACK, Couple.with("G2", 0)); + put(Sequence.SPELL, Couple.with("G2", 8)); + put(Sequence.ATTACK_2H, Couple.with("G2", 16)); + }}; + + /** + * A helper method that parses the specified data array and generates a {@link IniMap} instance out of it. + * @param data a String array containing table values for a specific table entry. + * @return a {@code IniMap} instance with the value derived from the specified data array. + * Returns {@code null} if no data could be derived. + */ + public static IniMap processTableData(String[] data) + { + IniMap retVal = null; + if (data == null || data.length < 16) { + return retVal; + } + + String resref = SpriteTables.valueToString(data, SpriteTables.COLUMN_RESREF, ""); + if (resref.isEmpty()) { + return retVal; + } + int falseColor = SpriteTables.valueToInt(data, SpriteTables.COLUMN_CLOWN, 0); + int dualAttack = SpriteTables.valueToInt(data, SpriteTables.COLUMN_WEAPON, 0); + String resrefWeapon1 = SpriteTables.valueToString(data, SpriteTables.COLUMN_HEIGHT, ""); + String resrefWeapon2 = SpriteTables.valueToString(data, SpriteTables.COLUMN_HEIGHT_SHIELD, ""); + + List lines = processTableDataGeneral(data, ANIMATION_TYPE); + lines.add("[monster_layered_spell]"); + lines.add("dual_attack=" + dualAttack); + lines.add("false_color=" + falseColor); + lines.add("resref=" + resref); + lines.add("resref_weapon1=" + resrefWeapon1); + lines.add("resref_weapon2=" + resrefWeapon2); + + retVal = IniMap.from(lines); + + return retVal; + } + + public MonsterLayeredSpellDecoder(int animationId, IniMap ini) throws Exception + { + super(ANIMATION_TYPE, animationId, ini); + } + + public MonsterLayeredSpellDecoder(CreResource cre) + throws Exception + { + super(ANIMATION_TYPE, cre); + } + + /** ??? */ + public boolean isDualAttack() { return getAttribute(KEY_DUAL_ATTACK); } + protected void setDualAttack(boolean b) { setAttribute(KEY_DUAL_ATTACK, b); } + + /** Returns the two-letter weapon animation prefix for 1-handed weapons. */ + public String getWeapon1Overlay() { return getAttribute(KEY_RESREF_WEAPON1); } + protected void setWeapon1Overlay(String s) { setAttribute(KEY_RESREF_WEAPON1, s); } + + /** Returns the two-letter weapon animation prefix for 2-handed weapons. */ + public String getWeapon2Overlay() { return getAttribute(KEY_RESREF_WEAPON2); } + protected void setWeapon2Overlay(String s) { setAttribute(KEY_RESREF_WEAPON2, s); } + + @Override + public List getAnimationFiles(boolean essential) + { + String resref = getAnimationResref(); + final String w1 = !getWeapon1Overlay().isEmpty() ? getWeapon1Overlay().substring(0, 1) : ""; + final String w2 = !getWeapon2Overlay().isEmpty() ? getWeapon2Overlay().substring(0, 1) : ""; + final String[] suffix = { "G1", "G1E", "G2", "G2E" }; + ArrayList retVal = new ArrayList() {{ + for (final String s : suffix) { + add(resref + s + ".BAM"); + } + if (!w1.isEmpty()) { + for (final String s : suffix) { + add(resref + w1 + s + ".BAM"); + } + } + if (!w2.isEmpty()) { + for (final String s : suffix) { + add(resref + w2 + s + ".BAM"); + } + } + }}; + return retVal; + } + + @Override + public boolean isSequenceAvailable(Sequence seq) + { + return (getSequenceDefinition(seq) != null); + } + + @Override + protected void init() throws Exception + { + // setting properties + initDefaults(getAnimationInfo()); + + // Hardcoded defaults + int defFalseColor = 0; + int defDualAttack = 0; + String defWeapon1 = ""; + String defWeapon2 = ""; +// switch (getAnimationId() & 0xff00) { +// case 0x2000: // Sirine +// defFalseColor = 1; +// defWeapon2 = "BW"; +// break; +// case 0x2100: // Volo +// defWeapon1 = "MS"; +// break; +// case 0x2200: // Ogre Mage +// defFalseColor = 1; +// defDualAttack = 1; +// defWeapon1 = "S1"; +// break; +// case 0x2300: // Death Knight +// defDualAttack = 1; +// break; +// } + + IniMapSection section = getSpecificIniSection(); + if (section.getEntryCount() == 0) { + // EE: defined as "monster_layered" type + section = getAnimationInfo().getSection(AnimationType.MONSTER_LAYERED.getSectionName()); + } + + setDetectedByInfravision(true); + setFalseColor(section.getAsInteger(KEY_FALSE_COLOR.getName(), defFalseColor) != 0); + setDualAttack(section.getAsInteger(KEY_DUAL_ATTACK.getName(), defDualAttack) != 0); + setWeapon1Overlay(section.getAsString(KEY_RESREF_WEAPON1.getName(), defWeapon1)); + setWeapon2Overlay(section.getAsString(KEY_RESREF_WEAPON2.getName(), defWeapon2)); + } + + @Override + protected SeqDef getSequenceDefinition(Sequence seq) + { + SeqDef retVal = null; + + Couple data = suffixMap.get(seq); + if (data == null) { + return retVal; + } + + if (seq == Sequence.ATTACK_2H && !SpriteUtils.isWeaponTwoHanded(SpriteUtils.getEquippedWeapon(getCreResource()), false)) { + return retVal; + } + + ArrayList> creResList = new ArrayList<>(); + + // defining creature resref prefix + String resref = getAnimationResref(); + SegmentDef.Behavior behavior = SegmentDef.getBehaviorOf(data.getValue0()); + String suffix = SegmentDef.fixBehaviorSuffix(data.getValue0()); + creResList.add(Couple.with(resref + suffix, SegmentDef.SpriteType.AVATAR)); + + // defining weapon overlay for current creature + ItmResource itm = SpriteUtils.getEquippedWeapon(getCreResource()); + if (itm != null) { + String weapon = SpriteUtils.getItemAppearance(itm); + if (!weapon.isEmpty()) { + weapon = weapon.substring(0, 1); + } + weapon = weapon.trim(); + if (!getWeapon1Overlay().startsWith(weapon) && !getWeapon2Overlay().startsWith(weapon)) { + weapon = ""; + } + + if (!weapon.isEmpty()) { + creResList.add(Couple.with(resref + weapon + suffix, SegmentDef.SpriteType.WEAPON)); + } + } + + int cycle = data.getValue1().intValue(); + int cycleE = cycle + SeqDef.DIR_REDUCED_W.length; + + retVal = new SeqDef(seq); + for (Couple resEntry : creResList) { + ResourceEntry entry = ResourceFactory.getResourceEntry(resEntry.getValue0() + ".BAM"); + ResourceEntry entryE = ResourceFactory.getResourceEntry(resEntry.getValue0() + "E.BAM"); + if (SpriteUtils.bamCyclesExist(entry, cycle, SeqDef.DIR_REDUCED_W.length) && + SpriteUtils.bamCyclesExist(entryE, cycleE, SeqDef.DIR_REDUCED_E.length)) { + SeqDef tmp = SeqDef.createSequence(seq, SeqDef.DIR_REDUCED_W, false, entry, cycle, resEntry.getValue1(), behavior); + retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); + tmp = SeqDef.createSequence(seq, SeqDef.DIR_REDUCED_E, false, entryE, cycleE, resEntry.getValue1(), behavior); + retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); + } + } + + if (retVal.isEmpty()) { + retVal = null; + } + + return retVal; + } + +} diff --git a/src/org/infinity/resource/cre/decoder/MonsterMultiDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterMultiDecoder.java new file mode 100644 index 000000000..5a9e2602a --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/MonsterMultiDecoder.java @@ -0,0 +1,323 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; + +import org.infinity.resource.ResourceFactory; +import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.DecoderAttribute; +import org.infinity.resource.cre.decoder.internal.DirDef; +import org.infinity.resource.cre.decoder.internal.SegmentDef; +import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.IniMap; +import org.infinity.util.IniMapSection; +import org.infinity.util.Misc; +import org.infinity.util.tuples.Couple; + +/** + * Creature animation decoder for processing type 1000 (multi_monster) animations consisting of 9 quadrants. + * Note: This type can be incorrectly labeled as "multi_new" in INI files. + * Available ranges: [1200,12ff], [1400,1fff] + */ +public class MonsterMultiDecoder extends QuadrantsBaseDecoder +{ + /** The animation type associated with this class definition. */ + public static final AnimationType ANIMATION_TYPE = AnimationType.MONSTER_MULTI; + + public static final DecoderAttribute KEY_SPLIT_BAMS = DecoderAttribute.with("split_bams", DecoderAttribute.DataType.BOOLEAN); + public static final DecoderAttribute KEY_DOUBLE_BLIT = DecoderAttribute.with("double_blit", DecoderAttribute.DataType.BOOLEAN); + public static final DecoderAttribute KEY_PALETTE_1 = DecoderAttribute.with("palette1", DecoderAttribute.DataType.STRING); + public static final DecoderAttribute KEY_PALETTE_2 = DecoderAttribute.with("palette2", DecoderAttribute.DataType.STRING); + public static final DecoderAttribute KEY_PALETTE_3 = DecoderAttribute.with("palette3", DecoderAttribute.DataType.STRING); + public static final DecoderAttribute KEY_PALETTE_4 = DecoderAttribute.with("palette4", DecoderAttribute.DataType.STRING); + public static final DecoderAttribute KEY_PALETTE_5 = DecoderAttribute.with("palette5", DecoderAttribute.DataType.STRING); + + private static final HashMap> suffixMapUnsplit = new HashMap>() {{ + // Note: Replace 'underscore' in suffix by one-based quadrant index + // Note 2: 'exclamation mark' in suffix indicates reversed playback of frames (e.g. get up = reversed sleep); remove + put(Sequence.WALK, Couple.with("G1_", 0)); + put(Sequence.STANCE, Couple.with("G2_", 0)); + put(Sequence.SPELL, get(Sequence.STANCE)); + put(Sequence.STAND, Couple.with("G2_", 9)); // engine appears to use STANCE sequence instead +// put(Sequence.STAND, get(Sequence.STANCE)); + put(Sequence.ATTACK, Couple.with("G3_", 0)); +// put(Sequence.ATTACK_2, get(Sequence.ATTACK)); // apparently unused by the engine +// put(Sequence.ATTACK_3, get(Sequence.ATTACK)); // apparently unused by the engine + put(Sequence.GET_HIT, Couple.with("G4_", 0)); + put(Sequence.DIE, Couple.with("G4_", 9)); + put(Sequence.SLEEP, get(Sequence.DIE)); + put(Sequence.GET_UP, Couple.with("!G4_", 9)); + put(Sequence.TWITCH, Couple.with("G4_", 18)); + put(Sequence.CAST, Couple.with("G5_", 9)); + put(Sequence.SHOOT, get(Sequence.CAST)); + }}; + + private static final HashMap> suffixMapSplit = new HashMap>() {{ + // Note: Replace 'underscore' in suffix by one-based quadrant index + // Note 2: Replace 'plus' in suffix by zero-based direction index (0=south, 4=west, ...) + // Note 3: 'exclamation mark' in suffix indicates reversed playback of frames (e.g. get up = reversed sleep); remove + put(Sequence.WALK, Couple.with("1_0+", 0)); + put(Sequence.STANCE, Couple.with("2_0+", 0)); + put(Sequence.SPELL, get(Sequence.STANCE)); + put(Sequence.STAND, Couple.with("2_0+", 9)); // engine appears to use STANCE sequence instead +// put(Sequence.STAND, get(Sequence.STANCE)); + put(Sequence.ATTACK, Couple.with("3_0+", 0)); +// put(Sequence.ATTACK_2, get(Sequence.ATTACK)); // apparently unused by the engine +// put(Sequence.ATTACK_3, get(Sequence.ATTACK)); // apparently unused by the engine + put(Sequence.GET_HIT, Couple.with("4_0+", 0)); + put(Sequence.DIE, Couple.with("4_1+", 9)); + put(Sequence.SLEEP, get(Sequence.DIE)); + put(Sequence.GET_UP, Couple.with("!4_1+", 9)); + put(Sequence.TWITCH, Couple.with("4_2+", 18)); + put(Sequence.CAST, Couple.with("5_1+", 9)); + put(Sequence.SHOOT, get(Sequence.CAST)); + }}; + + /** + * A helper method that parses the specified data array and generates a {@link IniMap} instance out of it. + * @param data a String array containing table values for a specific table entry. + * @return a {@code IniMap} instance with the value derived from the specified data array. + * Returns {@code null} if no data could be derived. + */ + public static IniMap processTableData(String[] data) + { + IniMap retVal = null; + if (data == null || data.length < 16) { + return retVal; + } + + String resref = SpriteTables.valueToString(data, SpriteTables.COLUMN_RESREF, ""); + if (resref.isEmpty()) { + return retVal; + } + int splitBams = SpriteTables.valueToInt(data, SpriteTables.COLUMN_SPLIT, 0); + String paletteBase = SpriteTables.valueToString(data, SpriteTables.COLUMN_PALETTE, ""); + + List lines = processTableDataGeneral(data, ANIMATION_TYPE); + lines.add("[monster_multi]"); + lines.add("quadrants=9"); + lines.add("split_bams=" + splitBams); + lines.add("resref=" + resref); + if (!paletteBase.isEmpty()) { + lines.add("palette1=" + paletteBase + "1"); + lines.add("palette2=" + paletteBase + "2"); + lines.add("palette3=" + paletteBase + "3"); + lines.add("palette4=" + paletteBase + "4"); + lines.add("palette5=" + paletteBase + "5"); + } + + retVal = IniMap.from(lines); + + return retVal; + } + + public MonsterMultiDecoder(int animationId, IniMap ini) throws Exception + { + super(ANIMATION_TYPE, animationId, ini); + } + + public MonsterMultiDecoder(CreResource cre) throws Exception + { + super(ANIMATION_TYPE, cre); + } + + /** Returns the correct sequence map for the current settings. */ + private HashMap> getSuffixMap() + { + return isSplittedBams() ? suffixMapSplit : suffixMapUnsplit; + } + + /** Returns whether animations are spread over various subfiles. */ + public boolean isSplittedBams() { return getAttribute(KEY_SPLIT_BAMS); } + protected void setSplittedBams(boolean b) { setAttribute(KEY_SPLIT_BAMS, b); } + + /** unused */ + public boolean isDoubleBlit() { return getAttribute(KEY_DOUBLE_BLIT); } + protected void setDoubleBlit(boolean b) { setAttribute(KEY_DOUBLE_BLIT, b); } + + /** Returns the palette resref for the specified BAM prefix. Falls back to "new_palette" if needed. */ + public String getPalette(int idx) + { + String retVal = null; + switch (idx) { + case 1: + retVal = getAttribute(KEY_PALETTE_1); + break; + case 2: + retVal = getAttribute(KEY_PALETTE_2); + break; + case 3: + retVal = getAttribute(KEY_PALETTE_3); + break; + case 4: + retVal = getAttribute(KEY_PALETTE_4); + break; + case 5: + retVal = getAttribute(KEY_PALETTE_5); + break; + default: + return getNewPalette(); + } + + if (retVal == null || retVal.isEmpty()) { + String s = getNewPalette(); + if (!s.isEmpty()) { + retVal = s + idx; + } + } + + return retVal; + } + + protected void setPalette(int idx, String s) + { + switch (idx) { + case 1: + setAttribute(KEY_PALETTE_1, s); + break; + case 2: + setAttribute(KEY_PALETTE_2, s); + break; + case 3: + setAttribute(KEY_PALETTE_3, s); + break; + case 4: + setAttribute(KEY_PALETTE_4, s); + break; + case 5: + setAttribute(KEY_PALETTE_5, s); + break; + } + } + + @Override + public List getAnimationFiles(boolean essential) + { + String resref = getAnimationResref(); + final HashSet fileSet = new HashSet<>(); + for (final HashMap.Entry> entry : getSuffixMap().entrySet()) { + for (int i = 0; i < getQuadrants(); i++) { + for (int j = 0; j < SeqDef.DIR_FULL_W.length; j++) { + String suffix = entry.getValue().getValue0() + .replace("_", Integer.toString(i+1)) + .replace("+", Integer.toString(j)); + suffix = SegmentDef.fixBehaviorSuffix(suffix); + fileSet.add(resref + suffix + ".BAM"); + } + } + } + ArrayList retVal = new ArrayList() {{ + for (final String s : fileSet) { + add(s); + } + }}; + return retVal; + } + + @Override + protected void init() throws Exception + { + super.init(); + IniMapSection section = getSpecificIniSection(); + if (section.getEntryCount() == 0) { + // EE: defined as "multi_new" type + section = getAnimationInfo().getSection(AnimationType.MONSTER_MULTI_NEW.getSectionName()); + } + setSplittedBams(section.getAsInteger(KEY_SPLIT_BAMS.getName(), 0) != 0); + setQuadrants(section.getAsInteger(KEY_QUADRANTS.getName(), 9)); + setPalette(1, section.getAsString(KEY_PALETTE_1.getName())); + setPalette(2, section.getAsString(KEY_PALETTE_2.getName())); + setPalette(3, section.getAsString(KEY_PALETTE_3.getName())); + setPalette(4, section.getAsString(KEY_PALETTE_4.getName())); + setPalette(5, section.getAsString(KEY_PALETTE_5.getName())); + Misc.requireCondition(getQuadrants() < 10, "Too many quadrants defined: " + getQuadrants(), IllegalArgumentException.class); + } + + @Override + public boolean isSequenceAvailable(Sequence seq) + { + return (getSequenceDefinition(seq) != null); + } + + @Override + protected int[] getNewPaletteData(ResourceEntry bamRes) + { + int[] retVal = null; + + String bamResref = bamRes.getResourceRef(); + String resref = getAnimationResref(); + + int idx = Misc.toNumber(Character.toString(bamResref.charAt(resref.length())), -1); + retVal = SpriteUtils.loadReplacementPalette(getPalette(idx)); + + return retVal; + } + + @Override + protected SeqDef getSequenceDefinition(Sequence seq) + { + SeqDef retVal = null; + String resref = getAnimationResref(); + + List cycleList = new ArrayList<>(); + if (getSuffixMap().containsKey(seq)) { + String suffixBase = getSuffixMap().get(seq).getValue0(); + SegmentDef.Behavior behavior = SegmentDef.getBehaviorOf(suffixBase); + suffixBase = SegmentDef.fixBehaviorSuffix(suffixBase); + int cycleBase = getSuffixMap().get(seq).getValue1().intValue(); + retVal = new SeqDef(seq); + + // defining western directions + for (final Direction dir : SeqDef.DIR_FULL_W) { + cycleList.clear(); + for (int seg = 0; seg < getQuadrants(); seg++) { + String suffix = suffixBase.replace("_", Integer.toString(seg+1)).replace("+", Integer.toString(dir.getValue())); + ResourceEntry entry = ResourceFactory.getResourceEntry(resref + suffix + ".BAM"); + int cycleIdx = cycleBase + dir.getValue(); + if (!SpriteUtils.bamCyclesExist(entry, cycleIdx, 1)) { + return null; + } + cycleList.add(new SegmentDef(entry, cycleIdx, SegmentDef.SpriteType.AVATAR, behavior)); + } + SeqDef tmp = SeqDef.createSequence(seq, new Direction[] {dir}, false, cycleList); + retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); + } + + // calculating eastern directions + for (final Direction dir : SeqDef.DIR_FULL_E) { + cycleList.clear(); + int dir2 = SeqDef.DIR_FULL_W.length - (dir.getValue() - Direction.N.getValue() + 1); // direction to mirror + for (int seg = 0; seg < getQuadrants(); seg++) { + String suffix = suffixBase.replace("_", Integer.toString(seg+1)).replace("+", Integer.toString(dir2)); + ResourceEntry entry = ResourceFactory.getResourceEntry(resref + suffix + ".BAM"); + int cycleIdx = cycleBase + dir2; + cycleList.add(new SegmentDef(entry, cycleIdx, SegmentDef.SpriteType.AVATAR, behavior)); + } + SeqDef tmp = SeqDef.createSequence(seq, new Direction[] {dir}, true, cycleList); + retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); + } + +// Structure: +// xxxx1[1-9]0[0-8]: [0-8] +// xxxx1100: 0, xxxx1200: 0, xxxx1300: 0, xxxx1400: 0, xxxx1500: 0, xxxx1600: 0, xxxx1700: 0, xxxx1800: 0, xxxx1900: 0 +// xxxx1101: 1, xxxx1201: 1, xxxx1301: 1, xxxx1401: 1, xxxx1501: 1, xxxx1601: 1, xxxx1701: 1, xxxx1801: 1, xxxx1901: 1 +// xxxx1102: 2, xxxx1202: 2, xxxx1302: 2, xxxx1402: 2, xxxx1502: 2, xxxx1602: 2, xxxx1702: 2, xxxx1802: 2, xxxx1902: 2 +// xxxx1103: 3, xxxx1203: 3, xxxx1303: 3, xxxx1403: 3, xxxx1503: 3, xxxx1603: 3, xxxx1703: 3, xxxx1803: 3, xxxx1903: 3 +// xxxx1104: 4, xxxx1204: 4, xxxx1304: 4, xxxx1404: 4, xxxx1504: 4, xxxx1604: 4, xxxx1704: 4, xxxx1804: 4, xxxx1904: 4 +// xxxx1105: 5, xxxx1205: 5, xxxx1305: 5, xxxx1405: 5, xxxx1505: 5, xxxx1605: 5, xxxx1705: 5, xxxx1805: 5, xxxx1905: 5 +// xxxx1106: 6, xxxx1206: 6, xxxx1306: 6, xxxx1406: 6, xxxx1506: 6, xxxx1606: 6, xxxx1706: 6, xxxx1806: 6, xxxx1906: 6 +// xxxx1107: 7, xxxx1207: 7, xxxx1307: 7, xxxx1407: 7, xxxx1507: 7, xxxx1607: 7, xxxx1707: 7, xxxx1807: 7, xxxx1907: 7 +// xxxx1108: 8, xxxx1208: 8, xxxx1308: 8, xxxx1408: 8, xxxx1508: 8, xxxx1608: 8, xxxx1708: 8, xxxx1808: 8, xxxx1908: 8 + } + + return retVal; + } +} diff --git a/src/org/infinity/resource/cre/decoder/MonsterMultiNewDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterMultiNewDecoder.java new file mode 100644 index 000000000..f76522c0c --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/MonsterMultiNewDecoder.java @@ -0,0 +1,205 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.infinity.resource.ResourceFactory; +import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.DecoderAttribute; +import org.infinity.resource.cre.decoder.internal.DirDef; +import org.infinity.resource.cre.decoder.internal.SegmentDef; +import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.IniMap; +import org.infinity.util.IniMapSection; +import org.infinity.util.Misc; +import org.infinity.util.tuples.Couple; + +/** + * Creature animation decoder for processing type 1000 (multi_new) animations consisting of 4 segments. + * Available ranges: [1300,13ff] + */ +public class MonsterMultiNewDecoder extends QuadrantsBaseDecoder +{ + /** The animation type associated with this class definition. */ + public static final AnimationType ANIMATION_TYPE = AnimationType.MONSTER_MULTI_NEW; + + public static final DecoderAttribute KEY_CAN_LIE_DOWN = DecoderAttribute.with("can_lie_down", DecoderAttribute.DataType.BOOLEAN); + public static final DecoderAttribute KEY_PATH_SMOOTH = DecoderAttribute.with("path_smooth", DecoderAttribute.DataType.BOOLEAN); + public static final DecoderAttribute KEY_SPLIT_BAMS = DecoderAttribute.with("split_bams", DecoderAttribute.DataType.BOOLEAN); + + private static final HashMap> suffixMapSplit = new HashMap>() {{ + // Note: Replace underscore in suffix by one-based quadrant index + put(Sequence.WALK, Couple.with("G1_1", 0)); + put(Sequence.STANCE, Couple.with("G1_", 9)); + put(Sequence.STAND, Couple.with("G1_2", 18)); + put(Sequence.GET_HIT, Couple.with("G1_3", 27)); + put(Sequence.DIE, Couple.with("G1_4", 36)); + put(Sequence.SLEEP, get(Sequence.DIE)); + put(Sequence.GET_UP, Couple.with("!G1_4", 36)); + put(Sequence.TWITCH, Couple.with("G1_5", 45)); + put(Sequence.ATTACK, Couple.with("G2_", 0)); + put(Sequence.ATTACK_2, Couple.with("G2_1", 9)); + put(Sequence.ATTACK_3, Couple.with("G2_2", 18)); + put(Sequence.ATTACK_4, Couple.with("G2_3", 27)); + put(Sequence.ATTACK_5, Couple.with("G2_4", 36)); + put(Sequence.SPELL, Couple.with("G2_5", 45)); + put(Sequence.CAST, Couple.with("G2_6", 54)); + }}; + private static final HashMap> suffixMapUnsplit = new HashMap>() {{ + // Note: Replace underscore in suffix by one-based quadrant index + put(Sequence.WALK, Couple.with("G1_", 0)); + put(Sequence.STANCE, Couple.with("G1_", 9)); + put(Sequence.STAND, Couple.with("G1_", 18)); + put(Sequence.GET_HIT, Couple.with("G1_", 27)); + put(Sequence.DIE, Couple.with("G1_", 36)); + put(Sequence.SLEEP, get(Sequence.DIE)); + put(Sequence.GET_UP, Couple.with("!G1_", 36)); + put(Sequence.TWITCH, Couple.with("G1_", 45)); + put(Sequence.ATTACK, Couple.with("G2_", 0)); + put(Sequence.ATTACK_2, Couple.with("G2_", 9)); + put(Sequence.ATTACK_3, Couple.with("G2_", 18)); + put(Sequence.ATTACK_4, Couple.with("G2_", 27)); + put(Sequence.ATTACK_5, Couple.with("G2_", 36)); + put(Sequence.SPELL, Couple.with("G2_", 45)); + put(Sequence.CAST, Couple.with("G2_", 54)); + }}; + + /** + * A helper method that parses the specified data array and generates a {@link IniMap} instance out of it. + * @param data a String array containing table values for a specific table entry. + * @return a {@code IniMap} instance with the value derived from the specified data array. + * Returns {@code null} if no data could be derived. + */ + public static IniMap processTableData(String[] data) + { + IniMap retVal = null; + if (data == null || data.length < 16) { + return retVal; + } + + String resref = SpriteTables.valueToString(data, SpriteTables.COLUMN_RESREF, ""); + if (resref.isEmpty()) { + return retVal; + } + int falseColor = SpriteTables.valueToInt(data, SpriteTables.COLUMN_CLOWN, 0); + int splitBams = SpriteTables.valueToInt(data, SpriteTables.COLUMN_SPLIT, 0); + int translucent = SpriteTables.valueToInt(data, SpriteTables.COLUMN_TRANSLUCENT, 0); + + List lines = processTableDataGeneral(data, ANIMATION_TYPE); + lines.add("[multi_new]"); + lines.add("false_color=" + falseColor); + lines.add("quadrants=4"); + lines.add("split_bams=" + splitBams); + lines.add("translucent=" + translucent); + lines.add("resref=" + resref); + + retVal = IniMap.from(lines); + + return retVal; + } + + public MonsterMultiNewDecoder(int animationId, IniMap ini) throws Exception + { + super(ANIMATION_TYPE, animationId, ini); + } + + public MonsterMultiNewDecoder(CreResource cre) throws Exception + { + super(ANIMATION_TYPE, cre); + } + + /** Returns the correct sequence map for the current settings. */ + private HashMap> getSuffixMap() + { + return isSplittedBams() ? suffixMapSplit : suffixMapUnsplit; + } + + /** Returns whether the creature falls down when dead/unconscious. */ + public boolean canLieDown() { return getAttribute(KEY_CAN_LIE_DOWN); } + protected void setCanLieDown(boolean b) { setAttribute(KEY_CAN_LIE_DOWN, b); } + + /** ??? */ + public boolean isSmoothPath() { return getAttribute(KEY_PATH_SMOOTH); } + protected void setSmoothPath(boolean b) { setAttribute(KEY_PATH_SMOOTH, b); } + + /** Returns whether animations are spread over various subfiles. */ + public boolean isSplittedBams() { return getAttribute(KEY_SPLIT_BAMS); } + protected void setSplittedBams(boolean b) { setAttribute(KEY_SPLIT_BAMS, b); } + + @Override + public List getAnimationFiles(boolean essential) + { + String resref = getAnimationResref(); + ArrayList retVal = new ArrayList() {{ + for (final HashMap.Entry> entry : getSuffixMap().entrySet()) { + String suffixBase = SegmentDef.fixBehaviorSuffix(entry.getValue().getValue0()); + for (int i = 0; i < getQuadrants(); i++) { + String suffix = suffixBase.replace("_", Integer.toString(i+1)); + add(resref + suffix + ".BAM"); + } + } + }}; + return retVal; + } + + @Override + protected void init() throws Exception + { + super.init(); + IniMapSection section = getSpecificIniSection(); + setCanLieDown(section.getAsInteger(KEY_CAN_LIE_DOWN.getName(), 0) != 0); + setDetectedByInfravision(section.getAsInteger(KEY_DETECTED_BY_INFRAVISION.getName(), 0) != 0); + setFalseColor(section.getAsInteger(KEY_FALSE_COLOR.getName(), 0) != 0); + setSmoothPath(section.getAsInteger(KEY_PATH_SMOOTH.getName(), 0) != 0); + setSplittedBams(section.getAsInteger(KEY_SPLIT_BAMS.getName(), 0) != 0); + setTranslucent(section.getAsInteger(KEY_TRANSLUCENT.getName(), 0) != 0); + setQuadrants(section.getAsInteger(KEY_QUADRANTS.getName(), 4)); + Misc.requireCondition(getQuadrants() > 0 && getQuadrants() < 10, + "Invalid number of quadrants: " + getQuadrants(), IllegalArgumentException.class); + } + + @Override + public boolean isSequenceAvailable(Sequence seq) + { + return (getSequenceDefinition(seq) != null); + } + + @Override + protected SeqDef getSequenceDefinition(Sequence seq) + { + SeqDef retVal = null; + String resref = getAnimationResref(); + + List cycleList = new ArrayList<>(); + List cycleListE = new ArrayList<>(); + boolean valid = true; + if (getSuffixMap().containsKey(seq)) { + String suffixBase = getSuffixMap().get(seq).getValue0(); + SegmentDef.Behavior behavior = SegmentDef.getBehaviorOf(suffixBase); + suffixBase = SegmentDef.fixBehaviorSuffix(suffixBase); + int cycleOfs = getSuffixMap().get(seq).getValue1().intValue(); + for (int i = 0; valid && i < getQuadrants(); i++) { + String suffix = suffixBase.replace("_", Integer.toString(i+1)); + ResourceEntry entry = ResourceFactory.getResourceEntry(resref + suffix + ".BAM"); + cycleList.add(new SegmentDef(entry, cycleOfs, null, behavior)); + cycleListE.add(new SegmentDef(entry, cycleOfs + 1, null, behavior)); + valid &= SpriteUtils.bamCyclesExist(entry, cycleOfs, SeqDef.DIR_FULL_W.length); + } + } + + if (!cycleList.isEmpty() && valid) { + retVal = SeqDef.createSequence(seq, SeqDef.DIR_FULL_W, false, cycleList); + SeqDef tmp = SeqDef.createSequence(seq, SeqDef.DIR_FULL_E, true, cycleListE); + retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); + } + + return retVal; + } +} diff --git a/src/org/infinity/resource/cre/decoder/MonsterOldDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterOldDecoder.java new file mode 100644 index 000000000..7efea5cbb --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/MonsterOldDecoder.java @@ -0,0 +1,150 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.infinity.resource.ResourceFactory; +import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.DirDef; +import org.infinity.resource.cre.decoder.internal.SegmentDef; +import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.IniMap; +import org.infinity.util.IniMapSection; +import org.infinity.util.tuples.Couple; + +/** + * Creature animation decoder for processing type 7000 (monster_old) animations. + * Available ranges: (using notation slot/range where slot can be a formula with range definitions as [x,y]) + * (0x7000 | ([0x00,0x1f] << 4))/0x1 + * (0x7000 | ([0x20,0x2f] << 4))/0x3 + * (0x7000 | ([0x40,0x4f] << 4))/0x2 + * (0x7000 | ([0x50,0x5f] << 4))/0x1 + * (0x7000 | ([0x60,0x6f] << 4))/0xf + * (0x7000 | ([0x70,0x7f] << 4))/0x2 + * (0x7000 | ([0x80,0x8f] << 4))/0xf + * (0x7000 | ([0x90,0xaf] << 4))/0x4 + * (0x7000 | ([0xb0,0xbf] << 4))/0x6 + * (0x7000 | ([0xc0,0xcf] << 4))/0x1 + * (0x7000 | ([0xd0,0xdf] << 4))/0xf + * (0x7000 | ([0xe0,0xef] << 4))/0x1 + */ +public class MonsterOldDecoder extends SpriteDecoder +{ + /** The animation type associated with this class definition. */ + public static final AnimationType ANIMATION_TYPE = AnimationType.MONSTER_OLD; + + private static final HashMap> suffixMap = new HashMap>() {{ + put(Sequence.WALK, Couple.with("G1", 0)); + put(Sequence.STANCE, Couple.with("G1", 8)); + put(Sequence.STAND, Couple.with("G1", 16)); + put(Sequence.GET_HIT, Couple.with("G1", 24)); + put(Sequence.DIE, Couple.with("G1", 32)); + put(Sequence.SLEEP, get(Sequence.DIE)); + put(Sequence.GET_UP, Couple.with("!G1", 32)); + put(Sequence.TWITCH, Couple.with("G1", 40)); + put(Sequence.ATTACK, Couple.with("G2", 0)); + put(Sequence.ATTACK_2, Couple.with("G2", 8)); + put(Sequence.ATTACK_3, Couple.with("G2", 16)); + }}; + + /** + * A helper method that parses the specified data array and generates a {@link IniMap} instance out of it. + * @param data a String array containing table values for a specific table entry. + * @return a {@code IniMap} instance with the value derived from the specified data array. + * Returns {@code null} if no data could be derived. + */ + public static IniMap processTableData(String[] data) + { + IniMap retVal = null; + if (data == null || data.length < 16) { + return retVal; + } + + String resref = SpriteTables.valueToString(data, SpriteTables.COLUMN_RESREF, ""); + if (resref.isEmpty()) { + return retVal; + } + int falseColor = SpriteTables.valueToInt(data, SpriteTables.COLUMN_CLOWN, 0); + int translucent = SpriteTables.valueToInt(data, SpriteTables.COLUMN_TRANSLUCENT, 0); + + List lines = processTableDataGeneral(data, ANIMATION_TYPE); + lines.add("[monster_old]"); + lines.add("false_color=" + falseColor); + lines.add("translucent=" + translucent); + lines.add("resref=" + resref); + + retVal = IniMap.from(lines); + + return retVal; + } + + public MonsterOldDecoder(int animationId, IniMap ini) throws Exception + { + super(ANIMATION_TYPE, animationId, ini); + } + + public MonsterOldDecoder(CreResource cre) throws Exception + { + super(ANIMATION_TYPE, cre); + } + + @Override + public List getAnimationFiles(boolean essential) + { + String resref = getAnimationResref(); + ArrayList retVal = new ArrayList() {{ + add(resref + "G1.BAM"); + add(resref + "G1E.BAM"); + add(resref + "G2.BAM"); + add(resref + "G2E.BAM"); + }}; + return retVal; + } + + @Override + public boolean isSequenceAvailable(Sequence seq) + { + return (getSequenceDefinition(seq) != null); + } + + @Override + protected void init() throws Exception + { + // setting properties + initDefaults(getAnimationInfo()); + IniMapSection section = getSpecificIniSection(); + setFalseColor(section.getAsInteger(KEY_FALSE_COLOR.getName(), 0) != 0); + setDetectedByInfravision(section.getAsInteger(KEY_DETECTED_BY_INFRAVISION.getName(), 0) != 0); + setTranslucent(section.getAsInteger(KEY_TRANSLUCENT.getName(), 0) != 0); + } + + @Override + protected SeqDef getSequenceDefinition(Sequence seq) + { + SeqDef retVal = null; + String resref = getAnimationResref(); + Couple data = suffixMap.get(seq); + if (data != null) { + SegmentDef.Behavior behavior = SegmentDef.getBehaviorOf(data.getValue0()); + String suffix = SegmentDef.fixBehaviorSuffix(data.getValue0()); + ResourceEntry entry = ResourceFactory.getResourceEntry(resref + suffix + ".BAM"); + int cycle = data.getValue1().intValue(); + ResourceEntry entryE = ResourceFactory.getResourceEntry(resref + suffix + "E.BAM"); + int cycleE = cycle + SeqDef.DIR_REDUCED_W.length; + if (SpriteUtils.bamCyclesExist(entry, cycle, SeqDef.DIR_REDUCED_W.length) && + SpriteUtils.bamCyclesExist(entryE, cycleE, SeqDef.DIR_REDUCED_E.length)) { + retVal = SeqDef.createSequence(seq, SeqDef.DIR_REDUCED_W, false, entry, cycle, null, behavior); + SeqDef tmp = SeqDef.createSequence(seq, SeqDef.DIR_REDUCED_E, false, entryE, cycleE, null, behavior); + retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); + } + } + return retVal; + } +} diff --git a/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java new file mode 100644 index 000000000..51f9ce967 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java @@ -0,0 +1,503 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.TreeMap; + +import org.infinity.datatype.IsNumeric; +import org.infinity.resource.Profile; +import org.infinity.resource.ResourceFactory; +import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.DecoderAttribute; +import org.infinity.resource.cre.decoder.internal.DirDef; +import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.graphics.BamV1Decoder.BamV1Control; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.IniMap; +import org.infinity.util.IniMapSection; +import org.infinity.util.Misc; + +/** + * Creature animation decoder for processing type F000 (monster_planescape) animations. + * Available ranges: [f000,ffff] + */ +public class MonsterPlanescapeDecoder extends SpriteDecoder +{ + /** The animation type associated with this class definition. */ + public static final AnimationType ANIMATION_TYPE = AnimationType.MONSTER_PLANESCAPE; + + public static final DecoderAttribute KEY_BESTIARY = DecoderAttribute.with("bestiary", DecoderAttribute.DataType.INT); + public static final DecoderAttribute KEY_CLOWN = DecoderAttribute.with("clown", DecoderAttribute.DataType.BOOLEAN); + public static final DecoderAttribute KEY_ARMOR = DecoderAttribute.with("armor", DecoderAttribute.DataType.INT); + public static final DecoderAttribute KEY_WALKSCALE = DecoderAttribute.with("walkscale", DecoderAttribute.DataType.DECIMAL); + public static final DecoderAttribute KEY_RUNSCALE = DecoderAttribute.with("runscale", DecoderAttribute.DataType.DECIMAL); + public static final DecoderAttribute KEY_COLOR_LOCATION = DecoderAttribute.with("color", DecoderAttribute.DataType.USERDEFINED, new int[0]); + public static final DecoderAttribute KEY_SHADOW = DecoderAttribute.with("shadow", DecoderAttribute.DataType.STRING); + public static final DecoderAttribute KEY_RESREF2 = DecoderAttribute.with("resref2", DecoderAttribute.DataType.STRING); + + // available animation slot names + private static final HashMap Slots = new HashMap() {{ + put(Sequence.PST_ATTACK1, "attack1"); + put(Sequence.PST_ATTACK2, "attack2"); + put(Sequence.PST_ATTACK3, "attack3"); + put(Sequence.PST_GET_HIT, "gethit"); + put(Sequence.PST_RUN, "run"); + put(Sequence.PST_WALK, "walk"); + put(Sequence.PST_SPELL1, "spell1"); + put(Sequence.PST_SPELL2, "spell2"); + put(Sequence.PST_SPELL3, "spell3"); + put(Sequence.PST_GET_UP, "getup"); + put(Sequence.PST_DIE_FORWARD, "dieforward"); + put(Sequence.PST_DIE_BACKWARD, "diebackward"); + put(Sequence.PST_DIE_COLLAPSE, "diecollapse"); + put(Sequence.PST_TALK1, "talk1"); + put(Sequence.PST_TALK2, "talk2"); + put(Sequence.PST_TALK3, "talk3"); + put(Sequence.PST_STAND_FIDGET1, "standfidget1"); + put(Sequence.PST_STAND_FIDGET2, "standfidget2"); + put(Sequence.PST_STANCE_FIDGET1, "stancefidget1"); + put(Sequence.PST_STANCE_FIDGET2, "stancefidget2"); + put(Sequence.PST_STAND, "stand"); + put(Sequence.PST_STANCE, "stance"); + put(Sequence.PST_STANCE_TO_STAND, "stance2stand"); + put(Sequence.PST_STAND_TO_STANCE, "stand2stance"); + put(Sequence.PST_MISC1, "misc1"); + put(Sequence.PST_MISC2, "misc2"); + put(Sequence.PST_MISC3, "misc3"); + put(Sequence.PST_MISC4, "misc4"); + put(Sequence.PST_MISC5, "misc5"); + put(Sequence.PST_MISC6, "misc6"); + put(Sequence.PST_MISC7, "misc7"); + put(Sequence.PST_MISC8, "misc8"); + put(Sequence.PST_MISC9, "misc9"); + put(Sequence.PST_MISC10, "misc10"); + put(Sequence.PST_MISC11, "misc11"); + put(Sequence.PST_MISC12, "misc12"); + put(Sequence.PST_MISC13, "misc13"); + put(Sequence.PST_MISC14, "misc14"); + put(Sequence.PST_MISC15, "misc15"); + put(Sequence.PST_MISC16, "misc16"); + put(Sequence.PST_MISC17, "misc17"); + put(Sequence.PST_MISC18, "misc18"); + put(Sequence.PST_MISC19, "misc19"); + put(Sequence.PST_MISC20, "misc20"); + }}; + + // action prefixes used to determine BAM resref for animation sequences + private static final HashMap ActionPrefixes = new HashMap() {{ + put(Sequence.PST_ATTACK1, "at1"); + put(Sequence.PST_ATTACK2, "at2"); + put(Sequence.PST_ATTACK3, "at2"); + put(Sequence.PST_GET_HIT, "hit"); + put(Sequence.PST_RUN, "run"); + put(Sequence.PST_WALK, "wlk"); + put(Sequence.PST_SPELL1, "sp1"); + put(Sequence.PST_SPELL2, "sp2"); + put(Sequence.PST_SPELL3, "sp3"); + put(Sequence.PST_GET_UP, "gup"); + put(Sequence.PST_DIE_FORWARD, "dff"); + put(Sequence.PST_DIE_BACKWARD, "dfb"); + put(Sequence.PST_DIE_COLLAPSE, "dcl"); + put(Sequence.PST_TALK1, "tk1"); + put(Sequence.PST_TALK2, "tk2"); + put(Sequence.PST_TALK3, "tk3"); + put(Sequence.PST_STAND_FIDGET1, "sf1"); + put(Sequence.PST_STAND_FIDGET2, "sf2"); + put(Sequence.PST_STANCE_FIDGET1, "cf1"); + put(Sequence.PST_STANCE_FIDGET2, "cf2"); + put(Sequence.PST_STAND, "std"); + put(Sequence.PST_STANCE, "stc"); + put(Sequence.PST_STANCE_TO_STAND, "c2s"); + put(Sequence.PST_STAND_TO_STANCE, "s2c"); + put(Sequence.PST_MISC1, "ms1"); + put(Sequence.PST_MISC2, "ms2"); + put(Sequence.PST_MISC3, "ms3"); + put(Sequence.PST_MISC4, "ms4"); + put(Sequence.PST_MISC5, "ms5"); + put(Sequence.PST_MISC6, "ms6"); + put(Sequence.PST_MISC7, "ms7"); + put(Sequence.PST_MISC8, "ms8"); + put(Sequence.PST_MISC9, "ms9"); + // guessed action prefixes + put(Sequence.PST_MISC10, "msa"); + put(Sequence.PST_MISC11, "msb"); + put(Sequence.PST_MISC12, "msc"); + put(Sequence.PST_MISC13, "msd"); + put(Sequence.PST_MISC14, "mse"); + put(Sequence.PST_MISC15, "msf"); + put(Sequence.PST_MISC16, "msg"); + put(Sequence.PST_MISC17, "msh"); + put(Sequence.PST_MISC18, "msi"); + put(Sequence.PST_MISC19, "msj"); + put(Sequence.PST_MISC20, "msk"); + }}; + + /** + * Returns the action command associated with the specified action sequence. + * @param seq the action sequence. + * @return the action command, {@code null} otherwise. + */ + public static String getActionCommand(Sequence seq) + { + return Slots.get(seq); + } + + /** + * Returns the action prefix associated with the command of the associated action sequence + * according to the default naming scheme. + * @param seq the action sequence. + * @return the action prefix, {@code null} otherwise. + */ + public static String getActionPrefix(Sequence seq) + { + return ActionPrefixes.get(seq); + } + + /** + * A helper method that parses the specified data array and generates a {@link IniMap} instance out of it. + * @param data a String array containing table values for a specific table entry. + * @return a {@code IniMap} instance with the value derived from the specified data array. + * Returns {@code null} if no data could be derived. + */ + public static IniMap processTableData(String[] data) + { + IniMap retVal = null; + if (data == null || data.length < 9) { + return retVal; + } + + int id = SpriteTables.valueToInt(data, SpriteTables.COLUMN_ID, -1); + if (id < 0) { + return retVal; + } + boolean isSpecial = (id >= 0x1000); + + String s = SpriteTables.valueToString(data, SpriteTables.COLUMN_PST_RESREF, ""); + if (s.isEmpty()) { + return retVal; + } + String prefix = isSpecial ? "" : s.substring(0, 1); + String resref = isSpecial ? s : s.substring(1, s.length() - 1); + String suffix = isSpecial ? "" : s.substring(s.length() - 1, s.length()); + TreeMap actions = new TreeMap<>(); + if (isSpecial) { + actions.put(Slots.get(Sequence.PST_STAND), resref); + } else { + for (final Sequence seq : Slots.keySet()) { + String action = Slots.get(seq); + String actionPrefix = ActionPrefixes.get(seq); + if (action != null && actionPrefix != null) { + String bamRes = prefix + actionPrefix + resref; + if (ResourceFactory.resourceExists(bamRes + suffix + ".BAM")) { + actions.put(action, bamRes); + } + } + } + if (actions.isEmpty()) { + if (ResourceFactory.resourceExists(prefix + resref + suffix + ".BAM")) { + actions.put(Slots.get(Sequence.PST_STAND), prefix + resref); + } + } + } + int clown = SpriteTables.valueToInt(data, SpriteTables.COLUMN_PST_CLOWN, 0); + int bestiary = SpriteTables.valueToInt(data, SpriteTables.COLUMN_PST_BESTIARY, 0); + int armor = SpriteTables.valueToInt(data, SpriteTables.COLUMN_PST_ARMOR, 0); + + List lines = processTableDataGeneralPst(data); + lines.add("[monster_planescape]"); + for (final String action : actions.keySet()) { + lines.add(action + "=" + actions.get(action)); + } + if (clown != 0) { + lines.add("clown=" + clown); + } + if (bestiary != 0) { + lines.add("bestiary=" + bestiary); + } + if (armor != 0) { + lines.add("armor=" + armor); + } + + retVal = IniMap.from(lines); + + return retVal; + } + + public MonsterPlanescapeDecoder(int animationId, IniMap ini) throws Exception + { + super(ANIMATION_TYPE, animationId, ini); + } + + public MonsterPlanescapeDecoder(CreResource cre) throws Exception + { + super(ANIMATION_TYPE, cre); + } + + /** Returns the bestiary entry index. */ + public int getBestiaryIndex() { return getAttribute(KEY_BESTIARY); } + protected void setBestiaryIndex(int v) { setAttribute(KEY_BESTIARY, v); } + + /** ??? */ + public int getArmor() { return getAttribute(KEY_ARMOR); } + protected void setArmor(int v) { setAttribute(KEY_ARMOR, v); } + + /** Returns the walking speed of the creature animation. */ + public double getWalkScale() { return getAttribute(KEY_WALKSCALE); } + protected void setWalkScale(double d) { setAttribute(KEY_WALKSCALE, d); } + + /** Returns the running speed of the creature animation. */ + public double getRunScale() { return getAttribute(KEY_RUNSCALE); } + protected void setRunScale(double d) { setAttribute(KEY_RUNSCALE, d); } + + @Override + protected int getColorOffset(int locationIndex) + { + int retVal = -1; + int numLocations = getColorLocationCount(); + if (locationIndex >= 0 && locationIndex < numLocations) { + retVal = 256 - (numLocations - locationIndex) * 32; + } + return retVal; + } + + /** Returns the number of available false color ranges. */ + public int getColorLocationCount() + { + int[] color = getAttribute(KEY_COLOR_LOCATION); + return color.length; + } + + /** Returns the specified color location. */ + public int getColorLocation(int index) + { + int[] color = getAttribute(KEY_COLOR_LOCATION); + if (index < 0 || index > color.length) { + throw new IndexOutOfBoundsException(); + } + return color[index]; + } + + /** Returns a list of all valid color locations. */ + private int[] setColorLocations(IniMapSection section) + { + int[] retVal = null; + if (Profile.getGame() == Profile.Game.PST) { + retVal = setColorLocationsPST(); + } else { + retVal = setColorLocationsPSTEE(section); + } + return retVal; + } + + /** Returns a list of all valid color locations. (PSTEE) */ + private int[] setColorLocationsPSTEE(IniMapSection section) + { + final HashSet usedColors = new HashSet<>(); + int[] retVal = new int[7]; + int num = 0; + for (int i = 0; i < retVal.length; i++) { + int v = section.getAsInteger(KEY_COLOR_LOCATION.getName() + (i + 1), 0); + if (v >= 128 && v < 240 && !usedColors.contains(v & 0xf0)) { + usedColors.add(v & 0xf0); + retVal[num] = v; + num++; + } + } + if (num < retVal.length) { + retVal = Arrays.copyOf(retVal, num); + } + return retVal; + } + + /** Returns a list of all valid color locations. (original PST) */ + private int[] setColorLocationsPST() + { + final HashSet usedColors = new HashSet<>(); + CreResource cre = getCreResource(); + int[] retVal = new int[4]; + int num = 0; + for (int i = 0; i < retVal.length; i++) { + int l = ((IsNumeric)cre.getAttribute(String.format(CreResource.CRE_COLOR_PLACEMENT_FMT, i + 1))).getValue(); + int c = ((IsNumeric)cre.getAttribute(String.format(CreResource.CRE_COLOR_FMT, i + 1))).getValue(); + if (l > 0 && c > 0 && !usedColors.contains(l & 0xf0)) { + usedColors.add(l & 0xf0); + retVal[num] = l; + num++; + } + } + if (num < retVal.length) { + retVal = Arrays.copyOf(retVal, num); + } + return retVal; + } + + @Override + public List getAnimationFiles(boolean essential) + { + ArrayList retVal = new ArrayList<>(); + IniMapSection section = getAnimationInfo().getSection(getAnimationSectionName()); + for (final HashMap.Entry e : Slots.entrySet()) { + String resref = section.getAsString(e.getValue(), ""); + if (!resref.isEmpty()) { + resref = resref.toUpperCase(Locale.ENGLISH); + if (ResourceFactory.resourceExists(resref + "B.BAM")) { + retVal.add(resref + "B.BAM"); + } else if (ResourceFactory.resourceExists(resref + ".BAM")) { + retVal.add(resref + ".BAM"); + } + } + } + return retVal; + } + + @Override + public boolean isSequenceAvailable(Sequence seq) + { + return (getSequenceDefinition(seq) != null); + } + + @Override + protected void initDefaults(IniMap ini) throws Exception + { + IniMapSection section = getGeneralIniSection(Objects.requireNonNull(ini, "INI object cannot be null")); + + Objects.requireNonNull(section.getAsString(KEY_ANIMATION_TYPE.getName()), "animation_type required"); + Misc.requireCondition(getAnimationType().contains(getAnimationId()), + String.format("Animation slot (%04X) is not compatible with animation type (%s)", + getAnimationId(), getAnimationType().toString())); + + setMoveScale(section.getAsDouble(KEY_MOVE_SCALE.getName(), 0.0)); + setEllipse(section.getAsInteger(KEY_ELLIPSE.getName(), 16)); + setPersonalSpace(section.getAsInteger(KEY_PERSONAL_SPACE.getName(), 3)); + } + + @Override + protected void init() throws Exception + { + // setting properties + initDefaults(getAnimationInfo()); + IniMapSection section = getSpecificIniSection(); + setFalseColor(section.getAsInteger(KEY_CLOWN.getName(), 0) != 0); + setBestiaryIndex(section.getAsInteger(KEY_BESTIARY.getName(), 0)); + setArmor(section.getAsInteger(KEY_ARMOR.getName(), 0)); + setWalkScale(section.getAsDouble(KEY_WALKSCALE.getName(), getMoveScale())); + setRunScale(section.getAsDouble(KEY_RUNSCALE.getName(), getWalkScale())); + setAttribute(KEY_COLOR_LOCATION, setColorLocations(section)); + + // hardcoded stuff + // TODO: fix center positions of secondary BAM sources +// String resref = section.getAsString(Slots.get(Sequence.PST_Stand), ""); +// if ("POSMAIN".equalsIgnoreCase(resref)) { +// // pillar of skulls +// setAttribute(KEY_SHADOW, "POSSHAD"); +// } +// if ("IGHEAD".equalsIgnoreCase(resref)) { +// // "Coaxmetal" (iron golem) +// setAttribute(KEY_RESREF2, "IGARM"); +// } + if ((getAnimationId() & 0x0fff) == 0x000e) { + // Deionarra + setBrightest(true); + setLightSource(true); // TODO: confirm + } + } + + @Override + protected SeqDef getSequenceDefinition(Sequence seq) + { + SeqDef retVal = null; + IniMapSection section = getAnimationInfo().getSection(getAnimationSectionName()); + if (Slots.containsKey(seq)) { + ArrayList resrefList = new ArrayList<>(); + + // Shadow resref? + String resref = getAttribute(KEY_SHADOW); + if (!resref.isEmpty()) { + ResourceEntry entry = ResourceFactory.getResourceEntry(resref + ".BAM"); + if (entry != null) { + resrefList.add(entry); + } + } + + // Sprite resref + String name = Slots.get(seq); + resref = section.getAsString(name, ""); + if (!resref.isEmpty()) { + ResourceEntry entry = ResourceFactory.getResourceEntry(resref.toUpperCase(Locale.ENGLISH) + "B.BAM"); + if (entry == null) { + entry = ResourceFactory.getResourceEntry(resref.toUpperCase(Locale.ENGLISH) + ".BAM"); + } + if (entry != null) { + resrefList.add(entry); + } + } + + // Secondary sprite? + resref = getAttribute(KEY_RESREF2); + if (!resref.isEmpty()) { + ResourceEntry entry = ResourceFactory.getResourceEntry(resref + ".BAM"); + if (entry != null) { + resrefList.add(entry); + } + } + + retVal = new SeqDef(seq); + for (final ResourceEntry entry : resrefList) { + // determining number of directions + BamV1Control ctrl = SpriteUtils.loadBamController(entry); + int dirCount = ctrl.cycleCount(); + Direction[] directions = null; + Direction[] directionsE = null; + boolean mirror = true; + if (dirCount < SeqDef.DIR_REDUCED_W.length) { + // special: no individual directions + mirror = false; + directions = Arrays.copyOfRange(SeqDef.DIR_REDUCED_W, 0, dirCount); + } else if (dirCount == SeqDef.DIR_REDUCED_W.length) { + // reduced directions, mirrored east + directions = SeqDef.DIR_REDUCED_W; + directionsE = SeqDef.DIR_REDUCED_E; + } else if (dirCount == SeqDef.DIR_REDUCED.length) { + // special case: reduced directions, not mirrored + mirror = false; + directions = new Direction[SeqDef.DIR_REDUCED.length]; + System.arraycopy(SeqDef.DIR_REDUCED_W, 0, directions, 0, SeqDef.DIR_REDUCED_W.length); + System.arraycopy(SeqDef.DIR_REDUCED_E, 0, directions, SeqDef.DIR_REDUCED_W.length, SeqDef.DIR_REDUCED_E.length); + } else if (dirCount == SeqDef.DIR_FULL_W.length) { + // full directions, mirrored east + directions = SeqDef.DIR_FULL_W; + directionsE = SeqDef.DIR_FULL_E; + } else if (dirCount == SeqDef.DIR_FULL.length) { + // full directions, not mirrored + mirror = false; + directions = SeqDef.DIR_FULL; + } + + if (directions != null && SpriteUtils.bamCyclesExist(entry, 0, directions.length)) { + SeqDef tmp = SeqDef.createSequence(seq, directions, false, entry, 0); + retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); + if (mirror) { + tmp = SeqDef.createSequence(seq, directionsE, true, entry, 1); + retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); + } + } + } + + if (retVal.isEmpty()) { + retVal = null; + } + } + + return retVal; + } +} diff --git a/src/org/infinity/resource/cre/decoder/MonsterQuadrantDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterQuadrantDecoder.java new file mode 100644 index 000000000..9d9b8b1e2 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/MonsterQuadrantDecoder.java @@ -0,0 +1,203 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.infinity.resource.ResourceFactory; +import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.DecoderAttribute; +import org.infinity.resource.cre.decoder.internal.DirDef; +import org.infinity.resource.cre.decoder.internal.SegmentDef; +import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.IniMap; +import org.infinity.util.IniMapSection; +import org.infinity.util.Misc; +import org.infinity.util.tuples.Couple; + +/** + * Creature animation decoder for processing type 1000 (monster_quadrant) animations. + * Available ranges: [1000,1fff] + */ +public class MonsterQuadrantDecoder extends QuadrantsBaseDecoder +{ + /** The animation type associated with this class definition. */ + public static final AnimationType ANIMATION_TYPE = AnimationType.MONSTER_QUADRANT; + + public static final DecoderAttribute KEY_CASTER = DecoderAttribute.with("caster", DecoderAttribute.DataType.BOOLEAN); + public static final DecoderAttribute KEY_PATH_SMOOTH = DecoderAttribute.with("path_smooth", DecoderAttribute.DataType.BOOLEAN); + public static final DecoderAttribute KEY_EXTEND_DIRECTION = DecoderAttribute.with("extend_direction", DecoderAttribute.DataType.BOOLEAN); + public static final DecoderAttribute KEY_EXTEND_DIRECTION_TEST = DecoderAttribute.with("extend_direction_test", DecoderAttribute.DataType.INT); + + private static final HashMap> suffixMap = new HashMap>() {{ + put(Sequence.WALK, Couple.with("G1", 0)); + put(Sequence.STAND, Couple.with("G2", 0)); + put(Sequence.STANCE, Couple.with("G2", 16)); + put(Sequence.GET_HIT, Couple.with("G2", 32)); + put(Sequence.DIE, Couple.with("G2", 48)); + put(Sequence.SLEEP, get(Sequence.DIE)); + put(Sequence.GET_UP, Couple.with("!G2", 48)); + put(Sequence.TWITCH, Couple.with("G2", 64)); + put(Sequence.ATTACK, Couple.with("G3", 0)); + put(Sequence.ATTACK_2, Couple.with("G3", 16)); + put(Sequence.ATTACK_3, Couple.with("G3", 32)); + put(Sequence.CAST, get(Sequence.ATTACK_3)); + }}; + + /** + * A helper method that parses the specified data array and generates a {@link IniMap} instance out of it. + * @param data a String array containing table values for a specific table entry. + * @return a {@code IniMap} instance with the value derived from the specified data array. + * Returns {@code null} if no data could be derived. + */ + public static IniMap processTableData(String[] data) + { + IniMap retVal = null; + if (data == null || data.length < 16) { + return retVal; + } + + String resref = SpriteTables.valueToString(data, SpriteTables.COLUMN_RESREF, ""); + if (resref.isEmpty()) { + return retVal; + } + int falseColor = SpriteTables.valueToInt(data, SpriteTables.COLUMN_CLOWN, 0); + int caster = SpriteTables.valueToInt(data, SpriteTables.COLUMN_HELMET, 0); + int extendDirection = ResourceFactory.resourceExists(resref + "G11E.BAM") ? 1 : 0; + int extendDirectionTest = 9; + ResourceEntry bamEntry = ResourceFactory.getResourceEntry(resref + "G11.BAM"); + int numCycles = SpriteUtils.getBamCycles(bamEntry); + if (numCycles == 8) { + extendDirectionTest = 5; // TBC + } + + List lines = processTableDataGeneral(data, ANIMATION_TYPE); + lines.add("[monster_quadrant]"); + lines.add("false_color=" + falseColor); + lines.add("caster=" + caster); + lines.add("extend_direction=" + extendDirection); + lines.add("extend_direction_test=" + extendDirectionTest); + lines.add("quadrants=4"); + lines.add("resref=" + resref); + + retVal = IniMap.from(lines); + + return retVal; + } + + public MonsterQuadrantDecoder(int animationId, IniMap ini) throws Exception + { + super(ANIMATION_TYPE, animationId, ini); + } + + public MonsterQuadrantDecoder(CreResource cre) throws Exception + { + super(ANIMATION_TYPE, cre); + } + + /** Returns whether attack animations {@code Attack2} and {@code Attack3} are used as casting animations. */ + public boolean isCaster() { return getAttribute(KEY_CASTER); } + protected void setCaster(boolean b) { setAttribute(KEY_CASTER, b); } + + /** ??? */ + public boolean isSmoothPath() { return getAttribute(KEY_PATH_SMOOTH); } + protected void setSmoothPath(boolean b) { setAttribute(KEY_PATH_SMOOTH, b); } + + /** Returns whether eastern directions are available. */ + public boolean isExtendedDirection() { return getAttribute(KEY_EXTEND_DIRECTION); } + protected void setExtendedDirection(boolean b) { setAttribute(KEY_EXTEND_DIRECTION, b); } + + /** ??? */ + public int getExtendedDirectionSize() { return getAttribute(KEY_EXTEND_DIRECTION_TEST); } + protected void setExtendedDirectionSize(int v) { setAttribute(KEY_EXTEND_DIRECTION_TEST, v); } + + @Override + public List getAnimationFiles(boolean essential) + { + ArrayList retVal = new ArrayList<>(); + String resref = getAnimationResref(); + for (final String suffix : new String[] {"G1", "G2", "G3"}) { + for (int i = 0; i < getQuadrants(); i++) { + retVal.add(resref + suffix + (i+1) + ".BAM"); + if (isExtendedDirection()) { + retVal.add(resref + suffix + (i+1) + "E.BAM"); + } + } + } + return retVal; + } + + @Override + public boolean isSequenceAvailable(Sequence seq) + { + return (getSequenceDefinition(seq) != null); + } + + @Override + protected void init() throws Exception + { + // setting properties + super.init(); + IniMapSection section = getSpecificIniSection(); + setCaster(section.getAsInteger(KEY_CASTER.getName(), 0) != 0); + setFalseColor(section.getAsInteger(KEY_FALSE_COLOR.getName(), 0) != 0); + setSmoothPath(section.getAsInteger(KEY_PATH_SMOOTH.getName(), 0) != 0); + setExtendedDirection(section.getAsInteger(KEY_EXTEND_DIRECTION.getName(), 0) != 0); + setExtendedDirectionSize(section.getAsInteger(KEY_EXTEND_DIRECTION_TEST.getName(), 9)); + setQuadrants(section.getAsInteger(KEY_QUADRANTS.getName(), 4)); + Misc.requireCondition(getQuadrants() < 10, "Too many quadrants defined: " + getQuadrants(), IllegalArgumentException.class); + } + + @Override + protected SeqDef getSequenceDefinition(Sequence seq) + { + SeqDef retVal = null; + String resref = getAnimationResref(); + + if (isCaster() && (seq == Sequence.ATTACK_3)) { + return retVal; + } + + if (!isCaster() && (seq == Sequence.CAST)) { + return retVal; + } + + List cycleList = new ArrayList<>(); + List cycleListE = new ArrayList<>(); + boolean valid = true; + if (suffixMap.containsKey(seq) && (isCaster() || seq != Sequence.SPELL && seq != Sequence.CAST)) { + String suffix = suffixMap.get(seq).getValue0(); + SegmentDef.Behavior behavior = SegmentDef.getBehaviorOf(suffix); + suffix = SegmentDef.fixBehaviorSuffix(suffix); + int cycleOfs = suffixMap.get(seq).getValue1().intValue(); + for (int i = 1; valid && i <= getQuadrants(); i++) { + ResourceEntry entry = ResourceFactory.getResourceEntry(resref + suffix + i + ".BAM"); + ResourceEntry entryE = entry; + int cycle = cycleOfs; + int cycleE = cycleOfs + 1; + valid &= SpriteUtils.bamCyclesExist(entry, cycle, SeqDef.DIR_FULL_W.length); + if (isExtendedDirection()) { + entryE = ResourceFactory.getResourceEntry(resref + suffix + i + "E.BAM"); + cycleE = cycle + SeqDef.DIR_FULL_W.length; + valid &= SpriteUtils.bamCyclesExist(entryE, cycleE, SeqDef.DIR_FULL_E.length); + } + cycleList.add(new SegmentDef(entry, cycle, null, behavior)); + cycleListE.add(new SegmentDef(entryE, cycleE, null, behavior)); + } + } + + if (!cycleList.isEmpty() && valid) { + retVal = SeqDef.createSequence(seq, SeqDef.DIR_FULL_W, false, cycleList); + SeqDef tmp = SeqDef.createSequence(seq, SeqDef.DIR_FULL_E, !isExtendedDirection(), cycleListE); + retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); + } + + return retVal; + } +} diff --git a/src/org/infinity/resource/cre/decoder/QuadrantsBaseDecoder.java b/src/org/infinity/resource/cre/decoder/QuadrantsBaseDecoder.java new file mode 100644 index 000000000..40086c667 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/QuadrantsBaseDecoder.java @@ -0,0 +1,39 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder; + +import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.DecoderAttribute; +import org.infinity.util.IniMap; + +/** + * Common base for processing segmented creature animations. + */ +public abstract class QuadrantsBaseDecoder extends SpriteDecoder +{ + public static final DecoderAttribute KEY_QUADRANTS = DecoderAttribute.with("quadrants", DecoderAttribute.DataType.INT); + + /** Returns the number of segments this creature animation has been split into. */ + public int getQuadrants() { return getAttribute(KEY_QUADRANTS); } + protected void setQuadrants(int v) { setAttribute(KEY_QUADRANTS, v); } + + public QuadrantsBaseDecoder(AnimationType type, int animationId, IniMap ini) throws Exception + { + super(type, animationId, ini); + } + + protected QuadrantsBaseDecoder(AnimationType type, CreResource cre) throws Exception + { + super(type, cre); + } + + @Override + protected void init() throws Exception + { + // setting properties + initDefaults(getAnimationInfo()); + setDetectedByInfravision(true); + } +} diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java new file mode 100644 index 000000000..0d51231e7 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -0,0 +1,2615 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder; + +import java.awt.AlphaComposite; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.Stroke; +import java.awt.Transparency; +import java.awt.geom.AffineTransform; +import java.awt.image.AffineTransformOp; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.TreeMap; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +import org.infinity.datatype.IsNumeric; +import org.infinity.resource.Profile; +import org.infinity.resource.ResourceFactory; +import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.CreatureInfo; +import org.infinity.resource.cre.decoder.internal.CycleDef; +import org.infinity.resource.cre.decoder.internal.DecoderAttribute; +import org.infinity.resource.cre.decoder.internal.DirDef; +import org.infinity.resource.cre.decoder.internal.FrameInfo; +import org.infinity.resource.cre.decoder.internal.SegmentDef; +import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.graphics.ColorConvert; +import org.infinity.resource.graphics.PseudoBamDecoder; +import org.infinity.resource.graphics.BamV1Decoder.BamV1Control; +import org.infinity.resource.key.BufferedResourceEntry; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.IdsMap; +import org.infinity.util.IdsMapCache; +import org.infinity.util.IdsMapEntry; +import org.infinity.util.IniMap; +import org.infinity.util.IniMapCache; +import org.infinity.util.IniMapEntry; +import org.infinity.util.IniMapSection; +import org.infinity.util.Misc; +import org.infinity.util.Table2da; +import org.infinity.util.tuples.Couple; + +/** + * Specialized BAM decoder for creature animation sprites. + */ +public abstract class SpriteDecoder extends PseudoBamDecoder +{ + // List of general creature animation attributes + public static final DecoderAttribute KEY_ANIMATION_TYPE = DecoderAttribute.with("animation_type", DecoderAttribute.DataType.USERDEFINED); + public static final DecoderAttribute KEY_ANIMATION_SECTION = DecoderAttribute.with("animation_section", DecoderAttribute.DataType.STRING); + public static final DecoderAttribute KEY_MOVE_SCALE = DecoderAttribute.with("move_scale", DecoderAttribute.DataType.DECIMAL); + public static final DecoderAttribute KEY_ELLIPSE = DecoderAttribute.with("ellipse", DecoderAttribute.DataType.INT); + public static final DecoderAttribute KEY_COLOR_BLOOD = DecoderAttribute.with("color_blood", DecoderAttribute.DataType.INT); + public static final DecoderAttribute KEY_COLOR_CHUNKS = DecoderAttribute.with("color_chunks", DecoderAttribute.DataType.INT); + public static final DecoderAttribute KEY_SOUND_FREQ = DecoderAttribute.with("sound_freq", DecoderAttribute.DataType.INT); + public static final DecoderAttribute KEY_SOUND_DEATH = DecoderAttribute.with("sound_death", DecoderAttribute.DataType.STRING); + public static final DecoderAttribute KEY_PERSONAL_SPACE = DecoderAttribute.with("personal_space", DecoderAttribute.DataType.INT); + public static final DecoderAttribute KEY_CAST_FRAME = DecoderAttribute.with("cast_frame", DecoderAttribute.DataType.INT); + public static final DecoderAttribute KEY_HEIGHT_OFFSET = DecoderAttribute.with("height_offset", DecoderAttribute.DataType.INT); + public static final DecoderAttribute KEY_BRIGHTEST = DecoderAttribute.with("brightest", DecoderAttribute.DataType.BOOLEAN); + public static final DecoderAttribute KEY_MULTIPLY_BLEND = DecoderAttribute.with("multiply_blend", DecoderAttribute.DataType.BOOLEAN); + public static final DecoderAttribute KEY_LIGHT_SOURCE = DecoderAttribute.with("light_source", DecoderAttribute.DataType.BOOLEAN); + public static final DecoderAttribute KEY_NEW_PALETTE = DecoderAttribute.with("new_palette", DecoderAttribute.DataType.STRING); + public static final DecoderAttribute KEY_SOUND_REF = DecoderAttribute.with("sound_ref", DecoderAttribute.DataType.STRING); + public static final DecoderAttribute KEY_COMBAT_ROUND_0 = DecoderAttribute.with("combat_round_0", DecoderAttribute.DataType.STRING); + public static final DecoderAttribute KEY_COMBAT_ROUND_1 = DecoderAttribute.with("combat_round_1", DecoderAttribute.DataType.STRING); + public static final DecoderAttribute KEY_COMBAT_ROUND_2 = DecoderAttribute.with("combat_round_2", DecoderAttribute.DataType.STRING); + public static final DecoderAttribute KEY_COMBAT_ROUND_3 = DecoderAttribute.with("combat_round_3", DecoderAttribute.DataType.STRING); + public static final DecoderAttribute KEY_COMBAT_ROUND_4 = DecoderAttribute.with("combat_round_4", DecoderAttribute.DataType.STRING); + public static final DecoderAttribute KEY_WALK_SOUND = DecoderAttribute.with("walk_sound", DecoderAttribute.DataType.STRING); + // List of commonly used attributes specific to creature animation types + public static final DecoderAttribute KEY_RESREF = DecoderAttribute.with("resref", DecoderAttribute.DataType.STRING); + public static final DecoderAttribute KEY_DETECTED_BY_INFRAVISION = DecoderAttribute.with("detected_by_infravision", DecoderAttribute.DataType.BOOLEAN); + public static final DecoderAttribute KEY_FALSE_COLOR = DecoderAttribute.with("false_color", DecoderAttribute.DataType.BOOLEAN); + public static final DecoderAttribute KEY_TRANSLUCENT = DecoderAttribute.with("translucent", DecoderAttribute.DataType.BOOLEAN); + + + // Predefined sets of games with common animation slot mappings + private static final EnumSet TYPE_GAME_BG1 = EnumSet.of(Profile.Game.BG1, Profile.Game.BG1TotSC); + private static final EnumSet TYPE_GAME_BG2_TOB = EnumSet.of(Profile.Game.BG2ToB, Profile.Game.BGT, Profile.Game.Tutu); + // all BG2 game variants + private static final EnumSet TYPE_GAME_BG2 = EnumSet.of(Profile.Game.BG2SoA, Profile.Game.BG2ToB, Profile.Game.BGT, + Profile.Game.Tutu); + private static final EnumSet TYPE_GAME_IWD = EnumSet.of(Profile.Game.IWD); + private static final EnumSet TYPE_GAME_IWD_HOW = EnumSet.of(Profile.Game.IWDHoW, Profile.Game.IWDHowTotLM); + private static final EnumSet TYPE_GAME_IWD2 = EnumSet.of(Profile.Game.IWD2); + private static final EnumSet TYPE_GAME_PST = EnumSet.of(Profile.Game.PST); + // all EE games + private static final EnumSet TYPE_GAME_EE = EnumSet.of(Profile.Game.BG1EE, Profile.Game.BG1SoD, Profile.Game.BG2EE, + Profile.Game.EET, Profile.Game.IWDEE, Profile.Game.PSTEE); + private static final EnumSet TYPE_GAME_PSTEE = EnumSet.of(Profile.Game.PSTEE); + // all games except PST + private static final EnumSet TYPE_GAME_ALL = EnumSet.complementOf(EnumSet.of(Profile.Game.Unknown, Profile.Game.PST)); + + // Predefined slot ranges for specific animation types + private static final List RANGE_EFFECT = Arrays.asList(new Range(0, 0xfff)); + private static final List RANGE_MONSTER_QUADRANT = Arrays.asList(new Range(0x1000, 0x1ff)); + private static final List RANGE_MONSTER_MULTI = Arrays.asList(new Range(0x1200, 0xff), + new Range(0x1400, 0xbff)); + private static final List RANGE_MONSTER_MULTI_NEW = Arrays.asList(new Range(0x1300, 0xff)); + private static final List RANGE_MONSTER_LAYERED = Arrays.asList(new Range(0x8000, 0xfff)); + private static final List RANGE_MONSTER_LAYERED_SPELL = Arrays.asList(new Range(0x2000, 0xfff)); + private static final List RANGE_MONSTER_ANKHEG = Arrays.asList(new Range(0x3000, 0xfff)); + private static final List RANGE_TOWN_STATIC = Arrays.asList(new Range(0x4000, 0xfff)); + private static final List RANGE_CHARACTER = Arrays.asList(new Range(0x5000, 0x3ff), + new Range(0x5500, 0xff), + new Range(0x6000, 0x3ff), + new Range(0x6500, 0xff)); + private static final List RANGE_CHARACTER_IA = Arrays.asList(new Range(0x6600, 0x4ff)); + private static final List RANGE_CHARACTER_OLD = Arrays.asList(new Range(0x5400, 0xff), + new Range(0x5600, 0x9ff), + new Range(0x6400, 0xff), + new Range(0x6600, 0x9ff)); + private static final List RANGE_MONSTER = Arrays.asList(new Range(0x7002, 0xd, 0x00, 0x1f, 4), + new Range(0x7004, 0xb, 0x20, 0x0f, 4), + new Range(0x7000, 0xf, 0x30, 0x0f, 4), + new Range(0x7003, 0xc, 0x40, 0x0f, 4), + new Range(0x7002, 0xd, 0x50, 0x0f, 4), + new Range(0x7003, 0xc, 0x70, 0x0f, 4), + new Range(0x7005, 0xa, 0x90, 0x1f, 4), + new Range(0x7007, 0x8, 0xb0, 0x0f, 4), + new Range(0x7002, 0xd, 0xc0, 0x0f, 4), + new Range(0x7002, 0xd, 0xe0, 0x0f, 4), + new Range(0x7000, 0xf, 0xf0, 0x0f, 4)); + private static final List RANGE_MONSTER_IA = Arrays.asList(new Range(0x5b00, 0x4ff)); + private static final List RANGE_MONSTER_OLD = Arrays.asList(new Range(0x7000, 0x1, 0x00, 0x1f, 4), + new Range(0x7000, 0x3, 0x20, 0x0f, 4), + new Range(0x7000, 0x2, 0x40, 0x0f, 4), + new Range(0x7000, 0x1, 0x50, 0x0f, 4), + new Range(0x7000, 0xf, 0x60, 0x0f, 4), + new Range(0x7000, 0x2, 0x70, 0x0f, 4), + new Range(0x7000, 0xf, 0x80, 0x0f, 4), + new Range(0x7000, 0x4, 0x90, 0x1f, 4), + new Range(0x7000, 0x6, 0xb0, 0x0f, 4), + new Range(0x7000, 0x1, 0xc0, 0x0f, 4), + new Range(0x7000, 0xf, 0xd0, 0x0f, 4), + new Range(0x7000, 0x1, 0xe0, 0x0f, 4)); + private static final List RANGE_MONSTER_OLD_IA = Arrays.asList(new Range(0x547a, 0x479)); + private static final List RANGE_MONSTER_LARGE = Arrays.asList(new Range(0x9000, 0xfff)); + private static final List RANGE_MONSTER_LARGE_16 = Arrays.asList(new Range(0xa000, 0xfff)); + private static final List RANGE_AMBIENT_STATIC = Arrays.asList(new Range(0xb000, 0xfff)); + private static final List RANGE_AMBIENT = Arrays.asList(new Range(0xc000, 0xfff)); + private static final List RANGE_FLYING = Arrays.asList(new Range(0xd000, 0xfff)); + private static final List RANGE_MONSTER_ICEWIND = Arrays.asList(new Range(0xe000, 0xfff)); + private static final List RANGE_MONSTER_ICEWIND_EX = Arrays.asList(new Range(0xe000, 0x1fff)); + private static final List RANGE_MONSTER_ICEWIND_IA = Arrays.asList(new Range(0x5000, 0x478)); + private static final List RANGE_MONSTER_PLANESCAPE = Arrays.asList(new Range(0xf000, 0xfff)); + private static final List RANGE_MONSTER_PLANESCAPE_EX = Arrays.asList(new Range(0x0000, 0xffff)); + + /** Available creature animation types. */ + public enum AnimationType { + /** Animation type: 0000 */ + EFFECT(0x0000, "effect", Arrays.asList( + Couple.with(TYPE_GAME_ALL, RANGE_EFFECT))), // type=0 + /** Animation type: 1000 (slots [1000..11ff]) */ + MONSTER_QUADRANT(0x1000, "monster_quadrant", Arrays.asList( + Couple.with(TYPE_GAME_ALL, RANGE_MONSTER_QUADRANT))), // type=1 + /** Animation type: 1000 (slots [1200..12ff], [1400..1fff] */ + MONSTER_MULTI(0x1000, "monster_multi", Arrays.asList( + Couple.with(TYPE_GAME_EE, RANGE_MONSTER_MULTI), + Couple.with(TYPE_GAME_BG2, RANGE_MONSTER_MULTI), + Couple.with(TYPE_GAME_IWD2, RANGE_MONSTER_MULTI))), // type=2 + /** Animation type: 1000 (slots [1300..13ff]) */ + MONSTER_MULTI_NEW(0x1000, "multi_new", Arrays.asList( + Couple.with(TYPE_GAME_EE, RANGE_MONSTER_MULTI_NEW), + Couple.with(TYPE_GAME_BG2_TOB, RANGE_MONSTER_MULTI_NEW))), // type=3 + /** Animation type: 8000 */ + MONSTER_LAYERED(0x8000, "monster_layered", Arrays.asList( + Couple.with(TYPE_GAME_ALL, RANGE_MONSTER_LAYERED))), // type=4 + /** Animation type: 2000 */ + MONSTER_LAYERED_SPELL(0x2000, "monster_layered_spell", Arrays.asList( + Couple.with(TYPE_GAME_ALL, RANGE_MONSTER_LAYERED_SPELL))), // type=5 + /** Animation type: 3000 */ + MONSTER_ANKHEG(0x3000, "monster_ankheg", Arrays.asList( + Couple.with(TYPE_GAME_ALL, RANGE_MONSTER_ANKHEG))), // type=6 + /** Animation type: 4000 */ + TOWN_STATIC(0x4000, "town_static", Arrays.asList( + Couple.with(TYPE_GAME_ALL, RANGE_TOWN_STATIC))), // type=7 + /** Animation types: 5000, 6000 (slots [5000..53ff], [5500..55ff], [6000..63ff], [6500..65ff]) */ + CHARACTER(new int[] {0x5000, 0x6000}, "character", Arrays.asList( + Couple.with(TYPE_GAME_EE, RANGE_CHARACTER), + Couple.with(TYPE_GAME_BG2, RANGE_CHARACTER), + Couple.with(TYPE_GAME_IWD_HOW, RANGE_CHARACTER), + Couple.with(TYPE_GAME_IWD2, RANGE_CHARACTER)), + RANGE_CHARACTER_IA), // type=8 + /** Animation types: 5000, 6000 (slots [5400..54ff], [5600..5fff], [6400..64ff], [6600..6fff]) */ + CHARACTER_OLD(new int[] {0x5000, 0x6000}, "character_old", Arrays.asList( + Couple.with(TYPE_GAME_ALL, RANGE_CHARACTER_OLD), + Couple.with(TYPE_GAME_BG1, RANGE_CHARACTER), + Couple.with(TYPE_GAME_IWD, RANGE_CHARACTER))), // type=9 + /** Animation type: 7000 (many subranges) */ + MONSTER(0x7000, "monster", Arrays.asList( + Couple.with(TYPE_GAME_EE, RANGE_MONSTER), + Couple.with(TYPE_GAME_BG2, RANGE_MONSTER), + Couple.with(TYPE_GAME_IWD, RANGE_MONSTER), + Couple.with(TYPE_GAME_IWD_HOW, RANGE_MONSTER), + Couple.with(TYPE_GAME_IWD2, RANGE_MONSTER)), + RANGE_MONSTER_IA), // type=10 + /** Animation type: 7000 (many subranges) */ + MONSTER_OLD(0x7000, "monster_old", Arrays.asList( + Couple.with(TYPE_GAME_ALL, RANGE_MONSTER_OLD)), + RANGE_MONSTER_OLD_IA), // type=11 + /** Animation type: 9000 */ + MONSTER_LARGE(0x9000, "monster_large", Arrays.asList( + Couple.with(TYPE_GAME_ALL, RANGE_MONSTER_LARGE))), // type=12 + /** Animation type: A000 */ + MONSTER_LARGE_16(0xa000, "monster_large16", Arrays.asList( + Couple.with(TYPE_GAME_ALL, RANGE_MONSTER_LARGE_16))), // type=13 + /** Animation type: B000 */ + AMBIENT_STATIC(0xb000, "ambient_static", Arrays.asList( + Couple.with(TYPE_GAME_ALL, RANGE_AMBIENT_STATIC))), // type=14 + /** Animation type: C000 */ + AMBIENT(0xc000, "ambient", Arrays.asList( + Couple.with(TYPE_GAME_ALL, RANGE_AMBIENT))), // type=15 + /** Animation type: D000 */ + FLYING(0xd000, "flying", Arrays.asList( + Couple.with(TYPE_GAME_ALL, RANGE_FLYING))), // type=16 + /** Animation type: E000 (for non-EE: also slots [f000..ffff]) */ + MONSTER_ICEWIND(0xe000, "monster_icewind", Arrays.asList( + Couple.with(TYPE_GAME_EE, RANGE_MONSTER_ICEWIND), + Couple.with(TYPE_GAME_BG2, RANGE_MONSTER_ICEWIND), + Couple.with(TYPE_GAME_IWD, RANGE_MONSTER_ICEWIND_EX), + Couple.with(TYPE_GAME_IWD_HOW, RANGE_MONSTER_ICEWIND_EX), + Couple.with(TYPE_GAME_IWD2, RANGE_MONSTER_ICEWIND_EX)), + RANGE_MONSTER_ICEWIND_IA), // type=17 + /** Animation type: F000 */ + MONSTER_PLANESCAPE(0xf000, "monster_planescape", Arrays.asList( + Couple.with(TYPE_GAME_PSTEE, RANGE_MONSTER_PLANESCAPE), + Couple.with(TYPE_GAME_PST, RANGE_MONSTER_PLANESCAPE_EX))); // type=18 + + private final EnumMap> rangeMap = new EnumMap<>(Profile.Game.class); + private final List iaRanges; + private final int[] animationTypes; + private final String sectionName; + + /** + * @param type slot base range + * @param sectionName INI section name + * @param entries list of games and their associated slot ranges. + */ + private AnimationType(int type, String sectionName, List, List>> entries) + throws IllegalArgumentException + { + this(new int[] {type}, sectionName, entries, null); + } + + /** + * @param type + * @param sectionName + * @param entries + * @param infinityAnimationRanges + * @throws IllegalArgumentException + */ + private AnimationType(int type, String sectionName, List, List>> entries, + List infinityAnimationRanges) + throws IllegalArgumentException + { + this(new int[] {type}, sectionName, entries, infinityAnimationRanges); + } + + /** + * @param types + * @param sectionName + * @param entries + * @throws IllegalArgumentException + */ + private AnimationType(int[] types, String sectionName, List, List>> entries) + throws IllegalArgumentException + { + this(types, sectionName, entries, null); + } + + /** + * @param type list of slot base ranges + * @param sectionName INI section name + * @param entries list of games and their associated slot ranges. + * @throws IllegalArgumentException + */ + private AnimationType(int[] types, String sectionName, List, List>> entries, + List infinityAnimationRanges) throws IllegalArgumentException + { + try { + Misc.requireCondition(types != null && types.length > 0, "Type cannot be empty", IllegalArgumentException.class); + Misc.requireCondition(sectionName != null && !sectionName.isEmpty(), "Section name cannot be empty", IllegalArgumentException.class); + } catch (IllegalArgumentException iae) { + throw iae; + } catch (Exception e) { + } + this.animationTypes = types; + this.sectionName = sectionName; + for (final Couple, List> entry : entries) { + EnumSet games = entry.getValue0(); + List ranges = entry.getValue1(); + for (final Profile.Game game : games) { + if (ranges.size() > 0) { + List list = this.rangeMap.get(game); + if (list != null) { + list.addAll(ranges); + } else { + this.rangeMap.put(game, new ArrayList<>(ranges)); + } + } + } + } + this.iaRanges = infinityAnimationRanges; + } + + /** Returns the name for the type-specific INI section. */ + public String getSectionName() { return sectionName; } + + /** Returns the first available base animation type associated with the enum instance. */ + public int getType() { return animationTypes[0]; } + + /** Returns the number of defined base animation types associated with the enum instance. */ + public int getTypeCount() { return animationTypes.length; } + + /** Returns the specified base animation type associated with the enum instance. */ + public int getType(int idx) { return animationTypes[idx]; } + + /** + * Returns whether the specified value is covered by the ranges associated with the enum instance + * for the current game. + */ + public boolean contains(int value) { return contains(Profile.getGame(), value); } + + /** + * Returns whether the specified value is covered by the ranges associated with the enum instance + * for the specified game. + */ + public boolean contains(Profile.Game game, int value) + { + if (game == null) { + game = Profile.getGame(); + } + boolean retVal = false; + + // TODO: improve Infinity Animations check + AnimationType type = containsInfinityAnimations(value); + if (type != null && type != this) { + return retVal; + } + retVal = (type == this); + + if (!retVal) { + retVal = contains(value, rangeMap.get(Profile.getGame())); + } + return retVal; + } + + // Checks whether specified value is covered by the given ranges + private static boolean contains(int value, List ranges) + { + if (ranges != null) { + return ranges + .parallelStream() + .anyMatch(r -> r.contains(value)); + } + return false; + } + + /** + * Determines the {@code AnimationType} enum where a defined Infinity Animations (IA) range covers the specied value. + * @param value the value to check. + * @return {@code AnimationType} enum supporting the specified IA value. + * Returns {@code null} if value is not covered by any IA range. + */ + public static AnimationType containsInfinityAnimations(int value) + { + AnimationType retVal = null; + if (Profile.getProperty(Profile.Key.GET_INFINITY_ANIMATIONS) > 0) { + for (AnimationType type : AnimationType.values()) { + if (contains(value, type.iaRanges)) { + retVal = type; + break; + } + } + } + return retVal; + } + + /** + * Returns the {@code AnimationType} enum covering the specified animation id. + * @param animationId the animation id + * @return {@code AnimationType} enum that covers the specified animation id. Returns {@code null} otherwise. + */ + public static AnimationType typeOfId(int animationId) + { + for (final AnimationType type : values()) { + if (type.contains(animationId)) { + return type; + } + } + return null; + } + } + + /** Available animation sequences. Note: PST-specific animation sequences are prefixed by "PST_". */ + public enum Sequence { + /** Special value: Used when no animation sequence is loaded. */ + NONE, + STAND("Stand"), + STAND2("Stand 2"), + STAND3("Stand 3"), + /** For buried creatures only: stand sequence when emerged. */ + STAND_EMERGED("Stand (emerged)"), + /** For buried creatures only: stand sequence when hidden. */ + STAND_HIDDEN("Stand (hidden)"), + STANCE("Combat ready"), + STANCE2("Combat ready"), + GET_HIT("Get hit"), + /** Dying sequence; may also be used for sleep sequence. */ + DIE("Die"), + TWITCH("Twitch"), + /** The animation sequence used while chanting a spell. */ + SPELL("Conjure spell"), + /** The animation sequence used when releasing a spell. */ + CAST("Cast spell"), + SPELL1("Conjure spell 1"), + CAST1("Cast spell 1"), + SPELL2("Conjure spell 2"), + CAST2("Cast spell 2"), + SPELL3("Conjure spell 3"), + CAST3("Cast spell 3"), + SPELL4("Conjure spell 4"), + CAST4("Cast spell 4"), + SLEEP("Sleep"), + GET_UP("Get up"), + SLEEP2("Sleep 2"), + GET_UP2("Get up 2"), + WALK("Walk"), + ATTACK("Attack"), + ATTACK_2("Attack 2"), + ATTACK_3("Attack 3"), + ATTACK_4("Attack 4"), + ATTACK_5("Attack 5"), + ATTACK_2H("Attack (2-h)"), + /** 1-h slash attack; also used for throwing/sling in BG1/IWD */ + ATTACK_SLASH_1H("Attack (slash)"), + /** 1-h backslash attack */ + ATTACK_BACKSLASH_1H("Attack (backslash)"), + /** 1-h thrust/jab attack */ + ATTACK_JAB_1H("Attack (jab)"), + /** 2-h slash attack */ + ATTACK_SLASH_2H("Attack (slash)"), + /** 2-h backslash attack */ + ATTACK_BACKSLASH_2H("Attack (backslash)"), + /** 2-h thrust/jab attack */ + ATTACK_JAB_2H("Attack (jab)"), + /** two-weapon attack */ + ATTACK_2WEAPONS1("Attack (two-weapons) 1"), + /** two-weapon attack */ + ATTACK_2WEAPONS2("Attack (two-weapons) 2"), + /** Generic overhead attack; may be used for throwing weapons and weapons without equipped appearance. */ + ATTACK_OVERHEAD("Attack (overhead)"), + ATTACK_BOW("Attack (bow)"), + ATTACK_SLING("Attack (sling)"), + ATTACK_CROSSBOW("Attack (crossbow)"), + /** Generic ranged attack animation (e.g. for innate powers/breath weapons) */ + SHOOT("Attack (ranged)"), + /** For buried creatures only: monster emerges from underground. */ + EMERGE("Emerge"), + /** For buried creatures only: monster retreats to underground. */ + HIDE("Hide"), + + PST_ATTACK1("Attack"), + PST_ATTACK2("Attack 2"), + PST_ATTACK3("Attack 3"), + PST_GET_HIT("Get hit"), + PST_RUN("Run"), + PST_WALK("Walk"), + PST_SPELL1("Cast spell"), + PST_SPELL2("Cast spell 2"), + PST_SPELL3("Cast spell 3"), + PST_GET_UP("Get up"), + PST_DIE_FORWARD("Die (fall forward)"), + PST_DIE_BACKWARD("Die (fall backwards)"), + PST_DIE_COLLAPSE("Die (collapse)"), + PST_TALK1("Talk"), + PST_TALK2("Talk 2"), + PST_TALK3("Talk 3"), + PST_STAND_FIDGET1("Stand (fidget)"), + PST_STAND_FIDGET2("Stand (fidget 2)"), + PST_STANCE_FIDGET1("Combat ready (fidget)"), + PST_STANCE_FIDGET2("Combat ready (fidget 2)"), + PST_STAND("Stand"), + PST_STANCE("Combat ready"), + PST_STANCE_TO_STAND("Combat ready to stand"), + PST_STAND_TO_STANCE("Stand to combat ready"), + PST_MISC1("Custom sequence 1"), + PST_MISC2("Custom sequence 2"), + PST_MISC3("Custom sequence 3"), + PST_MISC4("Custom sequence 4"), + PST_MISC5("Custom sequence 5"), + PST_MISC6("Custom sequence 6"), + PST_MISC7("Custom sequence 7"), + PST_MISC8("Custom sequence 8"), + PST_MISC9("Custom sequence 9"), + PST_MISC10("Custom sequence 10"), + PST_MISC11("Custom sequence 11"), + PST_MISC12("Custom sequence 12"), + PST_MISC13("Custom sequence 13"), + PST_MISC14("Custom sequence 14"), + PST_MISC15("Custom sequence 15"), + PST_MISC16("Custom sequence 16"), + PST_MISC17("Custom sequence 17"), + PST_MISC18("Custom sequence 18"), + PST_MISC19("Custom sequence 19"), + PST_MISC20("Custom sequence 20"); + + private final String desc; + + private Sequence() { this(null); } + private Sequence(String desc) { this.desc = desc; } + + @Override + public String toString() + { + return (desc != null) ? desc : super.toString(); + } + } + + /** Available cardinal directions for action sequences. */ + public enum Direction { + /** South */ + S(0), + /** South-southwest */ + SSW(1), + /** Southwest */ + SW(2), + /** West-southwest */ + WSW(3), + /** West */ + W(4), + /** West-northwest */ + WNW(5), + /** Northwest */ + NW(6), + /** North-northwest */ + NNW(7), + /** North */ + N(8), + /** North-northeast */ + NNE(9), + /** Northeast */ + NE(10), + /** East-northeast */ + ENE(11), + /** East */ + E(12), + /** East-southeast */ + ESE(13), + /** Southeast */ + SE(14), + /** South-southeast */ + SSE(15); + + private final int dir; + private Direction(int dir) { this.dir = dir; } + + /** Returns the numeric direction value. */ + public int getValue() { return dir; } + + /** + * Determines the {@link Direction} instance associated with the specified numeric value and returns it. + * Return {@code null} if association could not be determined. + */ + public static Direction from(int value) { + for (final Direction d : Direction.values()) { + if (d.getValue() == value) { + return d; + } + } + return null; + } + } + + /** Mappings between animation types and compatible sprite classes. */ + private static final EnumMap> typeAssociations = + new EnumMap>(AnimationType.class) {{ + put(AnimationType.EFFECT, EffectDecoder.class); + put(AnimationType.MONSTER_QUADRANT, MonsterQuadrantDecoder.class); + put(AnimationType.MONSTER_MULTI, MonsterMultiDecoder.class); + put(AnimationType.MONSTER_MULTI_NEW, MonsterMultiNewDecoder.class); + put(AnimationType.MONSTER_LAYERED_SPELL, MonsterLayeredSpellDecoder.class); + put(AnimationType.MONSTER_ANKHEG, MonsterAnkhegDecoder.class); + put(AnimationType.TOWN_STATIC, TownStaticDecoder.class); + put(AnimationType.CHARACTER, CharacterDecoder.class); + put(AnimationType.CHARACTER_OLD, CharacterOldDecoder.class); + put(AnimationType.MONSTER, MonsterDecoder.class); + put(AnimationType.MONSTER_OLD, MonsterOldDecoder.class); + put(AnimationType.MONSTER_LAYERED, MonsterLayeredDecoder.class); + put(AnimationType.MONSTER_LARGE, MonsterLargeDecoder.class); + put(AnimationType.MONSTER_LARGE_16, MonsterLarge16Decoder.class); + put(AnimationType.AMBIENT_STATIC, AmbientStaticDecoder.class); + put(AnimationType.AMBIENT, AmbientDecoder.class); + put(AnimationType.FLYING, FlyingDecoder.class); + put(AnimationType.MONSTER_ICEWIND, MonsterIcewindDecoder.class); + put(AnimationType.MONSTER_PLANESCAPE, MonsterPlanescapeDecoder.class); + }}; + + /** + * A default operation that can be passed to the {@link #createAnimation(SeqDef, Consumer, Function, BiConsumer)} method. + * It is called once per source BAM resource. + * Performed actions: palette replacement, shadow color fix, false color replacement, translucency + */ + protected final BiConsumer FN_BEFORE_SRC_BAM = new BiConsumer() { + @Override + public void accept(BamV1Control control, SegmentDef sd) + { + if (sd.getSpriteType() == SegmentDef.SpriteType.AVATAR) { + int[] palette = getNewPaletteData(sd.getEntry()); + if (palette != null) { + SpriteUtils.applyNewPalette(control, palette); + } + } + SpriteUtils.fixShadowColor(control); + if (isFalseColor()) { + applyFalseColors(control, sd); + } + if (isTranslucent()) { + applyTranslucency(control); + } + } + }; + + /** + * A default operation that can be passed to the {@link #createAnimation(SeqDef, Consumer, Function, BiConsumer)} method. + * It is called for each source frame (segment) before being applied to the target frame. + * Performed actions: none + */ + protected final Function FN_BEFORE_SRC_FRAME = new Function() { + @Override + public BufferedImage apply(BufferedImage image) + { + // TODO: apply brightest, multiply_blend (?) + return image; + } + }; + + /** + * A default action that can be passed to the {@link #createAnimation(SeqDef, Consumer, Function, BiConsumer)} method. + * It calculates an eastern direction frame by mirroring it horizontally if needed. + */ + protected final BiConsumer FN_AFTER_DST_FRAME = new BiConsumer() { + @Override + public void accept(DirDef dd, Integer idx) + { + if (dd.isMirrored()) { + flipImageHorizontal(idx.intValue()); + } + } + }; + + private final CreatureInfo creInfo; + private final IniMap ini; + /** Storage for associations between directions and cycle indices. */ + private final EnumMap directionMap; + /** Cache for creature animation attributes. */ + private final TreeMap attributesMap; + + private Sequence currentSequence; + private boolean showCircle; + private boolean showPersonalSpace; + private boolean showBoundingBox; + private boolean renderSpriteAvatar; + private boolean renderSpriteWeapon; + private boolean renderSpriteHelmet; + private boolean renderSpriteShield; + private boolean animationChanged; + private boolean autoApplyChanges; + + /** + * Convenience method for loading the animation of the specified CRE resource. + * @param cre The CRE resource instance. + * @return A {@code SpriteDecoder} instance with processed animation data. + * @throws Exception if the specified resource could not be processed. + */ + public static SpriteDecoder importSprite(CreResource cre) throws Exception + { + Objects.requireNonNull(cre, "CRE resource cannot be null"); + int animationId = ((IsNumeric)cre.getAttribute(CreResource.CRE_ANIMATION)).getValue(); + Class spriteClass = + Objects.requireNonNull(detectAnimationType(animationId), "Could not determine animation type"); + try { + Constructor ctor = + Objects.requireNonNull(spriteClass.getConstructor(CreResource.class), "No matching constructor found"); + return ctor.newInstance(cre); + } catch (InvocationTargetException ite) { + throw (ite.getCause() instanceof Exception) ? (Exception)ite.getCause() : ite; + } + } + + /** + * Returns the {@code SpriteClass} class associated with the specified animation id. + * @param animationId the animation id + * @return a class type compatible with the specified animation id. + * Returns {@code null} if no class could be determined. + */ + public static Class getSpriteDecoderClass(int animationId) + { + Class retVal = null; + + // Testing Infinity Animation range first + AnimationType animType = AnimationType.containsInfinityAnimations(animationId); + if (animType != null) { + retVal = typeAssociations.get(animType); + } + + // Testing regular ranges + if (retVal == null) { + for (final AnimationType type : AnimationType.values()) { + if (type.contains(animationId)) { + retVal = typeAssociations.get(type); + if (retVal != null) { + break; + } + } + } + } + + return retVal; + } + + /** + * Returns the {@code SpriteClass} class associated with the specified {@code AnimationType} enum. + * @param type the {@code AnimationType} + * @return the associated {@code SpriteClass} class object. Returns {@code null} if class could not be determined. + */ + public static Class getSpriteDecoderClass(AnimationType type) + { + return typeAssociations.get(type); + } + + /** + * Instances creates with this constructor are only suited for identification purposes. + * @param type the animation type + * @param animationId specific animation id + * @param sectionName INI section name for animation-specific data + * @param ini the INI file with creature animation attributes + * @throws Exception + */ + protected SpriteDecoder(AnimationType type, int animationId, IniMap ini) throws Exception + { + Objects.requireNonNull(type, "Animation type cannot be null"); + Objects.requireNonNull(ini, "No INI data available for animation id: " + animationId); + this.attributesMap = new TreeMap<>(); + this.directionMap = new EnumMap<>(Direction.class); + setAttribute(KEY_ANIMATION_TYPE, type); + setAttribute(KEY_ANIMATION_SECTION, type.getSectionName()); + this.creInfo = new CreatureInfo(this, SpriteUtils.getPseudoCre(animationId, null, null)); + this.ini = ini; + this.currentSequence = Sequence.NONE; + init(); + if (!isMatchingAnimationType()) { + throw new IllegalArgumentException("Animation id is incompatible with animation type: " + type.toString()); + } + } + + /** + * This constructor creates an instance that can be used to render animation sequences. + * @param type the animation type + * @param cre the CRE resource instance. + * @throws Exception + */ + protected SpriteDecoder(AnimationType type, CreResource cre) throws Exception + { + Objects.requireNonNull(type, "Animation type cannot be null"); + this.attributesMap = new TreeMap<>(); + this.directionMap = new EnumMap<>(Direction.class); + setAttribute(KEY_ANIMATION_TYPE, type); + setAttribute(KEY_ANIMATION_SECTION, type.getSectionName()); + this.creInfo = new CreatureInfo(this, cre); + this.ini = Objects.requireNonNull(getAnimationInfo(getAnimationId()), "No INI data available for animation id: " + getAnimationId()); + this.currentSequence = Sequence.NONE; + this.showCircle = false; + this.showPersonalSpace = false; + this.showBoundingBox = false; + this.renderSpriteAvatar = true; + this.renderSpriteWeapon = true; + this.renderSpriteShield = true; + this.renderSpriteHelmet = true; + this.autoApplyChanges = true; + SpriteUtils.updateRandomPool(); + init(); + } + + /** + * Returns the data associated with the specified attribute name. + * @param key the attribute name. + * @return attribute data in the type inferred from the method call. + * Returns {@code null} if data is not available for the inferred type. + */ + @SuppressWarnings("unchecked") + public T getAttribute(DecoderAttribute att) + { + T retVal = null; + if (att == null) { + return retVal; + } + + Object data = attributesMap.getOrDefault(att, att.getDefaultValue()); + if (data != null) { + try { + retVal = (T)data; + } catch (ClassCastException e) { + // e.printStackTrace(); + } + } + return retVal; + } + + /** + * Stores the attribute key and value along with the autodetected data type. + * @param key the attribute name. + * @param value the value in one of the data types covered by {@link KeyType}. + */ + protected void setAttribute(DecoderAttribute att, Object value) + { + if (att == null) { + return; + } + attributesMap.put(att, value); + } + + /** Returns an iterator over the attribute keys. */ + protected Iterator getAttributeIterator() + { + return attributesMap.keySet().iterator(); + } + + /** Returns the type of the current creature animation. */ + public AnimationType getAnimationType() + { + return getAttribute(KEY_ANIMATION_TYPE); + } + + /** + * Returns the INI section name for the current animation type. + * Returns {@code null} if the name could not be determined. + */ + public String getAnimationSectionName() + { + return getAttribute(KEY_ANIMATION_SECTION); + } + + /** + * Returns a list of BAM filenames associated with the current animation type. + * @param essential if set returns only essential files required for the animation. + * @return list of BAM filenames associated with the current animation type. + * Returns {@code null} if files could not be determined. + */ + public abstract List getAnimationFiles(boolean essential); + + /** Recreates the creature animation based on the current creature resource. */ + public void reset() throws Exception + { + discard(); + // recreating current sequence + if (getCurrentSequence() != Sequence.NONE) { + createSequence(getCurrentSequence()); + } + } + + /** Removes the currently loaded animation sequence. */ + protected void discard() + { + frameClear(); + directionMap.clear(); + } + + /** + * Loads the specified sequence if available. Discards the currently active sequence. + * Call {@code reset()} instead to enforce reloading the same sequence with different + * creature attributes. + * @param seq the animation sequence to load. Specifying {@code Sequence.None} only discards the current sequence. + * @return whether the sequence was successfully loaded. + */ + public boolean loadSequence(Sequence seq) throws Exception + { + boolean retVal = true; + + if (getCurrentSequence() != Objects.requireNonNull(seq, "Animation sequence cannot be null")) { + // discarding current sequence + discard(); + + try { + createSequence(seq); + currentSequence = seq; + } catch (NullPointerException e) { + retVal = (seq != Sequence.NONE); + } catch (Exception e) { + e.printStackTrace(); + retVal = (seq != Sequence.NONE); + } + } + + return retVal; + } + + /** Returns the currently active sequence. */ + public Sequence getCurrentSequence() + { + return currentSequence; + } + + /** Returns whether the specified animation sequence is available for the current creature animation. */ + public abstract boolean isSequenceAvailable(Sequence seq); + + /** Provides access to the {@link CreatureInfo} instance associated with the sprite decoder. */ + public CreatureInfo getCreatureInfo() + { + return creInfo; + } + + /** Returns the {@code CreResource} instance of the current CRE resource. */ + public CreResource getCreResource() + { + return creInfo.getCreResource(); + } + + /** Returns the numeric animation id of the current CRE resource. */ + public int getAnimationId() + { + return creInfo.getAnimationId(); + } + + /** Returns a INI structure with creature animation info. */ + public IniMap getAnimationInfo() + { + return ini; + } + + /** Returns whether the selection circle for the creature is drawn. */ + public boolean isSelectionCircleEnabled() + { + return showCircle; + } + + /** Sets whether the selection circle for the creature is drawn. */ + public void setSelectionCircleEnabled(boolean b) + { + if (showCircle != b) { + showCircle = b; + selectionCircleChanged(); + } + } + + /** Returns whether the space occupied by the creature is visualized. */ + public boolean isPersonalSpaceVisible() + { + return showPersonalSpace; + } + + /** Sets whether the space occupied by the creature is visualized. */ + public void setPersonalSpaceVisible(boolean b) + { + if (showPersonalSpace != b) { + showPersonalSpace = b; + personalSpaceChanged(); + } + } + + /** Returns whether a bounding box is drawn around sprites (or quadrants) and secondary overlays. */ + public boolean isBoundingBoxVisible() + { + return showBoundingBox; + } + + /** Sets whether a bounding box is drawn around sprites (or quadrants) and secondary overlays. */ + public void setBoundingBoxVisible(boolean b) + { + if (showBoundingBox != b) { + showBoundingBox = b; + spriteChanged(); + } + } + + /** Returns whether the avatar sprite should be rendered. */ + public boolean getRenderAvatar() + { + return renderSpriteAvatar; + } + + /** Sets whether the avatar sprite should be rendered. */ + public void setRenderAvatar(boolean b) + { + if (renderSpriteAvatar != b) { + renderSpriteAvatar = b; + spriteChanged(); + } + } + + /** Returns whether the weapon overlay should be rendered. This option affects only specific animation types. */ + public boolean getRenderWeapon() + { + return renderSpriteWeapon; + } + + /** Sets whether the weapon overlay should be rendered. This option affects only specific animation types. */ + public void setRenderWeapon(boolean b) + { + if (renderSpriteWeapon != b) { + renderSpriteWeapon = b; + spriteChanged(); + } + } + + /** + * Returns whether the shield (or left-handed weapon) overlay should be rendered. + * This option affects only specific animation types. + */ + public boolean getRenderShield() + { + return renderSpriteShield; + } + + /** + * Sets whether the shield (or left-handed weapon) overlay should be rendered. + * This option affects only specific animation types. + */ + public void setRenderShield(boolean b) + { + if (renderSpriteShield != b) { + renderSpriteShield = b; + spriteChanged(); + } + } + + /** Returns whether the helmet overlay should be rendered. This option affects only specific animation types. */ + public boolean getRenderHelmet() + { + return renderSpriteHelmet; + } + + /** Sets whether the helmet overlay should be rendered. This option affects only specific animation types. */ + public void setRenderHelmet(boolean b) + { + if (renderSpriteHelmet != b) { + renderSpriteHelmet = b; + spriteChanged(); + } + } + + + /** Returns the moving speed of the creature animation. */ + public double getMoveScale() + { + return getAttribute(KEY_MOVE_SCALE); + } + + /** Sets the moving speed of the creature animation. */ + protected void setMoveScale(double value) + { + setAttribute(KEY_MOVE_SCALE, value); + } + + /** Returns the selection circle size of the creature animation. */ + public int getEllipse() + { + return getAttribute(KEY_ELLIPSE); + } + + /** Sets the selection circle size of the creature animation. */ + public void setEllipse(int value) + { + if (getEllipse() != value) { + setAttribute(KEY_ELLIPSE, value); + selectionCircleChanged(); + } + } + + /** Returns the map space (in search map units) reserved exclusively for the creature animation*/ + public int getPersonalSpace() + { + return getAttribute(KEY_PERSONAL_SPACE); + } + + /** Sets the map space (in search map units) reserved exclusively for the creature animation*/ + public void setPersonalSpace(int value) + { + if (getPersonalSpace() != value) { + setAttribute(KEY_PERSONAL_SPACE, value); + personalSpaceChanged(); + } + } + + /** Returns the resref (prefix) for the associated animation files. */ + public String getAnimationResref() + { + return getAttribute(KEY_RESREF); + } + + /** Sets the resref (prefix) for the associated animation files. */ + protected void setAnimationResref(String resref) + { + setAttribute(KEY_RESREF, resref); + } + + /** Returns the replacement palette for the creature animation. Returns empty string if no replacement palette exists. */ + public String getNewPalette() + { + return getAttribute(KEY_NEW_PALETTE); + } + + /** Sets the replacement palette for the creature animation. */ + public void setNewPalette(String resref) + { + resref = (resref != null) ? resref.trim() : ""; + if (!getNewPalette().equalsIgnoreCase(resref)) { + setAttribute(KEY_NEW_PALETTE, resref); + paletteChanged(); + } + } + + /** Loads the replacement palette associated with the specified BAM resource. */ + protected int[] getNewPaletteData(ResourceEntry bamRes) + { + // Note: method argument is irrelevant for base implementation + return SpriteUtils.loadReplacementPalette(getNewPalette()); + } + + /** ??? */ + public boolean isBrightest() + { + return getAttribute(KEY_BRIGHTEST); + } + + /** ??? */ + protected void setBrightest(boolean b) + { + setAttribute(KEY_BRIGHTEST, b); + } + + /** Returns whether blending mode is enabled. */ + public boolean isMultiplyBlend() + { + return getAttribute(KEY_MULTIPLY_BLEND); + } + + /** Sets blending mode. */ + protected void setMultiplyBlend(boolean b) + { + setAttribute(KEY_MULTIPLY_BLEND, b); + } + + /** ??? */ + public boolean isLightSource() + { + return getAttribute(KEY_LIGHT_SOURCE); + } + + /** ??? */ + protected void setLightSource(boolean b) + { + setAttribute(KEY_LIGHT_SOURCE, b); + } + + /** Returns whether a red tint is applied to the creature if detected by infravision. */ + public boolean isDetectedByInfravision() + { + return getAttribute(KEY_DETECTED_BY_INFRAVISION); + } + + /** Sets whether a red tint is applied to the creature if detected by infravision. */ + protected void setDetectedByInfravision(boolean b) + { + setAttribute(KEY_DETECTED_BY_INFRAVISION, b); + } + + /** Returns whether palette range replacement is enabled. */ + public boolean isFalseColor() + { + return getAttribute(KEY_FALSE_COLOR); + } + + /** Sets whether palette range replacement is enabled. */ + protected void setFalseColor(boolean b) + { + setAttribute(KEY_FALSE_COLOR, b); + } + + /** Returns whether creature animation is translucent. */ + public boolean isTranslucent() + { + return getCreatureInfo().getTranslucency() > 0; + } + + /** Sets whether creature animation is translucent. */ + protected void setTranslucent(boolean b) + { + setAttribute(KEY_TRANSLUCENT, b); + } + + /** Call this method whenever the visibility of the selection circle has been changed. */ + public void selectionCircleChanged() + { + setAnimationChanged(); + } + + /** Call this method whenever the visibility of personal space has been changed. */ + public void personalSpaceChanged() + { + setAnimationChanged(); + } + + /** Call this method whenever the visibility of any sprite types has been changed. */ + public void spriteChanged() + { + setAnimationChanged(); + } + + /** Call this method whenever the allegiance value has been changed. */ + public void allegianceChanged() + { + setAnimationChanged(); + } + + /** + * Call this method whenever the creature palette has changed. + * False color is processed by {@link #falseColorChanged()}. + */ + public void paletteChanged() + { + // Note: PST false color palette may also contain true color regions. + if (!isFalseColor() || + Profile.getGame() == Profile.Game.PSTEE || + Profile.getEngine() == Profile.Engine.PST) { + setAnimationChanged(); + } + } + + /** + * Call this method whenever the false color palette of the creature has changed. + * Conventional palette changes are processed by {@link #paletteChanged()}. */ + public void falseColorChanged() + { + if (isFalseColor()) { + setAnimationChanged(); + } + } + + /** This method reloads the creature animation if any relevant changes have been made. */ + public void applyAnimationChanges() + { + if (hasAnimationChanged()) { + resetAnimationChanged(); + try { + reset(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + /** Returns whether changes to the creature animation should be applied immediately. */ + public boolean isAutoApplyChanges() + { + return autoApplyChanges; + } + + /** Sets whether changes to the creature animation should be applied immediately. */ + public void setAutoApplyChanges(boolean b) + { + if (autoApplyChanges != b) { + autoApplyChanges = b; + if (autoApplyChanges) { + applyAnimationChanges(); + } + } + } + + /** + * Returns whether a creature animation reload has been requested. + * Call {@link #applyPendingChanges()} to apply the changes. + */ + public boolean hasAnimationChanged() + { + return animationChanged; + } + + /** Call to request a creature animation reload by the method {@link #applyPendingChanges()}. */ + public void setAnimationChanged() + { + animationChanged = true; + if (isAutoApplyChanges()) { + applyAnimationChanges(); + } + } + + /** Call to cancel the request of a creature animation reload by the method {@link #applyPendingChanges()}. */ + public void resetAnimationChanged() + { + animationChanged = false; + } + + // TODO: uncommment when Composite is implemented + /** + * Returns the preferred compositor for rendering the sprite on the target surface. + */ +// public Composite getBlendingComposite() +// { +// Composite retVal; +// int blending = ((isBrightest() ? 1 : 0) << 0) | +// ((isMultiplyBlend() ? 1 : 0) << 1); +// switch (blending) { +// case 1: // brightest +// retVal = BlendingComposite.Brightest; +// break; +// case 2: // multiply +// retVal = BlendingComposite.Multiply; +// break; +// case 3: // brightest + multiply +// retVal = BlendingComposite.BrightestMultiply; +// break; +// default: +// retVal = BlendingComposite.SrcOver; +// } +// return retVal; +// } + + /** Creates the BAM structure for the creature animation. */ + protected abstract void init() throws Exception; + + /** + * Initializes general data for the creature animation. + * @param ini The INI map containing creature animation data. + */ + protected void initDefaults(IniMap ini) throws Exception + { + IniMapSection section = getGeneralIniSection(Objects.requireNonNull(ini, "INI object cannot be null")); + Objects.requireNonNull(section.getAsString("animation_type"), "animation_type required"); + Misc.requireCondition(getAnimationType().contains(getAnimationId()), + String.format("Animation slot (%04X) is not compatible with animation type (%s)", + getAnimationId(), getAnimationType().toString())); + + setMoveScale(section.getAsDouble("move_scale", 0.0)); + setEllipse(section.getAsInteger("ellipse", 16)); + setPersonalSpace(section.getAsInteger("personal_space", 3)); + setBrightest(section.getAsInteger("brightest", 0) != 0); + setMultiplyBlend(section.getAsInteger("multiply_blend", 0) != 0); + setLightSource(section.getAsInteger("light_source", 0) != 0); + + String s = section.getAsString("new_palette", ""); + setNewPalette(s); + + // getting first available "resref" definition + for (Iterator iter = getAnimationInfo().iterator(); iter.hasNext(); ) { + section = iter.next(); + s = section.getAsString("resref", ""); + if (!s.isEmpty()) { + setAnimationResref(s); + break; + } + } + Misc.requireCondition(!getAnimationResref().isEmpty(), "Animation resource prefix required"); + } + + /** + * Returns the general INI map section defined for all supported creature animation types from the specified + * {@code IniMap} instance. Returns an empty {@code IniMapSection} instance if section could not be determined. + */ + protected IniMapSection getGeneralIniSection(IniMap ini) + { + final String sectionName = "general"; + IniMapSection retVal = null; + if (ini != null) { + retVal = ini.getSection(sectionName); + } + + if (retVal == null) { + retVal = new IniMapSection(sectionName, 0, null); + } + + return retVal; + } + + /** + * Returns the INI map section responsible for animation-type-specific attributes. + * Returns an empty {@code IniMapSection} instance if section could not be determined. + */ + protected IniMapSection getSpecificIniSection() + { + IniMapSection retVal = null; + IniMap ini = getAnimationInfo(); + if (ini != null) { + retVal = ini.getSection(getAnimationSectionName()); + } + + if (retVal == null) { + retVal = new IniMapSection(getAnimationSectionName(), 0, null); + } + + return retVal; + } + + /** + * Returns the BAM cycle associated with the specified direction. + * Returns -1 if entry not found. + */ + public int getCycleIndex(Direction dir) + { + int retVal = -1; + Integer value = directionMap.get(dir); + if (value != null) { + retVal = value.intValue(); + } + + return retVal; + } + + /** + * Returns a copy of the map containing associations between animation directions and bam sequence numbers. + */ + public EnumMap getDirectionMap() + { + return directionMap.clone(); + } + + /** + * Assigns a cycle index to the specified BAM sequence and direction. + * @param seq the sequence type for identification purposes. + * @param dir the direction type + * @param cycleIndex the cycle index associated with the specified sequence and direction. + * @return The previous BAM cycle index if available. -1 otherwise. + */ + protected int addDirection(Direction dir, int cycleIndex) + { + int retVal = -1; + dir = Objects.requireNonNull(dir, "Creature direction required"); + Integer value = directionMap.get(dir); + if (value != null) { + retVal = value.intValue(); + } + directionMap.put(dir, cycleIndex); + + return retVal; + } + + /** + * Generates definitions for the specified animation sequence. + * @param seq the requested animation sequence. + * @return a fully initialized {@code SeqDef} object if sequence is supported, {@code null} otherwise. + */ + protected abstract SeqDef getSequenceDefinition(Sequence seq); + + /** + * Loads the specified animation sequence into the SpriteDecoder. + * @param seq The sequence to load. + * @throws NullPointerException if specified sequence is not available. + */ + protected void createSequence(Sequence seq) throws Exception + { + SeqDef sd = Objects.requireNonNull(getSequenceDefinition(seq), "Sequence not available: " + (seq != null ? seq : "(null)")); + createAnimation(sd, FN_BEFORE_SRC_BAM, FN_BEFORE_SRC_FRAME, FN_AFTER_DST_FRAME); + } + + protected void createAnimation(SeqDef definition, + BiConsumer beforeSrcBam, + Function beforeSrcImage, + BiConsumer afterDstFrame) + { + PseudoBamControl dstCtrl = createControl(); + BamV1Control srcCtrl = null; + ResourceEntry entry = null; + definition = Objects.requireNonNull(definition, "Sequence definition cannot be null"); + + for (final DirDef dd : definition.getDirections()) { + CycleDef cd = dd.getCycle(); + int cycleIndex = dstCtrl.cycleAdd(); + addDirection(dd.getDirection(), cycleIndex); + + cd.reset(); + int frameCount = cd.getMaximumFrames(); + final ArrayList frameInfo = new ArrayList<>(); + for (int frame = 0; frame < frameCount; frame++) { + frameInfo.clear(); + for (final SegmentDef sd : cd.getCycles()) { + // checking visibility of sprite types + boolean skip = (sd.getSpriteType() == SegmentDef.SpriteType.AVATAR) && !getRenderAvatar(); + skip |= (sd.getSpriteType() == SegmentDef.SpriteType.WEAPON) && !getRenderWeapon(); + skip |= (sd.getSpriteType() == SegmentDef.SpriteType.SHIELD) && !getRenderShield(); + skip |= (sd.getSpriteType() == SegmentDef.SpriteType.HELMET) && !getRenderHelmet(); + if (skip) { + continue; + } + + entry = sd.getEntry(); + srcCtrl = Objects.requireNonNull(SpriteUtils.loadBamController(entry)); + srcCtrl.cycleSet(sd.getCycleIndex()); + + if (sd.getCurrentFrame() >= 0) { + if (beforeSrcBam != null) { + beforeSrcBam.accept(srcCtrl, sd); + } + frameInfo.add(new FrameInfo(srcCtrl, sd)); + } + sd.advance(); + } + + int frameIndex = createFrame(frameInfo.toArray(new FrameInfo[frameInfo.size()]), beforeSrcImage); + if (afterDstFrame != null) { + afterDstFrame.accept(dd, Integer.valueOf(frameIndex)); + } + dstCtrl.cycleAddFrames(cycleIndex, new int[] {frameIndex}); + } + } + } + + /** + * Creates a single creature animation frame from the given array of source frame segments + * and adds it to the BAM frame list. Each source frame segment can be processed by the specified lambda function + * before it is drawn onto to the target frame. + * @param sourceFrames array of source frame segments to compose. + * @param beforeSrcImage optional function to be executed before a source frame segment is drawn onto the + * target frame. It is passed the {@code BufferedImage} object of the source frame segment. + * @return the absolute target BAM frame index. + */ + protected int createFrame(FrameInfo[] sourceFrames, Function beforeSrcImage) + { + Rectangle rect; + if (Objects.requireNonNull(sourceFrames, "Source frame info objects required").length > 0) { + rect = getTotalFrameDimension(sourceFrames); + } else { + rect = new Rectangle(0, 0, 1, 1); + } + + // include personal space region in image size + if (isPersonalSpaceVisible()) { + rect = updateFrameDimension(rect, getPersonalSpaceSize(true)); + } + + // include selection circle in image size + float circleStrokeSize = (float)(Math.floor(Math.sqrt(getEllipse()) / 2.0)); // thickness relative to circle size + if (isSelectionCircleEnabled()) { + Dimension dim = getSelectionCircleSize(); + rect = updateFrameDimension(rect, new Dimension(2 * (dim.width + (int)circleStrokeSize), + 2 * (dim.height + (int)circleStrokeSize))); + } + + // creating target image + BufferedImage image; + if (rect.width > 0 && rect.height > 0) { + image = ColorConvert.createCompatibleImage(rect.width, rect.height, Transparency.TRANSLUCENT); + Graphics2D g = image.createGraphics(); + try { + g.setComposite(AlphaComposite.SrcOver); + g.setColor(new Color(0, true)); + g.fillRect(0, 0, image.getWidth(), image.getHeight()); + + Point center = new Point(-rect.x, -rect.y); + + if (isPersonalSpaceVisible()) { + // Drawing personal space region + drawPersonalSpace(g, center, null, 0.5f); + } + + if (isSelectionCircleEnabled()) { + // Drawing selection circle + drawSelectionCircle(g, center, null, circleStrokeSize); + } + + // drawing source frames to target image + for (final FrameInfo fi : sourceFrames) { + BamV1Control ctrl = fi.getController(); + ctrl.cycleSet(fi.getCycle()); + int frameIdx = fi.getFrame(); + ctrl.cycleSetFrameIndex(frameIdx); + BufferedImage srcImage = (BufferedImage)ctrl.cycleGetFrame(); + if (beforeSrcImage != null) { + srcImage = beforeSrcImage.apply(srcImage); + } + FrameEntry entry = ctrl.getDecoder().getFrameInfo(ctrl.cycleGetFrameIndexAbsolute()); + int x = -rect.x - entry.getCenterX(); + int y = -rect.y - entry.getCenterY(); + + if (isBoundingBoxVisible() && entry.getWidth() > 2 && entry.getHeight() > 2) { + // drawing bounding box around sprite elements + Stroke oldStroke = g.getStroke(); + Color oldColor = g.getColor(); + Object oldHints = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING); + try { + g.setStroke(FrameInfo.STROKE_BOUNDING_BOX); + g.setColor(FrameInfo.SPRITE_COLOR.getOrDefault(fi.getSegmentDefinition().getSpriteType(), FrameInfo.SPRITE_COLOR_DEFAULT)); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); + g.drawRect(x, y, entry.getWidth() - 1, entry.getHeight() - 1); + } finally { + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, (oldHints != null) ? oldHints : RenderingHints.VALUE_ANTIALIAS_DEFAULT); + if (oldColor != null) { + g.setColor(oldColor); + } + if (oldStroke != null) { + g.setStroke(oldStroke); + } + } + } + g.drawImage(srcImage, x, y, entry.getWidth(), entry.getHeight(), null); + ctrl = null; + } + } finally { + g.dispose(); + g = null; + } + } else { + // dummy graphics + image = ColorConvert.createCompatibleImage(1, 1, Transparency.TRANSLUCENT); + } + + // setting center point + int cx = -rect.x; + int cy = -rect.y; + + return frameAdd(image, new Point(cx, cy)); + } + + /** + * Calculates the total size of the personal space region. + * @param scaled whether dimension should be scaled according to search map unit size. + */ + protected Dimension getPersonalSpaceSize(boolean scaled) + { + int size = (Math.max(1, getPersonalSpace()) - 1) | 1; + if (scaled) { + return new Dimension(size * 16, size * 12); + } else { + return new Dimension(size, size); + } + } + + /** + * Draws the personal space region onto the specified graphics object. + * @param g the {@code Graphics2D} instance of the image. + * @param center center position of the personal space. + * @param color the fill color of the drawn region. Specify {@code null} to use a default color. + * @param alpha alpha transparency in range [0.0, 1.0] where 0.0 is fully transparent (invisible) and 1.0 is fully opaque. + */ + protected void drawPersonalSpace(Graphics2D g, Point center, Color color, float alpha) + { + if (g != null) { + BufferedImage image = createPersonalSpace(color, alpha); + g.drawImage(image, center.x - (image.getWidth() / 2), center.y - (image.getHeight() / 2), null); + } + } + + /** Creates a bitmap with the personal space tiles. */ + private BufferedImage createPersonalSpace(Color color, float alpha) + { + // preparations + if (color == null) { + color = new Color(224, 0, 224); + } + alpha = Math.max(0.0f, Math.min(1.0f, alpha)); // clamping alpha to [0.0, 1.0] + color = new Color(color.getRed(), color.getGreen(), color.getBlue(), (int)(255 * alpha)); + + // creating personal space pattern (unscaled) + Dimension dim = getPersonalSpaceSize(false); + BufferedImage image = new BufferedImage(dim.width, dim.height, BufferedImage.TYPE_INT_ARGB); + int[] bitmap = ((DataBufferInt)image.getRaster().getDataBuffer()).getData(); + int cx = dim.width / 2; + int cy = dim.height / 2; + int c = color.getRGB(); + int maxDist = dim.width * dim.width / 4; + for (int y = 0; y < dim.height; y++) { + for (int x = 0; x < dim.width; x++) { + int ofs = y * dim.width + x; + int dx = (cx - x) * (cx - x); + int dy = (cy - y) * (cy - y); + if (dx + dy < maxDist) { + bitmap[ofs] = c; + } + } + } + + // scaling up to search map unit size + dim = getPersonalSpaceSize(true); + BufferedImage retVal = new BufferedImage(dim.width, dim.height, image.getType()); + Graphics2D g = retVal.createGraphics(); + try { + g.setComposite(AlphaComposite.Src); + Object oldHints = g.getRenderingHint(RenderingHints.KEY_INTERPOLATION); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); + g.drawImage(image, 0, 0, dim.width, dim.height, null); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, + (oldHints != null) ? oldHints : RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); + } finally { + g.dispose(); + g = null; + } + + return retVal; + } + + /** Calculates the horizontal and vertical radius of the selection circle (ellipse). */ + protected Dimension getSelectionCircleSize() + { + Dimension dim = new Dimension(); + dim.width = Math.max(0, getEllipse()); + dim.height = dim.width * 4 / 7; // ratio 1.75 + if (dim.height % 7 > 3) { + // rounding up + dim.height++; + } + return dim; + } + + /** + * Draws a selection circle onto the specified graphics object. + * @param g the {@code Graphics2D} instance of the image. + * @param center center position of the circle. + * @param color the circle color. Specify {@code null} to use global defaults. + * @param strokeSize the thickness of the selection circle. + */ + protected void drawSelectionCircle(Graphics2D g, Point center, Color color, float strokeSize) + { + if (g != null) { + Dimension dim = getSelectionCircleSize(); + if (color == null) { + color = getAllegianceColor(getCreatureInfo().getAllegiance()); + } + Object oldHints = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setColor(color); + g.setStroke(new BasicStroke(strokeSize)); + g.drawOval(center.x - dim.width, center.y - dim.height, 2 * dim.width, 2 * dim.height); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, (oldHints != null) ? oldHints : RenderingHints.VALUE_ANTIALIAS_DEFAULT); + } + } + + /** + * Translates the specified color location index into a palette color offset. + * @param locationIndex the location to translate. + * @return the resulting palette color offset. Returns -1 if location is not supported. + */ + protected int getColorOffset(int locationIndex) + { + int retVal = -1; + if (locationIndex >= 0 && locationIndex < 7) { + retVal = 4 + locationIndex * 12; + } + return retVal; + } + + /** + * Returns the palette data for the specified color entry. + * @param colorIndex the color entry. + * @return palette data as int array. Returns {@code null} if palette data could not be determined. + */ + protected int[] getColorData(int colorIndex) + { + int[] retVal = null; + try { + retVal = SpriteUtils.getColorGradient(getAnimationType(), colorIndex, true); + } catch (Exception e) { + e.printStackTrace(); + } + return retVal; + } + + /** + * Replaces false colors with color ranges defined in the associated CRE resource. + * @param control the BAM controller. + */ + protected void applyFalseColors(BamV1Control control, SegmentDef sd) + { + if (control == null || sd == null) { + return; + } + + // preparations + final Map colorRanges = new HashMap(); + for (int loc = 0; loc < 7; loc++) { + int ofs = getColorOffset(loc); + int col = getCreatureInfo().getEffectiveColorValue(sd.getSpriteType(), loc); + if (ofs > 0 && col >= 0) { + int[] range = getColorData(col); + if (range != null) { + colorRanges.put(ofs, range); + } + } + } + + // applying colors + int[] palette = control.getCurrentPalette(); + for (final Integer ofs : colorRanges.keySet()) { + // replacing base ranges + final int[] range = colorRanges.get(ofs); + palette = SpriteUtils.replaceColors(palette, range, ofs.intValue(), range.length, false); + } + + if (getAnimationType() != AnimationType.MONSTER_PLANESCAPE) { + // preparing offset array + final int srcOfs = 4; + final int dstOfs = 88; + final int srcLen = 12; + final int dstLen = 8; + final int[] offsets = new int[colorRanges.size()]; + for (int i = 0; i < offsets.length; i++) { + offsets[i] = srcOfs + i * srcLen; + } + + // calculating mixed ranges + int k = 0; + for (int i = 0; i < offsets.length - 1; i++) { + int ofs1 = offsets[i]; + for (int j = i + 1; j < offsets.length; j++, k++) { + int ofs2 = offsets[j]; + int ofs3 = dstOfs + k * dstLen; + palette = SpriteUtils.interpolateColors(palette, ofs1, ofs2, srcLen, ofs3, dstLen, false); + } + } + } + + control.setExternalPalette(palette); + } + + /** + * The specified frame is mirrored horizontally. Both pixel data and center point are adjusted. + * @param frameIndex absolute frame index in the BAM frame list. + */ + protected void flipImageHorizontal(int frameIndex) + { + PseudoBamFrameEntry frame = getFrameInfo(frameIndex); + // flipping image horizontally + BufferedImage image = frame.getFrame(); + AffineTransform at = AffineTransform.getScaleInstance(-1, 1); + at.translate(-image.getWidth(), 0); + AffineTransformOp op = new AffineTransformOp(at, AffineTransformOp.TYPE_NEAREST_NEIGHBOR); + image = op.filter(image, null); + // updating frame data + frame.setFrame(image); + frame.setCenterX(frame.getWidth() - frame.getCenterX() - 1); + } + + /** + * Applies translucency to the specified paletted image. + * @param control the BAM controller. + */ + protected void applyTranslucency(BamV1Control control) + { + if (control != null) { + int alpha = getCreatureInfo().getTranslucency() << 24; + int[] palette = control.getCurrentPalette(); + for (int i = 2; i < palette.length; i++) { + palette[i] = alpha | (palette[i] & 0x00ffffff); + } + control.setExternalPalette(palette); + } + } + + /** + * Returns whether the current CRE resource provides an animation that is compatible with the + * {@code SpriteDecoder} class. + * @return {@code true} if the animation type of the CRE is compatible with this {@code SpriteDecoder} instance. + * Returns {@code false} otherwise. + */ + protected boolean isMatchingAnimationType() + { + boolean retVal = false; + + List names = getAnimationFiles(true); + if (!names.isEmpty()) { + retVal = names.parallelStream().allMatch(ResourceFactory::resourceExists); + } + + return retVal; + } + + /** + * A helper method for calculating a dimension that can contain all specified source frames. + * @param frames one or more source frames. + * @return A rectangle object where x and y indicate the top-left corner relative to the center point. + * Width and height specify frame dimension. + */ + protected static Rectangle getTotalFrameDimension(FrameInfo... frames) + { + Rectangle retVal = new Rectangle(); + + if (frames.length > 0) { + int left = Integer.MAX_VALUE, top = Integer.MAX_VALUE, right = Integer.MIN_VALUE, bottom = Integer.MIN_VALUE; + for (final FrameInfo fi : frames) { + BamV1Control ctrl = fi.getController(); + int frameIdx = fi.getFrame(); + frameIdx = ctrl.cycleGetFrameIndexAbsolute(fi.getCycle(), frameIdx); + FrameEntry entry = fi.getController().getDecoder().getFrameInfo(frameIdx); + left = Math.min(left, -entry.getCenterX()); + top = Math.min(top, -entry.getCenterY()); + right = Math.max(right, entry.getWidth() - entry.getCenterX()); + bottom = Math.max(bottom, entry.getHeight() - entry.getCenterY()); + } + + retVal.x = left; + retVal.y = top; + retVal.width = right - left; + retVal.height = bottom - top; + } + + return retVal; + } + + /** A helper method that expands the rectangle to fit the specified dimension. */ + protected static Rectangle updateFrameDimension(Rectangle rect, Dimension dim) + { + Rectangle retVal = new Rectangle(Objects.requireNonNull(rect, "Bounding box cannot be null")); + if (dim != null) { + int w2 = dim.width / 2; + int h2 = dim.height / 2; + int left = retVal.x; + int top = retVal.y; + int right = left + retVal.width; + int bottom = top + retVal.height; + left = Math.min(left, -w2); + top = Math.min(top, -h2); + right = Math.max(right, w2); + bottom = Math.max(bottom, h2); + retVal.x = left; + retVal.y = top; + retVal.width = right - left; + retVal.height = bottom - top; + } + return retVal; + } + + + /** + * Determines the right allegiance color for selection circles and returns it as {@code Color} object. + * @param value numeric allegiance value. + * @return a {@code Color} object with the associated allegiance color. + */ + protected static Color getAllegianceColor(int value) + { + Color c = null; + if (value >= 2 && value <= 4 || value == 201) { + // ally + c = new Color(0x00fa00, false); + } else if (value == 255 || value == 254 || value == 28 || value == 6 || value == 5) { + // enemy + c = new Color(0xff0020, false); + } else { + // neutral + c = new Color(0x40ffff, false); + } + + return c; + } + + /** + * A helper method that parses the specified data array and generates a list of INI lines + * related to the "general" section. + * @param data the String array containing data for a specific table entry. + * @param type the animation type. + * @return the initialized "general" INI section as list of strings. An empty list otherwise. + */ + protected static List processTableDataGeneral(String[] data, AnimationType type) + { + List retVal = new ArrayList<>(); + if (data == null || type == null) { + return retVal; + } + + int id = SpriteTables.valueToAnimationId(data, SpriteTables.COLUMN_ID, -1); + if (id < 0) { + return retVal; + } + int ellipse = SpriteTables.valueToInt(data, SpriteTables.COLUMN_ELLIPSE, 16); + int space = SpriteTables.valueToInt(data, SpriteTables.COLUMN_SPACE, 3); + int blending = SpriteTables.valueToInt(data, SpriteTables.COLUMN_BLENDING, 0); + String palette = SpriteTables.valueToString(data, SpriteTables.COLUMN_PALETTE, ""); + + int animIndex = SpriteTables.valueToInt(data, SpriteTables.COLUMN_TYPE, -1); + if (animIndex < 0 || animIndex >= AnimationType.values().length || AnimationType.values()[animIndex] != type) { + return retVal; + } + + int animType = -1; + for (int i = 0; i < type.getTypeCount(); i++) { + if (animType < 0 || (id & 0xf000) == type.getType(i)) { + animType = type.getType(i); + } + } + + retVal.add("[general]"); + retVal.add(String.format("animation_type=%04X", animType)); + retVal.add("ellipse=" + ellipse); + retVal.add("personal_space=" + space); + if ((blending & 1) == 1) { + retVal.add("brightest=1"); + } + if ((blending & 2) == 2) { + retVal.add("multiply_blend=1"); + } + if ((blending & 4) == 4) { + retVal.add("light_source=1"); + } + if (!palette.isEmpty()) { + retVal.add("new_palette=" + palette); + } + + return retVal; + } + + /** + * A helper method for PST animations that parses the specified data array and generates a list of INI lines + * related to the "general" section. + * @param data the String array containing data for a specific table entry. + * @return the initialized "general" INI section as list of strings. An empty list otherwise. + */ + protected static List processTableDataGeneralPst(String[] data) + { + List retVal = new ArrayList<>(); + if (data == null) { + return retVal; + } + + int id = SpriteTables.valueToInt(data, SpriteTables.COLUMN_ID, -1); + if (id < 0) { + return retVal; + } + int ellipse = SpriteTables.valueToInt(data, SpriteTables.COLUMN_PST_ELLIPSE, 16); + int space = SpriteTables.valueToInt(data, SpriteTables.COLUMN_PST_SPACE, 3); + + retVal.add("[general]"); + retVal.add("animation_type=F000"); + retVal.add("ellipse=" + ellipse); + retVal.add("personal_space=" + space); + + return retVal; + } + + /** + * Returns whether the specified {@code SpriteDecoder} class is compatible with the given animation id + * and any of the IniMap definitions. + */ + private static boolean isSpriteDecoderAvailable(Class spriteClass, int animationId, List iniList) + { + boolean retVal = false; + if (spriteClass == null || iniList == null) { + return retVal; + } + + try { + Constructor ctor = spriteClass.getConstructor(int.class, IniMap.class); + if (ctor != null) { + for (final IniMap ini : iniList) { + try { + retVal = (ctor.newInstance(animationId, ini).getClass() != null); + } catch (IllegalAccessException | InvocationTargetException | InstantiationException e) { + } + } + } + } catch (NoSuchMethodException e) { + } + + return retVal; + } + + /** + * Attempts to determine the animation type assigned to the specified creature. + * @return Class instance responsible for handling the detected animation type. {@code null} if type could not be determined. + */ + protected static Class detectAnimationType(int animationId) + { + Class retVal = null; + + List iniList = new ArrayList<>(); + iniList.addAll(getAnimationInfoByIni(animationId)); + + if (iniList.isEmpty()) { + iniList.addAll(getAnimationInfoByTable(animationId)); + } + + if (iniList.isEmpty()) { + iniList.addAll(getAnimationInfoByGuess(animationId)); + } + + if (!iniList.isEmpty()) { + // trying recommended sprite decoder class first + Class cls = getSpriteDecoderClass(animationId); + if (isSpriteDecoderAvailable(cls, animationId, iniList)) { + retVal = cls; + } + + if (retVal == null) { + // trying out all available sprite decoder classes otherwise + if (Profile.getGame() == Profile.Game.PST || Profile.getGame() == Profile.Game.PSTEE) { + if (isSpriteDecoderAvailable(MonsterPlanescapeDecoder.class, animationId, iniList)) { + retVal = cls; + } + } else { + for (final AnimationType type : AnimationType.values()) { + if (type != AnimationType.MONSTER_PLANESCAPE) { + cls = typeAssociations.get(type); + if (isSpriteDecoderAvailable(cls, animationId, iniList)) { + retVal = cls; + break; + } + } + } + } + } + } + + return retVal; + } + + /** + * Returns creature animation info from an existing INI file. + * @param animationId the creature animation id + * @return an list of {@link IniMap} instances with potential creature animation data. + * Returns {@code null} if no matching INI was found. + */ + protected static List getAnimationInfoByIni(int animationId) + { + List retVal = new ArrayList<>(); + + animationId &= 0xffff; + String iniFile = String.format("%04X.INI", animationId); + if (ResourceFactory.resourceExists(iniFile)) { + retVal.add(new IniMap(ResourceFactory.getResourceEntry(iniFile), true)); + } + + return retVal; + } + + /** + * Returns creature animation info from hardcoded creature data. + * @param animationId the creature animation id + * @return an list of {@link IniMap} instance with potential creature animation data. + * Returns empty list if no creature data was found. + */ + protected static List getAnimationInfoByTable(int animationId) + { + return SpriteTables.createIniMaps(animationId & 0xffff); + } + + /** + * Returns creature animation info based on ANISND.2DA data and analyzing potential slot ranges. + * May return false positives. + * @param animationId the creature animation id + * @return a list of {@link IniMap} instances with potential creature animation data. + * Returns {@code null} if no potential match was found. + */ + protected static List getAnimationInfoByGuess(int animationId) + { + if (Profile.getGame() == Profile.Game.PST || Profile.getGame() == Profile.Game.PSTEE) { + return guessIniMapsPst(animationId); + } else { + return guessIniMaps(animationId); + } + } + + /** + * Returns creature animation info in INI format. Section and field format is based on the EE v2.0 INI format. + * The method will first look for existing INI data in the game resources. Failing that it will look up data in + * hardcoded tables and fill in missing data from associated 2DA file if available. Failing that it will guess + * the correct format based on animation type and available resources. + * @param animationId the 16-bit animation id. + * @return An IniMap structure containing necessary data for rendering creature animation. Returns {@code null} if no + * animation info could be assembled. + */ + protected static IniMap getAnimationInfo(int animationId) + { + List retVal = new ArrayList<>(); + + // 1. look up existing INI resource + retVal.addAll(getAnimationInfoByIni(animationId)); + + if (retVal.isEmpty()) { + // 2. look up hardcoded tables + retVal.addAll(getAnimationInfoByTable(animationId)); + } + + if (retVal.isEmpty()) { + // 3. guess animation info based on anisnd.2da entry and available sprite classes + retVal.addAll(getAnimationInfoByGuess(animationId)); + } + + if (!retVal.isEmpty()) { + return retVal.get(0); + } else { + return null; + } + } + + // Attempts to find potential non-PST-specific IniMap instances + private static List guessIniMaps(int animationId) + { + List retVal = new ArrayList<>(); + String resref = null; + String palette = null; + + // evaluate ANIMATE.SRC if available + ResourceEntry resEntry = ResourceFactory.getResourceEntry("ANIMATE.SRC"); + if (resEntry != null) { + IniMap anisrc = IniMapCache.get(resEntry); + if (anisrc != null) { + IniMapSection iniSection = anisrc.getUnnamedSection(); + if (iniSection != null) { + for (final Iterator iter = iniSection.iterator(); iter.hasNext(); ) { + IniMapEntry entry = iter.next(); + try { + String key = entry.getKey(); + int id = (key.startsWith("0x") || key.startsWith("0X")) ? Misc.toNumber(key.substring(2, key.length()), 16, -1) + : Misc.toNumber(key, -1); + if (id == animationId) { + String value = entry.getValue(); + if (id > 0x1000 && value.length() > 4) { + value = value.substring(0, 4); + } + resref = value; + break; + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + } + + if (resref == null) { + // evaluate ANISND.IDS if available + IdsMap anisnd = IdsMapCache.get("ANISND.IDS"); + if (anisnd != null) { + IdsMapEntry anisndEntry = anisnd.get(animationId); + if (anisndEntry != null) { + String[] elements = anisndEntry.getSymbol().split("\\s+"); + if (elements.length > 0 && elements[0].length() <= 8) { + resref = elements[0]; + int pos = resref.indexOf('_'); + if (pos > 0) { + // assuming underscore indicates a palette resref + palette = resref; + resref = resref.substring(0, pos); + } else if (animationId >= 0x1000 && resref.length() > 4) { + resref = resref.substring(0, 4); + } + } + } + } + } + + if (resref == null) { + return retVal; + } + + if (palette == null) { + palette = "*"; + } + + List tableEntries = new ArrayList<>(); + AnimationType type = AnimationType.typeOfId(animationId); + if (type == null) { + return retVal; + } + + ResourceEntry bamEntry; + switch (type) { + case EFFECT: + tableEntries.add(String.format("0x%04x %s 0 0 0 * %s * * * * * * * * *", animationId, resref, palette)); + break; + case MONSTER_QUADRANT: + if (ResourceFactory.resourceExists(resref + "G14.BAM")) { + tableEntries.add(String.format("0x%04x %s 1 32 5 * %s * * * * * * * * *", animationId, resref, palette)); + } + break; + case MONSTER_MULTI: + if (ResourceFactory.resourceExists(resref + "1908.BAM")) { + tableEntries.add(String.format("0x%04x %s 2 72 13 * %s * * * * 1 * * * *", animationId, resref, palette)); + } + break; + case MONSTER_MULTI_NEW: + if (ResourceFactory.resourceExists(resref + "G145.BAM")) { + tableEntries.add(String.format("0x%04x %s 2 32 5 * %s * * * * 1 * * * *", animationId, resref, palette)); + } else if (ResourceFactory.resourceExists(resref + "G1.BAM")) { + tableEntries.add(String.format("0x%04x %s 2 32 5 * %s * * * * 0 * * * *", animationId, resref, palette)); + } + break; + case MONSTER_LAYERED_SPELL: + resref = guessResourceRef(resref, "G1"); + bamEntry = ResourceFactory.getResourceEntry(resref + "G1.BAM"); + if (bamEntry != null) { + int falseColor = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; + tableEntries.add(String.format("0x%04x %s 4 16 3 * %s * * * %d * * * * *", animationId, resref, palette, falseColor)); + } + break; + case MONSTER_ANKHEG: + resref = guessResourceRef(resref, "DG1"); + bamEntry = ResourceFactory.getResourceEntry(resref + "DG1.BAM"); + if (bamEntry != null) { + tableEntries.add(String.format("0x%04x %s 6 24 5 * %s * * * * * * * * *", animationId, resref, palette)); + } + break; + case TOWN_STATIC: + resref = guessResourceRef(resref, ""); + bamEntry = ResourceFactory.getResourceEntry(resref + ".BAM"); + if (bamEntry != null) { + int falseColor = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; + tableEntries.add(String.format("0x%04x %s 7 16 3 * %s * * * %d * * * * *", animationId, resref, palette, falseColor)); + } + break; + case CHARACTER: + bamEntry = ResourceFactory.getResourceEntry(resref + "1G1.BAM"); + if (bamEntry != null) { + int falseColor = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; + int split = ResourceFactory.resourceExists(resref + "1G15.BAM") ? 1 : 0; + tableEntries.add(String.format("0x%04x %s 8 16 3 * * * * * %d %d 1 * * *", animationId, resref, falseColor, split)); + } + break; + case CHARACTER_OLD: + bamEntry = ResourceFactory.getResourceEntry(resref + "1G1.BAM"); + if (bamEntry != null) { + int falseColor = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; + tableEntries.add(String.format("0x%04x %s 9 16 3 * * * * * %d * * * * *", animationId, resref, falseColor)); + } + break; + case MONSTER: + resref = guessResourceRef(resref, "G1"); + bamEntry = ResourceFactory.getResourceEntry(resref + "G1.BAM"); + if (bamEntry != null) { + int split = ResourceFactory.resourceExists(resref + "G15.BAM") ? 1 : 0; + int falseColor = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; + tableEntries.add(String.format("0x%04x %s 10 16 3 * %s * * * %d %d * * * *", animationId, resref, palette, falseColor, split)); + tableEntries.add(String.format("0x%04x %s 11 16 3 * %s * * * %d * * * * *", animationId, resref, palette, falseColor)); + } + break; + case MONSTER_OLD: + resref = guessResourceRef(resref, "G1"); + bamEntry = ResourceFactory.getResourceEntry(resref + "G1.BAM"); + if (bamEntry != null) { + int falseColor = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; + tableEntries.add(String.format("0x%04x %s 11 16 3 * %s * * * %d * * * * *", animationId, resref, palette, falseColor)); + } + break; + case MONSTER_LAYERED: + resref = guessResourceRef(resref, "G1"); + bamEntry = ResourceFactory.getResourceEntry(resref + "G1.BAM"); + if (bamEntry != null) { + tableEntries.add(String.format("0x%04x %s 4 16 3 * * * * * * * * * * *", animationId, resref)); + } + break; + case MONSTER_LARGE: + resref = guessResourceRef(resref, "G1"); + bamEntry = ResourceFactory.getResourceEntry(resref + "G1.BAM"); + if (bamEntry != null) { + int falseColor = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; + tableEntries.add(String.format("0x%04x %s 12 16 3 * %s * * * %d * * * * *", animationId, resref, palette, falseColor)); + } + break; + case MONSTER_LARGE_16: + resref = guessResourceRef(resref, "G1"); + bamEntry = ResourceFactory.getResourceEntry(resref + "G1.BAM"); + if (bamEntry != null) { + int falseColor = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; + tableEntries.add(String.format("0x%04x %s 13 16 3 * %s * * * %d * * * * *", animationId, resref, palette, falseColor)); + } + break; + case AMBIENT_STATIC: + resref = guessResourceRef(resref, "G1"); + bamEntry = ResourceFactory.getResourceEntry(resref + "G1.BAM"); + if (bamEntry != null) { + int falseColor = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; + tableEntries.add(String.format("0x%04x %s 14 16 3 * %s * * * %d * * * * *", animationId, resref, palette, falseColor)); + } + break; + case AMBIENT: + resref = guessResourceRef(resref, "G1"); + bamEntry = ResourceFactory.getResourceEntry(resref + "G1.BAM"); + if (bamEntry != null) { + int falseColor = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; + tableEntries.add(String.format("0x%04x %s 15 16 3 * %s * * * %d * * * * *", animationId, resref, palette, falseColor)); + } + break; + case FLYING: + resref = guessResourceRef(resref, ""); + bamEntry = ResourceFactory.getResourceEntry(resref + ".BAM"); + if (bamEntry != null) { + int falseColor = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; + tableEntries.add(String.format("0x%04x %s 16 16 3 * %s * * * %d * * * * *", animationId, resref, palette, falseColor)); + } + break; + case MONSTER_ICEWIND: + { + boolean found = false; + while (resref.length() >= 4 && !found) { + for (final String suffix : new String[] { "A1", "A2", "A3", "A4", "CA", "DE", "GH", "GU", "SC", "SD", "SL", "SP", "TW", "WK" }) { + if (ResourceFactory.resourceExists(resref + suffix + ".BAM")) { + found = true; + break; + } + } + } + if (found) { + tableEntries.add(String.format("0x%04x %s 17 24 3 * %s * * * * * * * * *", animationId, resref, palette)); + } + break; + } + default: + } + + if (!tableEntries.isEmpty()) { + for (final String line : tableEntries) { + StringBuilder sb = new StringBuilder(); + sb.append("2DA V1.0").append('\n'); + sb.append("*").append('\n'); + sb.append(" RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD").append('\n'); + sb.append(line).append('\n'); + ResourceEntry entry = new BufferedResourceEntry(ByteBuffer.wrap(sb.toString().getBytes()), Integer.toString(animationId, 16) + ".2DA"); + Table2da table = new Table2da(entry); + retVal.addAll(SpriteTables.processTable(Profile.getGame(), table, animationId)); + } + } + + return retVal; + } + + // Helper method: attempts to find an existing resource with the specified name parts. + // Returns the resref of the matching resource. Returns the original resref otherwise. + private static String guessResourceRef(String resref, String suffix) + { + String retVal = resref; + if (retVal == null) { + return retVal; + } + + if (suffix == null) { + suffix = ""; + } + + while (retVal.length() >= 4) { + if (ResourceFactory.resourceExists(retVal + suffix + ".BAM")) { + return retVal; + } + retVal = retVal.substring(0, resref.length() - 1); + } + + return resref; + } + + // Attempts to find potential PST-specific IniMap instances + private static List guessIniMapsPst(int animationId) + { + List retVal = new ArrayList<>(); + String resref = null; + + IniMap resIni = IniMapCache.get("RESDATA.INI", true); + if (resIni == null) { + return retVal; + } + + // only regular animations are considered + int id = animationId & 0x0fff; + IniMapSection iniSection = resIni.getSection(Integer.toString(id)); + if (iniSection == null) { + iniSection = resIni.getSection("0x" + Integer.toString(id, 16)); + } + if (iniSection == null) { + return retVal; + } + + int clown = 0; + for (final Sequence seq : Sequence.values()) { + String cmd = MonsterPlanescapeDecoder.getActionCommand(seq); + if (cmd != null) { + String key = iniSection.getAsString(cmd); + if (key != null && key.length() >= 7) { + ResourceEntry bamEntry = ResourceFactory.getResourceEntry(key + "b.bam"); + if (bamEntry != null) { + clown = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; + resref = key.substring(0, 1) + key.substring(4, key.length()) + "b"; + break; + } + } + } + } + + if (resref != null) { + int armor = iniSection.getAsInteger("armor", 0); + int bestiary = iniSection.getAsInteger("bestiary", 0); + + StringBuilder sb = new StringBuilder(); + sb.append("2DA V1.0").append('\n'); + sb.append("*").append('\n'); + sb.append(" RESREF RESREF2 TYPE ELLIPSE SPACE CLOWN ARMOR BESTIARY").append('\n'); + sb.append(String.format("0x%04x %s * 18 16 3 %d %d %d", id, resref, clown, armor, bestiary)).append('\n'); + ResourceEntry entry = new BufferedResourceEntry(ByteBuffer.wrap(sb.toString().getBytes()), Integer.toString(animationId, 16) + ".2DA"); + Table2da table = new Table2da(entry); + retVal = SpriteTables.processTable(Profile.getGame(), table, animationId); + } + + return retVal; + } + + @Override + public int hashCode() + { + int hash = super.hashCode(); + hash = 31 * hash + ((creInfo == null) ? 0 : creInfo.hashCode()); + hash = 31 * hash + ((ini == null) ? 0 : ini.hashCode()); + hash = 31 * hash + ((directionMap == null) ? 0 : directionMap.hashCode()); + hash = 31 * hash + ((attributesMap == null) ? 0 : attributesMap.hashCode()); + hash = 31 * hash + ((currentSequence == null) ? 0 : currentSequence.hashCode()); + hash = 31 * hash + Boolean.valueOf(showCircle).hashCode(); + hash = 31 * hash + Boolean.valueOf(showPersonalSpace).hashCode(); + hash = 31 * hash + Boolean.valueOf(renderSpriteWeapon).hashCode(); + hash = 31 * hash + Boolean.valueOf(renderSpriteHelmet).hashCode(); + hash = 31 * hash + Boolean.valueOf(renderSpriteShield).hashCode(); + hash = 31 * hash + Boolean.valueOf(animationChanged).hashCode(); + hash = 31 * hash + Boolean.valueOf(autoApplyChanges).hashCode(); + return hash; + } + + @Override + public boolean equals(Object o) + { + if (!(o instanceof SpriteDecoder)) { + return false; + } + boolean retVal = super.equals(o); + if (retVal) { + SpriteDecoder other = (SpriteDecoder)o; + retVal &= (this.creInfo == null && other.creInfo == null) || + (this.creInfo != null && this.creInfo.equals(other.creInfo)); + retVal &= (this.ini == null && other.ini == null) || + (this.ini != null && this.ini.equals(other.ini)); + retVal &= (this.directionMap == null && other.directionMap == null) || + (this.directionMap != null && this.directionMap.equals(other.directionMap)); + retVal &= (this.attributesMap == null && other.attributesMap == null) || + (this.attributesMap != null && this.attributesMap.equals(other.attributesMap)); + retVal &= (this.currentSequence == null && other.currentSequence == null) || + (this.currentSequence != null && this.currentSequence.equals(other.currentSequence)); + retVal &= (this.showCircle == other.showCircle); + retVal &= (this.showPersonalSpace == other.showPersonalSpace); + retVal &= (this.renderSpriteWeapon == other.renderSpriteWeapon); + retVal &= (this.renderSpriteHelmet == other.renderSpriteHelmet); + retVal &= (this.renderSpriteShield == other.renderSpriteShield); + retVal &= (this.animationChanged == other.animationChanged); + retVal &= (this.autoApplyChanges == other.autoApplyChanges); + } + return retVal; + } + +//-------------------------- INNER CLASSES -------------------------- + + /** + * A helper class that allows you to define numeric ranges. + */ + public static class Range + { + private final List> ranges = new ArrayList<>(); + + /** + * Defines a range that starts at base and ends at base + range (inclusive). + * @param base start value of the range. + * @param range length of the range. + */ + public Range(int base, int range) + { + this(base, range, 0, 0, 0); + } + + /** + * Defines a set of common ranges. The resolved list of ranges can be defined as:

+ * base + ([subBase, subBase+subRange] << subPos) + [0, range]

+ * where [x, y] defines a range from x to y (inclusive). + * @param base start value of the range. + * @param range length of the range. + */ + public Range(int base, int range, int subBase, int subRange, int subPos) + { + init(base, range, subBase, subRange, subPos); + } + + /** Returns whether the range covers the specified value. */ + public boolean contains(int value) + { + return ranges + .parallelStream() + .anyMatch(c -> (value >= c.getValue0().intValue() && + value <= (c.getValue0().intValue() + c.getValue1().intValue()))); + } + + private void init(int base, int range, int subBase, int subRange, int subPos) + { + range = Math.abs(range); + subRange = Math.abs(subRange); + subPos = Math.max(0, Math.min(32, subPos)); + for (int i = 0; i <= subRange; i++) { + int curBase = base + ((subBase + i) << subPos); + ranges.add(Couple.with(curBase, range)); + } + } + } +} diff --git a/src/org/infinity/resource/cre/decoder/SpriteUtils.java b/src/org/infinity/resource/cre/decoder/SpriteUtils.java new file mode 100644 index 000000000..e29052052 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/SpriteUtils.java @@ -0,0 +1,1256 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Locale; +import java.util.Objects; +import java.util.Random; + +import org.infinity.datatype.IsNumeric; +import org.infinity.datatype.IsTextual; +import org.infinity.resource.AbstractStruct; +import org.infinity.resource.Profile; +import org.infinity.resource.ResourceFactory; +import org.infinity.resource.StructEntry; +import org.infinity.resource.cre.CreResource; +import org.infinity.resource.graphics.BamV1Decoder; +import org.infinity.resource.graphics.ColorConvert; +import org.infinity.resource.graphics.GraphicsResource; +import org.infinity.resource.graphics.BamV1Decoder.BamV1Control; +import org.infinity.resource.itm.Ability; +import org.infinity.resource.itm.ItmResource; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.Misc; +import org.infinity.util.Table2da; +import org.infinity.util.Table2daCache; +import org.infinity.util.io.StreamUtils; +import org.infinity.util.tuples.Couple; + +/** + * Collection of helpful methods for Sprite rendering. + */ +public class SpriteUtils +{ + /** Cache for structured game resources */ + private static final HashMap resourceCache = new HashMap<>(); + /** Cache for source BAM resources (decoder and attached controller). */ + private static final HashMap> bamCache = new HashMap<>(); + /** Cache for replacement palettes. */ + private static final HashMap paletteCache = new HashMap<>(); + /** Cache for color gradients. */ + private static final HashMap colorGradients = new HashMap<>(350); + /** Cache for randomized color gradients. */ + private static final HashMap randomGradientIndices = new HashMap<>(); + /** A random number generator for general use */ + private static final Random random = new Random(); + + /** A stable pool of random numbers. */ + private static int[] randomPool; + + /** Clears cached resources. */ + public static void clearCache() + { + resourceCache.clear(); + bamCache.clear(); + paletteCache.clear(); + colorGradients.clear(); + randomGradientIndices.clear(); + } + + /** + * Creates a pseudo CRE resource with the specified animation id and an optional set of creature colors. + * @param animationId The animation id for the CRE. + * @param colors Optional creature colors. Each entry uses the CRE_COLOR_xxx field names defined in {@link CreResource} + * associated with the numeric color value. + * @param equipment Optional equipment. Each entry uses inventory slot index (as defined in CRE) as key and + * item resref as value. + * These items are only used as source for overlay bams (i.e. weapons, shields, helmets). + * @return A {@code CreResource} instance with the virtual creature data. + */ + public static CreResource getPseudoCre(int animationId, HashMap colors, HashMap equipment) throws Exception + { + CreResource entry = null; + ByteBuffer buffer = null; + if ((Boolean)Profile.getProperty(Profile.Key.IS_SUPPORTED_CRE_V90)) { + // IWD + int sizeBase = 0x33c; + int slotsSize = 0x50; // item slots + buffer = StreamUtils.getByteBuffer(sizeBase + slotsSize); + buffer.position(0); + buffer.put("CRE V9.0".getBytes(Misc.CHARSET_ASCII)); + // creature colors + if (colors != null) { + for (final HashMap.Entry e : colors.entrySet()) { + switch (e.getKey()) { + case CreResource.CRE_COLOR_METAL: + buffer.put(0x2c, e.getValue().byteValue()); + break; + case CreResource.CRE_COLOR_MINOR: + buffer.put(0x2d, e.getValue().byteValue()); + break; + case CreResource.CRE_COLOR_MAJOR: + buffer.put(0x2e, e.getValue().byteValue()); + break; + case CreResource.CRE_COLOR_SKIN: + buffer.put(0x2f, e.getValue().byteValue()); + break; + case CreResource.CRE_COLOR_LEATHER: + buffer.put(0x30, e.getValue().byteValue()); + break; + case CreResource.CRE_COLOR_ARMOR: + buffer.put(0x31, e.getValue().byteValue()); + break; + case CreResource.CRE_COLOR_HAIR: + buffer.put(0x32, e.getValue().byteValue()); + break; + } + } + } + // Enemy-Ally + buffer.put(0x2d8, (byte)128); + // setting valid offsets + for (int ofs : new int[] {0x308, 0x310, 0x318, 0x320, 0x324, 0x32c}) { + buffer.putInt(ofs, sizeBase); + } + for (int i = 0; i < slotsSize - 2; i++) { + // marking item slots as empty + buffer.put(sizeBase + i, (byte)0xff); + } + } else if ((Boolean)Profile.getProperty(Profile.Key.IS_SUPPORTED_CRE_V22)) { + // IWD2 + int sizeBase = 0x62e; + int slotsSize = 0x68; // item slots + buffer = StreamUtils.getByteBuffer(sizeBase + slotsSize); + buffer.position(0); + buffer.put("CRE V2.2".getBytes(Misc.CHARSET_ASCII)); + // creature colors + if (colors != null) { + for (final HashMap.Entry e : colors.entrySet()) { + switch (e.getKey()) { + case CreResource.CRE_COLOR_METAL: + buffer.put(0x2c, e.getValue().byteValue()); + break; + case CreResource.CRE_COLOR_MINOR: + buffer.put(0x2d, e.getValue().byteValue()); + break; + case CreResource.CRE_COLOR_MAJOR: + buffer.put(0x2e, e.getValue().byteValue()); + break; + case CreResource.CRE_COLOR_SKIN: + buffer.put(0x2f, e.getValue().byteValue()); + break; + case CreResource.CRE_COLOR_LEATHER: + buffer.put(0x30, e.getValue().byteValue()); + break; + case CreResource.CRE_COLOR_ARMOR: + buffer.put(0x31, e.getValue().byteValue()); + break; + case CreResource.CRE_COLOR_HAIR: + buffer.put(0x32, e.getValue().byteValue()); + break; + } + } + } + // Enemy-Ally + buffer.put(0x384, (byte)128); + // setting valid offsets + for (int i = 0; i < 63; i++) { + buffer.putInt(0x3ba + (i * 4), sizeBase); + } + for (int i = 0; i < 9; i++) { + buffer.putInt(0x5b2+ (i * 4), sizeBase); + } + for (int ofs : new int[] {0x5fa, 0x602, 0x60a, 0x612, 0x616, 0x61e}) { + buffer.putInt(ofs, sizeBase); + } + for (int i = 0; i < slotsSize - 2; i++) { + // marking item slots as empty + buffer.put(sizeBase + i, (byte)0xff); + } + } else if ((Boolean)Profile.getProperty(Profile.Key.IS_SUPPORTED_CRE_V12)) { + // PST + int sizeBase = 0x378; + int slotsSize = 0x60; // item slots + buffer = StreamUtils.getByteBuffer(sizeBase + slotsSize); + buffer.position(0); + buffer.put("CRE V1.2".getBytes(Misc.CHARSET_ASCII)); + // creature colors + if (colors != null) { + for (final HashMap.Entry e : colors.entrySet()) { + for (int i = 0; i < 7; i++) { + String labelColor = String.format(CreResource.CRE_COLOR_FMT, i + 1); + String labelColorPlacement = String.format(CreResource.CRE_COLOR_PLACEMENT_FMT, i + 1); + if (labelColor.equals(e.getKey())) { + buffer.putShort(0x2e4 + (i * 2), e.getValue().shortValue()); + } else if (labelColorPlacement.equals(e.getKey())) { + buffer.put(0x2f5 + i, e.getValue().byteValue()); + } + } + } + } + // Enemy-Ally + buffer.put(0x314, (byte)128); + // setting valid offsets + for (int ofs : new int[] {0x344, 0x34c, 0x354, 0x35c, 0x360, 0x368}) { + buffer.putInt(ofs, sizeBase); + } + for (int i = 0; i < slotsSize - 2; i++) { + // marking item slots as empty + buffer.put(sizeBase + i, (byte)0xff); + } + } else { + // BG, BG2, EE + int sizeBase = 0x2d4; + int slotsSize = 0x50; // item slots + buffer = StreamUtils.getByteBuffer(sizeBase + slotsSize); + buffer.position(0); + buffer.put("CRE V1.0".getBytes(Misc.CHARSET_ASCII)); + // creature colors + if (colors != null) { + for (final HashMap.Entry e : colors.entrySet()) { + switch (e.getKey()) { + case CreResource.CRE_COLOR_METAL: + buffer.put(0x2c, e.getValue().byteValue()); + break; + case CreResource.CRE_COLOR_MINOR: + buffer.put(0x2d, e.getValue().byteValue()); + break; + case CreResource.CRE_COLOR_MAJOR: + buffer.put(0x2e, e.getValue().byteValue()); + break; + case CreResource.CRE_COLOR_SKIN: + buffer.put(0x2f, e.getValue().byteValue()); + break; + case CreResource.CRE_COLOR_LEATHER: + buffer.put(0x30, e.getValue().byteValue()); + break; + case CreResource.CRE_COLOR_ARMOR: + buffer.put(0x31, e.getValue().byteValue()); + break; + case CreResource.CRE_COLOR_HAIR: + buffer.put(0x32, e.getValue().byteValue()); + break; + } + } + } + // Enemy-Ally + buffer.put(0x270, (byte)128); + // setting valid offsets + for (int ofs : new int[] {0x2a0, 0x2a8, 0x2b0, 0x2b8, 0x2bc, 0x2c4}) { + buffer.putInt(ofs, sizeBase); + } + for (int i = 0; i < slotsSize - 2; i++) { + // marking item slots as empty + buffer.put(sizeBase + i, (byte)0xff); + } + } + + if (buffer != null) { + buffer.putInt(0x08, -1); // creature name + buffer.putInt(0x0c, -1); // creature tooltip + buffer.putInt(0x28, animationId); + if (equipment != null) { + for (final HashMap.Entry itm : equipment.entrySet()) { + addPseudoCreItem(buffer, itm.getKey().intValue(), itm.getValue()); + } + } + + entry = new CreResource(null, String.format("%04X", animationId & 0xffff), buffer, 0); + } + + return entry; + } + + /** + * Adds a new item to the pseudo CRE resource and assigns it to an item slot. + * @param buffer the source CRE buffer. + * @param slot slot to assign the new item. Specify negative value to skip. + * @param resref The item resref. + * @return the updated CRE buffer. + */ + private static ByteBuffer addPseudoCreItem(ByteBuffer buffer, int slot, String resref) + { + ByteBuffer outBuffer = buffer; + if (buffer == null || resref == null || resref.isEmpty()) { + return outBuffer; + } + + // preparing item entry + ByteBuffer item = StreamUtils.getByteBuffer(20); + item.position(0); + item.put(resref.getBytes(Misc.CHARSET_ASCII)); + + int numSlots = 0, ofsSlotsOffset = 0, ofsItemsCount = 0; + String ver = StreamUtils.readString(buffer, 4, 4); + switch (ver) { + case "V9.0": + numSlots = 38; + ofsSlotsOffset = 0x320; + ofsItemsCount = 0x328; + break; + case "V2.2": + numSlots = 50; + ofsSlotsOffset = 0x612; + ofsItemsCount = 0x61a; + break; + case "V1.2": + numSlots = 46; + ofsSlotsOffset = 0x35c; + ofsItemsCount = 0x364; + break; + case "V1.0": + numSlots = 38; + ofsSlotsOffset = 0x2b8; + ofsItemsCount = 0x2c0; + break; + } + + if (ofsSlotsOffset > 0) { + outBuffer = StreamUtils.getByteBuffer(buffer.limit() + item.limit()); + int ofsSlots = buffer.getInt(ofsSlotsOffset); + int numItems = buffer.getInt(ofsItemsCount); + outBuffer.position(0); + // adding CRE base + outBuffer.put(buffer.array(), 0, ofsSlots); + // adding new item + outBuffer.put(item.array(), 0, item.limit()); + // adding CRE inventory slots + outBuffer.put(buffer.array(), ofsSlots, buffer.limit() - ofsSlots); + // updating items count + numItems++; + outBuffer.putInt(ofsItemsCount, numItems); + // updating slots offset + ofsSlots += item.limit(); + outBuffer.putInt(ofsSlotsOffset, ofsSlots); + // assigning item to slot + if (slot >= 0 && slot < numSlots) { + outBuffer.putShort(ofsSlots + (slot * 2), (short)(numItems - 1)); + } + } + + return outBuffer; + } + + /** + * Attempts to fetch the requested resource structure from cache before creating a new instance. + * @param the desired type of the return value. + * @param classType the specific class type of the resource entry. + * @param entry the resource entry to load. + * @return a structured resource of type {@code T} if successful. Returns {@code null} otherwise. + * @throws Exception propagated exception from the instantiation process. + */ + @SuppressWarnings("unchecked") + public static T loadResource(Class classType, ResourceEntry entry) throws Exception + { + T retVal = null; + if (classType == null || entry == null) { + return retVal; + } + + AbstractStruct as = resourceCache.get(entry); + if (as == null) { + try { + Constructor ctor = classType.getConstructor(ResourceEntry.class); + if (ctor != null) { + as = ctor.newInstance(entry); + resourceCache.put(entry, as); + } + } catch (IllegalAccessException | InstantiationException | InvocationTargetException | NoSuchMethodException e) { + } + } + + if (classType.isAssignableFrom(as.getClass())) { + retVal = (T)as; + } + + return retVal; + } + + /** + * Returns whether the specified item is a two-handed weapon. + * @param itm the item resource + * @param meleeOnly indicates whether to consider only melee weapons. + * @return {@code true} if the item is a valid two-handed weapon. This also includes fake two-handed weapons in EE games. + * Returns {@code false} otherwise. + */ + public static boolean isWeaponTwoHanded(ItmResource itm, boolean meleeOnly) + { + boolean retVal = false; + if (itm == null) { + return retVal; + } + + int flags = ((IsNumeric)itm.getAttribute(ItmResource.ITM_FLAGS)).getValue(); + int mask = 1 << 1; // two-handed + if (Profile.isEnhancedEdition()) { + mask |= 1 << 12; // fake two-handed + } + retVal = (flags & mask) != 0; + + String appearance = ((IsTextual)itm.getAttribute(ItmResource.ITM_EQUIPPED_APPEARANCE)).getText().trim(); + retVal &= !appearance.isEmpty(); + + if (retVal && meleeOnly) { + int abilType = -1; + int numAbil = ((IsNumeric)itm.getAttribute(ItmResource.ITM_NUM_ABILITIES)).getValue(); + if (numAbil > 0) { + int ofsAbil = ((IsNumeric)itm.getAttribute(ItmResource.ITM_OFFSET_ABILITIES)).getValue(); + StructEntry se = itm.getAttribute(ofsAbil, true); + if (se instanceof IsNumeric) { + abilType = ((IsNumeric)se).getValue(); + } + } + retVal &= (abilType == 1); // ability type "melee" + } + + return retVal; + } + + /** + * Returns whether the specified item is a weapon that can be wielded with the left hand. + * @param itm the item resource + * @return {@code true} if the weapon can be wielded with the left hand. Returns {@code false} otherwise. + */ + public static boolean isLeftHandedWeapon(ItmResource itm) + { + boolean retVal = false; + if (itm != null) { + int flags = ((IsNumeric)itm.getAttribute(ItmResource.ITM_FLAGS)).getValue(); + boolean isTwoHanded = (flags & (1 << 1)) != 0; // two-handed + boolean allowLeftHanded = !Profile.isEnhancedEdition() || ((flags & (1 << 13)) == 0); + String appearance = ((IsTextual)itm.getAttribute(ItmResource.ITM_EQUIPPED_APPEARANCE)).getText().trim(); + int abilType = -1; + int numAbil = ((IsNumeric)itm.getAttribute(ItmResource.ITM_NUM_ABILITIES)).getValue(); + if (numAbil > 0) { + int ofsAbil = ((IsNumeric)itm.getAttribute(ItmResource.ITM_OFFSET_ABILITIES)).getValue(); + StructEntry se = itm.getAttribute(ofsAbil, true); + if (se instanceof IsNumeric) { + abilType = ((IsNumeric)se).getValue(); + } + // eligible left-handed weapons: not two-handed; left-handed allowed; has equipped appearance; ability type = Melee + retVal = !isTwoHanded && allowLeftHanded && !appearance.isEmpty() && (abilType == 1); + } + } + return retVal; + } + + + /** + * Returns the two-letter appearance code for the specified item. + * @param itm The item resource. + * @return A two-letter string indicating the item appearance for creature animations. Returns an empty string otherwise. + */ + public static String getItemAppearance(ItmResource itm) + { + String retVal = ""; + if (itm != null) { + retVal = ((IsTextual)itm.getAttribute(ItmResource.ITM_EQUIPPED_APPEARANCE)).getText().toUpperCase(Locale.ENGLISH); + if (retVal.length() > 2) { + retVal = retVal.substring(0, 2); + } else { + while (retVal.length() < 2) { + retVal += " "; + } + } + } + return retVal; + } + + /** + * Returns the index of the selected weapon ability. + * @param cre the CRE resource. + * @return ability index. Returns 0 if ability could not be determined. + */ + public static int getEquippedWeaponAbility(CreResource cre) + { + if (cre != null) { + return ((IsNumeric)cre.getAttribute(CreResource.CRE_SELECTED_WEAPON_ABILITY)).getValue(); + } + return 0; + } + + /** + * Determines the absolute item slot index of the specified weapon-related slot id. + * Special slots that cannot be mapped to slot indices are returned as -1. + * Only weapon-related slots are considered, which includes the actual weapon slot ids as well as the ammo ids. + * @param cre the CRE resource + * @param slotId the slot id (as defined in SLOTS.IDS) + * @return absolute item slot index. Returns 1000 for fist slot. Returns -1 if slot id could not be mapped to a slot index. + */ + public static int getWeaponSlotIndex(CreResource cre, int slotId) + { + int retVal = -1; + if (slotId == 1000) { + // fist weapon + return slotId; + } + + String creVer = ((IsTextual)Objects.requireNonNull(cre).getAttribute(CreResource.COMMON_VERSION)).getText().toUpperCase(Locale.ENGLISH); + slotId += 35; // determine SLOTS.IDS value + switch (slotId) { + case 3: // IWD2: ammo1 + case 4: // IWD2: ammo2 + case 5: // IWD2: ammo3 + case 6: // IWD2: ammo4 + if (creVer.equals("V2.2")) { + retVal = slotId + 14; + } + break; + case 11: // ammo1 + case 12: // ammo2 + case 13: // ammo3 + case 14: // ammo4 + if (!creVer.equals("V2.2")) { + retVal = slotId + 2; + } + break; + case 15: + case 16: + if (creVer.equals("V1.2")) { + retVal = slotId + 2; + } + break; + case 34: // magically created weapon + { + switch (creVer) { + case "V1.2": + retVal = slotId + 11; + break; + case "V2.2": + retVal = slotId + 15; + break; + default: + { + if (Profile.getGame() == Profile.Game.PSTEE) { + // special: PSTEE party members have customized item slots + int numSlots = ((IsNumeric)cre.getAttribute(CreResource.CRE_NUM_ITEM_SLOTS)).getValue(); + if (numSlots > 0) { + Table2da table = Table2daCache.get("ITMSLOTS.2DA"); + if (table != null) { + for (int row = 0, rowCount = table.getRowCount(); row < rowCount; row++) { + if (Misc.toNumber(table.get(row, 1), -1) == 13) { // magic weapon slot? + retVal = Misc.toNumber(table.get(row, 5), -1); + } + } + } + } + } + if (retVal < 0) { + retVal = slotId + 3; + } + } + } + break; + } + case 35: // weapon1 + case 36: // weapon2 (IWD2: shield1) + case 37: // weapon3 (IWD2: weapon2) + case 38: // weapon4 (IWD2: shield2) + case 39: // IWD2: weapon3 + case 40: // IWD2: shield3 + case 41: // IWD2: weapon4 + case 42: // IWD2: shield4 + retVal = slotId - 26; + break; + } + return retVal; + } + + /** + * Analyses the item at the specified slot index and returns the index if the item can be used directly. + * If the item requires a launcher the method scans the weapon slots of the specified CRE resource and + * returns the slot index of a matching launcher item. + * @param cre the CRE resource + * @param slotIndex the absolute slot index of the item to check + * @return the absolute slot index of the effective weapon. Returns {@code -1} if no weapon could be determined. + */ + public static int getEffectiveWeaponIndex(CreResource cre, int slotIndex) + { + int retVal = -1; + if (slotIndex < 0) { + return retVal; + } + + // getting item entry index + int ofsSlots = cre.getExtraOffset() + ((IsNumeric)cre.getAttribute(CreResource.CRE_OFFSET_ITEM_SLOTS)).getValue(); + int itmIndex = ((IsNumeric)cre.getAttribute(ofsSlots + slotIndex * 2)).getValue(); + int numItems = ((IsNumeric)cre.getAttribute(CreResource.CRE_NUM_ITEMS)).getValue(); + if (itmIndex < 0 || itmIndex >= numItems) { + return retVal; + } + + // loading referenced item + ItmResource itm = null; + int ofsItems = Objects.requireNonNull(cre).getExtraOffset() + ((IsNumeric)cre.getAttribute(CreResource.CRE_OFFSET_ITEMS)).getValue(); + try { + String itmResref = ((IsTextual)cre.getAttribute(ofsItems + itmIndex * 20, true)).getText(); + itm = loadResource(ItmResource.class, ResourceFactory.getResourceEntry(itmResref + ".ITM")); + } catch (Exception e) { + return retVal; + } + + // check if item requires a launcher + int abilityIndex = ((IsNumeric)cre.getAttribute(CreResource.CRE_SELECTED_WEAPON_ABILITY)).getValue(); + int numAbil = ((IsNumeric)Objects.requireNonNull(itm).getAttribute(ItmResource.ITM_NUM_ABILITIES)).getValue(); + abilityIndex = Math.min(abilityIndex, numAbil - 1); + if (abilityIndex < 0) { + return retVal; + } + int ofsAbil = ((IsNumeric)itm.getAttribute(ItmResource.ITM_OFFSET_ABILITIES)).getValue(); + Ability ability = itm.getAttribute(ofsAbil + abilityIndex * 0x38, Ability.class, false); + if (ability == null) { + return retVal; + } + int launcherType = ((IsNumeric)ability.getAttribute(Ability.ITM_ABIL_LAUNCHER_REQUIRED)).getValue(); + if (launcherType == 0) { + // item can be used directly + retVal = slotIndex; + } + + if (retVal < 0) { + // launcher required: find a weapon in weapon slots 1-4 with a matching item category + String creVer = ((IsTextual)Objects.requireNonNull(cre).getAttribute(CreResource.COMMON_VERSION)).getText().toUpperCase(Locale.ENGLISH); + int idxWeaponSlots = 9; + int slotGroupSize = creVer.equals("V2.2") ? 2 : 1; // IWD2 uses weapon/shield pairs + for (int i = 0; i < 4; i++) { + int ofs = ofsSlots + (idxWeaponSlots + i * slotGroupSize) * 2; + itmIndex = ((IsNumeric)cre.getAttribute(ofs)).getValue(); + if (itmIndex >= 0 && itmIndex < numItems) { + int cat = -1; + try { + String itmResref = ((IsTextual)cre.getAttribute(ofsItems + itmIndex * 20, true)).getText(); + ResourceEntry itmEntry = ResourceFactory.getResourceEntry(itmResref + ".ITM"); + if (itmEntry != null) { + try (InputStream is = itmEntry.getResourceDataAsStream()) { + Misc.requireCondition(is.skip(0x1c) == 0x1c, "Could not read item category", IOException.class); + cat = is.read(); + cat |= is.read() << 8; + } + } + // checking if launcher type corresponds with item category + if (launcherType == 1 && cat == 15 || // Bow + launcherType == 2 && cat == 27 || // Crossbow + launcherType == 3 && cat == 18) { // Sling + retVal = idxWeaponSlots + i * slotGroupSize; + break; + } + } catch (Exception e) { + } + } + } + } + + return retVal; + } + + /** + * Returns the active weapon of the specified creature. + * @param cre The CRE resource. + * @return The ITM resource of the active weapon. Returns {@code null} if no weapon is active. + * @throws NullPointerException if no creature resource specified. + */ + public static ItmResource getEquippedWeapon(CreResource cre) + { + ItmResource retVal = null; + + // find selected weapon slot and determine the associated item + int slotIndex = getWeaponSlotIndex(cre, ((IsNumeric)cre.getAttribute(CreResource.CRE_SELECTED_WEAPON_SLOT)).getValue()); + if (slotIndex == 1000) { + // fist weapon + retVal = getFistWeapon(cre); + } else { + if (slotIndex < 0) { + return retVal; + } + + int weaponSlotIdx = getEffectiveWeaponIndex(cre, slotIndex); + if (weaponSlotIdx < 0) { + return retVal; + } + + retVal = getInventoryItem(cre, weaponSlotIdx); + } + return retVal; + } + + /** + * Returns the equipped helmet of the specified creature. + * @param cre The CRE resource. + * @return The ITM resource of the helmet. Returns {@code null} if no helmet is equipped. + * @throws NullPointerException if no creature resource specified. + */ + public static ItmResource getEquippedHelmet(CreResource cre) + { + if (Profile.getEngine() == Profile.Engine.PST || Profile.getGame() == Profile.Game.PSTEE) { + // PST does not support helmets + return null; + } + + ItmResource retVal = getInventoryItem(cre, 0); + if (retVal != null) { + // checking item category + switch (((IsNumeric)retVal.getAttribute(ItmResource.ITM_CATEGORY)).getValue()) { + case 7: // Headgear + case 72: // Hats + break; + default: + retVal = null; + } + } + return retVal; + } + + /** + * Returns the equipped shield or left-handed weapon of the specified creature. + * @param cre The CRE resource. + * @return The ITM resource of the shield or left-handed weapon. Returns {@code null} if left hand is empty. + * @throws NullPointerException if no creature resource specified. + */ + public static ItmResource getEquippedShield(CreResource cre) + { + ItmResource retVal = null; + if (Profile.getEngine() == Profile.Engine.PST || Profile.getGame() == Profile.Game.PSTEE) { + // PST does not support shields + return retVal; + } + + String creVer = ((IsTextual)cre.getAttribute(AbstractStruct.COMMON_VERSION)).getText().toUpperCase(Locale.ENGLISH); + if ("V2.2".equals(creVer)) { + int selectedSlot = ((IsNumeric)cre.getAttribute(CreResource.CRE_SELECTED_WEAPON_SLOT)).getValue(); + int itemSlotIdx = getWeaponSlotIndex(cre, selectedSlot); + int weaponSlotIdx = getEffectiveWeaponIndex(cre, itemSlotIdx); + if (weaponSlotIdx >= 0 && weaponSlotIdx < 1000) { + // IWD2 uses weapon/shield slot combinations + retVal = getInventoryItem(cre, weaponSlotIdx + 1); + } + } else { + retVal = getInventoryItem(cre, 2); + } + + return retVal; + } + + /** + * Returns the equipped armor or robe of the specified creature. + * @param cre The CRE resource. + * @return The ITM resource of armor or robe. Returns {@code null} if no armor is equipped. + */ + public static ItmResource getEquippedArmor(CreResource cre) + { + return getInventoryItem(cre, 1); + } + + /** + * Returns the item resource at the specified item slot of the given creature. + * @param cre the CRE resource + * @param slotIndex absolute slot index of the requested item + * @return the ITM resource. Returns {@code null} if item could not be determined. + */ + public static ItmResource getInventoryItem(CreResource cre, int slotIndex) + { + ItmResource retVal = null; + if (cre == null || slotIndex < 0) { + return retVal; + } + + // determining number of item slots + String creVer = ((IsTextual)cre.getAttribute(AbstractStruct.COMMON_VERSION)).getText().toUpperCase(Locale.ENGLISH); + int numSlots = 0; + switch (creVer) { + case "V1.2": + numSlots = 48; + break; + case "V2.2": + numSlots = 52; + break; + default: + if (Profile.getGame() == Profile.Game.PSTEE) { + numSlots = ((IsNumeric)cre.getAttribute(CreResource.CRE_NUM_ITEM_SLOTS)).getValue(); + } + if (numSlots == 0) { + numSlots = 40; + } + } + + if (numSlots == 0 || slotIndex >= numSlots) { + return retVal; + } + + // loading referenced item + try { + int ofsSlots = cre.getExtraOffset() + ((IsNumeric)cre.getAttribute(CreResource.CRE_OFFSET_ITEM_SLOTS)).getValue(); + int itemIndex = ((IsNumeric)cre.getAttribute(ofsSlots + slotIndex * 2)).getValue(); + if (itemIndex >= 0) { + int numItems = ((IsNumeric)cre.getAttribute(CreResource.CRE_NUM_ITEMS)).getValue(); + if (itemIndex < numItems) { + int ofsItems = cre.getExtraOffset() + ((IsNumeric)cre.getAttribute(CreResource.CRE_OFFSET_ITEMS)).getValue(); + String itemRef = ((IsTextual)cre.getAttribute(ofsItems + itemIndex * 20, true)).getText(); + retVal = loadResource(ItmResource.class, ResourceFactory.getResourceEntry(itemRef + ".ITM")); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + + return retVal; + } + + /** Attempts to find the virtual "fist" weapon item used for barehanded attacks. */ + public static ItmResource getFistWeapon(CreResource cre) + { + ItmResource retVal = null; + + int animId = ((IsNumeric)cre.getAttribute(CreResource.CRE_ANIMATION)).getValue(); + if ((animId & 0xff00) == 0x6500) { + // monk: return fist item + int level = ((IsNumeric)cre.getAttribute(CreResource.CRE_LEVEL_FIRST_CLASS)).getValue(); + + // 1. try 2DA lookup + Table2da table = Table2daCache.get("MONKFIST.2DA"); + if (table != null) { + level = Math.max(1, Math.min(table.getRowCount(), level)); + String resref = table.get(level, 1); + ResourceEntry entry = ResourceFactory.getResourceEntry(resref + ".ITM"); + try { + retVal = loadResource(ItmResource.class, entry); + } catch (Exception e) { + e.printStackTrace(); + } + } + if (retVal == null) { + // 2. try hardcoded selection + String resref = null; + int[] minLevels = { Integer.MIN_VALUE, 3, 6, 9, 12, 15, 19, 25, Integer.MAX_VALUE }; + for (int i = 0; i < minLevels.length && resref == null; i++) { + if (level < minLevels[i]) { + resref = "MFIST" + i; + ResourceEntry entry = ResourceFactory.getResourceEntry(resref + ".ITM"); + try { + retVal = loadResource(ItmResource.class, entry); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + } + + if (retVal == null) { + // return default fist + ResourceEntry entry = ResourceFactory.getResourceEntry("FIST.ITM"); + try { + retVal = loadResource(ItmResource.class, entry); + } catch (Exception e) { + e.printStackTrace(); + } + } + + return retVal; + } + + + /** + * Loads the decoder instance for the specified BAM V1 resource. Retrieves the decoder from cache if available. + * @param entry the BAM resource entry. + * @return the {@code BamV1Decoder} instance created from the BAM resource. + * Returns {@code null} if decoder could not be retrieved. + */ + public static BamV1Decoder loadBamDecoder(ResourceEntry entry) + { + Couple retVal = loadBamDecoderController(entry); + return (retVal != null) ? retVal.getValue0() : null; + } + + /** + * Loads the BAM controller instance for the specified BAM V1 resource. Retrieves the object from cache if available. + * @param entry the BAM resource entry. + * @return the {@code BamV1Control} instance created from the BAM resource. + * Returns {@code null} if the controller could not be retrieved. + */ + public static BamV1Decoder.BamV1Control loadBamController(ResourceEntry entry) + { + Couple retVal = loadBamDecoderController(entry); + return (retVal != null) ? retVal.getValue1() : null; + } + + /** + * Loads the decoder instance and an associated controller for the specified BAM V1 resource. Retrieves the objects + * from cache if available. + * @param entry the BAM resource entry. + * @return {@code BamV1Decoder} and {@code BamV1Control} instances created from the BAM resource. + * Returns {@code null} if the objects could not be retrieved. + */ + public static Couple loadBamDecoderController(ResourceEntry entry) + { + Couple retVal = bamCache.getOrDefault(entry, null); + if (retVal == null) { + try { + BamV1Decoder decoder = new BamV1Decoder(entry); + BamV1Decoder.BamV1Control control = decoder.createControl(); + retVal = Couple.with(decoder, control); + bamCache.put(entry, retVal); + } catch (Exception e) { + e.printStackTrace(); + } + } + return retVal; + } + + /** + * Returns whether the specified BAM V1 resource exists and contains the specified range of cycles. + * @param entry the BAM resource to check. + * @param cycle start index of cycle range. + * @param length number of cycles. + * @return {@code true} if the BAM resource exists, is paletted and contains the specified cycles. + */ + public static boolean bamCyclesExist(ResourceEntry entry, int cycle, int length) + { + try { + BamV1Decoder.BamV1Control control = loadBamController(entry); + int numCycles = control.cycleCount(); + return (numCycles >= cycle + length && control.cycleFrameCount(cycle) > 0); + } catch (Exception e) { + } + return false; + } + + /** + * Returns the number of cycles available in the specified BAM resource. + * @param entry BAM resource to check. + * @return number of cycles. Returns 0 if number of cycles could not be determined. + */ + public static int getBamCycles(ResourceEntry entry) + { + int retVal = 0; + try { + BamV1Decoder.BamV1Control control = loadBamController(entry); + retVal = control.cycleCount(); + } catch (Exception e) { + } + return retVal; + } + + /** + * Returns the number of frames available in the specified BAM cycle. + * @param entry the BAM resource + * @param cycleIdx the BAM cycle to check. + * @return number of frames in the specified BAM cycle. Returns 0 if number of frames could not be determined. + */ + public static int getBamCycleFrames(ResourceEntry entry, int cycleIdx) + { + int retVal = 0; + try { + BamV1Decoder.BamV1Control control = loadBamController(entry); + if (cycleIdx >= 0 && cycleIdx < control.cycleCount()) { + retVal = control.cycleFrameCount(cycleIdx); + } + } catch (Exception e) { + } + return retVal; + } + + /** + * Returns whether the specified BAM contains a palette that is most likely replaced by external color ranges. + * @param entry BAM resource to check + * @return {@code true} if BAM contains a false color palette, {@code false} otherwise. + */ + public static boolean bamHasFalseColors(ResourceEntry entry) + { + boolean retVal = false; + try { + BamV1Decoder.BamV1Control control = loadBamController(entry); + int[] palette = control.getCurrentPalette(); + if (Profile.getGame() == Profile.Game.PST || Profile.getGame() == Profile.Game.PSTEE) { + retVal = (palette[224] & 0xffffff) == 0x0000ff; + retVal &= (palette[240] & 0xffffff) == 0x00009f; + } else { + retVal = (palette[15] & 0xffffff) == 0x1e1e1e; + retVal &= (palette[27] & 0xffffff) == 0x004040; + retVal &= (palette[39] & 0xffffff) == 0x400040; + retVal &= (palette[51] & 0xffffff) == 0x404000; + retVal &= (palette[63] & 0xffffff) == 0x400000; + retVal &= (palette[75] & 0xffffff) == 0x000040; + retVal &= (palette[87] & 0xffffff) == 0x004000; + } + } catch (Exception e) { + } + return retVal; + } + + /** + * Ensures that "transparent" and "shadow" palette entries of the animation are properly set. + * @param control the BAM controller associated with the animation. + */ + public static void fixShadowColor(BamV1Control control) + { + if (control != null) { + int[] palette = control.getCurrentPalette(); + palette[0] = 0x0000FF00; + palette[1] = 0x80000000; + control.setExternalPalette(palette); + } + } + + /** + * Applies the specified palette to the BAM animation associated with the specified controller. + * @param control the BAM controller associated with the animation. + * @param palette the new palette data. + */ + public static void applyNewPalette(BamV1Control control, int[] palette) + { + if (palette == null) { + return; + } + if (palette.length < 256) { + palette = Arrays.copyOf(palette, 256); + } + control.setExternalPalette(palette); + } + + /** + * Loads the palette specified by the resource reference. + * It looks for a palette in the following order: [resref].BMP, [resref].BAM. + * @param resref the resource reference to use for loading a palette. + * @return palette data with 256 entries. Returns {@code null} if palette could not be loaded. + */ + public static int[] loadReplacementPalette(String resref) + { + return loadReplacementPalette(resref, -1); + } + + /** + * Loads the palette specified by the resource reference and index. + * It looks for a palette in the following order: [resref+suffix].BMP, [resref+suffix].BAM, [resref].BMP, [resref].BAM. + * @param resref the resource reference to use for loading a palette. + * @param index a numeric suffix added to the resref. + * @return palette data with 256 entries. Returns {@code null} if palette could not be loaded. + */ + public static int[] loadReplacementPalette(String resref, int index) + { + int[] retVal = null; + if (resref == null || resref.isEmpty()) { + return retVal; + } + + String resName = resref; + String suffix = (index >= 0) ? Integer.toString(index) : ""; + String[] suffixList = (suffix.isEmpty()) ? new String[] {""} : new String[] {suffix, ""}; + ResourceEntry entry = null; + for (final String s : suffixList) { + if (ResourceFactory.resourceExists(resName + s + ".BMP")) { + entry = ResourceFactory.getResourceEntry(resName + s + ".BMP"); + break; + } + } + if (entry == null) { + return retVal; + } + + retVal = paletteCache.getOrDefault(entry, null); + if (retVal == null) { + try { + retVal = ColorConvert.loadPaletteBMP(entry); + if (retVal != null) { + if (retVal.length < 256) { + retVal = Arrays.copyOf(retVal, 256); + } + paletteCache.put(entry, retVal); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + return retVal; + } + + /** + * Replaces a range of color entries in the palette with new entries. + * @param palette the palette to modify. + * @param newColors array of colors to add. + * @param startOffset first color entry in {@code palette} to update. + * @param len number of color entries to update. Parameter is adjusted if {@code newColors} array contains + * less entries than {@code len} or range would exceed palette size. + * @param allowAlpha {@code true} to use alpha from {@code newColors}; {@code false} to discard alpha values. + * @return the updated palette. + */ + public static int[] replaceColors(int[] palette, int[] newColors, int startOffset, int len, boolean allowAlpha) + { + int[] retVal = palette; + if (palette != null && newColors != null) { + retVal = Arrays.copyOf(palette, palette.length); + if (newColors.length > 0 && startOffset >= 0 && startOffset < retVal.length) { + len = Math.min(len, newColors.length); + len = Math.min(len, retVal.length - startOffset); + int mask = allowAlpha ? 0xffffffff : 0x00ffffff; + for (int i = 0; i < len; i++) { + retVal[startOffset + i] = newColors[i] & mask; + } + } + } + return retVal; + } + + /** + * Interpolates two palette ranges and stores the result in a third palette range. + * @param palette the palette to modify. + * @param srcOfs1 start offset of first source color range. + * @param srcOfs2 start offset of second source color range. + * @param srcLen number of source color entries to read. + * @param dstOfs start offset of target color range. + * @param dstLen number of interpolated color entries to write. + * @param allowAlpha {@code true} to interpolate alpha, {@code false} to discard alpha values. + * @return the updated palette. + */ + public static int[] interpolateColors(int[] palette, int srcOfs1, int srcOfs2, int srcLen, + int dstOfs, int dstLen, boolean allowAlpha) + { + int[] retVal = palette; + if (palette != null && srcLen > 0 && dstLen > 0 && + srcOfs1 >= 0 && srcOfs1 + srcLen <= palette.length && + srcOfs2 >= 0 && srcOfs2 + srcLen <= palette.length && + dstOfs >= 0 && dstOfs + dstLen <= palette.length) { + retVal = Arrays.copyOf(palette, palette.length); + for (int dstIdx = 0; dstIdx < dstLen; dstIdx++) { + int srcIdx = dstIdx * srcLen / dstLen; + int r = ((retVal[srcOfs1 + srcIdx] & 0xff) + (retVal[srcOfs2 + srcIdx] & 0xff)) >>> 1; + int g = (((retVal[srcOfs1 + srcIdx] >> 8) & 0xff) + ((retVal[srcOfs2 + srcIdx] >> 8) & 0xff)) >>> 1; + int b = (((retVal[srcOfs1 + srcIdx] >> 16) & 0xff) + ((retVal[srcOfs2 + srcIdx] >> 16) & 0xff)) >>> 1; + int a = 0xff000000; + if (allowAlpha) { + a = (((retVal[srcOfs1 + srcIdx] >> 24) & 0xff) + ((retVal[srcOfs2 + srcIdx] >> 24) & 0xff)) >>> 1; + } + retVal[dstOfs + dstIdx] = (a << 24) | (b << 16) | (g << 8) | r; + } + } + return retVal; + } + + /** + * Returns the specified color gradient. Colors are retrieved from the game-specific gradient resource. + * Optionally takes random color entry definitions into account. + * @param animType Indicates what type of color gradient should be loaded. + * @param index Index of the color gradient. + * @param allowRandom whether random color entries are taken into account. + * @return the gradient as array of colors. Returns {@code null} if color index does not exist. + */ + public static int[] getColorGradient(SpriteDecoder.AnimationType animType, int index, boolean allowRandom) + { + if (colorGradients.isEmpty()) { + // initializing color gradient map on demand + ResourceEntry palFile = null; + if (animType == SpriteDecoder.AnimationType.MONSTER_PLANESCAPE) { + palFile = ResourceFactory.getResourceEntry("PAL32.BMP"); + } else if (ResourceFactory.resourceExists("RANGES12.BMP")) { + palFile = ResourceFactory.getResourceEntry("RANGES12.BMP"); + } else if (ResourceFactory.resourceExists("MPALETTE.BMP")) { + palFile = ResourceFactory.getResourceEntry("MPALETTE.BMP"); + } + + if (palFile != null) { + try { + BufferedImage image = new GraphicsResource(palFile).getImage(); + for (int y = 0; y < image.getHeight(); y++) { + int[] pixels = image.getRGB(0, y, image.getWidth(), 1, null, 0, image.getWidth()); + colorGradients.put(y, pixels); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + int[] retVal = colorGradients.getOrDefault(index, null); + if (retVal == null && allowRandom) { + retVal = getRandomColorGradient(animType, index); + } + + return retVal; + } + + /** + * Attempts to resolve a random color entry to a real color entry. + * @param index random color entry index. + * @return a randomly chosen color gradient from the random color entry list. + * Returns {@code null} if no real color gradient could be determined. + */ + public static int[] getRandomColorGradient(SpriteDecoder.AnimationType animType, int index) + { + if (randomGradientIndices.isEmpty()) { + if (ResourceFactory.resourceExists("RANDCOLR.2DA")) { + Table2da table = Table2daCache.get("RANDCOLR.2DA"); + if (table != null && table.getRowCount() > 1) { + for (int col = 1, numCols = table.getColCount(); col < numCols; col++) { + // random entry index + int randIdx = Misc.toNumber(table.get(0, col), -1); + if (randIdx >= 0) { + int[] indices = new int[table.getRowCount() - 1]; + for (int row = 1, numRows = table.getRowCount(); row < numRows; row++) { + indices[row - 1] = Misc.toNumber(table.get(row, col), 0); + } + randomGradientIndices.put(randIdx, indices); + } + } + } + } + } + + int failCounter = 100; + int[] retVal = null; + int[] indices = randomGradientIndices.getOrDefault(index, null); + while (retVal == null && indices != null && indices.length > 0 && failCounter-- > 0) { + int idx = indices[getRandomInt(index, false) % indices.length]; + if (randomGradientIndices.containsKey(idx)) { + // random color entries may refer to other random color entries + indices = randomGradientIndices.getOrDefault(index, null); + } else { + retVal = getColorGradient(animType, idx, false); + } + } + + return retVal; + } + + /** + * Returns a random number from a predefined pool of random numbers. + * @param index Index into the random number pool. + * @param allowNegative whether negative return values are allowed. + * @return the pseudo-random number specified by the index. + */ + public static int getRandomInt(int index, boolean allowNegative) + { + if (randomPool == null) { + updateRandomPool(); + } + int retVal = randomPool[index % randomPool.length]; + if (!allowNegative && retVal < 0) { + retVal = -retVal; + } + return retVal; + } + + /** Recreates the pool of random numbers. */ + public static void updateRandomPool() + { + if (randomPool == null) { + randomPool = new int[256]; + } + for (int i = 0; i < randomPool.length; i++) { + randomPool[i] = random.nextInt(); + } + } + + private SpriteUtils() + { + } +} diff --git a/src/org/infinity/resource/cre/decoder/TownStaticDecoder.java b/src/org/infinity/resource/cre/decoder/TownStaticDecoder.java new file mode 100644 index 000000000..1b13cc98a --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/TownStaticDecoder.java @@ -0,0 +1,126 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.infinity.resource.ResourceFactory; +import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.DecoderAttribute; +import org.infinity.resource.cre.decoder.internal.SegmentDef; +import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.IniMap; +import org.infinity.util.IniMapSection; + +/** + * Creature animation decoder for processing type 4000 (town_static) animations. + * Available ranges: [4000,4fff] + */ +public class TownStaticDecoder extends SpriteDecoder +{ + /** The animation type associated with this class definition. */ + public static final AnimationType ANIMATION_TYPE = AnimationType.TOWN_STATIC; + + public static final DecoderAttribute KEY_CAN_LIE_DOWN = DecoderAttribute.with("can_lie_down", DecoderAttribute.DataType.BOOLEAN); + + /** Mapping between sequence and cycle index. */ + private static final HashMap cycleMap = new HashMap() {{ + put(Sequence.STANCE, 0); + put(Sequence.STAND, 16); + put(Sequence.GET_HIT, 32); + put(Sequence.DIE, 48); + put(Sequence.SLEEP, 48); + put(Sequence.GET_UP, 48); + put(Sequence.TWITCH, 64); + }}; + + /** + * A helper method that parses the specified data array and generates a {@link IniMap} instance out of it. + * @param data a String array containing table values for a specific table entry. + * @return a {@code IniMap} instance with the value derived from the specified data array. + * Returns {@code null} if no data could be derived. + */ + public static IniMap processTableData(String[] data) + { + IniMap retVal = null; + if (data == null) { + return retVal; + } + + String resref = SpriteTables.valueToString(data, SpriteTables.COLUMN_RESREF, ""); + if (resref.isEmpty()) { + return retVal; + } + int falseColor = SpriteTables.valueToInt(data, SpriteTables.COLUMN_CLOWN, 0); + + List lines = processTableDataGeneral(data, ANIMATION_TYPE); + lines.add("[town_static]"); + lines.add("false_color=" + falseColor); + lines.add("resref=" + resref); + + retVal = IniMap.from(lines); + + return retVal; + } + + public TownStaticDecoder(int animationId, IniMap ini) throws Exception + { + super(ANIMATION_TYPE, animationId, ini); + } + + public TownStaticDecoder(CreResource cre) throws Exception + { + super(ANIMATION_TYPE, cre); + } + + /** Returns whether the creature falls down when dead/unconscious. */ + public boolean canLieDown() { return getAttribute(KEY_CAN_LIE_DOWN); } + protected void setCanLieDown(boolean b) { setAttribute(KEY_CAN_LIE_DOWN, b); } + + @Override + public List getAnimationFiles(boolean essential) + { + ArrayList retVal = new ArrayList<>(); + retVal.add(getAnimationResref() + ".BAM"); + return retVal; + } + + @Override + public boolean isSequenceAvailable(Sequence seq) + { + return (getSequenceDefinition(seq) != null); + } + + @Override + protected void init() throws Exception + { + // setting properties + initDefaults(getAnimationInfo()); + IniMapSection section = getSpecificIniSection(); + setFalseColor(section.getAsInteger(KEY_FALSE_COLOR.getName(), 0) != 0); + setCanLieDown(section.getAsInteger(KEY_CAN_LIE_DOWN.getName(), 0) != 0); + } + + @Override + protected SeqDef getSequenceDefinition(Sequence seq) + { + SeqDef retVal = null; + if (!cycleMap.containsKey(seq)) { + return retVal; + } + + SegmentDef.Behavior behavior = (seq == Sequence.GET_UP) ? SegmentDef.getBehaviorOf("!") : SegmentDef.getBehaviorOf(""); + int cycle = cycleMap.getOrDefault(seq, 0); + ResourceEntry entry = ResourceFactory.getResourceEntry(getAnimationResref() + ".BAM"); + if (SpriteUtils.bamCyclesExist(entry, cycle, SeqDef.DIR_FULL.length)) { + retVal = SeqDef.createSequence(seq, SeqDef.DIR_FULL, false, entry, cycle, null, behavior); + } + return retVal; + } +} diff --git a/src/org/infinity/resource/cre/decoder/internal/ColorInfo.java b/src/org/infinity/resource/cre/decoder/internal/ColorInfo.java new file mode 100644 index 000000000..82856955c --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/internal/ColorInfo.java @@ -0,0 +1,118 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder.internal; + +import java.util.Collections; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * Provides information about color definitions for sprites and sprite overlays. + */ +public class ColorInfo +{ + private static final int[] EMPTY_INT_ARRAY = new int[0]; + + // Maps value (Map) to an individual sprite overlay types (avatar, weapon, shield, ...) + private final EnumMap> colorMap = new EnumMap<>(SegmentDef.SpriteType.class); + + public ColorInfo() + { + } + + /** Returns an iterator over the sprite overlay types for color definitions. */ + public Iterator getTypeIterator() { return colorMap.keySet().iterator(); } + + /** Returns an array of sprite overlay types for color definitions. */ + public SegmentDef.SpriteType[] getTypes() { return colorMap.keySet().toArray(new SegmentDef.SpriteType[colorMap.size()]); } + + /** Returns an iterator over the color locations for the specified sprite overlay type. */ + public Iterator getLocationIterator(SegmentDef.SpriteType type) + { + Map map = colorMap.get(type); + if (map != null) { + return map.keySet().iterator(); + } + return Collections.emptyList().iterator(); // empty iterator + } + + /** Returns an array of color location indices for the specified sprite overlay type. */ + public int[] getLocations(SegmentDef.SpriteType type) + { + int[] retVal = EMPTY_INT_ARRAY; + Map map = colorMap.get(type); + if (map != null) { + retVal = new int[map.size()]; + int i = 0; + for (final Integer v : map.keySet()) { + retVal[i] = v.intValue(); + i++; + } + } + return retVal; + } + + /** + * Returns the color value for the specified sprite overlay type and location index. + * Returns -1 if value is not available. + */ + public int getValue(SegmentDef.SpriteType type, int index) + { + Map map = colorMap.get(type); + if (map != null) { + Integer v = map.get(index); + if (v != null) { + return v.intValue(); + } + } + return -1; + } + + /** + * Adds a color entry and associates it with a sprite overlay type and color location. + * Existing color entries will be updated. + * @param type the sprite overlay type. + * @param locationIndex the color location. + * @param colorIndex the color index. + */ + public void add(SegmentDef.SpriteType type, int locationIndex, int colorIndex) + { + if (type == null) { + return; + } + if (locationIndex >= -1 && locationIndex < 7) { + Map map = colorMap.get(type); + if (map == null) { + map = new HashMap(); + } + map.put(locationIndex, colorIndex); + colorMap.put(type, map); + } + } + + @Override + public int hashCode() + { + int hash = 7; + hash = 31 * hash + ((colorMap == null) ? 0 : colorMap.hashCode()); + return hash; + } + + @Override + public boolean equals(Object o) + { + if (o == this) { + return true; + } + if (!(o instanceof ColorInfo)) { + return false; + } + ColorInfo other = (ColorInfo)o; + return (this.colorMap == null && other.colorMap == null) || + (this.colorMap != null && this.colorMap.equals(other.colorMap)); + } +} diff --git a/src/org/infinity/resource/cre/decoder/internal/CreatureInfo.java b/src/org/infinity/resource/cre/decoder/internal/CreatureInfo.java new file mode 100644 index 000000000..59a29393d --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/internal/CreatureInfo.java @@ -0,0 +1,818 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder.internal; + +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Objects; + +import org.infinity.datatype.EffectType; +import org.infinity.datatype.IsNumeric; +import org.infinity.datatype.IsTextual; +import org.infinity.resource.AbstractStruct; +import org.infinity.resource.Effect; +import org.infinity.resource.Effect2; +import org.infinity.resource.Profile; +import org.infinity.resource.ResourceFactory; +import org.infinity.resource.StructEntry; +import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.Item; +import org.infinity.resource.cre.decoder.MonsterPlanescapeDecoder; +import org.infinity.resource.cre.decoder.SpriteDecoder; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.Misc; +import org.infinity.util.Table2da; +import org.infinity.util.Table2daCache; + +/** + * Provides useful information about a creature resource and their equipment. + */ +public class CreatureInfo +{ + /** Value to disable allegiance override. */ + public static final int ALLEGIANCE_OVERRIDE_NONE = -1; + + /** + * Identifies the equipment slot for an item. + * Entries in this enumeration are sorted by effect application order. + */ + public enum ItemSlots { + /** Slot for helmets and ioun stones. */ + HELMET, + /** Slot for armor or robes. */ + ARMOR, + /** Slot for shields or left-handed weapons. */ + SHIELD, + /** Slot for gauntlets and bracers. */ + GAUNTLETS, + /** Right slot for rings. */ + RING_RIGHT, + /** Left slot for rings. */ + RING_LEFT, + /** Slot for amulets or necklaces. */ + AMULET, + /** Slot for belts. */ + BELT, + /** Slot for boots. */ + BOOTS, + /** Slot for cloaks and mantles. */ + CLOAK, + /** Slot for the currently active weapon. */ + WEAPON, + } + + private final EnumMap equipment = new EnumMap<>(ItemSlots.class); + private final ColorInfo colorInfo = new ColorInfo(); // storage for color-related effects applied to the creature + private final SpriteDecoder decoder; + private final CreResource cre; + + private int allegianceOverride; + + public CreatureInfo(SpriteDecoder decoder, CreResource cre) throws Exception + { + this.decoder = Objects.requireNonNull(decoder, "SpriteDecoder instance cannot be null"); + this.cre = Objects.requireNonNull(cre, "CRE resource cannot be null"); + this.allegianceOverride = ALLEGIANCE_OVERRIDE_NONE; + init(); + } + + /** Returns the {@code CreResource} instance of the creature resource. */ + public CreResource getCreResource() { return cre; } + + /** Returns creature flags. */ + public int getFlags() { return ((IsNumeric)cre.getAttribute(CreResource.CRE_FLAGS)).getValue(); } + + /** Returns the creature animation id. */ + public int getAnimationId() { return ((IsNumeric)cre.getAttribute(CreResource.CRE_ANIMATION)).getValue(); } + + /** + * Returns the translucency strength of the creature. Values can range from 0 (fully opaque) to 255 (fully transparent). + * The method takes creature animation attributes and (EE only) creature attributes into account. + */ + public int getTranslucency() + { + int retVal = 0; + if (decoder.getAttribute(SpriteDecoder.KEY_TRANSLUCENT)) { // avoid circular dependencies + retVal = 128; + } + if (Profile.isEnhancedEdition()) { + int v = ((IsNumeric)cre.getAttribute(CreResource.CRE_TRANSLUCENCY)).getValue(); + if (v > 0) { + retVal = Math.min(v, 255); + } + } + return retVal; + } + + /** + * Returns the (average or highest) class level of the creature. Returns 0 if level could not be determined. + * @param highestOnly specify {@code false} to determine the average level of all (active and inactive) classes. + * Specify {@code true} to return the highest level of all (active and inactive) classes. + */ + public int getClassLevel(boolean highestOnly) + { + int retVal = 0; + int ofsBase = cre.getExtraOffset(); + + String creVersion = getCreatureVersion(); + if ("V2.2".equals(creVersion)) { + // IWD2 + int level = 0; + int maxLevel = 0; + int numClasses = 0; + for (int i = 0; i < 11; i++) { + int v = ((IsNumeric)cre.getAttribute(ofsBase + 0x8b + i)).getValue(); + if (v > 0) { + level += v; + maxLevel = Math.max(maxLevel, v); + numClasses++; + } + } + if (highestOnly) { + retVal = maxLevel; + } else { + retVal = level; + if (numClasses > 0) { + retVal /= numClasses; + } + } + } else { + // non-IWD2 + int cls = ((IsNumeric)cre.getAttribute(CreResource.CRE_CLASS)).getValue(); + int numClasses; + switch (cls) { + case 7: + case 8: + case 9: + case 13: + case 14: + case 15: + case 16: + case 18: + numClasses = 2; + break; + case 10: + case 17: + numClasses =3; + break; + default: + numClasses = 1; + } + int level = 0; + int maxLevel = 0; + for (int i = 0; i < numClasses; i++) { + int v = ((IsNumeric)cre.getAttribute(ofsBase + 0x234 + i)).getValue(); + level += v; + maxLevel = Math.max(maxLevel, v); + } + if (highestOnly) { + retVal = maxLevel; + } else { + retVal = level / numClasses; + } + } + + return retVal; + } + + /** Returns the allegiance of the creature. May return overridden allegiance if set. */ + public int getAllegiance() + { + return getAllegiance(true); + } + + /** + * Returns the allegiance of the creature. + * @param allowOverride whether allegiance override of the parent sprite decoder is considered. + * @return the (overridden) allegiance value of the creature. + */ + public int getAllegiance(boolean allowOverride) + { + int retVal = ALLEGIANCE_OVERRIDE_NONE; + if (allowOverride) { + retVal = getAllegianceOverride(); + } + if (retVal == ALLEGIANCE_OVERRIDE_NONE) { + retVal = ((IsNumeric)cre.getAttribute(CreResource.CRE_ALLEGIANCE)).getValue(); + } + return retVal; + } + + /** Returns the overridden allegiance. Returns {@link #ALLEGIANCE_OVERRIDE_NONE} if allegiance has not been overridden. */ + public int getAllegianceOverride() + { + return allegianceOverride; + } + + /** + * Overrides the creature's allegiance. + * @param allegiance new allegiance of the creature. Uses the same values as defined in EA.IDS. + * Specify {@link #ALLEGIANCE_OVERRIDE_NONE} to disable. + */ + public void setAllegianceOverride(int allegiance) + { + allegiance = Math.max(ALLEGIANCE_OVERRIDE_NONE, Math.min(255, allegiance)); + if (allegianceOverride != allegiance) { + allegianceOverride = allegiance; + decoder.allegianceChanged(); + } + } + + /** + * Returns the ability index of the currently selected weapon. + * Returns -1 if no weapon (not even fist) is available or could not be determined. + */ + public int getSelectedWeaponAbility() + { + int retVal = -1; + + ItemInfo itemInfo = equipment.get(ItemSlots.WEAPON); + if (itemInfo != null) { + int numAbils = itemInfo.getAbilityCount(); + int idxAbil = ((IsNumeric)cre.getAttribute(CreResource.CRE_SELECTED_WEAPON_ABILITY)).getValue(); + retVal = Math.max(0, Math.min(numAbils - 1, idxAbil)); + } + return retVal; + } + + /** + * Returns the {@link ItemInfo} instance associated with the specified item slot. + * @param slot the slot where the item is equipped. + * @return {@code ItemInfo} instance of the item, {@code null} if no item equipped. + */ + public ItemInfo getItemInfo(ItemSlots slot) + { + if (slot == null) { + return null; + } + return equipment.get(slot); + } + + /** + * Returns a list of equipped items in the order of effect application. + * @return List of {@code ItemInfo} instances in the order of effect application. + */ + public ItemInfo[] getEffectiveItemInfo() + { + ArrayList items = new ArrayList<>(); + + for (final ItemSlots slot : ItemSlots.values()) { + ItemInfo info = equipment.get(slot); + if (info != null) { + items.add(info); + } + } + + return items.toArray(new ItemInfo[items.size()]); + } + + /** + * Provides access to the {@link ColorInfo} instance which contains color definitions + * set by effects applied to the creature. + */ + public ColorInfo getColorInfo() { return colorInfo; } + + /** + * Returns the number of defined color entries for the creature. + * Number can vary for PST or PST:EE creatures. Otherwise it always returns 7. + */ + public int getColorCount() + { + int retVal = 7; + String creVersion = getCreatureVersion(); + if ("V1.1".equals(creVersion) || "V1.2".equals(creVersion)) { + if (decoder instanceof MonsterPlanescapeDecoder && decoder.isFalseColor()) { + retVal = ((MonsterPlanescapeDecoder)decoder).getColorLocationCount(); + } else { + retVal = 0; + } + } else if (Profile.getGame() == Profile.Game.PSTEE) { + if (decoder instanceof MonsterPlanescapeDecoder) { + retVal = ((MonsterPlanescapeDecoder)decoder).getColorLocationCount(); + } + } + return retVal; + } + + /** + * Returns the number of defined color entries for the specified sprite overlay type. + * @param type the sprite overlay type. + * @return number of defined color entries for the sprite overlay type. + */ + public int getColorCount(SegmentDef.SpriteType type) + { + int retVal = 0; + + if (type == null) { + type = SegmentDef.SpriteType.AVATAR; + } + + if (type == SegmentDef.SpriteType.AVATAR) { + return getColorCount(); + } + + ItemInfo itemInfo = null; + switch (type) { + case HELMET: + itemInfo = equipment.get(ItemSlots.HELMET); + break; + case SHIELD: + itemInfo = equipment.get(ItemSlots.SHIELD); + break; + case WEAPON: + itemInfo = equipment.get(ItemSlots.WEAPON); + break; + default: + } + + if (itemInfo != null) { + HashSet set = new HashSet<>(); + for (Iterator iter = itemInfo.getColorInfo().getLocationIterator(type); iter.hasNext(); ) { + set.add(iter.next()); + } + retVal = set.size(); + } + + return retVal; + } + + /** + * Returns the color entry of the specified location index as defined by the creature resource. + * Color locations range from 0 to 6. For PST and PST:EE range can differ depending on creature animation. + * Returns -1 if color entry is not available. + */ + public int getColorValue(int locationIndex) + { + int retVal = -1; + if (locationIndex >= 0 && locationIndex < getColorCount()) { + String creVersion = getCreatureVersion(); + if ("V1.1".equals(creVersion) || "V1.2".equals(creVersion)) { + retVal = ((IsNumeric)cre.getAttribute(String.format(CreResource.CRE_COLOR_FMT, locationIndex + 1))).getValue(); + } else { + int ofsBase = cre.getExtraOffset(); + retVal = ((IsNumeric)cre.getAttribute(ofsBase + 0x2c + locationIndex)).getValue(); + } + } + return retVal; + } + + /** + * Returns the color entry of the specified location index for the specified sprite overlay type. + * @param type the sprite overlay type. + * @param locationIndex the color location index. + * @return color entry index. Returns -1 if color entry is not available. + */ + public int getColorValue(SegmentDef.SpriteType type, int locationIndex) + { + int retVal = -1; + + if (type == null) { + type = SegmentDef.SpriteType.AVATAR; + } + + if (type == SegmentDef.SpriteType.AVATAR) { + return getColorValue(locationIndex); + } + + ItemInfo itemInfo = null; + switch (type) { + case HELMET: + itemInfo = equipment.get(ItemSlots.HELMET); + break; + case SHIELD: + itemInfo = equipment.get(ItemSlots.SHIELD); + break; + case WEAPON: + itemInfo = equipment.get(ItemSlots.WEAPON); + break; + default: + } + + if (itemInfo != null) { + retVal = itemInfo.getColorInfo().getValue(type, locationIndex); + } + + return retVal; + } + + /** + * Returns the color entry of the specified location index for the avatar sprite + * after applying all equipment and effect colors. + * Returns -1 if color entry is not available. + */ + public int getEffectiveColorValue(int locationIndex) + { + return getEffectiveColorValue(SegmentDef.SpriteType.AVATAR, locationIndex); + } + + /** + * Returns the color entry of the location index for the specified sprite overlay type + * after applying all equipment and effect colors. + * Returns -1 if color entry is not available. + */ + public int getEffectiveColorValue(SegmentDef.SpriteType type, int locationIndex) + { + // using creature color by default + int retVal = getColorValue(SegmentDef.SpriteType.AVATAR, locationIndex); + + if (type == null) { + type = SegmentDef.SpriteType.AVATAR; + } + + // checking equipped items + ItemInfo[] itemInfos = getEffectiveItemInfo(); + for (final ItemInfo info : itemInfos) { + int v = info.getColorInfo().getValue(type, locationIndex); + if (v >= 0) { + retVal = v; + } + } + + // checking creature effects + int v = getColorInfo().getValue(type, locationIndex); + if (v >= 0) { + retVal = v; + } + + return retVal; + } + + + /** Returns the creature resource version. */ + private String getCreatureVersion() + { + return ((IsTextual)cre.getAttribute(CreResource.COMMON_VERSION)).getText().toUpperCase(Locale.ENGLISH); + } + + private void init() throws Exception + { + // initialize attributes + initCommon(); + + switch (getCreatureVersion()) { + case "V1.0": + initV10(); + break; + case "V1.1": // non-standard PST format + initV10(); + break; + case "V1.2": + initV12(); + break; + case "V2.2": + initV22(); + break; + case "V9.0": + case "V9.1": + initV90(); + break; + default: + throw new Exception("Unsupported creature resource: " + cre.getResourceEntry().getResourceName()); + } + } + + // initialize common section of creature resources + private void initCommon() throws Exception + { + // collecting opcode 7 effects + initEffects(); + } + + // initialize BG/EE-style creature resources + private void initV10() throws Exception + { + int selectedWeaponSlot = 38; + if (Profile.getGame() == Profile.Game.PSTEE) { + Misc.requireCondition("V1.0".equalsIgnoreCase(((IsTextual)cre.getAttribute(CreResource.COMMON_VERSION)).getText()), + "CRE version not supported"); + int numSlots = ((IsNumeric)cre.getAttribute(CreResource.CRE_NUM_ITEM_SLOTS)).getValue(); + if (numSlots > 0) { + selectedWeaponSlot = numSlots - 1; + } + } + + // initializing equipment + HashMap slotMap = new HashMap() {{ + put(ItemSlots.HELMET, 0); + put(ItemSlots.ARMOR, 1); + put(ItemSlots.SHIELD, 2); + put(ItemSlots.GAUNTLETS, 3); + put(ItemSlots.RING_LEFT, 4); + put(ItemSlots.RING_RIGHT, 5); + put(ItemSlots.AMULET, 6); + put(ItemSlots.BELT, 7); + put(ItemSlots.BOOTS, 8); + put(ItemSlots.WEAPON, 9); + put(ItemSlots.CLOAK, 17); + }}; + + initEquipment(slotMap, selectedWeaponSlot); + } + + // initialize PST-style creature resources + private void initV12() throws Exception + { + // initializing equipment + int selectedWeaponSlot = 46; + HashMap slotMap = new HashMap() {{ + put(ItemSlots.HELMET, 0); + put(ItemSlots.ARMOR, 1); + put(ItemSlots.SHIELD, 2); + put(ItemSlots.GAUNTLETS, 3); + put(ItemSlots.RING_LEFT, 4); + put(ItemSlots.RING_RIGHT, 5); + put(ItemSlots.AMULET, 6); + put(ItemSlots.BELT, 7); + put(ItemSlots.BOOTS, 8); + put(ItemSlots.WEAPON, 9); + put(ItemSlots.CLOAK, 19); + }}; + + initEquipment(slotMap, selectedWeaponSlot); + } + + // initialize IWD2-style creature resources + private void initV22() throws Exception + { + // initializing equipment + initEquipmentV22(); + } + + // initialize IWD-style creature resources + private void initV90() throws Exception + { + // initializing equipment + int selectedWeaponSlot = 38; + HashMap slotMap = new HashMap() {{ + put(ItemSlots.HELMET, 0); + put(ItemSlots.ARMOR, 1); + put(ItemSlots.SHIELD, 2); + put(ItemSlots.GAUNTLETS, 3); + put(ItemSlots.RING_LEFT, 4); + put(ItemSlots.RING_RIGHT, 5); + put(ItemSlots.AMULET, 6); + put(ItemSlots.BELT, 7); + put(ItemSlots.BOOTS, 8); + put(ItemSlots.WEAPON, 9); + put(ItemSlots.CLOAK, 17); + }}; + + initEquipment(slotMap, selectedWeaponSlot); + } + + private void initEffects() + { + int fxType = ((IsNumeric)cre.getAttribute(CreResource.CRE_EFFECT_VERSION)).getValue(); + Class fxClass = (fxType == 1) ? Effect2.class : Effect.class; + List fxList= cre.getFields(fxClass); + if (fxList != null) { + for (final StructEntry se : fxList) { + if (se instanceof AbstractStruct) { + initEffect((AbstractStruct)se); + } + } + } + } + + private void initEffect(AbstractStruct as) + { + if (as == null) { + return; + } + StructEntry se = as.getField(EffectType.class, 0); + if (!(se instanceof EffectType)) { + return; + } + + int fxType = (as instanceof Effect2) ? 1 : 0; + + int opcode = ((EffectType)se).getValue(); + if (opcode == 7) { + int param1 = -1; + int param2 = -1; + if (fxType == 1) { + // EFF V2 + se = as.getAttribute(0x14); + if (se instanceof IsNumeric) { + param1 = ((IsNumeric)se).getValue(); + } + se = as.getAttribute(0x18); + if (se instanceof IsNumeric) { + param2 = ((IsNumeric)se).getValue(); + } + } else { + // EFF V1 + se = as.getAttribute(0x4); + if (se instanceof IsNumeric) { + param1 = ((IsNumeric)se).getValue(); + } + se = as.getAttribute(0x8); + if (se instanceof IsNumeric) { + param2 = ((IsNumeric)se).getValue(); + } + } + + if (param1 != -1 && param2 != -1) { + SegmentDef.SpriteType type = null; + int location = param2 & 0xf; + switch ((param2 >> 4) & 0xf) { + case 0: + type = SegmentDef.SpriteType.AVATAR; + break; + case 1: + type = SegmentDef.SpriteType.WEAPON; + break; + case 2: + type = SegmentDef.SpriteType.SHIELD; + break; + case 3: + type = SegmentDef.SpriteType.HELMET; + break; + default: + if ((param2 & 0xff) == 0xff) { + // affect all sprite colors + type = SegmentDef.SpriteType.AVATAR; + location = -1; + } + } + getColorInfo().add(type, location, param1); + } + } + } + + private void initEquipment(HashMap slotMap, int selectedWeaponSlot) + { + List itemList = cre.getFields(Item.class); + int ofsSlots = cre.getExtraOffset() + ((IsNumeric)cre.getAttribute(CreResource.CRE_OFFSET_ITEM_SLOTS)).getValue(); + for (HashMap.Entry entry : slotMap.entrySet()) { + ItemSlots slot = entry.getKey(); + int slotIdx = entry.getValue().intValue(); + int itemIdx = -1; + if (slot == ItemSlots.WEAPON) { + // special: determine weapon slot + int weaponIdx = ((IsNumeric)cre.getAttribute(ofsSlots + selectedWeaponSlot * 2)).getValue(); + if (weaponIdx == 1000) { + // selected weapon: fists + itemIdx = weaponIdx; + } else { + weaponIdx = Math.max(0, Math.min(4, weaponIdx)); + for (int idx = weaponIdx; idx >= 0; idx--) { + itemIdx = ((IsNumeric)cre.getAttribute(ofsSlots + (slotIdx + idx) * 2)).getValue(); + if (itemIdx >= 0 && itemIdx < itemList.size()) { + break; + } + } + } + } else { + itemIdx = ((IsNumeric)cre.getAttribute(ofsSlots + slotIdx * 2)).getValue(); + } + + initEquipmentItem(slot, itemIdx, itemList); + } + } + + private void initEquipmentV22() + { + int selectedWeaponSlot = 50; + HashMap slotMap = new HashMap() {{ + put(ItemSlots.HELMET, 0); + put(ItemSlots.ARMOR, 1); + put(ItemSlots.GAUNTLETS, 3); + put(ItemSlots.RING_LEFT, 4); + put(ItemSlots.RING_RIGHT, 5); + put(ItemSlots.AMULET, 6); + put(ItemSlots.BELT, 7); + put(ItemSlots.BOOTS, 8); + put(ItemSlots.WEAPON, 9); + put(ItemSlots.SHIELD, 10); + put(ItemSlots.CLOAK, 21); + }}; + + List itemList = cre.getFields(Item.class); + int ofsSlots = cre.getExtraOffset() + ((IsNumeric)cre.getAttribute(CreResource.CRE_OFFSET_ITEM_SLOTS)).getValue(); + for (HashMap.Entry entry : slotMap.entrySet()) { + ItemSlots slot = entry.getKey(); + int slotIdx = entry.getValue().intValue(); + int itemIdx = -1; + if (slot == ItemSlots.WEAPON || slot == ItemSlots.SHIELD) { + // special: weapon/shield combinations are grouped and determined by selected weapon slot + int weaponIdx = ((IsNumeric)cre.getAttribute(ofsSlots + selectedWeaponSlot * 2)).getValue(); + if (weaponIdx == 1000) { + // selected weapon: fists + itemIdx = weaponIdx; + } else { + weaponIdx = Math.max(0, Math.min(4, weaponIdx)); + for (int idx = weaponIdx; idx >= 0; idx--) { + itemIdx = ((IsNumeric)cre.getAttribute(ofsSlots + (slotIdx + idx * 2) * 2)).getValue(); + if (itemIdx >= 0 && itemIdx < itemList.size()) { + break; + } + } + } + } else { + itemIdx = ((IsNumeric)cre.getAttribute(ofsSlots + slotIdx * 2)).getValue(); + } + + initEquipmentItem(slot, itemIdx, itemList); + } + } + + private void initEquipmentItem(ItemSlots slot, int itemIndex, List itemList) + { + ResourceEntry itmEntry = null; + if (itemIndex == 1000) { + // weapon: fists + itmEntry = getFistWeapon(); + } else if (itemIndex >= 0 && itemIndex < itemList.size()) { + if (itemList.get(itemIndex) instanceof Item) { + String itmResref = ((IsTextual)((Item)itemList.get(itemIndex)).getAttribute(Item.CRE_ITEM_RESREF)).getText(); + itmEntry = ResourceFactory.getResourceEntry(itmResref + ".ITM"); + } + } + + if (itmEntry != null) { + try { + ItemInfo itemInfo = new ItemInfo(itmEntry); + equipment.put(slot, itemInfo); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + private ResourceEntry getFistWeapon() + { + ResourceEntry retVal = null; + + if ((getAnimationId() & 0xff00) == 0x6500) { + // hardcoded: monk animation activates special fists + int level = getClassLevel(false); + + // try 2DA table first + Table2da table = Table2daCache.get("MONKFIST.2DA"); + if (table != null) { + level = Math.max(1, Math.min(table.getRowCount(), level)); + String resref = table.get(level, 1); + retVal = ResourceFactory.getResourceEntry(resref + ".ITM"); + } + + // 2. fall back to hardcoded fists + if (retVal == null) { + int[] minLevels = { Integer.MIN_VALUE, 1, 3, 6, 9, 12, 15, 19, 25, Integer.MAX_VALUE }; + for (int i = 0; i < minLevels.length && retVal == null; i++) { + if (level < minLevels[i]) { + retVal = ResourceFactory.getResourceEntry("MFIST" + i + ".ITM"); + } + } + } + } + + // default fists + if (retVal == null) { + retVal = ResourceFactory.getResourceEntry("FIST.ITM"); + } + + return retVal; + } + + @Override + public int hashCode() + { + int hash = 7; + hash = 31 * hash + ((equipment == null) ? 0 : equipment.hashCode()); + hash = 31 * hash + ((colorInfo == null) ? 0 : colorInfo.hashCode()); + hash = 31 * hash + ((decoder == null) ? 0 : decoder.hashCode()); + hash = 31 * hash + ((cre == null) ? 0 : cre.hashCode()); + hash = 31 * hash + Integer.valueOf(allegianceOverride).hashCode(); + return hash; + } + + @Override + public boolean equals(Object o) + { + if (o == this) { + return true; + } + if (!(o instanceof CreatureInfo)) { + return false; + } + CreatureInfo other = (CreatureInfo)o; + boolean retVal = (this.equipment == null && other.equipment == null) || + (this.equipment != null && this.equipment.equals(other.equipment)); + retVal &= (this.colorInfo == null && other.colorInfo == null) || + (this.colorInfo != null && this.colorInfo.equals(other.colorInfo)); + retVal &= (this.decoder == null && other.decoder == null) || + (this.decoder != null && this.decoder.equals(other.decoder)); + retVal &= (this.cre == null && other.cre == null) || + (this.cre != null && this.cre.equals(other.cre)); + retVal &= (this.allegianceOverride == other.allegianceOverride); + return retVal; + } +} diff --git a/src/org/infinity/resource/cre/decoder/internal/CycleDef.java b/src/org/infinity/resource/cre/decoder/internal/CycleDef.java new file mode 100644 index 000000000..dee700b52 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/internal/CycleDef.java @@ -0,0 +1,183 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder.internal; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; + +import org.infinity.resource.cre.decoder.internal.SegmentDef.Behavior; +import org.infinity.resource.cre.decoder.internal.SegmentDef.SpriteType; +import org.infinity.resource.key.ResourceEntry; + +/** + * Definition of a single cycle for a specific direction in an animation sequence. + */ +public class CycleDef +{ + private final ArrayList cycles = new ArrayList<>(); + + private DirDef parent; + + /** + * Creates a new independent cycle definition with the specified parameters. + * Sprite type is assumed to be {@link SpriteType#AVATAR}. Behavior is assumed to be {@link Behavior#REPEAT}. + * @param bamResource the BAM resource + * @param cycle the BAM cycle index + */ + public CycleDef(ResourceEntry bamResource, int cycle) + { + this(null, bamResource, cycle, null, null); + } + + /** + * Creates a new independent cycle definition with the specified parameters. + * Behavior is assumed to be {@link Behavior#REPEAT}. + * @param bamResource the BAM resource + * @param cycle the BAM cycle index + * @param type the sprite type of the specified segment. + */ + public CycleDef(ResourceEntry bamResource, int cycle, SegmentDef.SpriteType type) + { + this(null, bamResource, cycle, type, null); + } + + /** + * Creates a new independent cycle definition with the specified parameters. + * Sprite type is assumed to be {@link SpriteType#AVATAR}. + * @param bamResource the BAM resource + * @param cycle the BAM cycle index + * @param behavior the playback behavior of the BAM cycle + */ + public CycleDef(ResourceEntry bamResource, int cycle, SegmentDef.Behavior behavior) + { + this(null, bamResource, cycle, null, behavior); + } + + /** + * Creates a new independent cycle definition with the specified parameters. + * @param bamResource the BAM resource + * @param cycle the BAM cycle index + * @param type the sprite type of the specified segment + * @param behavior the playback behavior of the BAM cycle + */ + public CycleDef(ResourceEntry bamResource, int cycle, SegmentDef.SpriteType type, SegmentDef.Behavior behavior) + { + this(null, bamResource, cycle, type, behavior); + } + + /** + * Creates a new cycle definition with the specified parameters linked to the specified {@link DirDef} instance. + * @param parent the parent {@code DirDef} instance + * @param bamResource the BAM resource + * @param cycle the BAM cycle index + * @param type the sprite type of the specified segment + * @param behavior the playback behavior of the BAM cycle + */ + public CycleDef(DirDef parent, ResourceEntry bamResource, int cycle, SegmentDef.SpriteType type, SegmentDef.Behavior behavior) + { + this.parent = parent; + addCycle(bamResource, cycle, type, behavior); + } + + public CycleDef(DirDef parent, Collection cycleInfo) + { + this.parent = parent; + addCycles(cycleInfo); + } + + /** Returns the parent {@link DirDef} instance linked to this object. */ + public DirDef getParent() { return parent; } + + /** Updates the parent link. Should not be called directly. */ + void setParent(DirDef parent) { this.parent = parent; } + + /** Provides access to the list of segment definitions for this cycle. */ + public List getCycles() { return cycles; } + + /** Adds a new cycle definition. */ + public void addCycle(ResourceEntry bamResource, int cycle) + { + addCycle(bamResource, cycle, null, null); + } + + /** Adds a new cycle definition. */ + public void addCycle(ResourceEntry bamResource, int cycle, SegmentDef.SpriteType type) + { + addCycle(bamResource, cycle, type, null); + } + + /** Adds a new cycle definition. */ + public void addCycle(ResourceEntry bamResource, int cycle, SegmentDef.SpriteType type, SegmentDef.Behavior behavior) + { + this.cycles.add(new SegmentDef(this, bamResource, cycle, type, behavior)); + } + + /** Adds new cycle definitions. */ + public void addCycles(Collection cycleInfo) + { + for (Iterator iter = cycleInfo.iterator(); iter.hasNext();) { + final SegmentDef sd = Objects.requireNonNull(iter.next(), "Segment definition cannot be null"); + if (!this.cycles.stream().anyMatch(sd2 -> sd2.equals(sd))) { + sd.setParent(this); + this.cycles.add(sd); + } + } + } + + /** Advances the animation by one frame for all segment definitions according to their respective behavior. */ + public void advance() + { + this.cycles.forEach(sd -> sd.advance()); + } + + /** Resets BAM cycles in all segment definitions back to the first frame. */ + public void reset() + { + this.cycles.forEach(sd -> sd.reset()); + } + + /** Determines the minimum number of frames in the whole list of segment definitions. */ + public int getMinimumFrames() + { + return cycles.stream().mapToInt(v -> v.getFrameCount()).min().orElse(0); + } + + /** Determines the maximum number of frames in the whole list of segment definitions. */ + public int getMaximumFrames() + { + return cycles.stream().mapToInt(v -> v.getFrameCount()).max().orElse(0); + } + + @Override + public String toString() + { + return "cycles=" + cycles.toString(); + } + + @Override + public int hashCode() + { + int hash = 7; + hash = 31 * hash + ((cycles == null) ? 0 : cycles.hashCode()); + return hash; + } + + @Override + public boolean equals(Object o) + { + if (o == this) { + return true; + } + if (!(o instanceof CycleDef)) { + return false; + } + CycleDef other = (CycleDef)o; + return (this.cycles == null && other.cycles == null) || + (this.cycles != null && this.cycles.equals(other.cycles)); + } +} diff --git a/src/org/infinity/resource/cre/decoder/internal/DecoderAttribute.java b/src/org/infinity/resource/cre/decoder/internal/DecoderAttribute.java new file mode 100644 index 000000000..1a930ad6c --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/internal/DecoderAttribute.java @@ -0,0 +1,118 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder.internal; + +import java.util.Objects; + +/** + * Class for accessing creature animation attributes. + */ +public class DecoderAttribute implements Comparable +{ + /** Supported data types for creature animation attributes. */ + public enum DataType { + /** Specifies an integer attribute. */ + INT(0), + /** Specifies a floating point attribute (double). */ + DECIMAL(0.0), + /** Specifies a boolean attribute. */ + BOOLEAN(false), + /** Specifies a string attribute. */ + STRING(""), + /** Specifies a custom data type of any kind. */ + USERDEFINED(null); + + public Object getDefaultValue() { return defValue; } + + private final Object defValue; + private DataType(Object defValue) { + this.defValue = defValue; + } + } + + private final String name; + private final DataType type; + private final Object defValue; + + /** Creates a new {@code Attribute} instance with the specified arguments. */ + public static DecoderAttribute with(String name, DataType type) + { + return new DecoderAttribute(name, type); + } + + /** Creates a new {@code Attribute} instance with the specified arguments. */ + public static DecoderAttribute with(String name, DataType type, Object defValue) + { + return new DecoderAttribute(name, type, defValue); + } + + public DecoderAttribute(String name, DataType type) + { + this(name, type, Objects.requireNonNull(type, "Attribute type cannot be null").getDefaultValue()); + } + + public DecoderAttribute(String name, DataType type, Object defValue) + { + this.name = Objects.requireNonNull(name, "Attribute name cannot be null"); + this.type = Objects.requireNonNull(type, "Attribute type cannot be null"); + this.defValue = defValue; + } + + /** Returns the attribute key. */ + public String getName() + { + return name; + } + + /** Returns the data type of the attribute value as one of several predefined enums. */ + public DataType getType() + { + return type; + } + + /** Returns a default value that is returned if the actual value is not available. */ + public Object getDefaultValue() + { + return defValue; + } + + @Override + public String toString() + { + return "key=" + name + ", type=" + type.toString(); + } + + @Override + public boolean equals(Object o) + { + if (o == this) { + return true; + } + if (!(o instanceof DecoderAttribute)) { + return false; + } + DecoderAttribute other = (DecoderAttribute)o; + boolean retVal = (this.name == null && other.name == null) || + (this.name != null && this.name.equals(other.name)); + retVal &= (this.type == null && other.type == null) || + (this.type != null && this.type.equals(other.type)); + return retVal; + } + + @Override + public int hashCode() + { + int hash = 7; + hash = 32 * hash + ((name == null) ? 0 : name.hashCode()); + hash = 32 * hash + ((type == null) ? 0 : type.hashCode()); + return hash; + } + + @Override + public int compareTo(DecoderAttribute o) + { + return name.compareTo(o.name); + } +} diff --git a/src/org/infinity/resource/cre/decoder/internal/DirDef.java b/src/org/infinity/resource/cre/decoder/internal/DirDef.java new file mode 100644 index 000000000..295179412 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/internal/DirDef.java @@ -0,0 +1,106 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder.internal; + +import java.util.Objects; + +import org.infinity.resource.cre.decoder.SpriteDecoder.Direction; + +/** + * Definition of a single direction in an animation sequence. + */ +public class DirDef +{ + private final Direction direction; + private final CycleDef cycle; + private final boolean mirrored; + + private SeqDef parent; + + /** + * Creates a new independent direction definition. + * @param dir the sprite direction + * @param mirrored whether the BAM frames of the sprite should be horizontally mirrored (fake eastern direction) + * @param cycle the cycle definition + */ + public DirDef(Direction dir, boolean mirrored, CycleDef cycle) + { + this(null, dir, mirrored, cycle); + } + + /** + * Creates a new direction definition linked to the specified {@link SeqDef} instance. + * @param parent the parent {@code SeqDef} instance + * @param dir the sprite direction + * @param mirrored whether the BAM frames of the sprite should be horizontally mirrored (fake eastern direction) + * @param cycle the cycle definition + */ + public DirDef(SeqDef parent, Direction dir, boolean mirrored, CycleDef cycle) + { + this.parent = parent; + this.direction = Objects.requireNonNull(dir, "Creature direction cannot be null"); + this.cycle = Objects.requireNonNull(cycle, "Cycle definition cannot be null"); + this.cycle.setParent(this); + this.mirrored = mirrored; + } + + /** Returns the parent {@link SeqDef} instance linked to this object. */ + public SeqDef getParent() { return parent; } + + /** Updates the parent link. Should not be called directly. */ + void setParent(SeqDef parent) { this.parent = parent; } + + /** Returns the direction defined by this instance. */ + public Direction getDirection() { return direction; } + + /** Returns the cycle definition associated with this direction. */ + public CycleDef getCycle() { return cycle; } + + /** Returns whether the BAM frames of this direction should be horizontally mirrored (fake eastern direction). */ + public boolean isMirrored() { return mirrored; } + + /** + * Advances the animation by one frame for all segment definitions in the associated cycle + * according to their respective behavior. + */ + public void advance() { cycle.advance(); } + + /** Resets BAM cycles in all segment definitions in the associated cycle back to the first frame. */ + public void reset() { cycle.reset(); } + + @Override + public String toString() + { + return "direction=" + direction.toString() + ", mirrored=" + Boolean.toString(mirrored) + ", cycle={" + cycle.toString() + "}"; + } + + @Override + public int hashCode() + { + int hash = 7; + hash = 31 * hash + ((direction == null) ? 0 : direction.hashCode()); + hash = 31 * hash + ((cycle == null) ? 0 : cycle.hashCode()); + hash = 31 * hash + Boolean.valueOf(mirrored).hashCode(); + return hash; + } + + @Override + public boolean equals(Object o) + { + if (o == this) { + return true; + } + if (!(o instanceof DirDef)) { + return false; + } + DirDef other = (DirDef)o; + boolean retVal = (this.direction == null && other.direction == null) || + (this.direction != null && this.direction.equals(other.direction)); + retVal &= (this.cycle == null && other.cycle == null) || + (this.cycle != null && this.cycle.equals(other.cycle)); + retVal &= (this.mirrored == other.mirrored); + return retVal; + } +} diff --git a/src/org/infinity/resource/cre/decoder/internal/FrameInfo.java b/src/org/infinity/resource/cre/decoder/internal/FrameInfo.java new file mode 100644 index 000000000..d76f82ef1 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/internal/FrameInfo.java @@ -0,0 +1,85 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder.internal; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Stroke; +import java.util.EnumMap; +import java.util.Objects; + +import org.infinity.resource.graphics.BamV1Decoder.BamV1Control; + +/** + * A structure that fully describes the location of a single BAM frame. + */ +public class FrameInfo +{ + /** Predefined stroke instance for drawing bounding boxes around creature sprite elements. */ + public static final Stroke STROKE_BOUNDING_BOX = new BasicStroke(1.0f); + + /** Predefined colors for the bounding box around creature sprite elements. */ + public static final EnumMap SPRITE_COLOR = new EnumMap(SegmentDef.SpriteType.class) {{ + put(SegmentDef.SpriteType.AVATAR, new Color(0x800000ff, true)); // blue + put(SegmentDef.SpriteType.WEAPON, new Color(0x80ff0000, true)); // red + put(SegmentDef.SpriteType.SHIELD, new Color(0x8000ff00, true)); // green + put(SegmentDef.SpriteType.HELMET, new Color(0x8000ffff, true)); // cyan + }}; + + /** Color definition as fallback solution. */ + public static final Color SPRITE_COLOR_DEFAULT = new Color(0x80808080, true); // gray + + private final BamV1Control bamControl; + private final SegmentDef segmentDef; + + public FrameInfo(BamV1Control bamControl, SegmentDef sd) + { + this.bamControl = Objects.requireNonNull(bamControl, "BAM controller cannot be null"); + this.segmentDef = Objects.requireNonNull(sd, "Segment definition cannot be null"); + } + + /** Returns the BAM control instance. */ + public BamV1Control getController() { return bamControl; } + + public SegmentDef getSegmentDefinition() { return segmentDef; } + + /** Returns the absolute cycle index. */ + public int getCycle() { return segmentDef.getCycleIndex(); } + + /** Returns the frame index relative to the cycle. */ + public int getFrame() { return segmentDef.getCurrentFrame(); } + + @Override + public String toString() + { + return "cycle=" + getCycle() + ", frame=" + getFrame(); + } + + @Override + public int hashCode() + { + int hash = 7; + hash = 31 * hash + ((bamControl == null) ? 0 : bamControl.hashCode()); + hash = 31 * hash + ((segmentDef == null) ? 0 : segmentDef.hashCode()); + return hash; + } + + @Override + public boolean equals(Object o) + { + if (o == this) { + return true; + } + if (!(o instanceof FrameInfo)) { + return false; + } + FrameInfo other = (FrameInfo)o; + boolean retVal = (this.bamControl == null && other.bamControl == null) || + (this.bamControl != null && this.bamControl.equals(other.bamControl)); + retVal &= (this.segmentDef == null && other.segmentDef == null) || + (this.segmentDef != null && this.segmentDef.equals(other.segmentDef)); + return retVal; + } +} diff --git a/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java b/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java new file mode 100644 index 000000000..b0abcdfc1 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java @@ -0,0 +1,293 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder.internal; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Locale; +import java.util.Objects; + +import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.Misc; +import org.infinity.util.io.StreamUtils; + +/** + * Provides useful information about equippable items. + */ +public class ItemInfo +{ + private final ColorInfo colorInfo = new ColorInfo(); + private final ResourceEntry itmEntry; + + private int flags; + private int category; + private int unusable; + private int unusableKits; + private String appearance; + // list of item abilities (sorted by index): contains ability type (1=melee, 2=range, 3=magical, 4=launcher) + private int[] abilities; + + /** + * This is a convenience method to speed up the process. + * Returns the category of the specified item resource. Returns -1 if category could not be determined. + */ + public static int getItemCategory(ResourceEntry itmEntry) + { + if (itmEntry != null) { + try (final InputStream is = itmEntry.getResourceDataAsStream()) { + byte[] sig = new byte[8]; + Misc.requireCondition(is.read(sig) == 8, "Could not read signature field"); + final String signature = new String(sig).toUpperCase(Locale.ENGLISH); + switch (signature) { + case "ITM V1 ": + case "ITM V1.1": + case "ITM V2.0": + break; + default: + throw new Exception("Not an item resource: " + itmEntry.getResourceName()); + } + Misc.requireCondition(is.skip(0x14) == 0x14, "Could not advance in data stream"); + return StreamUtils.readShort(is); + } catch (Exception e) { + e.printStackTrace(); + } + } + return -1; + } + + public ItemInfo(ResourceEntry itmEntry) throws Exception + { + this.itmEntry = Objects.requireNonNull(itmEntry, "ITM resource cannot be null"); + init(); + } + + /** Returns the {@code ResourceEntry} instance of the item resource. */ + public ResourceEntry getItemEntry() { return itmEntry; } + + /** Returns the item flags. */ + public int getFlags() { return flags; } + + /** Returns the item category. */ + public int getCategory() { return category; } + + /** Returns the general usability flags. */ + public int getUnusable() { return unusable; } + + /** Returns the kit-specific usability flags (packed value, kits #1 at bit 24, ..., kits #4 at bit 0). */ + public int getUnusableKits() { return unusableKits; } + + /** Returns the two-letter appearance code. */ + public String getAppearance() { return appearance; } + + /** Provides access to the {@link ColorInfo} instance associated with the item. */ + public ColorInfo getColorInfo() { return colorInfo; } + + /** Returns number of defined item abilities. */ + public int getAbilityCount() { return abilities.length; } + + /** Returns the type of the specified ability. */ + public int getAbility(int index) { return (index >= 0 && index < abilities.length) ? abilities[index] : -1; } + + private void init() throws IOException, Exception + { + try (final InputStream is = itmEntry.getResourceDataAsStream()) { + byte[] sig = new byte[8]; + // offset = 0x00 + Misc.requireCondition(is.read(sig) == 8, "Could not read signature field"); + final String signature = new String(sig).toUpperCase(Locale.ENGLISH); + switch (signature) { + case "ITM V1 ": + case "ITM V1.1": + case "ITM V2.0": + break; + default: + throw new Exception("Not an item resource: " + itmEntry.getResourceName()); + } + + // flags (common) + Misc.requireCondition(is.skip(0x10) == 0x10, "Could not advance in data stream"); + // offset = 0x18 + this.flags = StreamUtils.readInt(is); + + // category (common) + // offset = 0x1c + this.category = StreamUtils.readShort(is); + + // unusable (common) + // offset = 0x1e + this.unusable = StreamUtils.readInt(is); + + // appearance (common) + // offset = 0x22 + this.appearance = StreamUtils.readString(is, 2); + + // unusableKits (common for V1/V2.0 only) + this.unusableKits = 0; + if (!"ITM V1.1".equals(signature)) { + int v; + Misc.requireCondition(is.skip(5) == 5, "Could not advance in data stream"); + // offset = 0x29 + Misc.requireCondition((v = is.read()) != -1, "Could not read kits usability field"); + this.unusableKits |= (v << 24); + Misc.requireCondition(is.skip(1) == 1, "Could not advance in data stream"); + // offset = 0x2b + Misc.requireCondition((v = is.read()) != -1, "Could not read kits usability field"); + this.unusableKits |= (v << 16); + Misc.requireCondition(is.skip(1) == 1, "Could not advance in data stream"); + // offset = 0x2d + Misc.requireCondition((v = is.read()) != -1, "Could not read kits usability field"); + this.unusableKits |= (v << 8); + Misc.requireCondition(is.skip(1) == 1, "Could not advance in data stream"); + // offset = 0x2f + Misc.requireCondition((v = is.read()) != -1, "Could not read kits usability field"); + this.unusableKits |= v; + } else { + // to keep stream position synchronized + Misc.requireCondition(is.skip(0xc) == 0xc, "Could not advance in data stream"); + } + + // abilities (common: ofs, num, header) + Misc.requireCondition(is.skip(0x34) == 0x34, "Could not advance in data stream"); + // offset = 0x64 + int ofsAbil = StreamUtils.readInt(is); // abilities offset + int numAbil = StreamUtils.readShort(is); // abilities count + int ofsFx = StreamUtils.readInt(is); // effects offset + int idxFx = StreamUtils.readShort(is); // start index of global effects + int numFx = StreamUtils.readShort(is); // total effects count + ofsFx += idxFx * 0x30; // adjusting global effects offset + numFx -= idxFx; // adjusting global effects count + int curOfs = 0x72; // end of main structure + + // reading global color effects (attempt 1) + int skip; + if (ofsFx < ofsAbil) { + skip = ofsFx - curOfs; + Misc.requireCondition(is.skip(skip) == skip, "Could not advance in data stream"); + curOfs += skip; + // offset = [ofsFx] + curOfs += readEffects(is, numFx); + } + + // reading ability types + skip = ofsAbil - curOfs; + Misc.requireCondition(skip >= 0 && is.skip(skip) == skip, "Could not advance in data stream"); + curOfs += skip; + // offset = [ofsAbil] + curOfs += readAbilities(is, numAbil); + + // reading global color effects (attempt 2) + if (ofsFx >= ofsAbil) { + skip = ofsFx - curOfs; + Misc.requireCondition(skip >= 0 && is.skip(skip) == skip, "Could not advance in data stream"); + curOfs += skip; + // offset = [ofsFx] + curOfs += readEffects(is, numFx); + } + } + } + + // Processes global effects: only "set color" effect is considered + private int readEffects(InputStream is, int num) throws Exception + { + int retVal = 0; + while (num > 0) { + int opcode = StreamUtils.readShort(is); + if (opcode == 7) { + // set color + int target = is.read(); + is.read(); // skip power + int param1 = StreamUtils.readInt(is); + int param2 = StreamUtils.readInt(is); + int timing = is.read(); + Misc.requireCondition(is.skip(0x23) == 0x23, "Could not advance in data stream"); + if (target == 1 && timing ==2) { + // self target; on equip + SegmentDef.SpriteType type = null; + int location = param2 & 0xf; + switch ((param2 >> 4) & 0xf) { + case 0: + type = SegmentDef.SpriteType.AVATAR; + break; + case 1: + type = SegmentDef.SpriteType.WEAPON; + break; + case 2: + type = SegmentDef.SpriteType.SHIELD; + break; + case 3: + type = SegmentDef.SpriteType.HELMET; + break; + default: + if ((param2 & 0xff) == 0xff) { + type = SegmentDef.SpriteType.AVATAR; + location = -1; + } + } + getColorInfo().add(type, location, param1); + } + } else { + // sync stream offset + Misc.requireCondition(is.skip(0x2e) == 0x2e, "Could not advance in data stream"); + } + retVal += 0x30; + num--; + } + return retVal; // returns number of bytes read or skipped + } + + private int readAbilities(InputStream is, int num) throws Exception + { + int retVal = 0; + this.abilities = new int[num]; + while (num > 0) { + this.abilities[this.abilities.length - num] = StreamUtils.readShort(is); + retVal += 2; + Misc.requireCondition(is.skip(0x36) == 0x36, "Could not advance in data stream"); + retVal += 0x36; + num--; + } + return retVal; // returns number of bytes read or skipped + } + + @Override + public int hashCode() + { + int hash = 7; + hash = 31 * hash + ((colorInfo == null) ? 0 : colorInfo.hashCode()); + hash = 31 * hash + ((itmEntry == null) ? 0 : itmEntry.hashCode()); + hash = 31 * hash + flags; + hash = 31 * hash + category; + hash = 31 * hash + unusable; + hash = 31 * hash + unusableKits; + hash = 31 * hash + ((appearance == null) ? 0 : appearance.hashCode()); + hash = 31 * hash + ((abilities == null) ? 0 : abilities.hashCode()); + return hash; + } + + @Override + public boolean equals(Object o) + { + if (o == this) { + return true; + } + if (!(o instanceof ItemInfo)) { + return false; + } + ItemInfo other = (ItemInfo)o; + boolean retVal = (this.colorInfo == null && other.colorInfo == null) || + (this.colorInfo != null && this.colorInfo.equals(other.colorInfo)); + retVal &= (this.itmEntry == null && other.itmEntry == null) || + (this.itmEntry != null && this.itmEntry.equals(other.itmEntry)); + retVal &= this.flags == other.flags; + retVal &= this.category == other.category; + retVal &= this.unusable == other.unusable; + retVal &= this.unusableKits == other.unusableKits; + retVal &= (this.appearance == null && other.appearance == null) || + (this.appearance != null && this.appearance.equals(other.appearance)); + retVal &= (this.abilities == null && other.abilities == null) || + (this.abilities != null && this.abilities.equals(other.abilities)); + return retVal; + } +} diff --git a/src/org/infinity/resource/cre/decoder/internal/SegmentDef.java b/src/org/infinity/resource/cre/decoder/internal/SegmentDef.java new file mode 100644 index 000000000..e8cf998f9 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/internal/SegmentDef.java @@ -0,0 +1,357 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder.internal; + +import java.util.EnumMap; +import java.util.Objects; + +import org.infinity.resource.cre.decoder.SpriteUtils; +import org.infinity.resource.key.ResourceEntry; + +/** + * Definition of a single segment within a cycle definition. + */ +public class SegmentDef +{ + /** Indicates the sprite type for this particular BAM segment (such as avatar, weapon, shield or helmet). */ + public enum SpriteType { + /** Indicate that the current segment belongs to the creature animation. */ + AVATAR, + /** Indicate that the current segment belongs to the weapon overlay. */ + WEAPON, + /** Indicate that the current segment belongs to the shield overlay. */ + SHIELD, + /** Indicate that the current segment belongs to the helmet overlay. */ + HELMET, + } + + /** + * Indicates the playback behavior of this particular BAM segment. + */ + public enum Behavior { + /** + * Segment runs from start to end and repeats the process if other segments of the current cycle contain + * more frames. This is the default if no behavior has been specified. + */ + REPEAT, + /** + * Segment runs from start to end and stops even if other segments of the current cycle contain more frames. + */ + SINGLE, + /** + * Segment runs from start to end and continues to provide the last frame if other segments of the + * current cycle contain more frames. + */ + FREEZE, + /** + * Segment runs from start to the minimum number of cycle frames available in all segment definitions and stops. + */ + CUT, + /** The same as {@link #REPEAT} but runs from end to start. */ + REVERSE_REPEAT, + /** The same as {@link #SINGLE} but runs from end to start. */ + REVERSE_SINGLE, + /** The same as {@link #FREEZE} but runs from end to start. */ + REVERSE_FREEZE, + /** The same as {@link #CUT} but runs from end to start. */ + REVERSE_CUT; + + private static EnumMap opposites = new EnumMap(Behavior.class) {{ + put(REPEAT, REVERSE_REPEAT); + put(SINGLE, REVERSE_SINGLE); + put(FREEZE, REVERSE_FREEZE); + put(CUT, REVERSE_CUT); + put(REVERSE_REPEAT, REPEAT); + put(REVERSE_SINGLE, SINGLE); + put(REVERSE_FREEZE, FREEZE); + put(REVERSE_CUT, CUT); + }}; + + /** Returns the opposite value of the specified behavior regarding frame playback. */ + public static Behavior getOpposite(Behavior b) + { + return opposites.getOrDefault(b, REVERSE_REPEAT); + } + } + + private final ResourceEntry bamEntry; + private final int cycleIndex; + private final SpriteType type; + private final Behavior behavior; + private final int numFrames; + + private CycleDef parent; + private int curFrame; + + /** + * Determines the behavior variant specified by the given BAM animation suffix. + * If an exclamation mark is found it returns the opposite of the default behavior. + * Otherwise it will return the default behavior. + */ + public static Behavior getBehaviorOf(String suffix) + { + return getBehaviorOf(suffix, null); + } + + /** + * Determines the behavior variant specified by the given BAM animation suffix. + * If an exclamation mark is found it returns the opposite of the specified behavior. + * Otherwise it will return the specified behavior. + */ + public static Behavior getBehaviorOf(String suffix, Behavior behavior) + { + Behavior retVal = (behavior != null) ? behavior : Behavior.REPEAT; + if (suffix != null && suffix.indexOf('!') >= 0) { + retVal = Behavior.getOpposite(retVal); + } + return retVal; + } + + /** Returns the specified string without any behavior markers. */ + public static String fixBehaviorSuffix(String suffix) + { + String retVal = suffix; + if (retVal != null && retVal.indexOf('!') >= 0) { + retVal = retVal.replace("!", ""); + } + return retVal; + } + + /** + * Creates a new independent segment definition with the specified parameters. + * Sprite type is assumed to be {@link SpriteType#AVATAR}. Behavior is assumed to be {@link Behavior#REPEAT}. + * @param entry the BAM resource + * @param cycleIdx the BAM cycle index + */ + public SegmentDef(ResourceEntry entry, int cycleIdx) + { + this(null, entry, cycleIdx, SpriteType.AVATAR, null); + } + + /** + * Creates a new independent segment definition with the specified parameters. + * Behavior is assumed to be {@link Behavior#REPEAT}. + * @param entry the BAM resource + * @param cycleIdx the BAM cycle index + * @param type the sprite type of this segment. + */ + public SegmentDef(ResourceEntry entry, int cycleIdx, SpriteType type) + { + this(null, entry, cycleIdx, type, null); + } + + /** + * Creates a new independent segment definition with the specified parameters. + * @param entry the BAM resource + * @param cycleIdx the BAM cycle index + * @param type the sprite type of this segment. + * @param behavior the playback behavior of this segment. + */ + public SegmentDef(ResourceEntry entry, int cycleIdx, SpriteType type, Behavior behavior) + { + this(null, entry, cycleIdx, type, behavior); + } + + /** + * Creates a new segment definition with the specified parameters linked to the specified {@link CycleDef} instance. + * Sprite type is assumed to be {@link SpriteType#AVATAR}. Behavior is assumed to be {@link Behavior#REPEAT}. + * @param parent the parent {@code CycleDef} instance. + * @param entry the BAM resource + * @param cycleIdx the BAM cycle index + */ + public SegmentDef(CycleDef parent, ResourceEntry entry, int cycleIdx) + { + this(parent, entry, cycleIdx, SpriteType.AVATAR, null); + } + + /** + * Creates a new segment definition with the specified parameters linked to the specified {@link CycleDef} instance. + * Behavior is assumed to be {@link Behavior#REPEAT}. + * @param parent the parent {@code CycleDef} instance. + * @param entry the BAM resource + * @param cycleIdx the BAM cycle index + * @param type the sprite type of this segment. + */ + public SegmentDef(CycleDef parent, ResourceEntry entry, int cycleIdx, SpriteType type) + { + this(parent, entry, cycleIdx, type, null); + } + + /** + * Creates a new segment definition with the specified parameters linked to the specified {@link CycleDef} instance. + * @param parent the parent {@code CycleDef} instance. + * @param entry the BAM resource + * @param cycleIdx the BAM cycle index + * @param type the spriter type of this segment. + * @param behavior the playback behavior of this segment. + */ + public SegmentDef(CycleDef parent, ResourceEntry entry, int cycleIdx, SpriteType type, Behavior behavior) + { + this.parent = parent; + this.bamEntry = Objects.requireNonNull(entry, "BAM resource entry cannot be null"); + this.cycleIndex = cycleIdx; + this.type = (type != null) ? type : SpriteType.AVATAR; + this.behavior = (behavior != null) ? behavior : Behavior.REPEAT; + this.numFrames = SpriteUtils.getBamCycleFrames(this.bamEntry, this.cycleIndex); + reset(); + } + + /** Returns the parent {@link CycleDef} instance linked with this object. */ + public CycleDef getParent() { return parent; } + + /** Updates the parent link. Should not be called directly. */ + void setParent(CycleDef parent) { this.parent = parent; } + + /** Returns the BAM resource entry associated with the segment. */ + public ResourceEntry getEntry() { return bamEntry; } + + /** Returns the cycle index associated with the segment */ + public int getCycleIndex() { return cycleIndex; } + + /** Returns the sprite type assigned to the segment. */ + public SpriteType getSpriteType() { return type; } + + /** Returns the playback behavior for the current segment. */ + public Behavior getBehavior() { return behavior; } + + /** Returns the total number of frames in this cycle. */ + public int getFrameCount() { return numFrames; } + + /** Returns the current cycle frame. Returns -1 if no frame is currently selected. */ + public int getCurrentFrame() { return curFrame; } + + /** Advances the segment animation by one frame according to {@link Behavior}. */ + public void advance() + { + switch (behavior) { + case SINGLE: + if (curFrame >= 0 && curFrame < numFrames - 1) { + curFrame++; + } else { + curFrame = -1; + } + break; + case FREEZE: + if (curFrame < numFrames - 1) { + curFrame++; + } + break; + case CUT: + if (curFrame < getMinimumFrames() - 1) { + curFrame++; + } else { + curFrame = -1; + } + break; + case REVERSE_REPEAT: + if (curFrame > 0) { + curFrame--; + } else if (curFrame <= 0) { + curFrame = numFrames - 1; + } else if (curFrame >= getMaximumFrames() - 1) { + curFrame = numFrames - 1; + } + break; + case REVERSE_SINGLE: + if (curFrame > 0 && curFrame < numFrames) { + curFrame--; + } else { + curFrame = -1; + } + break; + case REVERSE_FREEZE: + if (curFrame > 0) { + curFrame--; + } else { + curFrame = -1; + } + break; + case REVERSE_CUT: + if (curFrame >= numFrames - getMinimumFrames() + 1) { + curFrame--; + } else { + curFrame = -1; + } + break; + default: // Repeat + if (curFrame < numFrames - 1) { + curFrame++; + } else if (curFrame >= numFrames - 1) { + curFrame = 0; + } else if (curFrame >= getMaximumFrames() - 1) { + curFrame = 0; + } + } + } + + /** Resets the BAM cycle back to the "first" frame as defined by {@link Behavior}. */ + public void reset() + { + switch (behavior) { + case REVERSE_REPEAT: + case REVERSE_SINGLE: + case REVERSE_FREEZE: + case REVERSE_CUT: + curFrame = numFrames - 1; + break; + default: + curFrame = 0; + } + } + + @Override + public String toString() + { + return "entry=" + bamEntry.toString() + ", cycle=" + cycleIndex + ", type=" + type.toString() + + ", behavior=" + behavior.toString() + ", numFrames=" + numFrames + ", curFrame=" + curFrame; + } + + @Override + public int hashCode() + { + int hash = 7; + hash = 31 * hash + ((bamEntry == null) ? 0 : bamEntry.hashCode()); + hash = 31 * hash + cycleIndex; + hash = 31 * hash + ((type == null) ? 0 : type.hashCode()); + hash = 31 * hash + ((behavior == null) ? 0 : behavior.hashCode()); + hash = 31 * hash + numFrames; + hash = 31 * hash + curFrame; + return hash; + } + + @Override + public boolean equals(Object o) + { + if (o == this) { + return true; + } + if (!(o instanceof SegmentDef)) { + return false; + } + SegmentDef other = (SegmentDef)o; + boolean retVal = (this.bamEntry == null && other.bamEntry == null) || + (this.bamEntry != null && this.bamEntry.equals(other.bamEntry)); + retVal &= (this.cycleIndex == other.cycleIndex); + retVal &= (this.type == null && other.type == null) || + (this.type != null && this.type.equals(other.type)); + retVal &= (this.behavior == null && other.behavior == null) || + (this.behavior != null && this.behavior.equals(other.behavior)); + retVal &= (this.numFrames == other.numFrames); + retVal &= (this.curFrame == other.curFrame); + return retVal; + } + + // Determines the minimum number of frames in the whole list of segment definitions. + private int getMinimumFrames() + { + return (parent != null) ? parent.getMinimumFrames() : 0; + } + + // Determines the maximum number of frames in the whole list of segment definitions. + private int getMaximumFrames() + { + return (parent != null) ? parent.getMaximumFrames() : numFrames; + } +} diff --git a/src/org/infinity/resource/cre/decoder/internal/SeqDef.java b/src/org/infinity/resource/cre/decoder/internal/SeqDef.java new file mode 100644 index 000000000..df4490807 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/internal/SeqDef.java @@ -0,0 +1,233 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder.internal; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; + +import org.infinity.resource.cre.decoder.SpriteDecoder; +import org.infinity.resource.cre.decoder.SpriteDecoder.Direction; +import org.infinity.resource.cre.decoder.SpriteDecoder.Sequence; +import org.infinity.resource.key.ResourceEntry; + +/** + * Definition of an animation sequence. + */ +public class SeqDef +{ + /** Full set of 16 directions (east & west). */ + public static final Direction[] DIR_FULL = Direction.values(); + /** Reduced set of 8 directions (east & west) */ + public static final Direction[] DIR_REDUCED = { Direction.S, Direction.SW, Direction.W, Direction.NW, + Direction.N, Direction.NE, Direction.E, Direction.SE }; + /** Full set of 9 non-eastern directions. */ + public static final Direction[] DIR_FULL_W = { Direction.S, Direction.SSW, Direction.SW, Direction.WSW, + Direction.W, Direction.WNW, Direction.NW, Direction.NNW, + Direction.N }; + /** Full set of 7 eastern directions. */ + public static final Direction[] DIR_FULL_E = { Direction.NNE, Direction.NE, Direction.ENE, Direction.E, + Direction.ESE, Direction.SE, Direction.SSE }; + /** Reduced set of 5 non-eastern directions. */ + public static final Direction[] DIR_REDUCED_W = { Direction.S, Direction.SW, Direction.W, Direction.NW, Direction.N }; + /** Reduced set of 3 eastern directions. */ + public static final Direction[] DIR_REDUCED_E = { Direction.NE, Direction.E, Direction.SE }; + + private final Sequence sequence; + private final ArrayList directions; + + /** + * Creates a new sequence definition with the specified directions. + * @param seq The defined sequence + * @param dirs One or more direction definitions + */ + public SeqDef(Sequence seq, DirDef... dirs) + { + this.sequence = Objects.requireNonNull(seq, "Sequence cannot be null"); + this.directions = new ArrayList<>(); + addDirections(dirs); + } + + /** Returns the sequence type this definition is assigned to. */ + public Sequence getSequence() { return sequence; } + + /** Provides access to the list of available direction definitions associated with this sequence. */ + public List getDirections() { return directions; } + + /** + * Appends list of direction definitions. + * Cycles of existing directions are appended. + */ + public void addDirections(DirDef... directions) + { + for (final DirDef dd : directions) { + DirDef dir = this.directions.stream().filter(d -> d.getDirection() == dd.getDirection()).findAny().orElse(null); + if (dir != null) { + dir.getCycle().addCycles(dd.getCycle().getCycles()); + } else { + dd.setParent(this); + this.directions.add(dd); + } + } + } + + /** Returns whether the sequence contains any directions with cycle definitions. */ + public boolean isEmpty() + { + boolean retVal = true; + if (!directions.isEmpty()) { + for (final DirDef dd : directions) { + if (!dd.getCycle().getCycles().isEmpty()) { + retVal = false; + break; + } + } + } + return retVal; + } + + @Override + public String toString() + { + return "sequence=" + sequence.toString() + ", directions={" + directions.toString() + "}"; + } + + @Override + public int hashCode() + { + int hash = 7; + hash = 31 * hash + ((sequence == null) ? 0 : sequence.hashCode()); + hash = 31 * hash + ((directions == null) ? 0 : directions.hashCode()); + return hash; + } + + @Override + public boolean equals(Object o) + { + if (o == this) { + return true; + } + if (!(o instanceof SeqDef)) { + return false; + } + SeqDef other = (SeqDef)o; + boolean retVal = (this.sequence == null && other.sequence == null) || + (this.sequence != null && this.sequence.equals(other.sequence)); + retVal &= (this.directions == null && other.directions == null) || + (this.directions != null && this.directions.equals(other.directions)); + return retVal; + } + + /** Convenience method: Returns a list of all BAM resources associated with the specified sequence definitions. */ + public static ResourceEntry[] getBamResourceList(SeqDef... sequences) + { + return getBamResourceList(null, sequences); + } + + /** + * Convenience method: Returns a list of all BAM resources associated with the specified sequence definitions + * matching the specified segment type. + */ + public static ResourceEntry[] getBamResourceList(SegmentDef.SpriteType type, SeqDef... sequences) + { + HashSet resources = new HashSet<>(); + for (final SeqDef sd : sequences) { + for (final DirDef dd : sd.directions) { + for (int i = 0, cnt = dd.getCycle().getCycles().size(); i < cnt; i++) { + if (type == null || dd.getCycle().getCycles().get(i).getSpriteType() == type) { + resources.add(dd.getCycle().getCycles().get(i).getEntry()); + } + } + } + } + return resources.toArray(new ResourceEntry[resources.size()]); + } + + /** + * Convenience method: Creates a fully defined sequence if specified directions are found within a single BAM resource. + * Sprite type is assumed to be {@link SegmentDef.SpriteType#AVATAR}. + * Behavior is assumed to be {@link SegmentDef.Behavior#REPEAT}. + * @param seq the animation {@link SpriteDecoder.Sequence}. + * @param directions List of directions to add. Cycle indices are advanced accordingly for each direction. + * @param mirrored indicates whether cycle indices are calculated in reversed direction + * @param bamResource the BAM resource used for all cycle definitions. + * @param cycleOfs the first BAM cycle index. Advanced by one for each direction. + * Cycle indices are processed in reversed direction if {@code mirrored} is {@code true}. + */ + public static SeqDef createSequence(Sequence seq, Direction[] directions, boolean mirrored, + ResourceEntry bamResource, int cycleOfs) + { + return createSequence(seq, directions, mirrored, + new ArrayList() {{ add(new SegmentDef(null, bamResource, cycleOfs)); }}); + } + + /** + * Convenience method: Creates a fully defined sequence if specified directions are found within a single BAM resource. + * Behavior is assumed to be {@link SegmentDef.Behavior#REPEAT}. + * @param seq the animation {@link SpriteDecoder.Sequence}. + * @param directions List of directions to add. Cycle indices are advanced accordingly for each direction. + * @param mirrored indicates whether cycle indices are calculated in reversed direction + * @param bamResource the BAM resource used for all cycle definitions. + * @param cycleOfs the first BAM cycle index. Advanced by one for each direction. + * Cycle indices are processed in reversed direction if {@code mirrored} is {@code true}. + * @param type the {@link SegmentDef.SpriteType} assigned to all cycle definitions. + */ + public static SeqDef createSequence(Sequence seq, Direction[] directions, boolean mirrored, + ResourceEntry bamResource, int cycleOfs, SegmentDef.SpriteType type) + { + return createSequence(seq, directions, mirrored, + new ArrayList() {{ add(new SegmentDef(null, bamResource, cycleOfs, type)); }}); + } + + /** + * Convenience method: Creates a fully defined sequence if specified directions are found within a single BAM resource. + * @param seq the animation {@link SpriteDecoder.Sequence}. + * @param directions List of directions to add. Cycle indices are advanced accordingly for each direction. + * @param mirrored indicates whether cycle indices are calculated in reversed direction + * @param bamResource the BAM resource used for all cycle definitions. + * @param cycleOfs the first BAM cycle index. Advanced by one for each direction. + * Cycle indices are processed in reversed direction if {@code mirrored} is {@code true}. + * @param type the {@link SegmentDef.SpriteType} assigned to all cycle definitions. + * @param behavior the {@link SegmentDef.Behavior} assigned to all cycle definitions. + */ + public static SeqDef createSequence(Sequence seq, Direction[] directions, boolean mirrored, + ResourceEntry bamResource, int cycleOfs, SegmentDef.SpriteType type, + SegmentDef.Behavior behavior) + { + return createSequence(seq, directions, mirrored, + new ArrayList() {{ add(new SegmentDef(null, bamResource, cycleOfs, type, behavior)); }}); + } + + /** + * Convenience method: Creates a fully defined sequence for all directions and their associated segment definitions. + * @param seq the animation {@link SpriteDecoder.Sequence}. + * @param directions List of directions to add. Cycle indices are advanced accordingly for each direction. + * @param mirrored indicates whether cycle indices are calculated in reversed direction + * @param cycleInfo collection of {@link SegmentDef} instances that are to be associated with the directions. + */ + public static SeqDef createSequence(Sequence seq, Direction[] directions, boolean mirrored, + Collection cycleInfo) + { + SeqDef retVal = new SeqDef(seq); + + DirDef[] dirs = new DirDef[Objects.requireNonNull(directions, "Array of creature directions cannot be null").length]; + List cycleDefs = new ArrayList<>(); + for (int i = 0; i < directions.length; i++) { + cycleDefs.clear(); + int inc = mirrored ? (directions.length - i - 1) : i; + for (Iterator iter = cycleInfo.iterator(); iter.hasNext();) { + SegmentDef sd = iter.next(); + cycleDefs.add(new SegmentDef(null, sd.getEntry(), sd.getCycleIndex() + inc, sd.getSpriteType(), sd.getBehavior())); + } + dirs[i] = new DirDef(retVal, directions[i], mirrored, new CycleDef(null, cycleDefs)); + } + retVal.addDirections(dirs); + + return retVal; + } +} diff --git a/src/org/infinity/resource/cre/decoder/tables/InfinityTables.java b/src/org/infinity/resource/cre/decoder/tables/InfinityTables.java new file mode 100644 index 000000000..6c2124ff6 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/tables/InfinityTables.java @@ -0,0 +1,255 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder.tables; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Objects; + +import org.infinity.resource.Profile; +import org.infinity.resource.cre.decoder.SpriteDecoder; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.IdsMap; +import org.infinity.util.IdsMapEntry; +import org.infinity.util.IniMap; +import org.infinity.util.Misc; + +/** + * A static class dedicated to processing Infinity Animation slots. + */ +public class InfinityTables +{ + private static final String[] TABLE_INFINITY_ANIMATIONS = { "", // not installed + "infinityanimations-v5.ids", // IA v5 or earlier + "infinityanimations-v6.ids", // IA v6 or later + }; + + /** + * Creates a creature animation INI definition from the Infinity Animations table. + * @param animationId the creature animation id + * @return a list of {@link IniMap} instances containing animation information. + * Returns an empty {@code IniMap} list if no information could be determined. + */ + public static List createIniMaps(int animationId) + { + List retVal = new ArrayList<>(); + + // determine correct IA version + int ver = Profile.getProperty(Profile.Key.GET_INFINITY_ANIMATIONS).intValue(); + if (ver < 1 || ver >= TABLE_INFINITY_ANIMATIONS.length) { + return retVal; + } + + ResourceEntry entry = SpriteTables.getTableResource(TABLE_INFINITY_ANIMATIONS[ver]); + if (entry == null) { + return retVal; + } + + IdsMap table = new IdsMap(entry); + retVal.addAll(processTable(table, animationId)); + + return retVal; + } + + /** Processes table data. */ + private static List processTable(IdsMap idsMap, int animationId) + { + List retVal = new ArrayList<>(); + if (idsMap == null) { + return retVal; + } + + // finding entry matching the specified animation id + animationId &= 0xffff; + IdsMapEntry idsEntry = idsMap.get(animationId); + if (idsEntry == null) { + return retVal; + } + + try { + // transforming IA table entry into internal table entry + String[] data = parseTableEntry(animationId, idsEntry.getSymbol()); + + // determining SpriteDecoder class instance + int classType = SpriteTables.valueToInt(data, SpriteTables.COLUMN_TYPE, -1); + SpriteDecoder.AnimationType animType = SpriteDecoder.AnimationType.values()[classType]; + Class cls = SpriteDecoder.getSpriteDecoderClass(animType); + if (cls != null) { + // calling method of signature: public static IniMap processTableData(String[] data); + Method method = cls.getMethod("processTableData", String[].class); + Object o = method.invoke(null, new Object[] { data }); + if (o instanceof IniMap) { + retVal.add((IniMap)o); + } + } + } catch (InvocationTargetException ite) { + if (ite.getCause() != null) { + ite.getCause().printStackTrace(); + } else { + ite.printStackTrace(); + } + } catch (Exception e) { + return retVal; + } + + + + return retVal; + } + + private static String[] parseTableEntry(int animationId, String entry) throws Exception + { + final String[] retVal = new String[SpriteTables.NUM_COLUMNS]; + + String[] items = Objects.requireNonNull(entry).split("\\s+"); + Misc.requireCondition(items.length > 5, "Infinity Animations table entry: too few entries"); + + String prefix = items[0].trim(); + Misc.requireCondition(prefix.length() > 0, "Animation prefix not available"); + + int space = Misc.toNumber(SpriteTables.valueToString(items, 3, "3"), 16, 3); + Misc.requireCondition(space > 0, "Invalid personal space: " + space); + int ellipse; + if (space <= 3) { + ellipse = 16; + } else if (space <= 5) { + ellipse = 24; + } else if (space <= 10) { + ellipse = 64; + } else { + ellipse = 72; + } + +// char type = SpriteTables.valueToString(items, 4, " ").charAt(0); + + final HashMap animationTypes = new HashMap() {{ + put("BGI MONSTER LONG 4 PART", SpriteDecoder.AnimationType.MONSTER_QUADRANT); + put("DRAGONS", SpriteDecoder.AnimationType.MONSTER_MULTI); + put("BGII SPLIT 4 PART", SpriteDecoder.AnimationType.MONSTER_MULTI_NEW); + put("BGI SIMPLE CASTER", SpriteDecoder.AnimationType.MONSTER_LAYERED_SPELL); + put("BROKEN ANKHEG", SpriteDecoder.AnimationType.MONSTER_ANKHEG); + put("CHARACTER BGII", SpriteDecoder.AnimationType.CHARACTER); + put("CHARACTER BGI", SpriteDecoder.AnimationType.CHARACTER_OLD); + put("BGII UNSPLIT EXT.", SpriteDecoder.AnimationType.MONSTER); + put("BGII SPLIT", SpriteDecoder.AnimationType.MONSTER); + put("BGI SIMPLE MONSTER", SpriteDecoder.AnimationType.MONSTER_OLD); + put("BGI MONSTER LONG", SpriteDecoder.AnimationType.MONSTER_LARGE_16); + put("IWD", SpriteDecoder.AnimationType.MONSTER_ICEWIND); + }}; + + String typeString = null; + SpriteDecoder.AnimationType animType = null; + for (int len = items.length - 5; len > 0 && animType == null; len--) { + typeString = concatItems(items, 5, len); + animType = animationTypes.get(typeString); + } + + int type = -1; + for (int i = SpriteDecoder.AnimationType.values().length - 1; i >= 0; i--) { + if (SpriteDecoder.AnimationType.values()[i] == animType) { + type = i; + break; + } + } + + if (type < 0) { + throw new Exception("Could not determine animation type"); + } + + int clown = SpriteTables.valueToString(items, items.length - 1, "unpaletted").equalsIgnoreCase("paletted") ? 1 : 0; + + int split = -1; + String height = ""; + String heightShield = ""; + switch (animType) { + case MONSTER_MULTI: + case MONSTER_MULTI_NEW: + split = 1; + break; + case MONSTER_LAYERED_SPELL: + { + String s = SpriteTables.valueToString(items, 8, ""); + if ("(BOW)".equalsIgnoreCase(s)) { + heightShield = "BW"; + } else if ("(S1)".equalsIgnoreCase(s)) { + height = "S1"; + } + break; + } + case CHARACTER: + case CHARACTER_OLD: + { + split = 0; + String s = SpriteTables.valueToString(items, 7, ""); + if (s.length() == 3) { + // height code specified + height = s; + } else { + // height code derived from prefix + height = prefix.substring(1); + } + break; + } + case MONSTER: + { + switch (typeString) { + case "BGII UNSPLIT EXT.": + split = 0; + break; + case "BGII SPLIT": + split = 1; + break; + } + break; + } + default: + } + + Arrays.fill(retVal, ""); + retVal[SpriteTables.COLUMN_ID] = String.format("0x%04x", animationId); + retVal[SpriteTables.COLUMN_RESREF] = prefix; + retVal[SpriteTables.COLUMN_TYPE] = Integer.toString(type); + retVal[SpriteTables.COLUMN_ELLIPSE] = Integer.toString(ellipse); + retVal[SpriteTables.COLUMN_SPACE] = Integer.toString(space); + retVal[SpriteTables.COLUMN_BLENDING] = "0"; + if (clown >= 0) { + retVal[SpriteTables.COLUMN_CLOWN] = Integer.toString(clown); + } + if (split >= 0) { + retVal[SpriteTables.COLUMN_SPLIT] = Integer.toString(split); + } + retVal[SpriteTables.COLUMN_HEIGHT] = height; + retVal[SpriteTables.COLUMN_HEIGHT_SHIELD] = heightShield; + + return retVal; + } + + /** Concatenates the specified strings to a single string, separated by a single space. */ + private static String concatItems(String[] data, int idx, int len) + { + StringBuilder sb = new StringBuilder(); + if (data == null || idx < 0 || idx >= data.length || len <= 0) { + return sb.toString(); + } + + if (data[idx] != null) { + sb.append(data[idx]); + } + + for (int i = 1; i < len; i++) { + if (data[idx + i] != null) { + sb.append(' ').append(data[idx + i]); + } + } + + return sb.toString(); + } + + private InfinityTables() { } +} diff --git a/src/org/infinity/resource/cre/decoder/tables/SpriteTables.java b/src/org/infinity/resource/cre/decoder/tables/SpriteTables.java new file mode 100644 index 000000000..1493d0290 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/tables/SpriteTables.java @@ -0,0 +1,409 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder.tables; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumMap; +import java.util.List; + +import org.infinity.resource.Profile; +import org.infinity.resource.cre.decoder.MonsterPlanescapeDecoder; +import org.infinity.resource.cre.decoder.SpriteDecoder; +import org.infinity.resource.key.BufferedResourceEntry; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.IniMap; +import org.infinity.util.Table2da; + +/** + * A static class that provides information and methods for hardcoded creature animations. + */ +public class SpriteTables +{ + // Number of non-PST table columns (id column included) + public static final int NUM_COLUMNS = 16; + // Number of PST table columns (id column included) + public static final int NUM_COLUMNS_PST = 9; + + // Header column index + public static final int COLUMN_ID = 0; // int (hex/composite) + // Column indices for non-PST sprite tables + public static final int COLUMN_RESREF = 1; // string + public static final int COLUMN_TYPE = 2; // int + public static final int COLUMN_ELLIPSE = 3; // int + public static final int COLUMN_SPACE = 4; // int + public static final int COLUMN_BLENDING = 5; // int (bitfield) + public static final int COLUMN_PALETTE = 6; // string + public static final int COLUMN_PALETTE2 = 7; // string + public static final int COLUMN_RESREF2 = 8; // string + public static final int COLUMN_TRANSLUCENT = 9; // bool + public static final int COLUMN_CLOWN = 10; // bool + public static final int COLUMN_SPLIT = 11; // bool + public static final int COLUMN_HELMET = 12; // bool + public static final int COLUMN_WEAPON = 13; // bool + public static final int COLUMN_HEIGHT = 14; // string + public static final int COLUMN_HEIGHT_SHIELD = 15; // string + // Column indices for PST-related sprite tables + public static final int COLUMN_PST_RESREF = 1; // string + public static final int COLUMN_PST_RESREF2 = 2; // string + public static final int COLUMN_PST_TYPE = 3; // int + public static final int COLUMN_PST_ELLIPSE = 4; // int + public static final int COLUMN_PST_SPACE = 5; // int + public static final int COLUMN_PST_CLOWN = 6; // int + public static final int COLUMN_PST_ARMOR = 7; // int + public static final int COLUMN_PST_BESTIARY = 8; // int + + private static final EnumMap> TableMaps = new EnumMap>(Profile.Game.class) {{ + put(Profile.Game.BG1, Arrays.asList("avatars-bg1.2da")); + put(Profile.Game.BG1TotSC, get(Profile.Game.BG1)); + + put(Profile.Game.IWD, Arrays.asList("avatars-iwd.2da")); + put(Profile.Game.IWDHoW, Arrays.asList("avatars-iwdhow.2da", "avatars-iwd.2da")); + put(Profile.Game.IWDHowTotLM, get(Profile.Game.IWDHoW)); + + put(Profile.Game.IWD2, Arrays.asList("avatars-iwd2.2da")); + + put(Profile.Game.PST, Arrays.asList("avatars-pst.2da")); + + put(Profile.Game.BG2SoA, Arrays.asList("avatars-bg2soa.2da")); + put(Profile.Game.BG2ToB, Arrays.asList("avatars-bg2tob.2da", "avatars-bg2soa.2da")); + put(Profile.Game.Tutu, get(Profile.Game.BG2ToB)); + put(Profile.Game.BGT, get(Profile.Game.BG2ToB)); + put(Profile.Game.Unknown, get(Profile.Game.BG2ToB)); + + put(Profile.Game.BG1EE, Arrays.asList("avatars-bgee.2da", "avatars-bg2ee.2da", "avatars-bg2tob.2da")); + put(Profile.Game.BG1SoD, get(Profile.Game.BG1EE)); + put(Profile.Game.BG2EE, Arrays.asList("avatars-bg2ee.2da", "avatars-bgee.2da", "avatars-bg2tob.2da")); + put(Profile.Game.EET, Arrays.asList("avatars-eet.2da", "avatars-bgee.2da", "avatars-bg2ee.2da", "avatars-bg2tob.2da")); + + put(Profile.Game.IWDEE, Arrays.asList("avatars-iwdee.2da", "avatars-bgee.2da", "avatars-bg2ee.2da")); + + put(Profile.Game.PSTEE, Arrays.asList("avatars-pstee.2da")); + }}; + + + /** + * Creates a creature animation INI definition from the table associated with the specified animation id. + * @param animationId the creature animation id + * @return a list of {@link IniMap} instances containing animation information. + * Returns an empty {@code IniMap} list if no information could be determined. + */ + public static List createIniMaps(int animationId) + { + return createIniMaps(Profile.getGame(), animationId); + } + + /** + * Creates a creature animation INI definition from the game-specific table associated with the specified animation id. + * @param game considers only table definitions for the specified game. + * @param animationId the creature animation id + * @return a list of {@link IniMap} instances containing animation information. + * Returns {@code null} if no information could be determined. + */ + public static List createIniMaps(Profile.Game game, int animationId) + { + List retVal = new ArrayList<>(); + if (game == null) { + game = Profile.getGame(); + } + + retVal.addAll(processInfinityAnimations(animationId)); + + if (retVal.isEmpty()) { + List tableNames = findTables(game); + for (final String tableName : tableNames) { + ResourceEntry tableEntry = getTableResource(tableName); + if (tableEntry != null) { + Table2da table = new Table2da(tableEntry); + if (table != null) { + List inis = processTable(game, table, animationId); + if (inis != null && !inis.isEmpty()) { + retVal.addAll(inis); + break; + } + } + } + } + } + + return retVal; + } + + /** + * Attempts to create a list of potential {@link IniMap} instances based on the specified arguments. + * @param game considers only table definitions for the specified game. + * @param table hardcoded table data used as source for generating a list of {@code IniMap} instances. + * @param animationId the creature animation id. + * @return a list of {@link IniMap} instances containing animation information. + * Returns an empty {@code IniMap} list if no information could be determined. + */ + public static List processTable(Profile.Game game, Table2da table, int animationId) + { + List retVal = null; + if (game == null || table == null) { + return retVal; + } + + if (game == Profile.Game.PST || game == Profile.Game.PSTEE) { + retVal = processTablePst(table, animationId); + } else { + retVal = processTable(table, animationId); + } + + return retVal; + } + + /** Processes tables for non-PST games. */ + private static List processTable(Table2da table, int animationId) + { + List retVal = new ArrayList<>(); + if (table == null) { + return retVal; + } + + // finding entry matching the specified animation id + int rowIndex = -1; + for (int row = 0, rowCount = table.getRowCount(); row < rowCount; row++) { + if (valueMatchesAnimationId(table.get(row, 0), animationId)) { + rowIndex = row; + break; + } + } + if (rowIndex < 0) { + return retVal; + } + + // loading all data into a String[] array + String[] data = new String[table.getColCount()]; + String defValue = table.getDefaultValue(); + for (int col = 0, colCount = table.getColCount(); col < colCount; col++) { + String value = table.get(rowIndex, col); + if (value.equalsIgnoreCase(defValue)) { + value = ""; + } + data[col] = value; + } + + // determining SpriteDecoder class instance + Class cls = SpriteDecoder.getSpriteDecoderClass(animationId); + if (cls != null) { + try { + // calling method of signature: public static IniMap processTableData(String[] data); + Method method = cls.getMethod("processTableData", String[].class); + Object o = method.invoke(null, new Object[] { data }); + if (o instanceof IniMap) { + retVal.add((IniMap)o); + } + } catch (InvocationTargetException ite) { + if (ite.getCause() != null) { + ite.getCause().printStackTrace(); + } else { + ite.printStackTrace(); + } + } catch (NoSuchMethodException | IllegalAccessException e) { + e.printStackTrace(); + } + } + + return retVal; + } + + /** Processes PST-style tables. */ + private static List processTablePst(Table2da table, int animationId) + { + List retVal = new ArrayList<>(); + if (table == null) { + return retVal; + } + + // finding entry matching the specified animation id + int rowIndex = -1; + // try special animations first + int id = animationId; + for (int row = 0, rowCount = table.getRowCount(); row < rowCount; row++) { + int v = valueToInt(new String[] {table.get(row, 0)}, 0, -1); + if (v == id) { + rowIndex = row; + break; + } + } + + // try regular animations next + if (rowIndex < 0) { + id = animationId & 0x0fff; + for (int row = 0, rowCount = table.getRowCount(); row < rowCount; row++) { + int v = valueToInt(new String[] {table.get(row, 0)}, 0, -1); + if (v == id) { + rowIndex = row; + break; + } + } + } + + if (rowIndex < 0) { + // no luck :( + return retVal; + } + + // loading all data into a String[] array + String[] data = new String[table.getColCount()]; + String defValue = table.getDefaultValue(); + for (int col = 0, colCount = table.getColCount(); col < colCount; col++) { + String value = table.get(rowIndex, col); + if (value.equalsIgnoreCase(defValue)) { + value = ""; + } + data[col] = value; + } + + // delegate parsing to SpriteDecoder class + retVal.add(MonsterPlanescapeDecoder.processTableData(data)); + + return retVal; + } + + /** Helper method for finding matching Infinity Animations entries. */ + private static List processInfinityAnimations(int animationId) + { + if (Profile.getProperty(Profile.Key.GET_INFINITY_ANIMATIONS) > 0) { + return InfinityTables.createIniMaps(animationId); + } + return new ArrayList(); + } + + + /** + * Convenience method: Safely retrieves an array item and returns it. + * Returns a default value if the item could not be retrieved or is {@code null}. + */ + public static String valueToString(String[] arr, int arrIdx, String defValue) + { + String retVal = defValue; + try { + retVal = arr[arrIdx]; + if (retVal == null) { + retVal = defValue; + } + } catch (Exception e) { + } + return retVal; + } + + /** Convenience method: Converts an array item into a numeric value. */ + public static int valueToInt(String[] arr, int arrIdx, int defValue) + { + int retVal = defValue; + try { + String s = arr[arrIdx]; + if (s.startsWith("0x") || s.startsWith("0X")) { + retVal = Integer.parseInt(s.substring(2), 16); + } else { + retVal = Integer.parseInt(s); + } + } catch (NullPointerException | ArrayIndexOutOfBoundsException | NumberFormatException e) { + } + return retVal; + } + + /** Convenience method: Returns the animation id from the specified array value. */ + public static int valueToAnimationId(String[] arr, int arrIdx, int defValue) + { + try { + return valueToInt(arr[arrIdx].split("_"), 0, defValue); + } catch (NullPointerException | ArrayIndexOutOfBoundsException e) { + return defValue; + } + } + + /** Convenience method: Checks if the specified animation id is covered by the id value (and mask) from the table. */ + public static boolean valueMatchesAnimationId(String value, int animationId) + { + boolean retVal = false; + if (value == null) { + return retVal; + } + + String[] parts = value.split("_"); + if (parts.length > 0) { + int base = valueToInt(new String[] {parts[0]}, 0, -1); + if (base >= 0) { + int range = 0; + if (parts.length > 1) { + range = valueToInt(new String[] {"0x" + parts[1]}, 0, 0); + base &= ~range; + } + retVal = (animationId >= base && animationId <= (base + range)); + } + } + + return retVal; + } + + /** + * Returns a list of names for tables associated with the specified game. + * @param game The game type. + * @return List of table names ordered by relevance. Returns an empty list if no tables could be determined. + */ + public static List findTables(Profile.Game game) + { + List retVal = TableMaps.get(game); + if (retVal == null) { + retVal = new ArrayList(); + } + return retVal; + } + + /** + * Returns a virtual {@code ResourceEntry} instance providing access to the underlying table data. + * @param fileName The filename of the table (without path). + * @return a {@link ResourceEntry} instance linked to the specified table. + * Returns {@code null} if table could not be opened. + */ + public static ResourceEntry getTableResource(String fileName) + { + ResourceEntry entry = null; + try { + try (ByteArrayOutputStream bos = new ByteArrayOutputStream(128 * 1024)) { + try (InputStream is = getResourceAsStream(null, fileName)) { + byte[] buf = new byte[65536]; + int len; + while ((len = is.read(buf)) > 0) { + bos.write(buf, 0, len); + } + } + entry = new BufferedResourceEntry(ByteBuffer.wrap(bos.toByteArray()), fileName); + } + } catch (Exception e) { + } + return entry; + } + + /** Returns an InputStream instance for reading from the specified resource. */ + static InputStream getResourceAsStream(Class c, String fileName) + { + InputStream retVal = null; + if (!fileName.isEmpty()) { + if (c == null) { + retVal = ClassLoader.getSystemResourceAsStream(fileName); + } + if (retVal == null) { + if (c == null) { + c = SpriteTables.class; + } + String basePath = c.getPackage().getName().replace('.', '/'); + String separator = (fileName.charAt(0) == '/') ? "" : "/"; + retVal = ClassLoader.getSystemResourceAsStream(basePath + separator + fileName); + } + } + return retVal; + } + + private SpriteTables() { } +} diff --git a/src/org/infinity/resource/cre/decoder/tables/avatars-bg1.2da b/src/org/infinity/resource/cre/decoder/tables/avatars-bg1.2da new file mode 100644 index 000000000..3330ec8f0 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/tables/avatars-bg1.2da @@ -0,0 +1,212 @@ +2DA V1.0 +* + RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD +0x0000 SPRING 0 0 0 0 * * * * 1 0 * * * * +0x0100 SPCHUNKS 0 0 0 0 * * SPSHADOW * 1 1 * * * * +0x0200 SPBLOOD 0 0 0 0 * * * * 0 0 0 * * * +0x0210 SPBLOOD 0 0 0 0 * * * * 0 0 1 * * * +0x0220 SPBLOOD 0 0 0 0 * * * * 0 0 2 * * * +0x0230 SPBLOOD 0 0 0 0 * * * * 0 0 3 * * * +0x0240 SPBLOOD 0 0 0 0 * * * * 0 0 4 * * * +0x0300 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * +0x0400 SKLH 0 0 0 0 * * SPSHADOW * 0 1 * * * * +0x0410 GLPHWRDH 0 0 0 0 * * SPSHADOW * 0 1 * * * * +0x0500 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * +0x0510 STNKCLDD 0 0 0 0 * * * * 1 0 0 * * * +0x0600 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * +0x0700 GREASEH 0 0 0 0 * * * * 0 0 * * * * +0x0710 GREASED 0 0 0 0 * * * * 0 1 * * * * +0x0800 WEBENTH 0 0 0 0 * * * * 1 0 * * * * +0x0810 WEBENTD 0 0 0 0 * * * * 1 0 * * * * +0x0900 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * +0x1000 MWYV 1 24 4 0 * * * * 0 * * * * * +0x1100 MTAN 1 32 5 0 * * * * 0 * 1 * * * +0x2000 MSIR 5 16 3 0 * * * * 1 * 1 * * BW +0x2100 UVOL 5 16 3 0 * * * * 0 * 1 * MS * +0x2200 MOGM 5 16 3 0 * * * * 1 * 1 1 S1 * +0x2300 MDKN 5 16 3 0 * * * * 0 * 1 1 * * +0x3000 MAKH 6 24 5 0 * * * * 0 * * * * * +0x4000 SNOMC 7 16 3 0 * * * * 1 * * * * * +0x4002 SNOMM 7 16 3 0 * * * * 1 * * * * * +0x4010 SNOWC 7 16 3 0 * * * * 1 * * * * * +0x4012 SNOWM 7 16 3 0 * * * * 1 * * * * * +0x4100 SSIMC 7 16 3 0 * * * * 1 * * * * * +0x4101 SSIMS 7 16 3 0 * * * * 1 * * * * * +0x4102 SSIMM 7 16 3 0 * * * * 1 * * * * * +0x4110 SSIWC 7 16 3 0 * * * * 1 * * * * * +0x4112 SSIWM 7 16 3 0 * * * * 1 * * * * * +0x4200 SHMCM 7 16 3 0 * * * * 1 * * * * * +0x4300 MSPLG1 7 32 5 0 * * * * 0 * * * * * +0x4400 LHMC 7 16 3 0 * * * * 1 * * * * * +0x4410 LHFC 7 16 3 0 * * * * 1 * * * * * +0x4500 LFAM 7 16 3 0 * * * * 1 * * * * * +0x4600 LDMF 7 16 3 0 * * * * 1 * * * * * +0x4700 LEMF 7 16 3 0 * * * * 1 * * * * * +0x4710 LEFF 7 16 3 0 * * * * 1 * * * * * +0x4800 LIMC 7 16 3 0 * * * * 1 * * * * * +0x5000 CHMC 9 16 3 0 * * * * 1 0 1 * WPL * +0x5001 CEMC 9 16 3 0 * * * * 1 0 1 * WPM * +0x5002 CDMC 9 16 3 0 * * * * 1 0 1 * WPS * +0x5003 CIMC 9 16 3 0 * * * * 1 0 1 * WPS * +0x5010 CHFC 9 16 3 0 * * * * 1 0 1 * WPM * +0x5011 CEFC 9 16 3 0 * * * * 1 0 1 * WPM * +0x5012 CDMC 9 16 3 0 * * * * 1 0 1 * WPS * +0x5013 CIFC 9 16 3 0 * * * * 1 0 1 * WPS * +0x5100 CHMF 9 16 3 0 * * * * 1 0 1 * WPL * +0x5101 CEMF 9 16 3 0 * * * * 1 0 1 * WPM * +0x5102 CDMF 9 16 3 0 * * * * 1 0 1 * WPS * +0x5103 CIMF 9 16 3 0 * * * * 1 0 1 * WPS * +0x5110 CHFF 9 16 3 0 * * * * 1 0 1 * WPM * +0x5111 CEFF 9 16 3 0 * * * * 1 0 1 * WPM * +0x5112 CDMF 9 16 3 0 * * * * 1 0 1 * WPS * +0x5113 CIFF 9 16 3 0 * * * * 1 0 1 * WPS * +0x5200 CHMW 9 16 3 0 * * * * 1 0 1 * WPL * +0x5201 CEMW 9 16 3 0 * * * * 1 0 1 * WPM * +0x5202 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * +0x5203 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * +0x5210 CHFW 9 16 3 0 * * * * 1 0 1 * WPM * +0x5211 CEFW 9 16 3 0 * * * * 1 0 1 * WPM * +0x5212 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * +0x5213 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * +0x5300 CHMT 9 16 3 0 * * * * 1 0 0 * WPL * +0x5301 CEMT 9 16 3 0 * * * * 1 0 0 * WPM * +0x5302 CDMT 9 16 3 0 * * * * 1 0 0 * WPS * +0x5303 CIMT 9 16 3 0 * * * * 1 0 0 * WPS * +0x5310 CHFT 9 16 3 0 * * * * 1 0 0 * WPM * +0x5311 CEFT 9 16 3 0 * * * * 1 0 0 * WPM * +0x5312 CDMT 9 16 3 0 * * * * 1 0 0 * WPS * +0x5313 CIFT 9 16 3 0 * * * * 1 0 0 * WPS * +0x6000 CHMC 9 16 3 0 * * * * 1 0 1 * WPL * +0x6001 CEMC 9 16 3 0 * * * * 1 0 1 * WPM * +0x6002 CDMC 9 16 3 0 * * * * 1 0 1 * WPS * +0x6003 CIMC 9 16 3 0 * * * * 1 0 1 * WPS * +0x6004 CDMC 9 16 3 0 * * * * 1 0 1 * WPS * +0x6005 CHMC 9 16 3 0 * * * * 1 0 1 * WPL * +0x6010 CHFC 9 16 3 0 * * * * 1 0 1 * WPM * +0x6011 CEFC 9 16 3 0 * * * * 1 0 1 * WPM * +0x6012 CDMC 9 16 3 0 * * * * 1 0 1 * WPS * +0x6013 CIFC 9 16 3 0 * * * * 1 0 1 * WPS * +0x6014 CIFC 9 16 3 0 * * * * 1 0 1 * WPS * +0x6015 CHFC 9 16 3 0 * * * * 1 0 1 * WPM * +0x6100 CHMF 9 16 3 0 * * * * 1 0 1 * WPL * +0x6101 CEMF 9 16 3 0 * * * * 1 0 1 * WPM * +0x6102 CDMF 9 16 3 0 * * * * 1 0 1 * WPS * +0x6103 CIMF 9 16 3 0 * * * * 1 0 1 * WPS * +0x6104 CDMF 9 16 3 0 * * * * 1 0 1 * WPS * +0x6105 CHMF 9 16 3 0 * * * * 1 0 1 * WPL * +0x6110 CHFF 9 16 3 0 * * * * 1 0 1 * WPM * +0x6111 CEFF 9 16 3 0 * * * * 1 0 1 * WPM * +0x6112 CDMF 9 16 3 0 * * * * 1 0 1 * WPS * +0x6113 CIFF 9 16 3 0 * * * * 1 0 1 * WPS * +0x6114 CIFF 9 16 3 0 * * * * 1 0 1 * WPS * +0x6115 CHFF 9 16 3 0 * * * * 1 0 1 * WPM * +0x6200 CHMW 9 16 3 0 * * * * 1 0 1 * WPL * +0x6201 CEMW 9 16 3 0 * * * * 1 0 1 * WPM * +0x6202 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * +0x6203 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * +0x6204 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * +0x6205 CHMW 9 16 3 0 * * * * 1 0 1 * WPL * +0x6210 CHFW 9 16 3 0 * * * * 1 0 1 * WPM * +0x6211 CEFW 9 16 3 0 * * * * 1 0 1 * WPM * +0x6212 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * +0x6213 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * +0x6214 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * +0x6215 CHFW 9 16 3 0 * * * * 1 0 1 * WPM * +0x6300 CHMT 9 16 3 0 * * * * 1 0 0 * WPL * +0x6301 CEMT 9 16 3 0 * * * * 1 0 0 * WPM * +0x6302 CDMT 9 16 3 0 * * * * 1 0 0 * WPS * +0x6303 CIMT 9 16 3 0 * * * * 1 0 0 * WPS * +0x6304 CDMT 9 16 3 0 * * * * 1 0 0 * WPS * +0x6305 CHMT 9 16 3 0 * * * * 1 0 0 * WPL * +0x6310 CHFT 9 16 3 0 * * * * 1 0 0 * WPM * +0x6311 CEFT 9 16 3 0 * * * * 1 0 0 * WPM * +0x6312 CDMT 9 16 3 0 * * * * 1 0 0 * WPS * +0x6313 CIFT 9 16 3 0 * * * * 1 0 0 * WPS * +0x6314 CIFT 9 16 3 0 * * * * 1 0 0 * WPS * +0x6315 CHFT 9 16 3 0 * * * * 1 0 0 * WPM * +0x6400 UDRZ 9 16 3 0 * * * * 1 * 1 0 WPM * +0x6401 UELM 9 16 3 0 * * * * 0 * 0 0 WPM * +0x6402 CMNK 9 16 3 0 * * * * 1 * 0 * WPM * +0x6403 MSKL 9 16 3 0 * * * * 1 * 1 * WPM * +0x6404 USAR 9 16 3 0 * * * * 0 * 0 0 WPL * +0x6405 MDGU 9 16 3 0 * * * * 1 * 1 * WPM * +0x6406 MDGU 9 24 5 0 * * * * 1 * 1 * WPM * +0x7000 MOGH 11 16 3 0 * * * * 1 * * * * * +0x7001 MOGN 11 16 3 0 * * * * 1 * * * * * +0x7100 MBAS 11 32 5 0 * * * * 0 * * * * * +0x7101 MBAS 11 32 5 0 MBAS_GR * * * 0 * * * * * +0x7200 MBER 11 16 3 0 MBER_BL * * * 0 * * * * * +0x7201 MBER 11 16 3 0 * * * * 0 * * * * * +0x7202 MBER 11 16 3 0 MBER_CA * * * 0 * * * * * +0x7203 MBER 11 16 3 0 MBER_PO * * * 0 * * * * * +0x7400 MDOG 11 16 3 0 MDOG_WI * * * 0 * * * * * +0x7401 MDOG 11 16 3 0 MDOG_WA * * * 0 * * * * * +0x7500 MDOP 11 16 3 0 * * * * 0 * * * * * +0x7501 MDOP 11 16 3 0 MDOP_GR * * * 0 * * * * * +0x7600 METT 11 16 3 0 * * * * 0 * * * * * +0x7700 MGHL 11 16 3 0 * * * * 0 * * * * * +0x7701 MGHL 11 16 3 0 MGHL_RE * * * 0 * * * * * +0x7702 MGHL 11 16 3 0 MGHL_GA * * * 0 * * * * * +0x7800 MGIB 11 16 3 0 * * * * 0 * * * * * +0x7900 MSLI 11 24 4 0 MSLI_GR * * 1 0 * * * * * +0x7901 MSLI 11 24 4 0 MSLI_OL * * 1 0 * * * * * +0x7902 MSLI 11 24 4 0 MSLI_MU * * 1 0 * * * * * +0x7903 MSLI 11 24 4 0 MSLI_OC * * 1 0 * * * * * +0x7904 MSLI 11 24 4 0 * * * 1 0 * * * * * +0x7a00 MSPI 11 16 3 0 MSPI_GI * * * 0 * * * * * +0x7a01 MSPI 11 16 3 0 MSPI_HU * * * 0 * * * * * +0x7a02 MSPI 11 16 3 0 MSPI_PH * * * 0 * * * * * +0x7a03 MSPI 11 16 3 0 MSPI_SW * * * 0 * * * * * +0x7a04 MSPI 11 16 3 0 MSPI_WR * * * 0 * * * * * +0x7b00 MWLF 11 16 3 0 * * * * 0 * * * * * +0x7b01 MWLF 11 16 3 0 MWLF_WO * * * 0 * * * * * +0x7b02 MWLF 11 16 3 0 MWLF_DI * * * 0 * * * * * +0x7b03 MWLF 11 16 3 0 MWLF_WI * * * 0 * * * * * +0x7b04 MWLF 11 16 3 0 MWLF_VA * * * 0 * * * * * +0x7b05 MWLF 11 16 3 0 MWLF_DR * * * 0 * * * * * +0x7c00 MXVT 11 16 3 0 * * * * 1 * * * * * +0x7c01 MTAS 11 16 3 0 * * * * 0 * * * * * +0x7d00 MZOM 11 16 3 0 * * * * 1 * * * * * +0x7e00 MWER 11 16 3 0 * * * * 0 * * * * * +0x7e01 MGWE 11 16 3 0 * * * * 0 * * * * * +0x8000 MGNL 4 16 3 0 * * * * * * * * S1 HB +0x8100 MHOB 4 16 3 0 * * * * * * * * S1 BW +0x8200 MKOB 4 16 3 0 * * * * * * * * SS BW +0x9000 MOGR 12 16 3 0 * * * * 1 * * * * * +0xa000 MWYV 13 16 3 0 * * * * 0 * * * * * +0xa100 MCAR 13 16 3 0 * * * * 0 * * * * * +0xb000 ACOW 14 32 5 0 * * * * 0 * * * * * +0xb100 AHRS 14 32 5 0 * * * * 0 * * * * * +0xb200 NBEGL 14 16 3 0 * * * * 1 * * * * * +0xb210 NPROL 14 16 3 0 * * * * 1 * * * * * +0xb300 NBOYL 14 16 3 0 * * * * 1 * * * * * +0xb310 NGRLL 14 16 3 0 * * * * 1 * * * * * +0xb400 NFAML 14 16 3 0 * * * * 1 * * * * * +0xb410 NFAWL 14 16 3 0 * * * * 1 * * * * * +0xb500 NSIML 14 16 3 0 * * * * 1 * * * * * +0xb510 NSIWL 14 16 3 0 * * * * 1 * * * * * +0xb600 NNOML 14 16 3 0 * * * * 1 * * * * * +0xb610 NNOWL 14 16 3 0 * * * * 1 * * * * * +0xb700 NSLVL 14 16 3 0 * * * * 1 * * * * * +0xc000 ABAT 15 12 3 0 * * * * 0 * * * * * +0xc100 ACAT 15 12 3 0 * * * * 0 * * * * * +0xc200 ACHK 15 12 3 0 * * * * 0 * * * * * +0xc300 ARAT 15 12 3 0 * * * * 0 * * * * * +0xc400 ASQU 15 12 3 0 * * * * 0 * * * * * +0xc500 ABAT 15 12 3 0 * * * * 0 * * * * * +0xc600 NBEGH 15 16 3 0 * * * * 1 * * * * * +0xc610 NPROH 15 16 3 0 * * * * 1 * * * * * +0xc700 NBOYH 15 16 3 0 * * * * 1 * * * * * +0xc710 NGRLH 15 16 3 0 * * * * 1 * * * * * +0xc800 NFAMH 15 16 3 0 * * * * 1 * * * * * +0xc810 NFAWH 15 16 3 0 * * * * 1 * * * * * +0xc900 NSIMH 15 16 3 0 * * * * 1 * * * * * +0xc910 NSIWH 15 16 3 0 * * * * 1 * * * * * +0xca00 NNOMH 15 16 3 0 * * * * 1 * * * * * +0xca10 NNOWH 15 16 3 0 * * * * 1 * * * * * +0xcb00 NSLVH 15 16 3 0 * * * * 1 * * * * * +0xd000 AEAGG1 16 0 3 0 * * * * 0 * * * * * +0xd100 AGULG1 16 0 3 0 * * * * 0 * * * * * +0xd200 AVULG1 16 0 3 0 * * * * 0 * * * * * +0xd300 ABIRG1 16 0 3 0 * * * * 0 * * * * * +0xd400 ABIRG1 16 0 3 0 * * * * 0 * * * * * diff --git a/src/org/infinity/resource/cre/decoder/tables/avatars-bg2ee.2da b/src/org/infinity/resource/cre/decoder/tables/avatars-bg2ee.2da new file mode 100644 index 000000000..f5176f20a --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/tables/avatars-bg2ee.2da @@ -0,0 +1,515 @@ +2DA V1.0 +* + RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD +0x0000 SPRING 0 0 0 0 * * * * 1 0 * * * * +0x0001 SPFLAMES 0 0 0 7 * * * * 0 0 * * * * +0x0002 SPRDRASI 0 0 0 7 * * * * 0 0 * * * * +0x0003 SPFLAMES 0 0 0 7 SPFLAMEB * * * 0 0 * * * * +0x0004 SPRDRASI 0 0 0 7 SPGDRASI * * * 0 0 * * * * +0x0100 SPCHUNKS 0 0 0 0 * * SPSHADOW * 1 1 * * * * +0x0101 BGLRYU 0 0 3 7 * * * * * 0 * * * * +0x0102 SPCL236U 0 0 3 7 * * * * * 0 * * * * +0x0200 SPBLOOD 0 0 0 0 * * * * 0 0 0 * * * +0x0210 SPBLOOD 0 0 0 0 * * * * 0 0 1 * * * +0x0220 SPBLOOD 0 0 0 0 * * * * 0 0 2 * * * +0x0230 SPBLOOD 0 0 0 0 * * * * 0 0 3 * * * +0x0240 SPBLOOD 0 0 0 0 * * * * 0 0 4 * * * +0x0300 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * +0x0301 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * +0x0400 SKLH 0 0 0 0 * * SPSHADOW * 0 1 * * * * +0x0410 GLPHWRDH 0 0 0 1 * * SPSHADOW * 0 1 * * * * +0x0500 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * +0x0510 STNKCLDD 0 0 0 0 * * * * 1 0 0 * * * +0x0520 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * +0x0600 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * +0x0610 SPICESTM 0 0 0 1 * * * * 0 0 0 * * * +0x0700 GREASEH 0 0 0 0 * * * * 0 0 * * * * +0x0710 GREASED 0 0 0 0 * * * * 0 1 * * * * +0x0800 WEBENTH 0 0 0 0 * * * * 1 0 * * * * +0x0810 WEBENTD 0 0 0 0 * * * * 1 0 * * * * +0x0900 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * +0x0910 SPMETSWM 0 0 0 1 * * * * 0 1 0 * * * +0x0a00 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * +0x0a01 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * +0x0a02 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * +0x0a03 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * +0x0a04 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * +0x0a10 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * +0x0a11 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * +0x0a12 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * +0x0a13 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * +0x0a14 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * +0x0a23 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * +0x0a24 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * +0x0b00 SPCSPRA2 0 0 0 1 * * * * 0 0 * * * * +0x0b01 SPCCOLDL 0 0 0 1 * * * * 0 0 * * * * +0x0b02 SPPRISM2 0 0 0 1 * * * * 0 0 * * * * +0x0b03 SPCSPRA3 0 0 0 1 * * * * 0 0 * * * * +0x0b04 SPPRISM3 0 0 0 1 * * * * 0 0 * * * * +0x0c00 SPSTRMVA 0 0 0 1 * * * * 0 1 * * SPSTRMVB * +0x0c10 SPSTRMVA 0 0 0 1 * * * * 0 1 * * SPSTRMVB * +0x1000 MWYV 1 24 4 0 * * * * 0 * * * * * +0x1003 MWYV 1 24 4 0 MWYV_WH * * * 0 * * * * * +0x1004 MWYV 1 24 4 0 MWYV_AL * * * 0 * * * * * +0x1100 MTAN 1 32 5 0 * * * * 0 * 1 * * * +0x1101 MWDR 1 72 7 0 * * * * 0 * * * * * +0x1102 MTAN 1 32 5 0 MTAN_BL * * * 0 * 1 * * * +0x1103 MTAN 1 32 5 0 MTAN_GR * * * 0 * 1 * * * +0x1104 MTAN 1 32 5 0 MTAN_RD * * * 0 * 1 * * * +0x1105 MWDR 1 72 7 0 MWDR_GR * * * 0 * * * * * +0x1200 MDR1 2 72 13 0 * * * * 0 1 * * * * +0x1201 MDR2 2 93 13 0 * * * * 0 1 * * * * +0x1202 MDR3 2 72 13 0 * * * * 0 1 * * * * +0x1203 MDR1 2 72 13 0 MDR1_GR * * * 0 1 * * * * +0x1204 MDR1 2 72 13 0 MDR1_AQ * * * 0 1 * * * * +0x1205 MDR1 2 72 13 0 MDR1_BL * * * 0 1 * * * * +0x1206 MDR1 2 72 13 0 MDR1_BR * * * 0 1 * * * * +0x1207 MDR1 2 72 13 0 MDR1_MC * * * 0 1 * * * * +0x1208 MDR1 2 72 13 0 MDR1_PU * * * 0 1 * * * * +0x1300 MDEM 3 32 5 0 * * * * 0 1 * * * * +0x2000 MSIR 5 16 3 0 * * * * 1 * 1 * * BW +0x2100 UVOL 5 16 3 0 * * * * 0 * 1 * MS * +0x2200 MOGM 5 16 3 0 * * * * 1 * 1 1 S1 * +0x2300 MDKN 5 16 3 0 * * * * 0 * 1 1 * * +0x3000 MAKH 6 24 5 0 * * * * 0 * * * * * +0x4000 SNOMC 7 16 3 0 * * * * 1 * * * * * +0x4001 SNONE 7 0 0 0 * * * * 0 * * * * * +0x4002 SNOMM 7 16 3 0 * * * * 1 * * * * * +0x4010 SNOWC 7 16 3 0 * * * * 1 * * * * * +0x4012 SNOWM 7 16 3 0 * * * * 1 * * * * * +0x4100 SSIMC 7 16 3 0 * * * * 1 * * * * * +0x4101 SSIMS 7 16 3 0 * * * * 1 * * * * * +0x4102 SSIMM 7 16 3 0 * * * * 1 * * * * * +0x4110 SSIWC 7 16 3 0 * * * * 1 * * * * * +0x4112 SSIWM 7 16 3 0 * * * * 1 * * * * * +0x4200 SHMCM 7 16 3 0 * * * * 1 * * * * * +0x4300 MSPLG1 7 32 5 0 * * * * 0 * * * * * +0x4400 LHMC 7 16 3 0 * * * * 1 * * * * * +0x4410 LHFC 7 16 3 0 * * * * 1 * * * * * +0x4500 LFAM 7 16 3 0 * * * * 1 * * * * * +0x4600 LDMF 7 16 3 0 * * * * 1 * * * * * +0x4700 LEMF 7 16 3 0 * * * * 1 * * * * * +0x4710 LEFF 7 16 3 0 * * * * 1 * * * * * +0x4800 LIMC 7 16 3 0 * * * * 1 * * * * * +0x5000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * +0x5001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * +0x5002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * +0x5003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH +0x5010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * +0x5011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * +0x5012 CDFB 8 16 3 0 * * CDFC * 1 1 1 * WQS * +0x5013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * +0x5100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * +0x5101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * +0x5102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * +0x5103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH +0x5110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * +0x5111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * +0x5112 CDFB 8 16 3 0 * * CDFF * 1 1 1 * WQS * +0x5113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * +0x5200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * +0x5201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * +0x5202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * +0x5210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * +0x5211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * +0x5212 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS * +0x5300 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * +0x5301 CEMT 8 16 3 0 * * CEMT * 1 1 0 * WQM * +0x5302 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * +0x5303 CIMT 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH +0x5310 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * +0x5311 CEFT 8 16 3 0 * * CEFT * 1 1 0 * WQM * +0x5312 CDFT 8 16 3 0 * * CDFT * 1 1 0 * WQS * +0x5313 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS * +0x6000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * +0x6001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * +0x6002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * +0x6003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH +0x6004 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS WQH +0x6005 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * +0x6010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * +0x6011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * +0x6012 CDFB 8 16 3 0 * * CDFC * 1 1 1 * WQS * +0x6013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * +0x6014 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS WQH +0x6015 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * +0x6100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * +0x6101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * +0x6102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * +0x6103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH +0x6104 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS WQH +0x6105 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * +0x6110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * +0x6111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * +0x6112 CDFB 8 16 3 0 * * CDFF * 1 1 1 * WQS * +0x6113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * +0x6114 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS WQH +0x6115 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * +0x6200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * +0x6201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * +0x6202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * +0x6204 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH +0x6205 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * +0x6210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * +0x6211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * +0x6212 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS * +0x6214 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS WQH +0x6215 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * +0x6300 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * +0x6301 CEMT 8 16 3 0 * * CEMT * 1 1 0 * WQM * +0x6302 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * +0x6303 CIMT 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH +0x6304 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS WQH +0x6305 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * +0x6310 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * +0x6311 CEFT 8 16 3 0 * * CEFT * 1 1 0 * WQM * +0x6312 CDFT 8 16 3 0 * * CDFT * 1 1 0 * WQS * +0x6313 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS * +0x6314 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS WQH +0x6315 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * +0x6400 UDRZ 9 16 3 0 * * * * 1 * 1 0 WPM * +0x6401 UELM 9 16 3 0 * * * * 0 * 0 0 WPM * +0x6402 CMNK 9 16 3 0 * * * * 1 * 0 * WPM * +0x6403 MSKL 9 16 3 0 * * * * 1 * 1 * WPM * +0x6404 USAR 9 16 3 0 * * * * 0 * 0 0 WPL * +0x6405 MDGU 9 16 3 0 * * * * 1 * 1 * WPM * +0x6406 MDGU 9 24 7 0 * * * * 1 * 1 * WPM * +0x6500 CHMM 8 16 3 0 * * CHMM * 1 1 1 * WQL * +0x6510 CHFM 8 16 3 0 * * CHFM * 1 1 1 * WQN * +0x6621 XHFF 9 16 3 0 * * * * 1 * 1 * WPM * +0x7000 MOGH 11 16 3 0 * * * * 1 * * * * * +0x7001 MOGN 11 16 3 0 * * * * 1 * * * * * +0x7100 MBAS 11 32 5 0 * * * * 0 * * * * * +0x7101 MBAS 11 32 5 0 MBAS_GR * * * 0 * * * * * +0x7200 MBER 11 16 3 0 MBER_BL * * * 0 * * * * * +0x7201 MBER 11 16 3 0 * * * * 0 * * * * * +0x7202 MBER 11 16 3 0 MBER_CA * * * 0 * * * * * +0x7203 MBER 11 16 3 0 MBER_PO * * * 0 * * * * * +0x7300 MEAE 10 32 5 0 * * * * 0 1 * * * * +0x7301 MEAS 10 16 3 0 * * * * 0 1 * * * * +0x7302 MEAE 10 32 5 0 MEAE_SH * * * 0 1 * * * * +0x7310 MFIE 10 16 3 4 * * * * 0 1 * * * * +0x7311 MFIS 10 16 3 4 * * * * 0 1 * * * * +0x7312 MFIE 10 16 3 4 MFIEG1B MFIEG2B * * 0 1 * * * * +0x7313 MFIS 10 16 3 4 MFISG1B MFISG2B * * 0 1 * * * * +0x7314 MFIE 10 16 3 4 MFIEG31 MFIEG3B * * 0 1 * * * * +0x7320 MAIR 10 32 5 0 * * * 1 0 1 * * * * +0x7321 MAIS 10 16 3 0 * * * 1 0 1 * * * * +0x7400 MDOG 11 16 3 0 MDOG_WI * * * 0 * * * * * +0x7401 MDOG 11 16 3 0 MDOG_WA * * * 0 * * * * * +0x7402 MDOG 11 16 3 0 MDOG_MO * * * 0 * * * * * +0x7500 MDOP 11 16 3 0 * * * * 0 * * * * * +0x7501 MDOP 11 16 3 0 MDOP_GR * * * 0 * * * * * +0x7600 METT 11 16 3 0 * * * * 0 * * * * * +0x7601 MGHL 11 16 3 0 MGHL_MA * * * 0 0 * * * * +0x7602 MSPI 11 16 3 0 MSPI_MY * * * 0 * * * * * +0x7603 MDOG 11 16 3 0 MDOG_GR * * * 0 * * * * * +0x7604 MSPI 11 16 3 0 MSPI_WA * * * 0 * * * * * +0x7700 MGHL 11 16 3 0 * * * * 0 * * * * * +0x7701 MGHL 11 16 3 0 MGHL_RE * * * 0 * * * * * +0x7702 MGHL 11 16 3 0 MGHL_GA * * * 0 * * * * * +0x7703 MSHD 10 16 3 0 * * * 1 0 1 * * * * +0x7800 MGIB 11 16 3 0 * * * * 0 * * * * * +0x7801 MGIB 11 16 3 0 MGIB_BR * * * 0 * * * * * +0x7802 MSLI 11 24 4 0 MSLI_BL * * 0 0 * * * * * +0x7900 MSLI 11 24 4 0 MSLI_GR * * 1 0 * * * * * +0x7901 MSLI 11 24 4 0 MSLI_OL * * 1 0 * * * * * +0x7902 MSLI 11 24 4 0 MSLI_MU * * 1 0 * * * * * +0x7903 MSLI 11 24 4 0 MSLI_OC * * 1 0 * * * * * +0x7904 MSLI 11 24 4 0 * * * 1 0 * * * * * +0x7a00 MSPI 11 16 3 0 MSPI_GI * * * 0 * * * * * +0x7a01 MSPI 11 16 3 0 MSPI_HU * * * 0 * * * * * +0x7a02 MSPI 11 16 3 0 MSPI_PH * * * 0 * * * * * +0x7a03 MSPI 11 16 3 0 MSPI_SW * * * 0 * * * * * +0x7a04 MSPI 11 16 3 0 MSPI_WR * * * 0 * * * * * +0x7b00 MWLF 11 16 3 0 * * * * 0 * * * * * +0x7b01 MWLF 11 16 3 0 MWLF_WO * * * 0 * * * * * +0x7b02 MWLF 11 16 3 0 MWLF_DI * * * 0 * * * * * +0x7b03 MWLF 11 16 3 0 MWLF_WI * * * 0 * * * * * +0x7b04 MWLF 11 16 3 0 MWLF_VA * * * 0 * * * * * +0x7b05 MWLF 11 16 3 0 MWLF_DR * * * 0 * * * * * +0x7b06 MWLS 11 16 3 0 * * * * 0 * * * * * +0x7c00 MXVT 11 16 3 0 * * * * 1 * * * * * +0x7c01 MTAS 11 16 3 0 * * * * 0 * * * * * +0x7d00 MZOM 11 16 3 0 * * * * 1 * * * * * +0x7d01 NSLF 11 16 3 0 * * * * 1 0 * * * * +0x7d02 ACHB 11 12 3 0 * * * * 0 0 * * * * +0x7d03 ACHW 11 12 3 0 * * * * 0 0 * * * * +0x7d04 NPRF 11 16 3 0 * * * * 1 0 * * * * +0x7d05 NNMF 11 16 3 0 * * * * 1 0 * * * * +0x7d06 NNWF 11 16 3 0 * * * * 1 0 * * * * +0x7d07 NSMF 11 16 3 0 * * * * 1 0 * * * * +0x7d08 NSWF 11 16 3 0 * * * * 1 0 * * * * +0x7e00 MWER 11 16 3 0 * * * * 0 * * * * * +0x7e01 MGWE 11 16 3 0 * * * * 0 * * * * * +0x7f00 MTRO 10 16 3 0 * * * * 0 1 * * * * +0x7f01 MMIN 10 16 3 0 * * * * 0 1 * * * * +0x7f02 MBEH 10 32 5 0 * * * * 0 1 * * * * +0x7f03 MIMP 10 16 3 0 * * * * 0 1 * * * * +0x7f04 MIGO 10 32 5 0 * * * * 0 1 * * * * +0x7f05 MDJI 10 16 3 0 * * * * 0 1 * * * * +0x7f06 MDJL 10 16 3 0 * * * * 0 1 * * * * +0x7f07 MGLC 10 16 3 0 * * * * 0 1 * * * * +0x7f08 MOTY 10 24 5 0 * * * * 0 1 * * * * +0x7f09 MSAH 10 16 3 0 * * * * 0 1 * * * * +0x7f0a MGCP 10 16 3 0 * * * * 0 1 * * * * +0x7f0b MGCL 10 16 3 0 * * * * 0 1 * * * * +0x7f0c MKUO 10 16 3 0 * * * * 0 1 * * * * +0x7f0d MLIC 10 16 3 0 * * * * 0 1 * * * * +0x7f0e MDLI 10 16 3 0 * * * * 0 1 * * * * +0x7f0f MTRS 10 16 3 0 * * * * 0 1 * * * * +0x7f10 MRAK 10 16 3 0 * * * * 0 1 * * * * +0x7f11 MUMB 10 16 3 0 * * * * 0 1 * * * * +0x7f12 MVAM 10 16 3 0 * * * * 0 1 * * * * +0x7f13 MSNK 10 16 3 0 * * * * 0 1 * * * * +0x7f14 MGIT 10 16 3 0 * * * * 0 1 * * * * +0x7f15 MBES 10 16 3 0 * * * * 0 1 * * * * +0x7f16 AMOO 10 32 5 0 * * * * 0 1 * * * * +0x7f17 ARAB 10 12 3 0 * * * * 0 1 * * * * +0x7f18 ADER 10 16 3 0 * * * * 0 1 * * * * +0x7f19 MDSW 10 16 3 0 * * * * 0 1 * * * * +0x7f20 AGRO 10 12 3 0 * * * * 0 1 * * * * +0x7f21 APHE 10 12 3 0 * * * * 0 1 * * * * +0x7f22 MVAF 10 16 3 0 * * * * 0 1 * * * * +0x7f23 MSAT 10 16 3 0 * * * * 0 1 * * * * +0x7f24 NPIR 10 16 3 0 * * * * 0 1 * * * * +0x7f27 MDRO 10 16 3 0 * * * * 0 1 * * * * +0x7f28 MKUL 10 16 3 0 * * * * 0 1 * * * * +0x7f29 MFDR 10 16 3 0 * * * * 0 1 * * * * +0x7f2a NSAI 10 16 3 0 * * * * 0 1 * * * * +0x7f2b MMAX 10 16 3 0 * * * * 0 0 * * * * +0x7f2c NSOL 10 16 3 0 * * * * 0 1 * * * * +0x7f2d MWFM 10 16 3 0 * * * * 0 1 * * * * +0x7f2e MRAV 10 32 5 0 * * * * 0 1 * * * * +0x7f2f MSPS 10 16 3 0 * * * * 0 1 * * * * +0x7f30 NBOH 10 16 3 0 * * * * 0 1 * * * * +0x7f31 NELL 10 16 3 0 * * * * 0 1 * * * * +0x7f32 MSLY 10 16 3 0 * * * * 0 1 * * * * +0x7f33 MKUR 10 16 3 0 * * * * 0 0 * * * * +0x7f34 MDOC 10 16 3 0 * * * * 0 0 * * * * +0x7f35 MMIS 10 16 3 0 * * * 1 0 1 * * * * +0x7f36 NSHD 10 16 3 0 * * * * 0 1 * * * * +0x7f37 NIRE 10 16 3 0 * * * * 0 1 * * * * +0x7f38 MEYE 10 16 3 0 * * * * 0 0 * * * * +0x7f39 MMST 10 16 3 1 * * * 0 0 0 * * * * +0x7f3a NIRO 10 16 3 0 * * * * 0 1 * * * * +0x7f3b MSOG 10 16 3 4 * * * 0 0 1 * * * * +0x7f3c MASG 10 16 3 4 * * * 0 0 1 * * * * +0x7f3d MMEL 10 24 3 0 * * * * 0 0 * * * * +0x7f3e MFIG 10 32 5 0 * * * * 0 1 * * * * +0x7f3f MFIG 10 32 5 0 MFIGG1B * * * 0 1 * * * * +0x7f40 MGLM 10 16 3 0 * * * * 0 1 * * * * +0x7f41 MDJL 10 16 3 0 MDJL_E1 MDJL_E2 * * 0 1 * * * * +0x7f42 NIRO 10 16 3 0 NIRO_RD * * * 0 1 * * * * +0x7f43 MOTY 10 16 3 0 MOTY_Y1 MOTY_Y2 * * 0 1 * * * * +0x7f44 NELW 10 16 3 0 * * * * 0 0 * * * * +0x7f45 MSAI 10 16 3 0 * * * 1 0 0 * * * * +0x7f46 MBEG 10 16 3 0 * * * * 0 0 * * * * +0x7f47 MFI2 10 32 5 0 * * * * 0 0 * * * * +0x7f48 MSOF 10 16 3 0 * * * 0 0 1 * * * * +0x7f49 MDMF 10 16 3 0 * * * 0 0 1 * * * * +0x7f4a MDAS 10 16 3 7 * * MDAG 0 0 1 * * * * +0x7f4b MDAF 10 16 3 0 * * * 0 0 1 * * * * +0x7f4c MPLN 10 16 3 7 * * MPLG 0 0 1 * * * * +0x7f4d MPLF 10 16 3 0 * * * 0 0 1 * * * * +0x7f4e MDVM 10 16 3 7 * * MDVG 0 0 1 * * * * +0x7f4f MDVF 10 16 3 0 * * * 0 0 1 * * * * +0x7f50 MMST 10 16 3 1 MMST_NI * * 0 0 0 * * * * +0x7f51 MMST 10 16 3 1 MMST_HA * * 0 0 0 * * * * +0x7f52 NSAI 10 16 3 0 NSAI_G1 NSAI_G2 * * 0 1 * * * * +0x7f53 NSAI 10 16 3 0 NSAI_R1 NSAI_R2 * * 0 1 * * * * +0x7f54 NSAI 10 16 3 0 NSAI_D1 NSAI_D2 * * 0 1 * * * * +0x7f55 NSOL 10 16 3 0 NSOL_C1 NSOL_C2 * * 0 1 * * * * +0x7f56 MWIL 10 16 3 7 * * * * 0 0 * * * * +0x7f57 MWIS 10 12 3 7 * * * * 0 0 * * * * +0x7f58 MABB 10 24 3 0 * * * * 0 0 * * * * +0x7f59 MABG 10 24 3 0 * * * * 0 0 * * * * +0x7f5a MABR 10 24 3 0 * * * * 0 0 * * * * +0x7f5b MABR 10 24 3 0 MABR_BL * * * 0 0 * * * * +0x7f5c MHAG 10 16 3 0 * * * * 0 0 * * * * +0x7f5d MHAG 10 16 3 0 MHAG_GR * * * 0 0 * * * * +0x7f5e MHAG 10 16 3 0 MHAG_SE * * * 0 0 * * * * +0x7f5f MHAN 10 16 3 0 * * * * 0 0 * * * * +0x7f60 MSNK 10 16 3 0 MSNK_W1 MSNK_W2 * * 0 1 * * * * +0x7f61 MDRJ 10 48 9 0 * * * * 0 0 * * * * +0x7f62 MDRJ 10 48 9 0 MDRJ_GR * * * 0 0 * * * * +0x8000 MGNL 4 16 3 0 * * * * * * * * S1 HB +0x8100 MHOB 4 16 3 0 * * * * * * * * S1 BW +0x8200 MKOB 4 16 3 0 * * * * * * * * SS BW +0x9000 MOGR 12 16 3 0 * * * * 1 * * * * * +0xa000 MWYV 13 16 3 0 * * * * 0 * * * * * +0xa100 MCAR 13 16 3 0 * * * * 0 * * * * * +0xa200 MWYV 13 24 4 0 MWYV_WS * * * 0 * * * * * +0xa201 MCEN 13 16 3 0 * * * * 0 * * * * * +0xa202 MTUN 13 16 3 0 * * * * 0 * * * * * +0xb000 ACOW 14 32 5 0 * * * * 0 * * * * * +0xb100 AHRS 14 32 5 0 * * * * 0 * * * * * +0xb200 NBEGL 14 16 3 0 * * * * 1 * * * * * +0xb210 NPROL 14 16 3 0 * * * * 1 * * * * * +0xb300 NBOYL 14 16 3 0 * * * * 1 * * * * * +0xb310 NGRLL 14 16 3 0 * * * * 1 * * * * * +0xb400 NFAML 14 16 3 0 * * * * 1 * * * * * +0xb410 NFAWL 14 16 3 0 * * * * 1 * * * * * +0xb500 NSIML 14 16 3 0 * * * * 1 * * * * * +0xb510 NSIWL 14 16 3 0 * * * * 1 * * * * * +0xb600 NNOML 14 16 3 0 * * * * 1 * * * * * +0xb610 NNOWL 14 16 3 0 * * * * 1 * * * * * +0xb700 NSLVL 14 16 3 0 * * * * 1 * * * * * +0xc000 ABAT 15 12 3 0 * * * * 0 * * * * * +0xc100 ACAT 15 12 3 0 * * * * 0 * * * * * +0xc200 ACHK 15 12 3 0 * * * * 0 * * * * * +0xc300 ARAT 15 12 3 0 * * * * 0 * * * * * +0xc400 ASQU 15 12 3 0 * * * * 0 * * * * * +0xc500 ABAT 15 12 3 0 * * * * 0 * * * * * +0xc600 NBEGH 15 16 3 0 * * * * 1 * * * * * +0xc610 NPROH 15 16 3 0 * * * * 1 * * * * * +0xc700 NBOYH 15 16 3 0 * * * * 1 * * * * * +0xc710 NGRLH 15 16 3 0 * * * * 1 * * * * * +0xc800 NFAMH 15 16 3 0 * * * * 1 * * * * * +0xc810 NFAWH 15 16 3 0 * * * * 1 * * * * * +0xc900 NSIMH 15 16 3 0 * * * * 1 * * * * * +0xc910 NSIWH 15 16 3 0 * * * * 1 * * * * * +0xca00 NNOMH 15 16 3 0 * * * * 1 * * * * * +0xca10 NNOWH 15 16 3 0 * * * * 1 * * * * * +0xcb00 NSLVH 15 16 3 0 * * * * 1 * * * * * +0xcc00 MKG1 15 16 3 0 * * * * 0 * * * * * +0xcc01 MKG2 15 16 3 0 * * * * 0 * * * * * +0xcc02 MKG3 15 16 3 0 * * * * 0 * * * * * +0xcc04 ARAT 15 0 1 0 * * * * 0 * * * * * +0xd000 AEAGG1 16 0 3 0 * * * * 0 * * * * * +0xd100 AGULG1 16 0 3 0 * * * * 0 * * * * * +0xd200 AVULG1 16 0 3 0 * * * * 0 * * * * * +0xd300 ABIRG1 16 0 3 0 * * * * 0 * * * * * +0xd400 ABIRG1 16 0 3 0 * * * * 0 * * * * * +0xe000 MCYC 17 48 5 0 * * * * * * * * * * +0xe010 METN 17 48 5 0 * * * * * * * * * * +0xe020 MTAN 17 32 5 0 * * * * * * * * * * +0xe040 MHIS 17 16 3 0 * * * * * * * * * * +0xe050 MLER 17 16 3 0 * * * * * * * * * * +0xe060 MLIC 17 16 3 0 * * * * * * * * * * +0xe070 MMIN 17 24 3 0 * * * * * * * * * * +0xe080 MMUM 17 16 3 0 * * * * * * * * * * +0xe090 MTAN 17 16 3 0 * * * * * * * * * * +0xe0a0 MTIC 17 20 3 0 * * * * * * * * * * +0xe0b0 MTRO 17 16 3 0 * * * * * * * * * * +0xe0c0 MTSN 17 24 3 0 * * * * * * * * * * +0xe0d0 MUMB 17 24 3 0 * * * * * * * * * * +0xe0e0 MCOR 17 24 3 0 * * * * * * * * * * +0xe0f0 MGIC 17 32 5 0 * * * * * * * * * * +0xe0f1 MGLA 17 24 3 0 * * * * * * * * * * +0xe0f2 MWAV 17 16 3 0 * * * 1 * * * * * * +0xe200 MBET 17 24 3 0 * * * * * * * * * * +0xe210 MBFI 17 12 3 0 * * * * * * * * * * +0xe220 MBBM 17 16 3 0 * * * * * * * * * * +0xe230 MBRH 17 64 7 0 * * * * * * * * * * +0xe240 MANI 17 24 3 0 * * * * * * * * * * +0xe241 MAN2 17 24 3 0 * * * * * * * * * * +0xe242 MAN3 17 24 3 0 * * * * * * * * * * +0xe243 MARU 17 16 3 0 * * * * * * * * * * +0xe244 MBA1 17 16 3 0 * * * * * * * * * * +0xe245 MBA2 17 16 3 0 * * * * * * * * * * +0xe246 MBA3 17 16 3 0 * * * * * * * * * * +0xe247 MBA4 17 16 3 0 * * * * * * * * * * +0xe248 MBA5 17 16 3 0 * * * * * * * * * * +0xe249 MBA6 17 16 3 0 * * * * * * * * * * +0xe24a MBAI 17 16 3 0 * * * * * * * * * * +0xe24b MELE 17 36 3 0 * * * * * * * * * * +0xe24c MELF 17 36 3 0 * * * * * * * * * * +0xe24d MELW 17 36 3 0 * * * * * * * * * * +0xe24e MGFI 17 48 3 0 * * * * * * * * * * +0xe24f MGFR 17 48 5 0 * * * * * * * * * * +0xe250 MGIR 17 24 3 0 * * * * * * * * * * +0xe251 MGVE 17 32 5 0 * * * * * * * * * * +0xe252 MHAR 17 16 3 0 * * * * * * * * * * +0xe253 MREM 17 48 5 0 * * * * * * * * * * +0xe254 MSCR 17 24 3 0 * * * * * * * * * * +0xe255 MSEE 17 16 3 0 * * * * * * * * * * +0xe259 MSVI 17 16 3 0 * * * * * * * * * * +0xe25a MSV2 17 16 3 0 * * * * * * * * * * +0xe25b MUM2 17 24 3 0 * * * * * * * * * * +0xe25c MTA2 17 16 3 0 * * * * * * * * * * +0xe25d MYET 17 24 3 0 * * * * * * * * * * +0xe25e MWI4 17 16 3 0 * * * * * * * * * * +0xe25f MDRD 17 16 3 0 * * * * * * * * * * +0xe260 MCRD 17 24 3 0 * * * * * * * * * * +0xe261 MSAH 17 24 3 0 * * * * * * * * * * +0xe262 MSAT 17 24 3 0 * * * * * * * * * * +0xe263 MSV3 17 16 3 0 * * * * * * * * * * +0xe264 MSV4 17 16 3 0 * * * * * * * * * * +0xe265 MBOA 17 24 3 0 * * * * * * * * * * +0xe266 MBU2 17 16 3 0 * * * * * * * * * * +0xe267 MBUG 17 16 3 0 * * * * * * * * * * +0xe26a MDH2 17 24 3 0 * * * * * * * * * * +0xe26b MDTR 17 24 3 0 * * * * * * * * * * +0xe26d MFE2 17 24 3 0 * * * * * * * * * * +0xe26e MFEY 17 24 3 0 * * * * * * * * * * +0xe26f MGFO 17 24 4 0 * * * * * * * * * * +0xe270 MGO5 17 16 3 0 * * * * * * * * * * +0xe271 MGOC 17 16 3 0 * * * * * * * * * * +0xe272 MGWO 17 24 3 0 * * * * * * * * * * +0xe273 MGW2 17 24 3 0 * * * * * * * * * * +0xe274 MHOH 17 24 3 0 * * * * * * * * * * +0xe276 MLEM 17 16 3 0 * * * * * * * * * * +0xe279 MNOS 17 24 3 0 * * * * * * * * * * +0xe27d MWEB 17 16 3 0 * * * * * * * * * * +0xe27e MWRA 17 16 3 0 * * * * * * * * * * +0xe27f MBOA 17 24 3 0 MBOA_BR * * * * * * * * * +0xe280 MWOR 17 24 3 0 * * * * * * * * * * +0xe281 MYUH 17 16 3 0 * * * * * * * * * * +0xe282 MDRF 17 32 4 0 * * * * * * * * * * +0xe283 MDRM 17 32 4 0 * * * * * * * * * * +0xe288 MGWO 17 24 3 0 MGWO_DK * * * * * * * * * +0xe289 MGW2 17 24 3 0 MGW2_DK * * * * * * * * * +0xe28a MTRO 17 16 3 0 MTRO_DK * * * * * * * * * +0xe28b MABW 17 24 3 0 * * * * * * * * * * +0xe28c MWD2 17 36 5 0 * * * * * * * * * * +0xe28d MWD2 17 36 5 0 MWD2_SI * * * * * * * * * +0xe28e MWD2 17 36 5 0 MWD2_GR * * * * * * * * * +0xe28f MDRD 17 16 3 0 MDRD_RE * * * * * * * * * +0xe290 MDH2 17 24 3 0 MDH2_GR * * * * * * * * * +0xe291 MBU2 17 16 3 0 MBU2_SH * * * * * * * * * +0xe292 METN 17 48 5 0 METN_GH * * * * * * * * * +0xe293 MGHI 17 40 5 0 * * * * * * * * * * +0xe294 MBON 17 16 3 0 * * * * * * * * * * +0xe300 MGHO 17 16 3 0 * * * 1 * * * * * * +0xe310 MGH2 17 16 3 0 * * * * * * * * * * +0xe320 MGH3 17 16 3 0 * * * * * * * * * * +0xe400 MGO1 17 16 3 0 * * * * * * * * * * +0xe410 MGO2 17 16 3 0 * * * * * * * * * * +0xe420 MGO3 17 16 3 0 * * * * * * * * * * +0xe430 MGO4 17 16 3 0 * * * * * * * * * * +0xe440 MGO6 17 16 3 0 * * * * * * * * * * +0xe441 MGO7 17 16 3 0 * * * * * * * * * * +0xe442 MGO8 17 16 3 0 * * * * * * * * * * +0xe443 MGO9 17 16 3 0 * * * * * * * * * * +0xe444 MGO10 17 16 3 0 * * * * * * * * * * +0xe500 MLIZ 17 24 3 0 * * * * * * * * * * +0xe510 MLI2 17 24 3 0 * * * * * * * * * * +0xe520 MLI3 17 32 5 0 * * * * * * * * * * +0xe600 MMYC 17 16 3 0 * * * * * * * * * * +0xe610 MMY2 17 16 3 0 * * * * * * * * * * +0xe700 MNO1 17 20 3 0 * * * * * * * * * * +0xe710 MNO2 17 20 3 0 * * * * * * * * * * +0xe720 MNO3 17 24 3 0 * * * * * * * * * * +0xe800 MOR1 17 16 3 0 * * * * * * * * * * +0xe810 MOR2 17 16 3 0 * * * * * * * * * * +0xe820 MOR3 17 16 3 0 * * * * * * * * * * +0xe830 MOR4 17 16 3 0 * * * * * * * * * * +0xe840 MOR5 17 16 3 0 * * * * * * * * * * +0xe900 MSAL 17 16 3 0 * * * * * * * * * * +0xe910 MSA2 17 16 3 0 * * * * * * * * * * +0xea00 MSHR 17 24 3 0 * * * * * * * * * * +0xea10 MSH1 17 16 3 0 * * * 1 * * * * * * +0xea20 MSH2 17 24 3 0 * * * 1 * * * * * * +0xeb00 MSKT 17 16 3 0 * * * * * * * * * * +0xeb10 MSKA 17 16 3 0 * * * * * * * * * * +0xeb20 MSKB 17 24 3 0 * * * * * * * * * * +0xec00 MWIG 17 16 3 0 * * * * * * * * * * +0xec10 MWI2 17 16 3 0 * * * * * * * * * * +0xec20 MWI3 17 16 3 0 * * * * * * * * * * +0xed00 MYU1 17 16 3 0 * * * * * * * * * * +0xed10 MYU2 17 16 3 0 * * * * * * * * * * +0xed20 MYU3 17 16 3 0 * * * * * * * * * * +0xee00 MZO2 17 16 3 0 * * * * * * * * * * +0xee10 MZO3 17 16 3 0 * * * * * * * * * * +0xef10 MWWE 17 24 3 0 * * * * * * * * * * diff --git a/src/org/infinity/resource/cre/decoder/tables/avatars-bg2soa.2da b/src/org/infinity/resource/cre/decoder/tables/avatars-bg2soa.2da new file mode 100644 index 000000000..a926cd892 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/tables/avatars-bg2soa.2da @@ -0,0 +1,357 @@ +2DA V1.0 +* + RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD +0x0000 SPRING 0 0 0 0 * * * * 1 0 * * * * +0x0001 SPFLAMES 0 0 0 7 * * * * 0 0 * * * * +0x0002 SPRDRASI 0 0 0 7 * * * * 0 0 * * * * +0x0003 SPFLAMES 0 0 0 7 SPFLAMEB * * * 0 0 * * * * +0x0004 SPRDRASI 0 0 0 7 SPGDRASI * * * 0 0 * * * * +0x0100 SPCHUNKS 0 0 0 0 * * SPSHADOW * 1 1 * * * * +0x0200 SPBLOOD 0 0 0 0 * * * * 0 0 0 * * * +0x0210 SPBLOOD 0 0 0 0 * * * * 0 0 1 * * * +0x0220 SPBLOOD 0 0 0 0 * * * * 0 0 2 * * * +0x0230 SPBLOOD 0 0 0 0 * * * * 0 0 3 * * * +0x0240 SPBLOOD 0 0 0 0 * * * * 0 0 4 * * * +0x0300 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * +0x0400 SKLH 0 0 0 0 * * SPSHADOW * 0 1 * * * * +0x0410 GLPHWRDH 0 0 0 1 * * SPSHADOW * 0 1 * * * * +0x0500 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * +0x0510 STNKCLDD 0 0 0 0 * * * * 1 0 0 * * * +0x0520 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * +0x0600 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * +0x0610 SPICESTM 0 0 0 1 * * * * 0 0 0 * * * +0x0700 GREASEH 0 0 0 0 * * * * 0 0 * * * * +0x0710 GREASED 0 0 0 0 * * * * 0 1 * * * * +0x0800 WEBENTH 0 0 0 0 * * * * 1 0 * * * * +0x0810 WEBENTD 0 0 0 0 * * * * 1 0 * * * * +0x0900 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * +0x0910 SPMETSWM 0 0 0 1 * * * * 0 1 0 * * * +0x0a00 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * +0x0a01 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * +0x0a02 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * +0x0a03 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * +0x0a04 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * +0x0a10 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * +0x0a11 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * +0x0a12 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * +0x0a13 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * +0x0a14 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * +0x0a23 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * +0x0a24 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * +0x0b00 SPCSPRA2 0 0 0 1 * * * * 0 0 * * * * +0x0b01 SPCCOLDL 0 0 0 1 * * * * 0 0 * * * * +0x0b02 SPPRISM2 0 0 0 1 * * * * 0 0 * * * * +0x0b03 SPCSPRA3 0 0 0 1 * * * * 0 0 * * * * +0x0b04 SPPRISM3 0 0 0 1 * * * * 0 0 * * * * +0x1000 MWYV 1 24 4 0 * * * * 0 * * * * * +0x1100 MTAN 1 32 5 0 * * * * 0 * 1 * * * +0x1200 MDR1 2 72 13 0 * * * * 0 1 * * * * +0x1201 MDR2 2 93 13 0 * * * * 0 1 * * * * +0x1202 MDR3 2 72 13 0 * * * * 0 1 * * * * +0x1203 MDR1 2 72 13 0 MDR1_GR * * * 0 1 * * * * +0x1204 MDR1 2 72 13 0 MDR1_AQ * * * 0 1 * * * * +0x1205 MDR1 2 72 13 0 MDR1_BL * * * 0 1 * * * * +0x1206 MDR1 2 72 13 0 MDR1_BR * * * 0 1 * * * * +0x1207 MDR1 2 72 13 0 MDR1_MC * * * 0 1 * * * * +0x1208 MDR1 2 72 13 0 MDR1_PU * * * 0 1 * * * * +0x2000 MSIR 5 16 3 0 * * * * 1 * 1 * * BW +0x2100 UVOL 5 16 3 0 * * * * 0 * 1 * MS * +0x2200 MOGM 5 16 3 0 * * * * 1 * 1 1 S1 * +0x2300 MDKN 5 16 3 0 * * * * 0 * 1 1 * * +0x3000 MAKH 6 24 5 0 * * * * 0 * * * * * +0x4000 SNOMC 7 16 3 0 * * * * 1 * * * * * +0x4002 SNOMM 7 16 3 0 * * * * 1 * * * * * +0x4010 SNOWC 7 16 3 0 * * * * 1 * * * * * +0x4012 SNOWM 7 16 3 0 * * * * 1 * * * * * +0x4100 SSIMC 7 16 3 0 * * * * 1 * * * * * +0x4101 SSIMS 7 16 3 0 * * * * 1 * * * * * +0x4102 SSIMM 7 16 3 0 * * * * 1 * * * * * +0x4110 SSIWC 7 16 3 0 * * * * 1 * * * * * +0x4112 SSIWM 7 16 3 0 * * * * 1 * * * * * +0x4200 SHMCM 7 16 3 0 * * * * 1 * * * * * +0x4300 MSPLG1 7 32 5 0 * * * * 0 * * * * * +0x4400 LHMC 7 16 3 0 * * * * 1 * * * * * +0x4410 LHFC 7 16 3 0 * * * * 1 * * * * * +0x4500 LFAM 7 16 3 0 * * * * 1 * * * * * +0x4600 LDMF 7 16 3 0 * * * * 1 * * * * * +0x4700 LEMF 7 16 3 0 * * * * 1 * * * * * +0x4710 LEFF 7 16 3 0 * * * * 1 * * * * * +0x4800 LIMC 7 16 3 0 * * * * 1 * * * * * +0x5000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * +0x5001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * +0x5002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * +0x5003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH +0x5010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * +0x5011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * +0x5012 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * +0x5013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * +0x5100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * +0x5101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * +0x5102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * +0x5103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH +0x5110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * +0x5111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * +0x5112 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * +0x5113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * +0x5200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * +0x5201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * +0x5202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * +0x5210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * +0x5211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * +0x5212 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * +0x5300 CHMB 8 16 3 0 * * CHMT * 1 1 0 * WQL * +0x5301 CEMB 8 16 3 0 * * CEMT * 1 1 0 * WQM * +0x5302 CDMB 8 16 3 0 * * CDMT * 1 1 0 * WQS * +0x5303 CIMB 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH +0x5310 CHFB 8 16 3 0 * * CHFT * 1 1 0 * WQN * +0x5311 CEFB 8 16 3 0 * * CEFT * 1 1 0 * WQM * +0x5312 CDFB 8 16 3 0 * * CDFT * 1 1 0 * WQS * +0x5313 CIFB 8 16 3 0 * * CIFT * 1 1 0 * WQS * +0x6000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * +0x6001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * +0x6002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * +0x6003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH +0x6004 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS WQH +0x6005 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * +0x6010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * +0x6011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * +0x6012 CDMB 8 16 3 0 * * CDFC * 1 1 1 * WQS * +0x6013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * +0x6014 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS WQH +0x6015 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * +0x6100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * +0x6101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * +0x6102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * +0x6103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH +0x6104 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS WQH +0x6105 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * +0x6110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * +0x6111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * +0x6112 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * +0x6113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * +0x6114 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS WQH +0x6115 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * +0x6200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * +0x6201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * +0x6202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * +0x6204 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH +0x6205 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * +0x6210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * +0x6211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * +0x6212 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * +0x6214 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH +0x6215 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * +0x6300 CHMB 8 16 3 0 * * CHMT * 1 1 0 * WQL * +0x6301 CEMB 8 16 3 0 * * CEMT * 1 1 0 * WQM * +0x6302 CDMB 8 16 3 0 * * CDMT * 1 1 0 * WQS * +0x6303 CIMB 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH +0x6304 CDMB 8 16 3 0 * * CDMT * 1 1 0 * WQS WQH +0x6305 CHMB 8 16 3 0 * * CHMT * 1 1 0 * WQL * +0x6310 CHFB 8 16 3 0 * * CHFT * 1 1 0 * WQN * +0x6311 CEFB 8 16 3 0 * * CEFT * 1 1 0 * WQM * +0x6312 CDMB 8 16 3 0 * * CDMT * 1 1 0 * WQS * +0x6313 CIFB 8 16 3 0 * * CIFT * 1 1 0 * WQS * +0x6314 CIFB 8 16 3 0 * * CIFT * 1 1 0 * WQS WQH +0x6315 CHFB 8 16 3 0 * * CHFT * 1 1 0 * WQN * +0x6400 UDRZ 9 16 3 0 * * * * 1 * 1 0 WPM * +0x6401 UELM 9 16 3 0 * * * * 0 * 0 0 WPM * +0x6402 CMNK 9 16 3 0 * * * * 1 * 0 * WPM * +0x6403 MSKL 9 16 3 0 * * * * 1 * 1 * WPM * +0x6404 USAR 9 16 3 0 * * * * 0 * 0 0 WPL * +0x6405 MDGU 9 16 3 0 * * * * 1 * 1 * WPM * +0x6406 MDGU 9 24 7 0 * * * * 1 * 1 * WPM * +0x6500 CHMM 8 16 3 0 * * CHMM * 1 1 1 * WQL * +0x6510 CHFM 8 16 3 0 * * CHFM * 1 1 1 * WQN * +0x7000 MOGH 11 16 3 0 * * * * 1 * * * * * +0x7001 MOGN 11 16 3 0 * * * * 1 * * * * * +0x7100 MBAS 11 32 5 0 * * * * 0 * * * * * +0x7101 MBAS 11 32 5 0 MBAS_GR * * * 0 * * * * * +0x7200 MBER 11 16 3 0 MBER_BL * * * 0 * * * * * +0x7201 MBER 11 16 3 0 * * * * 0 * * * * * +0x7202 MBER 11 16 3 0 MBER_CA * * * 0 * * * * * +0x7203 MBER 11 16 3 0 MBER_PO * * * 0 * * * * * +0x7300 MEAE 10 32 5 0 * * * * 0 1 * * * * +0x7301 MEAS 10 16 3 0 * * * * 0 1 * * * * +0x7302 MEAE 10 32 5 0 MEAE_SH * * * 0 1 * * * * +0x7310 MFIE 10 16 3 4 * * * * 0 1 * * * * +0x7311 MFIS 10 16 3 4 * * * * 0 1 * * * * +0x7320 MAIR 10 32 5 0 * * * 1 0 1 * * * * +0x7321 MAIS 10 16 3 0 * * * 1 0 1 * * * * +0x7400 MDOG 11 16 3 0 MDOG_WI * * * 0 * * * * * +0x7401 MDOG 11 16 3 0 MDOG_WA * * * 0 * * * * * +0x7402 MDOG 11 16 3 0 MDOG_MO * * * 0 * * * * * +0x7500 MDOP 11 16 3 0 * * * * 0 * * * * * +0x7501 MDOP 11 16 3 0 MDOP_GR * * * 0 * * * * * +0x7600 METT 11 16 3 0 * * * * 0 * * * * * +0x7700 MGHL 11 16 3 0 * * * * 0 * * * * * +0x7701 MGHL 11 16 3 0 MGHL_RE * * * 0 * * * * * +0x7702 MGHL 11 16 3 0 MGHL_GA * * * 0 * * * * * +0x7703 MSHD 10 16 3 0 * * * 1 0 1 * * * * +0x7800 MGIB 11 16 3 0 * * * * 0 * * * * * +0x7900 MSLI 11 24 4 0 MSLI_GR * * 1 0 * * * * * +0x7901 MSLI 11 24 4 0 MSLI_OL * * 1 0 * * * * * +0x7902 MSLI 11 24 4 0 MSLI_MU * * 1 0 * * * * * +0x7903 MSLI 11 24 4 0 MSLI_OC * * 1 0 * * * * * +0x7904 MSLI 11 24 4 0 * * * 1 0 * * * * * +0x7a00 MSPI 11 16 3 0 MSPI_GI * * * 0 * * * * * +0x7a01 MSPI 11 16 3 0 MSPI_HU * * * 0 * * * * * +0x7a02 MSPI 11 16 3 0 MSPI_PH * * * 0 * * * * * +0x7a03 MSPI 11 16 3 0 MSPI_SW * * * 0 * * * * * +0x7a04 MSPI 11 16 3 0 MSPI_WR * * * 0 * * * * * +0x7b00 MWLF 11 16 3 0 * * * * 0 * * * * * +0x7b01 MWLF 11 16 3 0 MWLF_WO * * * 0 * * * * * +0x7b02 MWLF 11 16 3 0 MWLF_DI * * * 0 * * * * * +0x7b03 MWLF 11 16 3 0 MWLF_WI * * * 0 * * * * * +0x7b04 MWLF 11 16 3 0 MWLF_VA * * * 0 * * * * * +0x7b05 MWLF 11 16 3 0 MWLF_DR * * * 0 * * * * * +0x7b06 MWLS 11 16 3 0 * * * * 0 * * * * * +0x7c00 MXVT 11 16 3 0 * * * * 1 * * * * * +0x7c01 MTAS 11 16 3 0 * * * * 0 * * * * * +0x7d00 MZOM 11 16 3 0 * * * * 1 * * * * * +0x7e00 MWER 11 16 3 0 * * * * 0 * * * * * +0x7e01 MGWE 11 16 3 0 * * * * 0 * * * * * +0x7f00 MTRO 10 16 3 0 * * * * 0 1 * * * * +0x7f01 MMIN 10 16 3 0 * * * * 0 1 * * * * +0x7f02 MBEH 10 32 5 0 * * * * 0 1 * * * * +0x7f03 MIMP 10 16 3 0 * * * * 0 1 * * * * +0x7f04 MIGO 10 32 5 0 * * * * 0 1 * * * * +0x7f05 MDJI 10 16 3 0 * * * * 0 1 * * * * +0x7f06 MDJL 10 16 3 0 * * * * 0 1 * * * * +0x7f07 MGLC 10 16 3 0 * * * * 0 1 * * * * +0x7f08 MOTY 10 24 5 0 * * * * 0 1 * * * * +0x7f09 MSAH 10 16 3 0 * * * * 0 1 * * * * +0x7f0a MGCP 10 16 3 0 * * * * 0 1 * * * * +0x7f0b MGCL 10 16 3 0 * * * * 0 1 * * * * +0x7f0c MKUO 10 16 3 0 * * * * 0 1 * * * * +0x7f0d MLIC 10 16 3 0 * * * * 0 1 * * * * +0x7f0e MDLI 10 16 3 0 * * * * 0 1 * * * * +0x7f0f MTRS 10 16 3 0 * * * * 0 1 * * * * +0x7f10 MRAK 10 16 3 0 * * * * 0 1 * * * * +0x7f11 MUMB 10 16 3 0 * * * * 0 1 * * * * +0x7f12 MVAM 10 16 3 0 * * * * 0 1 * * * * +0x7f13 MSNK 10 16 3 0 * * * * 0 1 * * * * +0x7f14 MGIT 10 16 3 0 * * * * 0 1 * * * * +0x7f15 MBES 10 16 3 0 * * * * 0 1 * * * * +0x7f16 AMOO 10 32 5 0 * * * * 0 1 * * * * +0x7f17 ARAB 10 12 3 0 * * * * 0 1 * * * * +0x7f18 ADER 10 16 3 0 * * * * 0 1 * * * * +0x7f19 MDSW 10 16 3 0 * * * * 0 1 * * * * +0x7f20 AGRO 10 12 3 0 * * * * 0 1 * * * * +0x7f21 APHE 10 12 3 0 * * * * 0 1 * * * * +0x7f22 MVAF 10 16 3 0 * * * * 0 1 * * * * +0x7f23 MSAT 10 16 3 0 * * * * 0 1 * * * * +0x7f24 NPIR 10 16 3 0 * * * * 0 1 * * * * +0x7f27 MDRO 10 16 3 0 * * * * 0 1 * * * * +0x7f28 MKUL 10 16 3 0 * * * * 0 1 * * * * +0x7f29 MFDR 10 16 3 0 * * * * 0 1 * * * * +0x7f2a NSAI 10 16 3 0 * * * * 0 1 * * * * +0x7f2b MMAX 10 16 3 0 * * * * 0 0 * * * * +0x7f2c NSOL 10 16 3 0 * * * * 0 1 * * * * +0x7f2d MWFM 10 16 3 0 * * * * 0 1 * * * * +0x7f2e MRAV 10 32 5 0 * * * * 0 1 * * * * +0x7f2f MSPS 10 16 3 0 * * * * 0 1 * * * * +0x7f30 NBOH 10 16 3 0 * * * * 0 1 * * * * +0x7f31 NELL 10 16 3 0 * * * * 0 1 * * * * +0x7f32 MSLY 10 16 3 0 * * * * 0 1 * * * * +0x7f33 MKUR 10 16 3 0 * * * * 0 0 * * * * +0x7f34 MDOC 10 16 3 0 * * * * 0 0 * * * * +0x7f35 MMIS 10 16 3 0 * * * 1 0 1 * * * * +0x7f36 NSHD 10 16 3 0 * * * * 0 1 * * * * +0x7f37 NIRE 10 16 3 0 * * * * 0 1 * * * * +0x7f38 MEYE 10 16 3 0 * * * * 0 0 * * * * +0x7f39 MMST 10 16 3 1 * * * 0 0 0 * * * * +0x7f3a NIRO 10 16 3 0 * * * * 0 1 * * * * +0x8000 MGNL 4 16 3 0 * * * * * * * * S1 HB +0x8100 MHOB 4 16 3 0 * * * * * * * * S1 BW +0x8200 MKOB 4 16 3 0 * * * * * * * * SS BW +0x9000 MOGR 12 16 3 0 * * * * 1 * * * * * +0xa000 MWYV 13 16 3 0 * * * * 0 * * * * * +0xa100 MCAR 13 16 3 0 * * * * 0 * * * * * +0xb000 ACOW 14 32 5 0 * * * * 0 * * * * * +0xb100 AHRS 14 32 5 0 * * * * 0 * * * * * +0xb200 NBEGL 14 16 3 0 * * * * 1 * * * * * +0xb210 NPROL 14 16 3 0 * * * * 1 * * * * * +0xb300 NBOYL 14 16 3 0 * * * * 1 * * * * * +0xb310 NGRLL 14 16 3 0 * * * * 1 * * * * * +0xb400 NFAML 14 16 3 0 * * * * 1 * * * * * +0xb410 NFAWL 14 16 3 0 * * * * 1 * * * * * +0xb500 NSIML 14 16 3 0 * * * * 1 * * * * * +0xb510 NSIWL 14 16 3 0 * * * * 1 * * * * * +0xb600 NNOML 14 16 3 0 * * * * 1 * * * * * +0xb610 NNOWL 14 16 3 0 * * * * 1 * * * * * +0xb700 NSLVL 14 16 3 0 * * * * 1 * * * * * +0xc000 ABAT 15 12 3 0 * * * * 0 * * * * * +0xc100 ACAT 15 12 3 0 * * * * 0 * * * * * +0xc200 ACHK 15 12 3 0 * * * * 0 * * * * * +0xc300 ARAT 15 12 3 0 * * * * 0 * * * * * +0xc400 ASQU 15 12 3 0 * * * * 0 * * * * * +0xc500 ABAT 15 12 3 0 * * * * 0 * * * * * +0xc600 NBEGH 15 16 3 0 * * * * 1 * * * * * +0xc610 NPROH 15 16 3 0 * * * * 1 * * * * * +0xc700 NBOYH 15 16 3 0 * * * * 1 * * * * * +0xc710 NGRLH 15 16 3 0 * * * * 1 * * * * * +0xc800 NFAMH 15 16 3 0 * * * * 1 * * * * * +0xc810 NFAWH 15 16 3 0 * * * * 1 * * * * * +0xc900 NSIMH 15 16 3 0 * * * * 1 * * * * * +0xc910 NSIWH 15 16 3 0 * * * * 1 * * * * * +0xca00 NNOMH 15 16 3 0 * * * * 1 * * * * * +0xca10 NNOWH 15 16 3 0 * * * * 1 * * * * * +0xcb00 NSLVH 15 16 3 0 * * * * 1 * * * * * +0xd000 AEAGG1 16 0 3 0 * * * * 0 * * * * * +0xd100 AGULG1 16 0 3 0 * * * * 0 * * * * * +0xd200 AVULG1 16 0 3 0 * * * * 0 * * * * * +0xd300 ABIRG1 16 0 3 0 * * * * 0 * * * * * +0xd400 ABIRG1 16 0 3 0 * * * * 0 * * * * * +0xe000 MCYC 17 48 5 0 * * * * * * * * * * +0xe010 METN 17 48 5 0 * * * * * * * * * * +0xe020 MGFR 17 32 5 0 * * * * * * * * * * +0xe040 MHIS 17 16 3 0 * * * * * * * * * * +0xe050 MLER 17 16 3 0 * * * * * * * * * * +0xe060 MLIC 17 16 3 0 * * * * * * * * * * +0xe070 MMIN 17 24 3 0 * * * * * * * * * * +0xe080 MMUM 17 16 3 0 * * * * * * * * * * +0xe090 MTAN 17 16 3 0 * * * * * * * * * * +0xe0a0 MTIC 17 20 3 0 * * * * * * * * * * +0xe0b0 MTRO 17 16 3 0 * * * * * * * * * * +0xe0c0 MTSN 17 24 3 0 * * * * * * * * * * +0xe0d0 MUMB 17 24 3 0 * * * * * * * * * * +0xe200 MBET 17 24 3 0 * * * * * * * * * * +0xe210 MBFI 17 12 3 0 * * * * * * * * * * +0xe220 MBBM 17 16 3 0 * * * * * * * * * * +0xe230 MBRH 17 64 7 0 * * * * * * * * * * +0xe300 MGHO 17 16 3 0 * * * 1 * * * * * * +0xe310 MGH2 17 16 3 0 * * * * * * * * * * +0xe320 MGH3 17 16 3 0 * * * * * * * * * * +0xe400 MGO1 17 16 3 0 * * * * * * * * * * +0xe410 MGO2 17 16 3 0 * * * * * * * * * * +0xe420 MGO3 17 16 3 0 * * * * * * * * * * +0xe430 MGO4 17 16 3 0 * * * * * * * * * * +0xe500 MLIZ 17 24 3 0 * * * * * * * * * * +0xe510 MLI2 17 24 3 0 * * * * * * * * * * +0xe520 MLI3 17 32 5 0 * * * * * * * * * * +0xe600 MMYC 17 16 3 0 * * * * * * * * * * +0xe610 MMY2 17 16 3 0 * * * * * * * * * * +0xe700 MNO1 17 20 3 0 * * * * * * * * * * +0xe710 MNO2 17 20 3 0 * * * * * * * * * * +0xe720 MNO3 17 24 3 0 * * * * * * * * * * +0xe800 MOR1 17 16 3 0 * * * * * * * * * * +0xe810 MOR2 17 16 3 0 * * * * * * * * * * +0xe820 MOR3 17 16 3 0 * * * * * * * * * * +0xe830 MOR4 17 16 3 0 * * * * * * * * * * +0xe840 MOR5 17 16 3 0 * * * * * * * * * * +0xe900 MSAL 17 16 3 0 * * * * * * * * * * +0xe910 MSA2 17 16 3 0 * * * * * * * * * * +0xea00 MSHR 17 24 3 0 * * * * * * * * * * +0xea10 MSH1 17 16 3 0 * * * 1 * * * * * * +0xea20 MSH2 17 24 3 0 * * * 1 * * * * * * +0xeb00 MSKT 17 16 3 0 * * * * * * * * * * +0xeb10 MSKA 17 16 3 0 * * * * * * * * * * +0xeb20 MSKB 17 24 3 0 * * * * * * * * * * +0xec00 MWIG 17 16 3 0 * * * * * * * * * * +0xec10 MWI2 17 16 3 0 * * * * * * * * * * +0xec20 MWI3 17 16 3 0 * * * * * * * * * * +0xed00 MYU1 17 16 3 0 * * * * * * * * * * +0xed10 MYU2 17 16 3 0 * * * * * * * * * * +0xed20 MYU3 17 16 3 0 * * * * * * * * * * +0xee00 MZO2 17 16 3 0 * * * * * * * * * * +0xee10 MZO3 17 16 3 0 * * * * * * * * * * diff --git a/src/org/infinity/resource/cre/decoder/tables/avatars-bg2tob.2da b/src/org/infinity/resource/cre/decoder/tables/avatars-bg2tob.2da new file mode 100644 index 000000000..a1fe254c8 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/tables/avatars-bg2tob.2da @@ -0,0 +1,373 @@ +2DA V1.0 +* + RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD +0x0000 SPRING 0 0 0 0 * * * * 1 0 * * * * +0x0001 SPFLAMES 0 0 0 7 * * * * 0 0 * * * * +0x0002 SPRDRASI 0 0 0 7 * * * * 0 0 * * * * +0x0003 SPFLAMES 0 0 0 7 SPFLAMEB * * * 0 0 * * * * +0x0004 SPRDRASI 0 0 0 7 SPGDRASI * * * 0 0 * * * * +0x0100 SPCHUNKS 0 0 0 0 * * SPSHADOW * 1 1 * * * * +0x0200 SPBLOOD 0 0 0 0 * * * * 0 0 0 * * * +0x0210 SPBLOOD 0 0 0 0 * * * * 0 0 1 * * * +0x0220 SPBLOOD 0 0 0 0 * * * * 0 0 2 * * * +0x0230 SPBLOOD 0 0 0 0 * * * * 0 0 3 * * * +0x0240 SPBLOOD 0 0 0 0 * * * * 0 0 4 * * * +0x0300 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * +0x0400 SKLH 0 0 0 0 * * SPSHADOW * 0 1 * * * * +0x0410 GLPHWRDH 0 0 0 1 * * SPSHADOW * 0 1 * * * * +0x0500 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * +0x0510 STNKCLDD 0 0 0 0 * * * * 1 0 0 * * * +0x0520 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * +0x0600 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * +0x0610 SPICESTM 0 0 0 1 * * * * 0 0 0 * * * +0x0700 GREASEH 0 0 0 0 * * * * 0 0 * * * * +0x0710 GREASED 0 0 0 0 * * * * 0 1 * * * * +0x0800 WEBENTH 0 0 0 0 * * * * 1 0 * * * * +0x0810 WEBENTD 0 0 0 0 * * * * 1 0 * * * * +0x0900 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * +0x0910 SPMETSWM 0 0 0 1 * * * * 0 1 0 * * * +0x0a00 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * +0x0a01 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * +0x0a02 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * +0x0a03 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * +0x0a04 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * +0x0a10 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * +0x0a11 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * +0x0a12 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * +0x0a13 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * +0x0a14 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * +0x0a23 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * +0x0a24 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * +0x0b00 SPCSPRA2 0 0 0 1 * * * * 0 0 * * * * +0x0b01 SPCCOLDL 0 0 0 1 * * * * 0 0 * * * * +0x0b02 SPPRISM2 0 0 0 1 * * * * 0 0 * * * * +0x0b03 SPCSPRA3 0 0 0 1 * * * * 0 0 * * * * +0x0b04 SPPRISM3 0 0 0 1 * * * * 0 0 * * * * +0x0c00 SPSTRMVA 0 0 0 1 * * * * 0 1 * * SPSTRMVB * +0x0c10 SPSTRMVA 0 0 0 1 * * * * 0 1 * * SPSTRMVB * +0x1000 MWYV 1 24 4 0 * * * * 0 * * * * * +0x1100 MTAN 1 32 5 0 * * * * 0 * 1 * * * +0x1200 MDR1 2 72 13 0 * * * * 0 1 * * * * +0x1201 MDR2 2 93 13 0 * * * * 0 1 * * * * +0x1202 MDR3 2 72 13 0 * * * * 0 1 * * * * +0x1203 MDR1 2 72 13 0 MDR1_GR * * * 0 1 * * * * +0x1204 MDR1 2 72 13 0 MDR1_AQ * * * 0 1 * * * * +0x1205 MDR1 2 72 13 0 MDR1_BL * * * 0 1 * * * * +0x1206 MDR1 2 72 13 0 MDR1_BR * * * 0 1 * * * * +0x1207 MDR1 2 72 13 0 MDR1_MC * * * 0 1 * * * * +0x1208 MDR1 2 72 13 0 MDR1_PU * * * 0 1 * * * * +0x1300 MDEM 3 32 5 0 * * * * 0 1 * * * * +0x2000 MSIR 5 16 3 0 * * * * 1 * 1 * * BW +0x2100 UVOL 5 16 3 0 * * * * 0 * 1 * MS * +0x2200 MOGM 5 16 3 0 * * * * 1 * 1 1 S1 * +0x2300 MDKN 5 16 3 0 * * * * 0 * 1 1 * * +0x3000 MAKH 6 24 5 0 * * * * 0 * * * * * +0x4000 SNOMC 7 16 3 0 * * * * 1 * * * * * +0x4002 SNOMM 7 16 3 0 * * * * 1 * * * * * +0x4010 SNOWC 7 16 3 0 * * * * 1 * * * * * +0x4012 SNOWM 7 16 3 0 * * * * 1 * * * * * +0x4100 SSIMC 7 16 3 0 * * * * 1 * * * * * +0x4101 SSIMS 7 16 3 0 * * * * 1 * * * * * +0x4102 SSIMM 7 16 3 0 * * * * 1 * * * * * +0x4110 SSIWC 7 16 3 0 * * * * 1 * * * * * +0x4112 SSIWM 7 16 3 0 * * * * 1 * * * * * +0x4200 SHMCM 7 16 3 0 * * * * 1 * * * * * +0x4300 MSPLG1 7 32 5 0 * * * * 0 * * * * * +0x4400 LHMC 7 16 3 0 * * * * 1 * * * * * +0x4410 LHFC 7 16 3 0 * * * * 1 * * * * * +0x4500 LFAM 7 16 3 0 * * * * 1 * * * * * +0x4600 LDMF 7 16 3 0 * * * * 1 * * * * * +0x4700 LEMF 7 16 3 0 * * * * 1 * * * * * +0x4710 LEFF 7 16 3 0 * * * * 1 * * * * * +0x4800 LIMC 7 16 3 0 * * * * 1 * * * * * +0x5000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * +0x5001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * +0x5002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * +0x5003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH +0x5010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * +0x5011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * +0x5012 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * +0x5013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * +0x5100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * +0x5101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * +0x5102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * +0x5103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH +0x5110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * +0x5111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * +0x5112 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * +0x5113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * +0x5200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * +0x5201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * +0x5202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * +0x5210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * +0x5211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * +0x5212 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * +0x5300 CHMB 8 16 3 0 * * CHMT * 1 1 0 * WQL * +0x5301 CEMB 8 16 3 0 * * CEMT * 1 1 0 * WQM * +0x5302 CDMB 8 16 3 0 * * CDMT * 1 1 0 * WQS * +0x5303 CIMB 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH +0x5310 CHFB 8 16 3 0 * * CHFT * 1 1 0 * WQN * +0x5311 CEFB 8 16 3 0 * * CEFT * 1 1 0 * WQM * +0x5312 CDFB 8 16 3 0 * * CDFT * 1 1 0 * WQS * +0x5313 CIFB 8 16 3 0 * * CIFT * 1 1 0 * WQS * +0x6000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * +0x6001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * +0x6002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * +0x6003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH +0x6004 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS WQH +0x6005 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * +0x6010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * +0x6011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * +0x6012 CDMB 8 16 3 0 * * CDFC * 1 1 1 * WQS * +0x6013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * +0x6014 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS WQH +0x6015 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * +0x6100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * +0x6101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * +0x6102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * +0x6103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH +0x6104 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS WQH +0x6105 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * +0x6110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * +0x6111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * +0x6112 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * +0x6113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * +0x6114 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS WQH +0x6115 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * +0x6200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * +0x6201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * +0x6202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * +0x6204 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH +0x6205 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * +0x6210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * +0x6211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * +0x6212 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * +0x6214 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH +0x6215 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * +0x6300 CHMB 8 16 3 0 * * CHMT * 1 1 0 * WQL * +0x6301 CEMB 8 16 3 0 * * CEMT * 1 1 0 * WQM * +0x6302 CDMB 8 16 3 0 * * CDMT * 1 1 0 * WQS * +0x6303 CIMB 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH +0x6304 CDMB 8 16 3 0 * * CDMT * 1 1 0 * WQS WQH +0x6305 CHMB 8 16 3 0 * * CHMT * 1 1 0 * WQL * +0x6310 CHFB 8 16 3 0 * * CHFT * 1 1 0 * WQN * +0x6311 CEFB 8 16 3 0 * * CEFT * 1 1 0 * WQM * +0x6312 CDMB 8 16 3 0 * * CDMT * 1 1 0 * WQS * +0x6313 CIFB 8 16 3 0 * * CIFT * 1 1 0 * WQS * +0x6314 CIFB 8 16 3 0 * * CIFT * 1 1 0 * WQS WQH +0x6315 CHFB 8 16 3 0 * * CHFT * 1 1 0 * WQN * +0x6400 UDRZ 9 16 3 0 * * * * 1 * 1 0 WPM * +0x6401 UELM 9 16 3 0 * * * * 0 * 0 0 WPM * +0x6402 CMNK 9 16 3 0 * * * * 1 * 0 * WPM * +0x6403 MSKL 9 16 3 0 * * * * 1 * 1 * WPM * +0x6404 USAR 9 16 3 0 * * * * 0 * 0 0 WPL * +0x6405 MDGU 9 16 3 0 * * * * 1 * 1 * WPM * +0x6406 MDGU 9 24 7 0 * * * * 1 * 1 * WPM * +0x6500 CHMM 8 16 3 0 * * CHMM * 1 1 1 * WQL * +0x6510 CHFM 8 16 3 0 * * CHFM * 1 1 1 * WQN * +0x7000 MOGH 11 16 3 0 * * * * 1 * * * * * +0x7001 MOGN 11 16 3 0 * * * * 1 * * * * * +0x7100 MBAS 11 32 5 0 * * * * 0 * * * * * +0x7101 MBAS 11 32 5 0 MBAS_GR * * * 0 * * * * * +0x7200 MBER 11 16 3 0 MBER_BL * * * 0 * * * * * +0x7201 MBER 11 16 3 0 * * * * 0 * * * * * +0x7202 MBER 11 16 3 0 MBER_CA * * * 0 * * * * * +0x7203 MBER 11 16 3 0 MBER_PO * * * 0 * * * * * +0x7300 MEAE 10 32 5 0 * * * * 0 1 * * * * +0x7301 MEAS 10 16 3 0 * * * * 0 1 * * * * +0x7302 MEAE 10 32 5 0 MEAE_SH * * * 0 1 * * * * +0x7310 MFIE 10 16 3 4 * * * * 0 1 * * * * +0x7311 MFIS 10 16 3 4 * * * * 0 1 * * * * +0x7312 MFIE 10 16 3 4 MFIEG1B MFIEG2B * * 0 1 * * * * +0x7313 MFIS 10 16 3 4 MFIEG1B MFIEG2B * * 0 1 * * * * +0x7314 MFIE 10 16 3 4 MFIEG31 MFIEG3B * * 0 1 * * * * +0x7320 MAIR 10 32 5 0 * * * 1 0 1 * * * * +0x7321 MAIS 10 16 3 0 * * * 1 0 1 * * * * +0x7400 MDOG 11 16 3 0 MDOG_WI * * * 0 * * * * * +0x7401 MDOG 11 16 3 0 MDOG_WA * * * 0 * * * * * +0x7402 MDOG 11 16 3 0 MDOG_MO * * * 0 * * * * * +0x7500 MDOP 11 16 3 0 * * * * 0 * * * * * +0x7501 MDOP 11 16 3 0 MDOP_GR * * * 0 * * * * * +0x7600 METT 11 16 3 0 * * * * 0 * * * * * +0x7700 MGHL 11 16 3 0 * * * * 0 * * * * * +0x7701 MGHL 11 16 3 0 MGHL_RE * * * 0 * * * * * +0x7702 MGHL 11 16 3 0 MGHL_GA * * * 0 * * * * * +0x7703 MSHD 10 16 3 0 * * * 1 0 1 * * * * +0x7800 MGIB 11 16 3 0 * * * * 0 * * * * * +0x7900 MSLI 11 24 4 0 MSLI_GR * * 1 0 * * * * * +0x7901 MSLI 11 24 4 0 MSLI_OL * * 1 0 * * * * * +0x7902 MSLI 11 24 4 0 MSLI_MU * * 1 0 * * * * * +0x7903 MSLI 11 24 4 0 MSLI_OC * * 1 0 * * * * * +0x7904 MSLI 11 24 4 0 * * * 1 0 * * * * * +0x7a00 MSPI 11 16 3 0 MSPI_GI * * * 0 * * * * * +0x7a01 MSPI 11 16 3 0 MSPI_HU * * * 0 * * * * * +0x7a02 MSPI 11 16 3 0 MSPI_PH * * * 0 * * * * * +0x7a03 MSPI 11 16 3 0 MSPI_SW * * * 0 * * * * * +0x7a04 MSPI 11 16 3 0 MSPI_WR * * * 0 * * * * * +0x7b00 MWLF 11 16 3 0 * * * * 0 * * * * * +0x7b01 MWLF 11 16 3 0 MWLF_WO * * * 0 * * * * * +0x7b02 MWLF 11 16 3 0 MWLF_DI * * * 0 * * * * * +0x7b03 MWLF 11 16 3 0 MWLF_WI * * * 0 * * * * * +0x7b04 MWLF 11 16 3 0 MWLF_VA * * * 0 * * * * * +0x7b05 MWLF 11 16 3 0 MWLF_DR * * * 0 * * * * * +0x7b06 MWLS 11 16 3 0 * * * * 0 * * * * * +0x7c00 MXVT 11 16 3 0 * * * * 1 * * * * * +0x7c01 MTAS 11 16 3 0 * * * * 0 * * * * * +0x7d00 MZOM 11 16 3 0 * * * * 1 * * * * * +0x7e00 MWER 11 16 3 0 * * * * 0 * * * * * +0x7e01 MGWE 11 16 3 0 * * * * 0 * * * * * +0x7f00 MTRO 10 16 3 0 * * * * 0 1 * * * * +0x7f01 MMIN 10 16 3 0 * * * * 0 1 * * * * +0x7f02 MBEH 10 32 5 0 * * * * 0 1 * * * * +0x7f03 MIMP 10 16 3 0 * * * * 0 1 * * * * +0x7f04 MIGO 10 32 5 0 * * * * 0 1 * * * * +0x7f05 MDJI 10 16 3 0 * * * * 0 1 * * * * +0x7f06 MDJL 10 16 3 0 * * * * 0 1 * * * * +0x7f07 MGLC 10 16 3 0 * * * * 0 1 * * * * +0x7f08 MOTY 10 24 5 0 * * * * 0 1 * * * * +0x7f09 MSAH 10 16 3 0 * * * * 0 1 * * * * +0x7f0a MGCP 10 16 3 0 * * * * 0 1 * * * * +0x7f0b MGCL 10 16 3 0 * * * * 0 1 * * * * +0x7f0c MKUO 10 16 3 0 * * * * 0 1 * * * * +0x7f0d MLIC 10 16 3 0 * * * * 0 1 * * * * +0x7f0e MDLI 10 16 3 0 * * * * 0 1 * * * * +0x7f0f MTRS 10 16 3 0 * * * * 0 1 * * * * +0x7f10 MRAK 10 16 3 0 * * * * 0 1 * * * * +0x7f11 MUMB 10 16 3 0 * * * * 0 1 * * * * +0x7f12 MVAM 10 16 3 0 * * * * 0 1 * * * * +0x7f13 MSNK 10 16 3 0 * * * * 0 1 * * * * +0x7f14 MGIT 10 16 3 0 * * * * 0 1 * * * * +0x7f15 MBES 10 16 3 0 * * * * 0 1 * * * * +0x7f16 AMOO 10 32 5 0 * * * * 0 1 * * * * +0x7f17 ARAB 10 12 3 0 * * * * 0 1 * * * * +0x7f18 ADER 10 16 3 0 * * * * 0 1 * * * * +0x7f19 MDSW 10 16 3 0 * * * * 0 1 * * * * +0x7f20 AGRO 10 12 3 0 * * * * 0 1 * * * * +0x7f21 APHE 10 12 3 0 * * * * 0 1 * * * * +0x7f22 MVAF 10 16 3 0 * * * * 0 1 * * * * +0x7f23 MSAT 10 16 3 0 * * * * 0 1 * * * * +0x7f24 NPIR 10 16 3 0 * * * * 0 1 * * * * +0x7f27 MDRO 10 16 3 0 * * * * 0 1 * * * * +0x7f28 MKUL 10 16 3 0 * * * * 0 1 * * * * +0x7f29 MFDR 10 16 3 0 * * * * 0 1 * * * * +0x7f2a NSAI 10 16 3 0 * * * * 0 1 * * * * +0x7f2b MMAX 10 16 3 0 * * * * 0 0 * * * * +0x7f2c NSOL 10 16 3 0 * * * * 0 1 * * * * +0x7f2d MWFM 10 16 3 0 * * * * 0 1 * * * * +0x7f2e MRAV 10 32 5 0 * * * * 0 1 * * * * +0x7f2f MSPS 10 16 3 0 * * * * 0 1 * * * * +0x7f30 NBOH 10 16 3 0 * * * * 0 1 * * * * +0x7f31 NELL 10 16 3 0 * * * * 0 1 * * * * +0x7f32 MSLY 10 16 3 0 * * * * 0 1 * * * * +0x7f33 MKUR 10 16 3 0 * * * * 0 0 * * * * +0x7f34 MDOC 10 16 3 0 * * * * 0 0 * * * * +0x7f35 MMIS 10 16 3 0 * * * 1 0 1 * * * * +0x7f36 NSHD 10 16 3 0 * * * * 0 1 * * * * +0x7f37 NIRE 10 16 3 0 * * * * 0 1 * * * * +0x7f38 MEYE 10 16 3 0 * * * * 0 0 * * * * +0x7f39 MMST 10 16 3 1 * * * 0 0 0 * * * * +0x7f3a NIRO 10 16 3 0 * * * * 0 1 * * * * +0x7f3b MSOG 10 16 3 4 * * * 0 0 1 * * * * +0x7f3c MASG 10 16 3 4 * * * 0 0 1 * * * * +0x7f3d MMEL 10 24 3 0 * * * * 0 0 * * * * +0x7f3e MFIG 10 32 5 0 * * * * 0 1 * * * * +0x7f3f MFIG 10 32 5 0 MFIGG1B * * * 0 1 * * * * +0x8000 MGNL 4 16 3 0 * * * * * * * * S1 HB +0x8100 MHOB 4 16 3 0 * * * * * * * * S1 BW +0x8200 MKOB 4 16 3 0 * * * * * * * * SS BW +0x9000 MOGR 12 16 3 0 * * * * 1 * * * * * +0xa000 MWYV 13 16 3 0 * * * * 0 * * * * * +0xa100 MCAR 13 16 3 0 * * * * 0 * * * * * +0xb000 ACOW 14 32 5 0 * * * * 0 * * * * * +0xb100 AHRS 14 32 5 0 * * * * 0 * * * * * +0xb200 NBEGL 14 16 3 0 * * * * 1 * * * * * +0xb210 NPROL 14 16 3 0 * * * * 1 * * * * * +0xb300 NBOYL 14 16 3 0 * * * * 1 * * * * * +0xb310 NGRLL 14 16 3 0 * * * * 1 * * * * * +0xb400 NFAML 14 16 3 0 * * * * 1 * * * * * +0xb410 NFAWL 14 16 3 0 * * * * 1 * * * * * +0xb500 NSIML 14 16 3 0 * * * * 1 * * * * * +0xb510 NSIWL 14 16 3 0 * * * * 1 * * * * * +0xb600 NNOML 14 16 3 0 * * * * 1 * * * * * +0xb610 NNOWL 14 16 3 0 * * * * 1 * * * * * +0xb700 NSLVL 14 16 3 0 * * * * 1 * * * * * +0xc000 ABAT 15 12 3 0 * * * * 0 * * * * * +0xc100 ACAT 15 12 3 0 * * * * 0 * * * * * +0xc200 ACHK 15 12 3 0 * * * * 0 * * * * * +0xc300 ARAT 15 12 3 0 * * * * 0 * * * * * +0xc400 ASQU 15 12 3 0 * * * * 0 * * * * * +0xc500 ABAT 15 12 3 0 * * * * 0 * * * * * +0xc600 NBEGH 15 16 3 0 * * * * 1 * * * * * +0xc610 NPROH 15 16 3 0 * * * * 1 * * * * * +0xc700 NBOYH 15 16 3 0 * * * * 1 * * * * * +0xc710 NGRLH 15 16 3 0 * * * * 1 * * * * * +0xc800 NFAMH 15 16 3 0 * * * * 1 * * * * * +0xc810 NFAWH 15 16 3 0 * * * * 1 * * * * * +0xc900 NSIMH 15 16 3 0 * * * * 1 * * * * * +0xc910 NSIWH 15 16 3 0 * * * * 1 * * * * * +0xca00 NNOMH 15 16 3 0 * * * * 1 * * * * * +0xca10 NNOWH 15 16 3 0 * * * * 1 * * * * * +0xcb00 NSLVH 15 16 3 0 * * * * 1 * * * * * +0xd000 AEAGG1 16 0 3 0 * * * * 0 * * * * * +0xd100 AGULG1 16 0 3 0 * * * * 0 * * * * * +0xd200 AVULG1 16 0 3 0 * * * * 0 * * * * * +0xd300 ABIRG1 16 0 3 0 * * * * 0 * * * * * +0xd400 ABIRG1 16 0 3 0 * * * * 0 * * * * * +0xe000 MCYC 17 48 5 0 * * * * * * * * * * +0xe010 METN 17 48 5 0 * * * * * * * * * * +0xe020 MTAN 17 32 5 0 * * * * * * * * * * +0xe040 MHIS 17 16 3 0 * * * * * * * * * * +0xe050 MLER 17 16 3 0 * * * * * * * * * * +0xe060 MLIC 17 16 3 0 * * * * * * * * * * +0xe070 MMIN 17 24 3 0 * * * * * * * * * * +0xe080 MMUM 17 16 3 0 * * * * * * * * * * +0xe090 MTAN 17 16 3 0 * * * * * * * * * * +0xe0a0 MTIC 17 20 3 0 * * * * * * * * * * +0xe0b0 MTRO 17 16 3 0 * * * * * * * * * * +0xe0c0 MTSN 17 24 3 0 * * * * * * * * * * +0xe0d0 MUMB 17 24 3 0 * * * * * * * * * * +0xe0e0 MCOR 17 24 3 0 * * * * * * * * * * +0xe0f0 MGIC 17 32 5 0 * * * * * * * * * * +0xe0f1 MGLA 17 24 3 0 * * * * * * * * * * +0xe0f2 MWAV 17 16 3 0 * * * 1 * * * * * * +0xe200 MBET 17 24 3 0 * * * * * * * * * * +0xe210 MBFI 17 12 3 0 * * * * * * * * * * +0xe220 MBBM 17 16 3 0 * * * * * * * * * * +0xe230 MBRH 17 64 7 0 * * * * * * * * * * +0xe300 MGHO 17 16 3 0 * * * 1 * * * * * * +0xe310 MGH2 17 16 3 0 * * * * * * * * * * +0xe320 MGH3 17 16 3 0 * * * * * * * * * * +0xe400 MGO1 17 16 3 0 * * * * * * * * * * +0xe410 MGO2 17 16 3 0 * * * * * * * * * * +0xe420 MGO3 17 16 3 0 * * * * * * * * * * +0xe430 MGO4 17 16 3 0 * * * * * * * * * * +0xe500 MLIZ 17 24 3 0 * * * * * * * * * * +0xe510 MLI2 17 24 3 0 * * * * * * * * * * +0xe520 MLI3 17 32 5 0 * * * * * * * * * * +0xe600 MMYC 17 16 3 0 * * * * * * * * * * +0xe610 MMY2 17 16 3 0 * * * * * * * * * * +0xe700 MNO1 17 20 3 0 * * * * * * * * * * +0xe710 MNO2 17 20 3 0 * * * * * * * * * * +0xe720 MNO3 17 24 3 0 * * * * * * * * * * +0xe800 MOR1 17 16 3 0 * * * * * * * * * * +0xe810 MOR2 17 16 3 0 * * * * * * * * * * +0xe820 MOR3 17 16 3 0 * * * * * * * * * * +0xe830 MOR4 17 16 3 0 * * * * * * * * * * +0xe840 MOR5 17 16 3 0 * * * * * * * * * * +0xe900 MSAL 17 16 3 0 * * * * * * * * * * +0xe910 MSA2 17 16 3 0 * * * * * * * * * * +0xea00 MSHR 17 24 3 0 * * * * * * * * * * +0xea10 MSH1 17 16 3 0 * * * 1 * * * * * * +0xea20 MSH2 17 24 3 0 * * * 1 * * * * * * +0xeb00 MSKT 17 16 3 0 * * * * * * * * * * +0xeb10 MSKA 17 16 3 0 * * * * * * * * * * +0xeb20 MSKB 17 24 3 0 * * * * * * * * * * +0xec00 MWIG 17 16 3 0 * * * * * * * * * * +0xec10 MWI2 17 16 3 0 * * * * * * * * * * +0xec20 MWI3 17 16 3 0 * * * * * * * * * * +0xed00 MYU1 17 16 3 0 * * * * * * * * * * +0xed10 MYU2 17 16 3 0 * * * * * * * * * * +0xed20 MYU3 17 16 3 0 * * * * * * * * * * +0xee00 MZO2 17 16 3 0 * * * * * * * * * * +0xee10 MZO3 17 16 3 0 * * * * * * * * * * +0xef10 MWWE 17 24 3 0 * * * * * * * * * * diff --git a/src/org/infinity/resource/cre/decoder/tables/avatars-bgee.2da b/src/org/infinity/resource/cre/decoder/tables/avatars-bgee.2da new file mode 100644 index 000000000..df737deb8 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/tables/avatars-bgee.2da @@ -0,0 +1,519 @@ +2DA V1.0 +* + RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD +0x0000 SPRING 0 0 0 0 * * * * 1 0 * * * * +0x0001 SPFLAMES 0 0 0 7 * * * * 0 0 * * * * +0x0002 SPRDRASI 0 0 0 7 * * * * 0 0 * * * * +0x0003 SPFLAMES 0 0 0 7 SPFLAMEB * * * 0 0 * * * * +0x0004 SPRDRASI 0 0 0 7 SPGDRASI * * * 0 0 * * * * +0x0100 SPCHUNKS 0 0 0 0 * * SPSHADOW * 1 1 * * * * +0x0101 BGLRYU 0 0 3 7 * * * * * 0 * * * * +0x0102 SPCL236U 0 0 3 7 * * * * * 0 * * * * +0x0200 SPBLOOD 0 0 0 0 * * * * 0 0 0 * * * +0x0210 SPBLOOD 0 0 0 0 * * * * 0 0 1 * * * +0x0220 SPBLOOD 0 0 0 0 * * * * 0 0 2 * * * +0x0230 SPBLOOD 0 0 0 0 * * * * 0 0 3 * * * +0x0240 SPBLOOD 0 0 0 0 * * * * 0 0 4 * * * +0x0300 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * +0x0301 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * +0x0400 SKLH 0 0 0 0 * * SPSHADOW * 0 1 * * * * +0x0410 GLPHWRDH 0 0 0 1 * * SPSHADOW * 0 1 * * * * +0x0500 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * +0x0510 STNKCLDD 0 0 0 0 * * * * 1 0 0 * * * +0x0520 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * +0x0600 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * +0x0610 SPICESTM 0 0 0 1 * * * * 0 0 0 * * * +0x0700 GREASEH 0 0 0 0 * * * * 0 0 * * * * +0x0710 GREASED 0 0 0 0 * * * * 0 1 * * * * +0x0800 WEBENTH 0 0 0 0 * * * * 1 0 * * * * +0x0810 WEBENTD 0 0 0 0 * * * * 1 0 * * * * +0x0900 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * +0x0910 SPMETSWM 0 0 0 1 * * * * 0 1 0 * * * +0x0a00 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * +0x0a01 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * +0x0a02 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * +0x0a03 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * +0x0a04 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * +0x0a10 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * +0x0a11 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * +0x0a12 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * +0x0a13 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * +0x0a14 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * +0x0a23 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * +0x0a24 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * +0x0b00 SPCSPRA2 0 0 0 1 * * * * 0 0 * * * * +0x0b01 SPCCOLDL 0 0 0 1 * * * * 0 0 * * * * +0x0b02 SPPRISM2 0 0 0 1 * * * * 0 0 * * * * +0x0b03 SPCSPRA3 0 0 0 1 * * * * 0 0 * * * * +0x0b04 SPPRISM3 0 0 0 1 * * * * 0 0 * * * * +0x0c00 SPSTRMVA 0 0 0 1 * * * * 0 1 * * SPSTRMVB * +0x0c10 SPSTRMVA 0 0 0 1 * * * * 0 1 * * SPSTRMVB * +0x1000 MWYV 1 24 4 0 * * * * 0 * * * * * +0x1003 MWYV 1 24 4 0 MWYV_WH * * * 0 * * * * * +0x1004 MWYV 1 24 4 0 MWYV_AL * * * 0 * * * * * +0x1100 MTAN 1 32 5 0 * * * * 0 * 1 * * * +0x1101 MWDR 1 72 7 0 * * * * 0 * * * * * +0x1102 MTAN 1 32 5 0 MTAN_BL * * * 0 * 1 * * * +0x1103 MTAN 1 32 5 0 MTAN_GR * * * 0 * 1 * * * +0x1104 MTAN 1 32 5 0 MTAN_RD * * * 0 * 1 * * * +0x1105 MWDR 1 72 7 0 MWDR_GR * * * 0 * * * * * +0x1200 MDR1 2 72 13 0 * * * * 0 1 * * * * +0x1201 MDR2 2 72 13 0 * * * * 0 1 * * * * +0x1202 MDR3 2 72 13 0 * * * * 0 1 * * * * +0x1203 MDR1 2 72 13 0 MDR1_GR * * * 0 1 * * * * +0x1204 MDR1 2 72 13 0 MDR1_AQ * * * 0 1 * * * * +0x1205 MDR1 2 72 13 0 MDR1_BL * * * 0 1 * * * * +0x1206 MDR1 2 72 13 0 MDR1_BR * * * 0 1 * * * * +0x1207 MDR1 2 72 13 0 MDR1_MC * * * 0 1 * * * * +0x1208 MDR1 2 72 13 0 MDR1_PU * * * 0 1 * * * * +0x1300 MDEM 3 32 5 0 * * * * 0 1 * * * * +0x2000 MSIR 5 16 3 0 * * * * 1 * 1 * * BW +0x2100 UVOL 5 16 3 0 * * * * 0 * 1 * MS * +0x2200 MOGM 5 16 3 0 * * * * 1 * 1 1 S1 * +0x2300 MDKN 5 16 3 0 * * * * 0 * 1 1 * * +0x3000 MAKH 6 24 5 0 * * * * 0 * * * * * +0x3001 MNEO 6 60 5 0 * * * * 0 * * * * * +0x4000 SNOMC 7 16 3 0 * * * * 1 * * * * * +0x4001 SNONE 7 0 0 0 * * * * 0 * * * * * +0x4002 SNOMM 7 16 3 0 * * * * 1 * * * * * +0x4010 SNOWC 7 16 3 0 * * * * 1 * * * * * +0x4012 SNOWM 7 16 3 0 * * * * 1 * * * * * +0x4100 SSIMC 7 16 3 0 * * * * 1 * * * * * +0x4101 SSIMS 7 16 3 0 * * * * 1 * * * * * +0x4102 SSIMM 7 16 3 0 * * * * 1 * * * * * +0x4110 SSIWC 7 16 3 0 * * * * 1 * * * * * +0x4112 SSIWM 7 16 3 0 * * * * 1 * * * * * +0x4200 SHMCM 7 16 3 0 * * * * 1 * * * * * +0x4300 MSPLG1 7 32 5 0 * * * * 0 * * * * * +0x4400 LHMC 7 16 3 0 * * * * 1 * * * * * +0x4410 LHFC 7 16 3 0 * * * * 1 * * * * * +0x4500 LFAM 7 16 3 0 * * * * 1 * * * * * +0x4600 LDMF 7 16 3 0 * * * * 1 * * * * * +0x4700 LEMF 7 16 3 0 * * * * 1 * * * * * +0x4710 LEFF 7 16 3 0 * * * * 1 * * * * * +0x4800 LIMC 7 16 3 0 * * * * 1 * * * * * +0x5000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * +0x5001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * +0x5002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * +0x5003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH +0x5010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * +0x5011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * +0x5012 CDFB 8 16 3 0 * * CDFC * 1 1 1 * WQS * +0x5013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * +0x5100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * +0x5101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * +0x5102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * +0x5103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH +0x5110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * +0x5111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * +0x5112 CDFB 8 16 3 0 * * CDFF * 1 1 1 * WQS * +0x5113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * +0x5200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * +0x5201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * +0x5202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * +0x5210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * +0x5211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * +0x5212 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS * +0x5300 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * +0x5301 CEMT 8 16 3 0 * * CEMT * 1 1 0 * WQM * +0x5302 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * +0x5303 CIMT 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH +0x5310 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * +0x5311 CEFT 8 16 3 0 * * CEFT * 1 1 0 * WQM * +0x5312 CDFT 8 16 3 0 * * CDFT * 1 1 0 * WQS * +0x5313 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS * +0x6000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * +0x6001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * +0x6002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * +0x6003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH +0x6004 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS WQH +0x6005 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * +0x6010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * +0x6011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * +0x6012 CDFB 8 16 3 0 * * CDFC * 1 1 1 * WQS * +0x6013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * +0x6014 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS WQH +0x6015 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * +0x6100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * +0x6101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * +0x6102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * +0x6103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH +0x6104 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS WQH +0x6105 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * +0x6110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * +0x6111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * +0x6112 CDFB 8 16 3 0 * * CDFF * 1 1 1 * WQS * +0x6113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * +0x6114 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS WQH +0x6115 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * +0x6200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * +0x6201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * +0x6202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * +0x6204 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH +0x6205 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * +0x6210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * +0x6211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * +0x6212 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS * +0x6214 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS WQH +0x6215 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * +0x6300 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * +0x6301 CEMT 8 16 3 0 * * CEMT * 1 1 0 * WQM * +0x6302 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * +0x6303 CIMT 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH +0x6304 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS WQH +0x6305 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * +0x6310 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * +0x6311 CEFT 8 16 3 0 * * CEFT * 1 1 0 * WQM * +0x6312 CDFT 8 16 3 0 * * CDFT * 1 1 0 * WQS * +0x6313 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS * +0x6314 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS WQH +0x6315 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * +0x6400 UDRZ 9 16 3 0 * * * * 1 * 1 0 WPM * +0x6401 UELM 9 16 3 0 * * * * 0 * 0 0 WPM * +0x6402 CMNK 9 16 3 0 * * * * 1 * 0 * WPM * +0x6403 MSKL 9 16 3 0 * * * * 1 * 1 * WPM * +0x6404 USAR 9 16 3 0 * * * * 0 * 0 0 WPL * +0x6405 MDGU 9 16 3 0 * * * * 1 * 1 * WPM * +0x6406 MDGU 9 24 5 0 * * * * 1 * 1 * WPM * +0x6500 CHMM 8 16 3 0 * * CHMM * 1 1 1 * WQL * +0x6510 CHFM 8 16 3 0 * * CHFM * 1 1 1 * WQN * +0x6621 XHFF 9 16 3 0 * * * * 1 * 1 * WPM * +0x7000 MOGH 11 16 3 0 * * * * 1 * * * * * +0x7001 MOGN 11 16 3 0 * * * * 1 * * * * * +0x7100 MBAS 11 32 5 0 * * * * 0 * * * * * +0x7101 MBAS 11 32 5 0 MBAS_GR * * * 0 * * * * * +0x7200 MBER 11 16 3 0 MBER_BL * * * 0 * * * * * +0x7201 MBER 11 16 3 0 * * * * 0 * * * * * +0x7202 MBER 11 16 3 0 MBER_CA * * * 0 * * * * * +0x7203 MBER 11 16 3 0 MBER_PO * * * 0 * * * * * +0x7300 MEAE 10 32 5 0 * * * * 0 1 * * * * +0x7301 MEAS 10 16 3 0 * * * * 0 1 * * * * +0x7302 MEAE 10 32 5 0 MEAE_SH * * * 0 1 * * * * +0x7310 MFIE 10 16 3 4 * * * * 0 1 * * * * +0x7311 MFIS 10 16 3 4 * * * * 0 1 * * * * +0x7312 MFIE 10 16 3 4 MFIEG1B MFIEG2B * * 0 1 * * * * +0x7313 MFIS 10 16 3 4 MFISG1B MFISG2B * * 0 1 * * * * +0x7314 MFIE 10 16 3 4 MFIEG31 MFIEG3B * * 0 1 * * * * +0x7320 MAIR 10 32 5 0 * * * 1 0 1 * * * * +0x7321 MAIS 10 16 3 0 * * * 1 0 1 * * * * +0x7400 MDOG 11 16 3 0 MDOG_WI * * * 0 * * * * * +0x7401 MDOG 11 16 3 0 MDOG_WA * * * 0 * * * * * +0x7402 MDOG 11 16 3 0 MDOG_MO * * * 0 * * * * * +0x7500 MDOP 11 16 3 0 * * * * 0 * * * * * +0x7501 MDOP 11 16 3 0 MDOP_GR * * * 0 * * * * * +0x7600 METT 11 16 3 0 * * * * 0 * * * * * +0x7601 MGHL 11 16 3 0 MGHL_MA * * * 0 0 * * * * +0x7602 MSPI 11 16 3 0 MSPI_MY * * * 0 * * * * * +0x7603 MDOG 11 16 3 0 MDOG_GR * * * 0 * * * * * +0x7604 MSPI 11 16 3 0 MSPI_WA * * * 0 * * * * * +0x7700 MGHL 11 16 3 0 * * * * 0 * * * * * +0x7701 MGHL 11 16 3 0 MGHL_RE * * * 0 * * * * * +0x7702 MGHL 11 16 3 0 MGHL_GA * * * 0 * * * * * +0x7703 MSHD 10 16 3 0 * * * 1 0 1 * * * * +0x7800 MGIB 11 16 3 0 * * * * 0 * * * * * +0x7801 MGIB 11 16 3 0 MGIB_BR * * * 0 * * * * * +0x7802 MSLI 11 24 4 0 MSLI_BL * * 0 0 * * * * * +0x7900 MSLI 11 24 4 0 MSLI_GR * * 1 0 * * * * * +0x7901 MSLI 11 24 4 0 MSLI_OL * * 1 0 * * * * * +0x7902 MSLI 11 24 4 0 MSLI_MU * * 1 0 * * * * * +0x7903 MSLI 11 24 4 0 MSLI_OC * * 1 0 * * * * * +0x7904 MSLI 11 24 4 0 * * * 1 0 * * * * * +0x7a00 MSPI 11 16 3 0 MSPI_GI * * * 0 * * * * * +0x7a01 MSPI 11 16 3 0 MSPI_HU * * * 0 * * * * * +0x7a02 MSPI 11 16 3 0 MSPI_PH * * * 0 * * * * * +0x7a03 MSPI 11 16 3 0 MSPI_SW * * * 0 * * * * * +0x7a04 MSPI 11 16 3 0 MSPI_WR * * * 0 * * * * * +0x7b00 MWLF 11 16 3 0 * * * * 0 * * * * * +0x7b01 MWLF 11 16 3 0 MWLF_WO * * * 0 * * * * * +0x7b02 MWLF 11 16 3 0 MWLF_DI * * * 0 * * * * * +0x7b03 MWLF 11 16 3 0 MWLF_WI * * * 0 * * * * * +0x7b04 MWLF 11 16 3 0 MWLF_VA * * * 0 * * * * * +0x7b05 MWLF 11 16 3 0 MWLF_DR * * * 0 * * * * * +0x7b06 MWLS 11 16 3 0 * * * * 0 * * * * * +0x7c00 MXVT 11 16 3 0 * * * * 1 * * * * * +0x7c01 MTAS 11 16 3 0 * * * * 0 * * * * * +0x7d00 MZOM 11 16 3 0 * * * * 1 * * * * * +0x7d01 NSLF 11 16 3 0 * * * * 1 0 * * * * +0x7d02 ACHB 11 12 3 0 * * * * 0 0 * * * * +0x7d03 ACHW 11 12 3 0 * * * * 0 0 * * * * +0x7d04 NPRF 11 16 3 0 * * * * 1 0 * * * * +0x7d05 NNMF 11 16 3 0 * * * * 1 0 * * * * +0x7d06 NNWF 11 16 3 0 * * * * 1 0 * * * * +0x7d07 NSMF 11 16 3 0 * * * * 1 0 * * * * +0x7d08 NSWF 11 16 3 0 * * * * 1 0 * * * * +0x7e00 MWER 11 16 3 0 * * * * 0 * * * * * +0x7e01 MGWE 11 16 3 0 * * * * 0 * * * * * +0x7f00 MTRO 10 16 3 0 * * * * 0 1 * * * * +0x7f01 MMIN 10 16 3 0 * * * * 0 1 * * * * +0x7f02 MBEH 10 32 5 0 * * * * 0 1 * * * * +0x7f03 MIMP 10 16 3 0 * * * * 0 1 * * * * +0x7f04 MIGO 10 32 5 0 * * * * 0 1 * * * * +0x7f05 MDJI 10 16 3 0 * * * * 0 1 * * * * +0x7f06 MDJL 10 16 3 0 * * * * 0 1 * * * * +0x7f07 MGLC 10 16 3 0 * * * * 0 1 * * * * +0x7f08 MOTY 10 24 5 0 * * * * 0 1 * * * * +0x7f09 MSAH 10 16 3 0 * * * * 0 1 * * * * +0x7f0a MGCP 10 16 3 0 * * * * 0 1 * * * * +0x7f0b MGCL 10 16 3 0 * * * * 0 1 * * * * +0x7f0c MKUO 10 16 3 0 * * * * 0 1 * * * * +0x7f0d MLIC 10 16 3 0 * * * * 0 1 * * * * +0x7f0e MDLI 10 16 3 0 * * * * 0 1 * * * * +0x7f0f MTRS 10 16 3 0 * * * * 0 1 * * * * +0x7f10 MRAK 10 16 3 0 * * * * 0 1 * * * * +0x7f11 MUMB 10 16 3 0 * * * * 0 1 * * * * +0x7f12 MVAM 10 16 3 0 * * * * 0 1 * * * * +0x7f13 MSNK 10 16 3 0 * * * * 0 1 * * * * +0x7f14 MGIT 10 16 3 0 * * * * 0 1 * * * * +0x7f15 MBES 10 16 3 0 * * * * 0 1 * * * * +0x7f16 AMOO 10 32 5 0 * * * * 0 1 * * * * +0x7f17 ARAB 10 12 3 0 * * * * 0 1 * * * * +0x7f18 ADER 10 16 3 0 * * * * 0 1 * * * * +0x7f19 MDSW 10 16 3 0 * * * * 0 1 * * * * +0x7f20 AGRO 10 12 3 0 * * * * 0 1 * * * * +0x7f21 APHE 10 12 3 0 * * * * 0 1 * * * * +0x7f22 MVAF 10 16 3 0 * * * * 0 1 * * * * +0x7f23 MSAT 10 16 3 0 * * * * 0 1 * * * * +0x7f24 NPIR 10 16 3 0 * * * * 0 1 * * * * +0x7f27 MDRO 10 16 3 0 * * * * 0 1 * * * * +0x7f28 MKUL 10 16 3 0 * * * * 0 1 * * * * +0x7f29 MFDR 10 16 3 0 * * * * 0 1 * * * * +0x7f2a NSAI 10 16 3 0 * * * * 0 1 * * * * +0x7f2b MMAX 10 16 3 0 * * * * 0 0 * * * * +0x7f2c NSOL 10 16 3 0 * * * * 0 1 * * * * +0x7f2d MWFM 10 16 3 0 * * * * 0 1 * * * * +0x7f2e MRAV 10 32 5 0 * * * * 0 1 * * * * +0x7f2f MSPS 10 16 3 0 * * * * 0 1 * * * * +0x7f30 NBOH 10 16 3 0 * * * * 0 1 * * * * +0x7f31 NELL 10 16 3 0 * * * * 0 1 * * * * +0x7f32 MSLY 10 16 3 0 * * * * 0 1 * * * * +0x7f33 MKUR 10 16 3 0 * * * * 0 0 * * * * +0x7f34 MDOC 10 16 3 0 * * * * 0 0 * * * * +0x7f35 MMIS 10 16 3 0 * * * 1 0 1 * * * * +0x7f36 NSHD 10 16 3 0 * * * * 0 1 * * * * +0x7f37 NIRE 10 16 3 0 * * * * 0 1 * * * * +0x7f38 MEYE 10 16 3 0 * * * * 0 0 * * * * +0x7f39 MMST 10 16 3 1 * * * 0 0 0 * * * * +0x7f3a NIRO 10 16 3 0 * * * * 0 1 * * * * +0x7f3b MSOG 10 16 3 4 * * * 0 0 1 * * * * +0x7f3c MASL 10 16 3 7 * * MASG 0 0 1 * * * * +0x7f3d MMEL 10 24 3 0 * * * * 0 0 * * * * +0x7f3e MFIG 10 32 5 0 * * * * 0 1 * * * * +0x7f3f MFIG 10 32 5 0 MFIGG1B * * * 0 1 * * * * +0x7f40 MGLM 10 16 3 0 * * * * 0 1 * * * * +0x7f41 MDJL 10 16 3 0 MDJL_E1 MDJL_E2 * * 0 1 * * * * +0x7f42 NIRO 10 16 3 0 NIRO_RD * * * 0 1 * * * * +0x7f43 MOTY 10 16 3 0 MOTY_Y1 MOTY_Y2 * * 0 1 * * * * +0x7f44 NELW 10 16 3 0 * * * * 0 0 * * * * +0x7f45 MSAI 10 16 3 0 * * * 1 0 0 * * * * +0x7f46 MBEG 10 16 3 0 * * * * 0 0 * * * * +0x7f47 MFI2 10 32 5 0 * * * * 0 0 * * * * +0x7f48 MSOF 10 16 3 0 * * * 0 0 1 * * * * +0x7f49 MDMF 10 16 3 0 * * * 0 0 1 * * * * +0x7f4a MDAS 10 16 3 7 * * MDAG 0 0 1 * * * * +0x7f4b MDAF 10 16 3 0 * * * 0 0 1 * * * * +0x7f4c MPLN 10 16 3 7 * * MPLG 0 0 1 * * * * +0x7f4d MPLF 10 16 3 0 * * * 0 0 1 * * * * +0x7f4e MDVM 10 16 3 7 * * MDVG 0 0 1 * * * * +0x7f4f MDVF 10 16 3 0 * * * 0 0 1 * * * * +0x7f50 MMST 10 16 3 1 MMST_NI * * 0 0 0 * * * * +0x7f51 MMST 10 16 3 1 MMST_HA * * 0 0 0 * * * * +0x7f52 NSAI 10 16 3 0 NSAI_G1 NSAI_G2 * * 0 1 * * * * +0x7f53 NSAI 10 16 3 0 NSAI_R1 NSAI_R2 * * 0 1 * * * * +0x7f54 NSAI 10 16 3 0 NSAI_D1 NSAI_D2 * * 0 1 * * * * +0x7f55 NSOL 10 16 3 0 NSOL_C1 NSOL_C2 * * 0 1 * * * * +0x7f56 MWIL 10 16 3 7 * * * * 0 0 * * * * +0x7f57 MWIS 10 12 3 7 * * * * 0 0 * * * * +0x7f58 MABB 10 24 3 0 * * * * 0 0 * * * * +0x7f59 MABG 10 24 3 0 * * * * 0 0 * * * * +0x7f5a MABR 10 24 3 0 * * * * 0 0 * * * * +0x7f5b MABR 10 24 3 0 MABR_BL * * * 0 0 * * * * +0x7f5c MHAG 10 16 3 0 * * * * 0 0 * * * * +0x7f5d MHAG 10 16 3 0 MHAG_GR * * * 0 0 * * * * +0x7f5e MHAG 10 16 3 0 MHAG_SE * * * 0 0 * * * * +0x7f5f MHAN 10 16 3 0 * * * * 0 0 * * * * +0x7f60 MSNK 10 16 3 0 MSNK_W1 MSNK_W2 * * 0 1 * * * * +0x7f61 MDRJ 10 48 9 0 * * * * 0 0 * * * * +0x7f62 MDRJ 10 72 9 0 MDRJ_GR * * * 0 0 * * * * +0x8000 MGNL 4 16 3 0 * * * * * * * * S1 HB +0x8100 MHOB 4 16 3 0 * * * * * * * * S1 BW +0x8200 MKOB 4 16 3 0 * * * * * * * * SS BW +0x9000 MOGR 12 16 3 0 * * * * 1 * * * * * +0xa000 MWYV 13 16 3 0 * * * * 0 * * * * * +0xa100 MCAR 13 16 3 0 * * * * 0 * * * * * +0xa200 MWYV 13 24 4 0 MWYV_WS * * * 0 * * * * * +0xa201 MCEN 13 16 3 0 * * * * 0 * * * * * +0xa202 MTUN 13 16 3 0 * * * * 0 * * * * * +0xb000 ACOW 14 32 5 0 * * * * 0 * * * * * +0xb100 AHRS 14 32 5 0 * * * * 0 * * * * * +0xb200 NBEGL 14 16 3 0 * * * * 1 * * * * * +0xb210 NPROL 14 16 3 0 * * * * 1 * * * * * +0xb300 NBOYL 14 16 3 0 * * * * 1 * * * * * +0xb310 NGRLL 14 16 3 0 * * * * 1 * * * * * +0xb400 NFAML 14 16 3 0 * * * * 1 * * * * * +0xb410 NFAWL 14 16 3 0 * * * * 1 * * * * * +0xb500 NSIML 14 16 3 0 * * * * 1 * * * * * +0xb510 NSIWL 14 16 3 0 * * * * 1 * * * * * +0xb600 NNOML 14 16 3 0 * * * * 1 * * * * * +0xb610 NNOWL 14 16 3 0 * * * * 1 * * * * * +0xb700 NSLVL 14 16 3 0 * * * * 1 * * * * * +0xc000 ABAT 15 12 3 0 * * * * 0 * * * * * +0xc100 ACAT 15 12 3 0 * * * * 0 * * * * * +0xc200 ACHK 15 12 3 0 * * * * 0 * * * * * +0xc300 ARAT 15 12 3 0 * * * * 0 * * * * * +0xc400 ASQU 15 12 3 0 * * * * 0 * * * * * +0xc500 ABAT 15 12 3 0 * * * * 0 * * * * * +0xc600 NBEGH 15 16 3 0 * * * * 1 * * * * * +0xc610 NPROH 15 16 3 0 * * * * 1 * * * * * +0xc700 NBOYH 15 16 3 0 * * * * 1 * * * * * +0xc710 NGRLH 15 16 3 0 * * * * 1 * * * * * +0xc800 NFAMH 15 16 3 0 * * * * 1 * * * * * +0xc810 NFAWH 15 16 3 0 * * * * 1 * * * * * +0xc900 NSIMH 15 16 3 0 * * * * 1 * * * * * +0xc910 NSIWH 15 16 3 0 * * * * 1 * * * * * +0xca00 NNOMH 15 16 3 0 * * * * 1 * * * * * +0xca10 NNOWH 15 16 3 0 * * * * 1 * * * * * +0xcb00 NSLVH 15 16 3 0 * * * * 1 * * * * * +0xcc00 MKG1 15 16 3 0 * * * * 0 * * * * * +0xcc01 MKG2 15 16 3 0 * * * * 0 * * * * * +0xcc02 MKG3 15 16 3 0 * * * * 0 * * * * * +0xcc04 ARAT 15 0 1 0 * * * * 0 * * * * * +0xd000 AEAGG1 16 0 3 0 * * * * 0 * * * * * +0xd100 AGULG1 16 0 3 0 * * * * 0 * * * * * +0xd200 AVULG1 16 0 3 0 * * * * 0 * * * * * +0xd300 ABIRG1 16 0 3 0 * * * * 0 * * * * * +0xd400 ABIRG1 16 0 3 0 * * * * 0 * * * * * +0xe000 MCYC 17 48 5 0 * * * * * * * * * * +0xe010 METN 17 48 5 0 * * * * * * * * * * +0xe020 MTAN 17 32 5 0 * * * * * * * * * * +0xe040 MHIS 17 16 3 0 * * * * * * * * * * +0xe050 MLER 17 16 3 0 * * * * * * * * * * +0xe060 MLIC 17 16 3 0 * * * * * * * * * * +0xe070 MMIN 17 24 3 0 * * * * * * * * * * +0xe080 MMUM 17 16 3 0 * * * * * * * * * * +0xe090 MTAN 17 16 3 0 * * * * * * * * * * +0xe0a0 MTIC 17 20 3 0 * * * * * * * * * * +0xe0b0 MTRO 17 16 3 0 * * * * * * * * * * +0xe0c0 MTSN 17 24 3 0 * * * * * * * * * * +0xe0d0 MUMB 17 24 3 0 * * * * * * * * * * +0xe0e0 MCOR 17 24 3 0 * * * * * * * * * * +0xe0f0 MGIC 17 32 5 0 * * * * * * * * * * +0xe0f1 MGLA 17 24 3 0 * * * * * * * * * * +0xe0f2 MWAV 17 16 3 0 * * * 1 * * * * * * +0xe200 MBET 17 24 3 0 * * * * * * * * * * +0xe210 MBFI 17 12 3 0 * * * * * * * * * * +0xe220 MBBM 17 16 3 0 * * * * * * * * * * +0xe230 MBRH 17 64 7 0 * * * * * * * * * * +0xe240 MANI 17 24 3 0 * * * * * * * * * * +0xe241 MAN2 17 24 3 0 * * * * * * * * * * +0xe242 MAN3 17 24 3 0 * * * * * * * * * * +0xe243 MARU 17 16 3 0 * * * * * * * * * * +0xe244 MBA1 17 16 3 0 * * * * * * * * * * +0xe245 MBA2 17 16 3 0 * * * * * * * * * * +0xe246 MBA3 17 16 3 0 * * * * * * * * * * +0xe247 MBA4 17 16 3 0 * * * * * * * * * * +0xe248 MBA5 17 16 3 0 * * * * * * * * * * +0xe249 MBA6 17 16 3 0 * * * * * * * * * * +0xe24a MBAI 17 16 3 0 * * * * * * * * * * +0xe24b MELE 17 36 3 0 * * * * * * * * * * +0xe24c MELF 17 36 3 0 * * * * * * * * * * +0xe24d MELW 17 36 3 0 * * * * * * * * * * +0xe24e MGFI 17 48 3 0 * * * * * * * * * * +0xe24f MGFR 17 48 5 0 * * * * * * * * * * +0xe250 MGIR 17 24 3 0 * * * * * * * * * * +0xe251 MGVE 17 32 5 0 * * * * * * * * * * +0xe252 MHAR 17 16 3 0 * * * * * * * * * * +0xe253 MREM 17 48 5 0 * * * * * * * * * * +0xe254 MSCR 17 24 3 0 * * * * * * * * * * +0xe255 MSEE 17 16 3 0 * * * * * * * * * * +0xe256 MBE1 17 32 4 0 * * * * * * * * * * +0xe257 MBE2 17 16 3 0 * * * * * * * * * * +0xe258 MBE2 17 16 3 0 MBE2_HE * * * * * * * * * +0xe259 MSVI 17 16 3 0 * * * * * * * * * * +0xe25a MSV2 17 16 3 0 * * * * * * * * * * +0xe25b MUM2 17 24 3 0 * * * * * * * * * * +0xe25c MTA2 17 16 3 0 * * * * * * * * * * +0xe25d MYET 17 24 3 0 * * * * * * * * * * +0xe25e MWI4 17 16 3 0 * * * * * * * * * * +0xe25f MDRD 17 16 3 0 * * * * * * * * * * +0xe260 MCRD 17 24 3 0 * * * * * * * * * * +0xe261 MSAH 17 24 3 0 * * * * * * * * * * +0xe262 MSAT 17 24 3 0 * * * * * * * * * * +0xe263 MSV3 17 16 3 0 * * * * * * * * * * +0xe264 MSV4 17 16 3 0 * * * * * * * * * * +0xe265 MBOA 17 24 3 0 * * * * * * * * * * +0xe266 MBU2 17 16 3 0 * * * * * * * * * * +0xe267 MBUG 17 16 3 0 * * * * * * * * * * +0xe26a MDH2 17 24 3 0 * * * * * * * * * * +0xe26b MDTR 17 24 3 0 * * * * * * * * * * +0xe26d MFE2 17 24 3 0 * * * * * * * * * * +0xe26e MFEY 17 24 3 0 * * * * * * * * * * +0xe26f MGFO 17 24 4 0 * * * * * * * * * * +0xe270 MGO5 17 16 3 0 * * * * * * * * * * +0xe271 MGOC 17 16 3 0 * * * * * * * * * * +0xe272 MGWO 17 24 3 0 * * * * * * * * * * +0xe273 MGW2 17 24 3 0 * * * * * * * * * * +0xe274 MHOH 17 24 3 0 * * * * * * * * * * +0xe276 MLEM 17 16 3 0 * * * * * * * * * * +0xe279 MNOS 17 24 3 0 * * * * * * * * * * +0xe27d MWEB 17 16 3 0 * * * * * * * * * * +0xe27e MWRA 17 16 3 0 * * * * * * * * * * +0xe27f MBOA 17 24 3 0 MBOA_BR * * * * * * * * * +0xe280 MWOR 17 24 3 0 * * * * * * * * * * +0xe281 MYUH 17 16 3 0 * * * * * * * * * * +0xe282 MDRF 17 32 4 0 * * * * * * * * * * +0xe283 MDRM 17 32 4 0 * * * * * * * * * * +0xe288 MGWO 17 24 3 0 MGWO_DK * * * * * * * * * +0xe289 MGW2 17 24 3 0 MGW2_DK * * * * * * * * * +0xe28a MTRO 17 16 3 0 MTRO_DK * * * * * * * * * +0xe28b MABW 17 24 3 0 * * * * * * * * * * +0xe28c MWD2 17 36 5 0 * * * * * * * * * * +0xe28d MWD2 17 36 5 0 MWD2_SI * * * * * * * * * +0xe28e MWD2 17 36 5 0 MWD2_GR * * * * * * * * * +0xe28f MDRD 17 16 3 0 MDRD_RE * * * * * * * * * +0xe290 MDH2 17 24 3 0 MDH2_GR * * * * * * * * * +0xe291 MBU2 17 16 3 0 MBU2_SH * * * * * * * * * +0xe292 METN 17 48 5 0 METN_GH * * * * * * * * * +0xe293 MGVE 17 40 5 0 * * * * * * * * * * +0xe294 MBON 17 16 3 0 * * * * * * * * * * +0xe300 MGHO 17 16 3 0 * * * 1 * * * * * * +0xe310 MGH2 17 16 3 0 * * * * * * * * * * +0xe320 MGH3 17 16 3 0 * * * * * * * * * * +0xe400 MGO1 17 16 3 0 * * * * * * * * * * +0xe410 MGO2 17 16 3 0 * * * * * * * * * * +0xe420 MGO3 17 16 3 0 * * * * * * * * * * +0xe430 MGO4 17 16 3 0 * * * * * * * * * * +0xe440 MGO6 17 16 3 0 * * * * * * * * * * +0xe441 MGO7 17 16 3 0 * * * * * * * * * * +0xe442 MGO8 17 16 3 0 * * * * * * * * * * +0xe443 MGO9 17 16 3 0 * * * * * * * * * * +0xe444 MGO10 17 16 3 0 * * * * * * * * * * +0xe500 MLIZ 17 24 3 0 * * * * * * * * * * +0xe510 MLI2 17 24 3 0 * * * * * * * * * * +0xe520 MLI3 17 32 5 0 * * * * * * * * * * +0xe600 MMYC 17 16 3 0 * * * * * * * * * * +0xe610 MMY2 17 16 3 0 * * * * * * * * * * +0xe700 MNO1 17 20 3 0 * * * * * * * * * * +0xe710 MNO2 17 20 3 0 * * * * * * * * * * +0xe720 MNO3 17 24 3 0 * * * * * * * * * * +0xe800 MOR1 17 16 3 0 * * * * * * * * * * +0xe810 MOR2 17 16 3 0 * * * * * * * * * * +0xe820 MOR3 17 16 3 0 * * * * * * * * * * +0xe830 MOR4 17 16 3 0 * * * * * * * * * * +0xe840 MOR5 17 16 3 0 * * * * * * * * * * +0xe900 MSAL 17 16 3 0 * * * * * * * * * * +0xe910 MSA2 17 16 3 0 * * * * * * * * * * +0xea00 MSHR 17 24 3 0 * * * * * * * * * * +0xea10 MSH1 17 16 3 0 * * * 1 * * * * * * +0xea20 MSH2 17 24 3 0 * * * 1 * * * * * * +0xeb00 MSKT 17 16 3 0 * * * * * * * * * * +0xeb10 MSKA 17 16 3 0 * * * * * * * * * * +0xeb20 MSKB 17 24 3 0 * * * * * * * * * * +0xec00 MWIG 17 16 3 0 * * * * * * * * * * +0xec10 MWI2 17 16 3 0 * * * * * * * * * * +0xec20 MWI3 17 16 3 0 * * * * * * * * * * +0xed00 MYU1 17 16 3 0 * * * * * * * * * * +0xed10 MYU2 17 16 3 0 * * * * * * * * * * +0xed20 MYU3 17 16 3 0 * * * * * * * * * * +0xee00 MZO2 17 16 3 0 * * * * * * * * * * +0xee10 MZO3 17 16 3 0 * * * * * * * * * * +0xef10 MWWE 17 24 3 0 * * * * * * * * * * diff --git a/src/org/infinity/resource/cre/decoder/tables/avatars-eet.2da b/src/org/infinity/resource/cre/decoder/tables/avatars-eet.2da new file mode 100644 index 000000000..265437607 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/tables/avatars-eet.2da @@ -0,0 +1,519 @@ +2DA V1.0 +* + RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD +0x0000 SPRING 0 0 0 0 * * * * 1 0 * * * * +0x0001 SPFLAMES 0 0 0 7 * * * * 0 0 * * * * +0x0002 SPRDRASI 0 0 0 7 * * * * 0 0 * * * * +0x0003 SPFLAMES 0 0 0 7 SPFLAMEB * * * 0 0 * * * * +0x0004 SPRDRASI 0 0 0 7 SPGDRASI * * * 0 0 * * * * +0x0100 SPCHUNKS 0 0 0 0 * * SPSHADOW * 1 1 * * * * +0x0101 BGLRYU 0 0 3 7 * * * * * 0 * * * * +0x0102 SPCL236U 0 0 3 7 * * * * * 0 * * * * +0x0200 SPBLOOD 0 0 0 0 * * * * 0 0 0 * * * +0x0210 SPBLOOD 0 0 0 0 * * * * 0 0 1 * * * +0x0220 SPBLOOD 0 0 0 0 * * * * 0 0 2 * * * +0x0230 SPBLOOD 0 0 0 0 * * * * 0 0 3 * * * +0x0240 SPBLOOD 0 0 0 0 * * * * 0 0 4 * * * +0x0300 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * +0x0301 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * +0x0400 SKLH 0 0 0 0 * * SPSHADOW * 0 1 * * * * +0x0410 GLPHWRDH 0 0 0 1 * * SPSHADOW * 0 1 * * * * +0x0500 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * +0x0510 STNKCLDD 0 0 0 0 * * * * 1 0 0 * * * +0x0520 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * +0x0600 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * +0x0610 SPICESTM 0 0 0 1 * * * * 0 0 0 * * * +0x0700 GREASEH 0 0 0 0 * * * * 0 0 * * * * +0x0710 GREASED 0 0 0 0 * * * * 0 1 * * * * +0x0800 WEBENTH 0 0 0 0 * * * * 1 0 * * * * +0x0810 WEBENTD 0 0 0 0 * * * * 1 0 * * * * +0x0900 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * +0x0910 SPMETSWM 0 0 0 1 * * * * 0 1 0 * * * +0x0a00 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * +0x0a01 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * +0x0a02 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * +0x0a03 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * +0x0a04 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * +0x0a10 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * +0x0a11 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * +0x0a12 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * +0x0a13 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * +0x0a14 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * +0x0a23 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * +0x0a24 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * +0x0b00 SPCSPRA2 0 0 0 1 * * * * 0 0 * * * * +0x0b01 SPCCOLDL 0 0 0 1 * * * * 0 0 * * * * +0x0b02 SPPRISM2 0 0 0 1 * * * * 0 0 * * * * +0x0b03 SPCSPRA3 0 0 0 1 * * * * 0 0 * * * * +0x0b04 SPPRISM3 0 0 0 1 * * * * 0 0 * * * * +0x0c00 SPSTRMVA 0 0 0 1 * * * * 0 1 * * SPSTRMVB * +0x0c10 SPSTRMVA 0 0 0 1 * * * * 0 1 * * SPSTRMVB * +0x1000 MWYV 1 24 4 0 * * * * 0 * * * * * +0x1003 MWYV 1 24 4 0 MWYV_WH * * * 0 * * * * * +0x1004 MWYV 1 24 4 0 MWYV_AL * * * 0 * * * * * +0x1100 MTAN 1 32 5 0 * * * * 0 * 1 * * * +0x1101 MWDR 1 72 7 0 * * * * 0 * * * * * +0x1102 MTAN 1 32 5 0 MTAN_BL * * * 0 * 1 * * * +0x1103 MTAN 1 32 5 0 MTAN_GR * * * 0 * 1 * * * +0x1104 MTAN 1 32 5 0 MTAN_RD * * * 0 * 1 * * * +0x1105 MWDR 1 72 7 0 MWDR_GR * * * 0 * * * * * +0x1200 MDR1 2 72 13 0 * * * * 0 1 * * * * +0x1201 MDR2 2 93 13 0 * * * * 0 1 * * * * +0x1202 MDR3 2 72 13 0 * * * * 0 1 * * * * +0x1203 MDR1 2 72 13 0 MDR1_GR * * * 0 1 * * * * +0x1204 MDR1 2 72 13 0 MDR1_AQ * * * 0 1 * * * * +0x1205 MDR1 2 72 13 0 MDR1_BL * * * 0 1 * * * * +0x1206 MDR1 2 72 13 0 MDR1_BR * * * 0 1 * * * * +0x1207 MDR1 2 72 13 0 MDR1_MC * * * 0 1 * * * * +0x1208 MDR1 2 72 13 0 MDR1_PU * * * 0 1 * * * * +0x1300 MDEM 3 32 5 0 * * * * 0 1 * * * * +0x2000 MSIR 5 16 3 0 * * * * 1 * 1 * * BW +0x2100 UVOL 5 16 3 0 * * * * 0 * 1 * MS * +0x2200 MOGM 5 16 3 0 * * * * 1 * 1 1 S1 * +0x2300 MDKN 5 16 3 0 * * * * 0 * 1 1 * * +0x3000 MAKH 6 24 5 0 * * * * 0 * * * * * +0x3001 MNEO 6 60 5 0 * * * * 0 * * * * * +0x4000 SNOMC 7 16 3 0 * * * * 1 * * * * * +0x4001 SNONE 7 0 0 0 * * * * 0 * * * * * +0x4002 SNOMM 7 16 3 0 * * * * 1 * * * * * +0x4010 SNOWC 7 16 3 0 * * * * 1 * * * * * +0x4012 SNOWM 7 16 3 0 * * * * 1 * * * * * +0x4100 SSIMC 7 16 3 0 * * * * 1 * * * * * +0x4101 SSIMS 7 16 3 0 * * * * 1 * * * * * +0x4102 SSIMM 7 16 3 0 * * * * 1 * * * * * +0x4110 SSIWC 7 16 3 0 * * * * 1 * * * * * +0x4112 SSIWM 7 16 3 0 * * * * 1 * * * * * +0x4200 SHMCM 7 16 3 0 * * * * 1 * * * * * +0x4300 MSPLG1 7 32 5 0 * * * * 0 * * * * * +0x4400 LHMC 7 16 3 0 * * * * 1 * * * * * +0x4410 LHFC 7 16 3 0 * * * * 1 * * * * * +0x4500 LFAM 7 16 3 0 * * * * 1 * * * * * +0x4600 LDMF 7 16 3 0 * * * * 1 * * * * * +0x4700 LEMF 7 16 3 0 * * * * 1 * * * * * +0x4710 LEFF 7 16 3 0 * * * * 1 * * * * * +0x4800 LIMC 7 16 3 0 * * * * 1 * * * * * +0x5000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * +0x5001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * +0x5002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * +0x5003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH +0x5010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * +0x5011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * +0x5012 CDFB 8 16 3 0 * * CDFC * 1 1 1 * WQS * +0x5013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * +0x5100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * +0x5101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * +0x5102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * +0x5103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH +0x5110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * +0x5111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * +0x5112 CDFB 8 16 3 0 * * CDFF * 1 1 1 * WQS * +0x5113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * +0x5200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * +0x5201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * +0x5202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * +0x5210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * +0x5211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * +0x5212 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS * +0x5300 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * +0x5301 CEMT 8 16 3 0 * * CEMT * 1 1 0 * WQM * +0x5302 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * +0x5303 CIMT 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH +0x5310 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * +0x5311 CEFT 8 16 3 0 * * CEFT * 1 1 0 * WQM * +0x5312 CDFT 8 16 3 0 * * CDFT * 1 1 0 * WQS * +0x5313 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS * +0x6000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * +0x6001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * +0x6002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * +0x6003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH +0x6004 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS WQH +0x6005 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * +0x6010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * +0x6011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * +0x6012 CDFB 8 16 3 0 * * CDFC * 1 1 1 * WQS * +0x6013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * +0x6014 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS WQH +0x6015 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * +0x6100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * +0x6101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * +0x6102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * +0x6103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH +0x6104 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS WQH +0x6105 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * +0x6110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * +0x6111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * +0x6112 CDFB 8 16 3 0 * * CDFF * 1 1 1 * WQS * +0x6113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * +0x6114 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS WQH +0x6115 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * +0x6200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * +0x6201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * +0x6202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * +0x6204 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH +0x6205 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * +0x6210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * +0x6211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * +0x6212 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS * +0x6214 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS WQH +0x6215 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * +0x6300 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * +0x6301 CEMT 8 16 3 0 * * CEMT * 1 1 0 * WQM * +0x6302 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * +0x6303 CIMT 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH +0x6304 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS WQH +0x6305 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * +0x6310 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * +0x6311 CEFT 8 16 3 0 * * CEFT * 1 1 0 * WQM * +0x6312 CDFT 8 16 3 0 * * CDFT * 1 1 0 * WQS * +0x6313 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS * +0x6314 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS WQH +0x6315 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * +0x6400 UDRZ 9 16 3 0 * * * * 1 * 1 0 WPM * +0x6401 UELM 9 16 3 0 * * * * 0 * 0 0 WPM * +0x6402 CMNK 9 16 3 0 * * * * 1 * 0 * WPM * +0x6403 MSKL 9 16 3 0 * * * * 1 * 1 * WPM * +0x6404 USAR 9 16 3 0 * * * * 0 * 0 0 WPL * +0x6405 MDGU 9 16 3 0 * * * * 1 * 1 * WPM * +0x6406 MDGU 9 24 7 0 * * * * 1 * 1 * WPM * +0x6500 CHMM 8 16 3 0 * * CHMM * 1 1 1 * WQL * +0x6510 CHFM 8 16 3 0 * * CHFM * 1 1 1 * WQN * +0x6621 XHFF 9 16 3 0 * * * * 1 * 1 * WPM * +0x7000 MOGH 11 16 3 0 * * * * 1 * * * * * +0x7001 MOGN 11 16 3 0 * * * * 1 * * * * * +0x7100 MBAS 11 32 5 0 * * * * 0 * * * * * +0x7101 MBAS 11 32 5 0 MBAS_GR * * * 0 * * * * * +0x7200 MBER 11 16 3 0 MBER_BL * * * 0 * * * * * +0x7201 MBER 11 16 3 0 * * * * 0 * * * * * +0x7202 MBER 11 16 3 0 MBER_CA * * * 0 * * * * * +0x7203 MBER 11 16 3 0 MBER_PO * * * 0 * * * * * +0x7300 MEAE 10 32 5 0 * * * * 0 1 * * * * +0x7301 MEAS 10 16 3 0 * * * * 0 1 * * * * +0x7302 MEAE 10 32 5 0 MEAE_SH * * * 0 1 * * * * +0x7310 MFIE 10 16 3 4 * * * * 0 1 * * * * +0x7311 MFIS 10 16 3 4 * * * * 0 1 * * * * +0x7312 MFIE 10 16 3 4 MFIEG1B MFIEG2B * * 0 1 * * * * +0x7313 MFIS 10 16 3 4 MFISG1B MFISG2B * * 0 1 * * * * +0x7314 MFIE 10 16 3 4 MFIEG31 MFIEG3B * * 0 1 * * * * +0x7320 MAIR 10 32 5 0 * * * 1 0 1 * * * * +0x7321 MAIS 10 16 3 0 * * * 1 0 1 * * * * +0x7400 MDOG 11 16 3 0 MDOG_WI * * * 0 * * * * * +0x7401 MDOG 11 16 3 0 MDOG_WA * * * 0 * * * * * +0x7402 MDOG 11 16 3 0 MDOG_MO * * * 0 * * * * * +0x7500 MDOP 11 16 3 0 * * * * 0 * * * * * +0x7501 MDOP 11 16 3 0 MDOP_GR * * * 0 * * * * * +0x7600 METT 11 16 3 0 * * * * 0 * * * * * +0x7601 MGHL 11 16 3 0 MGHL_MA * * * 0 0 * * * * +0x7602 MSPI 11 16 3 0 MSPI_MY * * * 0 * * * * * +0x7603 MDOG 11 16 3 0 MDOG_GR * * * 0 * * * * * +0x7604 MSPI 11 16 3 0 MSPI_WA * * * 0 * * * * * +0x7700 MGHL 11 16 3 0 * * * * 0 * * * * * +0x7701 MGHL 11 16 3 0 MGHL_RE * * * 0 * * * * * +0x7702 MGHL 11 16 3 0 MGHL_GA * * * 0 * * * * * +0x7703 MSHD 10 16 3 0 * * * 1 0 1 * * * * +0x7800 MGIB 11 16 3 0 * * * * 0 * * * * * +0x7801 MGIB 11 16 3 0 MGIB_BR * * * 0 * * * * * +0x7802 MSLI 11 24 4 0 MSLI_BL * * 0 0 * * * * * +0x7900 MSLI 11 24 4 0 MSLI_GR * * 1 0 * * * * * +0x7901 MSLI 11 24 4 0 MSLI_OL * * 1 0 * * * * * +0x7902 MSLI 11 24 4 0 MSLI_MU * * 1 0 * * * * * +0x7903 MSLI 11 24 4 0 MSLI_OC * * 1 0 * * * * * +0x7904 MSLI 11 24 4 0 * * * 1 0 * * * * * +0x7a00 MSPI 11 16 3 0 MSPI_GI * * * 0 * * * * * +0x7a01 MSPI 11 16 3 0 MSPI_HU * * * 0 * * * * * +0x7a02 MSPI 11 16 3 0 MSPI_PH * * * 0 * * * * * +0x7a03 MSPI 11 16 3 0 MSPI_SW * * * 0 * * * * * +0x7a04 MSPI 11 16 3 0 MSPI_WR * * * 0 * * * * * +0x7b00 MWLF 11 16 3 0 * * * * 0 * * * * * +0x7b01 MWLF 11 16 3 0 MWLF_WO * * * 0 * * * * * +0x7b02 MWLF 11 16 3 0 MWLF_DI * * * 0 * * * * * +0x7b03 MWLF 11 16 3 0 MWLF_WI * * * 0 * * * * * +0x7b04 MWLF 11 16 3 0 MWLF_VA * * * 0 * * * * * +0x7b05 MWLF 11 16 3 0 MWLF_DR * * * 0 * * * * * +0x7b06 MWLS 11 16 3 0 * * * * 0 * * * * * +0x7c00 MXVT 11 16 3 0 * * * * 1 * * * * * +0x7c01 MTAS 11 16 3 0 * * * * 0 * * * * * +0x7d00 MZOM 11 16 3 0 * * * * 1 * * * * * +0x7d01 NSLF 11 16 3 0 * * * * 1 0 * * * * +0x7d02 ACHB 11 12 3 0 * * * * 0 0 * * * * +0x7d03 ACHW 11 12 3 0 * * * * 0 0 * * * * +0x7d04 NPRF 11 16 3 0 * * * * 1 0 * * * * +0x7d05 NNMF 11 16 3 0 * * * * 1 0 * * * * +0x7d06 NNWF 11 16 3 0 * * * * 1 0 * * * * +0x7d07 NSMF 11 16 3 0 * * * * 1 0 * * * * +0x7d08 NSWF 11 16 3 0 * * * * 1 0 * * * * +0x7e00 MWER 11 16 3 0 * * * * 0 * * * * * +0x7e01 MGWE 11 16 3 0 * * * * 0 * * * * * +0x7f00 MTRO 10 16 3 0 * * * * 0 1 * * * * +0x7f01 MMIN 10 16 3 0 * * * * 0 1 * * * * +0x7f02 MBEH 10 32 5 0 * * * * 0 1 * * * * +0x7f03 MIMP 10 16 3 0 * * * * 0 1 * * * * +0x7f04 MIGO 10 32 5 0 * * * * 0 1 * * * * +0x7f05 MDJI 10 16 3 0 * * * * 0 1 * * * * +0x7f06 MDJL 10 16 3 0 * * * * 0 1 * * * * +0x7f07 MGLC 10 16 3 0 * * * * 0 1 * * * * +0x7f08 MOTY 10 24 5 0 * * * * 0 1 * * * * +0x7f09 MSAH 10 16 3 0 * * * * 0 1 * * * * +0x7f0a MGCP 10 16 3 0 * * * * 0 1 * * * * +0x7f0b MGCL 10 16 3 0 * * * * 0 1 * * * * +0x7f0c MKUO 10 16 3 0 * * * * 0 1 * * * * +0x7f0d MLIC 10 16 3 0 * * * * 0 1 * * * * +0x7f0e MDLI 10 16 3 0 * * * * 0 1 * * * * +0x7f0f MTRS 10 16 3 0 * * * * 0 1 * * * * +0x7f10 MRAK 10 16 3 0 * * * * 0 1 * * * * +0x7f11 MUMB 10 16 3 0 * * * * 0 1 * * * * +0x7f12 MVAM 10 16 3 0 * * * * 0 1 * * * * +0x7f13 MSNK 10 16 3 0 * * * * 0 1 * * * * +0x7f14 MGIT 10 16 3 0 * * * * 0 1 * * * * +0x7f15 MBES 10 16 3 0 * * * * 0 1 * * * * +0x7f16 AMOO 10 32 5 0 * * * * 0 1 * * * * +0x7f17 ARAB 10 12 3 0 * * * * 0 1 * * * * +0x7f18 ADER 10 16 3 0 * * * * 0 1 * * * * +0x7f19 MDSW 10 16 3 0 * * * * 0 1 * * * * +0x7f20 AGRO 10 12 3 0 * * * * 0 1 * * * * +0x7f21 APHE 10 12 3 0 * * * * 0 1 * * * * +0x7f22 MVAF 10 16 3 0 * * * * 0 1 * * * * +0x7f23 MSAT 10 16 3 0 * * * * 0 1 * * * * +0x7f24 NPIR 10 16 3 0 * * * * 0 1 * * * * +0x7f27 MDRO 10 16 3 0 * * * * 0 1 * * * * +0x7f28 MKUL 10 16 3 0 * * * * 0 1 * * * * +0x7f29 MFDR 10 16 3 0 * * * * 0 1 * * * * +0x7f2a NSAI 10 16 3 0 * * * * 0 1 * * * * +0x7f2b MMAX 10 16 3 0 * * * * 0 0 * * * * +0x7f2c NSOL 10 16 3 0 * * * * 0 1 * * * * +0x7f2d MWFM 10 16 3 0 * * * * 0 1 * * * * +0x7f2e MRAV 10 32 5 0 * * * * 0 1 * * * * +0x7f2f MSPS 10 16 3 0 * * * * 0 1 * * * * +0x7f30 NBOH 10 16 3 0 * * * * 0 1 * * * * +0x7f31 NELL 10 16 3 0 * * * * 0 1 * * * * +0x7f32 MSLY 10 16 3 0 * * * * 0 1 * * * * +0x7f33 MKUR 10 16 3 0 * * * * 0 0 * * * * +0x7f34 MDOC 10 16 3 0 * * * * 0 0 * * * * +0x7f35 MMIS 10 16 3 0 * * * 1 0 1 * * * * +0x7f36 NSHD 10 16 3 0 * * * * 0 1 * * * * +0x7f37 NIRE 10 16 3 0 * * * * 0 1 * * * * +0x7f38 MEYE 10 16 3 0 * * * * 0 0 * * * * +0x7f39 MMST 10 16 3 1 * * * 0 0 0 * * * * +0x7f3a NIRO 10 16 3 0 * * * * 0 1 * * * * +0x7f3b MSOG 10 16 3 4 * * * 0 0 1 * * * * +0x7f3c MASG 10 16 3 4 * * * 0 0 1 * * * * +0x7f3d MMEL 10 24 3 0 * * * * 0 0 * * * * +0x7f3e MFIG 10 32 5 0 * * * * 0 1 * * * * +0x7f3f MFIG 10 32 5 0 MFIGG1B * * * 0 1 * * * * +0x7f40 MGLM 10 16 3 0 * * * * 0 1 * * * * +0x7f41 MDJL 10 16 3 0 MDJL_E1 MDJL_E2 * * 0 1 * * * * +0x7f42 NIRO 10 16 3 0 NIRO_RD * * * 0 1 * * * * +0x7f43 MOTY 10 16 3 0 MOTY_Y1 MOTY_Y2 * * 0 1 * * * * +0x7f44 NELW 10 16 3 0 * * * * 0 0 * * * * +0x7f45 MSAI 10 16 3 0 * * * 1 0 0 * * * * +0x7f46 MBEG 10 16 3 0 * * * * 0 0 * * * * +0x7f47 MFI2 10 32 5 0 * * * * 0 0 * * * * +0x7f48 MSOF 10 16 3 0 * * * 0 0 1 * * * * +0x7f49 MDMF 10 16 3 0 * * * 0 0 1 * * * * +0x7f4a MDAS 10 16 3 7 * * MDAG 0 0 1 * * * * +0x7f4b MDAF 10 16 3 0 * * * 0 0 1 * * * * +0x7f4c MPLN 10 16 3 7 * * MPLG 0 0 1 * * * * +0x7f4d MPLF 10 16 3 0 * * * 0 0 1 * * * * +0x7f4e MDVM 10 16 3 7 * * MDVG 0 0 1 * * * * +0x7f4f MDVF 10 16 3 0 * * * 0 0 1 * * * * +0x7f50 MMST 10 16 3 1 MMST_NI * * 0 0 0 * * * * +0x7f51 MMST 10 16 3 1 MMST_HA * * 0 0 0 * * * * +0x7f52 NSAI 10 16 3 0 NSAI_G1 NSAI_G2 * * 0 1 * * * * +0x7f53 NSAI 10 16 3 0 NSAI_R1 NSAI_R2 * * 0 1 * * * * +0x7f54 NSAI 10 16 3 0 NSAI_D1 NSAI_D2 * * 0 1 * * * * +0x7f55 NSOL 10 16 3 0 NSOL_C1 NSOL_C2 * * 0 1 * * * * +0x7f56 MWIL 10 16 3 7 * * * * 0 0 * * * * +0x7f57 MWIS 10 12 3 7 * * * * 0 0 * * * * +0x7f58 MABB 10 24 3 0 * * * * 0 0 * * * * +0x7f59 MABG 10 24 3 0 * * * * 0 0 * * * * +0x7f5a MABR 10 24 3 0 * * * * 0 0 * * * * +0x7f5b MABR 10 24 3 0 MABR_BL * * * 0 0 * * * * +0x7f5c MHAG 10 16 3 0 * * * * 0 0 * * * * +0x7f5d MHAG 10 16 3 0 MHAG_GR * * * 0 0 * * * * +0x7f5e MHAG 10 16 3 0 MHAG_SE * * * 0 0 * * * * +0x7f5f MHAN 10 16 3 0 * * * * 0 0 * * * * +0x7f60 MSNK 10 16 3 0 MSNK_W1 MSNK_W2 * * 0 1 * * * * +0x7f61 MDRJ 10 48 9 0 * * * * 0 0 * * * * +0x7f62 MDRJ 10 48 9 0 MDRJ_GR * * * 0 0 * * * * +0x8000 MGNL 4 16 3 0 * * * * * * * * S1 HB +0x8100 MHOB 4 16 3 0 * * * * * * * * S1 BW +0x8200 MKOB 4 16 3 0 * * * * * * * * SS BW +0x9000 MOGR 12 16 3 0 * * * * 1 * * * * * +0xa000 MWYV 13 16 3 0 * * * * 0 * * * * * +0xa100 MCAR 13 16 3 0 * * * * 0 * * * * * +0xa200 MWYV 13 24 4 0 MWYV_WS * * * 0 * * * * * +0xa201 MCEN 13 16 3 0 * * * * 0 * * * * * +0xa202 MTUN 13 16 3 0 * * * * 0 * * * * * +0xb000 ACOW 14 32 5 0 * * * * 0 * * * * * +0xb100 AHRS 14 32 5 0 * * * * 0 * * * * * +0xb200 NBEGL 14 16 3 0 * * * * 1 * * * * * +0xb210 NPROL 14 16 3 0 * * * * 1 * * * * * +0xb300 NBOYL 14 16 3 0 * * * * 1 * * * * * +0xb310 NGRLL 14 16 3 0 * * * * 1 * * * * * +0xb400 NFAML 14 16 3 0 * * * * 1 * * * * * +0xb410 NFAWL 14 16 3 0 * * * * 1 * * * * * +0xb500 NSIML 14 16 3 0 * * * * 1 * * * * * +0xb510 NSIWL 14 16 3 0 * * * * 1 * * * * * +0xb600 NNOML 14 16 3 0 * * * * 1 * * * * * +0xb610 NNOWL 14 16 3 0 * * * * 1 * * * * * +0xb700 NSLVL 14 16 3 0 * * * * 1 * * * * * +0xc000 ABAT 15 12 3 0 * * * * 0 * * * * * +0xc100 ACAT 15 12 3 0 * * * * 0 * * * * * +0xc200 ACHK 15 12 3 0 * * * * 0 * * * * * +0xc300 ARAT 15 12 3 0 * * * * 0 * * * * * +0xc400 ASQU 15 12 3 0 * * * * 0 * * * * * +0xc500 ABAT 15 12 3 0 * * * * 0 * * * * * +0xc600 NBEGH 15 16 3 0 * * * * 1 * * * * * +0xc610 NPROH 15 16 3 0 * * * * 1 * * * * * +0xc700 NBOYH 15 16 3 0 * * * * 1 * * * * * +0xc710 NGRLH 15 16 3 0 * * * * 1 * * * * * +0xc800 NFAMH 15 16 3 0 * * * * 1 * * * * * +0xc810 NFAWH 15 16 3 0 * * * * 1 * * * * * +0xc900 NSIMH 15 16 3 0 * * * * 1 * * * * * +0xc910 NSIWH 15 16 3 0 * * * * 1 * * * * * +0xca00 NNOMH 15 16 3 0 * * * * 1 * * * * * +0xca10 NNOWH 15 16 3 0 * * * * 1 * * * * * +0xcb00 NSLVH 15 16 3 0 * * * * 1 * * * * * +0xcc00 MKG1 15 16 3 0 * * * * 0 * * * * * +0xcc01 MKG2 15 16 3 0 * * * * 0 * * * * * +0xcc02 MKG3 15 16 3 0 * * * * 0 * * * * * +0xcc04 ARAT 15 0 1 0 * * * * 0 * * * * * +0xd000 AEAGG1 16 0 3 0 * * * * 0 * * * * * +0xd100 AGULG1 16 0 3 0 * * * * 0 * * * * * +0xd200 AVULG1 16 0 3 0 * * * * 0 * * * * * +0xd300 ABIRG1 16 0 3 0 * * * * 0 * * * * * +0xd400 ABIRG1 16 0 3 0 * * * * 0 * * * * * +0xe000 MCYC 17 48 5 0 * * * * * * * * * * +0xe010 METN 17 48 5 0 * * * * * * * * * * +0xe020 MTAN 17 32 5 0 * * * * * * * * * * +0xe040 MHIS 17 16 3 0 * * * * * * * * * * +0xe050 MLER 17 16 3 0 * * * * * * * * * * +0xe060 MLIC 17 16 3 0 * * * * * * * * * * +0xe070 MMIN 17 24 3 0 * * * * * * * * * * +0xe080 MMUM 17 16 3 0 * * * * * * * * * * +0xe090 MTAN 17 16 3 0 * * * * * * * * * * +0xe0a0 MTIC 17 20 3 0 * * * * * * * * * * +0xe0b0 MTRO 17 16 3 0 * * * * * * * * * * +0xe0c0 MTSN 17 24 3 0 * * * * * * * * * * +0xe0d0 MUMB 17 24 3 0 * * * * * * * * * * +0xe0e0 MCOR 17 24 3 0 * * * * * * * * * * +0xe0f0 MGIC 17 32 5 0 * * * * * * * * * * +0xe0f1 MGLA 17 24 3 0 * * * * * * * * * * +0xe0f2 MWAV 17 16 3 0 * * * 1 * * * * * * +0xe200 MBET 17 24 3 0 * * * * * * * * * * +0xe210 MBFI 17 12 3 0 * * * * * * * * * * +0xe220 MBBM 17 16 3 0 * * * * * * * * * * +0xe230 MBRH 17 64 7 0 * * * * * * * * * * +0xe240 MANI 17 24 3 0 * * * * * * * * * * +0xe241 MAN2 17 24 3 0 * * * * * * * * * * +0xe242 MAN3 17 24 3 0 * * * * * * * * * * +0xe243 MARU 17 16 3 0 * * * * * * * * * * +0xe244 MBA1 17 16 3 0 * * * * * * * * * * +0xe245 MBA2 17 16 3 0 * * * * * * * * * * +0xe246 MBA3 17 16 3 0 * * * * * * * * * * +0xe247 MBA4 17 16 3 0 * * * * * * * * * * +0xe248 MBA5 17 16 3 0 * * * * * * * * * * +0xe249 MBA6 17 16 3 0 * * * * * * * * * * +0xe24a MBAI 17 16 3 0 * * * * * * * * * * +0xe24b MELE 17 36 3 0 * * * * * * * * * * +0xe24c MELF 17 36 3 0 * * * * * * * * * * +0xe24d MELW 17 36 3 0 * * * * * * * * * * +0xe24e MGFI 17 48 3 0 * * * * * * * * * * +0xe24f MGFR 17 48 5 0 * * * * * * * * * * +0xe250 MGIR 17 24 3 0 * * * * * * * * * * +0xe251 MGVE 17 32 5 0 * * * * * * * * * * +0xe252 MHAR 17 16 3 0 * * * * * * * * * * +0xe253 MREM 17 48 5 0 * * * * * * * * * * +0xe254 MSCR 17 24 3 0 * * * * * * * * * * +0xe255 MSEE 17 16 3 0 * * * * * * * * * * +0xe256 MBE1 17 32 4 0 * * * * * * * * * * +0xe257 MBE2 17 16 3 0 * * * * * * * * * * +0xe258 MBE2 17 16 3 0 MBE2_HE * * * * * * * * * +0xe259 MSVI 17 16 3 0 * * * * * * * * * * +0xe25a MSV2 17 16 3 0 * * * * * * * * * * +0xe25b MUM2 17 24 3 0 * * * * * * * * * * +0xe25c MTA2 17 16 3 0 * * * * * * * * * * +0xe25d MYET 17 24 3 0 * * * * * * * * * * +0xe25e MWI4 17 16 3 0 * * * * * * * * * * +0xe25f MDRD 17 16 3 0 * * * * * * * * * * +0xe260 MCRD 17 24 3 0 * * * * * * * * * * +0xe261 MSAH 17 24 3 0 * * * * * * * * * * +0xe262 MSAT 17 24 3 0 * * * * * * * * * * +0xe263 MSV3 17 16 3 0 * * * * * * * * * * +0xe264 MSV4 17 16 3 0 * * * * * * * * * * +0xe265 MBOA 17 24 3 0 * * * * * * * * * * +0xe266 MBU2 17 16 3 0 * * * * * * * * * * +0xe267 MBUG 17 16 3 0 * * * * * * * * * * +0xe26a MDH2 17 24 3 0 * * * * * * * * * * +0xe26b MDTR 17 24 3 0 * * * * * * * * * * +0xe26d MFE2 17 24 3 0 * * * * * * * * * * +0xe26e MFEY 17 24 3 0 * * * * * * * * * * +0xe26f MGFO 17 24 4 0 * * * * * * * * * * +0xe270 MGO5 17 16 3 0 * * * * * * * * * * +0xe271 MGOC 17 16 3 0 * * * * * * * * * * +0xe272 MGWO 17 24 3 0 * * * * * * * * * * +0xe273 MGW2 17 24 3 0 * * * * * * * * * * +0xe274 MHOH 17 24 3 0 * * * * * * * * * * +0xe276 MLEM 17 16 3 0 * * * * * * * * * * +0xe279 MNOS 17 24 3 0 * * * * * * * * * * +0xe27d MWEB 17 16 3 0 * * * * * * * * * * +0xe27e MWRA 17 16 3 0 * * * * * * * * * * +0xe27f MBOA 17 24 3 0 MBOA_BR * * * * * * * * * +0xe280 MWOR 17 24 3 0 * * * * * * * * * * +0xe281 MYUH 17 16 3 0 * * * * * * * * * * +0xe282 MDRF 17 32 4 0 * * * * * * * * * * +0xe283 MDRM 17 32 4 0 * * * * * * * * * * +0xe288 MGWO 17 24 3 0 MGWO_DK * * * * * * * * * +0xe289 MGW2 17 24 3 0 MGW2_DK * * * * * * * * * +0xe28a MTRO 17 16 3 0 MTRO_DK * * * * * * * * * +0xe28b MABW 17 24 3 0 * * * * * * * * * * +0xe28c MWD2 17 36 5 0 * * * * * * * * * * +0xe28d MWD2 17 36 5 0 MWD2_SI * * * * * * * * * +0xe28e MWD2 17 36 5 0 MWD2_GR * * * * * * * * * +0xe28f MDRD 17 16 3 0 MDRD_RE * * * * * * * * * +0xe290 MDH2 17 24 3 0 MDH2_GR * * * * * * * * * +0xe291 MBU2 17 16 3 0 MBU2_SH * * * * * * * * * +0xe292 METN 17 48 5 0 METN_GH * * * * * * * * * +0xe293 MGHI 17 40 5 0 * * * * * * * * * * +0xe294 MBON 17 16 3 0 * * * * * * * * * * +0xe300 MGHO 17 16 3 0 * * * 1 * * * * * * +0xe310 MGH2 17 16 3 0 * * * * * * * * * * +0xe320 MGH3 17 16 3 0 * * * * * * * * * * +0xe400 MGO1 17 16 3 0 * * * * * * * * * * +0xe410 MGO2 17 16 3 0 * * * * * * * * * * +0xe420 MGO3 17 16 3 0 * * * * * * * * * * +0xe430 MGO4 17 16 3 0 * * * * * * * * * * +0xe440 MGO6 17 16 3 0 * * * * * * * * * * +0xe441 MGO7 17 16 3 0 * * * * * * * * * * +0xe442 MGO8 17 16 3 0 * * * * * * * * * * +0xe443 MGO9 17 16 3 0 * * * * * * * * * * +0xe444 MGO10 17 16 3 0 * * * * * * * * * * +0xe500 MLIZ 17 24 3 0 * * * * * * * * * * +0xe510 MLI2 17 24 3 0 * * * * * * * * * * +0xe520 MLI3 17 32 5 0 * * * * * * * * * * +0xe600 MMYC 17 16 3 0 * * * * * * * * * * +0xe610 MMY2 17 16 3 0 * * * * * * * * * * +0xe700 MNO1 17 20 3 0 * * * * * * * * * * +0xe710 MNO2 17 20 3 0 * * * * * * * * * * +0xe720 MNO3 17 24 3 0 * * * * * * * * * * +0xe800 MOR1 17 16 3 0 * * * * * * * * * * +0xe810 MOR2 17 16 3 0 * * * * * * * * * * +0xe820 MOR3 17 16 3 0 * * * * * * * * * * +0xe830 MOR4 17 16 3 0 * * * * * * * * * * +0xe840 MOR5 17 16 3 0 * * * * * * * * * * +0xe900 MSAL 17 16 3 0 * * * * * * * * * * +0xe910 MSA2 17 16 3 0 * * * * * * * * * * +0xea00 MSHR 17 24 3 0 * * * * * * * * * * +0xea10 MSH1 17 16 3 0 * * * 1 * * * * * * +0xea20 MSH2 17 24 3 0 * * * 1 * * * * * * +0xeb00 MSKT 17 16 3 0 * * * * * * * * * * +0xeb10 MSKA 17 16 3 0 * * * * * * * * * * +0xeb20 MSKB 17 24 3 0 * * * * * * * * * * +0xec00 MWIG 17 16 3 0 * * * * * * * * * * +0xec10 MWI2 17 16 3 0 * * * * * * * * * * +0xec20 MWI3 17 16 3 0 * * * * * * * * * * +0xed00 MYU1 17 16 3 0 * * * * * * * * * * +0xed10 MYU2 17 16 3 0 * * * * * * * * * * +0xed20 MYU3 17 16 3 0 * * * * * * * * * * +0xee00 MZO2 17 16 3 0 * * * * * * * * * * +0xee10 MZO3 17 16 3 0 * * * * * * * * * * +0xef10 MWWE 17 24 3 0 * * * * * * * * * * diff --git a/src/org/infinity/resource/cre/decoder/tables/avatars-iwd.2da b/src/org/infinity/resource/cre/decoder/tables/avatars-iwd.2da new file mode 100644 index 000000000..74f59561f --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/tables/avatars-iwd.2da @@ -0,0 +1,273 @@ +2DA V1.0 +* + RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD +0x0000 SPRING 0 0 0 0 * * * * 1 0 * * * * +0x0100 SPCHUNKS 0 0 0 0 * * SPSHADOW * 1 1 * * * * +0x0200 SPBLOOD 0 0 0 0 * * * * 0 0 0 * * * +0x0210 SPBLOOD 0 0 0 0 * * * * 0 0 1 * * * +0x0220 SPBLOOD 0 0 0 0 * * * * 0 0 2 * * * +0x0230 SPBLOOD 0 0 0 0 * * * * 0 0 3 * * * +0x0240 SPBLOOD 0 0 0 0 * * * * 0 0 4 * * * +0x0300 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * +0x0400 SKLH 0 0 0 0 * * SPSHADOW * 0 1 * * * * +0x0410 GLPHWRDH 0 0 0 1 * * SPSHADOW * 0 1 * * * * +0x0500 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * +0x0510 STNKCLDD 0 0 0 0 * * * * 1 0 0 * * * +0x0600 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * +0x0700 GREASEH 0 0 0 0 * * * * 0 0 * * * * +0x0710 GREASED 0 0 0 0 * * * * 0 1 * * * * +0x0800 WEBENTH 0 0 0 0 * * * * 1 0 * * * * +0x0810 WEBENTD 0 0 0 0 * * * * 1 0 * * * * +0x0900 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * +0x1000 MWYV 1 24 5 0 * * * * 0 * * * * * +0x1001 MWYV 1 24 5 0 * * * * 0 * * * * * +0x2000 MSIR 5 16 3 0 * * * * 1 * 1 * * BW +0x2100 UVOL 5 16 3 0 * * * * 0 * 1 * MS * +0x2200 MOGM 5 24 5 0 * * * * 1 * 1 1 S1 * +0x2300 MDKN 5 24 5 0 * * * * 0 * 1 1 * * +0x3000 MAKH 6 24 5 0 * * * * 0 * * * * * +0x4000 SNOMC 7 16 3 0 * * * * 1 * * * * * +0x4002 SNOMM 7 16 3 0 * * * * 1 * * * * * +0x4010 SNOWC 7 16 3 0 * * * * 1 * * * * * +0x4012 SNOWM 7 16 3 0 * * * * 1 * * * * * +0x4100 SSIMC 7 16 3 0 * * * * 1 * * * * * +0x4101 SSIMS 7 16 3 0 * * * * 1 * * * * * +0x4102 SSIMM 7 16 3 0 * * * * 1 * * * * * +0x4110 SSIWC 7 16 3 0 * * * * 1 * * * * * +0x4112 SSIWM 7 16 3 0 * * * * 1 * * * * * +0x4200 SHMCM 7 16 3 0 * * * * 1 * * * * * +0x4300 MSPLG1 7 32 5 0 * * * * 0 * * * * * +0x4400 LHMC 7 20 3 0 * * * * 1 * * * * * +0x4410 LHFC 7 20 3 0 * * * * 1 * * * * * +0x4500 LFAM 7 20 3 0 * * * * 1 * * * * * +0x4600 LDMF 7 20 3 0 * * * * 1 * * * * * +0x4700 LEMF 7 20 3 0 * * * * 1 * * * * * +0x4710 LEFF 7 20 3 0 * * * * 1 * * * * * +0x4800 LIMC 7 20 3 0 * * * * 1 * * * * * +0x5000 CHMC 9 16 3 0 * * * * 1 0 1 * WPL * +0x5001 CEMC 9 16 3 0 * * * * 1 0 1 * WPM * +0x5002 CDMC 9 16 3 0 * * * * 1 0 1 * WPS * +0x5003 CIMC 9 16 3 0 * * * * 1 0 1 * WPS * +0x5010 CHFC 9 16 3 0 * * * * 1 0 1 * WPM * +0x5011 CEFC 9 16 3 0 * * * * 1 0 1 * WPM * +0x5012 CDMC 9 16 3 0 * * * * 1 0 1 * WPS * +0x5013 CIFC 9 16 3 0 * * * * 1 0 1 * WPS * +0x5100 CHMF 9 16 3 0 * * * * 1 0 1 * WPL * +0x5101 CEMF 9 16 3 0 * * * * 1 0 1 * WPM * +0x5102 CDMF 9 16 3 0 * * * * 1 0 1 * WPS * +0x5103 CIMF 9 16 3 0 * * * * 1 0 1 * WPS * +0x5110 CHFF 9 16 3 0 * * * * 1 0 1 * WPM * +0x5111 CEFF 9 16 3 0 * * * * 1 0 1 * WPM * +0x5112 CDMF 9 16 3 0 * * * * 1 0 1 * WPS * +0x5113 CIFF 9 16 3 0 * * * * 1 0 1 * WPS * +0x5200 CHMW 9 16 3 0 * * * * 1 0 1 * WPL * +0x5201 CEMW 9 16 3 0 * * * * 1 0 1 * WPM * +0x5202 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * +0x5203 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * +0x5210 CHFW 9 16 3 0 * * * * 1 0 1 * WPM * +0x5211 CEFW 9 16 3 0 * * * * 1 0 1 * WPM * +0x5212 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * +0x5213 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * +0x5300 CHMT 9 16 3 0 * * * * 1 0 0 * WPL * +0x5301 CEMT 9 16 3 0 * * * * 1 0 0 * WPM * +0x5302 CDMT 9 16 3 0 * * * * 1 0 0 * WPS * +0x5303 CIMT 9 16 3 0 * * * * 1 0 0 * WPS * +0x5310 CHFT 9 16 3 0 * * * * 1 0 0 * WPM * +0x5311 CEFT 9 16 3 0 * * * * 1 0 0 * WPM * +0x5312 CDMT 9 16 3 0 * * * * 1 0 0 * WPS * +0x5313 CIFT 9 16 3 0 * * * * 1 0 0 * WPS * +0x6000 CHMC 9 16 3 0 * * * * 1 0 1 * WPL * +0x6001 CEMC 9 16 3 0 * * * * 1 0 1 * WPM * +0x6002 CDMC 9 16 3 0 * * * * 1 0 1 * WPS * +0x6003 CIMC 9 16 3 0 * * * * 1 0 1 * WPS * +0x6010 CHFC 9 16 3 0 * * * * 1 0 1 * WPM * +0x6011 CEFC 9 16 3 0 * * * * 1 0 1 * WPM * +0x6012 CIFC 9 16 3 0 * * * * 1 0 1 * WPS * +0x6013 CIFC 9 16 3 0 * * * * 1 0 1 * WPS * +0x6100 CHMF 9 16 3 0 * * * * 1 0 1 * WPL * +0x6101 CEMF 9 16 3 0 * * * * 1 0 1 * WPM * +0x6102 CDMF 9 16 3 0 * * * * 1 0 1 * WPS * +0x6103 CIMF 9 16 3 0 * * * * 1 0 1 * WPS * +0x6110 CHFF 9 16 3 0 * * * * 1 0 1 * WPM * +0x6111 CEFF 9 16 3 0 * * * * 1 0 1 * WPM * +0x6112 CIFF 9 16 3 0 * * * * 1 0 1 * WPS * +0x6113 CIFF 9 16 3 0 * * * * 1 0 1 * WPS * +0x6200 CHMW 9 16 3 0 * * * * 1 0 1 * WPL * +0x6201 CEMW 9 16 3 0 * * * * 1 0 1 * WPM * +0x6202 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * +0x6203 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * +0x6210 CHFW 9 16 3 0 * * * * 1 0 1 * WPM * +0x6211 CEFW 9 16 3 0 * * * * 1 0 1 * WPM * +0x6212 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * +0x6213 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * +0x6300 CHMT 9 16 3 0 * * * * 1 0 0 * WPL * +0x6301 CEMT 9 16 3 0 * * * * 1 0 0 * WPM * +0x6302 CDMT 9 16 3 0 * * * * 1 0 0 * WPS * +0x6303 CIMT 9 16 3 0 * * * * 1 0 0 * WPS * +0x6310 CHFT 9 16 3 0 * * * * 1 0 0 * WPM * +0x6311 CEFT 9 16 3 0 * * * * 1 0 0 * WPM * +0x6312 CIFT 9 16 3 0 * * * * 1 0 0 * WPS * +0x6313 CIFT 9 16 3 0 * * * * 1 0 0 * WPS * +0x6400 UDRZ 9 16 3 0 * * * * 1 * 1 0 WPM * +0x6401 CTES 9 16 3 0 * * * * 1 * 0 0 WPM * +0x6402 CMNK 9 16 3 0 * * * * 1 * 0 * WPM * +0x6403 MSKL 9 16 3 0 * * * * 1 * 1 * WPM * +0x6404 USAR 9 16 3 0 * * * * 0 * 0 0 WPL * +0x6405 MDGU 9 16 3 0 * * * * 1 * 1 * WPM * +0x7000 MOGH 11 16 3 0 * * * * 1 * * * * * +0x7001 MOGN 11 16 3 0 * * * * 1 * * * * * +0x7100 MBAS 11 32 5 0 * * * * 0 * * * * * +0x7101 MBAS 11 32 5 0 MBAS_GR * * * 0 * * * * * +0x7200 MBER 11 24 5 0 MBER_BL * * * 0 * * * * * +0x7201 MBER 11 24 5 0 * * * * 0 * * * * * +0x7202 MBER 11 24 5 0 MBER_CA * * * 0 * * * * * +0x7203 MBER 11 24 5 0 MBER_PO * * * 0 * * * * * +0x7300 MEAE 10 32 5 0 * * * * 0 1 * * * * +0x7302 MEAE 10 32 5 0 MEAE_SH * * * 0 1 * * * * +0x7400 MDOG 11 16 3 0 MDOG_WI * * * 0 * * * * * +0x7401 MDOG 11 16 3 0 MDOG_WA * * * 0 * * * * * +0x7402 MDOG 11 16 3 0 MDOG_MO * * * 0 * * * * * +0x7500 MDOP 11 16 3 0 * * * * 0 * * * * * +0x7501 MDOP 11 16 3 0 MDOP_GR * * * 0 * * * * * +0x7600 METT 11 16 3 0 * * * * 0 * * * * * +0x7701 MGHL 11 16 3 0 MGHL_RE * * * 0 * * * * * +0x7702 MGHL 11 16 3 0 MGHL_GA * * * 0 * * * * * +0x7800 MGIB 11 16 3 0 * * * * 0 * * * * * +0x7900 MSLI 11 24 4 0 MSLI_GR * * 1 0 * * * * * +0x7901 MSLI 11 24 4 0 MSLI_OL * * 1 0 * * * * * +0x7902 MSLI 11 24 4 0 MSLI_MU * * 1 0 * * * * * +0x7903 MSLI 11 24 4 0 MSLI_OC * * 1 0 * * * * * +0x7904 MSLI 11 24 4 0 * * * 1 0 * * * * * +0x7a00 MSPI 11 24 4 0 MSPI_GI * * * 0 * * * * * +0x7a01 MSPI 11 24 4 0 MSPI_HU * * * 0 * * * * * +0x7a02 MSPI 11 24 4 0 MSPI_PH * * * 0 * * * * * +0x7a03 MSPI 11 24 4 0 MSPI_SW * * * 0 * * * * * +0x7a04 MSPI 11 24 4 0 MSPI_WR * * * 0 * * * * * +0x7b00 MWLF 11 16 3 0 * * * * 0 * * * * * +0x7b01 MWLF 11 16 3 0 MWLF_WO * * * 0 * * * * * +0x7b02 MWLF 11 16 3 0 MWLF_DI * * * 0 * * * * * +0x7b03 MWLF 11 16 3 0 MWLF_WI * * * 0 * * * * * +0x7b04 MWLF 11 16 3 0 MWLF_VA * * * 0 * * * * * +0x7b05 MWLF 11 16 3 0 MWLF_DR * * * 0 * * * * * +0x7c00 MXVT 11 16 3 0 * * * * 1 * * * * * +0x7c01 MTAS 11 16 3 0 * * * * 0 * * * * * +0x7d00 MZOM 11 16 3 0 * * * * 1 * * * * * +0x7e00 MWER 11 16 3 0 * * * * 0 * * * * * +0x7e01 MGWE 11 16 3 0 * * * * 0 * * * * * +0x7f00 MTRO 10 16 3 0 * * * * 0 1 * * * * +0x7f01 MMIN 10 16 3 0 * * * * 0 1 * * * * +0x7f02 MBEH 10 32 5 0 * * * * 0 1 * * * * +0x7f03 MIMP 10 16 3 0 * * * * 0 1 * * * * +0x7f06 MDJL 10 16 3 0 * * * * 0 1 * * * * +0x7f10 MRAK 10 16 3 0 * * * * 0 1 * * * * +0x8000 MGNL 4 20 3 0 * * * * * * * * S1 HB +0x8100 MHOB 4 16 3 0 * * * * * * * * S1 BW +0x8200 MKOB 4 16 3 0 * * * * * * * * SS BW +0x9000 MOGR 12 24 5 0 * * * * 1 * * * * * +0xa000 MWYV 13 16 3 0 * * * * 0 * * * * * +0xa100 MCAR 13 32 5 0 * * * * 0 * * * * * +0xb000 ACOW 14 32 5 0 * * * * 0 * * * * * +0xb100 AHRS 14 32 5 0 * * * * 0 * * * * * +0xb200 NBEGL 14 16 3 0 * * * * 1 * * * * * +0xb210 NPROL 14 16 3 0 * * * * 1 * * * * * +0xb300 NBOYL 14 16 3 0 * * * * 1 * * * * * +0xb310 NGRLL 14 16 3 0 * * * * 1 * * * * * +0xb400 NFAML 14 16 3 0 * * * * 1 * * * * * +0xb410 NFAWL 14 16 3 0 * * * * 1 * * * * * +0xb500 NSIML 14 16 3 0 * * * * 1 * * * * * +0xb510 NSIWL 14 16 3 0 * * * * 1 * * * * * +0xb600 NNOML 14 16 3 0 * * * * 1 * * * * * +0xb610 NNOWL 14 16 3 0 * * * * 1 * * * * * +0xb700 NSLVL 14 16 3 0 * * * * 1 * * * * * +0xc000 ABAT 15 12 3 0 * * * * 0 * * * * * +0xc100 ACAT 15 12 3 0 * * * * 0 * * * * * +0xc200 ACHK 15 12 3 0 * * * * 0 * * * * * +0xc300 ARAT 15 12 3 0 * * * * 0 * * * * * +0xc400 ASQU 15 12 3 0 * * * * 0 * * * * * +0xc500 ABAT 15 12 3 0 * * * * 0 * * * * * +0xc600 NBEGH 15 16 3 0 * * * * 1 * * * * * +0xc610 NPROH 15 16 3 0 * * * * 1 * * * * * +0xc700 NBOYH 15 16 3 0 * * * * 1 * * * * * +0xc710 NGRLH 15 16 3 0 * * * * 1 * * * * * +0xc800 NFAMH 15 16 3 0 * * * * 1 * * * * * +0xc810 NFAWH 15 16 3 0 * * * * 1 * * * * * +0xc900 NSIMH 15 16 3 0 * * * * 1 * * * * * +0xc910 NSIWH 15 16 3 0 * * * * 1 * * * * * +0xca00 NNOMH 15 16 3 0 * * * * 1 * * * * * +0xca10 NNOWH 15 16 3 0 * * * * 1 * * * * * +0xcb00 NSLVH 15 16 3 0 * * * * 1 * * * * * +0xd000 AEAGG1 16 0 3 0 * * * * 0 * * * * * +0xd100 AGULG1 16 0 3 0 * * * * 0 * * * * * +0xd200 AVULG1 16 0 3 0 * * * * 0 * * * * * +0xd300 ABIRG1 16 0 3 0 * * * * 0 * * * * * +0xd400 ABIRG1 16 0 3 0 * * * * 0 * * * * * +0xe000_000f MBET 17 24 3 0 * * * * * * * * * * +0xe010_000f MBBM 17 20 3 0 * * * * * * * * * * +0xe020_000f MBBR 17 16 3 0 * * * * * * * * * * +0xe030_000f MBFI 17 12 3 0 * * * * * * * * * * +0xe040_000f MBRH 17 64 7 0 * * * * * * * * * * +0xe100_000f MCYC 17 48 7 0 * * * * * * * * * * +0xe110_000f METN 17 48 5 0 * * * * * * * * * * +0xe130_000f MGFR 17 40 5 0 * * * * * * * * * * +0xe140_000f MGVE 17 32 5 0 * * * * * * * * * * +0xe210_000f MELE 17 32 5 0 * * * * * * * * * * +0xe220_000f MELF 17 32 5 0 * * * * * * * * * * +0xe230_000f MELW 17 32 5 0 * * * * * * * * * * +0xe300_000f MGH2 17 16 3 0 * * * * * * * * * * +0xe310_000f MGH3 17 16 3 0 * * * * * * * * * * +0xe320_000f MWIG 17 16 3 0 * * * * * * * * * * +0xe330_000f MZO2 17 16 3 0 * * * * * * * * * * +0xe340_000f MWI2 17 16 3 0 * * * * * * * * * * +0xe350_000f MZO3 17 16 3 0 * * * * * * * * * * +0xe360_000f MWI3 17 16 3 0 * * * * * * * * * * +0xe380_000f MMUM 17 16 3 0 * * * * * * * * * * +0xe390_000f MHIS 17 16 3 0 * * * * * * * * * * +0xe400_000f MGO1 17 16 3 0 * * * * * * * * * * +0xe410_000f MGO2 17 16 3 0 * * * * * * * * * * +0xe420_000f MGO3 17 16 3 0 * * * * * * * * * * +0xe430_000f MGO4 17 16 3 0 * * * * * * * * * * +0xe440_000f MSVI 17 16 3 0 * * * * * * * * * * +0xe450_000f MSV2 17 16 3 0 * * * * * * * * * * +0xe510_000f MGIR 17 24 3 0 * * * * * * * * * * +0xe620_000f MSKB 17 24 3 0 * * * * * * * * * * +0xe700_000f MMIN 17 24 3 0 * * * * * * * * * * +0xe710_000f MTRO 17 16 3 0 * * * * * * * * * * +0xe720_000f MTIC 17 16 3 0 * * * * * * * * * * +0xe730_000f MTSN 17 24 3 0 * * * * * * * * * * +0xe750_000f MUMB 17 24 5 0 * * * * * * * * * * +0xe760_000f MYET 17 24 3 0 * * * * * * * * * * +0xe810_000f MOR1 17 16 3 0 * * * * * * * * * * +0xe820_000f MOR2 17 16 3 0 * * * * * * * * * * +0xe830_000f MOR3 17 16 3 0 * * * * * * * * * * +0xe840_000f MOR4 17 16 3 0 * * * * * * * * * * +0xe850_000f MOR5 17 16 3 0 * * * * * * * * * * +0xe860_000f MNO1 17 16 3 0 * * * * * * * * * * +0xe870_000f MNO2 17 16 3 0 * * * * * * * * * * +0xe880_000f MNO3 17 24 5 0 * * * * * * * * * * +0xe890_000f MLI3 17 32 5 0 * * * * * * * * * * +0xe8a0_000f MYU3 17 16 3 0 * * * * * * * * * * +0xe900_000f MSH1 17 16 3 0 * * * 1 * * * * * * +0xe910_000f MSH2 17 24 3 0 * * * 1 * * * * * * +0xe920_000f MGHO 17 16 3 0 * * * 1 * * * * * * +0xea20_000f MCRD 17 24 3 0 * * * * * * * * * * +0xeb00_000f MANI 17 24 3 0 * * * * * * * * * * +0xeb10_000f MAN2 17 24 3 0 * * * * * * * * * * +0xeb20_000f MAN3 17 24 3 0 * * * * * * * * * * +0xeb30_000f MBE1 17 32 3 0 * * * * * * * * * * +0xeb40_000f MBE2 17 16 3 0 * * * * * * * * * * +0xeb60_000f MLIC 17 16 3 0 * * * * * * * * * * +0xeb70_000f MLER 17 16 3 0 * * * * * * * * * * +0xeb90_000f MMYC 17 16 3 0 * * * * * * * * * * +0xeba0_000f MMY2 17 16 3 0 * * * * * * * * * * +0xebb0_000f MSHR 17 24 3 0 * * * * * * * * * * +0xebc0_000f MTAN 17 24 3 0 * * * * * * * * * * +0xebd0_000f MSAL 17 16 3 0 * * * * * * * * * * +0xebe0_000f MSA2 17 16 3 0 * * * * * * * * * * +0xebf1_000f MARU 17 16 3 0 * * * * * * * * * * +0xf000_000f MSKA 17 16 3 0 * * * * * * * * * * +0xf010_000f MSKT 17 16 3 0 * * * * * * * * * * +0xf100_000f MYU1 17 16 3 0 * * * * * * * * * * +0xf110_000f MYU2 17 16 3 0 * * * * * * * * * * +0xf200_000f MLIZ 17 24 5 0 * * * * * * * * * * +0xf210_000f MLI2 17 24 5 0 * * * * * * * * * * +0xf300_000f MGFI 17 40 5 0 * * * * * * * * * * diff --git a/src/org/infinity/resource/cre/decoder/tables/avatars-iwd2.2da b/src/org/infinity/resource/cre/decoder/tables/avatars-iwd2.2da new file mode 100644 index 000000000..f71fce26b --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/tables/avatars-iwd2.2da @@ -0,0 +1,384 @@ +2DA V1.0 +* + RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD +0x0000 SPRING 0 0 0 0 * * * * 1 0 * * * * +0x0001 SPFLAMES 0 0 0 7 * * * * 0 0 * * * * +0x0100 SPCHUNKS 0 0 0 0 * * SPSHADOW * 1 1 * * * * +0x0200 SPBLOOD 0 0 0 0 * * * * 0 0 0 * * * +0x0210 SPBLOOD 0 0 0 0 * * * * 0 0 1 * * * +0x0220 SPBLOOD 0 0 0 0 * * * * 0 0 2 * * * +0x0230 SPBLOOD 0 0 0 0 * * * * 0 0 3 * * * +0x0240 SPBLOOD 0 0 0 0 * * * * 0 0 4 * * * +0x0300 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * +0x0400 SKLH 0 0 0 0 * * SPSHADOW * 0 1 * * * * +0x0410 GLPHWRDH 0 0 0 1 * * SPSHADOW * 0 1 * * * * +0x0500 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * +0x0510 STNKCLDD 0 0 0 0 * * * * 1 0 0 * * * +0x0600 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * +0x0700 GREASEH 0 0 0 0 * * * * 0 0 * * * * +0x0710 GREASED 0 0 0 0 * * * * 0 1 * * * * +0x0800 WEBENTH 0 0 0 0 * * * * 1 0 * * * * +0x0810 WEBENTD 0 0 0 0 * * * * 1 0 * * * * +0x0900 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * +0x1000 MWYV 1 24 5 0 * * * * 0 * * * * * +0x1001 MWY2 1 24 5 0 * * * * 0 * * * * * +0x1200 MDR1 2 72 13 0 * * * * 0 1 * * * * +0x1201 MDR2 2 93 13 0 * * * * 0 1 * * * * +0x1202 MDR3 2 72 13 0 * * * * 0 1 * * * * +0x1203 MDR1 2 72 13 0 MDR1_GR * * * 0 1 * * * * +0x1204 MDR1 2 72 13 0 MDR1_AQ * * * 0 1 * * * * +0x1205 MDR1 2 72 13 0 MDR1_BL * * * 0 1 * * * * +0x1206 MDR1 2 72 13 0 MDR1_BR * * * 0 1 * * * * +0x1207 MDR1 2 72 13 0 MDR1_MC * * * 0 1 * * * * +0x1208 MDR1 2 72 13 0 MDR1_PU * * * 0 1 * * * * +0x2000 MSIR 5 16 3 0 * * * * 1 * 1 * * BW +0x2100 UVOL 5 16 3 0 * * * * 0 * 1 * MS * +0x2200 MOGM 5 24 5 0 * * * * 1 * 1 1 S1 * +0x2300 MDKN 5 24 5 0 * * * * 0 * 1 1 * * +0x3000 MAKH 6 24 5 0 * * * * 0 * * * * * +0x4000 SNOMC 7 16 3 0 * * * * 1 * * * * * +0x4002 SNOMM 7 16 3 0 * * * * 1 * * * * * +0x4010 SNOWC 7 16 3 0 * * * * 1 * * * * * +0x4012 SNOWM 7 16 3 0 * * * * 1 * * * * * +0x4100 SSIMC 7 16 3 0 * * * * 1 * * * * * +0x4101 SSIMS 7 16 3 0 * * * * 1 * * * * * +0x4102 SSIMM 7 16 3 0 * * * * 1 * * * * * +0x4110 SSIWC 7 16 3 0 * * * * 1 * * * * * +0x4112 SSIWM 7 16 3 0 * * * * 1 * * * * * +0x4200 SHMCM 7 16 3 0 * * * * 1 * * * * * +0x4300 MSPLG1 7 32 5 0 * * * * 0 * * * * * +0x4400 LHMC 7 20 3 0 * * * * 1 * * * * * +0x4410 LHFC 7 20 3 0 * * * * 1 * * * * * +0x4500 LFAM 7 20 3 0 * * * * 1 * * * * * +0x4600 LDMF 7 20 3 0 * * * * 1 * * * * * +0x4700 LEMF 7 20 3 0 * * * * 1 * * * * * +0x4710 LEFF 7 20 3 0 * * * * 1 * * * * * +0x4800 LIMC 7 20 3 0 * * * * 1 * * * * * +0x5000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * +0x5001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * +0x5002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * +0x5003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH +0x5010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * +0x5011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * +0x5012 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * +0x5013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * +0x5100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * +0x5101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * +0x5102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * +0x5103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH +0x5110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * +0x5111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * +0x5112 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * +0x5113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * +0x5200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * +0x5201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * +0x5202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * +0x5210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * +0x5211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * +0x5212 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * +0x5300 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * +0x5301 CEMT 8 16 3 0 * * CEMT * 1 1 0 * WQM * +0x5302 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * +0x5303 CIMT 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH +0x5310 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * +0x5311 CEFT 8 16 3 0 * * CEFT * 1 1 0 * WQM * +0x5312 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * +0x5313 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS * +0x6000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * +0x6001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * +0x6002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * +0x6003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH +0x6004 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS WQH +0x6005 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL WQH +0x6010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * +0x6011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * +0x6012 CIFB 8 16 3 0 * * CIFB * 1 1 1 * WQS * +0x6013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * +0x6014 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS WQH +0x6015 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQL WQH +0x6100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * +0x6101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * +0x6102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * +0x6103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH +0x6104 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS WQH +0x6105 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * +0x6110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * +0x6111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * +0x6112 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * +0x6113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * +0x6114 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS WQH +0x6115 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * +0x6200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * +0x6201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * +0x6202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * +0x6204 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH +0x6205 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * +0x6210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * +0x6211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * +0x6212 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * +0x6214 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH +0x6215 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * +0x6300 CHMT 8 16 3 0 * * CHMF * 1 1 0 * WQL * +0x6301 CEMT 8 16 3 0 * * CEMF * 1 1 0 * WQM * +0x6302 CDMT 8 16 3 0 * * CDMF * 1 1 0 * WQS * +0x6303 CIMT 8 16 3 0 * * CIMF * 1 1 0 * WQS WQH +0x6304 CDMT 8 16 3 0 * * CDMF * 1 1 0 * WQS WQH +0x6305 CHMT 8 16 3 0 * * CHMF * 1 1 0 * WQL * +0x6310 CHFT 8 16 3 0 * * CHFF * 1 1 0 * WQN * +0x6311 CEFT 8 16 3 0 * * CEFF * 1 1 0 * WQM * +0x6312 CIFT 8 16 3 0 * * CIFF * 1 1 0 * WQS * +0x6313 CIFT 8 16 3 0 * * CIFF * 1 1 0 * WQS * +0x6314 CIFT 8 16 3 0 * * CIFF * 1 1 0 * WQS WQH +0x6315 CHFT 8 16 3 0 * * CHFF * 1 1 0 * WQN * +0x6400 UDRZ 9 16 3 0 * * * * 1 * 1 0 WPM * +0x6401 CTES 9 16 3 0 * * * * 1 * 0 0 WPM * +0x6402 CMNK 9 16 3 0 * * * * 1 * 0 * WPM * +0x6403 MSKL 9 16 3 0 * * * * 1 * 1 * WPM * +0x6404 USAR 9 16 3 0 * * * * 0 * 0 0 WPL * +0x6405 MDGU 9 16 3 0 * * * * 1 * 1 * WPM * +0x6406 MDGU 9 16 3 0 * * * * 1 * 1 * WPL * +0x6500 CHMM 8 16 3 0 * * CHMM * 1 1 1 * WQL * +0x6510 CHFM 8 16 3 0 * * CHFM * 1 1 1 * WQN * +0x7000 MOGH 11 16 3 0 * * * * 1 * * * * * +0x7001 MOGN 11 16 3 0 * * * * 1 * * * * * +0x7100 MBAS 11 32 5 0 * * * * 0 * * * * * +0x7101 MBAS 11 32 5 0 MBAS_GR * * * 0 * * * * * +0x7200 MBER 11 24 5 0 MBER_BL * * * 0 * * * * * +0x7201 MBER 11 24 5 0 * * * * 0 * * * * * +0x7202 MBER 11 24 5 0 MBER_CA * * * 0 * * * * * +0x7203 MBER 11 24 5 0 MBER_PO * * * 0 * * * * * +0x7300 MEAE 10 32 5 0 * * * * 0 1 * * * * +0x7301 MEAS 10 16 3 0 * * * * 0 1 * * * * +0x7302 MEAE 10 32 5 0 MEAE_SH * * * 0 1 * * * * +0x7310 MFIE 10 16 3 4 * * * * 0 1 * * * * +0x7311 MFIS 10 16 3 4 * * * * 0 1 * * * * +0x7320 MAIR 10 32 5 0 * * * 1 0 1 * * * * +0x7321 MAIS 10 16 3 0 * * * 1 0 1 * * * * +0x7400 MDOG 11 16 3 0 MDOG_WI * * * 0 * * * * * +0x7401 MDOG 11 16 3 0 MDOG_WA * * * 0 * * * * * +0x7402 MDOG 11 16 3 0 MDOG_MO * * * 0 * * * * * +0x7500 MDOP 11 16 3 0 * * * * 0 * * * * * +0x7501 MDOP 11 16 3 0 MDOP_GR * * * 0 * * * * * +0x7600 METT 11 16 3 0 * * * * 0 * * * * * +0x7700 MGHL 11 16 3 0 * * * * 0 * * * * * +0x7701 MGHL 11 16 3 0 MGHL_RE * * * 0 * * * * * +0x7702 MGHL 11 16 3 0 MGHL_GA * * * 0 * * * * * +0x7800 MGIB 11 16 3 0 * * * * 0 * * * * * +0x7900 MSLI 11 24 4 0 MSLI_GR * * 1 0 * * * * * +0x7901 MSLI 11 24 4 0 MSLI_OL * * 1 0 * * * * * +0x7902 MSLI 11 24 4 0 MSLI_MU * * 1 0 * * * * * +0x7903 MSLI 11 24 4 0 MSLI_OC * * 1 0 * * * * * +0x7904 MSLI 11 24 4 0 * * * 1 0 * * * * * +0x7a00 MSPI 11 24 4 0 MSPI_GI * * * 0 * * * * * +0x7a01 MSPI 11 24 4 0 MSPI_HU * * * 0 * * * * * +0x7a02 MSPI 11 24 4 0 MSPI_PH * * * 0 * * * * * +0x7a03 MSPI 11 24 4 0 MSPI_SW * * * 0 * * * * * +0x7a04 MSPI 11 24 4 0 MSPI_WR * * * 0 * * * * * +0x7b00 MWLF 11 16 3 0 * * * * 0 * * * * * +0x7b01 MWLF 11 16 3 0 MWLF_WO * * * 0 * * * * * +0x7b02 MWLF 11 16 3 0 MWLF_DI * * * 0 * * * * * +0x7b03 MWLF 11 16 3 0 MWLF_WI * * * 0 * * * * * +0x7b04 MWLF 11 16 3 0 MWLF_VA * * * 0 * * * * * +0x7b05 MWLF 11 16 3 0 MWLF_DR * * * 0 * * * * * +0x7c00 MXVT 11 16 3 0 * * * * 1 * * * * * +0x7c01 MTAS 11 16 3 0 * * * * 0 * * * * * +0x7d00 MZOM 11 16 3 0 * * * * 1 * * * * * +0x7e00 MWER 11 16 3 0 * * * * 0 * * * * * +0x7e01 MGWE 11 16 3 0 * * * * 0 * * * * * +0x7f00 MTRO 10 16 3 0 * * * * 0 1 * * * * +0x7f01 MMIN 10 16 3 0 * * * * 0 1 * * * * +0x7f02 MBEH 10 32 5 0 * * * * 0 1 * * * * +0x7f03 MIMP 10 16 3 0 * * * * 0 1 * * * * +0x7f04 MIGO 10 32 5 0 * * * * 0 1 * * * * +0x7f05 MDJI 10 16 3 0 * * * * 0 1 * * * * +0x7f06 MDJL 10 16 3 0 * * * * 0 1 * * * * +0x7f07 MGLC 10 16 3 0 * * * * 0 1 * * * * +0x7f08 MOTY 10 24 5 0 * * * * 0 1 * * * * +0x7f0a MGCP 10 16 3 0 * * * * 0 1 * * * * +0x7f0b MGCL 10 16 3 0 * * * * 0 1 * * * * +0x7f10 MRAK 10 16 3 0 * * * * 0 1 * * * * +0x7f13 MSNK 10 16 3 0 * * * * 0 1 * * * * +0x7f18 ADER 10 16 3 0 * * * * 0 1 * * * * +0x7f24 NPIR 10 16 3 0 * * * * 0 1 * * * * +0x7f27 MDRO 10 16 3 0 * * * * 0 1 * * * * +0x7f29 MFDR 10 16 3 0 * * * * 0 1 * * * * +0x7f2a NSAI 10 16 3 0 * * * * 0 1 * * * * +0x7f2e MRAV 10 32 5 0 * * * * 0 1 * * * * +0x7f2f MSPS 10 16 3 0 * * * * 0 1 * * * * +0x7f32 MSLY 10 16 3 0 * * * * 0 1 * * * * +0x7f38 MEYE 10 16 3 0 * * * * 0 0 * * * * +0x8000 MGNL 4 20 3 0 * * * * * * * * S1 HB +0x8100 MHOB 4 16 3 0 * * * * * * * * S1 BW +0x8200 MKOB 4 16 3 0 * * * * * * * * SS BW +0x9000 MOGR 12 24 5 0 * * * * 1 * * * * * +0xa000 MWYV 13 16 3 0 * * * * 0 * * * * * +0xa100 MCAR 13 32 5 0 * * * * 0 * * * * * +0xb000 ACOW 14 32 5 0 * * * * 0 * * * * * +0xb100 AHRS 14 32 5 0 * * * * 0 * * * * * +0xb200 NBEGL 14 16 3 0 * * * * 1 * * * * * +0xb210 NPROL 14 16 3 0 * * * * 1 * * * * * +0xb300 NBOYL 14 16 3 0 * * * * 1 * * * * * +0xb310 NGRLL 14 16 3 0 * * * * 1 * * * * * +0xb400 NFAML 14 16 3 0 * * * * 1 * * * * * +0xb410 NFAWL 14 16 3 0 * * * * 1 * * * * * +0xb500 NSIML 14 16 3 0 * * * * 1 * * * * * +0xb510 NSIWL 14 16 3 0 * * * * 1 * * * * * +0xb600 NNOML 14 16 3 0 * * * * 1 * * * * * +0xb610 NNOWL 14 16 3 0 * * * * 1 * * * * * +0xb700 NSLVL 14 16 3 0 * * * * 1 * * * * * +0xc000 ABAT 15 12 3 0 * * * * 0 * * * * * +0xc100 ACAT 15 12 3 0 * * * * 0 * * * * * +0xc200 ACHK 15 12 3 0 * * * * 0 * * * * * +0xc300 ARAT 15 12 3 0 * * * * 0 * * * * * +0xc400 ASQU 15 12 3 0 * * * * 0 * * * * * +0xc500 ABAT 15 12 3 0 * * * * 0 * * * * * +0xc600 NBEGH 15 16 3 0 * * * * 1 * * * * * +0xc610 NPROH 15 16 3 0 * * * * 1 * * * * * +0xc700 NBOYH 15 16 3 0 * * * * 1 * * * * * +0xc710 NGRLH 15 16 3 0 * * * * 1 * * * * * +0xc800 NFAMH 15 16 3 0 * * * * 1 * * * * * +0xc810 NFAWH 15 16 3 0 * * * * 1 * * * * * +0xc900 NSIMH 15 16 3 0 * * * * 1 * * * * * +0xc910 NSIWH 15 16 3 0 * * * * 1 * * * * * +0xca00 NNOMH 15 16 3 0 * * * * 1 * * * * * +0xca10 NNOWH 15 16 3 0 * * * * 1 * * * * * +0xcb00 NSLVH 15 16 3 0 * * * * 1 * * * * * +0xd000 AEAGG1 16 0 3 0 * * * * 0 * * * * * +0xd100 AGULG1 16 0 3 0 * * * * 0 * * * * * +0xd200 AVULG1 16 0 3 0 * * * * 0 * * * * * +0xd300 ABIRG1 16 0 3 0 * * * * 0 * * * * * +0xd400 ABIRG1 16 0 3 0 * * * * 0 * * * * * +0xe000_000f MBET 17 24 3 0 * * * * * * * * * * +0xe010_000f MBBM 17 20 3 0 * * * * * * * * * * +0xe020_000f MBBR 17 16 3 0 * * * * * * * * * * +0xe030_000f MBFI 17 12 3 0 * * * * * * * * * * +0xe040_000f MBRH 17 64 7 0 * * * * * * * * * * +0xe050_000f MREM 17 48 5 0 * * * * * * * * * * +0xe060_000f MHOH 17 24 3 0 * * * * * * * * * * +0xe100_000f MCYC 17 48 7 0 * * * * * * * * * * +0xe110_000f METN 17 48 5 0 * * * * * * * * * * +0xe130_000f MGFR 17 40 5 0 * * * * * * * * * * +0xe140_000f MGVE 17 32 5 0 * * * * * * * * * * +0xe150_000f MGFO 17 32 5 0 * * * * * * * * * * +0xe210_000f MELE 17 32 5 0 * * * * * * * * * * +0xe220_000f MELF 17 32 5 0 * * * * * * * * * * +0xe230_000f MELW 17 32 5 0 * * * * * * * * * * +0xe240_000f MHAR 17 16 3 0 * * * * * * * * * * +0xe250_000f MWWE 17 24 3 0 * * * * * * * * * * +0xe260_000f MFEY 17 24 3 0 * * * * * * * * * * +0xe270_000f MDTR 17 24 3 0 * * * * * * * * * * +0xe280_000f MFE2 17 24 3 0 * * * * * * * * * * +0xe290_000f MEW2 17 24 3 0 * * * * * * * * * * +0xe300_000f MGH2 17 16 3 0 * * * * * * * * * * +0xe310_000f MGH3 17 16 3 0 * * * * * * * * * * +0xe320_000f MWIG 17 16 3 0 * * * * * * * * * * +0xe330_000f MZO2 17 16 3 0 * * * * * * * * * * +0xe340_000f MZO3 17 16 3 0 * * * * * * * * * * +0xe350_000f MWI2 17 16 3 0 * * * * * * * * * * +0xe360_000f MWI3 17 16 3 0 * * * * * * * * * * +0xe380_000f MMUM 17 16 3 0 * * * * * * * * * * +0xe390_000f MHIS 17 16 3 0 * * * * * * * * * * +0xe3a0_000f MDRD 17 24 3 0 * * * * * * * * * * +0xe3b0_000f MWAV 17 16 3 0 * * * 1 * * * * * * +0xe400_000f MGO1 17 16 3 0 * * * * * * * * * * +0xe410_000f MGO2 17 16 3 0 * * * * * * * * * * +0xe420_000f MGO3 17 16 3 0 * * * * * * * * * * +0xe430_000f MGO4 17 16 3 0 * * * * * * * * * * +0xe440_000f MSVI 17 16 3 0 * * * * * * * * * * +0xe450_000f MSV2 17 16 3 0 * * * * * * * * * * +0xe460_000f MGWO 17 24 3 0 * * * * * * * * * * +0xe470_000f MGOC 17 16 3 0 * * * * * * * * * * +0xe480_000f MGW2 17 24 3 0 * * * * * * * * * * +0xe490_000f MGO5 17 16 3 0 * * * * * * * * * * +0xe510_000f MGIR 17 24 3 0 * * * * * * * * * * +0xe520_000f MGIC 17 32 5 0 * * * * * * * * * * +0xe620_000f MSKB 17 24 3 0 * * * * * * * * * * +0xe700_000f MMIN 17 24 3 0 * * * * * * * * * * +0xe710_000f MTRO 17 16 3 0 * * * * * * * * * * +0xe720_000f MTIC 17 16 3 0 * * * * * * * * * * +0xe730_000f MTSN 17 24 3 0 * * * * * * * * * * +0xe750_000f MUMB 17 24 5 0 * * * * * * * * * * +0xe760_000f MYET 17 24 3 0 * * * * * * * * * * +0xe770_000f MBA4 17 16 3 0 * * * * * * * * * * +0xe780_000f MBA5 17 16 3 0 * * * * * * * * * * +0xe790_000f MBA6 17 16 3 0 * * * * * * * * * * +0xe7a0_000f MBAI 17 16 3 0 * * * * * * * * * * +0xe7b0_000f MBOA 17 24 3 0 * * * * * * * * * * +0xe7c0_000f MABW 17 24 3 0 * * * * * * * * * * +0xe7d0_000f MMAL 17 16 3 0 * * * * * * * * * * +0xe7e0_000f MSCR 17 24 3 0 * * * * * * * * * * +0xe7f0_000f MUM2 17 24 5 0 * * * * * * * * * * +0xe800_000f MOR6 17 16 3 0 * * * * * * * * * * +0xe810_000f MOR1 17 16 3 0 * * * * * * * * * * +0xe820_000f MOR2 17 16 3 0 * * * * * * * * * * +0xe830_000f MOR3 17 16 3 0 * * * * * * * * * * +0xe840_000f MOR4 17 16 3 0 * * * * * * * * * * +0xe850_000f MOR5 17 16 3 0 * * * * * * * * * * +0xe860_000f MNO1 17 16 3 0 * * * * * * * * * * +0xe870_000f MNO2 17 16 3 0 * * * * * * * * * * +0xe880_000f MNO3 17 24 5 0 * * * * * * * * * * +0xe890_000f MLI3 17 32 5 0 * * * * * * * * * * +0xe8a0_000f MYU3 17 16 3 0 * * * * * * * * * * +0xe8b0_000f MYUH 17 16 3 0 * * * * * * * * * * +0xe8c0_000f MBUG 17 16 3 0 * * * * * * * * * * +0xe8d0_000f MNOS 17 24 3 0 * * * * * * * * * * +0xe8e0_000f MBU2 17 16 3 0 * * * * * * * * * * +0xe8f0_000f MOR7 17 16 3 0 * * * * * * * * * * +0xe900_000f MSH1 17 16 3 0 * * * 1 * * * * * * +0xe910_000f MSH2 17 24 3 0 * * * 1 * * * * * * +0xe920_000f MGHO 17 16 3 0 * * * 1 * * * * * * +0xea20_000f MCRD 17 24 3 0 * * * * * * * * * * +0xeb00_000f MANI 17 24 3 0 * * * * * * * * * * +0xeb10_000f MAN2 17 24 3 0 * * * * * * * * * * +0xeb20_000f MAN3 17 24 3 0 * * * * * * * * * * +0xeb30_000f MBE1 17 32 3 0 * * * * * * * * * * +0xeb40_000f MBE2 17 16 3 0 * * * * * * * * * * +0xeb51 MSEE 17 16 3 0 * * * * * * * * * * +0xeb60_000f MLIC 17 16 3 0 * * * * * * * * * * +0xeb70_000f MLER 17 16 3 0 * * * * * * * * * * +0xeb90_000f MMYC 17 16 3 0 * * * * * * * * * * +0xeba0_000f MMY2 17 16 3 0 * * * * * * * * * * +0xebb0_000f MSHR 17 24 3 0 * * * * * * * * * * +0xebc0_000f MTAN 17 24 3 0 * * * * * * * * * * +0xebd0_000f MSAL 17 16 3 0 * * * * * * * * * * +0xebe0_000f MSA2 17 16 3 0 * * * * * * * * * * +0xebf1_000f MARU 17 16 3 0 * * * * * * * * * * +0xec00_000f MWDR 17 72 7 0 * * * * * * * * * * +0xec10_000f MCHY 17 16 3 0 * * * * * * * * * * +0xec20_000f MSHE 17 24 3 0 * * * * * * * * * * +0xec30_000f MCHI 17 48 5 0 * * * * * * * * * * +0xec40_000f MDH1 17 24 3 0 * * * * * * * * * * +0xec50_000f MDH2 17 24 3 0 * * * * * * * * * * +0xed00_000f MCOR 17 24 5 0 * * * * * * * * * * +0xed10_000f MGLA 17 24 5 0 * * * * * * * * * * +0xed20_000f MLEM 17 16 3 0 * * * * * * * * * * +0xee00_000f MWEB 17 24 3 0 * * * * * * * * * * +0xee10_000f MWRA 17 24 3 0 * * * * * * * * * * +0xef00_000f MISA 17 24 3 0 * * * * * * * * * * +0xef10_000f MMAD 17 24 3 0 * * * * * * * * * * +0xef20_000f MWOR 17 24 5 0 * * * * * * * * * * +0xef50_000f MKG1 17 16 3 0 * * * * * * * * * * +0xef60_000f MKG2 17 16 3 0 * * * * * * * * * * +0xef70_000f MKG3 17 16 3 0 * * * * * * * * * * +0xef90_000f MWIL 17 16 3 0 * * * * * * * * * * +0xefa0_000f MGEN 17 16 2 0 * * * * * * * * * * +0xefb0_000f MGEN 17 24 3 0 * * * * * * * * * * +0xefc0_000f MGEN 17 36 4 0 * * * * * * * * * * +0xefd0_000f MGEN 17 48 5 0 * * * * * * * * * * +0xefe0_000f MGEN 17 12 3 0 * * * * * * * * * * +0xeff0_000f MGEN 17 64 7 0 * * * * * * * * * * +0xf000_000f MSKA 17 16 3 0 * * * * * * * * * * +0xf010_000f MSKT 17 16 3 0 * * * * * * * * * * +0xf020_000f MWI4 17 16 3 0 * * * * * * * * * * +0xf100_000f MYU1 17 16 3 0 * * * * * * * * * * +0xf110_000f MYU2 17 16 3 0 * * * * * * * * * * +0xf200_000f MLIZ 17 24 5 0 * * * * * * * * * * +0xf210_000f MLI2 17 24 5 0 * * * * * * * * * * +0xf300_000f MGFI 17 40 5 0 * * * * * * * * * * +0xf400_000f MSAH 17 24 3 0 * * * * * * * * * * +0xf410_000f MSAT 17 32 5 0 * * * * * * * * * * +0xf500_000f MDRM 17 32 4 0 * * * * * * * * * * +0xf510_000f MDRF 17 32 4 0 * * * * * * * * * * +0xf770_000f MBA1 17 16 3 0 * * * * * * * * * * +0xf780_000f MBA2 17 16 3 0 * * * * * * * * * * +0xf790_000f MBA3 17 16 3 0 * * * * * * * * * * diff --git a/src/org/infinity/resource/cre/decoder/tables/avatars-iwdee.2da b/src/org/infinity/resource/cre/decoder/tables/avatars-iwdee.2da new file mode 100644 index 000000000..4f0d14602 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/tables/avatars-iwdee.2da @@ -0,0 +1,453 @@ +2DA V1.0 +* + RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD +0x0000 SPRING 0 0 0 0 * * * * 1 0 * * * * +0x0001 SPFLAMES 0 0 0 7 * * * * 0 0 * * * * +0x0002 SPRDRASI 0 0 0 7 * * * * 0 0 * * * * +0x0003 SPFLAMES 0 0 0 7 SPFLAMEB * * * 0 0 * * * * +0x0004 SPRDRASI 0 0 0 7 SPGDRASI * * * 0 0 * * * * +0x0100 SPCHUNKS 0 0 0 0 * * SPSHADOW * 1 1 * * * * +0x0101 BGLRYU 0 0 3 7 * * * * * 0 * * * * +0x0102 SPCL236U 0 0 3 7 * * * * * 0 * * * * +0x0200 SPBLOOD 0 0 0 0 * * * * 0 0 0 * * * +0x0210 SPBLOOD 0 0 0 0 * * * * 0 0 1 * * * +0x0220 SPBLOOD 0 0 0 0 * * * * 0 0 2 * * * +0x0230 SPBLOOD 0 0 0 0 * * * * 0 0 3 * * * +0x0240 SPBLOOD 0 0 0 0 * * * * 0 0 4 * * * +0x0300 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * +0x0301 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * +0x0400 SKLH 0 0 0 0 * * SPSHADOW * 0 1 * * * * +0x0410 GLPHWRDH 0 0 0 1 * * SPSHADOW * 0 1 * * * * +0x0500 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * +0x0510 STNKCLDD 0 0 0 0 * * * * 1 0 0 * * * +0x0520 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * +0x0600 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * +0x0610 SPICESTM 0 0 0 1 * * * * 0 0 0 * * * +0x0700 GREASEH 0 0 0 0 * * * * 0 0 * * * * +0x0710 GREASED 0 0 0 0 * * * * 0 1 * * * * +0x0800 WEBENTH 0 0 0 0 * * * * 1 0 * * * * +0x0810 WEBENTD 0 0 0 0 * * * * 1 0 * * * * +0x0900 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * +0x0910 SPMETSWM 0 0 0 1 * * * * 0 1 0 * * * +0x0a00 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * +0x0a01 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * +0x0a02 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * +0x0a03 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * +0x0a04 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * +0x0a10 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * +0x0a11 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * +0x0a12 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * +0x0a13 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * +0x0a14 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * +0x0a23 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * +0x0a24 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * +0x0b00 SPCSPRA2 0 0 0 1 * * * * 0 0 * * * * +0x0b01 SPCCOLDL 0 0 0 1 * * * * 0 0 * * * * +0x0b02 SPPRISM2 0 0 0 1 * * * * 0 0 * * * * +0x0b03 SPCSPRA3 0 0 0 1 * * * * 0 0 * * * * +0x0b04 SPPRISM3 0 0 0 1 * * * * 0 0 * * * * +0x0c00 SPSTRMVA 0 0 0 1 * * * * 0 1 * * SPSTRMVB * +0x0c10 SPSTRMVA 0 0 0 1 * * * * 0 1 * * SPSTRMVB * +0x1000 MWYV 1 24 5 0 * * * * 0 * * * * * +0x1003 MWYV 1 24 5 0 MWYV_WH * * * 0 * * * * * +0x1100 MTAN 1 32 5 0 * * * * 0 * 1 * * * +0x1101 MWDR 1 72 7 0 * * * * 0 * * * * * +0x1102 MTAN 1 32 5 0 MTAN_BL * * * 0 * 1 * * * +0x1103 MTAN 1 32 5 0 MTAN_GR * * * 0 * 1 * * * +0x1104 MTAN 1 32 5 0 MTAN_RD * * * 0 * 1 * * * +0x1200 MDR1 2 72 13 0 * * * * 0 1 * * * * +0x1201 MDR2 2 72 13 0 * * * * 0 1 * * * * +0x1202 MDR3 2 72 13 0 * * * * 0 1 * * * * +0x1203 MDR1 2 72 13 0 MDR1_GR * * * 0 1 * * * * +0x1204 MDR1 2 72 13 0 MDR1_AQ * * * 0 1 * * * * +0x1205 MDR1 2 72 13 0 MDR1_BL * * * 0 1 * * * * +0x1206 MDR1 2 72 13 0 MDR1_BR * * * 0 1 * * * * +0x1207 MDR1 2 72 13 0 MDR1_MC * * * 0 1 * * * * +0x1208 MDR1 2 72 13 0 MDR1_PU * * * 0 1 * * * * +0x1300 MDEM 3 40 5 0 * * * * 0 1 * * * * +0x2000 MSIR 5 16 3 0 * * * * 1 * 1 * * BW +0x2100 UVOL 5 16 3 0 * * * * 0 * 1 * MS * +0x2200 MOGM 5 24 5 0 * * * * 1 * 1 1 S1 * +0x2300 MDKN 5 24 5 0 * * * * 0 * 1 1 * * +0x3000 MAKH 6 24 5 0 * * * * 0 * * * * * +0x4000 SNOMC 7 16 3 0 * * * * 1 * * * * * +0x4002 SNOMM 7 16 3 0 * * * * 1 * * * * * +0x4010 SNOWC 7 16 3 0 * * * * 1 * * * * * +0x4012 SNOWM 7 16 3 0 * * * * 1 * * * * * +0x4100 SSIMC 7 16 3 0 * * * * 1 * * * * * +0x4101 SSIMS 7 16 3 0 * * * * 1 * * * * * +0x4102 SSIMM 7 16 3 0 * * * * 1 * * * * * +0x4110 SSIWC 7 16 3 0 * * * * 1 * * * * * +0x4112 SSIWM 7 16 3 0 * * * * 1 * * * * * +0x4200 SHMCM 7 16 3 0 * * * * 1 * * * * * +0x4300 MSPLG1 7 32 5 0 * * * * 0 * * * * * +0x4400 LHMC 7 20 3 0 * * * * 1 * * * * * +0x4410 LHFC 7 20 3 0 * * * * 1 * * * * * +0x4500 LFAM 7 20 3 0 * * * * 1 * * * * * +0x4600 LDMF 7 20 3 0 * * * * 1 * * * * * +0x4700 LEMF 7 20 3 0 * * * * 1 * * * * * +0x4710 LEFF 7 20 3 0 * * * * 1 * * * * * +0x4800 LIMC 7 20 3 0 * * * * 1 * * * * * +0x5000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * +0x5001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * +0x5002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * +0x5003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH +0x5010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * +0x5011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * +0x5012 CDFB 8 16 3 0 * * CDFC * 1 1 1 * WQS * +0x5013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * +0x5100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * +0x5101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * +0x5102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * +0x5103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH +0x5110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * +0x5111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * +0x5112 CDFB 8 16 3 0 * * CDFF * 1 1 1 * WQS * +0x5113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * +0x5200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * +0x5201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * +0x5202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * +0x5210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * +0x5211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * +0x5212 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS * +0x5300 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * +0x5301 CEMT 8 16 3 0 * * CEMT * 1 1 0 * WQM * +0x5302 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * +0x5303 CIMT 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH +0x5310 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * +0x5311 CEFT 8 16 3 0 * * CEFT * 1 1 0 * WQM * +0x5312 CDFT 8 16 3 0 * * CDFT * 1 1 0 * WQS * +0x5313 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS * +0x6000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * +0x6001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * +0x6002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * +0x6003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH +0x6004 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS WQH +0x6005 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * +0x6010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * +0x6011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * +0x6012 CDFB 8 16 3 0 * * CDFC * 1 1 1 * WQS * +0x6013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * +0x6014 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS WQH +0x6015 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * +0x6100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * +0x6101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * +0x6102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * +0x6103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH +0x6104 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS WQH +0x6105 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * +0x6110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * +0x6111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * +0x6112 CDFB 8 16 3 0 * * CDFF * 1 1 1 * WQS * +0x6113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * +0x6114 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS WQH +0x6115 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * +0x6200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * +0x6201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * +0x6202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * +0x6204 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH +0x6205 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * +0x6210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * +0x6211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * +0x6212 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS * +0x6214 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS WQH +0x6215 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * +0x6300 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * +0x6301 CEMT 8 16 3 0 * * CEMT * 1 1 0 * WQM * +0x6302 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * +0x6303 CIMT 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH +0x6304 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS WQH +0x6305 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * +0x6310 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * +0x6311 CEFT 8 16 3 0 * * CEFT * 1 1 0 * WQM * +0x6312 CDFT 8 16 3 0 * * CDFT * 1 1 0 * WQS * +0x6313 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS * +0x6314 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS WQH +0x6315 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * +0x6400 UDRZ 9 16 3 0 * * * * 1 * 1 0 WPM * +0x6401 UELM 9 16 3 0 * * * * 0 * 0 0 WPM * +0x6402 CMNK 9 16 3 0 * * * * 1 * 0 * WPM * +0x6403 MSKL 9 16 3 0 * * * * 1 * 1 * WPM * +0x6404 USAR 9 16 3 0 * * * * 0 * 0 0 WPL * +0x6405 MDGU 9 16 3 0 * * * * 1 * 1 * WPM * +0x6406 MDGU 9 24 7 0 * * * * 1 * 1 * WPM * +0x6500 CHMM 8 16 3 0 * * CHMM * 1 1 1 * WQL * +0x6510 CHFM 8 16 3 0 * * CHFM * 1 1 1 * WQN * +0x7000 MOGH 11 16 3 0 * * * * 1 * * * * * +0x7001 MOGN 11 16 3 0 * * * * 1 * * * * * +0x7100 MBAS 11 32 5 0 * * * * 0 * * * * * +0x7101 MBAS 11 32 5 0 MBAS_GR * * * 0 * * * * * +0x7200 MBER 11 24 5 0 MBER_BL * * * 0 * * * * * +0x7201 MBER 11 24 5 0 * * * * 0 * * * * * +0x7202 MBER 11 24 5 0 MBER_CA * * * 0 * * * * * +0x7203 MBER 11 24 5 0 MBER_PO * * * 0 * * * * * +0x7300 MEAE 10 32 5 0 * * * * 0 1 * * * * +0x7301 MEAS 10 16 3 0 * * * * 0 1 * * * * +0x7302 MEAE 10 32 5 0 MEAE_SH * * * 0 1 * * * * +0x7310 MFIE 10 24 3 4 * * * * 0 1 * * * * +0x7311 MFIS 10 16 3 4 * * * * 0 1 * * * * +0x7312 MFIE 10 24 3 4 MFIEG1B MFIEG2B * * 0 1 * * * * +0x7313 MFIS 10 16 3 4 MFISG1B MFISG2B * * 0 1 * * * * +0x7314 MFIE 10 24 3 4 MFIEG31 MFIEG3B * * 0 1 * * * * +0x7320 MAIR 10 24 5 0 * * * 1 0 1 * * * * +0x7321 MAIS 10 16 3 0 * * * 1 0 1 * * * * +0x7400 MDOG 11 16 3 0 MDOG_WI * * * 0 * * * * * +0x7401 MDOG 11 16 3 0 MDOG_WA * * * 0 * * * * * +0x7402 MDOG 11 16 3 0 MDOG_MO * * * 0 * * * * * +0x7500 MDOP 11 16 3 0 * * * * 0 * * * * * +0x7501 MDOP 11 16 3 0 MDOP_GR * * * 0 * * * * * +0x7600 METT 11 16 3 0 * * * * 0 * * * * * +0x7601 MGHL 11 16 3 0 MGHL_MA * * * 0 0 * * * * +0x7700 MGHL 11 16 3 0 * * * * 0 * * * * * +0x7701 MGHL 11 16 3 0 MGHL_RE * * * 0 * * * * * +0x7702 MGHL 11 16 3 0 MGHL_GA * * * 0 * * * * * +0x7703 MSHD 10 16 3 0 * * * 1 0 1 * * * * +0x7800 MGIB 11 16 3 0 * * * * 0 * * * * * +0x7900 MSLI 11 24 4 0 MSLI_GR * * 1 0 * * * * * +0x7901 MSLI 11 24 4 0 MSLI_OL * * 1 0 * * * * * +0x7902 MSLI 11 24 4 0 MSLI_MU * * 1 0 * * * * * +0x7903 MSLI 11 24 4 0 MSLI_OC * * 1 0 * * * * * +0x7904 MSLI 11 24 4 0 * * * 1 0 * * * * * +0x7a00 MSPI 11 24 4 0 MSPI_GI * * * 0 * * * * * +0x7a01 MSPI 11 24 4 0 MSPI_HU * * * 0 * * * * * +0x7a02 MSPI 11 24 4 0 MSPI_PH * * * 0 * * * * * +0x7a03 MSPI 11 24 4 0 MSPI_SW * * * 0 * * * * * +0x7a04 MSPI 11 24 4 0 MSPI_WR * * * 0 * * * * * +0x7b00 MWLF 11 16 3 0 * * * * 0 * * * * * +0x7b01 MWLF 11 16 3 0 MWLF_WO * * * 0 * * * * * +0x7b02 MWLF 11 16 3 0 MWLF_DI * * * 0 * * * * * +0x7b03 MWLF 11 16 3 0 MWLF_WI * * * 0 * * * * * +0x7b04 MWLF 11 16 3 0 MWLF_VA * * * 0 * * * * * +0x7b05 MWLF 11 16 3 0 MWLF_DR * * * 0 * * * * * +0x7b06 MWLS 11 16 3 0 * * * * 0 * * * * * +0x7c00 MXVT 11 16 3 0 * * * * 1 * * * * * +0x7c01 MTAS 11 16 3 0 * * * * 0 * * * * * +0x7d00 MZOM 11 16 3 0 * * * * 1 * * * * * +0x7d01 NSLF 11 16 3 0 * * * * 1 0 * * * * +0x7d02 MCHB 11 12 3 0 * * * * 0 0 * * * * +0x7d03 MCHW 11 12 3 0 * * * * 0 0 * * * * +0x7e00 MWER 11 16 3 0 * * * * 0 * * * * * +0x7e01 MGWE 11 16 3 0 * * * * 0 * * * * * +0x7f00 MTRO 10 16 3 0 * * * * 0 1 * * * * +0x7f01 MMIN 10 16 3 0 * * * * 0 1 * * * * +0x7f02 MBEH 10 32 5 0 * * * * 0 1 * * * * +0x7f03 MIMP 10 16 3 0 * * * * 0 1 * * * * +0x7f04 MIGO 10 32 5 0 * * * * 0 1 * * * * +0x7f05 MDJI 10 16 3 0 * * * * 0 1 * * * * +0x7f06 MDJL 10 16 3 0 * * * * 0 1 * * * * +0x7f07 MGLC 10 24 3 0 * * * * 0 1 * * * * +0x7f08 MOTY 10 24 7 0 * * * * 0 1 * * * * +0x7f09 MSAH 10 24 3 0 * * * * 0 1 * * * * +0x7f0a MGCP 10 24 5 0 * * * * 0 1 * * * * +0x7f0b MGCL 10 24 5 0 * * * * 0 1 * * * * +0x7f0c MKUO 10 24 3 0 * * * * 0 1 * * * * +0x7f0d MLIC 10 16 3 0 * * * * 0 1 * * * * +0x7f0e MDLI 10 16 3 0 * * * * 0 1 * * * * +0x7f0f MTRS 10 12 3 0 * * * * 0 1 * * * * +0x7f10 MRAK 10 16 3 0 * * * * 0 1 * * * * +0x7f11 MUMB 10 24 5 0 * * * * 0 1 * * * * +0x7f12 MVAM 10 16 3 0 * * * * 0 1 * * * * +0x7f13 MSNK 10 24 5 0 * * * * 0 1 * * * * +0x7f14 MGIT 10 16 3 0 * * * * 0 1 * * * * +0x7f15 MBES 10 16 3 0 * * * * 0 1 * * * * +0x7f16 AMOO 10 32 5 0 * * * * 0 1 * * * * +0x7f17 ARAB 10 12 3 0 * * * * 0 1 * * * * +0x7f18 ADER 10 16 3 0 * * * * 0 1 * * * * +0x7f19 MDSW 10 16 3 0 * * * * 0 1 * * * * +0x7f20 AGRO 10 12 3 0 * * * * 0 1 * * * * +0x7f21 APHE 10 12 3 0 * * * * 0 1 * * * * +0x7f22 MVAF 10 16 3 0 * * * * 0 1 * * * * +0x7f23 MSAT 10 32 5 0 * * * * 0 1 * * * * +0x7f24 NPIR 10 16 3 0 * * * * 0 1 * * * * +0x7f27 MDRO 10 16 3 0 * * * * 0 1 * * * * +0x7f28 MKUL 10 32 5 0 * * * * 0 1 * * * * +0x7f29 MFDR 10 16 3 0 * * * * 0 1 * * * * +0x7f2a NSAI 10 16 3 0 * * * * 0 1 * * * * +0x7f2b MMAX 10 16 3 0 * * * * 0 0 * * * * +0x7f2c NSOL 10 16 3 0 * * * * 0 1 * * * * +0x7f2d MWFM 10 16 3 0 * * * * 0 1 * * * * +0x7f2e MRAV 10 32 5 0 * * * * 0 1 * * * * +0x7f2f MSPS 10 16 3 0 * * * * 0 1 * * * * +0x7f30 NBOH 10 16 3 0 * * * * 0 1 * * * * +0x7f31 NELL 10 16 3 0 * * * * 0 1 * * * * +0x7f32 MSLY 10 24 5 0 * * * * 0 1 * * * * +0x7f33 MKUR 10 16 3 0 * * * * 0 0 * * * * +0x7f34 MDOC 10 16 3 0 * * * * 0 0 * * * * +0x7f35 MMIS 10 16 3 0 * * * 1 0 1 * * * * +0x7f36 NSHD 10 16 3 0 * * * * 0 1 * * * * +0x7f37 NIRE 10 16 3 0 * * * * 0 1 * * * * +0x7f38 MEYE 10 16 3 0 * * * * 0 0 * * * * +0x7f39 MMST 10 24 3 1 * * * 0 0 0 * * * * +0x7f3a NIRO 10 16 3 0 * * * * 0 1 * * * * +0x7f3b MSOL 10 16 3 7 * * MSOG 0 0 1 * * * * +0x7f3c MASL 10 16 3 7 * * MASG 0 0 1 * * * * +0x7f3d MMEL 10 24 3 0 * * * * 0 0 * * * * +0x7f3e MFIG 10 32 5 0 * * * * 0 1 * * * * +0x7f3f MFIG 10 32 5 0 MFIGG1B * * * 0 1 * * * * +0x7f40 MGLM 10 16 3 0 * * * * 0 1 * * * * +0x7f41 MDJL 10 16 3 0 MDJL_E1 MDJL_E2 * * 0 1 * * * * +0x7f42 NIRO 10 16 3 0 NIRO_RD * * * 0 1 * * * * +0x7f43 MOTY 10 24 7 0 MOTY_Y1 MOTY_Y2 * * 0 1 * * * * +0x7f44 NELW 10 16 3 0 * * * * 0 0 * * * * +0x7f45 MSAI 10 16 3 0 * * * 1 0 0 * * * * +0x7f46 MBEG 10 32 5 0 * * * * 0 0 * * * * +0x7f47 MFI2 10 32 5 0 * * * * 0 0 * * * * +0x7f48 MSOF 10 16 3 0 * * * 0 0 1 * * * * +0x7f49 MDMF 10 16 3 0 * * * 0 0 1 * * * * +0x7f4a MDAS 10 16 3 7 * * MDAG 0 0 1 * * * * +0x7f4b MDAF 10 16 3 0 * * * 0 0 1 * * * * +0x7f4c MPLN 10 16 3 7 * * MPLG 0 0 1 * * * * +0x7f4d MPLF 10 16 3 0 * * * 0 0 1 * * * * +0x7f4e MDVM 10 16 3 7 * * MDVG 0 0 1 * * * * +0x7f4f MDVF 10 16 3 0 * * * 0 0 1 * * * * +0x7f50 MMST 10 24 3 1 MMST_NI * * 0 0 0 * * * * +0x7f51 MMST 10 24 3 1 MMST_HA * * 0 0 0 * * * * +0x7f52 NSAI 10 16 3 0 NSAI_G1 NSAI_G2 * * 0 1 * * * * +0x8000 MGNL 4 20 3 0 * * * * * * * * S1 HB +0x8100 MHOB 4 16 3 0 * * * * * * * * S1 BW +0x8200 MKOB 4 16 3 0 * * * * * * * * SS BW +0x9000 MOGR 12 24 5 0 * * * * 1 * * * * * +0xa000 MWYV 13 16 3 0 * * * * 0 * * * * * +0xa100 MCAR 13 32 5 0 * * * * 0 * * * * * +0xa200 MWYV 13 16 3 0 MWYV_WS * * * 0 * * * * * +0xb000 ACOW 14 32 5 0 * * * * 0 * * * * * +0xb100 AHRS 14 32 5 0 * * * * 0 * * * * * +0xb200 NBEGL 14 16 3 0 * * * * 1 * * * * * +0xb210 NPROL 14 16 3 0 * * * * 1 * * * * * +0xb300 NBOYL 14 16 3 0 * * * * 1 * * * * * +0xb310 NGRLL 14 16 3 0 * * * * 1 * * * * * +0xb400 NFAML 14 16 3 0 * * * * 1 * * * * * +0xb410 NFAWL 14 16 3 0 * * * * 1 * * * * * +0xb500 NSIML 14 16 3 0 * * * * 1 * * * * * +0xb510 NSIWL 14 16 3 0 * * * * 1 * * * * * +0xb600 NNOML 14 16 3 0 * * * * 1 * * * * * +0xb610 NNOWL 14 16 3 0 * * * * 1 * * * * * +0xb700 NSLVL 14 16 3 0 * * * * 1 * * * * * +0xc000 ABAT 15 12 3 0 * * * * 0 * * * * * +0xc100 ACAT 15 12 3 0 * * * * 0 * * * * * +0xc200 ACHK 15 12 3 0 * * * * 0 * * * * * +0xc300 ARAT 15 12 3 0 * * * * 0 * * * * * +0xc400 ASQU 15 12 3 0 * * * * 0 * * * * * +0xc500 ABAT 15 12 3 0 * * * * 0 * * * * * +0xc600 NBEGH 15 16 3 0 * * * * 1 * * * * * +0xc610 NPROH 15 16 3 0 * * * * 1 * * * * * +0xc700 NBOYH 15 16 3 0 * * * * 1 * * * * * +0xc710 NGRLH 15 16 3 0 * * * * 1 * * * * * +0xc800 NFAMH 15 16 3 0 * * * * 1 * * * * * +0xc810 NFAWH 15 16 3 0 * * * * 1 * * * * * +0xc900 NSIMH 15 16 3 0 * * * * 1 * * * * * +0xc910 NSIWH 15 16 3 0 * * * * 1 * * * * * +0xca00 NNOMH 15 16 3 0 * * * * 1 * * * * * +0xca10 NNOWH 15 16 3 0 * * * * 1 * * * * * +0xcb00 NSLVH 15 16 3 0 * * * * 1 * * * * * +0xd000 AEAGG1 16 0 3 0 * * * * 0 * * * * * +0xd100 AGULG1 16 0 3 0 * * * * 0 * * * * * +0xd200 AVULG1 16 0 3 0 * * * * 0 * * * * * +0xd300 ABIRG1 16 0 3 0 * * * * 0 * * * * * +0xd400 ABIRG1 16 0 3 0 * * * * 0 * * * * * +0xe000 MCYC 17 48 7 0 * * * * * * * * * * +0xe010 METN 17 48 5 0 * * * * * * * * * * +0xe020 MTAN 17 32 7 0 * * * * * * * * * * +0xe040 MHIS 17 16 3 0 * * * * * * * * * * +0xe050 MLER 17 16 3 0 * * * * * * * * * * +0xe060 MLIC 17 16 3 0 * * * * * * * * * * +0xe070 MMIN 17 24 3 0 * * * * * * * * * * +0xe080 MMUM 17 16 3 0 * * * * * * * * * * +0xe090 MTAN 17 24 3 0 * * * * * * * * * * +0xe0a0 MTIC 17 16 3 0 * * * * * * * * * * +0xe0b0 MTRO 17 16 3 0 * * * * * * * * * * +0xe0c0 MTSN 17 24 3 0 * * * * * * * * * * +0xe0d0 MUMB 17 24 5 0 * * * * * * * * * * +0xe0e0 MCOR 17 24 5 0 * * * * * * * * * * +0xe0f0 MGIC 17 32 5 0 * * * * * * * * * * +0xe0f1 MGLA 17 24 5 0 * * * * * * * * * * +0xe0f2 MWAV 17 16 3 0 * * * 1 * * * * * * +0xe200 MBET 17 24 3 0 * * * * * * * * * * +0xe210 MBFI 17 12 3 0 * * * * * * * * * * +0xe220 MBBM 17 20 3 0 * * * * * * * * * * +0xe230 MBRH 17 64 7 0 * * * * * * * * * * +0xe240 MANI 17 24 3 0 * * * * * * * * * * +0xe241 MAN2 17 24 3 0 * * * * * * * * * * +0xe242 MAN3 17 24 3 0 * * * * * * * * * * +0xe243 MARU 17 16 3 0 * * * * * * * * * * +0xe244 MBA1 17 16 3 0 * * * * * * * * * * +0xe245 MBA2 17 16 3 0 * * * * * * * * * * +0xe246 MBA3 17 16 3 0 * * * * * * * * * * +0xe247 MBA4 17 16 3 0 * * * * * * * * * * +0xe248 MBA5 17 16 3 0 * * * * * * * * * * +0xe249 MBA6 17 16 3 0 * * * * * * * * * * +0xe24a MBAI 17 16 3 0 * * * * * * * * * * +0xe24b MELE 17 32 5 0 * * * * * * * * * * +0xe24c MELF 17 32 5 0 * * * * * * * * * * +0xe24d MELW 17 32 5 0 * * * * * * * * * * +0xe24e MGFI 17 40 5 0 * * * * * * * * * * +0xe24f MGFR 17 40 5 0 * * * * * * * * * * +0xe250 MGIR 17 24 3 0 * * * * * * * * * * +0xe251 MGVE 17 32 5 0 * * * * * * * * * * +0xe252 MHAR 17 16 3 0 * * * * * * * * * * +0xe253 MREM 17 48 5 0 * * * * * * * * * * +0xe254 MSCR 17 24 3 0 * * * * * * * * * * +0xe255 MSEE 17 16 3 0 * * * * * * * * * * +0xe256 MBE1 17 32 3 0 * * * * * * * * * * +0xe257 MBE2 17 16 3 0 * * * * * * * * * * +0xe258 MBE3 17 16 3 0 * * * * * * * * * * +0xe259 MSVI 17 16 3 0 * * * * * * * * * * +0xe25a MSV2 17 16 3 0 * * * * * * * * * * +0xe25b MUM2 17 24 5 0 * * * * * * * * * * +0xe25c MTA2 17 24 3 0 * * * * * * * * * * +0xe25d MYET 17 24 3 0 * * * * * * * * * * +0xe25e MWI4 17 16 3 0 * * * * * * * * * * +0xe25f MDRD 17 24 3 0 * * * * * * * * * * +0xe260 MCRD 17 24 3 0 * * * * * * * * * * +0xe261 MSAH 17 24 3 0 * * * * * * * * * * +0xe262 MSAT 17 32 5 0 * * * * * * * * * * +0xe263 MSV3 17 16 3 0 * * * * * * * * * * +0xe264 MSV4 17 16 3 0 * * * * * * * * * * +0xe265 MBOA 17 24 3 0 * * * * * * * * * * +0xe26b MDTR 17 24 5 0 * * * * * * * * * * +0xe26e MFEY 17 24 3 0 * * * * * * * * * * +0xe270 MGO5 17 16 3 0 * * * * * * * * * * +0xe276 MLEM 17 24 3 0 * * * * * * * * * * +0xe283 MDRM 17 24 5 0 * * * * * * * * * * +0xe288 MKG1 17 16 3 0 * * * * * * * * * * +0xe289 MKG2 17 16 3 0 * * * * * * * * * * +0xe28a MKG3 17 16 3 0 * * * * * * * * * * +0xe28b MABW 17 24 3 0 * * * * * * * * * * +0xe28c MWD2 17 24 5 0 * * * * * * * * * * +0xe300 MGHO 17 16 3 0 * * * 1 * * * * * * +0xe310 MGH2 17 16 3 0 * * * * * * * * * * +0xe320 MGH3 17 16 3 0 * * * * * * * * * * +0xe400 MGO1 17 16 3 0 * * * * * * * * * * +0xe410 MGO2 17 16 3 0 * * * * * * * * * * +0xe420 MGO3 17 16 3 0 * * * * * * * * * * +0xe430 MGO4 17 16 3 0 * * * * * * * * * * +0xe500 MLIZ 17 24 5 0 * * * * * * * * * * +0xe510 MLI2 17 24 5 0 * * * * * * * * * * +0xe520 MLI3 17 32 5 0 * * * * * * * * * * +0xe600 MMYC 17 16 3 0 * * * * * * * * * * +0xe610 MMY2 17 16 3 0 * * * * * * * * * * +0xe700 MNO1 17 16 3 0 * * * * * * * * * * +0xe710 MNO2 17 16 3 0 * * * * * * * * * * +0xe720 MNO3 17 24 5 0 * * * * * * * * * * +0xe800 MOR1 17 16 3 0 * * * * * * * * * * +0xe810 MOR2 17 16 3 0 * * * * * * * * * * +0xe820 MOR3 17 16 3 0 * * * * * * * * * * +0xe830 MOR4 17 16 3 0 * * * * * * * * * * +0xe840 MOR5 17 16 3 0 * * * * * * * * * * +0xe900 MSAL 17 16 3 0 * * * * * * * * * * +0xe910 MSA2 17 16 3 0 * * * * * * * * * * +0xea00 MSHR 17 24 3 0 * * * * * * * * * * +0xea10 MSH1 17 16 3 0 * * * 1 * * * * * * +0xea20 MSH2 17 24 3 0 * * * 1 * * * * * * +0xeb00 MSKT 17 16 3 0 * * * * * * * * * * +0xeb10 MSKA 17 16 3 0 * * * * * * * * * * +0xeb20 MSKB 17 24 3 0 * * * * * * * * * * +0xec00 MWIG 17 16 3 0 * * * * * * * * * * +0xec10 MWI2 17 16 3 0 * * * * * * * * * * +0xec20 MWI3 17 16 3 0 * * * * * * * * * * +0xed00 MYU1 17 16 3 0 * * * * * * * * * * +0xed10 MYU2 17 16 3 0 * * * * * * * * * * +0xed20 MYU3 17 16 3 0 * * * * * * * * * * +0xee00 MZO2 17 16 3 0 * * * * * * * * * * +0xee10 MZO3 17 16 3 0 * * * * * * * * * * +0xef10 MWWE 17 24 3 0 * * * * * * * * * * diff --git a/src/org/infinity/resource/cre/decoder/tables/avatars-iwdhow.2da b/src/org/infinity/resource/cre/decoder/tables/avatars-iwdhow.2da new file mode 100644 index 000000000..9d9223d78 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/tables/avatars-iwdhow.2da @@ -0,0 +1,303 @@ +2DA V1.0 +* + RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD +0x0000 SPRING 0 0 0 0 * * * * 1 0 * * * * +0x0001 SPFLAMES 0 0 0 7 * * * * 0 0 * * * * +0x0100 SPCHUNKS 0 0 0 0 * * SPSHADOW * 1 1 * * * * +0x0200 SPBLOOD 0 0 0 0 * * * * 0 0 0 * * * +0x0210 SPBLOOD 0 0 0 0 * * * * 0 0 1 * * * +0x0220 SPBLOOD 0 0 0 0 * * * * 0 0 2 * * * +0x0230 SPBLOOD 0 0 0 0 * * * * 0 0 3 * * * +0x0240 SPBLOOD 0 0 0 0 * * * * 0 0 4 * * * +0x0300 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * +0x0400 SKLH 0 0 0 0 * * SPSHADOW * 0 1 * * * * +0x0410 GLPHWRDH 0 0 0 1 * * SPSHADOW * 0 1 * * * * +0x0500 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * +0x0510 STNKCLDD 0 0 0 0 * * * * 1 0 0 * * * +0x0600 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * +0x0700 GREASEH 0 0 0 0 * * * * 0 0 * * * * +0x0710 GREASED 0 0 0 0 * * * * 0 1 * * * * +0x0800 WEBENTH 0 0 0 0 * * * * 1 0 * * * * +0x0810 WEBENTD 0 0 0 0 * * * * 1 0 * * * * +0x0900 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * +0x1000 MWYV 1 24 5 0 * * * * 0 * * * * * +0x1001 MWYV 1 24 5 0 * * * * 0 * * * * * +0x2000 MSIR 5 16 3 0 * * * * 1 * 1 * * BW +0x2100 UVOL 5 16 3 0 * * * * 0 * 1 * MS * +0x2200 MOGM 5 24 5 0 * * * * 1 * 1 1 S1 * +0x2300 MDKN 5 24 5 0 * * * * 0 * 1 1 * * +0x3000 MAKH 6 24 5 0 * * * * 0 * * * * * +0x4000 SNOMC 7 16 3 0 * * * * 1 * * * * * +0x4002 SNOMM 7 16 3 0 * * * * 1 * * * * * +0x4010 SNOWC 7 16 3 0 * * * * 1 * * * * * +0x4012 SNOWM 7 16 3 0 * * * * 1 * * * * * +0x4100 SSIMC 7 16 3 0 * * * * 1 * * * * * +0x4101 SSIMS 7 16 3 0 * * * * 1 * * * * * +0x4102 SSIMM 7 16 3 0 * * * * 1 * * * * * +0x4110 SSIWC 7 16 3 0 * * * * 1 * * * * * +0x4112 SSIWM 7 16 3 0 * * * * 1 * * * * * +0x4200 SHMCM 7 16 3 0 * * * * 1 * * * * * +0x4300 MSPLG1 7 32 5 0 * * * * 0 * * * * * +0x4400 LHMC 7 20 3 0 * * * * 1 * * * * * +0x4410 LHFC 7 20 3 0 * * * * 1 * * * * * +0x4500 LFAM 7 20 3 0 * * * * 1 * * * * * +0x4600 LDMF 7 20 3 0 * * * * 1 * * * * * +0x4700 LEMF 7 20 3 0 * * * * 1 * * * * * +0x4710 LEFF 7 20 3 0 * * * * 1 * * * * * +0x4800 LIMC 7 20 3 0 * * * * 1 * * * * * +0x5000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * +0x5001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * +0x5002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * +0x5003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH +0x5010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * +0x5011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * +0x5012 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * +0x5013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * +0x5100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * +0x5101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * +0x5102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * +0x5103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH +0x5110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * +0x5111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * +0x5112 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * +0x5113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * +0x5200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * +0x5201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * +0x5202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * +0x5210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * +0x5211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * +0x5212 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * +0x5300 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * +0x5301 CEMT 8 16 3 0 * * CEMT * 1 1 0 * WQM * +0x5302 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * +0x5303 CIMT 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH +0x5310 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * +0x5311 CEFT 8 16 3 0 * * CEFT * 1 1 0 * WQM * +0x5312 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * +0x5313 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS * +0x6000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * +0x6001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * +0x6002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * +0x6003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH +0x6004 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS WQH +0x6010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * +0x6011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * +0x6012 CIFB 8 16 3 0 * * CIFB * 1 1 1 * WQS * +0x6013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * +0x6014 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS WQH +0x6100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * +0x6101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * +0x6102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * +0x6103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH +0x6104 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS WQH +0x6110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * +0x6111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * +0x6112 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * +0x6113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * +0x6114 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS WQH +0x6200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * +0x6201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * +0x6202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * +0x6204 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH +0x6210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * +0x6211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * +0x6212 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * +0x6214 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH +0x6300 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * +0x6301 CEMT 8 16 3 0 * * CEMT * 1 1 0 * WQM * +0x6302 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * +0x6303 CIMT 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH +0x6304 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS WQH +0x6310 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * +0x6311 CEFT 8 16 3 0 * * CEFT * 1 1 0 * WQM * +0x6312 CIFT 8 16 3 0 * * CIFF * 1 1 0 * WQS * +0x6313 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS * +0x6314 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS WQH +0x6400 UDRZ 9 16 3 0 * * * * 1 * 1 0 WPM * +0x6401 CTES 9 16 3 0 * * * * 1 * 0 0 WPM * +0x6402 CMNK 9 16 3 0 * * * * 1 * 0 * WPM * +0x6403 MSKL 9 16 3 0 * * * * 1 * 1 * WPM * +0x6404 USAR 9 16 3 0 * * * * 0 * 0 0 WPL * +0x6405 MDGU 9 16 3 0 * * * * 1 * 1 * WPM * +0x7000 MOGH 11 16 3 0 * * * * 1 * * * * * +0x7001 MOGN 11 16 3 0 * * * * 1 * * * * * +0x7100 MBAS 11 32 5 0 * * * * 0 * * * * * +0x7101 MBAS 11 32 5 0 MBAS_GR * * * 0 * * * * * +0x7200 MBER 11 24 5 0 MBER_BL * * * 0 * * * * * +0x7201 MBER 11 24 5 0 * * * * 0 * * * * * +0x7202 MBER 11 24 5 0 MBER_CA * * * 0 * * * * * +0x7203 MBER 11 24 5 0 MBER_PO * * * 0 * * * * * +0x7300 MEAE 10 32 5 0 * * * * 0 1 * * * * +0x7302 MEAE 10 32 5 0 MEAE_SH * * * 0 1 * * * * +0x7400 MDOG 11 16 3 0 MDOG_WI * * * 0 * * * * * +0x7401 MDOG 11 16 3 0 MDOG_WA * * * 0 * * * * * +0x7402 MDOG 11 16 3 0 MDOG_MO * * * 0 * * * * * +0x7500 MDOP 11 16 3 0 * * * * 0 * * * * * +0x7501 MDOP 11 16 3 0 MDOP_GR * * * 0 * * * * * +0x7600 METT 11 16 3 0 * * * * 0 * * * * * +0x7701 MGHL 11 16 3 0 MGHL_RE * * * 0 * * * * * +0x7702 MGHL 11 16 3 0 MGHL_GA * * * 0 * * * * * +0x7800 MGIB 11 16 3 0 * * * * 0 * * * * * +0x7900 MSLI 11 24 4 0 MSLI_GR * * 1 0 * * * * * +0x7901 MSLI 11 24 4 0 MSLI_OL * * 1 0 * * * * * +0x7902 MSLI 11 24 4 0 MSLI_MU * * 1 0 * * * * * +0x7903 MSLI 11 24 4 0 MSLI_OC * * 1 0 * * * * * +0x7904 MSLI 11 24 4 0 * * * 1 0 * * * * * +0x7a00 MSPI 11 24 4 0 MSPI_GI * * * 0 * * * * * +0x7a01 MSPI 11 24 4 0 MSPI_HU * * * 0 * * * * * +0x7a02 MSPI 11 24 4 0 MSPI_PH * * * 0 * * * * * +0x7a03 MSPI 11 24 4 0 MSPI_SW * * * 0 * * * * * +0x7a04 MSPI 11 24 4 0 MSPI_WR * * * 0 * * * * * +0x7b00 MWLF 11 16 3 0 * * * * 0 * * * * * +0x7b01 MWLF 11 16 3 0 MWLF_WO * * * 0 * * * * * +0x7b02 MWLF 11 16 3 0 MWLF_DI * * * 0 * * * * * +0x7b03 MWLF 11 16 3 0 MWLF_WI * * * 0 * * * * * +0x7b04 MWLF 11 16 3 0 MWLF_VA * * * 0 * * * * * +0x7b05 MWLF 11 16 3 0 MWLF_DR * * * 0 * * * * * +0x7c00 MXVT 11 16 3 0 * * * * 1 * * * * * +0x7c01 MTAS 11 16 3 0 * * * * 0 * * * * * +0x7d00 MZOM 11 16 3 0 * * * * 1 * * * * * +0x7e00 MWER 11 16 3 0 * * * * 0 * * * * * +0x7e01 MGWE 11 16 3 0 * * * * 0 * * * * * +0x7f00 MTRO 10 16 3 0 * * * * 0 1 * * * * +0x7f01 MMIN 10 16 3 0 * * * * 0 1 * * * * +0x7f02 MBEH 10 32 5 0 * * * * 0 1 * * * * +0x7f03 MIMP 10 16 3 0 * * * * 0 1 * * * * +0x7f06 MDJL 10 16 3 0 * * * * 0 1 * * * * +0x7f10 MRAK 10 16 3 0 * * * * 0 1 * * * * +0x8000 MGNL 4 20 3 0 * * * * * * * * S1 HB +0x8100 MHOB 4 16 3 0 * * * * * * * * S1 BW +0x8200 MKOB 4 16 3 0 * * * * * * * * SS BW +0x9000 MOGR 12 24 5 0 * * * * 1 * * * * * +0xa000 MWYV 13 16 3 0 * * * * 0 * * * * * +0xa100 MCAR 13 32 5 0 * * * * 0 * * * * * +0xb000 ACOW 14 32 5 0 * * * * 0 * * * * * +0xb100 AHRS 14 32 5 0 * * * * 0 * * * * * +0xb200 NBEGL 14 16 3 0 * * * * 1 * * * * * +0xb210 NPROL 14 16 3 0 * * * * 1 * * * * * +0xb300 NBOYL 14 16 3 0 * * * * 1 * * * * * +0xb310 NGRLL 14 16 3 0 * * * * 1 * * * * * +0xb400 NFAML 14 16 3 0 * * * * 1 * * * * * +0xb410 NFAWL 14 16 3 0 * * * * 1 * * * * * +0xb500 NSIML 14 16 3 0 * * * * 1 * * * * * +0xb510 NSIWL 14 16 3 0 * * * * 1 * * * * * +0xb600 NNOML 14 16 3 0 * * * * 1 * * * * * +0xb610 NNOWL 14 16 3 0 * * * * 1 * * * * * +0xb700 NSLVL 14 16 3 0 * * * * 1 * * * * * +0xc000 ABAT 15 12 3 0 * * * * 0 * * * * * +0xc100 ACAT 15 12 3 0 * * * * 0 * * * * * +0xc200 ACHK 15 12 3 0 * * * * 0 * * * * * +0xc300 ARAT 15 12 3 0 * * * * 0 * * * * * +0xc400 ASQU 15 12 3 0 * * * * 0 * * * * * +0xc500 ABAT 15 12 3 0 * * * * 0 * * * * * +0xc600 NBEGH 15 16 3 0 * * * * 1 * * * * * +0xc610 NPROH 15 16 3 0 * * * * 1 * * * * * +0xc700 NBOYH 15 16 3 0 * * * * 1 * * * * * +0xc710 NGRLH 15 16 3 0 * * * * 1 * * * * * +0xc800 NFAMH 15 16 3 0 * * * * 1 * * * * * +0xc810 NFAWH 15 16 3 0 * * * * 1 * * * * * +0xc900 NSIMH 15 16 3 0 * * * * 1 * * * * * +0xc910 NSIWH 15 16 3 0 * * * * 1 * * * * * +0xca00 NNOMH 15 16 3 0 * * * * 1 * * * * * +0xca10 NNOWH 15 16 3 0 * * * * 1 * * * * * +0xcb00 NSLVH 15 16 3 0 * * * * 1 * * * * * +0xd000 AEAGG1 16 0 3 0 * * * * 0 * * * * * +0xd100 AGULG1 16 0 3 0 * * * * 0 * * * * * +0xd200 AVULG1 16 0 3 0 * * * * 0 * * * * * +0xd300 ABIRG1 16 0 3 0 * * * * 0 * * * * * +0xd400 ABIRG1 16 0 3 0 * * * * 0 * * * * * +0xe000_000f MBET 17 24 3 0 * * * * * * * * * * +0xe010_000f MBBM 17 20 3 0 * * * * * * * * * * +0xe020_000f MBBR 17 16 3 0 * * * * * * * * * * +0xe030_000f MBFI 17 12 3 0 * * * * * * * * * * +0xe040_000f MBRH 17 64 7 0 * * * * * * * * * * +0xe050_000f MREM 17 48 5 0 * * * * * * * * * * +0xe100_000f MCYC 17 48 7 0 * * * * * * * * * * +0xe110_000f METN 17 48 5 0 * * * * * * * * * * +0xe130_000f MGFR 17 40 5 0 * * * * * * * * * * +0xe140_000f MGVE 17 32 5 0 * * * * * * * * * * +0xe210_000f MELE 17 32 5 0 * * * * * * * * * * +0xe220_000f MELF 17 32 5 0 * * * * * * * * * * +0xe230_000f MELW 17 32 5 0 * * * * * * * * * * +0xe240_000f MHAR 17 16 3 0 * * * * * * * * * * +0xe250_000f MWWE 17 24 3 0 * * * * * * * * * * +0xe300_000f MGH2 17 16 3 0 * * * * * * * * * * +0xe310_000f MGH3 17 16 3 0 * * * * * * * * * * +0xe320_000f MWIG 17 16 3 0 * * * * * * * * * * +0xe330_000f MZO2 17 16 3 0 * * * * * * * * * * +0xe340_000f MWI2 17 16 3 0 * * * * * * * * * * +0xe350_000f MZO3 17 16 3 0 * * * * * * * * * * +0xe360_000f MWI3 17 16 3 0 * * * * * * * * * * +0xe380_000f MMUM 17 16 3 0 * * * * * * * * * * +0xe390_000f MHIS 17 16 3 0 * * * * * * * * * * +0xe3a0_000f MDRD 17 24 3 0 * * * * * * * * * * +0xe3b0_000f MWAV 17 16 3 0 * * * 1 * * * * * * +0xe400_000f MGO1 17 16 3 0 * * * * * * * * * * +0xe410_000f MGO2 17 16 3 0 * * * * * * * * * * +0xe420_000f MGO3 17 16 3 0 * * * * * * * * * * +0xe430_000f MGO4 17 16 3 0 * * * * * * * * * * +0xe440_000f MSVI 17 16 3 0 * * * * * * * * * * +0xe450_000f MSV2 17 16 3 0 * * * * * * * * * * +0xe510_000f MGIR 17 24 3 0 * * * * * * * * * * +0xe520_000f MGIC 17 32 5 0 * * * * * * * * * * +0xe620_000f MSKB 17 24 3 0 * * * * * * * * * * +0xe700_000f MMIN 17 24 3 0 * * * * * * * * * * +0xe710_000f MTRO 17 16 3 0 * * * * * * * * * * +0xe720_000f MTIC 17 16 3 0 * * * * * * * * * * +0xe730_000f MTSN 17 24 3 0 * * * * * * * * * * +0xe750_000f MUMB 17 24 5 0 * * * * * * * * * * +0xe760_000f MYET 17 24 3 0 * * * * * * * * * * +0xe770_000f MBA4 17 16 3 0 * * * * * * * * * * +0xe780_000f MBA5 17 16 3 0 * * * * * * * * * * +0xe790_000f MBA6 17 16 3 0 * * * * * * * * * * +0xe7a0_000f MBAI 17 16 3 0 * * * * * * * * * * +0xe7e0_000f MSCR 17 24 3 0 * * * * * * * * * * +0xe7f0_000f MUM2 17 24 5 0 * * * * * * * * * * +0xe810_000f MOR1 17 16 3 0 * * * * * * * * * * +0xe820_000f MOR2 17 16 3 0 * * * * * * * * * * +0xe830_000f MOR3 17 16 3 0 * * * * * * * * * * +0xe840_000f MOR4 17 16 3 0 * * * * * * * * * * +0xe850_000f MOR5 17 16 3 0 * * * * * * * * * * +0xe860_000f MNO1 17 16 3 0 * * * * * * * * * * +0xe870_000f MNO2 17 16 3 0 * * * * * * * * * * +0xe880_000f MNO3 17 24 5 0 * * * * * * * * * * +0xe890_000f MLI3 17 32 5 0 * * * * * * * * * * +0xe8a0_000f MYU3 17 16 3 0 * * * * * * * * * * +0xe900_000f MSH1 17 16 3 0 * * * 1 * * * * * * +0xe910_000f MSH2 17 24 3 0 * * * 1 * * * * * * +0xe920_000f MGHO 17 16 3 0 * * * 1 * * * * * * +0xea20_000f MCRD 17 24 3 0 * * * * * * * * * * +0xeb00_000f MANI 17 24 3 0 * * * * * * * * * * +0xeb10_000f MAN2 17 24 3 0 * * * * * * * * * * +0xeb20_000f MAN3 17 24 3 0 * * * * * * * * * * +0xeb30_000f MBE1 17 32 3 0 * * * * * * * * * * +0xeb40_000f MBE2 17 16 3 0 * * * * * * * * * * +0xeb51 MSEE 17 16 3 0 * * * * * * * * * * +0xeb52 MFIR 17 16 3 0 * * * * * * * * * * +0xeb60_000f MLIC 17 16 3 0 * * * * * * * * * * +0xeb70_000f MLER 17 16 3 0 * * * * * * * * * * +0xeb80_000f MMAN 17 32 5 0 * * * * * * * * * * +0xeb90_000f MMYC 17 16 3 0 * * * * * * * * * * +0xeba0_000f MMY2 17 16 3 0 * * * * * * * * * * +0xebb0_000f MSHR 17 24 3 0 * * * * * * * * * * +0xebc0_000f MTAN 17 24 3 0 * * * * * * * * * * +0xebd0_000f MSAL 17 16 3 0 * * * * * * * * * * +0xebe0_000f MSA2 17 16 3 0 * * * * * * * * * * +0xebf1_000f MARU 17 16 3 0 * * * * * * * * * * +0xec00_000f MWDR 17 72 7 0 * * * * * * * * * * +0xec10_000f MCHY 17 16 3 0 * * * * * * * * * * +0xed00_000f MCOR 17 24 5 0 * * * * * * * * * * +0xed10_000f MGLA 17 24 5 0 * * * * * * * * * * +0xf000_000f MSKA 17 16 3 0 * * * * * * * * * * +0xf010_000f MSKT 17 16 3 0 * * * * * * * * * * +0xf020_000f MWI4 17 16 3 0 * * * * * * * * * * +0xf100_000f MYU1 17 16 3 0 * * * * * * * * * * +0xf110_000f MYU2 17 16 3 0 * * * * * * * * * * +0xf200_000f MLIZ 17 24 5 0 * * * * * * * * * * +0xf210_000f MLI2 17 24 5 0 * * * * * * * * * * +0xf300_000f MGFI 17 40 5 0 * * * * * * * * * * +0xf400_000f MSAH 17 24 3 0 * * * * * * * * * * +0xf410_000f MSAT 17 32 5 0 * * * * * * * * * * +0xf770_000f MBA1 17 16 3 0 * * * * * * * * * * +0xf780_000f MBA2 17 16 3 0 * * * * * * * * * * +0xf790_000f MBA3 17 16 3 0 * * * * * * * * * * diff --git a/src/org/infinity/resource/cre/decoder/tables/avatars-pst.2da b/src/org/infinity/resource/cre/decoder/tables/avatars-pst.2da new file mode 100644 index 000000000..924cfbfc2 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/tables/avatars-pst.2da @@ -0,0 +1,110 @@ +2DA V1.0 +* + RESREF RESREF2 TYPE ELLIPSE SPACE CLOWN ARMOR BESTIARY +0x0000 cabsb * 18 20 4 * 5 0 +0x0001 cadvb * 18 16 3 * 0 3 +0x0002 dannb * 18 16 3 1 0 4 +0x0003 cbaub * 18 16 3 * * 69 +0x0004 ccamb * 18 22 5 * 2 6 +0x0005 ccolb * 18 16 3 * 0 7 +0x0006 dcthb * 18 16 3 1 1 78 +0x0007 ccorb * 18 20 4 * 5 8 +0x0008 ccrnb * 18 12 3 * 5 9 +0x0009 ccgdb * 18 16 3 * 2 10 +0x000a dctfb * 18 16 3 1 0 85 +0x000b dctmb * 18 16 3 1 0 11 +0x000c cdabb * 18 16 3 * 0 12 +0x000d ddkkb * 18 16 3 1 1 13 +0x000e cdeib * 18 16 3 * 6 14 +0x000f cdhlb * 18 20 4 * 0 15 +0x0010 dhkmb * 18 20 4 1 2 16 +0x0011 clkmb * 18 16 5 * 2 17 +0x0012 cwizb * 18 16 3 * 0 18 +0x0013 ddtfb * 18 16 3 1 0 86 +0x0014 ddtmb * 18 16 3 1 0 19 +0x0015 cebbb * 18 16 3 * 2 20 +0x0016 cffgb * 18 16 3 * 0 21 +0x0017 cfhjb * 18 20 4 * 5 23 +0x0018 cgarb * 18 16 3 * 5 24 +0x0019 dghfb * 18 16 3 1 0 87 +0x001a dghmb * 18 16 3 1 0 25 +0x001b cgstb * 18 16 3 * 3 26 +0x001c cacab * 18 16 3 * 0 27 +0x001d dgytb * 18 16 3 1 1 28 +0x001e cglab * 18 30 5 * 5 29 +0x001f dgodb * 18 16 3 1 1 30 +0x0020 cgdgb * 18 16 3 * 2 31 +0x0021 cgrib * 18 16 3 * 5 32 +0x0022 cgrkb * 18 30 5 * 5 33 +0x0023 chrsb * 18 16 5 * 5 34 +0x0024 cigyb * 18 16 3 * 10 35 +0x0025 cjbrb * 18 16 3 * 5 36 +0x0026 cjftb * 18 16 3 * 5 37 +0x0027 clopb * 18 30 5 * 0 38 +0x0028 clemb * 18 20 3 * 5 39 +0x0029 clthb * 18 16 3 * 0 40 +0x002a dmhtb * 18 16 3 1 0 74 +0x002b cmklb * 18 16 3 * 2 41 +0x002c dmidb * 18 16 3 * 0 75 +0x002d cmdrb * 18 16 3 * 7 42 +0x002e cmrtb * 18 16 3 * 4 43 +0x002f dnoab * 18 16 3 * 8 76 +0x0030 dnocb * 18 16 3 * 8 76 +0x0031 dnodb * 18 16 3 * 8 76 +0x0032 dnofb * 18 16 3 * 8 76 +0x0033 dnohb * 18 16 3 * 8 76 +0x0034 cndmb * 18 16 3 * 7 44 +0x0035 cnprb * 18 16 3 * 5 45 +0x0036 cphdb * 18 16 3 * 0 46 +0x0037 dprob * 18 16 3 1 0 47 +0x0038 cpuzb * 18 16 3 * 0 48 +0x0039 csm1b * 18 16 3 * 9 49 +0x003a carmb * 18 20 4 * 2 52 +0x003b dsptb * 18 16 3 1 0 53 +0x003c dskwb * 18 16 3 1 4 54 +0x003d dhlfb * 18 16 3 1 0 72 +0x003e dhlmb * 18 16 3 1 0 73 +0x003f dhufb * 18 16 3 1 0 80 +0x0040 dhumb * 18 16 3 1 0 81 +0x0041 ctrzb * 18 20 4 * 6 55 +0x0042 ctgtb * 18 16 3 * 5 56 +0x0043 ctreb * 18 16 3 * 5 57 +0x0044 ctrsb * 18 16 3 * 5 58 +0x0045 ctrcb * 18 32 5 * 5 59 +0x0046 cvgyb * 18 16 3 * 5 60 +0x0047 dvhab * 18 16 3 1 2 61 +0x0048 dwreb * 18 16 3 1 0 62 +0x0049 cwrmb * 18 16 3 * 1 63 +0x004a cwurb * 18 16 3 * 5 64 +0x004b dzmfb * 18 16 3 1 5 83 +0x004c dzmmb * 18 16 3 1 5 65 +0x004d cfelb * 18 16 3 * 0 22 +0x004e csm2b * 18 16 3 * 9 50 +0x004f csm3b * 18 22 5 * 9 51 +0x0050 dthkb * 18 22 5 1 5 5 +0x0051 dnosb * 18 16 3 * 8 76 +0x0052 dnozb * 18 16 3 * 5 76 +0x0053 dtffb * 18 16 3 1 0 84 +0x0054 dtlrb * 18 16 3 1 1 77 +0x0055 dtfmb * 18 16 3 1 1 79 +0x0056 dbaub * 18 16 3 1 1 69 +0x0057 charb * 18 16 5 * 2 71 +0x0058 dmidb * 18 16 3 1 0 75 +0x0059 daafb * 18 16 3 1 0 70 +0x005a cgabb * 18 20 4 * 5 1 +0x005b crabb * 18 20 4 * 5 2 +0x005c dtwzb * 18 16 3 1 0 82 +0x005d dnomb * 18 16 3 * 0 19 +0x005e cuhdb * 18 16 3 * 3 * +0x005f dquib * 18 16 3 1 3 * +0x0060 ctrdb * 18 16 5 * 5 58 +0x0061 dnofb * 18 16 3 * 8 76 +0x0062 dvhstdb * 18 16 3 1 2 * +0x0063 dctmb * 18 16 3 1 0 * +0x0064 daafb * 18 16 3 1 0 80 +0x0066 dctfb * 18 16 3 1 0 * +0x022e carmb * 18 16 3 * 2 52 +0x080e cdeib * 18 16 3 * 6 14 +0x2000 GEARS1 GEARS2 18 60 13 * * * +0x3000 IGHEAD IGARM 18 15 13 * * 66 +0xf000 POSMAIN POSSHAD 18 60 13 * * 67 diff --git a/src/org/infinity/resource/cre/decoder/tables/avatars-pstee.2da b/src/org/infinity/resource/cre/decoder/tables/avatars-pstee.2da new file mode 100644 index 000000000..85ffcb27a --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/tables/avatars-pstee.2da @@ -0,0 +1,105 @@ +2DA V1.0 +* + RESREF RESREF2 TYPE ELLIPSE SPACE CLOWN ARMOR BESTIARY +0x0000 cabsb * 18 20 4 * 5 0 +0x0001 cadvb * 18 16 3 * 0 3 +0x0002 dannb * 18 16 3 1 0 4 +0x0003 cbaub * 18 16 3 * * 69 +0x0004 ccamb * 18 22 5 * 2 6 +0x0005 ccolb * 18 16 3 * 0 7 +0x0006 dcthb * 18 16 3 1 1 78 +0x0007 ccorb * 18 20 4 * 5 8 +0x0008 ccrnb * 18 12 3 * 5 9 +0x0009 ccgdb * 18 16 3 * 2 10 +0x000a dctfb * 18 16 3 1 0 85 +0x000b dctmb * 18 16 3 1 0 11 +0x000c cdabb * 18 16 3 * 0 12 +0x000d ddkkb * 18 16 3 1 1 13 +0x000e cdeib * 18 16 3 * 6 14 +0x000f cdhlb * 18 20 4 * 0 15 +0x0010 dhkmb * 18 20 4 1 2 16 +0x0011 clkmb * 18 16 5 * 2 17 +0x0012 cwizb * 18 16 3 * 0 18 +0x0013 ddtfb * 18 16 3 1 0 86 +0x0014 ddtmb * 18 16 3 1 0 19 +0x0015 cebbb * 18 16 3 * 2 20 +0x0016 cffgb * 18 16 3 * 0 21 +0x0017 cfhjb * 18 20 4 * 5 23 +0x0018 cgarb * 18 16 3 * 5 24 +0x0019 dghfb * 18 16 3 1 0 87 +0x001a dghmb * 18 16 3 1 0 25 +0x001b cgstb * 18 16 3 * 3 26 +0x001c cacab * 18 16 3 * 0 27 +0x001d dgytb * 18 16 3 1 1 28 +0x001e cglab * 18 30 5 * 5 29 +0x001f dgodb * 18 16 3 1 1 30 +0x0020 cgdgb * 18 16 3 * 2 31 +0x0021 cgrib * 18 16 3 * 5 32 +0x0022 cgrkb * 18 30 5 * 5 33 +0x0023 chrsb * 18 16 5 * 5 34 +0x0024 cigyb * 18 16 3 * 10 35 +0x0025 cjbrb * 18 16 3 * 5 36 +0x0026 cjftb * 18 16 3 * 5 37 +0x0027 clopb * 18 30 5 * 0 38 +0x0028 clemb * 18 20 3 * 5 39 +0x0029 clthb * 18 16 3 * 0 40 +0x002a dmhtb * 18 16 3 1 0 74 +0x002b cmklb * 18 16 3 * 2 41 +0x002c dmidb * 18 16 3 * 0 75 +0x002d cmdrb * 18 16 3 * 7 42 +0x002e cmrtb * 18 16 3 * 4 43 +0x002f dnoab * 18 16 3 * 8 76 +0x0030 dnocb * 18 16 3 * 8 76 +0x0031 dnodb * 18 16 3 * 8 76 +0x0032 dnofb * 18 16 3 * 8 76 +0x0033 dnohb * 18 16 3 * 8 76 +0x0034 cndmb * 18 16 3 * 7 44 +0x0035 cnprb * 18 16 3 * 5 45 +0x0036 cphdb * 18 16 3 * 0 46 +0x0037 dprob * 18 16 3 1 0 47 +0x0038 cpuzb * 18 16 3 * 0 48 +0x0039 csm1b * 18 16 3 * 9 49 +0x003a carmb * 18 20 4 * 2 52 +0x003b dsptb * 18 16 3 1 0 53 +0x003c dskwb * 18 16 3 1 4 54 +0x003d dhlfb * 18 16 3 1 0 72 +0x003e dhlmb * 18 16 3 1 0 73 +0x003f dhufb * 18 16 3 1 0 80 +0x0040 dhumb * 18 16 3 1 0 81 +0x0041 ctrzb * 18 20 4 * 6 55 +0x0042 ctgtb * 18 16 3 * 5 56 +0x0043 ctreb * 18 16 3 * 5 57 +0x0044 ctrsb * 18 16 3 * 5 58 +0x0045 ctrcb * 18 32 5 * 5 59 +0x0046 cvgyb * 18 16 3 * 5 60 +0x0047 dvhab * 18 16 3 1 2 61 +0x0048 dwreb * 18 16 3 1 0 62 +0x0049 cwrmb * 18 16 3 * 1 63 +0x004a cwurb * 18 16 3 * 5 64 +0x004b dzmfb * 18 16 3 1 5 83 +0x004c dzmmb * 18 16 3 1 5 65 +0x004d cfelb * 18 16 3 * 0 22 +0x004e csm2b * 18 16 3 * 9 50 +0x004f csm3b * 18 22 5 * 9 51 +0x0050 dthkb * 18 22 5 1 5 5 +0x0051 dnosb * 18 16 3 * 8 76 +0x0052 dnozb * 18 16 3 * 5 76 +0x0053 dtffb * 18 16 3 1 0 84 +0x0054 dtlrb * 18 16 3 1 1 77 +0x0055 dtfmb * 18 16 3 1 1 79 +0x0056 dbaub * 18 16 3 1 1 69 +0x0057 charb * 18 16 5 * 2 71 +0x0058 dmidb * 18 16 3 1 0 75 +0x0059 daafb * 18 16 3 1 0 70 +0x005a cgabb * 18 20 4 * 5 1 +0x005b crabb * 18 20 4 * 5 2 +0x005c dtwzb * 18 16 3 1 0 82 +0x005d dnomb * 18 16 3 * 0 19 +0x005e cuhdb * 18 16 3 * 3 * +0x005f dquib * 18 16 3 1 3 * +0x0060 ctrdb * 18 16 5 * 5 58 +0x0061 dnofb * 18 16 3 * 8 76 +0x0062 dvhstdb * 18 16 3 1 2 * +0x2000 GEARS1 GEARS2 18 60 13 * * * +0xf100 POSMAIN POSSHAD 18 60 13 * * 67 +0xf101 IGHEAD IGARM 18 15 13 * * 66 diff --git a/src/org/infinity/resource/cre/decoder/tables/infinityanimations-v5.ids b/src/org/infinity/resource/cre/decoder/tables/infinityanimations-v5.ids new file mode 100644 index 000000000..13e5ac87c --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/tables/infinityanimations-v5.ids @@ -0,0 +1,8724 @@ +IDS +0x1001 ÆAA EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1002 ÆAB EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1003 ÆAC EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1004 ÆAD EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1005 ÆAE EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1006 ÆAF EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1007 ÆAG EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1008 ÆAH EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1009 ÆAI EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x100a ÆAJ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x100b ÆAK EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x100c ÆAL EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x100d ÆAM EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x100e ÆAN EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x100f ÆAO EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1010 ÆAP EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1011 ÆAQ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1012 ÆAR EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1013 ÆAS EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1014 ÆAT EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1015 ÆAU EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1016 ÆAV EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1017 ÆAW EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1018 ÆAX EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1019 ÆAY EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x101a ÆAZ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x101b ÆBA EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x101c ÆBB EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x101d ÆBC EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x101e ÆBD EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x101f ÆBE EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1020 ÆBF EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1021 ÆBG EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1022 ÆBH EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1023 ÆBI EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1024 ÆBJ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1025 ÆBK EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1026 ÆBL EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1027 ÆBM EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1028 ÆBN EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1029 ÆBO EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x102a ÆBP EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x102b ÆBQ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x102c ÆBR EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x102d ÆBS EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x102e ÆBT EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x102f ÆBU EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1030 ÆBV EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1031 ÆBW EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1032 ÆBX EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1033 ÆBY EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1034 ÆBZ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1035 ÆCA EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1036 ÆCB EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1037 ÆCC EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1038 ÆCD EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1039 ÆCE EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x103a ÆCF EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x103b ÆCG EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x103c ÆCH EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x103d ÆCI EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x103e ÆCJ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x103f ÆCK EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1040 ÆCL EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1041 ÆCM EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1042 ÆCN EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1043 ÆCO EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1044 ÆCP EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1045 ÆCQ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1046 ÆCR EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1047 ÆCS EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1048 ÆCT EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1049 ÆCU EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x104a ÆCV EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x104b ÆCW EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x104c ÆCX EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x104d ÆCY EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x104e ÆCZ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x104f ÆDA EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1050 ÆDB EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1051 ÆDC EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1052 ÆDD EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1053 ÆDE EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1054 ÆDF EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1055 ÆDG EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1056 ÆDH EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1057 ÆDI EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1058 ÆDJ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1059 ÆDK EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x105a ÆDL EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x105b ÆDM EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x105c ÆDN EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x105d ÆDO EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x105e ÆDP EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x105f ÆDQ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1060 ÆDR EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1061 ÆDS EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1062 ÆDT EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1063 ÆDU EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1064 ÆDV EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1065 ÆDW EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1066 ÆDX EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1067 ÆDY EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1068 ÆDZ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1069 ÆEA EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x106a ÆEB EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x106b ÆEC EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x106c ÆED EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x106d ÆEE EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x106e ÆEF EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x106f ÆEG EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1070 ÆEH EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1071 ÆEI EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1072 ÆEJ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1073 ÆEK EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1074 ÆEL EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1075 ÆEM EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1076 ÆEN EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1077 ÆEO EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1078 ÆEP EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1079 ÆEQ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x107a ÆER EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x107b ÆES EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x107c ÆET EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x107d ÆEU EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x107e ÆEV EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x107f ÆEW EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1080 ÆEX EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1081 ÆEY EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1082 ÆEZ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1083 ÆFA EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1084 ÆFB EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1085 ÆFC EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1086 ÆFD EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1087 ÆFE EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1088 ÆFF EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1089 ÆFG EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x108a ÆFH EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x108b ÆFI EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x108c ÆFJ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x108d ÆFK EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x108e ÆFL EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x108f ÆFM EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1090 ÆFN EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1091 ÆFO EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1092 ÆFP EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1093 ÆFQ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1094 ÆFR EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1095 ÆFS EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1096 ÆFT EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1097 ÆFU EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1098 ÆFV EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1099 ÆFW EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x109a ÆFX EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x109b ÆFY EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x109c ÆFZ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x109d ÆGA EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x109e ÆGB EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x109f ÆGC EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10a0 ÆGD EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10a1 ÆGE EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10a2 ÆGF EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10a3 ÆGG EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10a4 ÆGH EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10a5 ÆGI EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10a6 ÆGJ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10a7 ÆGK EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10a8 ÆGL EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10a9 ÆGM EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10aa ÆGN EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10ab ÆGO EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10ac ÆGP EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10ad ÆGQ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10ae ÆGR EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10af ÆGS EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10b0 ÆGT EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10b1 ÆGU EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10b2 ÆGV EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10b3 ÆGW EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10b4 ÆGX EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10b5 ÆGY EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10b6 ÆGZ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10b7 ÆHA EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10b8 ÆHB EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10b9 ÆHC EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10ba ÆHD EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10bb ÆHE EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10bc ÆHF EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10bd ÆHG EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10be ÆHH EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10bf ÆHI EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10c0 ÆHJ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10c1 ÆHK EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10c2 ÆHL EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10c3 ÆHM EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10c4 ÆHN EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10c5 ÆHO EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10c6 ÆHP EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10c7 ÆHQ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10c8 ÆHR EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10c9 ÆHS EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10ca ÆHT EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10cb ÆHU EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10cc ÆHV EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10cd ÆHW EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10ce ÆHX EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10cf ÆHY EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10d0 ÆHZ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10d1 ÆIA EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10d2 ÆIB EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10d3 ÆIC EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10d4 ÆID EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10d5 ÆIE EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10d6 ÆIF EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10d7 ÆIG EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10d8 ÆIH EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10d9 ÆII EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10da ÆIJ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10db ÆIK EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10dc ÆIL EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10dd ÆIM EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10de ÆIN EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10df ÆIO EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10e0 ÆIP EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10e1 ÆIQ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10e2 ÆIR EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10e3 ÆIS EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10e4 ÆIT EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10e5 ÆIU EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10e6 ÆIV EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10e7 ÆIW EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10e8 ÆIX EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10e9 ÆIY EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10ea ÆIZ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10eb ÆJA EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10ec ÆJB EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10ed ÆJC EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10ee ÆJD EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10ef ÆJE EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10f0 ÆJF EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10f1 ÆJG EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10f2 ÆJH EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10f3 ÆJI EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10f4 ÆJJ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10f5 ÆJK EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10f6 ÆJL EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10f7 ÆJM EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10f8 ÆJN EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10f9 ÆJO EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10fa ÆJP EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10fb ÆJQ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10fc ÆJR EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10fd ÆJS EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10fe ÆJT EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10ff ÆJU EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1101 ÆJW EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1102 ÆJX EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1103 ÆJY EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1104 ÆJZ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1105 ÆKA EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1106 ÆKB EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1107 ÆKC EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1108 ÆKD EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1109 ÆKE EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x110a ÆKF EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x110b ÆKG EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x110c ÆKH EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x110d ÆKI EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x110e ÆKJ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x110f ÆKK EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1110 ÆKL EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1111 ÆKM EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1112 ÆKN EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1113 ÆKO EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1114 ÆKP EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1115 ÆKQ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1116 ÆKR EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1117 ÆKS EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1118 ÆKT EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1119 ÆKU EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x111a ÆKV EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x111b ÆKW EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x111c ÆKX EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x111d ÆKY EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x111e ÆKZ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x111f ÆLA EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1120 ÆLB EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1121 ÆLC EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1122 ÆLD EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1123 ÆLE EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1124 ÆLF EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1125 ÆLG EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1126 ÆLH EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1127 ÆLI EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1128 ÆLJ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1129 ÆLK EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x112a ÆLL EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x112b ÆLM EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x112c ÆLN EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x112d ÆLO EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x112e ÆLP EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x112f ÆLQ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1130 ÆLR EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1131 ÆLS EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1132 ÆLT EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1133 ÆLU EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1134 ÆLV EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1135 ÆLW EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1136 ÆLX EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1137 ÆLY EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1138 ÆLZ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1139 ÆMA EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x113a ÆMB EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x113b ÆMC EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x113c ÆMD EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x113d ÆME EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x113e ÆMF EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x113f ÆMG EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1140 ÆMH EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1141 ÆMI EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1142 ÆMJ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1143 ÆMK EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1144 ÆML EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1145 ÆMM EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1146 ÆMN EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1147 ÆMO EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1148 ÆMP EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1149 ÆMQ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x114a ÆMR EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x114b ÆMS EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x114c ÆMT EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x114d ÆMU EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x114e ÆMV EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x114f ÆMW EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1150 ÆMX EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1151 ÆMY EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1152 ÆMZ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1153 ÆNA EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1154 ÆNB EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1155 ÆNC EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1156 ÆND EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1157 ÆNE EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1158 ÆNF EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1159 ÆNG EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x115a ÆNH EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x115b ÆNI EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x115c ÆNJ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x115d ÆNK EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x115e ÆNL EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x115f ÆNM EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1160 ÆNN EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1161 ÆNO EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1162 ÆNP EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1163 ÆNQ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1164 ÆNR EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1165 ÆNS EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1166 ÆNT EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1167 ÆNU EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1168 ÆNV EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1169 ÆNW EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x116a ÆNX EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x116b ÆNY EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x116c ÆNZ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x116d ÆOA EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x116e ÆOB EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x116f ÆOC EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1170 ÆOD EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1171 ÆOE EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1172 ÆOF EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1173 ÆOG EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1174 ÆOH EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1175 ÆOI EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1176 ÆOJ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1177 ÆOK EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1178 ÆOL EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1179 ÆOM EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x117a ÆON EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x117b ÆOO EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x117c ÆOP EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x117d ÆOQ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x117e ÆOR EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x117f ÆOS EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1180 ÆOT EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1181 ÆOU EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1182 ÆOV EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1183 ÆOW EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1184 ÆOX EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1185 ÆOY EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1186 ÆOZ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1187 ÆPA EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1188 ÆPB EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1189 ÆPC EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x118a ÆPD EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x118b ÆPE EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x118c ÆPF EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x118d ÆPG EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x118e ÆPH EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x118f ÆPI EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1190 ÆPJ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1191 ÆPK EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1192 ÆPL EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1193 ÆPM EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1194 ÆPN EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1195 ÆPO EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1196 ÆPP EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1197 ÆPQ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1198 ÆPR EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1199 ÆPS EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x119a ÆPT EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x119b ÆPU EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x119c ÆPV EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x119d ÆPW EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x119e ÆPX EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x119f ÆPY EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11a0 ÆPZ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11a1 ÆQA EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11a2 ÆQB EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11a3 ÆQC EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11a4 ÆQD EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11a5 ÆQE EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11a6 ÆQF EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11a7 ÆQG EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11a8 ÆQH EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11a9 ÆQI EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11aa ÆQJ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11ab ÆQK EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11ac ÆQL EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11ad ÆQM EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11ae ÆQN EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11af ÆQO EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11b0 ÆQP EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11b1 ÆQQ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11b2 ÆQR EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11b3 ÆQS EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11b4 ÆQT EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11b5 ÆQU EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11b6 ÆQV EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11b7 ÆQW EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11b8 ÆQX EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11b9 ÆQY EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11ba ÆQZ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11bb ÆRA EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11bc ÆRB EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11bd ÆRC EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11be ÆRD EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11bf ÆRE EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11c0 ÆRF EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11c1 ÆRG EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11c2 ÆRH EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11c3 ÆRI EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11c4 ÆRJ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11c5 ÆRK EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11c6 ÆRL EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11c7 ÆRM EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11c8 ÆRN EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11c9 ÆRO EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11ca ÆRP EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11cb ÆRQ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11cc ÆRR EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11cd ÆRS EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11ce ÆRT EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11cf ÆRU EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11d0 ÆRV EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11d1 ÆRW EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11d2 ÆRX EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11d3 ÆRY EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11d4 ÆRZ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11d5 ÆSA EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11d6 ÆSB EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11d7 ÆSC EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11d8 ÆSD EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11d9 ÆSE EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11da ÆSF EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11db ÆSG EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11dc ÆSH EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11dd ÆSI EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11de ÆSJ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11df ÆSK EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11e0 ÆSL EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11e1 ÆSM EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11e2 ÆSN EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11e3 ÆSO EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11e4 ÆSP EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11e5 ÆSQ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11e6 ÆSR EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11e7 ÆSS EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11e8 ÆST EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11e9 ÆSU EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11ea ÆSV EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11eb ÆSW EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11ec ÆSX EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11ed ÆSY EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11ee ÆSZ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11ef ÆTA EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11f0 ÆTB EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11f1 ÆTC EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11f2 ÆTD EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11f3 ÆTE EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11f4 ÆTF EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11f5 ÆTG EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11f6 ÆTH EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11f7 ÆTI EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11f8 ÆTJ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11f9 ÆTK EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11fa ÆTL EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11fb ÆTM EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11fc ÆTN EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11fd ÆTO EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11fe ÆTP EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11ff ÆTQ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1209 XDR1 EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x120a XDR2 EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x120b XDR3 EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x120c XDR4 EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x120d XDR5 EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x120e XDR6 EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x120f XDR7 EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1210 XDR8 EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1211 XDR9 EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1212 XDRA EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1213 XDRB EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1214 XDRC EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1215 XDRD EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1216 XDRE EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1217 XDRF EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1218 XDRG EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1219 XDRH EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x121a XDRI EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x121b XDRJ EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x121c XDRK EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x121d XDRL EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x121e XDRM EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x121f XDRN EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1220 XDRO EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1221 XDRP EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1222 XDRQ EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1223 XDRR EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1224 XDRS EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1225 XDRT EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1226 XDRU EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1227 XDRV EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1228 XDRW EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1229 XDRX EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x122a XDRY EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x122b XDRZ EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x122c XDRµ EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x122d XDRÐ EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x122e XDRÞ EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x122f XDRØ EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1230 XDRÑ EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1231 XDRÆ EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1232 XDRŒ EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1233 XDRß EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1234 XDRÇ EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1301 ÞAA EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1302 ÞAB EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1303 ÞAC EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1304 ÞAD EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1305 ÞAE EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1306 ÞAF EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1307 ÞAG EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1308 ÞAH EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1309 ÞAI EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x130a ÞAJ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x130b ÞAK EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x130c ÞAL EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x130d ÞAM EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x130e ÞAN EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x130f ÞAO EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1310 ÞAP EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1311 ÞAQ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1312 ÞAR EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1313 ÞAS EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1314 ÞAT EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1315 ÞAU EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1316 ÞAV EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1317 ÞAW EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1318 ÞAX EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1319 ÞAY EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x131a ÞAZ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x131b ÞBA EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x131c ÞBB EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x131d ÞBC EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x131e ÞBD EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x131f ÞBE EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1320 ÞBF EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1321 ÞBG EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1322 ÞBH EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1323 ÞBI EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1324 ÞBJ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1325 ÞBK EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1326 ÞBL EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1327 ÞBM EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1328 ÞBN EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1329 ÞBO EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x132a ÞBP EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x132b ÞBQ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x132c ÞBR EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x132d ÞBS EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x132e ÞBT EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x132f ÞBU EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1330 ÞBV EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1331 ÞBW EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1332 ÞBX EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1333 ÞBY EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1334 ÞBZ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1335 ÞCA EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1336 ÞCB EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1337 ÞCC EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1338 ÞCD EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1339 ÞCE EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x133a ÞCF EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x133b ÞCG EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x133c ÞCH EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x133d ÞCI EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x133e ÞCJ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x133f ÞCK EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1340 ÞCL EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1341 ÞCM EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1342 ÞCN EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1343 ÞCO EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1344 ÞCP EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1345 ÞCQ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1346 ÞCR EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1347 ÞCS EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1348 ÞCT EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1349 ÞCU EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x134a ÞCV EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x134b ÞCW EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x134c ÞCX EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x134d ÞCY EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x134e ÞCZ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x134f ÞDA EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1350 ÞDB EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1351 ÞDC EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1352 ÞDD EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1353 ÞDE EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1354 ÞDF EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1355 ÞDG EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1356 ÞDH EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1357 ÞDI EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1358 ÞDJ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1359 ÞDK EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x135a ÞDL EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x135b ÞDM EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x135c ÞDN EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x135d ÞDO EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x135e ÞDP EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x135f ÞDQ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1360 ÞDR EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1361 ÞDS EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1362 ÞDT EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1363 ÞDU EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1364 ÞDV EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1365 ÞDW EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1366 ÞDX EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1367 ÞDY EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1368 ÞDZ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1369 ÞEA EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x136a ÞEB EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x136b ÞEC EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x136c ÞED EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x136d ÞEE EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x136e ÞEF EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x136f ÞEG EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1370 ÞEH EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1371 ÞEI EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1372 ÞEJ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1373 ÞEK EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1374 ÞEL EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1375 ÞEM EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1376 ÞEN EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1377 ÞEO EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1378 ÞEP EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1379 ÞEQ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x137a ÞER EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x137b ÞES EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x137c ÞET EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x137d ÞEU EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x137e ÞEV EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x137f ÞEW EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1380 ÞEX EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1381 ÞEY EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1382 ÞEZ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1383 ÞFA EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1384 ÞFB EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1385 ÞFC EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1386 ÞFD EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1387 ÞFE EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1388 ÞFF EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1389 ÞFG EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x138a ÞFH EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x138b ÞFI EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x138c ÞFJ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x138d ÞFK EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x138e ÞFL EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x138f ÞFM EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1390 ÞFN EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1391 ÞFO EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1392 ÞFP EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1393 ÞFQ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1394 ÞFR EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1395 ÞFS EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1396 ÞFT EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1397 ÞFU EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1398 ÞFV EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1399 ÞFW EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x139a ÞFX EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x139b ÞFY EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x139c ÞFZ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x139d ÞGA EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x139e ÞGB EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x139f ÞGC EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13a0 ÞGD EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13a1 ÞGE EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13a2 ÞGF EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13a3 ÞGG EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13a4 ÞGH EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13a5 ÞGI EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13a6 ÞGJ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13a7 ÞGK EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13a8 ÞGL EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13a9 ÞGM EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13aa ÞGN EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13ab ÞGO EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13ac ÞGP EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13ad ÞGQ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13ae ÞGR EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13af ÞGS EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13b0 ÞGT EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13b1 ÞGU EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13b2 ÞGV EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13b3 ÞGW EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13b4 ÞGX EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13b5 ÞGY EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13b6 ÞGZ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13b7 ÞHA EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13b8 ÞHB EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13b9 ÞHC EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13ba ÞHD EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13bb ÞHE EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13bc ÞHF EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13bd ÞHG EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13be ÞHH EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13bf ÞHI EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13c0 ÞHJ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13c1 ÞHK EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13c2 ÞHL EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13c3 ÞHM EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13c4 ÞHN EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13c5 ÞHO EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13c6 ÞHP EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13c7 ÞHQ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13c8 ÞHR EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13c9 ÞHS EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13ca ÞHT EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13cb ÞHU EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13cc ÞHV EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13cd ÞHW EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13ce ÞHX EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13cf ÞHY EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13d0 ÞHZ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13d1 ÞIA EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13d2 ÞIB EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13d3 ÞIC EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13d4 ÞID EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13d5 ÞIE EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13d6 ÞIF EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13d7 ÞIG EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13d8 ÞIH EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13d9 ÞII EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13da ÞIJ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13db ÞIK EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13dc ÞIL EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13dd ÞIM EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13de ÞIN EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13df ÞIO EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13e0 ÞIP EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13e1 ÞIQ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13e2 ÞIR EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13e3 ÞIS EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13e4 ÞIT EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13e5 ÞIU EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13e6 ÞIV EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13e7 ÞIW EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13e8 ÞIX EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13e9 ÞIY EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13ea ÞIZ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13eb ÞJA EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13ec ÞJB EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13ed ÞJC EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13ee ÞJD EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x13ef ÞJE EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x13f0 ÞJF EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x13f1 ÞJG EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x13f2 ÞJH EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x13f3 ÞJI EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x13f4 ÞJJ EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x13f5 ÞJK EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x13f6 ÞJL EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x13f7 ÞJM EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x13f8 ÞJN EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x13f9 ÞJO EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x13fa ÞJP EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x13fb ÞJQ EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x13fc ÞJR EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x13fd ÞJS EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x13fe ÞJT EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x13ff ÞJU EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x2001 ¥AA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2002 ¥AB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2003 ¥AC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2004 ¥AD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2005 ¥AE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2006 ¥AF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2007 ¥AG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2008 ¥AH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2009 ¥AI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x200a ¥AJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x200b ¥AK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x200c ¥AL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x200d ¥AM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x200e ¥AN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x200f ¥AO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2010 ¥AP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2011 ¥AQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2012 ¥AR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2013 ¥AS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2014 ¥AT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2015 ¥AU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2016 ¥AV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2017 ¥AW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2018 ¥AX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2019 ¥AY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x201a ¥AZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x201b ¥BA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x201c ¥BB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x201d ¥BC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x201e ¥BD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x201f ¥BE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2020 ¥BF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2021 ¥BG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2022 ¥BH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2023 ¥BI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2024 ¥BJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2025 ¥BK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2026 ¥BL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2027 ¥BM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2028 ¥BN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2029 ¥BO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x202a ¥BP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x202b ¥BQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x202c ¥BR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x202d ¥BS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x202e ¥BT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x202f ¥BU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2030 ¥BV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2031 ¥BW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2032 ¥BX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2033 ¥BY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2034 ¥BZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2035 ¥CA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2036 ¥CB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2037 ¥CC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2038 ¥CD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2039 ¥CE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x203a ¥CF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x203b ¥CG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x203c ¥CH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x203d ¥CI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x203e ¥CJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x203f ¥CK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2040 ¥CL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2041 ¥CM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2042 ¥CN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2043 ¥CO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2044 ¥CP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2045 ¥CQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2046 ¥CR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2047 ¥CS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2048 ¥CT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2049 ¥CU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x204a ¥CV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x204b ¥CW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x204c ¥CX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x204d ¥CY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x204e ¥CZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x204f ¥DA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2050 ¥DB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2051 ¥DC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2052 ¥DD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2053 ¥DE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2054 ¥DF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2055 ¥DG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2056 ¥DH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2057 ¥DI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2058 ¥DJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2059 ¥DK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x205a ¥DL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x205b ¥DM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x205c ¥DN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x205d ¥DO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x205e ¥DP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x205f ¥DQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2060 ¥DR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2061 ¥DS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2062 ¥DT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2063 ¥DU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2064 ¥DV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2065 ¥DW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2066 ¥DX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2067 ¥DY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2068 ¥DZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2069 ¥EA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x206a ¥EB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x206b ¥EC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x206c ¥ED EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x206d ¥EE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x206e ¥EF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x206f ¥EG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2070 ¥EH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2071 ¥EI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2072 ¥EJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2073 ¥EK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2074 ¥EL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2075 ¥EM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2076 ¥EN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2077 ¥EO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2078 ¥EP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2079 ¥EQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x207a ¥ER EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x207b ¥ES EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x207c ¥ET EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x207d ¥EU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x207e ¥EV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x207f ¥EW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2080 ¥EX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2081 ¥EY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2082 ¥EZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2083 ¥FA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2084 ¥FB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2085 ¥FC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2086 ¥FD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2087 ¥FE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2088 ¥FF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2089 ¥FG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x208a ¥FH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x208b ¥FI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x208c ¥FJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x208d ¥FK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x208e ¥FL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x208f ¥FM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2090 ¥FN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2091 ¥FO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2092 ¥FP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2093 ¥FQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2094 ¥FR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2095 ¥FS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2096 ¥FT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2097 ¥FU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2098 ¥FV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2099 ¥FW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x209a ¥FX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x209b ¥FY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x209c ¥FZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x209d ¥GA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x209e ¥GB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x209f ¥GC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20a0 ¥GD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20a1 ¥GE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20a2 ¥GF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20a3 ¥GG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20a4 ¥GH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20a5 ¥GI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20a6 ¥GJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20a7 ¥GK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20a8 ¥GL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20a9 ¥GM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20aa ¥GN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20ab ¥GO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20ac ¥GP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20ad ¥GQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20ae ¥GR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20af ¥GS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20b0 ¥GT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20b1 ¥GU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20b2 ¥GV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20b3 ¥GW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20b4 ¥GX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20b5 ¥GY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20b6 ¥GZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20b7 ¥HA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20b8 ¥HB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20b9 ¥HC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20ba ¥HD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20bb ¥HE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20bc ¥HF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20bd ¥HG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20be ¥HH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20bf ¥HI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20c0 ¥HJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20c1 ¥HK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20c2 ¥HL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20c3 ¥HM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20c4 ¥HN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20c5 ¥HO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20c6 ¥HP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20c7 ¥HQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20c8 ¥HR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20c9 ¥HS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20ca ¥HT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20cb ¥HU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20cc ¥HV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20cd ¥HW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20ce ¥HX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20cf ¥HY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20d0 ¥HZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20d1 ¥IA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20d2 ¥IB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20d3 ¥IC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20d4 ¥ID EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20d5 ¥IE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20d6 ¥IF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20d7 ¥IG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20d8 ¥IH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20d9 ¥II EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20da ¥IJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20db ¥IK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20dc ¥IL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20dd ¥IM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20de ¥IN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20df ¥IO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20e0 ¥IP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20e1 ¥IQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20e2 ¥IR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20e3 ¥IS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20e4 ¥IT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20e5 ¥IU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20e6 ¥IV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20e7 ¥IW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20e8 ¥IX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20e9 ¥IY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20ea ¥IZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20eb ¥JA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20ec ¥JB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20ed ¥JC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20ee ¥JD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20ef ¥JE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20f0 ¥JF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20f1 ¥JG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20f2 ¥JH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20f3 ¥JI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20f4 ¥JJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20f5 ¥JK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20f6 ¥JL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20f7 ¥JM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20f8 ¥JN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20f9 ¥JO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20fa ¥JP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20fb ¥JQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20fc ¥JR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20fd ¥JS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20fe ¥JT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20ff ¥JU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2101 ¥JW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2102 ¥JX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2103 ¥JY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2104 ¥JZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2105 ¥KA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2106 ¥KB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2107 ¥KC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2108 ¥KD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2109 ¥KE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x210a ¥KF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x210b ¥KG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x210c ¥KH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x210d ¥KI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x210e ¥KJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x210f ¥KK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2110 ¥KL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2111 ¥KM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2112 ¥KN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2113 ¥KO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2114 ¥KP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2115 ¥KQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2116 ¥KR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2117 ¥KS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2118 ¥KT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2119 ¥KU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x211a ¥KV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x211b ¥KW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x211c ¥KX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x211d ¥KY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x211e ¥KZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x211f ¥LA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2120 ¥LB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2121 ¥LC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2122 ¥LD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2123 ¥LE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2124 ¥LF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2125 ¥LG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2126 ¥LH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2127 ¥LI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2128 ¥LJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2129 ¥LK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x212a ¥LL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x212b ¥LM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x212c ¥LN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x212d ¥LO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x212e ¥LP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x212f ¥LQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2130 ¥LR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2131 ¥LS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2132 ¥LT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2133 ¥LU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2134 ¥LV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2135 ¥LW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2136 ¥LX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2137 ¥LY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2138 ¥LZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2139 ¥MA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x213a ¥MB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x213b ¥MC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x213c ¥MD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x213d ¥ME EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x213e ¥MF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x213f ¥MG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2140 ¥MH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2141 ¥MI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2142 ¥MJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2143 ¥MK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2144 ¥ML EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2145 ¥MM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2146 ¥MN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2147 ¥MO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2148 ¥MP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2149 ¥MQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x214a ¥MR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x214b ¥MS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x214c ¥MT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x214d ¥MU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x214e ¥MV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x214f ¥MW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2150 ¥MX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2151 ¥MY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2152 ¥MZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2153 ¥NA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2154 ¥NB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2155 ¥NC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2156 ¥ND EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2157 ¥NE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2158 ¥NF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2159 ¥NG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x215a ¥NH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x215b ¥NI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x215c ¥NJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x215d ¥NK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x215e ¥NL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x215f ¥NM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2160 ¥NN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2161 ¥NO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2162 ¥NP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2163 ¥NQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2164 ¥NR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2165 ¥NS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2166 ¥NT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2167 ¥NU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2168 ¥NV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2169 ¥NW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x216a ¥NX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x216b ¥NY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x216c ¥NZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x216d ¥OA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x216e ¥OB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x216f ¥OC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2170 ¥OD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2171 ¥OE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2172 ¥OF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2173 ¥OG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2174 ¥OH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2175 ¥OI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2176 ¥OJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2177 ¥OK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2178 ¥OL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2179 ¥OM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x217a ¥ON EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x217b ¥OO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x217c ¥OP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x217d ¥OQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x217e ¥OR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x217f ¥OS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2180 ¥OT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2181 ¥OU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2182 ¥OV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2183 ¥OW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2184 ¥OX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2185 ¥OY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2186 ¥OZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2187 ¥PA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2188 ¥PB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2189 ¥PC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x218a ¥PD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x218b ¥PE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x218c ¥PF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x218d ¥PG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x218e ¥PH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x218f ¥PI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2190 ¥PJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2191 ¥PK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2192 ¥PL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2193 ¥PM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2194 ¥PN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2195 ¥PO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2196 ¥PP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2197 ¥PQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2198 ¥PR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2199 ¥PS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x219a ¥PT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x219b ¥PU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x219c ¥PV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x219d ¥PW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x219e ¥PX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x219f ¥PY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21a0 ¥PZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21a1 ¥QA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21a2 ¥QB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21a3 ¥QC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21a4 ¥QD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21a5 ¥QE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21a6 ¥QF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21a7 ¥QG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21a8 ¥QH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21a9 ¥QI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21aa ¥QJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21ab ¥QK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21ac ¥QL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21ad ¥QM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21ae ¥QN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21af ¥QO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21b0 ¥QP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21b1 ¥QQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21b2 ¥QR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21b3 ¥QS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21b4 ¥QT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21b5 ¥QU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21b6 ¥QV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21b7 ¥QW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21b8 ¥QX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21b9 ¥QY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21ba ¥QZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21bb ¥RA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21bc ¥RB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21bd ¥RC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21be ¥RD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21bf ¥RE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21c0 ¥RF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21c1 ¥RG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21c2 ¥RH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21c3 ¥RI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21c4 ¥RJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21c5 ¥RK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21c6 ¥RL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21c7 ¥RM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21c8 ¥RN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21c9 ¥RO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21ca ¥RP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21cb ¥RQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21cc ¥RR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21cd ¥RS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21ce ¥RT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21cf ¥RU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21d0 ¥RV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21d1 ¥RW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21d2 ¥RX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21d3 ¥RY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21d4 ¥RZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21d5 ¥SA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21d6 ¥SB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21d7 ¥SC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21d8 ¥SD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21d9 ¥SE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21da ¥SF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21db ¥SG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21dc ¥SH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21dd ¥SI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21de ¥SJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21df ¥SK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21e0 ¥SL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21e1 ¥SM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21e2 ¥SN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21e3 ¥SO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21e4 ¥SP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21e5 ¥SQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21e6 ¥SR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21e7 ¥SS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21e8 ¥ST EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21e9 ¥SU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21ea ¥SV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21eb ¥SW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21ec ¥SX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21ed ¥SY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21ee ¥SZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21ef ¥TA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21f0 ¥TB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21f1 ¥TC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21f2 ¥TD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21f3 ¥TE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21f4 ¥TF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21f5 ¥TG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21f6 ¥TH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21f7 ¥TI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21f8 ¥TJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21f9 ¥TK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21fa ¥TL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21fb ¥TM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21fc ¥TN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21fd ¥TO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21fe ¥TP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21ff ¥TQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2201 ÐAA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2202 ÐAB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2203 ÐAC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2204 ÐAD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2205 ÐAE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2206 ÐAF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2207 ÐAG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2208 ÐAH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2209 ÐAI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x220a ÐAJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x220b ÐAK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x220c ÐAL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x220d ÐAM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x220e ÐAN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x220f ÐAO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2210 ÐAP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2211 ÐAQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2212 ÐAR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2213 ÐAS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2214 ÐAT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2215 ÐAU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2216 ÐAV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2217 ÐAW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2218 ÐAX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2219 ÐAY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x221a ÐAZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x221b ÐBA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x221c ÐBB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x221d ÐBC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x221e ÐBD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x221f ÐBE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2220 ÐBF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2221 ÐBG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2222 ÐBH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2223 ÐBI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2224 ÐBJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2225 ÐBK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2226 ÐBL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2227 ÐBM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2228 ÐBN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2229 ÐBO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x222a ÐBP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x222b ÐBQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x222c ÐBR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x222d ÐBS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x222e ÐBT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x222f ÐBU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2230 ÐBV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2231 ÐBW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2232 ÐBX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2233 ÐBY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2234 ÐBZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2235 ÐCA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2236 ÐCB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2237 ÐCC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2238 ÐCD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2239 ÐCE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x223a ÐCF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x223b ÐCG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x223c ÐCH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x223d ÐCI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x223e ÐCJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x223f ÐCK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2240 ÐCL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2241 ÐCM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2242 ÐCN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2243 ÐCO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2244 ÐCP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2245 ÐCQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2246 ÐCR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2247 ÐCS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2248 ÐCT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2249 ÐCU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x224a ÐCV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x224b ÐCW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x224c ÐCX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x224d ÐCY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x224e ÐCZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x224f ÐDA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2250 ÐDB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2251 ÐDC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2252 ÐDD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2253 ÐDE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2254 ÐDF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2255 ÐDG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2256 ÐDH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2257 ÐDI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2258 ÐDJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2259 ÐDK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x225a ÐDL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x225b ÐDM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x225c ÐDN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x225d ÐDO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x225e ÐDP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x225f ÐDQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2260 ÐDR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2261 ÐDS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2262 ÐDT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2263 ÐDU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2264 ÐDV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2265 ÐDW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2266 ÐDX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2267 ÐDY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2268 ÐDZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2269 ÐEA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x226a ÐEB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x226b ÐEC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x226c ÐED EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x226d ÐEE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x226e ÐEF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x226f ÐEG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2270 ÐEH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2271 ÐEI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2272 ÐEJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2273 ÐEK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2274 ÐEL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2275 ÐEM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2276 ÐEN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2277 ÐEO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2278 ÐEP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2279 ÐEQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x227a ÐER EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x227b ÐES EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x227c ÐET EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x227d ÐEU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x227e ÐEV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x227f ÐEW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2280 ÐEX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2281 ÐEY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2282 ÐEZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2283 ÐFA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2284 ÐFB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2285 ÐFC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2286 ÐFD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2287 ÐFE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2288 ÐFF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2289 ÐFG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x228a ÐFH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x228b ÐFI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x228c ÐFJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x228d ÐFK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x228e ÐFL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x228f ÐFM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2290 ÐFN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2291 ÐFO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2292 ÐFP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2293 ÐFQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2294 ÐFR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2295 ÐFS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2296 ÐFT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2297 ÐFU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2298 ÐFV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2299 ÐFW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x229a ÐFX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x229b ÐFY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x229c ÐFZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x229d ÐGA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x229e ÐGB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x229f ÐGC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22a0 ÐGD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22a1 ÐGE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22a2 ÐGF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22a3 ÐGG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22a4 ÐGH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22a5 ÐGI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22a6 ÐGJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22a7 ÐGK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22a8 ÐGL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22a9 ÐGM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22aa ÐGN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22ab ÐGO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22ac ÐGP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22ad ÐGQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22ae ÐGR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22af ÐGS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22b0 ÐGT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22b1 ÐGU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22b2 ÐGV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22b3 ÐGW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22b4 ÐGX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22b5 ÐGY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22b6 ÐGZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22b7 ÐHA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22b8 ÐHB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22b9 ÐHC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22ba ÐHD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22bb ÐHE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22bc ÐHF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22bd ÐHG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22be ÐHH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22bf ÐHI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22c0 ÐHJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22c1 ÐHK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22c2 ÐHL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22c3 ÐHM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22c4 ÐHN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22c5 ÐHO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22c6 ÐHP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22c7 ÐHQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22c8 ÐHR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22c9 ÐHS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22ca ÐHT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22cb ÐHU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22cc ÐHV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22cd ÐHW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22ce ÐHX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22cf ÐHY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22d0 ÐHZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22d1 ÐIA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22d2 ÐIB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22d3 ÐIC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22d4 ÐID EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22d5 ÐIE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22d6 ÐIF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22d7 ÐIG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22d8 ÐIH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22d9 ÐII EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22da ÐIJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22db ÐIK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22dc ÐIL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22dd ÐIM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22de ÐIN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22df ÐIO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22e0 ÐIP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22e1 ÐIQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22e2 ÐIR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22e3 ÐIS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22e4 ÐIT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22e5 ÐIU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22e6 ÐIV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22e7 ÐIW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22e8 ÐIX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22e9 ÐIY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22ea ÐIZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22eb ÐJA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22ec ÐJB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22ed ÐJC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22ee ÐJD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22ef ÐJE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22f0 ÐJF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22f1 ÐJG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22f2 ÐJH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22f3 ÐJI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22f4 ÐJJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22f5 ÐJK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22f6 ÐJL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22f7 ÐJM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22f8 ÐJN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22f9 ÐJO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22fa ÐJP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22fb ÐJQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22fc ÐJR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22fd ÐJS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22fe ÐJT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22ff ÐJU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x3001 ÆAA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3002 ÆAB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3003 ÆAC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3004 ÆAD EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3005 ÆAE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3006 ÆAF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3007 ÆAG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3008 ÆAH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3009 ÆAI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x300a ÆAJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x300b ÆAK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x300c ÆAL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x300d ÆAM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x300e ÆAN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x300f ÆAO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3010 ÆAP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3011 ÆAQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3012 ÆAR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3013 ÆAS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3014 ÆAT EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3015 ÆAU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3016 ÆAV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3017 ÆAW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3018 ÆAX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3019 ÆAY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x301a ÆAZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x301b ÆBA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x301c ÆBB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x301d ÆBC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x301e ÆBD EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x301f ÆBE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3020 ÆBF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3021 ÆBG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3022 ÆBH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3023 ÆBI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3024 ÆBJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3025 ÆBK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3026 ÆBL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3027 ÆBM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3028 ÆBN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3029 ÆBO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x302a ÆBP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x302b ÆBQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x302c ÆBR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x302d ÆBS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x302e ÆBT EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x302f ÆBU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3030 ÆBV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3031 ÆBW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3032 ÆBX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3033 ÆBY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3034 ÆBZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3035 ÆCA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3036 ÆCB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3037 ÆCC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3038 ÆCD EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3039 ÆCE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x303a ÆCF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x303b ÆCG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x303c ÆCH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x303d ÆCI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x303e ÆCJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x303f ÆCK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3040 ÆCL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3041 ÆCM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3042 ÆCN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3043 ÆCO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3044 ÆCP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3045 ÆCQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3046 ÆCR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3047 ÆCS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3048 ÆCT EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3049 ÆCU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x304a ÆCV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x304b ÆCW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x304c ÆCX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x304d ÆCY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x304e ÆCZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x304f ÆDA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3050 ÆDB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3051 ÆDC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3052 ÆDD EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3053 ÆDE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3054 ÆDF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3055 ÆDG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3056 ÆDH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3057 ÆDI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3058 ÆDJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3059 ÆDK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x305a ÆDL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x305b ÆDM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x305c ÆDN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x305d ÆDO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x305e ÆDP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x305f ÆDQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3060 ÆDR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3061 ÆDS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3062 ÆDT EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3063 ÆDU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3064 ÆDV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3065 ÆDW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3066 ÆDX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3067 ÆDY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3068 ÆDZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3069 ÆEA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x306a ÆEB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x306b ÆEC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x306c ÆED EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x306d ÆEE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x306e ÆEF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x306f ÆEG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3070 ÆEH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3071 ÆEI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3072 ÆEJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3073 ÆEK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3074 ÆEL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3075 ÆEM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3076 ÆEN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3077 ÆEO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3078 ÆEP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3079 ÆEQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x307a ÆER EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x307b ÆES EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x307c ÆET EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x307d ÆEU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x307e ÆEV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x307f ÆEW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3080 ÆEX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3081 ÆEY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3082 ÆEZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3083 ÆFA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3084 ÆFB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3085 ÆFC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3086 ÆFD EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3087 ÆFE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3088 ÆFF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3089 ÆFG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x308a ÆFH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x308b ÆFI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x308c ÆFJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x308d ÆFK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x308e ÆFL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x308f ÆFM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3090 ÆFN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3091 ÆFO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3092 ÆFP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3093 ÆFQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3094 ÆFR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3095 ÆFS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3096 ÆFT EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3097 ÆFU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3098 ÆFV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3099 ÆFW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x309a ÆFX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x309b ÆFY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x309c ÆFZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x309d ÆGA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x309e ÆGB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x309f ÆGC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30a0 ÆGD EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30a1 ÆGE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30a2 ÆGF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30a3 ÆGG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30a4 ÆGH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30a5 ÆGI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30a6 ÆGJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30a7 ÆGK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30a8 ÆGL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30a9 ÆGM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30aa ÆGN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30ab ÆGO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30ac ÆGP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30ad ÆGQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30ae ÆGR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30af ÆGS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30b0 ÆGT EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30b1 ÆGU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30b2 ÆGV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30b3 ÆGW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30b4 ÆGX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30b5 ÆGY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30b6 ÆGZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30b7 ÆHA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30b8 ÆHB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30b9 ÆHC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30ba ÆHD EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30bb ÆHE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30bc ÆHF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30bd ÆHG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30be ÆHH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30bf ÆHI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30c0 ÆHJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30c1 ÆHK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30c2 ÆHL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30c3 ÆHM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30c4 ÆHN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30c5 ÆHO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30c6 ÆHP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30c7 ÆHQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30c8 ÆHR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30c9 ÆHS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30ca ÆHT EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30cb ÆHU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30cc ÆHV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30cd ÆHW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30ce ÆHX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30cf ÆHY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30d0 ÆHZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30d1 ÆIA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30d2 ÆIB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30d3 ÆIC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30d4 ÆID EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30d5 ÆIE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30d6 ÆIF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30d7 ÆIG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30d8 ÆIH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30d9 ÆII EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30da ÆIJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30db ÆIK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30dc ÆIL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30dd ÆIM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30de ÆIN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30df ÆIO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30e0 ÆIP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30e1 ÆIQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30e2 ÆIR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30e3 ÆIS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30e4 ÆIT EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30e5 ÆIU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30e6 ÆIV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30e7 ÆIW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30e8 ÆIX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30e9 ÆIY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30ea ÆIZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30eb ÆJA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30ec ÆJB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30ed ÆJC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30ee ÆJD EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30ef ÆJE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30f0 ÆJF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30f1 ÆJG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30f2 ÆJH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30f3 ÆJI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30f4 ÆJJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30f5 ÆJK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30f6 ÆJL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30f7 ÆJM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30f8 ÆJN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30f9 ÆJO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30fa ÆJP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30fb ÆJQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30fc ÆJR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30fd ÆJS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30fe ÆJT EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30ff ÆJU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3100 ÆJV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3101 ÆJW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3102 ÆJX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3103 ÆJY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3104 ÆJZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3105 ÆKA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3106 ÆKB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3107 ÆKC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3108 ÆKD EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3109 ÆKE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x310a ÆKF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x310b ÆKG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x310c ÆKH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x310d ÆKI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x310e ÆKJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x310f ÆKK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3110 ÆKL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3111 ÆKM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3112 ÆKN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3113 ÆKO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3114 ÆKP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3115 ÆKQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3116 ÆKR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3117 ÆKS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3118 ÆKT EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3119 ÆKU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x311a ÆKV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x311b ÆKW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x311c ÆKX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x311d ÆKY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x311e ÆKZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x311f ÆLA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3120 ÆLB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3121 ÆLC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3122 ÆLD EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3123 ÆLE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3124 ÆLF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3125 ÆLG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3126 ÆLH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3127 ÆLI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3128 ÆLJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3129 ÆLK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x312a ÆLL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x312b ÆLM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x312c ÆLN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x312d ÆLO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x312e ÆLP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x312f ÆLQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3130 ÆLR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3131 ÆLS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3132 ÆLT EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3133 ÆLU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3134 ÆLV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3135 ÆLW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3136 ÆLX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3137 ÆLY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3138 ÆLZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3139 ÆMA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x313a ÆMB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x313b ÆMC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x313c ÆMD EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x313d ÆME EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x313e ÆMF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x313f ÆMG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3140 ÆMH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3141 ÆMI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3142 ÆMJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3143 ÆMK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3144 ÆML EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3145 ÆMM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3146 ÆMN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3147 ÆMO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3148 ÆMP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3149 ÆMQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x314a ÆMR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x314b ÆMS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x314c ÆMT EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x314d ÆMU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x314e ÆMV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x314f ÆMW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3150 ÆMX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3151 ÆMY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3152 ÆMZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3153 ÆNA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3154 ÆNB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3155 ÆNC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3156 ÆND EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3157 ÆNE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3158 ÆNF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3159 ÆNG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x315a ÆNH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x315b ÆNI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x315c ÆNJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x315d ÆNK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x315e ÆNL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x315f ÆNM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3160 ÆNN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3161 ÆNO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3162 ÆNP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3163 ÆNQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3164 ÆNR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3165 ÆNS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3166 ÆNT EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3167 ÆNU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3168 ÆNV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3169 ÆNW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x316a ÆNX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x316b ÆNY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x316c ÆNZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x316d ÆOA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x316e ÆOB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x316f ÆOC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3170 ÆOD EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3171 ÆOE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3172 ÆOF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3173 ÆOG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3174 ÆOH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3175 ÆOI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3176 ÆOJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3177 ÆOK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3178 ÆOL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3179 ÆOM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x317a ÆON EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x317b ÆOO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x317c ÆOP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x317d ÆOQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x317e ÆOR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x317f ÆOS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3180 ÆOT EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3181 ÆOU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3182 ÆOV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3183 ÆOW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3184 ÆOX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3185 ÆOY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3186 ÆOZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3187 ÆPA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3188 ÆPB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3189 ÆPC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x318a ÆPD EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x318b ÆPE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x318c ÆPF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x318d ÆPG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x318e ÆPH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x318f ÆPI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3190 ÆPJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3191 ÆPK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3192 ÆPL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3193 ÆPM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3194 ÆPN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3195 ÆPO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3196 ÆPP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3197 ÆPQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3198 ÆPR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3199 ÆPS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x319a ÆPT EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x319b ÆPU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x319c ÆPV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x319d ÆPW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x319e ÆPX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x319f ÆPY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31a0 ÆPZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31a1 ÆQA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31a2 ÆQB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31a3 ÆQC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31a4 ÆQD EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31a5 ÆQE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31a6 ÆQF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31a7 ÆQG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31a8 ÆQH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31a9 ÆQI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31aa ÆQJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31ab ÆQK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31ac ÆQL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31ad ÆQM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31ae ÆQN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31af ÆQO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31b0 ÆQP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31b1 ÆQQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31b2 ÆQR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31b3 ÆQS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31b4 ÆQT EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31b5 ÆQU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31b6 ÆQV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31b7 ÆQW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31b8 ÆQX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31b9 ÆQY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31ba ÆQZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31bb ÆRA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31bc ÆRB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31bd ÆRC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31be ÆRD EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31bf ÆRE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31c0 ÆRF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31c1 ÆRG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31c2 ÆRH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31c3 ÆRI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31c4 ÆRJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31c5 ÆRK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31c6 ÆRL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31c7 ÆRM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31c8 ÆRN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31c9 ÆRO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31ca ÆRP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31cb ÆRQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31cc ÆRR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31cd ÆRS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31ce ÆRT EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31cf ÆRU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31d0 ÆRV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31d1 ÆRW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31d2 ÆRX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31d3 ÆRY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31d4 ÆRZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31d5 ÆSA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31d6 ÆSB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31d7 ÆSC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31d8 ÆSD EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31d9 ÆSE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31da ÆSF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31db ÆSG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31dc ÆSH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31dd ÆSI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31de ÆSJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31df ÆSK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31e0 ÆSL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31e1 ÆSM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31e2 ÆSN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31e3 ÆSO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31e4 ÆSP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31e5 ÆSQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31e6 ÆSR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31e7 ÆSS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31e8 ÆST EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31e9 ÆSU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31ea ÆSV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31eb ÆSW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31ec ÆSX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31ed ÆSY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31ee ÆSZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31ef ÆTA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31f0 ÆTB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31f1 ÆTC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31f2 ÆTD EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31f3 ÆTE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31f4 ÆTF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31f5 ÆTG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31f6 ÆTH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31f7 ÆTI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31f8 ÆTJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31f9 ÆTK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31fa ÆTL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31fb ÆTM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31fc ÆTN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31fd ÆTO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31fe ÆTP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31ff ÆTQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x5000 µAA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5001 µAB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5002 µAC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5003 µAD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5004 µAE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5005 µAF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5006 µAG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5007 µAH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5008 µAI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5009 µAJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x500a µAK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x500b µAL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x500c µAM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x500d µAN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x500e µAO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x500f µAP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5010 µAQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5011 µAR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5012 µAS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5013 µAT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5014 µAU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5015 µAV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5016 µAW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5017 µAX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5018 µAY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5019 µAZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x501a µBA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x501b µBB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x501c µBC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x501d µBD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x501e µBE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x501f µBF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5020 µBG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5021 µBH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5022 µBI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5023 µBJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5024 µBK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5025 µBL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5026 µBM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5027 µBN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5028 µBO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5029 µBP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x502a µBQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x502b µBR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x502c µBS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x502d µBT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x502e µBU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x502f µBV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5030 µBW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5031 µBX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5032 µBY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5033 µBZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5034 µCA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5035 µCB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5036 µCC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5037 µCD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5038 µCE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5039 µCF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x503a µCG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x503b µCH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x503c µCI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x503d µCJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x503e µCK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x503f µCL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5040 µCM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5041 µCN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5042 µCO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5043 µCP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5044 µCQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5045 µCR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5046 µCS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5047 µCT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5048 µCU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5049 µCV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x504a µCW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x504b µCX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x504c µCY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x504d µCZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x504e µDA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x504f µDB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5050 µDC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5051 µDD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5052 µDE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5053 µDF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5054 µDG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5055 µDH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5056 µDI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5057 µDJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5058 µDK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5059 µDL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x505a µDM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x505b µDN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x505c µDO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x505d µDP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x505e µDQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x505f µDR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5060 µDS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5061 µDT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5062 µDU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5063 µDV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5064 µDW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5065 µDX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5066 µDY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5067 µDZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5068 µEA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5069 µEB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x506a µEC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x506b µED EMPTY LARGE 5 Io IWD LARGE unpaletted +0x506c µEE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x506d µEF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x506e µEG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x506f µEH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5070 µEI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5071 µEJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5072 µEK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5073 µEL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5074 µEM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5075 µEN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5076 µEO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5077 µEP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5078 µEQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5079 µER EMPTY LARGE 5 Io IWD LARGE unpaletted +0x507a µES EMPTY LARGE 5 Io IWD LARGE unpaletted +0x507b µET EMPTY LARGE 5 Io IWD LARGE unpaletted +0x507c µEU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x507d µEV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x507e µEW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x507f µEX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5080 µEY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5081 µEZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5082 µFA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5083 µFB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5084 µFC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5085 µFD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5086 µFE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5087 µFF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5088 µFG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5089 µFH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x508a µFI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x508b µFJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x508c µFK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x508d µFL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x508e µFM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x508f µFN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5090 µFO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5091 µFP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5092 µFQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5093 µFR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5094 µFS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5095 µFT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5096 µFU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5097 µFV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5098 µFW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5099 µFX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x509a µFY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x509b µFZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x509c µGA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x509d µGB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x509e µGC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x509f µGD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50a0 µGE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50a1 µGF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50a2 µGG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50a3 µGH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50a4 µGI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50a5 µGJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50a6 µGK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50a7 µGL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50a8 µGM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50a9 µGN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50aa µGO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50ab µGP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50ac µGQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50ad µGR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50ae µGS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50af µGT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50b0 µGU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50b1 µGV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50b2 µGW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50b3 µGX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50b4 µGY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50b5 µGZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50b6 µHA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50b7 µHB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50b8 µHC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50b9 µHD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50ba µHE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50bb µHF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50bc µHG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50bd µHH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50be µHI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50bf µHJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50c0 µHK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50c1 µHL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50c2 µHM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50c3 µHN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50c4 µHO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50c5 µHP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50c6 µHQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50c7 µHR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50c8 µHS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50c9 µHT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50ca µHU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50cb µHV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50cc µHW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50cd µHX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50ce µHY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50cf µHZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50d0 µIA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50d1 µIB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50d2 µIC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50d3 µID EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50d4 µIE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50d5 µIF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50d6 µIG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50d7 µIH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50d8 µII EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50d9 µIJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50da µIK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50db µIL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50dc µIM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50dd µIN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50de µIO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50df µIP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50e0 µIQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50e1 µIR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50e2 µIS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50e3 µIT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50e4 µIU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50e5 µIV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50e6 µIW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50e7 µIX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50e8 µIY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50e9 µIZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50ea µJA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50eb µJB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50ec µJC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50ed µJD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50ee µJE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50ef µJF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50f0 µJG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50f1 µJH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50f2 µJI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50f3 µJJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50f4 µJK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50f5 µJL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50f6 µJM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50f7 µJN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50f8 µJO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50f9 µJP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50fa µJQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50fb µJR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50fc µJS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50fd µJT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50fe µJU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50ff µJV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5100 µJW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5101 µJX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5102 µJY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5103 µJZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5104 µKA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5105 µKB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5106 µKC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5107 µKD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5108 µKE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5109 µKF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x510a µKG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x510b µKH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x510c µKI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x510d µKJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x510e µKK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x510f µKL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5110 µKM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5111 µKN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5112 µKO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5113 µKP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5114 µKQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5115 µKR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5116 µKS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5117 µKT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5118 µKU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5119 µKV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x511a µKW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x511b µKX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x511c µKY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x511d µKZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x511e µLA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x511f µLB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5120 µLC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5121 µLD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5122 µLE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5123 µLF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5124 µLG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5125 µLH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5126 µLI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5127 µLJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5128 µLK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5129 µLL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x512a µLM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x512b µLN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x512c µLO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x512d µLP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x512e µLQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x512f µLR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5130 µLS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5131 µLT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5132 µLU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5133 µLV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5134 µLW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5135 µLX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5136 µLY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5137 µLZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5138 µMA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5139 µMB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x513a µMC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x513b µMD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x513c µME EMPTY LARGE 5 Io IWD LARGE unpaletted +0x513d µMF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x513e µMG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x513f µMH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5140 µMI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5141 µMJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5142 µMK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5143 µML EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5144 µMM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5145 µMN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5146 µMO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5147 µMP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5148 µMQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5149 µMR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x514a µMS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x514b µMT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x514c µMU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x514d µMV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x514e µMW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x514f µMX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5150 µMY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5151 µMZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5152 µNA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5153 µNB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5154 µNC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5155 µND EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5156 µNE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5157 µNF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5158 µNG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5159 µNH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x515a µNI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x515b µNJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x515c µNK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x515d µNL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x515e µNM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x515f µNN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5160 µNO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5161 µNP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5162 µNQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5163 µNR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5164 µNS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5165 µNT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5166 µNU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5167 µNV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5168 µNW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5169 µNX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x516a µNY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x516b µNZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x516c µOA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x516d µOB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x516e µOC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x516f µOD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5170 µOE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5171 µOF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5172 µOG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5173 µOH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5174 µOI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5175 µOJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5176 µOK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5177 µOL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5178 µOM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5179 µON EMPTY LARGE 5 Io IWD LARGE unpaletted +0x517a µOO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x517b µOP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x517c µOQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x517d µOR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x517e µOS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x517f µOT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5180 µOU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5181 µOV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5182 µOW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5183 µOX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5184 µOY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5185 µOZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5186 µPA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5187 µPB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5188 µPC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5189 µPD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x518a µPE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x518b µPF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x518c µPG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x518d µPH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x518e µPI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x518f µPJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5190 µPK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5191 µPL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5192 µPM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5193 µPN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5194 µPO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5195 µPP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5196 µPQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5197 µPR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5198 µPS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5199 µPT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x519a µPU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x519b µPV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x519c µPW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x519d µPX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x519e µPY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x519f µPZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51a0 µQA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51a1 µQB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51a2 µQC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51a3 µQD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51a4 µQE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51a5 µQF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51a6 µQG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51a7 µQH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51a8 µQI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51a9 µQJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51aa µQK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51ab µQL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51ac µQM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51ad µQN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51ae µQO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51af µQP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51b0 µQQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51b1 µQR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51b2 µQS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51b3 µQT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51b4 µQU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51b5 µQV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51b6 µQW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51b7 µQX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51b8 µQY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51b9 µQZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51ba µRA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51bb µRB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51bc µRC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51bd µRD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51be µRE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51bf µRF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51c0 µRG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51c1 µRH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51c2 µRI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51c3 µRJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51c4 µRK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51c5 µRL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51c6 µRM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51c7 µRN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51c8 µRO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51c9 µRP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51ca µRQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51cb µRR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51cc µRS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51cd µRT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51ce µRU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51cf µRV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51d0 µRW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51d1 µRX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51d2 µRY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51d3 µRZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51d4 µSA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51d5 µSB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51d6 µSC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51d7 µSD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51d8 µSE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51d9 µSF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51da µSG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51db µSH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51dc µSI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51dd µSJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51de µSK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51df µSL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51e0 µSM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51e1 µSN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51e2 µSO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51e3 µSP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51e4 µSQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51e5 µSR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51e6 µSS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51e7 µST EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51e8 µSU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51e9 µSV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51ea µSW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51eb µSX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51ec µSY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51ed µSZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51ee µTA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51ef µTB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51f0 µTC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51f1 µTD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51f2 µTE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51f3 µTF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51f4 µTG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51f5 µTH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51f6 µTI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51f7 µTJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51f8 µTK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51f9 µTL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51fa µTM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51fb µTN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51fc µTO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51fd µTP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51fe µTQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51ff µTR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5200 µTS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5201 µTT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5202 µTU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5203 µTV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5204 µTW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5205 µTX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5206 µTY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5207 µTZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5208 µUA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5209 µUB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x520a µUC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x520b µUD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x520c µUE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x520d µUF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x520e µUG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x520f µUH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5210 µUI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5211 µUJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5212 µUK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5213 µUL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5214 µUM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5215 µUN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5216 µUO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5217 µUP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5218 µUQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5219 µUR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x521a µUS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x521b µUT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x521c µUU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x521d µUV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x521e µUW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x521f µUX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5220 µUY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5221 µUZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5222 µVA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5223 µVB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5224 µVC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5225 µVD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5226 µVE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5227 µVF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5228 µVG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5229 µVH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x522a µVI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x522b µVJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x522c µVK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x522d µVL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x522e µVM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x522f µVN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5230 µVO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5231 µVP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5232 µVQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5233 µVR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5234 µVS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5235 µVT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5236 µVU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5237 µVV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5238 µVW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5239 µVX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x523a µVY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x523b µVZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x523d µAA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x523e µAB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x523f µAC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5240 µAD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5241 µAE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5242 µAF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5243 µAG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5244 µAH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5245 µAI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5246 µAJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5247 µAK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5248 µAL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5249 µAM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x524a µAN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x524b µAO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x524c µAP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x524d µAQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x524e µAR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x524f µAS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5250 µAT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5251 µAU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5252 µAV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5253 µAW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5254 µAX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5255 µAY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5256 µAZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5257 µBA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5258 µBB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5259 µBC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x525a µBD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x525b µBE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x525c µBF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x525d µBG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x525e µBH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x525f µBI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5260 µBJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5261 µBK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5262 µBL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5263 µBM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5264 µBN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5265 µBO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5266 µBP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5267 µBQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5268 µBR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5269 µBS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x526a µBT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x526b µBU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x526c µBV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x526d µBW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x526e µBX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x526f µBY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5270 µBZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5271 µCA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5272 µCB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5273 µCC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5274 µCD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5275 µCE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5276 µCF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5277 µCG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5278 µCH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5279 µCI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x527a µCJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x527b µCK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x527c µCL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x527d µCM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x527e µCN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x527f µCO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5280 µCP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5281 µCQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5282 µCR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5283 µCS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5284 µCT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5285 µCU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5286 µCV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5287 µCW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5288 µCX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5289 µCY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x528a µCZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x528b µDA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x528c µDB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x528d µDC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x528e µDD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x528f µDE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5290 µDF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5291 µDG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5292 µDH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5293 µDI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5294 µDJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5295 µDK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5296 µDL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5297 µDM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5298 µDN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5299 µDO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x529a µDP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x529b µDQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x529c µDR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x529d µDS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x529e µDT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x529f µDU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52a0 µDV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52a1 µDW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52a2 µDX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52a3 µDY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52a4 µDZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52a5 µEA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52a6 µEB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52a7 µEC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52a8 µED EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52a9 µEE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52aa µEF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52ab µEG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52ac µEH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52ad µEI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52ae µEJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52af µEK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52b0 µEL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52b1 µEM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52b2 µEN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52b3 µEO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52b4 µEP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52b5 µEQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52b6 µER EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52b7 µES EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52b8 µET EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52b9 µEU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52ba µEV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52bb µEW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52bc µEX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52bd µEY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52be µEZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52bf µFA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52c0 µFB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52c1 µFC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52c2 µFD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52c3 µFE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52c4 µFF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52c5 µFG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52c6 µFH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52c7 µFI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52c8 µFJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52c9 µFK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52ca µFL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52cb µFM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52cc µFN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52cd µFO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52ce µFP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52cf µFQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52d0 µFR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52d1 µFS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52d2 µFT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52d3 µFU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52d4 µFV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52d5 µFW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52d6 µFX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52d7 µFY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52d8 µFZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52d9 µGA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52da µGB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52db µGC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52dc µGD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52dd µGE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52de µGF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52df µGG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52e0 µGH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52e1 µGI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52e2 µGJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52e3 µGK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52e4 µGL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52e5 µGM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52e6 µGN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52e7 µGO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52e8 µGP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52e9 µGQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52ea µGR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52eb µGS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52ec µGT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52ed µGU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52ee µGV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52ef µGW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52f0 µGX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52f1 µGY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52f2 µGZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52f3 µHA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52f4 µHB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52f5 µHC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52f6 µHD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52f7 µHE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52f8 µHF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52f9 µHG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52fa µHH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52fb µHI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52fc µHJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52fd µHK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52fe µHL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52ff µHM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5300 µHN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5301 µHO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5302 µHP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5303 µHQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5304 µHR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5305 µHS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5306 µHT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5307 µHU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5308 µHV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5309 µHW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x530a µHX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x530b µHY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x530c µHZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x530d µIA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x530e µIB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x530f µIC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5310 µID EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5311 µIE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5312 µIF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5313 µIG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5314 µIH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5315 µII EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5316 µIJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5317 µIK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5318 µIL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5319 µIM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x531a µIN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x531b µIO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x531c µIP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x531d µIQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x531e µIR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x531f µIS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5320 µIT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5321 µIU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5322 µIV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5323 µIW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5324 µIX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5325 µIY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5326 µIZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5327 µJA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5328 µJB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5329 µJC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x532a µJD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x532b µJE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x532c µJF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x532d µJG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x532e µJH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x532f µJI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5330 µJJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5331 µJK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5332 µJL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5333 µJM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5334 µJN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5335 µJO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5336 µJP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5337 µJQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5338 µJR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5339 µJS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x533a µJT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x533b µJU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x533c µJV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x533d µJW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x533e µJX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x533f µJY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5340 µJZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5341 µKA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5342 µKB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5343 µKC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5344 µKD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5345 µKE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5346 µKF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5347 µKG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5348 µKH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5349 µKI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x534a µKJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x534b µKK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x534c µKL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x534d µKM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x534e µKN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x534f µKO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5350 µKP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5351 µKQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5352 µKR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5353 µKS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5354 µKT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5355 µKU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5356 µKV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5357 µKW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5358 µKX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5359 µKY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x535a µKZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x535b µLA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x535c µLB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x535d µLC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x535e µLD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x535f µLE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5360 µLF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5361 µLG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5362 µLH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5363 µLI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5364 µLJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5365 µLK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5366 µLL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5367 µLM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5368 µLN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5369 µLO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x536a µLP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x536b µLQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x536c µLR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x536d µLS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x536e µLT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x536f µLU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5370 µLV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5371 µLW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5372 µLX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5373 µLY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5374 µLZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5375 µMA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5376 µMB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5377 µMC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5378 µMD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5379 µME EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x537a µMF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x537b µMG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x537c µMH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x537d µMI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x537e µMJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x537f µMK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5380 µML EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5381 µMM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5382 µMN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5383 µMO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5384 µMP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5385 µMQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5386 µMR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5387 µMS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5388 µMT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5389 µMU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x538a µMV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x538b µMW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x538c µMX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x538d µMY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x538e µMZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x538f µNA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5390 µNB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5391 µNC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5392 µND EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5393 µNE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5394 µNF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5395 µNG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5396 µNH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5397 µNI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5398 µNJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5399 µNK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x539a µNL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x539b µNM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x539c µNN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x539d µNO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x539e µNP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x539f µNQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53a0 µNR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53a1 µNS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53a2 µNT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53a3 µNU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53a4 µNV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53a5 µNW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53a6 µNX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53a7 µNY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53a8 µNZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53a9 µOA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53aa µOB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53ab µOC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53ac µOD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53ad µOE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53ae µOF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53af µOG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53b0 µOH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53b1 µOI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53b2 µOJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53b3 µOK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53b4 µOL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53b5 µOM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53b6 µON EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53b7 µOO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53b8 µOP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53b9 µOQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53ba µOR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53bb µOS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53bc µOT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53bd µOU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53be µOV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53bf µOW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53c0 µOX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53c1 µOY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53c2 µOZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53c3 µPA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53c4 µPB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53c5 µPC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53c6 µPD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53c7 µPE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53c8 µPF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53c9 µPG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53ca µPH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53cb µPI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53cc µPJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53cd µPK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53ce µPL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53cf µPM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53d0 µPN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53d1 µPO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53d2 µPP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53d3 µPQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53d4 µPR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53d5 µPS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53d6 µPT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53d7 µPU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53d8 µPV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53d9 µPW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53da µPX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53db µPY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53dc µPZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53dd µQA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53de µQB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53df µQC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53e0 µQD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53e1 µQE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53e2 µQF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53e3 µQG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53e4 µQH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53e5 µQI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53e6 µQJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53e7 µQK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53e8 µQL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53e9 µQM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53ea µQN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53eb µQO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53ec µQP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53ed µQQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53ee µQR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53ef µQS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53f0 µQT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53f1 µQU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53f2 µQV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53f3 µQW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53f4 µQX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53f5 µQY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53f6 µQZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53f7 µRA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53f8 µRB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53f9 µRC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53fa µRD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53fb µRE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53fc µRF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53fd µRG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53fe µRH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53ff µRI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5400 µRJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5401 µRK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5402 µRL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5403 µRM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5404 µRN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5405 µRO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5406 µRP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5407 µRQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5408 µRR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5409 µRS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x540a µRT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x540b µRU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x540c µRV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x540d µRW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x540e µRX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x540f µRY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5410 µRZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5411 µSA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5412 µSB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5413 µSC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5414 µSD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5415 µSE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5416 µSF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5417 µSG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5418 µSH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5419 µSI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x541a µSJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x541b µSK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x541c µSL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x541d µSM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x541e µSN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x541f µSO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5420 µSP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5421 µSQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5422 µSR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5423 µSS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5424 µST EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5425 µSU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5426 µSV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5427 µSW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5428 µSX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5429 µSY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x542a µSZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x542b µTA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x542c µTB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x542d µTC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x542e µTD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x542f µTE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5430 µTF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5431 µTG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5432 µTH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5433 µTI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5434 µTJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5435 µTK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5436 µTL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5437 µTM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5438 µTN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5439 µTO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x543a µTP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x543b µTQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x543c µTR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x543d µTS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x543e µTT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x543f µTU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5440 µTV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5441 µTW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5442 µTX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5443 µTY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5444 µTZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5445 µUA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5446 µUB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5447 µUC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5448 µUD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5449 µUE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x544a µUF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x544b µUG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x544c µUH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x544d µUI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x544e µUJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x544f µUK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5450 µUL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5451 µUM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5452 µUN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5453 µUO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5454 µUP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5455 µUQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5456 µUR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5457 µUS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5458 µUT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5459 µUU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x545a µUV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x545b µUW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x545c µUX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x545d µUY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x545e µUZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x545f µVA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5460 µVB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5461 µVC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5462 µVD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5463 µVE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5464 µVF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5465 µVG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5466 µVH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5467 µVI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5468 µVJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5469 µVK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x546a µVL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x546b µVM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x546c µVN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x546d µVO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x546e µVP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x546f µVQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5470 µVR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5471 µVS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5472 µVT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5473 µVU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5474 µVV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5475 µVW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5476 µVX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5477 µVY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5478 µVZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x547a £AA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x547b £AB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x547c £AC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x547d £AD EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x547e £AE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x547f £AF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5480 £AG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5481 £AH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5482 £AI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5483 £AJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5484 £AK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5485 £AL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5486 £AM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5487 £AN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5488 £AO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5489 £AP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x548a £AQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x548b £AR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x548c £AS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x548d £AT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x548e £AU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x548f £AV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5490 £AW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5491 £AX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5492 £AY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5493 £AZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5494 £BA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5495 £BB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5496 £BC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5497 £BD EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5498 £BE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5499 £BF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x549a £BG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x549b £BH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x549c £BI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x549d £BJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x549e £BK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x549f £BL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54a0 £BM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54a1 £BN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54a2 £BO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54a3 £BP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54a4 £BQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54a5 £BR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54a6 £BS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54a7 £BT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54a8 £BU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54a9 £BV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54aa £BW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54ab £BX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54ac £BY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54ad £BZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54ae £CA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54af £CB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54b0 £CC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54b1 £CD EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54b2 £CE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54b3 £CF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54b4 £CG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54b5 £CH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54b6 £CI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54b7 £CJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54b8 £CK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54b9 £CL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54ba £CM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54bb £CN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54bc £CO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54bd £CP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54be £CQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54bf £CR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54c0 £CS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54c1 £CT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54c2 £CU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54c3 £CV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54c4 £CW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54c5 £CX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54c6 £CY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54c7 £CZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54c8 £DA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54c9 £DB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54ca £DC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54cb £DD EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54cc £DE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54cd £DF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54ce £DG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54cf £DH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54d0 £DI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54d1 £DJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54d2 £DK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54d3 £DL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54d4 £DM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54d5 £DN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54d6 £DO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54d7 £DP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54d8 £DQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54d9 £DR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54da £DS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54db £DT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54dc £DU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54dd £DV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54de £DW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54df £DX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54e0 £DY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54e1 £DZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54e2 £EA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54e3 £EB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54e4 £EC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54e5 £ED EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54e6 £EE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54e7 £EF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54e8 £EG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54e9 £EH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54ea £EI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54eb £EJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54ec £EK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54ed £EL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54ee £EM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54ef £EN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54f0 £EO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54f1 £EP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54f2 £EQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54f3 £ER EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54f4 £ES EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54f5 £ET EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54f6 £EU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54f7 £EV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54f8 £EW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54f9 £EX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54fa £EY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54fb £EZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54fc £FA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54fd £FB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54fe £FC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54ff £FD EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5500 £FE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5501 £FF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5502 £FG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5503 £FH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5504 £FI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5505 £FJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5506 £FK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5507 £FL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5508 £FM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5509 £FN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x550a £FO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x550b £FP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x550c £FQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x550d £FR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x550e £FS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x550f £FT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5510 £FU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5511 £FV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5512 £FW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5513 £FX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5514 £FY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5515 £FZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5516 £GA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5517 £GB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5518 £GC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5519 £GD EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x551a £GE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x551b £GF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x551c £GG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x551d £GH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x551e £GI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x551f £GJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5520 £GK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5521 £GL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5522 £GM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5523 £GN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5524 £GO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5525 £GP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5526 £GQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5527 £GR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5528 £GS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5529 £GT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x552a £GU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x552b £GV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x552c £GW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x552d £GX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x552e £GY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x552f £GZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5530 £HA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5531 £HB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5532 £HC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5533 £HD EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5534 £HE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5535 £HF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5536 £HG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5537 £HH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5538 £HI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5539 £HJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x553a £HK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x553b £HL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x553c £HM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x553d £HN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x553e £HO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x553f £HP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5540 £HQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5541 £HR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5542 £HS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5543 £HT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5544 £HU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5545 £HV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5546 £HW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5547 £HX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5548 £HY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5549 £HZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x554a £IA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x554b £IB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x554c £IC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x554d £ID EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x554e £IE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x554f £IF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5550 £IG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5551 £IH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5552 £II EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5553 £IJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5554 £IK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5555 £IL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5556 £IM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5557 £IN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5558 £IO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5559 £IP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x555a £IQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x555b £IR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x555c £IS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x555d £IT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x555e £IU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x555f £IV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5560 £IW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5561 £IX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5562 £IY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5563 £IZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5564 £JA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5565 £JB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5566 £JC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5567 £JD EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5568 £JE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5569 £JF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x556a £JG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x556b £JH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x556c £JI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x556d £JJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x556e £JK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x556f £JL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5570 £JM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5571 £JN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5572 £JO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5573 £JP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5574 £JQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5575 £JR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5576 £JS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5577 £JT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5578 £JU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5579 £JV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x557a £JW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x557b £JX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x557c £JY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x557d £JZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x557e £KA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x557f £KB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5580 £KC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5581 £KD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5582 £KE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5583 £KF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5584 £KG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5585 £KH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5586 £KI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5587 £KJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5588 £KK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5589 £KL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x558a £KM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x558b £KN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x558c £KO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x558d £KP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x558e £KQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x558f £KR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5590 £KS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5591 £KT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5592 £KU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5593 £KV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5594 £KW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5595 £KX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5596 £KY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5597 £KZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5598 £LA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5599 £LB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x559a £LC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x559b £LD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x559c £LE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x559d £LF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x559e £LG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x559f £LH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55a0 £LI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55a1 £LJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55a2 £LK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55a3 £LL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55a4 £LM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55a5 £LN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55a6 £LO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55a7 £LP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55a8 £LQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55a9 £LR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55aa £LS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55ab £LT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55ac £LU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55ad £LV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55ae £LW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55af £LX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55b0 £LY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55b1 £LZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55b2 £MA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55b3 £MB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55b4 £MC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55b5 £MD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55b6 £ME EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55b7 £MF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55b8 £MG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55b9 £MH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55ba £MI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55bb £MJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55bc £MK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55bd £ML EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55be £MM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55bf £MN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55c0 £MO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55c1 £MP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55c2 £MQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55c3 £MR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55c4 £MS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55c5 £MT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55c6 £MU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55c7 £MV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55c8 £MW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55c9 £MX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55ca £MY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55cb £MZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55cc £NA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55cd £NB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55ce £NC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55cf £ND EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55d0 £NE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55d1 £NF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55d2 £NG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55d3 £NH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55d4 £NI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55d5 £NJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55d6 £NK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55d7 £NL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55d8 £NM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55d9 £NN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55da £NO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55db £NP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55dc £NQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55dd £NR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55de £NS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55df £NT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55e0 £NU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55e1 £NV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55e2 £NW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55e3 £NX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55e4 £NY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55e5 £NZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55e6 £OA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55e7 £OB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55e8 £OC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55e9 £OD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55ea £OE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55eb £OF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55ec £OG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55ed £OH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55ee £OI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55ef £OJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55f0 £OK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55f1 £OL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55f2 £OM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55f3 £ON EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55f4 £OO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55f5 £OP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55f6 £OQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55f7 £OR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55f8 £OS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55f9 £OT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55fa £OU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55fb £OV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55fc £OW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55fd £OX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55fe £OY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55ff £OZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5600 £PA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5601 £PB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5602 £PC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5603 £PD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5604 £PE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5605 £PF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5606 £PG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5607 £PH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5608 £PI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5609 £PJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x560a £PK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x560b £PL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x560c £PM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x560d £PN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x560e £PO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x560f £PP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5610 £PQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5611 £PR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5612 £PS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5613 £PT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5614 £PU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5615 £PV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5616 £PW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5617 £PX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5618 £PY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5619 £PZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x561a £QA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x561b £QB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x561c £QC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x561d £QD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x561e £QE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x561f £QF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5620 £QG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5621 £QH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5622 £QI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5623 £QJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5624 £QK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5625 £QL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5626 £QM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5627 £QN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5628 £QO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5629 £QP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x562a £QQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x562b £QR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x562c £QS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x562d £QT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x562e £QU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x562f £QV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5630 £QW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5631 £QX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5632 £QY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5633 £QZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5634 £RA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5635 £RB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5636 £RC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5637 £RD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5638 £RE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5639 £RF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x563a £RG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x563b £RH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x563c £RI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x563d £RJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x563e £RK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x563f £RL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5640 £RM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5641 £RN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5642 £RO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5643 £RP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5644 £RQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5645 £RR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5646 £RS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5647 £RT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5648 £RU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5649 £RV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x564a £RW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x564b £RX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x564c £RY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x564d £RZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x564e £SA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x564f £SB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5650 £SC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5651 £SD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5652 £SE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5653 £SF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5654 £SG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5655 £SH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5656 £SI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5657 £SJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5658 £SK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5659 £SL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x565a £SM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x565b £SN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x565c £SO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x565d £SP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x565e £SQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x565f £SR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5660 £SS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5661 £ST EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5662 £SU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5663 £SV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5664 £SW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5665 £SX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5666 £SY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5667 £SZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5668 £TA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5669 £TB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x566a £TC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x566b £TD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x566c £TE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x566d £TF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x566e £TG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x566f £TH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5670 £TI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5671 £TJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5672 £TK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5673 £TL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5674 £TM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5675 £TN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5676 £TO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5677 £TP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5678 £TQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5679 £TR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x567a £TS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x567b £TT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x567c £TU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x567d £TV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x567e £TW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x567f £TX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5680 £TY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5681 £TZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5682 £UA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5683 £UB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5684 £UC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5685 £UD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5686 £UE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5687 £UF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5688 £UG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5689 £UH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x568a £UI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x568b £UJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x568c £UK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x568d £UL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x568e £UM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x568f £UN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5690 £UO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5691 £UP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5692 £UQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5693 £UR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5694 £US EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5695 £UT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5696 £UU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5697 £UV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5698 £UW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5699 £UX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x569a £UY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x569b £UZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x569c £VA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x569d £VB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x569e £VC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x569f £VD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56a0 £VE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56a1 £VF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56a2 £VG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56a3 £VH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56a4 £VI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56a5 £VJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56a6 £VK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56a7 £VL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56a8 £VM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56a9 £VN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56aa £VO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56ab £VP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56ac £VQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56ad £VR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56ae £VS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56af £VT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56b0 £VU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56b1 £VV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56b2 £VW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56b3 £VX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56b4 £VY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56b5 £VZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56b6 £WA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56b7 £AA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56b8 £AB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56b9 £AC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56ba £AD EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56bb £AE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56bc £AF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56bd £AG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56be £AH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56bf £AI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56c0 £AJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56c1 £AK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56c2 £AL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56c3 £AM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56c4 £AN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56c5 £AO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56c6 £AP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56c7 £AQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56c8 £AR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56c9 £AS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56ca £AT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56cb £AU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56cc £AV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56cd £AW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56ce £AX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56cf £AY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56d0 £AZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56d1 £BA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56d2 £BB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56d3 £BC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56d4 £BD EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56d5 £BE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56d6 £BF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56d7 £BG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56d8 £BH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56d9 £BI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56da £BJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56db £BK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56dc £BL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56dd £BM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56de £BN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56df £BO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56e0 £BP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56e1 £BQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56e2 £BR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56e3 £BS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56e4 £BT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56e5 £BU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56e6 £BV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56e7 £BW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56e8 £BX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56e9 £BY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56ea £BZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56eb £CA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56ec £CB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56ed £CC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56ee £CD EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56ef £CE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56f0 £CF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56f1 £CG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56f2 £CH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56f3 £CI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56f4 £CJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56f5 £CK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56f6 £CL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56f7 £CM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56f8 £CN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56f9 £CO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56fa £CP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56fb £CQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56fc £CR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56fd £CS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56fe £CT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56ff £CU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5700 £CV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5701 £CW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5702 £CX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5703 £CY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5704 £CZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5705 £DA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5706 £DB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5707 £DC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5708 £DD EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5709 £DE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x570a £DF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x570b £DG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x570c £DH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x570d £DI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x570e £DJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x570f £DK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5710 £DL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5711 £DM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5712 £DN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5713 £DO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5714 £DP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5715 £DQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5716 £DR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5717 £DS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5718 £DT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5719 £DU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x571a £DV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x571b £DW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x571c £DX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x571d £DY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x571e £DZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x571f £EA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5720 £EB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5721 £EC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5722 £ED EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5723 £EE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5724 £EF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5725 £EG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5726 £EH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5727 £EI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5728 £EJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5729 £EK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x572a £EL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x572b £EM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x572c £EN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x572d £EO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x572e £EP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x572f £EQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5730 £ER EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5731 £ES EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5732 £ET EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5733 £EU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5734 £EV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5735 £EW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5736 £EX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5737 £EY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5738 £EZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5739 £FA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x573a £FB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x573b £FC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x573c £FD EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x573d £FE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x573e £FF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x573f £FG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5740 £FH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5741 £FI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5742 £FJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5743 £FK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5744 £FL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5745 £FM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5746 £FN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5747 £FO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5748 £FP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5749 £FQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x574a £FR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x574b £FS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x574c £FT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x574d £FU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x574e £FV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x574f £FW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5750 £FX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5751 £FY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5752 £FZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5753 £GA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5754 £GB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5755 £GC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5756 £GD EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5757 £GE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5758 £GF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5759 £GG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x575a £GH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x575b £GI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x575c £GJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x575d £GK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x575e £GL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x575f £GM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5760 £GN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5761 £GO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5762 £GP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5763 £GQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5764 £GR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5765 £GS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5766 £GT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5767 £GU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5768 £GV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5769 £GW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x576a £GX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x576b £GY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x576c £GZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x576d £HA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x576e £HB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x576f £HC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5770 £HD EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5771 £HE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5772 £HF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5773 £HG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5774 £HH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5775 £HI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5776 £HJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5777 £HK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5778 £HL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5779 £HM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x577a £HN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x577b £HO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x577c £HP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x577d £HQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x577e £HR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x577f £HS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5780 £HT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5781 £HU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5782 £HV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5783 £HW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5784 £HX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5785 £HY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5786 £HZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5787 £IA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5788 £IB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5789 £IC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x578a £ID EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x578b £IE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x578c £IF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x578d £IG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x578e £IH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x578f £II EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5790 £IJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5791 £IK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5792 £IL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5793 £IM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5794 £IN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5795 £IO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5796 £IP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5797 £IQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5798 £IR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5799 £IS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x579a £IT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x579b £IU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x579c £IV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x579d £IW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x579e £IX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x579f £IY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57a0 £IZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57a1 £JA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57a2 £JB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57a3 £JC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57a4 £JD EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57a5 £JE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57a6 £JF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57a7 £JG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57a8 £JH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57a9 £JI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57aa £JJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57ab £JK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57ac £JL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57ad £JM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57ae £JN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57af £JO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57b0 £JP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57b1 £JQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57b2 £JR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57b3 £JS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57b4 £JT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57b5 £JU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57b6 £JV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57b7 £JW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57b8 £JX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57b9 £JY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57ba £JZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57bb £KA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57bc £KB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57bd £KC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57be £KD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57bf £KE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57c0 £KF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57c1 £KG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57c2 £KH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57c3 £KI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57c4 £KJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57c5 £KK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57c6 £KL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57c7 £KM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57c8 £KN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57c9 £KO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57ca £KP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57cb £KQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57cc £KR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57cd £KS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57ce £KT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57cf £KU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57d0 £KV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57d1 £KW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57d2 £KX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57d3 £KY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57d4 £KZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57d5 £LA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57d6 £LB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57d7 £LC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57d8 £LD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57d9 £LE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57da £LF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57db £LG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57dc £LH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57dd £LI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57de £LJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57df £LK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57e0 £LL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57e1 £LM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57e2 £LN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57e3 £LO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57e4 £LP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57e5 £LQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57e6 £LR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57e7 £LS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57e8 £LT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57e9 £LU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57ea £LV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57eb £LW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57ec £LX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57ed £LY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57ee £LZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57ef £MA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57f0 £MB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57f1 £MC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57f2 £MD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57f3 £ME EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57f4 £MF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57f5 £MG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57f6 £MH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57f7 £MI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57f8 £MJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57f9 £MK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57fa £ML EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57fb £MM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57fc £MN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57fd £MO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57fe £MP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57ff £MQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5800 £MR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5801 £MS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5802 £MT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5803 £MU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5804 £MV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5805 £MW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5806 £MX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5807 £MY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5808 £MZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5809 £NA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x580a £NB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x580b £NC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x580c £ND EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x580d £NE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x580e £NF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x580f £NG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5810 £NH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5811 £NI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5812 £NJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5813 £NK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5814 £NL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5815 £NM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5816 £NN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5817 £NO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5818 £NP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5819 £NQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x581a £NR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x581b £NS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x581c £NT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x581d £NU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x581e £NV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x581f £NW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5820 £NX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5821 £NY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5822 £NZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5823 £OA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5824 £OB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5825 £OC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5826 £OD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5827 £OE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5828 £OF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5829 £OG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x582a £OH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x582b £OI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x582c £OJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x582d £OK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x582e £OL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x582f £OM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5830 £ON EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5831 £OO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5832 £OP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5833 £OQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5834 £OR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5835 £OS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5836 £OT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5837 £OU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5838 £OV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5839 £OW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x583a £OX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x583b £OY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x583c £OZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x583d £PA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x583e £PB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x583f £PC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5840 £PD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5841 £PE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5842 £PF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5843 £PG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5844 £PH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5845 £PI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5846 £PJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5847 £PK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5848 £PL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5849 £PM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x584a £PN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x584b £PO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x584c £PP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x584d £PQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x584e £PR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x584f £PS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5850 £PT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5851 £PU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5852 £PV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5853 £PW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5854 £PX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5855 £PY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5856 £PZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5857 £QA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5858 £QB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5859 £QC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x585a £QD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x585b £QE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x585c £QF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x585d £QG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x585e £QH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x585f £QI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5860 £QJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5861 £QK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5862 £QL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5863 £QM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5864 £QN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5865 £QO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5866 £QP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5867 £QQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5868 £QR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5869 £QS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x586a £QT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x586b £QU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x586c £QV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x586d £QW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x586e £QX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x586f £QY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5870 £QZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5871 £RA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5872 £RB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5873 £RC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5874 £RD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5875 £RE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5876 £RF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5877 £RG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5878 £RH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5879 £RI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x587a £RJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x587b £RK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x587c £RL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x587d £RM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x587e £RN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x587f £RO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5880 £RP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5881 £RQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5882 £RR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5883 £RS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5884 £RT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5885 £RU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5886 £RV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5887 £RW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5888 £RX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5889 £RY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x588a £RZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x588b £SA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x588c £SB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x588d £SC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x588e £SD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x588f £SE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5890 £SF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5891 £SG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5892 £SH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5893 £SI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5894 £SJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5895 £SK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5896 £SL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5897 £SM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5898 £SN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5899 £SO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x589a £SP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x589b £SQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x589c £SR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x589d £SS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x589e £ST EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x589f £SU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58a0 £SV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58a1 £SW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58a2 £SX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58a3 £SY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58a4 £SZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58a5 £TA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58a6 £TB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58a7 £TC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58a8 £TD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58a9 £TE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58aa £TF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58ab £TG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58ac £TH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58ad £TI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58ae £TJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58af £TK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58b0 £TL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58b1 £TM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58b2 £TN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58b3 £TO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58b4 £TP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58b5 £TQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58b6 £TR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58b7 £TS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58b8 £TT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58b9 £TU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58ba £TV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58bb £TW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58bc £TX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58bd £TY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58be £TZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58bf £UA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58c0 £UB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58c1 £UC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58c2 £UD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58c3 £UE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58c4 £UF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58c5 £UG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58c6 £UH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58c7 £UI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58c8 £UJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58c9 £UK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58ca £UL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58cb £UM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58cc £UN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58cd £UO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58ce £UP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58cf £UQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58d0 £UR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58d1 £US EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58d2 £UT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58d3 £UU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58d4 £UV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58d5 £UW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58d6 £UX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58d7 £UY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58d8 £UZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58d9 £VA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58da £VB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58db £VC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58dc £VD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58dd £VE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58de £VF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58df £VG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58e0 £VH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58e1 £VI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58e2 £VJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58e3 £VK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58e4 £VL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58e5 £VM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58e6 £VN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58e7 £VO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58e8 £VP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58e9 £VQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58ea £VR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58eb £VS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58ec £VT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58ed £VU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58ee £VV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58ef £VW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58f0 £VX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58f1 £VY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58f2 £VZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58f3 £WA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5b00 µAA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b01 µAB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b02 µAC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b03 µAD EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b04 µAE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b05 µAF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b06 µAG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b07 µAH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b08 µAI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b09 µAJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b0a µAK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b0b µAL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b0c µAM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b0d µAN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b0e µAO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b0f µAP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b10 µAQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b11 µAR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b12 µAS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b13 µAT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b14 µAU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b15 µAV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b16 µAW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b17 µAX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b18 µAY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b19 µAZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b1a µBA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b1b µBB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b1c µBC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b1d µBD EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b1e µBE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b1f µBF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b20 µBG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b21 µBH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b22 µBI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b23 µBJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b24 µBK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b25 µBL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b26 µBM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b27 µBN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b28 µBO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b29 µBP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b2a µBQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b2b µBR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b2c µBS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b2d µBT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b2e µBU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b2f µBV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b30 µBW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b31 µBX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b32 µBY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b33 µBZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b34 µCA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b35 µCB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b36 µCC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b37 µCD EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b38 µCE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b39 µCF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b3a µCG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b3b µCH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b3c µCI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b3d µCJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b3e µCK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b3f µCL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b40 µCM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b41 µCN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b42 µCO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b43 µCP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b44 µCQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b45 µCR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b46 µCS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b47 µCT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b48 µCU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b49 µCV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b4a µCW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b4b µCX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b4c µCY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b4d µCZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b4e µDA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b4f µDB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b50 µDC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b51 µDD EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b52 µDE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b53 µDF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b54 µDG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b55 µDH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b56 µDI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b57 µDJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b58 µDK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b59 µDL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b5a µDM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b5b µDN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b5c µDO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b5d µDP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b5e µDQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b5f µDR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b60 µDS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b61 µDT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b62 µDU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b63 µDV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b64 µDW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b65 µDX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b66 µDY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b67 µDZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b68 µEA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b69 µEB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b6a µEC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b6b µED EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b6c µEE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b6d µEF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b6e µEG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b6f µEH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b70 µEI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b71 µEJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b72 µEK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b73 µEL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b74 µEM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b75 µEN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b76 µEO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b77 µEP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b78 µEQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b79 µER EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b7a µES EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b7b µET EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b7c µEU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b7d µEV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b7e µEW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b7f µEX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b80 µEY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b81 µEZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b82 µFA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b83 µFB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b84 µFC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b85 µFD EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b86 µFE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b87 µFF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b88 µFG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b89 µFH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b8a µFI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b8b µFJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b8c µFK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b8d µFL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b8e µFM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b8f µFN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b90 µFO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b91 µFP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b92 µFQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b93 µFR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b94 µFS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b95 µFT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b96 µFU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b97 µFV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b98 µFW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b99 µFX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b9a µFY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b9b µFZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b9c µGA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b9d µGB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b9e µGC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b9f µGD EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5ba0 µGE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5ba1 µGF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5ba2 µGG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5ba3 µGH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5ba4 µGI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5ba5 µGJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5ba6 µGK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5ba7 µGL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5ba8 µGM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5ba9 µGN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5baa µGO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bab µGP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bac µGQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bad µGR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bae µGS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5baf µGT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bb0 µGU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bb1 µGV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bb2 µGW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bb3 µGX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bb4 µGY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bb5 µGZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bb6 µHA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bb7 µHB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bb8 µHC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bb9 µHD EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bba µHE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bbb µHF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bbc µHG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bbd µHH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bbe µHI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bbf µHJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bc0 µHK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bc1 µHL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bc2 µHM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bc3 µHN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bc4 µHO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bc5 µHP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bc6 µHQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bc7 µHR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bc8 µHS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bc9 µHT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bca µHU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bcb µHV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bcc µHW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bcd µHX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bce µHY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bcf µHZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bd0 µIA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bd1 µIB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bd2 µIC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bd3 µID EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bd4 µIE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bd5 µIF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bd6 µIG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bd7 µIH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bd8 µII EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bd9 µIJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bda µIK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bdb µIL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bdc µIM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bdd µIN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bde µIO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bdf µIP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5be0 µIQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5be1 µIR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5be2 µIS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5be3 µIT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5be4 µIU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5be5 µIV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5be6 µIW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5be7 µIX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5be8 µIY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5be9 µIZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bea µJA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5beb µJB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bec µJC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bed µJD EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bee µJE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bef µJF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bf0 µJG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bf1 µJH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bf2 µJI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bf3 µJJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bf4 µJK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bf5 µJL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bf6 µJM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bf7 µJN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bf8 µJO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bf9 µJP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bfa µJQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bfb µJR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bfc µJS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bfd µJT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bfe µJU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bff µJV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5c00 µJW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c01 µJX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c02 µJY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c03 µJZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c04 µKA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c05 µKB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c06 µKC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c07 µKD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c08 µKE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c09 µKF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c0a µKG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c0b µKH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c0c µKI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c0d µKJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c0e µKK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c0f µKL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c10 µKM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c11 µKN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c12 µKO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c13 µKP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c14 µKQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c15 µKR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c16 µKS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c17 µKT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c18 µKU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c19 µKV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c1a µKW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c1b µKX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c1c µKY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c1d µKZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c1e µLA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c1f µLB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c20 µLC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c21 µLD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c22 µLE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c23 µLF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c24 µLG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c25 µLH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c26 µLI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c27 µLJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c28 µLK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c29 µLL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c2a µLM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c2b µLN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c2c µLO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c2d µLP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c2e µLQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c2f µLR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c30 µLS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c31 µLT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c32 µLU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c33 µLV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c34 µLW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c35 µLX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c36 µLY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c37 µLZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c38 µMA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c39 µMB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c3a µMC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c3b µMD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c3c µME EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c3d µMF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c3e µMG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c3f µMH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c40 µMI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c41 µMJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c42 µMK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c43 µML EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c44 µMM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c45 µMN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c46 µMO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c47 µMP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c48 µMQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c49 µMR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c4a µMS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c4b µMT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c4c µMU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c4d µMV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c4e µMW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c4f µMX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c50 µMY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c51 µMZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c52 µNA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c53 µNB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c54 µNC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c55 µND EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c56 µNE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c57 µNF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c58 µNG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c59 µNH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c5a µNI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c5b µNJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c5c µNK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c5d µNL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c5e µNM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c5f µNN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c60 µNO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c61 µNP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c62 µNQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c63 µNR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c64 µNS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c65 µNT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c66 µNU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c67 µNV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c68 µNW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c69 µNX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c6a µNY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c6b µNZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c6c µOA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c6d µOB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c6e µOC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c6f µOD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c70 µOE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c71 µOF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c72 µOG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c73 µOH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c74 µOI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c75 µOJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c76 µOK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c77 µOL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c78 µOM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c79 µON EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c7a µOO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c7b µOP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c7c µOQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c7d µOR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c7e µOS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c7f µOT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c80 µOU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c81 µOV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c82 µOW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c83 µOX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c84 µOY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c85 µOZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c86 µPA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c87 µPB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c88 µPC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c89 µPD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c8a µPE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c8b µPF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c8c µPG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c8d µPH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c8e µPI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c8f µPJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c90 µPK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c91 µPL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c92 µPM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c93 µPN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c94 µPO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c95 µPP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c96 µPQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c97 µPR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c98 µPS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c99 µPT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c9a µPU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c9b µPV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c9c µPW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c9d µPX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c9e µPY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c9f µPZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ca0 µQA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ca1 µQB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ca2 µQC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ca3 µQD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ca4 µQE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ca5 µQF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ca6 µQG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ca7 µQH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ca8 µQI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ca9 µQJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5caa µQK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cab µQL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cac µQM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cad µQN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cae µQO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5caf µQP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cb0 µQQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cb1 µQR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cb2 µQS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cb3 µQT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cb4 µQU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cb5 µQV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cb6 µQW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cb7 µQX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cb8 µQY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cb9 µQZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cba µRA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cbb µRB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cbc µRC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cbd µRD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cbe µRE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cbf µRF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cc0 µRG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cc1 µRH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cc2 µRI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cc3 µRJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cc4 µRK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cc5 µRL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cc6 µRM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cc7 µRN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cc8 µRO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cc9 µRP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cca µRQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ccb µRR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ccc µRS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ccd µRT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cce µRU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ccf µRV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cd0 µRW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cd1 µRX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cd2 µRY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cd3 µRZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cd4 µSA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cd5 µSB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cd6 µSC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cd7 µSD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cd8 µSE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cd9 µSF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cda µSG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cdb µSH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cdc µSI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cdd µSJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cde µSK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cdf µSL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ce0 µSM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ce1 µSN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ce2 µSO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ce3 µSP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ce4 µSQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ce5 µSR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ce6 µSS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ce7 µST EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ce8 µSU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ce9 µSV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cea µSW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ceb µSX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cec µSY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ced µSZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cee µTA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cef µTB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cf0 µTC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cf1 µTD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cf2 µTE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cf3 µTF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cf4 µTG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cf5 µTH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cf6 µTI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cf7 µTJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cf8 µTK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cf9 µTL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cfa µTM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cfb µTN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cfc µTO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cfd µTP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cfe µTQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cff µTR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d00 µTS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d01 µTT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d02 µTU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d03 µTV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d04 µTW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d05 µTX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d06 µTY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d07 µTZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d08 µUA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d09 µUB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d0a µUC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d0b µUD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d0c µUE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d0d µUF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d0e µUG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d0f µUH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d10 µUI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d11 µUJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d12 µUK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d13 µUL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d14 µUM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d15 µUN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d16 µUO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d17 µUP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d18 µUQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d19 µUR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d1a µUS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d1b µUT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d1c µUU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d1d µUV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d1e µUW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d1f µUX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d20 µUY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d21 µUZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d22 µVA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d23 µVB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d24 µVC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d25 µVD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d26 µVE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d27 µVF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d28 µVG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d29 µVH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d2a µVI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d2b µVJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d2c µVK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d2d µVL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d2e µVM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d2f µVN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d30 µVO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d31 µVP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d32 µVQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d33 µVR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d34 µVS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d35 µVT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d36 µVU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d37 µVV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d38 µVW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d39 µVX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d3a µVY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d3b µVZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d3d µAA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d3e µAB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d3f µAC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d40 µAD EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d41 µAE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d42 µAF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d43 µAG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d44 µAH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d45 µAI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d46 µAJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d47 µAK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d48 µAL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d49 µAM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d4a µAN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d4b µAO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d4c µAP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d4d µAQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d4e µAR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d4f µAS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d50 µAT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d51 µAU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d52 µAV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d53 µAW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d54 µAX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d55 µAY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d56 µAZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d57 µBA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d58 µBB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d59 µBC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d5a µBD EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d5b µBE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d5c µBF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d5d µBG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d5e µBH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d5f µBI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d60 µBJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d61 µBK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d62 µBL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d63 µBM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d64 µBN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d65 µBO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d66 µBP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d67 µBQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d68 µBR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d69 µBS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d6a µBT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d6b µBU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d6c µBV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d6d µBW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d6e µBX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d6f µBY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d70 µBZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d71 µCA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d72 µCB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d73 µCC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d74 µCD EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d75 µCE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d76 µCF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d77 µCG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d78 µCH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d79 µCI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d7a µCJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d7b µCK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d7c µCL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d7d µCM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d7e µCN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d7f µCO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d80 µCP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d81 µCQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d82 µCR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d83 µCS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d84 µCT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d85 µCU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d86 µCV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d87 µCW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d88 µCX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d89 µCY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d8a µCZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d8b µDA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d8c µDB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d8d µDC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d8e µDD EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d8f µDE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d90 µDF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d91 µDG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d92 µDH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d93 µDI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d94 µDJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d95 µDK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d96 µDL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d97 µDM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d98 µDN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d99 µDO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d9a µDP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d9b µDQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d9c µDR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d9d µDS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d9e µDT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d9f µDU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5da0 µDV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5da1 µDW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5da2 µDX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5da3 µDY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5da4 µDZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5da5 µEA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5da6 µEB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5da7 µEC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5da8 µED EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5da9 µEE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5daa µEF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dab µEG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dac µEH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dad µEI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dae µEJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5daf µEK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5db0 µEL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5db1 µEM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5db2 µEN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5db3 µEO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5db4 µEP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5db5 µEQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5db6 µER EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5db7 µES EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5db8 µET EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5db9 µEU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dba µEV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dbb µEW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dbc µEX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dbd µEY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dbe µEZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dbf µFA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dc0 µFB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dc1 µFC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dc2 µFD EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dc3 µFE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dc4 µFF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dc5 µFG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dc6 µFH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dc7 µFI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dc8 µFJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dc9 µFK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dca µFL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dcb µFM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dcc µFN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dcd µFO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dce µFP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dcf µFQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dd0 µFR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dd1 µFS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dd2 µFT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dd3 µFU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dd4 µFV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dd5 µFW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dd6 µFX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dd7 µFY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dd8 µFZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dd9 µGA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dda µGB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5ddb µGC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5ddc µGD EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5ddd µGE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dde µGF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5ddf µGG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5de0 µGH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5de1 µGI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5de2 µGJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5de3 µGK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5de4 µGL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5de5 µGM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5de6 µGN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5de7 µGO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5de8 µGP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5de9 µGQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dea µGR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5deb µGS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dec µGT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5ded µGU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dee µGV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5def µGW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5df0 µGX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5df1 µGY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5df2 µGZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5df3 µHA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5df4 µHB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5df5 µHC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5df6 µHD EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5df7 µHE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5df8 µHF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5df9 µHG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dfa µHH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dfb µHI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dfc µHJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dfd µHK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dfe µHL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dff µHM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e00 µHN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e01 µHO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e02 µHP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e03 µHQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e04 µHR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e05 µHS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e06 µHT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e07 µHU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e08 µHV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e09 µHW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e0a µHX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e0b µHY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e0c µHZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e0d µIA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e0e µIB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e0f µIC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e10 µID EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e11 µIE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e12 µIF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e13 µIG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e14 µIH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e15 µII EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e16 µIJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e17 µIK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e18 µIL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e19 µIM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e1a µIN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e1b µIO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e1c µIP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e1d µIQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e1e µIR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e1f µIS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e20 µIT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e21 µIU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e22 µIV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e23 µIW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e24 µIX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e25 µIY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e26 µIZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e27 µJA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e28 µJB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e29 µJC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e2a µJD EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e2b µJE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e2c µJF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e2d µJG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e2e µJH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e2f µJI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e30 µJJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e31 µJK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e32 µJL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e33 µJM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e34 µJN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e35 µJO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e36 µJP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e37 µJQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e38 µJR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e39 µJS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e3a µJT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e3b µJU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e3c µJV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e3d µJW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e3e µJX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e3f µJY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e40 µJZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e41 µKA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e42 µKB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e43 µKC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e44 µKD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e45 µKE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e46 µKF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e47 µKG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e48 µKH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e49 µKI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e4a µKJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e4b µKK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e4c µKL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e4d µKM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e4e µKN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e4f µKO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e50 µKP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e51 µKQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e52 µKR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e53 µKS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e54 µKT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e55 µKU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e56 µKV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e57 µKW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e58 µKX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e59 µKY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e5a µKZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e5b µLA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e5c µLB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e5d µLC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e5e µLD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e5f µLE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e60 µLF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e61 µLG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e62 µLH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e63 µLI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e64 µLJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e65 µLK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e66 µLL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e67 µLM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e68 µLN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e69 µLO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e6a µLP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e6b µLQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e6c µLR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e6d µLS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e6e µLT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e6f µLU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e70 µLV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e71 µLW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e72 µLX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e73 µLY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e74 µLZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e75 µMA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e76 µMB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e77 µMC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e78 µMD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e79 µME EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e7a µMF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e7b µMG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e7c µMH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e7d µMI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e7e µMJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e7f µMK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e80 µML EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e81 µMM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e82 µMN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e83 µMO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e84 µMP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e85 µMQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e86 µMR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e87 µMS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e88 µMT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e89 µMU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e8a µMV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e8b µMW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e8c µMX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e8d µMY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e8e µMZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e8f µNA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e90 µNB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e91 µNC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e92 µND EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e93 µNE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e94 µNF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e95 µNG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e96 µNH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e97 µNI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e98 µNJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e99 µNK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e9a µNL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e9b µNM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e9c µNN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e9d µNO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e9e µNP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e9f µNQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ea0 µNR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ea1 µNS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ea2 µNT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ea3 µNU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ea4 µNV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ea5 µNW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ea6 µNX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ea7 µNY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ea8 µNZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ea9 µOA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eaa µOB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eab µOC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eac µOD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ead µOE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eae µOF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eaf µOG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eb0 µOH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eb1 µOI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eb2 µOJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eb3 µOK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eb4 µOL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eb5 µOM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eb6 µON EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eb7 µOO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eb8 µOP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eb9 µOQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eba µOR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ebb µOS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ebc µOT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ebd µOU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ebe µOV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ebf µOW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ec0 µOX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ec1 µOY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ec2 µOZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ec3 µPA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ec4 µPB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ec5 µPC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ec6 µPD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ec7 µPE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ec8 µPF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ec9 µPG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eca µPH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ecb µPI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ecc µPJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ecd µPK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ece µPL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ecf µPM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ed0 µPN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ed1 µPO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ed2 µPP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ed3 µPQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ed4 µPR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ed5 µPS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ed6 µPT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ed7 µPU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ed8 µPV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ed9 µPW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eda µPX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5edb µPY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5edc µPZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5edd µQA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ede µQB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5edf µQC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ee0 µQD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ee1 µQE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ee2 µQF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ee3 µQG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ee4 µQH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ee5 µQI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ee6 µQJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ee7 µQK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ee8 µQL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ee9 µQM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eea µQN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eeb µQO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eec µQP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eed µQQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eee µQR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eef µQS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ef0 µQT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ef1 µQU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ef2 µQV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ef3 µQW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ef4 µQX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ef5 µQY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ef6 µQZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ef7 µRA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ef8 µRB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ef9 µRC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5efa µRD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5efb µRE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5efc µRF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5efd µRG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5efe µRH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eff µRI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f00 µRJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f01 µRK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f02 µRL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f03 µRM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f04 µRN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f05 µRO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f06 µRP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f07 µRQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f08 µRR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f09 µRS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f0a µRT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f0b µRU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f0c µRV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f0d µRW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f0e µRX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f0f µRY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f10 µRZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f11 µSA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f12 µSB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f13 µSC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f14 µSD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f15 µSE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f16 µSF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f17 µSG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f18 µSH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f19 µSI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f1a µSJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f1b µSK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f1c µSL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f1d µSM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f1e µSN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f1f µSO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f20 µSP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f21 µSQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f22 µSR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f23 µSS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f24 µST EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f25 µSU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f26 µSV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f27 µSW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f28 µSX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f29 µSY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f2a µSZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f2b µTA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f2c µTB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f2d µTC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f2e µTD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f2f µTE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f30 µTF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f31 µTG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f32 µTH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f33 µTI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f34 µTJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f35 µTK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f36 µTL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f37 µTM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f38 µTN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f39 µTO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f3a µTP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f3b µTQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f3c µTR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f3d µTS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f3e µTT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f3f µTU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f40 µTV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f41 µTW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f42 µTX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f43 µTY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f44 µTZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f45 µUA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f46 µUB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f47 µUC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f48 µUD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f49 µUE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f4a µUF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f4b µUG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f4c µUH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f4d µUI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f4e µUJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f4f µUK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f50 µUL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f51 µUM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f52 µUN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f53 µUO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f54 µUP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f55 µUQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f56 µUR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f57 µUS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f58 µUT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f59 µUU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f5a µUV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f5b µUW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f5c µUX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f5d µUY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f5e µUZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f5f µVA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f60 µVB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f61 µVC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f62 µVD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f63 µVE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f64 µVF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f65 µVG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f66 µVH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f67 µVI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f68 µVJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f69 µVK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f6a µVL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f6b µVM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f6c µVN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f6d µVO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f6e µVP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f6f µVQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f70 µVR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f71 µVS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f72 µVT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f73 µVU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f74 µVV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f75 µVW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f76 µVX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f77 µVY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f78 µVZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f7a ØAA EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f7b ØAB EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f7c ØAC EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f7d ØAD EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f7e ØAE EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f7f ØAF EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f80 ØAG EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f81 ØAH EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f82 ØAI EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f83 ØAJ EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f84 ØAK EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f85 ØAL EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f86 ØAM EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f87 ØAN EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f88 ØAO EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f89 ØAP EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f8a ØAQ EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f8b ØAR EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f8c ØAS EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f8d ØAT EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f8e ØAU EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f8f ØAV EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f90 ØAW EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f91 ØAX EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f92 ØAY EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f93 ØAZ EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f94 ØBA EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f95 ØBB EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f96 ØBC EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f97 ØBD EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f98 ØBE EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f99 ØBF EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f9a ØBG EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f9b ØBH EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5f9c ØBI EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5f9d ØBJ EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5f9e ØBK EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5f9f ØBL EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fa0 ØBM EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fa1 ØBN EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fa2 ØBO EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fa3 ØBP EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fa4 ØBQ EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fa5 ØBR EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fa6 ØBS EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fa7 ØBT EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fa8 ØBU EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fa9 ØBV EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5faa ØBW EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fab ØBX EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fac ØBY EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fad ØBZ EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fae ØCA EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5faf ØCB EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fb0 ØCC EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fb1 ØCD EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fb2 ØCE EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fb3 ØCF EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fb4 ØCG EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fb5 ØCH EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fb6 ØCI EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fb7 ØCJ EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fb8 ØCK EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fb9 ØCL EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fba ØCM EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fbb ØCN EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fbc ØCO EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fbd ØCP EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fbe ØCQ EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fbf ØCR EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fc0 ØCS EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fc1 ØCT EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fc2 ØCU EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fc3 ØCV EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fc4 ØCW EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fc5 ØCX EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fc6 ØCY EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fc7 ØCZ EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fc8 ØDA EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fc9 ØDB EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fca ØDC EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fcb ØDD EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fcc ØDE EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fcd ØDF EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fce ØDG EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fcf ØDH EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fd0 ØDI EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fd1 ØDJ EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fd2 ØDK EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fd3 ØDL EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fd4 ØDM EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fd5 ØDN EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fd6 ØDO EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fd7 ØDP EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fd8 ØDQ EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fd9 ØDR EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fda ØDS EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fdb ØDT EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fdc ØDU EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fdd ØDV EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fde ØDW EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fdf ØDX EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fe0 ØDY EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fe1 ØDZ EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fe2 ØEA EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fe3 ØEB EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fe4 ØEC EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fe5 ØED EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fe6 ØEE EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fe7 ØEF EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fe8 ØEG EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fe9 ØEH EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fea ØEI EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5feb ØEJ EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fec ØEK EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fed ØEL EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fee ØEM EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fef ØEN EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5ff0 ØEO EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5ff1 ØEP EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5ff2 ØEQ EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5ff3 ØER EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5ff4 ØES EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5ff5 ØET EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5ff6 ØEU EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5ff7 ØEV EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5ff8 ØEW EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5ff9 ØEX EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5ffa ØEY EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5ffb ØEZ EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5ffc ØFA EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5ffd ØFB EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5ffe ØFC EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fff ØFD EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x6600 ¢AµA EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6601 ¢AµB EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6602 ¢AµC EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6603 ¢AµD EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6604 ¢AµE EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6605 ¢AµF EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6606 ¢AµG EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6607 ¢AµH EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6608 ¢AµI EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6609 ¢AµJ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x660a ¢AµK EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x660b ¢AµL EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x660c ¢AµM EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x660d ¢AµN EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x660e ¢AµO EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x660f ¢AµP EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6610 ¢AµQ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6611 ¢AµR EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6612 ¢AµS EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6613 ¢AµT EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6614 ¢AµU EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6615 ¢AµV EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6616 ¢AµW EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6617 ¢AµX EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6618 ¢AµY EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6619 ¢AµZ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x661a ¢BµA EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x661b ¢BµB EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x661c ¢BµC EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x661d ¢BµD EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x661e ¢BµE EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x661f ¢BµF EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6620 ¢BµG EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6621 ¢BµH EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6622 ¢BµI EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6623 ¢BµJ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6624 ¢BµK EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6625 ¢BµL EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6626 ¢BµM EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6627 ¢BµN EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6628 ¢BµO EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6629 ¢BµP EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x662a ¢BµQ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x662b ¢BµR EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x662c ¢BµS EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x662d ¢BµT EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x662e ¢BµU EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x662f ¢BµV EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6630 ¢BµW EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6631 ¢BµX EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6632 ¢BµY EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6633 ¢BµZ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6634 ¢CµA EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6635 ¢CµB EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6636 ¢CµC EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6637 ¢CµD EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6638 ¢CµE EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6639 ¢CµF EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x663a ¢CµG EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x663b ¢CµH EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x663c ¢CµI EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x663d ¢CµJ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x663e ¢CµK EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x663f ¢CµL EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6640 ¢CµM EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6641 ¢CµN EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6642 ¢CµO EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6643 ¢CµP EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6644 ¢CµQ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6645 ¢CµR EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6646 ¢CµS EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6647 ¢CµT EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6648 ¢CµU EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6649 ¢CµV EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x664a ¢CµW EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x664b ¢CµX EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x664c ¢CµY EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x664d ¢CµZ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x664e ¢DµA EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x664f ¢DµB EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6650 ¢DµC EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6651 ¢DµD EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6652 ¢DµE EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6653 ¢DµF EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6654 ¢DµG EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6655 ¢DµH EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6656 ¢DµI EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6657 ¢DµJ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6658 ¢DµK EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6659 ¢DµL EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x665a ¢DµM EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x665b ¢DµN EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x665c ¢DµO EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x665d ¢DµP EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x665e ¢DµQ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x665f ¢DµR EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6660 ¢DµS EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6661 ¢DµT EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6662 ¢DµU EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6663 ¢DµV EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6664 ¢DµW EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6665 ¢DµX EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6666 ¢DµY EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6667 ¢DµZ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6668 ¢EµA EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6669 ¢EµB EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x666a ¢EµC EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x666b ¢EµD EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x666c ¢EµE EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x666d ¢EµF EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x666e ¢EµG EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x666f ¢EµH EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6670 ¢EµI EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6671 ¢EµJ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6672 ¢EµK EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6673 ¢EµL EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6674 ¢EµM EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6675 ¢EµN EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6676 ¢EµO EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6677 ¢EµP EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6678 ¢EµQ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6679 ¢EµR EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x667a ¢EµS EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x667b ¢EµT EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x667c ¢EµU EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x667d ¢EµV EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x667e ¢EµW EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x667f ¢EµX EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6680 ¢EµY EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6681 ¢EµZ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6682 ¢FµA EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6683 ¢FµB EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6684 ¢FµC EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6685 ¢FµD EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6686 ¢FµE EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6687 ¢FµF EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6688 ¢FµG EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6689 ¢FµH EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x668a ¢FµI EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x668b ¢FµJ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x668c ¢FµK EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x668d ¢FµL EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x668e ¢FµM EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x668f ¢FµN EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6690 ¢FµO EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6691 ¢FµP EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6692 ¢FµQ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6693 ¢FµR EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6694 ¢FµS EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6695 ¢FµT EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6696 ¢FµU EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6697 ¢FµV EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6698 ¢FµW EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6699 ¢FµX EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x669a ¢FµY EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x669b ¢FµZ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x669c ¢GµA EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x669d ¢GµB EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x669e ¢GµC EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x669f ¢GµD EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66a0 ¢GµE EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66a1 ¢GµF EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66a2 ¢GµG EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66a3 ¢GµH EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66a4 ¢GµI EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66a5 ¢GµJ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66a6 ¢GµK EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66a7 ¢GµL EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66a8 ¢GµM EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66a9 ¢GµN EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66aa ¢GµO EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66ab ¢GµP EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66ac ¢GµQ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66ad ¢GµR EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66ae ¢GµS EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66af ¢GµT EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66b0 ¢GµU EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66b1 ¢GµV EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66b2 ¢GµW EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66b3 ¢GµX EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66b4 ¢GµY EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66b5 ¢GµZ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66b6 ¢HµA EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66b7 ¢HµB EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66b8 ¢HµC EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66b9 ¢HµD EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66ba ¢HµE EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66bb ¢HµF EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66bc ¢HµG EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66bd ¢HµH EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66be ¢HµI EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66bf ¢HµJ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66c0 ¢HµK EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66c1 ¢HµL EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66c2 ¢HµM EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66c3 ¢HµN EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66c4 ¢HµO EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66c5 ¢HµP EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66c6 ¢HµQ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66c7 ¢HµR EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66c8 ¢HµS EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66c9 ¢HµT EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66ca ¢HµU EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66cb ¢HµV EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66cc ¢HµW EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66cd ¢HµX EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66ce ¢HµY EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66cf ¢HµZ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66d0 ¢IµA EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66d1 ¢IµB EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66d2 ¢IµC EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66d3 ¢IµD EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66d4 ¢IµE EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66d5 ¢IµF EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66d6 ¢IµG EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66d7 ¢IµH EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66d8 ¢IµI EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66d9 ¢IµJ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66da ¢IµK EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66db ¢IµL EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66dc ¢IµM EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66dd ¢IµN EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66de ¢IµO EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66df ¢IµP EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66e0 ¢IµQ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66e1 ¢IµR EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66e2 ¢IµS EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66e3 ¢IµT EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66e4 ¢IµU EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66e5 ¢IµV EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66e6 ¢IµW EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66e7 ¢IµX EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66e8 ¢IµY EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66e9 ¢IµZ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66ea ¢JµA EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66eb ¢JµB EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66ec ¢JµC EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66ed ¢JµD EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66ee ¢JµE EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66ef ¢JµF EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66f0 ¢JµG EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66f1 ¢JµH EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66f2 ¢JµI EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66f3 ¢JµJ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66f4 ¢JµK EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66f5 ¢JµL EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66f6 ¢JµM EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66f7 ¢JµN EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66f8 ¢JµO EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66f9 ¢JµP EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66fa ¢JµQ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66fb ¢JµR EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66fc ¢JµS EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66fd ¢JµT EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66fe ¢JµU EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66ff ¢JµV EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6700 ¢JµW EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6701 ¢JµX EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6702 ¢JµY EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6703 ¢JµZ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6704 ¢KµA EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6705 ¢KµB EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6706 ¢KµC EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6707 ¢KµD EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6708 ¢KµE EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6709 ¢KµF EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x670a ¢KµG EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x670b ¢KµH EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x670c ¢KµI EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x670d ¢KµJ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x670e ¢KµK EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x670f ¢KµL EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6710 ¢KµM EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6711 ¢KµN EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6712 ¢KµO EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6713 ¢KµP EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6714 ¢KµQ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6715 ¢KµR EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6716 ¢KµS EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6717 ¢KµT EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6718 ¢KµU EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6719 ¢KµV EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x671a ¢KµW EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x671b ¢KµX EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x671c ¢KµY EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x671d ¢KµZ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x671e ¢LµA EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x671f ¢LµB EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6720 ¢LµC EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6721 ¢LµD EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6722 ¢LµE EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6723 ¢LµF EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6724 ¢LµG EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6725 ¢LµH EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6726 ¢LµI EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6727 ¢LµJ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6728 ¢LµK EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6729 ¢LµL EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x672a ¢LµM EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x672b ¢LµN EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x672c ¢LµO EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x672d ¢LµP EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x672e ¢LµQ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x672f ¢LµR EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6730 ¢LµS EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6731 ¢LµT EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6732 ¢LµU EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6733 ¢LµV EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6734 ¢LµW EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6735 ¢LµX EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6736 ¢LµY EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6737 ¢LµZ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6738 ¢MµA EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6739 ¢MµB EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x673a ¢MµC EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x673b ¢MµD EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x673c ¢MµE EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x673d ¢MµF EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x673e ¢MµG EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x673f ¢MµH EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6740 ¢MµI EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6741 ¢MµJ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6742 ¢MµK EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6743 ¢MµL EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6744 ¢MµM EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6745 ¢MµN EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6746 ¢MµO EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6747 ¢MµP EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6748 ¢MµQ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6749 ¢MµR EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x674a ¢MµS EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x674b ¢MµT EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x674c ¢MµU EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x674d ¢MµV EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x674e ¢MµW EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x674f ¢MµX EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6750 ¢MµY EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6751 ¢MµZ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6752 ¢NµA EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6753 ¢NµB EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6754 ¢NµC EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6755 ¢NµD EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6756 ¢NµE EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6757 ¢NµF EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6758 ¢NµG EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6759 ¢NµH EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x675a ¢NµI EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x675b ¢NµJ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x675c ¢NµK EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x675d ¢NµL EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x675e ¢NµM EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x675f ¢NµN EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6760 ¢NµO EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6761 ¢NµP EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6762 ¢NµQ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6763 ¢NµR EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6764 ¢NµS EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6765 ¢NµT EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6766 ¢NµU EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6767 ¢NµV EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6768 ¢NµW EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6769 ¢NµX EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x676a ¢NµY EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x676b ¢NµZ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x676c ¢OµA EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x676d ¢OµB EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x676e ¢OµC EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x676f ¢OµD EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6770 ¢OµE EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6771 ¢OµF EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6772 ¢OµG EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6773 ¢OµH EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6774 ¢OµI EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6775 ¢OµJ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6776 ¢OµK EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6777 ¢OµL EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6778 ¢OµM EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6779 ¢OµN EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x677a ¢OµO EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x677b ¢OµP EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x677c ¢OµQ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x677d ¢OµR EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x677e ¢OµS EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x677f ¢OµT EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6780 ¢OµU EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6781 ¢OµV EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6782 ¢OµW EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6783 ¢OµX EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6784 ¢OµY EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6785 ¢OµZ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6786 ¢PµA EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6787 ¢PµB EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6788 ¢PµC EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6789 ¢PµD EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x678a ¢PµE EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x678b ¢PµF EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x678c ¢PµG EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x678d ¢PµH EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x678e ¢PµI EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x678f ¢PµJ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6790 ¢PµK EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6791 ¢PµL EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6792 ¢PµM EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6793 ¢PµN EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6794 ¢PµO EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6795 ¢PµP EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6796 ¢PµQ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6797 ¢PµR EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6798 ¢PµS EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6799 ¢PµT EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x679a ¢PµU EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x679b ¢PµV EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x679c ¢PµW EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x679d ¢PµX EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x679e ¢PµY EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x679f ¢PµZ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67a0 ¢QµA EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67a1 ¢QµB EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67a2 ¢QµC EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67a3 ¢QµD EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67a4 ¢QµE EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67a5 ¢QµF EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67a6 ¢QµG EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67a7 ¢QµH EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67a8 ¢QµI EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67a9 ¢QµJ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67aa ¢QµK EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67ab ¢QµL EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67ac ¢QµM EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67ad ¢QµN EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67ae ¢QµO EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67af ¢QµP EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67b0 ¢QµQ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67b1 ¢QµR EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67b2 ¢QµS EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67b3 ¢QµT EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67b4 ¢QµU EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67b5 ¢QµV EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67b6 ¢QµW EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67b7 ¢QµX EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67b8 ¢QµY EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67b9 ¢QµZ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67ba ¢RµA EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67bb ¢RµB EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67bc ¢RµC EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67bd ¢RµD EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67be ¢RµE EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67bf ¢RµF EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67c0 ¢RµG EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67c1 ¢RµH EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67c2 ¢RµI EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67c3 ¢RµJ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67c4 ¢RµK EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67c5 ¢RµL EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67c6 ¢RµM EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67c7 ¢RµN EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67c8 ¢RµO EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67c9 ¢RµP EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67ca ¢RµQ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67cb ¢RµR EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67cc ¢RµS EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67cd ¢RµT EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67ce ¢RµU EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67cf ¢RµV EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67d0 ¢RµW EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67d1 ¢RµX EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67d2 ¢RµY EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67d3 ¢RµZ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67d4 ¢SµA EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67d5 ¢SµB EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67d6 ¢SµC EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67d7 ¢SµD EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67d8 ¢SµE EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67d9 ¢SµF EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67da ¢SµG EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67db ¢SµH EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67dc ¢SµI EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67dd ¢SµJ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67de ¢SµK EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67df ¢SµL EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67e0 ¢SµM EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67e1 ¢SµN EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67e2 ¢SµO EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67e3 ¢SµP EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67e4 ¢SµQ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67e5 ¢SµR EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67e6 ¢SµS EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67e7 ¢SµT EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67e8 ¢SµU EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67e9 ¢SµV EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67ea ¢SµW EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67eb ¢SµX EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67ec ¢SµY EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67ed ¢SµZ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67ee ¢TµA EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67ef ¢TµB EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67f0 ¢TµC EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67f1 ¢TµD EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67f2 ¢TµE EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67f3 ¢TµF EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67f4 ¢TµG EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67f5 ¢TµH EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67f6 ¢TµI EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67f7 ¢TµJ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67f8 ¢TµK EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67f9 ¢TµL EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67fa ¢TµM EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67fb ¢TµN EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67fc ¢TµO EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67fd ¢TµP EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67fe ¢TµQ EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67ff ¢TµR EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6800 ¢TµS EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6801 ¢TµT EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6802 ¢TµU EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6803 ¢TµV EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6804 ¢TµW EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6805 ¢TµX EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6806 ¢TµY EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6807 ¢TµZ EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6808 ¢UµA EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6809 ¢UµB EMPTY LARGE 5 Co CHARACTER BGII paletted +0x680a ¢UµC EMPTY LARGE 5 Co CHARACTER BGII paletted +0x680b ¢UµD EMPTY LARGE 5 Co CHARACTER BGII paletted +0x680c ¢UµE EMPTY LARGE 5 Co CHARACTER BGII paletted +0x680d ¢UµF EMPTY LARGE 5 Co CHARACTER BGII paletted +0x680e ¢UµG EMPTY LARGE 5 Co CHARACTER BGII paletted +0x680f ¢UµH EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6810 ¢UµI EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6811 ¢UµJ EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6812 ¢UµK EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6813 ¢UµL EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6814 ¢UµM EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6815 ¢UµN EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6816 ¢UµO EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6817 ¢UµP EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6818 ¢UµQ EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6819 ¢UµR EMPTY LARGE 5 Co CHARACTER BGII paletted +0x681a ¢UµS EMPTY LARGE 5 Co CHARACTER BGII paletted +0x681b ¢UµT EMPTY LARGE 5 Co CHARACTER BGII paletted +0x681c ¢UµU EMPTY LARGE 5 Co CHARACTER BGII paletted +0x681d ¢UµV EMPTY LARGE 5 Co CHARACTER BGII paletted +0x681e ¢UµW EMPTY LARGE 5 Co CHARACTER BGII paletted +0x681f ¢UµX EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6820 ¢UµY EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6821 ¢UµZ EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6822 ¢VµA EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6823 ¢VµB EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6824 ¢VµC EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6825 ¢VµD EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6826 ¢VµE EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6827 ¢VµF EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6828 ¢VµG EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6829 ¢VµH EMPTY LARGE 5 Co CHARACTER BGII paletted +0x682a ¢VµI EMPTY LARGE 5 Co CHARACTER BGII paletted +0x682b ¢VµJ EMPTY LARGE 5 Co CHARACTER BGII paletted +0x682c ¢VµK EMPTY LARGE 5 Co CHARACTER BGII paletted +0x682d ¢VµL EMPTY LARGE 5 Co CHARACTER BGII paletted +0x682e ¢VµM EMPTY LARGE 5 Co CHARACTER BGII paletted +0x682f ¢VµN EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6830 ¢VµO EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6831 ¢VµP EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6832 ¢VµQ EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6833 ¢VµR EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6834 ¢VµS EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6835 ¢VµT EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6836 ¢VµU EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6837 ¢VµV EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6838 ¢VµW EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6839 ¢VµX EMPTY LARGE 5 Co CHARACTER BGII paletted +0x683a ¢VµY EMPTY LARGE 5 Co CHARACTER BGII paletted +0x683b ¢VµZ EMPTY LARGE 5 Co CHARACTER BGII paletted +0x683c ¢WµA EMPTY LARGE 5 Co CHARACTER BGII paletted +0x683d ¢A¢A EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x683e ¢A¢B EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x683f ¢A¢C EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x6840 ¢A¢D EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x6841 ¢A¢E EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6842 ¢A¢F EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6843 ¢A¢G EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6844 ¢A¢H EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6845 ¢A¢I EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6846 ¢A¢J EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6847 ¢A¢K EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6848 ¢A¢L EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6849 ¢A¢M EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x684a ¢A¢N EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x684b ¢A¢O EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x684c ¢A¢P EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x684d ¢A¢Q EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x684e ¢A¢R EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x684f ¢A¢S EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x6850 ¢A¢T EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x6851 ¢A¢U EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6852 ¢A¢V EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6853 ¢A¢W EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6854 ¢A¢X EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6855 ¢A¢Y EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6856 ¢A¢Z EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6857 ¢B¢A EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6858 ¢B¢B EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6859 ¢B¢C EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x685a ¢B¢D EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x685b ¢B¢E EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x685c ¢B¢F EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x685d ¢B¢G EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x685e ¢B¢H EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x685f ¢B¢I EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x6860 ¢B¢J EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x6861 ¢B¢K EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6862 ¢B¢L EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6863 ¢B¢M EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6864 ¢B¢N EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6865 ¢B¢O EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6866 ¢B¢P EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6867 ¢B¢Q EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6868 ¢B¢R EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6869 ¢B¢S EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x686a ¢B¢T EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x686b ¢B¢U EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x686c ¢B¢V EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x686d ¢B¢W EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x686e ¢B¢X EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x686f ¢B¢Y EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x6870 ¢B¢Z EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x6871 ¢C¢A EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6872 ¢C¢B EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6873 ¢C¢C EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6874 ¢C¢D EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6875 ¢C¢E EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6876 ¢C¢F EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6877 ¢C¢G EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6878 ¢C¢H EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6879 ¢C¢I EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x687a ¢C¢J EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x687b ¢C¢K EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x687c ¢C¢L EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x687d ¢C¢M EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x687e ¢C¢N EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x687f ¢C¢O EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x6880 ¢C¢P EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x6881 ¢C¢Q EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6882 ¢C¢R EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6883 ¢C¢S EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6884 ¢C¢T EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6885 ¢C¢U EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6886 ¢C¢V EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6887 ¢C¢W EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6888 ¢C¢X EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6889 ¢C¢Y EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x688a ¢C¢Z EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x688b ¢D¢A EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x688c ¢D¢B EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x688d ¢D¢C EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x688e ¢D¢D EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x688f ¢D¢E EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x6890 ¢D¢F EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x6891 ¢D¢G EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6892 ¢D¢H EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6893 ¢D¢I EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6894 ¢D¢J EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6895 ¢D¢K EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6896 ¢D¢L EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6897 ¢D¢M EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6898 ¢D¢N EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6899 ¢D¢O EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x689a ¢D¢P EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x689b ¢D¢Q EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x689c ¢D¢R EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x689d ¢D¢S EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x689e ¢D¢T EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x689f ¢D¢U EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x68a0 ¢D¢V EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x68a1 ¢D¢W EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x68a2 ¢D¢X EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x68a3 ¢D¢Y EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x68a4 ¢D¢Z EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x68a5 ¢E¢A EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x68a6 ¢E¢B EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x68a7 ¢E¢C EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x68a8 ¢E¢D EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x68a9 ¢E¢E EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x68aa ¢E¢F EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x68ab ¢E¢G EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x68ac ¢E¢H EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x68ad ¢E¢I EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x68ae ¢E¢J EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x68af ¢E¢K EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x68b0 ¢E¢L EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x68b1 ¢E¢M EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x68b2 ¢E¢N EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x68b3 ¢E¢O EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x68b4 ¢E¢P EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x68b5 ¢E¢Q EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x68b6 ¢E¢R EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x68b7 ¢E¢S EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x68b8 ¢E¢T EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x68b9 ¢E¢U EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x68ba ¢E¢V EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x68bb ¢E¢W EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x68bc ¢E¢X EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x68bd ¢E¢Y EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x68be ¢E¢Z EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x68bf ¢F¢A EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x68c0 ¢F¢B EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x68c1 ¢F¢C EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x68c2 ¢F¢D EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x68c3 ¢AµA EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68c4 ¢AµB EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68c5 ¢AµC EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68c6 ¢AµD EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68c7 ¢AµE EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68c8 ¢AµF EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68c9 ¢AµG EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68ca ¢AµH EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68cb ¢AµI EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68cc ¢AµJ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68cd ¢AµK EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68ce ¢AµL EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68cf ¢AµM EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68d0 ¢AµN EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68d1 ¢AµO EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68d2 ¢AµP EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68d3 ¢AµQ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68d4 ¢AµR EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68d5 ¢AµS EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68d6 ¢AµT EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68d7 ¢AµU EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68d8 ¢AµV EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68d9 ¢AµW EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68da ¢AµX EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68db ¢AµY EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68dc ¢AµZ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68dd ¢BµA EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68de ¢BµB EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68df ¢BµC EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68e0 ¢BµD EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68e1 ¢BµE EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68e2 ¢BµF EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68e3 ¢BµG EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68e4 ¢BµH EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68e5 ¢BµI EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68e6 ¢BµJ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68e7 ¢BµK EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68e8 ¢BµL EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68e9 ¢BµM EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68ea ¢BµN EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68eb ¢BµO EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68ec ¢BµP EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68ed ¢BµQ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68ee ¢BµR EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68ef ¢BµS EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68f0 ¢BµT EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68f1 ¢BµU EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68f2 ¢BµV EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68f3 ¢BµW EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68f4 ¢BµX EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68f5 ¢BµY EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68f6 ¢BµZ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68f7 ¢CµA EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68f8 ¢CµB EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68f9 ¢CµC EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68fa ¢CµD EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68fb ¢CµE EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68fc ¢CµF EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68fd ¢CµG EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68fe ¢CµH EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68ff ¢CµI EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6900 ¢CµJ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6901 ¢CµK EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6902 ¢CµL EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6903 ¢CµM EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6904 ¢CµN EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6905 ¢CµO EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6906 ¢CµP EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6907 ¢CµQ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6908 ¢CµR EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6909 ¢CµS EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x690a ¢CµT EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x690b ¢CµU EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x690c ¢CµV EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x690d ¢CµW EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x690e ¢CµX EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x690f ¢CµY EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6910 ¢CµZ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6911 ¢DµA EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6912 ¢DµB EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6913 ¢DµC EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6914 ¢DµD EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6915 ¢DµE EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6916 ¢DµF EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6917 ¢DµG EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6918 ¢DµH EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6919 ¢DµI EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x691a ¢DµJ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x691b ¢DµK EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x691c ¢DµL EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x691d ¢DµM EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x691e ¢DµN EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x691f ¢DµO EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6920 ¢DµP EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6921 ¢DµQ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6922 ¢DµR EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6923 ¢DµS EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6924 ¢DµT EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6925 ¢DµU EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6926 ¢DµV EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6927 ¢DµW EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6928 ¢DµX EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6929 ¢DµY EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x692a ¢DµZ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x692b ¢EµA EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x692c ¢EµB EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x692d ¢EµC EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x692e ¢EµD EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x692f ¢EµE EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6930 ¢EµF EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6931 ¢EµG EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6932 ¢EµH EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6933 ¢EµI EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6934 ¢EµJ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6935 ¢EµK EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6936 ¢EµL EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6937 ¢EµM EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6938 ¢EµN EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6939 ¢EµO EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x693a ¢EµP EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x693b ¢EµQ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x693c ¢EµR EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x693d ¢EµS EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x693e ¢EµT EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x693f ¢EµU EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6940 ¢EµV EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6941 ¢EµW EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6942 ¢EµX EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6943 ¢EµY EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6944 ¢EµZ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6945 ¢FµA EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6946 ¢FµB EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6947 ¢FµC EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6948 ¢FµD EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6949 ¢FµE EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x694a ¢FµF EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x694b ¢FµG EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x694c ¢FµH EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x694d ¢FµI EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x694e ¢FµJ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x694f ¢FµK EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6950 ¢FµL EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6951 ¢FµM EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6952 ¢FµN EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6953 ¢FµO EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6954 ¢FµP EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6955 ¢FµQ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6956 ¢FµR EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6957 ¢FµS EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6958 ¢FµT EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6959 ¢FµU EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x695a ¢FµV EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x695b ¢FµW EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x695c ¢FµX EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x695d ¢FµY EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x695e ¢FµZ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x695f ¢GµA EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6960 ¢GµB EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6961 ¢GµC EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6962 ¢GµD EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6963 ¢GµE EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6964 ¢GµF EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6965 ¢GµG EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6966 ¢GµH EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6967 ¢GµI EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6968 ¢GµJ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6969 ¢GµK EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x696a ¢GµL EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x696b ¢GµM EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x696c ¢GµN EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x696d ¢GµO EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x696e ¢GµP EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x696f ¢GµQ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6970 ¢GµR EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6971 ¢GµS EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6972 ¢GµT EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6973 ¢GµU EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6974 ¢GµV EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6975 ¢GµW EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6976 ¢GµX EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6977 ¢GµY EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6978 ¢GµZ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6979 ¢HµA EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x697a ¢HµB EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x697b ¢HµC EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x697c ¢HµD EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x697d ¢HµE EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x697e ¢HµF EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x697f ¢HµG EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6980 ¢HµH EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6981 ¢HµI EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6982 ¢HµJ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6983 ¢HµK EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6984 ¢HµL EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6985 ¢HµM EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6986 ¢HµN EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6987 ¢HµO EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6988 ¢HµP EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6989 ¢HµQ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x698a ¢HµR EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x698b ¢HµS EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x698c ¢HµT EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x698d ¢HµU EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x698e ¢HµV EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x698f ¢HµW EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6990 ¢HµX EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6991 ¢HµY EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6992 ¢HµZ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6993 ¢IµA EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6994 ¢IµB EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6995 ¢IµC EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6996 ¢IµD EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6997 ¢IµE EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6998 ¢IµF EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6999 ¢IµG EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x699a ¢IµH EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x699b ¢IµI EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x699c ¢IµJ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x699d ¢IµK EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x699e ¢IµL EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x699f ¢IµM EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69a0 ¢IµN EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69a1 ¢IµO EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69a2 ¢IµP EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69a3 ¢IµQ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69a4 ¢IµR EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69a5 ¢IµS EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69a6 ¢IµT EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69a7 ¢IµU EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69a8 ¢IµV EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69a9 ¢IµW EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69aa ¢IµX EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69ab ¢IµY EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69ac ¢IµZ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69ad ¢JµA EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69ae ¢JµB EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69af ¢JµC EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69b0 ¢JµD EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69b1 ¢JµE EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69b2 ¢JµF EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69b3 ¢JµG EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69b4 ¢JµH EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69b5 ¢JµI EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69b6 ¢JµJ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69b7 ¢JµK EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69b8 ¢JµL EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69b9 ¢JµM EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69ba ¢JµN EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69bb ¢JµO EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69bc ¢JµP EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69bd ¢JµQ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69be ¢JµR EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69bf ¢JµS EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69c0 ¢JµT EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69c1 ¢JµU EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69c2 ¢JµV EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69c3 ¢JµW EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69c4 ¢JµX EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69c5 ¢JµY EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69c6 ¢JµZ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69c7 ¢KµA EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69c8 ¢KµB EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69c9 ¢KµC EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69ca ¢KµD EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69cb ¢KµE EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69cc ¢KµF EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69cd ¢KµG EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69ce ¢KµH EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69cf ¢KµI EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69d0 ¢KµJ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69d1 ¢KµK EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69d2 ¢KµL EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69d3 ¢KµM EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69d4 ¢KµN EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69d5 ¢KµO EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69d6 ¢KµP EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69d7 ¢KµQ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69d8 ¢KµR EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69d9 ¢KµS EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69da ¢KµT EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69db ¢KµU EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69dc ¢KµV EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69dd ¢KµW EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69de ¢KµX EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69df ¢KµY EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69e0 ¢KµZ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69e1 ¢LµA EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69e2 ¢LµB EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69e3 ¢LµC EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69e4 ¢LµD EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69e5 ¢LµE EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69e6 ¢LµF EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69e7 ¢LµG EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69e8 ¢LµH EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69e9 ¢LµI EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69ea ¢LµJ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69eb ¢LµK EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69ec ¢LµL EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69ed ¢LµM EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69ee ¢LµN EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69ef ¢LµO EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69f0 ¢LµP EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69f1 ¢LµQ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69f2 ¢LµR EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69f3 ¢LµS EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69f4 ¢LµT EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69f5 ¢LµU EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69f6 ¢LµV EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69f7 ¢LµW EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69f8 ¢LµX EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69f9 ¢LµY EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69fa ¢LµZ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69fb ¢MµA EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69fc ¢MµB EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69fd ¢MµC EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69fe ¢MµD EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69ff ¢MµE EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a00 ¢MµF EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a01 ¢MµG EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a02 ¢MµH EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a03 ¢MµI EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a04 ¢MµJ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a05 ¢MµK EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a06 ¢MµL EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a07 ¢MµM EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a08 ¢MµN EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a09 ¢MµO EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a0a ¢MµP EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a0b ¢MµQ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a0c ¢MµR EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a0d ¢MµS EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a0e ¢MµT EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a0f ¢MµU EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a10 ¢MµV EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a11 ¢MµW EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a12 ¢MµX EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a13 ¢MµY EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a14 ¢MµZ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a15 ¢NµA EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a16 ¢NµB EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a17 ¢NµC EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a18 ¢NµD EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a19 ¢NµE EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a1a ¢NµF EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a1b ¢NµG EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a1c ¢NµH EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a1d ¢NµI EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a1e ¢NµJ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a1f ¢NµK EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a20 ¢NµL EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a21 ¢NµM EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a22 ¢NµN EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a23 ¢NµO EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a24 ¢NµP EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a25 ¢NµQ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a26 ¢NµR EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a27 ¢NµS EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a28 ¢NµT EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a29 ¢NµU EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a2a ¢NµV EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a2b ¢NµW EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a2c ¢NµX EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a2d ¢NµY EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a2e ¢NµZ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a2f ¢OµA EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a30 ¢OµB EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a31 ¢OµC EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a32 ¢OµD EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a33 ¢OµE EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a34 ¢OµF EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a35 ¢OµG EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a36 ¢OµH EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a37 ¢OµI EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a38 ¢OµJ EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a39 ¢OµK EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a3a ¢OµL EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a3b ¢OµM EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a3c ¢OµN EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a3d ¢OµO EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a3e ¢OµP EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a3f ¢OµQ EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a40 ¢OµR EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a41 ¢OµS EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a42 ¢OµT EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a43 ¢OµU EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a44 ¢OµV EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a45 ¢OµW EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a46 ¢OµX EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a47 ¢OµY EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a48 ¢OµZ EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a49 ¢PµA EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a4a ¢PµB EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a4b ¢PµC EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a4c ¢PµD EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a4d ¢PµE EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a4e ¢PµF EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a4f ¢PµG EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a50 ¢PµH EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a51 ¢PµI EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a52 ¢PµJ EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a53 ¢PµK EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a54 ¢PµL EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a55 ¢PµM EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a56 ¢PµN EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a57 ¢PµO EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a58 ¢PµP EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a59 ¢PµQ EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a5a ¢PµR EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a5b ¢PµS EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a5c ¢PµT EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a5d ¢PµU EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a5e ¢PµV EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a5f ¢PµW EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a60 ¢PµX EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a61 ¢PµY EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a62 ¢PµZ EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a63 ¢QµA EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a64 ¢QµB EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a65 ¢QµC EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a66 ¢QµD EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a67 ¢QµE EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a68 ¢QµF EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a69 ¢QµG EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a6a ¢QµH EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a6b ¢QµI EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a6c ¢QµJ EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a6d ¢QµK EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a6e ¢QµL EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a6f ¢QµM EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a70 ¢QµN EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a71 ¢QµO EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a72 ¢QµP EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a73 ¢QµQ EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a74 ¢QµR EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a75 ¢QµS EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a76 ¢QµT EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a77 ¢QµU EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a78 ¢QµV EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a79 ¢QµW EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a7a ¢QµX EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a7b ¢QµY EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a7c ¢QµZ EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a7d ¢RµA EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a7e ¢RµB EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a7f ¢RµC EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a80 ¢RµD EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a81 ¢RµE EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a82 ¢RµF EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a83 ¢RµG EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a84 ¢RµH EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a85 ¢RµI EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a86 ¢RµJ EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a87 ¢RµK EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a88 ¢RµL EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a89 ¢RµM EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a8a ¢RµN EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a8b ¢RµO EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a8c ¢RµP EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a8d ¢RµQ EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a8e ¢RµR EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a8f ¢RµS EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a90 ¢RµT EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a91 ¢RµU EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a92 ¢RµV EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a93 ¢RµW EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a94 ¢RµX EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a95 ¢RµY EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a96 ¢RµZ EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a97 ¢SµA EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a98 ¢SµB EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a99 ¢SµC EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a9a ¢SµD EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a9b ¢SµE EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a9c ¢SµF EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a9d ¢SµG EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a9e ¢SµH EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a9f ¢SµI EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aa0 ¢SµJ EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aa1 ¢SµK EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aa2 ¢SµL EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aa3 ¢SµM EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aa4 ¢SµN EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aa5 ¢SµO EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aa6 ¢SµP EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aa7 ¢SµQ EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aa8 ¢SµR EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aa9 ¢SµS EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aaa ¢SµT EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aab ¢SµU EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aac ¢SµV EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aad ¢SµW EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aae ¢SµX EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aaf ¢SµY EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ab0 ¢SµZ EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ab1 ¢TµA EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ab2 ¢TµB EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ab3 ¢TµC EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ab4 ¢TµD EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ab5 ¢TµE EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ab6 ¢TµF EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ab7 ¢TµG EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ab8 ¢TµH EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ab9 ¢TµI EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aba ¢TµJ EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6abb ¢TµK EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6abc ¢TµL EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6abd ¢TµM EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6abe ¢TµN EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6abf ¢TµO EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ac0 ¢TµP EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ac1 ¢TµQ EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ac2 ¢TµR EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ac3 ¢TµS EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ac4 ¢TµT EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ac5 ¢TµU EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ac6 ¢TµV EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ac7 ¢TµW EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ac8 ¢TµX EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ac9 ¢TµY EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aca ¢TµZ EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6acb ¢UµA EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6acc ¢UµB EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6acd ¢UµC EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ace ¢UµD EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6acf ¢UµE EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ad0 ¢UµF EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ad1 ¢UµG EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ad2 ¢UµH EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ad3 ¢UµI EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ad4 ¢UµJ EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ad5 ¢UµK EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ad6 ¢UµL EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ad7 ¢UµM EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ad8 ¢UµN EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ad9 ¢UµO EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ada ¢UµP EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6adb ¢UµQ EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6adc ¢UµR EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6add ¢UµS EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ade ¢UµT EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6adf ¢UµU EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ae0 ¢UµV EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ae1 ¢UµW EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ae2 ¢UµX EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ae3 ¢UµY EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ae4 ¢UµZ EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ae5 ¢VµA EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ae6 ¢VµB EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ae7 ¢VµC EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ae8 ¢VµD EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ae9 ¢VµE EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aea ¢VµF EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aeb ¢VµG EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aec ¢VµH EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aed ¢VµI EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aee ¢VµJ EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aef ¢VµK EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6af0 ¢VµL EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6af1 ¢VµM EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6af2 ¢VµN EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6af3 ¢VµO EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6af4 ¢VµP EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6af5 ¢VµQ EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6af6 ¢VµR EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6af7 ¢VµS EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6af8 ¢VµT EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6af9 ¢VµU EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6afa ¢VµV EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6afb ¢VµW EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6afc ¢VµX EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6afd ¢VµY EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6afe ¢VµZ EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aff ¢WµA EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6b00 ¢AµA EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b01 ¢AµB EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b02 ¢AµC EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b03 ¢AµD EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b04 ¢AµE EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b05 ¢AµF EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b06 ¢AµG EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b07 ¢AµH EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b08 ¢AµI EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b09 ¢AµJ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b0a ¢AµK EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b0b ¢AµL EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b0c ¢AµM EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b0d ¢AµN EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b0e ¢AµO EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b0f ¢AµP EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b10 ¢AµQ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b11 ¢AµR EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b12 ¢AµS EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b13 ¢AµT EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b14 ¢AµU EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b15 ¢AµV EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b16 ¢AµW EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b17 ¢AµX EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b18 ¢AµY EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b19 ¢AµZ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b1a ¢BµA EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b1b ¢BµB EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b1c ¢BµC EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b1d ¢BµD EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b1e ¢BµE EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b1f ¢BµF EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b20 ¢BµG EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b21 ¢BµH EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b22 ¢BµI EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b23 ¢BµJ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b24 ¢BµK EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b25 ¢BµL EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b26 ¢BµM EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b27 ¢BµN EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b28 ¢BµO EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b29 ¢BµP EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b2a ¢BµQ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b2b ¢BµR EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b2c ¢BµS EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b2d ¢BµT EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b2e ¢BµU EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b2f ¢BµV EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b30 ¢BµW EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b31 ¢BµX EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b32 ¢BµY EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b33 ¢BµZ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b34 ¢CµA EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b35 ¢CµB EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b36 ¢CµC EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b37 ¢CµD EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b38 ¢CµE EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b39 ¢CµF EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b3a ¢CµG EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b3b ¢CµH EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b3c ¢CµI EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b3d ¢CµJ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b3e ¢CµK EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b3f ¢CµL EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b40 ¢CµM EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b41 ¢CµN EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b42 ¢CµO EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b43 ¢CµP EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b44 ¢CµQ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b45 ¢CµR EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b46 ¢CµS EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b47 ¢CµT EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b48 ¢CµU EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b49 ¢CµV EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b4a ¢CµW EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b4b ¢CµX EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b4c ¢CµY EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b4d ¢CµZ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b4e ¢DµA EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b4f ¢DµB EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b50 ¢DµC EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b51 ¢DµD EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b52 ¢DµE EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b53 ¢DµF EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b54 ¢DµG EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b55 ¢DµH EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b56 ¢DµI EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b57 ¢DµJ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b58 ¢DµK EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b59 ¢DµL EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b5a ¢DµM EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b5b ¢DµN EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b5c ¢DµO EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b5d ¢DµP EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b5e ¢DµQ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b5f ¢DµR EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b60 ¢DµS EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b61 ¢DµT EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b62 ¢DµU EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b63 ¢DµV EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b64 ¢DµW EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b65 ¢DµX EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b66 ¢DµY EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b67 ¢DµZ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b68 ¢EµA EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b69 ¢EµB EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b6a ¢EµC EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b6b ¢EµD EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b6c ¢EµE EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b6d ¢EµF EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b6e ¢EµG EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b6f ¢EµH EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b70 ¢EµI EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b71 ¢EµJ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b72 ¢EµK EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b73 ¢EµL EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b74 ¢EµM EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b75 ¢EµN EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b76 ¢EµO EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b77 ¢EµP EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b78 ¢EµQ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b79 ¢EµR EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b7a ¢EµS EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b7b ¢EµT EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b7c ¢EµU EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b7d ¢EµV EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b7e ¢EµW EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b7f ¢EµX EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b80 ¢EµY EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b81 ¢EµZ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b82 ¢FµA EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b83 ¢FµB EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b84 ¢FµC EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b85 ¢FµD EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b86 ¢FµE EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b87 ¢FµF EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b88 ¢FµG EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b89 ¢FµH EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b8a ¢FµI EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b8b ¢FµJ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b8c ¢FµK EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b8d ¢FµL EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b8e ¢FµM EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b8f ¢FµN EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b90 ¢FµO EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b91 ¢FµP EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b92 ¢FµQ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b93 ¢FµR EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b94 ¢FµS EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b95 ¢FµT EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b96 ¢FµU EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b97 ¢FµV EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b98 ¢FµW EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b99 ¢FµX EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b9a ¢FµY EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b9b ¢FµZ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b9c ¢GµA EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b9d ¢GµB EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b9e ¢GµC EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b9f ¢GµD EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ba0 ¢GµE EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ba1 ¢GµF EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ba2 ¢GµG EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ba3 ¢GµH EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ba4 ¢GµI EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ba5 ¢GµJ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ba6 ¢GµK EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ba7 ¢GµL EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ba8 ¢GµM EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ba9 ¢GµN EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6baa ¢GµO EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bab ¢GµP EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bac ¢GµQ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bad ¢GµR EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bae ¢GµS EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6baf ¢GµT EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bb0 ¢GµU EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bb1 ¢GµV EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bb2 ¢GµW EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bb3 ¢GµX EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bb4 ¢GµY EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bb5 ¢GµZ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bb6 ¢HµA EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bb7 ¢HµB EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bb8 ¢HµC EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bb9 ¢HµD EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bba ¢HµE EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bbb ¢HµF EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bbc ¢HµG EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bbd ¢HµH EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bbe ¢HµI EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bbf ¢HµJ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bc0 ¢HµK EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bc1 ¢HµL EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bc2 ¢HµM EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bc3 ¢HµN EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bc4 ¢HµO EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bc5 ¢HµP EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bc6 ¢HµQ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bc7 ¢HµR EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bc8 ¢HµS EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bc9 ¢HµT EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bca ¢HµU EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bcb ¢HµV EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bcc ¢HµW EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bcd ¢HµX EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bce ¢HµY EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bcf ¢HµZ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bd0 ¢IµA EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bd1 ¢IµB EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bd2 ¢IµC EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bd3 ¢IµD EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bd4 ¢IµE EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bd5 ¢IµF EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bd6 ¢IµG EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bd7 ¢IµH EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bd8 ¢IµI EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bd9 ¢IµJ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bda ¢IµK EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bdb ¢IµL EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bdc ¢IµM EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bdd ¢IµN EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bde ¢IµO EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bdf ¢IµP EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6be0 ¢IµQ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6be1 ¢IµR EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6be2 ¢IµS EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6be3 ¢IµT EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6be4 ¢IµU EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6be5 ¢IµV EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6be6 ¢IµW EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6be7 ¢IµX EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6be8 ¢IµY EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6be9 ¢IµZ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bea ¢JµA EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6beb ¢JµB EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bec ¢JµC EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bed ¢JµD EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bee ¢JµE EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bef ¢JµF EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bf0 ¢JµG EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bf1 ¢JµH EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bf2 ¢JµI EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bf3 ¢JµJ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bf4 ¢JµK EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bf5 ¢JµL EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bf6 ¢JµM EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bf7 ¢JµN EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bf8 ¢JµO EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bf9 ¢JµP EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bfa ¢JµQ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bfb ¢JµR EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bfc ¢JµS EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bfd ¢JµT EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bfe ¢JµU EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bff ¢JµV EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c00 ¢JµW EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c01 ¢JµX EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c02 ¢JµY EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c03 ¢JµZ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c04 ¢KµA EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c05 ¢KµB EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c06 ¢KµC EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c07 ¢KµD EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c08 ¢KµE EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c09 ¢KµF EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c0a ¢KµG EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c0b ¢KµH EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c0c ¢KµI EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c0d ¢KµJ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c0e ¢KµK EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c0f ¢KµL EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c10 ¢KµM EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c11 ¢KµN EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c12 ¢KµO EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c13 ¢KµP EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c14 ¢KµQ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c15 ¢KµR EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c16 ¢KµS EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c17 ¢KµT EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c18 ¢KµU EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c19 ¢KµV EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c1a ¢KµW EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c1b ¢KµX EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c1c ¢KµY EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c1d ¢KµZ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c1e ¢LµA EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c1f ¢LµB EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c20 ¢LµC EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c21 ¢LµD EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c22 ¢LµE EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c23 ¢LµF EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c24 ¢LµG EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c25 ¢LµH EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c26 ¢LµI EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c27 ¢LµJ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c28 ¢LµK EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c29 ¢LµL EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c2a ¢LµM EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c2b ¢LµN EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c2c ¢LµO EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c2d ¢LµP EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c2e ¢LµQ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c2f ¢LµR EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c30 ¢LµS EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c31 ¢LµT EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c32 ¢LµU EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c33 ¢LµV EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c34 ¢LµW EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c35 ¢LµX EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c36 ¢LµY EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c37 ¢LµZ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c38 ¢MµA EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c39 ¢MµB EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c3a ¢MµC EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c3b ¢MµD EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c3c ¢MµE EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c3d ¢MµF EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c3e ¢MµG EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c3f ¢MµH EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c40 ¢MµI EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c41 ¢MµJ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c42 ¢MµK EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c43 ¢MµL EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c44 ¢MµM EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c45 ¢MµN EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c46 ¢MµO EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c47 ¢MµP EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c48 ¢MµQ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c49 ¢MµR EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c4a ¢MµS EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c4b ¢MµT EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c4c ¢MµU EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c4d ¢MµV EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c4e ¢MµW EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c4f ¢MµX EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c50 ¢MµY EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c51 ¢MµZ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c52 ¢NµA EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c53 ¢NµB EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c54 ¢NµC EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c55 ¢NµD EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c56 ¢NµE EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c57 ¢NµF EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c58 ¢NµG EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c59 ¢NµH EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c5a ¢NµI EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c5b ¢NµJ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c5c ¢NµK EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c5d ¢NµL EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c5e ¢NµM EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c5f ¢NµN EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c60 ¢NµO EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c61 ¢NµP EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c62 ¢NµQ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c63 ¢NµR EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c64 ¢NµS EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c65 ¢NµT EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c66 ¢NµU EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c67 ¢NµV EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c68 ¢NµW EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c69 ¢NµX EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c6a ¢NµY EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c6b ¢NµZ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c6c ¢OµA EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c6d ¢OµB EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c6e ¢OµC EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c6f ¢OµD EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c70 ¢OµE EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c71 ¢OµF EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c72 ¢OµG EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c73 ¢OµH EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c74 ¢OµI EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c75 ¢OµJ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c76 ¢OµK EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c77 ¢OµL EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c78 ¢OµM EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c79 ¢OµN EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c7a ¢OµO EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c7b ¢OµP EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c7c ¢OµQ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c7d ¢OµR EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c7e ¢OµS EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c7f ¢OµT EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c80 ¢OµU EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c81 ¢OµV EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c82 ¢OµW EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c83 ¢OµX EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c84 ¢OµY EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c85 ¢OµZ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c86 ¢PµA EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c87 ¢PµB EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c88 ¢PµC EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c89 ¢PµD EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c8a ¢PµE EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c8b ¢PµF EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c8c ¢PµG EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c8d ¢PµH EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c8e ¢PµI EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c8f ¢PµJ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c90 ¢PµK EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c91 ¢PµL EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c92 ¢PµM EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c93 ¢PµN EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c94 ¢PµO EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c95 ¢PµP EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c96 ¢PµQ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c97 ¢PµR EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c98 ¢PµS EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c99 ¢PµT EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c9a ¢PµU EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c9b ¢PµV EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c9c ¢PµW EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c9d ¢PµX EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c9e ¢PµY EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c9f ¢PµZ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ca0 ¢QµA EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ca1 ¢QµB EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ca2 ¢QµC EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ca3 ¢QµD EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ca4 ¢QµE EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ca5 ¢QµF EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ca6 ¢QµG EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ca7 ¢QµH EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ca8 ¢QµI EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ca9 ¢QµJ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6caa ¢QµK EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cab ¢QµL EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cac ¢QµM EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cad ¢QµN EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cae ¢QµO EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6caf ¢QµP EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cb0 ¢QµQ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cb1 ¢QµR EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cb2 ¢QµS EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cb3 ¢QµT EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cb4 ¢QµU EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cb5 ¢QµV EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cb6 ¢QµW EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cb7 ¢QµX EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cb8 ¢QµY EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cb9 ¢QµZ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cba ¢RµA EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cbb ¢RµB EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cbc ¢RµC EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cbd ¢RµD EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cbe ¢RµE EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cbf ¢RµF EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cc0 ¢RµG EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cc1 ¢RµH EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cc2 ¢RµI EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cc3 ¢RµJ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cc4 ¢RµK EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cc5 ¢RµL EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cc6 ¢RµM EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cc7 ¢RµN EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cc8 ¢RµO EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cc9 ¢RµP EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cca ¢RµQ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ccb ¢RµR EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ccc ¢RµS EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ccd ¢RµT EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cce ¢RµU EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ccf ¢RµV EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cd0 ¢RµW EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cd1 ¢RµX EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cd2 ¢RµY EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cd3 ¢RµZ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cd4 ¢SµA EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cd5 ¢SµB EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cd6 ¢SµC EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cd7 ¢SµD EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cd8 ¢SµE EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cd9 ¢SµF EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cda ¢SµG EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cdb ¢SµH EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cdc ¢SµI EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cdd ¢SµJ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cde ¢SµK EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cdf ¢SµL EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ce0 ¢SµM EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ce1 ¢SµN EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ce2 ¢SµO EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ce3 ¢SµP EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ce4 ¢SµQ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ce5 ¢SµR EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ce6 ¢SµS EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ce7 ¢SµT EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ce8 ¢SµU EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ce9 ¢SµV EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cea ¢SµW EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ceb ¢SµX EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cec ¢SµY EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ced ¢SµZ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cee ¢TµA EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cef ¢TµB EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cf0 ¢TµC EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cf1 ¢TµD EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cf2 ¢TµE EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cf3 ¢TµF EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cf4 ¢TµG EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cf5 ¢TµH EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cf6 ¢TµI EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cf7 ¢TµJ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cf8 ¢TµK EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cf9 ¢TµL EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cfa ¢TµM EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cfb ¢TµN EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cfc ¢TµO EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cfd ¢TµP EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cfe ¢TµQ EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cff ¢TµR EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6d00 ¢TµS EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6d01 ¢TµT EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d02 ¢TµU EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d03 ¢TµV EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d04 ¢TµW EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d05 ¢TµX EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d06 ¢TµY EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d07 ¢TµZ EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d08 ¢UµA EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d09 ¢UµB EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d0a ¢UµC EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d0b ¢UµD EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d0c ¢UµE EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d0d ¢UµF EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d0e ¢UµG EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d0f ¢UµH EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d10 ¢UµI EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d11 ¢UµJ EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d12 ¢UµK EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d13 ¢UµL EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d14 ¢UµM EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d15 ¢UµN EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d16 ¢UµO EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d17 ¢UµP EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d18 ¢UµQ EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d19 ¢UµR EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d1a ¢UµS EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d1b ¢UµT EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d1c ¢UµU EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d1d ¢UµV EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d1e ¢UµW EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d1f ¢UµX EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d20 ¢UµY EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d21 ¢UµZ EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d22 ¢VµA EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d23 ¢VµB EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d24 ¢VµC EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d25 ¢VµD EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d26 ¢VµE EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d27 ¢VµF EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d28 ¢VµG EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d29 ¢VµH EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d2a ¢VµI EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d2b ¢VµJ EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d2c ¢VµK EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d2d ¢VµL EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d2e ¢VµM EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d2f ¢VµN EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d30 ¢VµO EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d31 ¢VµP EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d32 ¢VµQ EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d33 ¢VµR EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d34 ¢VµS EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d35 ¢VµT EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d36 ¢VµU EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d37 ¢VµV EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d38 ¢VµW EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d39 ¢VµX EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d3a ¢VµY EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d3b ¢VµZ EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d3c ¢WµA EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d3d ¢A¢A EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d3e ¢A¢B EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d3f ¢A¢C EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d40 ¢A¢D EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d41 ¢A¢E EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d42 ¢A¢F EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d43 ¢A¢G EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d44 ¢A¢H EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d45 ¢A¢I EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d46 ¢A¢J EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d47 ¢A¢K EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d48 ¢A¢L EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d49 ¢A¢M EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d4a ¢A¢N EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d4b ¢A¢O EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d4c ¢A¢P EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d4d ¢A¢Q EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d4e ¢A¢R EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d4f ¢A¢S EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d50 ¢A¢T EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d51 ¢A¢U EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d52 ¢A¢V EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d53 ¢A¢W EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d54 ¢A¢X EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d55 ¢A¢Y EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d56 ¢A¢Z EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d57 ¢B¢A EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d58 ¢B¢B EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d59 ¢B¢C EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d5a ¢B¢D EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d5b ¢B¢E EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d5c ¢B¢F EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d5d ¢B¢G EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d5e ¢B¢H EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d5f ¢B¢I EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d60 ¢B¢J EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d61 ¢B¢K EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d62 ¢B¢L EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d63 ¢B¢M EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d64 ¢B¢N EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d65 ¢B¢O EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d66 ¢B¢P EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d67 ¢B¢Q EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d68 ¢B¢R EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d69 ¢B¢S EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d6a ¢B¢T EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d6b ¢B¢U EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d6c ¢B¢V EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d6d ¢B¢W EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d6e ¢B¢X EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d6f ¢B¢Y EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d70 ¢B¢Z EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d71 ¢C¢A EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d72 ¢C¢B EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d73 ¢C¢C EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d74 ¢C¢D EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d75 ¢C¢E EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d76 ¢C¢F EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d77 ¢C¢G EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d78 ¢C¢H EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d79 ¢C¢I EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d7a ¢C¢J EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d7b ¢C¢K EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d7c ¢C¢L EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d7d ¢C¢M EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d7e ¢C¢N EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d7f ¢C¢O EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d80 ¢C¢P EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d81 ¢C¢Q EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d82 ¢C¢R EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d83 ¢C¢S EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d84 ¢C¢T EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d85 ¢C¢U EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d86 ¢C¢V EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d87 ¢C¢W EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d88 ¢C¢X EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d89 ¢C¢Y EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d8a ¢C¢Z EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d8b ¢D¢A EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d8c ¢D¢B EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d8d ¢D¢C EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d8e ¢D¢D EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d8f ¢D¢E EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d90 ¢D¢F EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d91 ¢D¢G EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d92 ¢D¢H EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d93 ¢D¢I EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d94 ¢D¢J EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d95 ¢D¢K EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d96 ¢D¢L EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d97 ¢D¢M EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d98 ¢D¢N EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d99 ¢D¢O EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d9a ¢D¢P EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d9b ¢D¢Q EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d9c ¢D¢R EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d9d ¢D¢S EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d9e ¢D¢T EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d9f ¢D¢U EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6da0 ¢D¢V EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6da1 ¢D¢W EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6da2 ¢D¢X EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6da3 ¢D¢Y EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6da4 ¢D¢Z EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6da5 ¢E¢A EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6da6 ¢E¢B EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6da7 ¢E¢C EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6da8 ¢E¢D EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6da9 ¢E¢E EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6daa ¢E¢F EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6dab ¢E¢G EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6dac ¢E¢H EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6dad ¢E¢I EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6dae ¢E¢J EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6daf ¢E¢K EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6db0 ¢E¢L EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6db1 ¢E¢M EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6db2 ¢E¢N EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6db3 ¢E¢O EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6db4 ¢E¢P EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6db5 ¢E¢Q EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6db6 ¢E¢R EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6db7 ¢E¢S EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6db8 ¢E¢T EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6db9 ¢E¢U EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6dba ¢E¢V EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6dbb ¢E¢W EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6dbc ¢E¢X EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6dbd ¢E¢Y EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6dbe ¢E¢Z EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6dbf ¢F¢A EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6dc0 ¢F¢B EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6dc1 ¢F¢C EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6dc2 ¢F¢D EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6dc3 ¢AµA EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dc4 ¢AµB EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dc5 ¢AµC EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dc6 ¢AµD EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dc7 ¢AµE EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dc8 ¢AµF EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dc9 ¢AµG EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dca ¢AµH EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dcb ¢AµI EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dcc ¢AµJ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dcd ¢AµK EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dce ¢AµL EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dcf ¢AµM EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dd0 ¢AµN EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dd1 ¢AµO EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dd2 ¢AµP EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dd3 ¢AµQ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dd4 ¢AµR EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dd5 ¢AµS EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dd6 ¢AµT EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dd7 ¢AµU EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dd8 ¢AµV EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dd9 ¢AµW EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dda ¢AµX EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ddb ¢AµY EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ddc ¢AµZ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ddd ¢BµA EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dde ¢BµB EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ddf ¢BµC EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6de0 ¢BµD EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6de1 ¢BµE EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6de2 ¢BµF EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6de3 ¢BµG EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6de4 ¢BµH EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6de5 ¢BµI EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6de6 ¢BµJ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6de7 ¢BµK EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6de8 ¢BµL EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6de9 ¢BµM EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dea ¢BµN EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6deb ¢BµO EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dec ¢BµP EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ded ¢BµQ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dee ¢BµR EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6def ¢BµS EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6df0 ¢BµT EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6df1 ¢BµU EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6df2 ¢BµV EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6df3 ¢BµW EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6df4 ¢BµX EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6df5 ¢BµY EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6df6 ¢BµZ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6df7 ¢CµA EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6df8 ¢CµB EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6df9 ¢CµC EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dfa ¢CµD EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dfb ¢CµE EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dfc ¢CµF EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dfd ¢CµG EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dfe ¢CµH EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dff ¢CµI EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e00 ¢CµJ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e01 ¢CµK EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e02 ¢CµL EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e03 ¢CµM EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e04 ¢CµN EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e05 ¢CµO EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e06 ¢CµP EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e07 ¢CµQ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e08 ¢CµR EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e09 ¢CµS EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e0a ¢CµT EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e0b ¢CµU EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e0c ¢CµV EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e0d ¢CµW EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e0e ¢CµX EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e0f ¢CµY EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e10 ¢CµZ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e11 ¢DµA EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e12 ¢DµB EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e13 ¢DµC EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e14 ¢DµD EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e15 ¢DµE EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e16 ¢DµF EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e17 ¢DµG EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e18 ¢DµH EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e19 ¢DµI EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e1a ¢DµJ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e1b ¢DµK EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e1c ¢DµL EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e1d ¢DµM EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e1e ¢DµN EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e1f ¢DµO EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e20 ¢DµP EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e21 ¢DµQ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e22 ¢DµR EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e23 ¢DµS EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e24 ¢DµT EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e25 ¢DµU EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e26 ¢DµV EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e27 ¢DµW EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e28 ¢DµX EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e29 ¢DµY EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e2a ¢DµZ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e2b ¢EµA EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e2c ¢EµB EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e2d ¢EµC EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e2e ¢EµD EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e2f ¢EµE EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e30 ¢EµF EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e31 ¢EµG EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e32 ¢EµH EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e33 ¢EµI EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e34 ¢EµJ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e35 ¢EµK EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e36 ¢EµL EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e37 ¢EµM EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e38 ¢EµN EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e39 ¢EµO EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e3a ¢EµP EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e3b ¢EµQ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e3c ¢EµR EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e3d ¢EµS EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e3e ¢EµT EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e3f ¢EµU EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e40 ¢EµV EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e41 ¢EµW EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e42 ¢EµX EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e43 ¢EµY EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e44 ¢EµZ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e45 ¢FµA EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e46 ¢FµB EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e47 ¢FµC EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e48 ¢FµD EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e49 ¢FµE EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e4a ¢FµF EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e4b ¢FµG EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e4c ¢FµH EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e4d ¢FµI EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e4e ¢FµJ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e4f ¢FµK EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e50 ¢FµL EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e51 ¢FµM EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e52 ¢FµN EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e53 ¢FµO EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e54 ¢FµP EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e55 ¢FµQ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e56 ¢FµR EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e57 ¢FµS EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e58 ¢FµT EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e59 ¢FµU EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e5a ¢FµV EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e5b ¢FµW EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e5c ¢FµX EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e5d ¢FµY EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e5e ¢FµZ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e5f ¢GµA EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e60 ¢GµB EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e61 ¢GµC EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e62 ¢GµD EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e63 ¢GµE EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e64 ¢GµF EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e65 ¢GµG EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e66 ¢GµH EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e67 ¢GµI EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e68 ¢GµJ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e69 ¢GµK EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e6a ¢GµL EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e6b ¢GµM EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e6c ¢GµN EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e6d ¢GµO EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e6e ¢GµP EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e6f ¢GµQ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e70 ¢GµR EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e71 ¢GµS EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e72 ¢GµT EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e73 ¢GµU EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e74 ¢GµV EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e75 ¢GµW EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e76 ¢GµX EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e77 ¢GµY EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e78 ¢GµZ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e79 ¢HµA EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e7a ¢HµB EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e7b ¢HµC EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e7c ¢HµD EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e7d ¢HµE EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e7e ¢HµF EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e7f ¢HµG EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e80 ¢HµH EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e81 ¢HµI EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e82 ¢HµJ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e83 ¢HµK EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e84 ¢HµL EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e85 ¢HµM EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e86 ¢HµN EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e87 ¢HµO EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e88 ¢HµP EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e89 ¢HµQ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e8a ¢HµR EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e8b ¢HµS EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e8c ¢HµT EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e8d ¢HµU EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e8e ¢HµV EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e8f ¢HµW EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e90 ¢HµX EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e91 ¢HµY EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e92 ¢HµZ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e93 ¢IµA EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e94 ¢IµB EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e95 ¢IµC EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e96 ¢IµD EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e97 ¢IµE EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e98 ¢IµF EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e99 ¢IµG EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e9a ¢IµH EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e9b ¢IµI EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e9c ¢IµJ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e9d ¢IµK EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e9e ¢IµL EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e9f ¢IµM EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ea0 ¢IµN EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ea1 ¢IµO EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ea2 ¢IµP EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ea3 ¢IµQ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ea4 ¢IµR EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ea5 ¢IµS EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ea6 ¢IµT EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ea7 ¢IµU EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ea8 ¢IµV EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ea9 ¢IµW EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eaa ¢IµX EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eab ¢IµY EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eac ¢IµZ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ead ¢JµA EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eae ¢JµB EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eaf ¢JµC EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eb0 ¢JµD EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eb1 ¢JµE EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eb2 ¢JµF EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eb3 ¢JµG EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eb4 ¢JµH EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eb5 ¢JµI EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eb6 ¢JµJ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eb7 ¢JµK EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eb8 ¢JµL EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eb9 ¢JµM EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eba ¢JµN EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ebb ¢JµO EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ebc ¢JµP EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ebd ¢JµQ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ebe ¢JµR EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ebf ¢JµS EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ec0 ¢JµT EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ec1 ¢JµU EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ec2 ¢JµV EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ec3 ¢JµW EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ec4 ¢JµX EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ec5 ¢JµY EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ec6 ¢JµZ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ec7 ¢KµA EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ec8 ¢KµB EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ec9 ¢KµC EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eca ¢KµD EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ecb ¢KµE EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ecc ¢KµF EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ecd ¢KµG EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ece ¢KµH EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ecf ¢KµI EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ed0 ¢KµJ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ed1 ¢KµK EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ed2 ¢KµL EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ed3 ¢KµM EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ed4 ¢KµN EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ed5 ¢KµO EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ed6 ¢KµP EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ed7 ¢KµQ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ed8 ¢KµR EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ed9 ¢KµS EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eda ¢KµT EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6edb ¢KµU EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6edc ¢KµV EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6edd ¢KµW EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ede ¢KµX EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6edf ¢KµY EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ee0 ¢KµZ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ee1 ¢LµA EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ee2 ¢LµB EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ee3 ¢LµC EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ee4 ¢LµD EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ee5 ¢LµE EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ee6 ¢LµF EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ee7 ¢LµG EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ee8 ¢LµH EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ee9 ¢LµI EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eea ¢LµJ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eeb ¢LµK EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eec ¢LµL EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eed ¢LµM EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eee ¢LµN EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eef ¢LµO EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ef0 ¢LµP EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ef1 ¢LµQ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ef2 ¢LµR EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ef3 ¢LµS EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ef4 ¢LµT EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ef5 ¢LµU EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ef6 ¢LµV EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ef7 ¢LµW EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ef8 ¢LµX EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ef9 ¢LµY EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6efa ¢LµZ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6efb ¢MµA EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6efc ¢MµB EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6efd ¢MµC EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6efe ¢MµD EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eff ¢MµE EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f00 ¢MµF EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f01 ¢MµG EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f02 ¢MµH EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f03 ¢MµI EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f04 ¢MµJ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f05 ¢MµK EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f06 ¢MµL EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f07 ¢MµM EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f08 ¢MµN EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f09 ¢MµO EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f0a ¢MµP EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f0b ¢MµQ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f0c ¢MµR EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f0d ¢MµS EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f0e ¢MµT EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f0f ¢MµU EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f10 ¢MµV EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f11 ¢MµW EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f12 ¢MµX EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f13 ¢MµY EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f14 ¢MµZ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f15 ¢NµA EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f16 ¢NµB EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f17 ¢NµC EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f18 ¢NµD EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f19 ¢NµE EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f1a ¢NµF EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f1b ¢NµG EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f1c ¢NµH EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f1d ¢NµI EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f1e ¢NµJ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f1f ¢NµK EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f20 ¢NµL EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f21 ¢NµM EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f22 ¢NµN EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f23 ¢NµO EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f24 ¢NµP EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f25 ¢NµQ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f26 ¢NµR EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f27 ¢NµS EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f28 ¢NµT EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f29 ¢NµU EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f2a ¢NµV EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f2b ¢NµW EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f2c ¢NµX EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f2d ¢NµY EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f2e ¢NµZ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f2f ¢OµA EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f30 ¢OµB EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f31 ¢OµC EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f32 ¢OµD EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f33 ¢OµE EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f34 ¢OµF EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f35 ¢OµG EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f36 ¢OµH EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f37 ¢OµI EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f38 ¢OµJ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f39 ¢OµK EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f3a ¢OµL EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f3b ¢OµM EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f3c ¢OµN EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f3d ¢OµO EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f3e ¢OµP EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f3f ¢OµQ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f40 ¢OµR EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f41 ¢OµS EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f42 ¢OµT EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f43 ¢OµU EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f44 ¢OµV EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f45 ¢OµW EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f46 ¢OµX EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f47 ¢OµY EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f48 ¢OµZ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f49 ¢PµA EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f4a ¢PµB EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f4b ¢PµC EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f4c ¢PµD EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f4d ¢PµE EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f4e ¢PµF EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f4f ¢PµG EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f50 ¢PµH EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f51 ¢PµI EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f52 ¢PµJ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f53 ¢PµK EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f54 ¢PµL EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f55 ¢PµM EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f56 ¢PµN EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f57 ¢PµO EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f58 ¢PµP EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f59 ¢PµQ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f5a ¢PµR EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f5b ¢PµS EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f5c ¢PµT EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f5d ¢PµU EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f5e ¢PµV EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f5f ¢PµW EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f60 ¢PµX EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f61 ¢PµY EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f62 ¢PµZ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f63 ¢QµA EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f64 ¢QµB EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f65 ¢QµC EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f66 ¢QµD EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f67 ¢QµE EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f68 ¢QµF EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f69 ¢QµG EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f6a ¢QµH EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f6b ¢QµI EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f6c ¢QµJ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f6d ¢QµK EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f6e ¢QµL EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f6f ¢QµM EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f70 ¢QµN EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f71 ¢QµO EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f72 ¢QµP EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f73 ¢QµQ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f74 ¢QµR EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f75 ¢QµS EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f76 ¢QµT EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f77 ¢QµU EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f78 ¢QµV EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f79 ¢QµW EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f7a ¢QµX EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f7b ¢QµY EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f7c ¢QµZ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f7d ¢RµA EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f7e ¢RµB EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f7f ¢RµC EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f80 ¢RµD EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f81 ¢RµE EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f82 ¢RµF EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f83 ¢RµG EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f84 ¢RµH EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f85 ¢RµI EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f86 ¢RµJ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f87 ¢RµK EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f88 ¢RµL EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f89 ¢RµM EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f8a ¢RµN EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f8b ¢RµO EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f8c ¢RµP EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f8d ¢RµQ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f8e ¢RµR EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f8f ¢RµS EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f90 ¢RµT EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f91 ¢RµU EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f92 ¢RµV EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f93 ¢RµW EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f94 ¢RµX EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f95 ¢RµY EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f96 ¢RµZ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f97 ¢SµA EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f98 ¢SµB EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f99 ¢SµC EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f9a ¢SµD EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f9b ¢SµE EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f9c ¢SµF EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f9d ¢SµG EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f9e ¢SµH EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f9f ¢SµI EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fa0 ¢SµJ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fa1 ¢SµK EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fa2 ¢SµL EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fa3 ¢SµM EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fa4 ¢SµN EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fa5 ¢SµO EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fa6 ¢SµP EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fa7 ¢SµQ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fa8 ¢SµR EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fa9 ¢SµS EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6faa ¢SµT EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fab ¢SµU EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fac ¢SµV EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fad ¢SµW EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fae ¢SµX EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6faf ¢SµY EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fb0 ¢SµZ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fb1 ¢TµA EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fb2 ¢TµB EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fb3 ¢TµC EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fb4 ¢TµD EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fb5 ¢TµE EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fb6 ¢TµF EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fb7 ¢TµG EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fb8 ¢TµH EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fb9 ¢TµI EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fba ¢TµJ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fbb ¢TµK EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fbc ¢TµL EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fbd ¢TµM EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fbe ¢TµN EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fbf ¢TµO EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fc0 ¢TµP EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fc1 ¢TµQ EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fc2 ¢TµR EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fc3 ¢TµS EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fc4 ¢TµT EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fc5 ¢TµU EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fc6 ¢TµV EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fc7 ¢TµW EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fc8 ¢TµX EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fc9 ¢TµY EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fca ¢TµZ EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fcb ¢UµA EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fcc ¢UµB EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fcd ¢UµC EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fce ¢UµD EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fcf ¢UµE EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fd0 ¢UµF EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fd1 ¢UµG EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fd2 ¢UµH EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fd3 ¢UµI EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fd4 ¢UµJ EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fd5 ¢UµK EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fd6 ¢UµL EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fd7 ¢UµM EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fd8 ¢UµN EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fd9 ¢UµO EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fda ¢UµP EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fdb ¢UµQ EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fdc ¢UµR EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fdd ¢UµS EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fde ¢UµT EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fdf ¢UµU EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fe0 ¢UµV EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fe1 ¢UµW EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fe2 ¢UµX EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fe3 ¢UµY EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fe4 ¢UµZ EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fe5 ¢VµA EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fe6 ¢VµB EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fe7 ¢VµC EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fe8 ¢VµD EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fe9 ¢VµE EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fea ¢VµF EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6feb ¢VµG EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fec ¢VµH EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fed ¢VµI EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fee ¢VµJ EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fef ¢VµK EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6ff0 ¢VµL EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6ff1 ¢VµM EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6ff2 ¢VµN EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6ff3 ¢VµO EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6ff4 ¢VµP EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6ff5 ¢VµQ EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6ff6 ¢VµR EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6ff7 ¢VµS EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6ff8 ¢VµT EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6ff9 ¢VµU EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6ffa ¢VµV EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6ffb ¢VµW EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6ffc ¢VµX EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6ffd ¢VµY EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6ffe ¢VµZ EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fff ¢WµA EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0xa001 ¥AA EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa002 ¥AB EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa003 ¥AC EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa004 ¥AD EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa005 ¥AE EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa006 ¥AF EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa007 ¥AG EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa008 ¥AH EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa009 ¥AI EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa00a ¥AJ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa00b ¥AK EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa00c ¥AL EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa00d ¥AM EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa00e ¥AN EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa00f ¥AO EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa010 ¥AP EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa011 ¥AQ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa012 ¥AR EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa013 ¥AS EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa014 ¥AT EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa015 ¥AU EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa016 ¥AV EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa017 ¥AW EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa018 ¥AX EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa019 ¥AY EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa01a ¥AZ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa01b ¥BA EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa01c ¥BB EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa01d ¥BC EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa01e ¥BD EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa01f ¥BE EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa020 ¥BF EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa021 ¥BG EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa022 ¥BH EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa023 ¥BI EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa024 ¥BJ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa025 ¥BK EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa026 ¥BL EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa027 ¥BM EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa028 ¥BN EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa029 ¥BO EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa02a ¥BP EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa02b ¥BQ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa02c ¥BR EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa02d ¥BS EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa02e ¥BT EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa02f ¥BU EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa030 ¥BV EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa031 ¥BW EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa032 ¥BX EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa033 ¥BY EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa034 ¥BZ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa035 ¥CA EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa036 ¥CB EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa037 ¥CC EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa038 ¥CD EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa039 ¥CE EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa03a ¥CF EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa03b ¥CG EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa03c ¥CH EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa03d ¥CI EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa03e ¥CJ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa03f ¥CK EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa040 ¥CL EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa041 ¥CM EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa042 ¥CN EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa043 ¥CO EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa044 ¥CP EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa045 ¥CQ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa046 ¥CR EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa047 ¥CS EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa048 ¥CT EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa049 ¥CU EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa04a ¥CV EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa04b ¥CW EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa04c ¥CX EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa04d ¥CY EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa04e ¥CZ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa04f ¥DA EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa050 ¥DB EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa051 ¥DC EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa052 ¥DD EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa053 ¥DE EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa054 ¥DF EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa055 ¥DG EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa056 ¥DH EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa057 ¥DI EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa058 ¥DJ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa059 ¥DK EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa05a ¥DL EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa05b ¥DM EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa05c ¥DN EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa05d ¥DO EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa05e ¥DP EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa05f ¥DQ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa060 ¥DR EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa061 ¥DS EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa062 ¥DT EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa063 ¥DU EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa064 ¥DV EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa065 ¥DW EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa066 ¥DX EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa067 ¥DY EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa068 ¥DZ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa069 ¥EA EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa06a ¥EB EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa06b ¥EC EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa06c ¥ED EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa06d ¥EE EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa06e ¥EF EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa06f ¥EG EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa070 ¥EH EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa071 ¥EI EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa072 ¥EJ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa073 ¥EK EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa074 ¥EL EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa075 ¥EM EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa076 ¥EN EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa077 ¥EO EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa078 ¥EP EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa079 ¥EQ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa07a ¥ER EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa07b ¥ES EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa07c ¥ET EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa07d ¥EU EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa07e ¥EV EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa07f ¥EW EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa080 ¥EX EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa081 ¥EY EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa082 ¥EZ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa083 ¥FA EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa084 ¥FB EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa085 ¥FC EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa086 ¥FD EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa087 ¥FE EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa088 ¥FF EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa089 ¥FG EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa08a ¥FH EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa08b ¥FI EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa08c ¥FJ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa08d ¥FK EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa08e ¥FL EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa08f ¥FM EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa090 ¥FN EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa091 ¥FO EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa092 ¥FP EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa093 ¥FQ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa094 ¥FR EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa095 ¥FS EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa096 ¥FT EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa097 ¥FU EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa098 ¥FV EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa099 ¥FW EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa09a ¥FX EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa09b ¥FY EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa09c ¥FZ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa09d ¥GA EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa09e ¥GB EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa09f ¥GC EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0a0 ¥GD EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0a1 ¥GE EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0a2 ¥GF EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0a3 ¥GG EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0a4 ¥GH EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0a5 ¥GI EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0a6 ¥GJ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0a7 ¥GK EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0a8 ¥GL EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0a9 ¥GM EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0aa ¥GN EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0ab ¥GO EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0ac ¥GP EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0ad ¥GQ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0ae ¥GR EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0af ¥GS EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0b0 ¥GT EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0b1 ¥GU EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0b2 ¥GV EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0b3 ¥GW EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0b4 ¥GX EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0b5 ¥GY EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0b6 ¥GZ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0b7 ¥HA EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0b8 ¥HB EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0b9 ¥HC EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0ba ¥HD EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0bb ¥HE EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0bc ¥HF EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0bd ¥HG EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0be ¥HH EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0bf ¥HI EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0c0 ¥HJ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0c1 ¥HK EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0c2 ¥HL EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0c3 ¥HM EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0c4 ¥HN EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0c5 ¥HO EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0c6 ¥HP EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0c7 ¥HQ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0c8 ¥HR EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0c9 ¥HS EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0ca ¥HT EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0cb ¥HU EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0cc ¥HV EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0cd ¥HW EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0ce ¥HX EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0cf ¥HY EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0d0 ¥HZ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0d1 ¥IA EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0d2 ¥IB EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0d3 ¥IC EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0d4 ¥ID EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0d5 ¥IE EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0d6 ¥IF EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0d7 ¥IG EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0d8 ¥IH EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0d9 ¥II EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0da ¥IJ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0db ¥IK EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0dc ¥IL EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0dd ¥IM EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0de ¥IN EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0df ¥IO EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0e0 ¥IP EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0e1 ¥IQ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0e2 ¥IR EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0e3 ¥IS EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0e4 ¥IT EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0e5 ¥IU EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0e6 ¥IV EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0e7 ¥IW EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0e8 ¥IX EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0e9 ¥IY EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0ea ¥IZ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0eb ¥JA EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0ec ¥JB EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0ed ¥JC EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0ee ¥JD EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0ef ¥JE EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0f0 ¥JF EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0f1 ¥JG EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0f2 ¥JH EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0f3 ¥JI EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0f4 ¥JJ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0f5 ¥JK EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0f6 ¥JL EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0f7 ¥JM EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0f8 ¥JN EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0f9 ¥JO EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0fa ¥JP EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0fb ¥JQ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0fc ¥JR EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0fd ¥JS EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0fe ¥JT EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0ff ¥JU EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa101 ¥JW EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa102 ¥JX EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa103 ¥JY EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa104 ¥JZ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa105 ¥KA EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa106 ¥KB EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa107 ¥KC EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa108 ¥KD EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa109 ¥KE EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa10a ¥KF EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa10b ¥KG EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa10c ¥KH EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa10d ¥KI EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa10e ¥KJ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa10f ¥KK EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa110 ¥KL EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa111 ¥KM EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa112 ¥KN EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa113 ¥KO EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa114 ¥KP EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa115 ¥KQ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa116 ¥KR EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa117 ¥KS EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa118 ¥KT EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa119 ¥KU EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa11a ¥KV EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa11b ¥KW EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa11c ¥KX EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa11d ¥KY EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa11e ¥KZ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa11f ¥LA EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa120 ¥LB EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa121 ¥LC EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa122 ¥LD EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa123 ¥LE EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa124 ¥LF EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa125 ¥LG EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa126 ¥LH EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa127 ¥LI EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa128 ¥LJ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa129 ¥LK EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa12a ¥LL EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa12b ¥LM EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa12c ¥LN EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa12d ¥LO EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa12e ¥LP EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa12f ¥LQ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa130 ¥LR EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa131 ¥LS EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa132 ¥LT EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa133 ¥LU EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa134 ¥LV EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa135 ¥LW EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa136 ¥LX EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa137 ¥LY EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa138 ¥LZ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa139 ¥MA EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa13a ¥MB EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa13b ¥MC EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa13c ¥MD EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa13d ¥ME EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa13e ¥MF EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa13f ¥MG EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa140 ¥MH EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa141 ¥MI EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa142 ¥MJ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa143 ¥MK EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa144 ¥ML EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa145 ¥MM EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa146 ¥MN EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa147 ¥MO EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa148 ¥MP EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa149 ¥MQ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa14a ¥MR EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa14b ¥MS EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa14c ¥MT EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa14d ¥MU EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa14e ¥MV EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa14f ¥MW EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa150 ¥MX EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa151 ¥MY EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa152 ¥MZ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa153 ¥NA EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa154 ¥NB EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa155 ¥NC EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa156 ¥ND EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa157 ¥NE EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa158 ¥NF EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa159 ¥NG EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa15a ¥NH EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa15b ¥NI EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa15c ¥NJ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa15d ¥NK EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa15e ¥NL EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa15f ¥NM EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa160 ¥NN EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa161 ¥NO EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa162 ¥NP EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa163 ¥NQ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa164 ¥NR EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa165 ¥NS EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa166 ¥NT EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa167 ¥NU EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa168 ¥NV EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa169 ¥NW EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa16a ¥NX EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa16b ¥NY EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa16c ¥NZ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa16d ¥OA EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa16e ¥OB EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa16f ¥OC EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa170 ¥OD EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa171 ¥OE EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa172 ¥OF EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa173 ¥OG EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa174 ¥OH EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa175 ¥OI EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa176 ¥OJ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa177 ¥OK EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa178 ¥OL EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa179 ¥OM EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa17a ¥ON EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa17b ¥OO EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa17c ¥OP EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa17d ¥OQ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa17e ¥OR EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa17f ¥OS EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa180 ¥OT EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa181 ¥OU EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa182 ¥OV EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa183 ¥OW EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa184 ¥OX EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa185 ¥OY EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa186 ¥OZ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa187 ¥PA EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa188 ¥PB EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa189 ¥PC EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa18a ¥PD EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa18b ¥PE EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa18c ¥PF EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa18d ¥PG EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa18e ¥PH EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa18f ¥PI EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa190 ¥PJ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa191 ¥PK EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa192 ¥PL EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa193 ¥PM EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa194 ¥PN EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa195 ¥PO EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa196 ¥PP EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa197 ¥PQ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa198 ¥PR EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa199 ¥PS EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa19a ¥PT EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa19b ¥PU EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa19c ¥PV EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa19d ¥PW EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa19e ¥PX EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa19f ¥PY EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1a0 ¥PZ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1a1 ¥QA EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1a2 ¥QB EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1a3 ¥QC EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1a4 ¥QD EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1a5 ¥QE EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1a6 ¥QF EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1a7 ¥QG EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1a8 ¥QH EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1a9 ¥QI EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1aa ¥QJ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1ab ¥QK EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1ac ¥QL EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1ad ¥QM EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1ae ¥QN EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1af ¥QO EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1b0 ¥QP EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1b1 ¥QQ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1b2 ¥QR EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1b3 ¥QS EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1b4 ¥QT EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1b5 ¥QU EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1b6 ¥QV EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1b7 ¥QW EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1b8 ¥QX EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1b9 ¥QY EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1ba ¥QZ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1bb ¥RA EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1bc ¥RB EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1bd ¥RC EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1be ¥RD EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1bf ¥RE EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1c0 ¥RF EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1c1 ¥RG EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1c2 ¥RH EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1c3 ¥RI EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1c4 ¥RJ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1c5 ¥RK EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1c6 ¥RL EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1c7 ¥RM EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1c8 ¥RN EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1c9 ¥RO EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1ca ¥RP EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1cb ¥RQ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1cc ¥RR EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1cd ¥RS EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1ce ¥RT EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1cf ¥RU EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1d0 ¥RV EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1d1 ¥RW EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1d2 ¥RX EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1d3 ¥RY EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1d4 ¥RZ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1d5 ¥SA EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1d6 ¥SB EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1d7 ¥SC EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1d8 ¥SD EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1d9 ¥SE EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1da ¥SF EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1db ¥SG EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1dc ¥SH EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1dd ¥SI EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1de ¥SJ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1df ¥SK EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1e0 ¥SL EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1e1 ¥SM EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1e2 ¥SN EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1e3 ¥SO EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1e4 ¥SP EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1e5 ¥SQ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1e6 ¥SR EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1e7 ¥SS EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1e8 ¥ST EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1e9 ¥SU EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1ea ¥SV EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1eb ¥SW EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1ec ¥SX EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1ed ¥SY EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1ee ¥SZ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1ef ¥TA EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1f0 ¥TB EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1f1 ¥TC EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1f2 ¥TD EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1f3 ¥TE EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1f4 ¥TF EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1f5 ¥TG EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1f6 ¥TH EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1f7 ¥TI EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1f8 ¥TJ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1f9 ¥TK EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1fa ¥TL EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1fb ¥TM EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1fc ¥TN EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1fd ¥TO EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1fe ¥TP EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1ff ¥TQ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted diff --git a/src/org/infinity/resource/cre/decoder/tables/infinityanimations-v6.ids b/src/org/infinity/resource/cre/decoder/tables/infinityanimations-v6.ids new file mode 100644 index 000000000..30ad010f3 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/tables/infinityanimations-v6.ids @@ -0,0 +1,8724 @@ +IDS +0x1001 4AA EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1002 4AB EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1003 4AC EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1004 4AD EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1005 4AE EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1006 4AF EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1007 4AG EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1008 4AH EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1009 4AI EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x100a 4AJ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x100b 4AK EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x100c 4AL EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x100d 4AM EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x100e 4AN EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x100f 4AO EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1010 4AP EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1011 4AQ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1012 4AR EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1013 4AS EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1014 4AT EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1015 4AU EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1016 4AV EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1017 4AW EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1018 4AX EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1019 4AY EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x101a 4AZ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x101b 4BA EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x101c 4BB EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x101d 4BC EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x101e 4BD EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x101f 4BE EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1020 4BF EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1021 4BG EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1022 4BH EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1023 4BI EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1024 4BJ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1025 4BK EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1026 4BL EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1027 4BM EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1028 4BN EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1029 4BO EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x102a 4BP EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x102b 4BQ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x102c 4BR EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x102d 4BS EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x102e 4BT EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x102f 4BU EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1030 4BV EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1031 4BW EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1032 4BX EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1033 4BY EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1034 4BZ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1035 4CA EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1036 4CB EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1037 4CC EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1038 4CD EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1039 4CE EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x103a 4CF EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x103b 4CG EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x103c 4CH EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x103d 4CI EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x103e 4CJ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x103f 4CK EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1040 4CL EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1041 4CM EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1042 4CN EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1043 4CO EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1044 4CP EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1045 4CQ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1046 4CR EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1047 4CS EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1048 4CT EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1049 4CU EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x104a 4CV EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x104b 4CW EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x104c 4CX EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x104d 4CY EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x104e 4CZ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x104f 4DA EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1050 4DB EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1051 4DC EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1052 4DD EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1053 4DE EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1054 4DF EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1055 4DG EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1056 4DH EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1057 4DI EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1058 4DJ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1059 4DK EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x105a 4DL EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x105b 4DM EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x105c 4DN EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x105d 4DO EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x105e 4DP EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x105f 4DQ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1060 4DR EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1061 4DS EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1062 4DT EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1063 4DU EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1064 4DV EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1065 4DW EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1066 4DX EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1067 4DY EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1068 4DZ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1069 4EA EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x106a 4EB EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x106b 4EC EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x106c 4ED EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x106d 4EE EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x106e 4EF EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x106f 4EG EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1070 4EH EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1071 4EI EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1072 4EJ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1073 4EK EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1074 4EL EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1075 4EM EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1076 4EN EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1077 4EO EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1078 4EP EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1079 4EQ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x107a 4ER EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x107b 4ES EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x107c 4ET EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x107d 4EU EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x107e 4EV EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x107f 4EW EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1080 4EX EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1081 4EY EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1082 4EZ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1083 4FA EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1084 4FB EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1085 4FC EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1086 4FD EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1087 4FE EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1088 4FF EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1089 4FG EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x108a 4FH EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x108b 4FI EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x108c 4FJ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x108d 4FK EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x108e 4FL EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x108f 4FM EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1090 4FN EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1091 4FO EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1092 4FP EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1093 4FQ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1094 4FR EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1095 4FS EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1096 4FT EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1097 4FU EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1098 4FV EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1099 4FW EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x109a 4FX EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x109b 4FY EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x109c 4FZ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x109d 4GA EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x109e 4GB EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x109f 4GC EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10a0 4GD EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10a1 4GE EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10a2 4GF EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10a3 4GG EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10a4 4GH EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10a5 4GI EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10a6 4GJ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10a7 4GK EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10a8 4GL EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10a9 4GM EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10aa 4GN EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10ab 4GO EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10ac 4GP EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10ad 4GQ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10ae 4GR EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10af 4GS EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10b0 4GT EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10b1 4GU EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10b2 4GV EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10b3 4GW EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10b4 4GX EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10b5 4GY EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10b6 4GZ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10b7 4HA EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10b8 4HB EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10b9 4HC EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10ba 4HD EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10bb 4HE EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10bc 4HF EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10bd 4HG EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10be 4HH EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10bf 4HI EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10c0 4HJ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10c1 4HK EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10c2 4HL EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10c3 4HM EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10c4 4HN EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10c5 4HO EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10c6 4HP EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10c7 4HQ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10c8 4HR EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10c9 4HS EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10ca 4HT EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10cb 4HU EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10cc 4HV EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10cd 4HW EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10ce 4HX EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10cf 4HY EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10d0 4HZ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10d1 4IA EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10d2 4IB EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10d3 4IC EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10d4 4ID EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10d5 4IE EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10d6 4IF EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10d7 4IG EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10d8 4IH EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10d9 4II EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10da 4IJ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10db 4IK EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10dc 4IL EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10dd 4IM EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10de 4IN EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10df 4IO EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10e0 4IP EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10e1 4IQ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10e2 4IR EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10e3 4IS EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10e4 4IT EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10e5 4IU EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10e6 4IV EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10e7 4IW EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10e8 4IX EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10e9 4IY EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10ea 4IZ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10eb 4JA EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10ec 4JB EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10ed 4JC EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10ee 4JD EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10ef 4JE EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10f0 4JF EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10f1 4JG EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10f2 4JH EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10f3 4JI EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10f4 4JJ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10f5 4JK EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10f6 4JL EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10f7 4JM EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10f8 4JN EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10f9 4JO EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10fa 4JP EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10fb 4JQ EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10fc 4JR EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10fd 4JS EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10fe 4JT EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x10ff 4JU EMPTY LARGE 5 Lo BGI MONSTER LONG 4 PART unpaletted +0x1101 4JW EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1102 4JX EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1103 4JY EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1104 4JZ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1105 4KA EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1106 4KB EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1107 4KC EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1108 4KD EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1109 4KE EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x110a 4KF EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x110b 4KG EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x110c 4KH EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x110d 4KI EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x110e 4KJ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x110f 4KK EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1110 4KL EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1111 4KM EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1112 4KN EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1113 4KO EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1114 4KP EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1115 4KQ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1116 4KR EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1117 4KS EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1118 4KT EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1119 4KU EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x111a 4KV EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x111b 4KW EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x111c 4KX EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x111d 4KY EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x111e 4KZ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x111f 4LA EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1120 4LB EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1121 4LC EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1122 4LD EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1123 4LE EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1124 4LF EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1125 4LG EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1126 4LH EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1127 4LI EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1128 4LJ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1129 4LK EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x112a 4LL EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x112b 4LM EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x112c 4LN EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x112d 4LO EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x112e 4LP EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x112f 4LQ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1130 4LR EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1131 4LS EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1132 4LT EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1133 4LU EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1134 4LV EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1135 4LW EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1136 4LX EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1137 4LY EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1138 4LZ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1139 4MA EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x113a 4MB EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x113b 4MC EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x113c 4MD EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x113d 4ME EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x113e 4MF EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x113f 4MG EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1140 4MH EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1141 4MI EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1142 4MJ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1143 4MK EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1144 4ML EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1145 4MM EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1146 4MN EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1147 4MO EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1148 4MP EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1149 4MQ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x114a 4MR EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x114b 4MS EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x114c 4MT EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x114d 4MU EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x114e 4MV EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x114f 4MW EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1150 4MX EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1151 4MY EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1152 4MZ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1153 4NA EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1154 4NB EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1155 4NC EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1156 4ND EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1157 4NE EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1158 4NF EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1159 4NG EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x115a 4NH EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x115b 4NI EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x115c 4NJ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x115d 4NK EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x115e 4NL EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x115f 4NM EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1160 4NN EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1161 4NO EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1162 4NP EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1163 4NQ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1164 4NR EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1165 4NS EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1166 4NT EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1167 4NU EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1168 4NV EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1169 4NW EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x116a 4NX EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x116b 4NY EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x116c 4NZ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x116d 4OA EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x116e 4OB EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x116f 4OC EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1170 4OD EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1171 4OE EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1172 4OF EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1173 4OG EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1174 4OH EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1175 4OI EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1176 4OJ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1177 4OK EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1178 4OL EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1179 4OM EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x117a 4ON EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x117b 4OO EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x117c 4OP EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x117d 4OQ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x117e 4OR EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x117f 4OS EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1180 4OT EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1181 4OU EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1182 4OV EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1183 4OW EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1184 4OX EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1185 4OY EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1186 4OZ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1187 4PA EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1188 4PB EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1189 4PC EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x118a 4PD EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x118b 4PE EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x118c 4PF EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x118d 4PG EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x118e 4PH EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x118f 4PI EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1190 4PJ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1191 4PK EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1192 4PL EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1193 4PM EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1194 4PN EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1195 4PO EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1196 4PP EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1197 4PQ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1198 4PR EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1199 4PS EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x119a 4PT EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x119b 4PU EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x119c 4PV EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x119d 4PW EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x119e 4PX EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x119f 4PY EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11a0 4PZ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11a1 4QA EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11a2 4QB EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11a3 4QC EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11a4 4QD EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11a5 4QE EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11a6 4QF EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11a7 4QG EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11a8 4QH EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11a9 4QI EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11aa 4QJ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11ab 4QK EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11ac 4QL EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11ad 4QM EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11ae 4QN EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11af 4QO EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11b0 4QP EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11b1 4QQ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11b2 4QR EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11b3 4QS EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11b4 4QT EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11b5 4QU EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11b6 4QV EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11b7 4QW EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11b8 4QX EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11b9 4QY EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11ba 4QZ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11bb 4RA EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11bc 4RB EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11bd 4RC EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11be 4RD EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11bf 4RE EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11c0 4RF EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11c1 4RG EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11c2 4RH EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11c3 4RI EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11c4 4RJ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11c5 4RK EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11c6 4RL EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11c7 4RM EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11c8 4RN EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11c9 4RO EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11ca 4RP EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11cb 4RQ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11cc 4RR EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11cd 4RS EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11ce 4RT EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11cf 4RU EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11d0 4RV EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11d1 4RW EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11d2 4RX EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11d3 4RY EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11d4 4RZ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11d5 4SA EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11d6 4SB EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11d7 4SC EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11d8 4SD EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11d9 4SE EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11da 4SF EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11db 4SG EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11dc 4SH EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11dd 4SI EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11de 4SJ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11df 4SK EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11e0 4SL EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11e1 4SM EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11e2 4SN EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11e3 4SO EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11e4 4SP EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11e5 4SQ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11e6 4SR EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11e7 4SS EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11e8 4ST EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11e9 4SU EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11ea 4SV EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11eb 4SW EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11ec 4SX EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11ed 4SY EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11ee 4SZ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11ef 4TA EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11f0 4TB EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11f1 4TC EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11f2 4TD EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11f3 4TE EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11f4 4TF EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11f5 4TG EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11f6 4TH EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11f7 4TI EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11f8 4TJ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11f9 4TK EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11fa 4TL EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11fb 4TM EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11fc 4TN EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11fd 4TO EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11fe 4TP EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x11ff 4TQ EMPTY HUGE A Lo BGI MONSTER LONG 4 PART unpaletted +0x1209 XDR1 EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x120a XDR2 EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x120b XDR3 EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x120c XDR4 EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x120d XDR5 EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x120e XDR6 EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x120f XDR7 EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1210 XDR8 EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1211 XDR9 EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1212 XDRA EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1213 XDRB EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1214 XDRC EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1215 XDRD EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1216 XDRE EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1217 XDRF EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1218 XDRG EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1219 XDRH EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x121a XDRI EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x121b XDRJ EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x121c XDRK EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x121d XDRL EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x121e XDRM EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x121f XDRN EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1220 XDRO EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1221 XDRP EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1222 XDRQ EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1223 XDRR EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1224 XDRS EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1225 XDRT EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1226 XDRU EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1227 XDRV EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1228 XDRW EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1229 XDRX EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x122a XDRY EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x122b XDRZ EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x122c XDR0 EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x122d XDR# EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x122e XDR- EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x122f XDR_ EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1230 XDR! EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1231 XDR@ EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1232 XDR$ EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1233 YDR0 EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1234 YDR1 EMPTY HUGE D Do DRAGONS (XDR) unpaletted +0x1301 -AA EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1302 -AB EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1303 -AC EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1304 -AD EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1305 -AE EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1306 -AF EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1307 -AG EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1308 -AH EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1309 -AI EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x130a -AJ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x130b -AK EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x130c -AL EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x130d -AM EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x130e -AN EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x130f -AO EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1310 -AP EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1311 -AQ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1312 -AR EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1313 -AS EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1314 -AT EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1315 -AU EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1316 -AV EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1317 -AW EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1318 -AX EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1319 -AY EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x131a -AZ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x131b -BA EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x131c -BB EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x131d -BC EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x131e -BD EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x131f -BE EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1320 -BF EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1321 -BG EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1322 -BH EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1323 -BI EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1324 -BJ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1325 -BK EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1326 -BL EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1327 -BM EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1328 -BN EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1329 -BO EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x132a -BP EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x132b -BQ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x132c -BR EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x132d -BS EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x132e -BT EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x132f -BU EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1330 -BV EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1331 -BW EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1332 -BX EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1333 -BY EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1334 -BZ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1335 -CA EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1336 -CB EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1337 -CC EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1338 -CD EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1339 -CE EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x133a -CF EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x133b -CG EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x133c -CH EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x133d -CI EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x133e -CJ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x133f -CK EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1340 -CL EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1341 -CM EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1342 -CN EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1343 -CO EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1344 -CP EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1345 -CQ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1346 -CR EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1347 -CS EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1348 -CT EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1349 -CU EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x134a -CV EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x134b -CW EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x134c -CX EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x134d -CY EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x134e -CZ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x134f -DA EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1350 -DB EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1351 -DC EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1352 -DD EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1353 -DE EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1354 -DF EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1355 -DG EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1356 -DH EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1357 -DI EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1358 -DJ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1359 -DK EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x135a -DL EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x135b -DM EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x135c -DN EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x135d -DO EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x135e -DP EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x135f -DQ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1360 -DR EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1361 -DS EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1362 -DT EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1363 -DU EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1364 -DV EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1365 -DW EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1366 -DX EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1367 -DY EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1368 -DZ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1369 -EA EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x136a -EB EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x136b -EC EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x136c -ED EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x136d -EE EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x136e -EF EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x136f -EG EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1370 -EH EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1371 -EI EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1372 -EJ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1373 -EK EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1374 -EL EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1375 -EM EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1376 -EN EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1377 -EO EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1378 -EP EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1379 -EQ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x137a -ER EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x137b -ES EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x137c -ET EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x137d -EU EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x137e -EV EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x137f -EW EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1380 -EX EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1381 -EY EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1382 -EZ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1383 -FA EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1384 -FB EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1385 -FC EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1386 -FD EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1387 -FE EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1388 -FF EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1389 -FG EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x138a -FH EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x138b -FI EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x138c -FJ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x138d -FK EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x138e -FL EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x138f -FM EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1390 -FN EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1391 -FO EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1392 -FP EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1393 -FQ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1394 -FR EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1395 -FS EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1396 -FT EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1397 -FU EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1398 -FV EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x1399 -FW EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x139a -FX EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x139b -FY EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x139c -FZ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x139d -GA EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x139e -GB EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x139f -GC EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13a0 -GD EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13a1 -GE EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13a2 -GF EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13a3 -GG EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13a4 -GH EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13a5 -GI EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13a6 -GJ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13a7 -GK EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13a8 -GL EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13a9 -GM EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13aa -GN EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13ab -GO EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13ac -GP EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13ad -GQ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13ae -GR EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13af -GS EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13b0 -GT EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13b1 -GU EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13b2 -GV EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13b3 -GW EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13b4 -GX EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13b5 -GY EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13b6 -GZ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13b7 -HA EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13b8 -HB EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13b9 -HC EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13ba -HD EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13bb -HE EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13bc -HF EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13bd -HG EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13be -HH EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13bf -HI EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13c0 -HJ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13c1 -HK EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13c2 -HL EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13c3 -HM EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13c4 -HN EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13c5 -HO EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13c6 -HP EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13c7 -HQ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13c8 -HR EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13c9 -HS EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13ca -HT EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13cb -HU EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13cc -HV EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13cd -HW EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13ce -HX EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13cf -HY EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13d0 -HZ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13d1 -IA EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13d2 -IB EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13d3 -IC EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13d4 -ID EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13d5 -IE EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13d6 -IF EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13d7 -IG EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13d8 -IH EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13d9 -II EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13da -IJ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13db -IK EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13dc -IL EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13dd -IM EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13de -IN EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13df -IO EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13e0 -IP EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13e1 -IQ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13e2 -IR EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13e3 -IS EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13e4 -IT EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13e5 -IU EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13e6 -IV EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13e7 -IW EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13e8 -IX EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13e9 -IY EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13ea -IZ EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13eb -JA EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13ec -JB EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13ed -JC EMPTY LARGE 5 Lo BGII SPLIT 4 PART unpaletted +0x13ee -JD EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x13ef -JE EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x13f0 -JF EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x13f1 -JG EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x13f2 -JH EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x13f3 -JI EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x13f4 -JJ EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x13f5 -JK EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x13f6 -JL EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x13f7 -JM EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x13f8 -JN EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x13f9 -JO EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x13fa -JP EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x13fb -JQ EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x13fc -JR EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x13fd -JS EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x13fe -JT EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x13ff -JU EMPTY HUGE A Lo BGII SPLIT 4 PART unpaletted +0x2001 2AA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2002 2AB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2003 2AC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2004 2AD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2005 2AE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2006 2AF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2007 2AG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2008 2AH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2009 2AI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x200a 2AJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x200b 2AK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x200c 2AL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x200d 2AM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x200e 2AN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x200f 2AO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2010 2AP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2011 2AQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2012 2AR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2013 2AS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2014 2AT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2015 2AU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2016 2AV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2017 2AW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2018 2AX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2019 2AY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x201a 2AZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x201b 2BA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x201c 2BB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x201d 2BC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x201e 2BD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x201f 2BE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2020 2BF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2021 2BG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2022 2BH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2023 2BI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2024 2BJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2025 2BK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2026 2BL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2027 2BM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2028 2BN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2029 2BO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x202a 2BP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x202b 2BQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x202c 2BR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x202d 2BS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x202e 2BT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x202f 2BU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2030 2BV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2031 2BW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2032 2BX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2033 2BY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2034 2BZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2035 2CA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2036 2CB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2037 2CC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2038 2CD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2039 2CE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x203a 2CF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x203b 2CG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x203c 2CH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x203d 2CI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x203e 2CJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x203f 2CK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2040 2CL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2041 2CM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2042 2CN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2043 2CO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2044 2CP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2045 2CQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2046 2CR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2047 2CS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2048 2CT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2049 2CU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x204a 2CV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x204b 2CW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x204c 2CX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x204d 2CY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x204e 2CZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x204f 2DA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2050 2DB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2051 2DC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2052 2DD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2053 2DE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2054 2DF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2055 2DG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2056 2DH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2057 2DI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2058 2DJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2059 2DK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x205a 2DL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x205b 2DM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x205c 2DN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x205d 2DO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x205e 2DP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x205f 2DQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2060 2DR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2061 2DS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2062 2DT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2063 2DU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2064 2DV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2065 2DW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2066 2DX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2067 2DY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2068 2DZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2069 2EA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x206a 2EB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x206b 2EC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x206c 2ED EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x206d 2EE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x206e 2EF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x206f 2EG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2070 2EH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2071 2EI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2072 2EJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2073 2EK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2074 2EL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2075 2EM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2076 2EN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2077 2EO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2078 2EP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2079 2EQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x207a 2ER EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x207b 2ES EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x207c 2ET EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x207d 2EU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x207e 2EV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x207f 2EW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2080 2EX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2081 2EY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2082 2EZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2083 2FA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2084 2FB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2085 2FC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2086 2FD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2087 2FE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2088 2FF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2089 2FG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x208a 2FH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x208b 2FI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x208c 2FJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x208d 2FK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x208e 2FL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x208f 2FM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2090 2FN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2091 2FO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2092 2FP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2093 2FQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2094 2FR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2095 2FS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2096 2FT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2097 2FU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2098 2FV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2099 2FW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x209a 2FX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x209b 2FY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x209c 2FZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x209d 2GA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x209e 2GB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x209f 2GC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20a0 2GD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20a1 2GE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20a2 2GF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20a3 2GG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20a4 2GH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20a5 2GI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20a6 2GJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20a7 2GK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20a8 2GL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20a9 2GM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20aa 2GN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20ab 2GO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20ac 2GP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20ad 2GQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20ae 2GR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20af 2GS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20b0 2GT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20b1 2GU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20b2 2GV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20b3 2GW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20b4 2GX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20b5 2GY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20b6 2GZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20b7 2HA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20b8 2HB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20b9 2HC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20ba 2HD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20bb 2HE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20bc 2HF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20bd 2HG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20be 2HH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20bf 2HI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20c0 2HJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20c1 2HK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20c2 2HL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20c3 2HM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20c4 2HN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20c5 2HO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20c6 2HP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20c7 2HQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20c8 2HR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20c9 2HS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20ca 2HT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20cb 2HU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20cc 2HV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20cd 2HW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20ce 2HX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20cf 2HY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20d0 2HZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20d1 2IA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20d2 2IB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20d3 2IC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20d4 2ID EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20d5 2IE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20d6 2IF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20d7 2IG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20d8 2IH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20d9 2II EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20da 2IJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20db 2IK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20dc 2IL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20dd 2IM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20de 2IN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20df 2IO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20e0 2IP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20e1 2IQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20e2 2IR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20e3 2IS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20e4 2IT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20e5 2IU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20e6 2IV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20e7 2IW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20e8 2IX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20e9 2IY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20ea 2IZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20eb 2JA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20ec 2JB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20ed 2JC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20ee 2JD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20ef 2JE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20f0 2JF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20f1 2JG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20f2 2JH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20f3 2JI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20f4 2JJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20f5 2JK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20f6 2JL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20f7 2JM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20f8 2JN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20f9 2JO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20fa 2JP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20fb 2JQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20fc 2JR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20fd 2JS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20fe 2JT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x20ff 2JU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2101 2JW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2102 2JX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2103 2JY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2104 2JZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2105 2KA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2106 2KB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2107 2KC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2108 2KD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2109 2KE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x210a 2KF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x210b 2KG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x210c 2KH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x210d 2KI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x210e 2KJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x210f 2KK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2110 2KL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2111 2KM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2112 2KN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2113 2KO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2114 2KP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2115 2KQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2116 2KR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2117 2KS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2118 2KT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2119 2KU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x211a 2KV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x211b 2KW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x211c 2KX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x211d 2KY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x211e 2KZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x211f 2LA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2120 2LB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2121 2LC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2122 2LD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2123 2LE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2124 2LF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2125 2LG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2126 2LH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2127 2LI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2128 2LJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2129 2LK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x212a 2LL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x212b 2LM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x212c 2LN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x212d 2LO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x212e 2LP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x212f 2LQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2130 2LR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2131 2LS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2132 2LT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2133 2LU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2134 2LV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2135 2LW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2136 2LX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2137 2LY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2138 2LZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2139 2MA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x213a 2MB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x213b 2MC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x213c 2MD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x213d 2ME EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x213e 2MF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x213f 2MG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2140 2MH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2141 2MI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2142 2MJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2143 2MK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2144 2ML EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2145 2MM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2146 2MN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2147 2MO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2148 2MP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2149 2MQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x214a 2MR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x214b 2MS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x214c 2MT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x214d 2MU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x214e 2MV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x214f 2MW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2150 2MX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2151 2MY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2152 2MZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2153 2NA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2154 2NB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2155 2NC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2156 2ND EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2157 2NE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2158 2NF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2159 2NG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x215a 2NH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x215b 2NI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x215c 2NJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x215d 2NK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x215e 2NL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x215f 2NM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2160 2NN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2161 2NO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2162 2NP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2163 2NQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2164 2NR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2165 2NS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2166 2NT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2167 2NU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2168 2NV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2169 2NW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x216a 2NX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x216b 2NY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x216c 2NZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x216d 2OA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x216e 2OB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x216f 2OC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2170 2OD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2171 2OE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2172 2OF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2173 2OG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2174 2OH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2175 2OI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2176 2OJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2177 2OK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2178 2OL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2179 2OM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x217a 2ON EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x217b 2OO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x217c 2OP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x217d 2OQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x217e 2OR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x217f 2OS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2180 2OT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2181 2OU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2182 2OV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2183 2OW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2184 2OX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2185 2OY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2186 2OZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2187 2PA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2188 2PB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2189 2PC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x218a 2PD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x218b 2PE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x218c 2PF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x218d 2PG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x218e 2PH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x218f 2PI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2190 2PJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2191 2PK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2192 2PL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2193 2PM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2194 2PN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2195 2PO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2196 2PP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2197 2PQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2198 2PR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2199 2PS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x219a 2PT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x219b 2PU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x219c 2PV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x219d 2PW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x219e 2PX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x219f 2PY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21a0 2PZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21a1 2QA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21a2 2QB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21a3 2QC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21a4 2QD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21a5 2QE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21a6 2QF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21a7 2QG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21a8 2QH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21a9 2QI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21aa 2QJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21ab 2QK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21ac 2QL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21ad 2QM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21ae 2QN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21af 2QO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21b0 2QP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21b1 2QQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21b2 2QR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21b3 2QS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21b4 2QT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21b5 2QU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21b6 2QV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21b7 2QW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21b8 2QX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21b9 2QY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21ba 2QZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21bb 2RA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21bc 2RB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21bd 2RC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21be 2RD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21bf 2RE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21c0 2RF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21c1 2RG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21c2 2RH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21c3 2RI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21c4 2RJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21c5 2RK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21c6 2RL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21c7 2RM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21c8 2RN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21c9 2RO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21ca 2RP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21cb 2RQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21cc 2RR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21cd 2RS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21ce 2RT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21cf 2RU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21d0 2RV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21d1 2RW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21d2 2RX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21d3 2RY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21d4 2RZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21d5 2SA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21d6 2SB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21d7 2SC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21d8 2SD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21d9 2SE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21da 2SF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21db 2SG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21dc 2SH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21dd 2SI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21de 2SJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21df 2SK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21e0 2SL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21e1 2SM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21e2 2SN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21e3 2SO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21e4 2SP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21e5 2SQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21e6 2SR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21e7 2SS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21e8 2ST EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21e9 2SU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21ea 2SV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21eb 2SW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21ec 2SX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21ed 2SY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21ee 2SZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21ef 2TA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21f0 2TB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21f1 2TC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21f2 2TD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21f3 2TE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21f4 2TF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21f5 2TG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21f6 2TH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21f7 2TI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21f8 2TJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21f9 2TK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21fa 2TL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21fb 2TM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21fc 2TN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21fd 2TO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21fe 2TP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x21ff 2TQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (BOW) paletted +0x2201 6AA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2202 6AB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2203 6AC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2204 6AD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2205 6AE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2206 6AF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2207 6AG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2208 6AH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2209 6AI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x220a 6AJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x220b 6AK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x220c 6AL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x220d 6AM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x220e 6AN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x220f 6AO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2210 6AP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2211 6AQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2212 6AR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2213 6AS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2214 6AT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2215 6AU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2216 6AV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2217 6AW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2218 6AX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2219 6AY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x221a 6AZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x221b 6BA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x221c 6BB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x221d 6BC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x221e 6BD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x221f 6BE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2220 6BF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2221 6BG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2222 6BH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2223 6BI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2224 6BJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2225 6BK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2226 6BL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2227 6BM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2228 6BN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2229 6BO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x222a 6BP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x222b 6BQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x222c 6BR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x222d 6BS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x222e 6BT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x222f 6BU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2230 6BV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2231 6BW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2232 6BX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2233 6BY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2234 6BZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2235 6CA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2236 6CB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2237 6CC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2238 6CD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2239 6CE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x223a 6CF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x223b 6CG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x223c 6CH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x223d 6CI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x223e 6CJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x223f 6CK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2240 6CL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2241 6CM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2242 6CN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2243 6CO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2244 6CP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2245 6CQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2246 6CR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2247 6CS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2248 6CT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2249 6CU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x224a 6CV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x224b 6CW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x224c 6CX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x224d 6CY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x224e 6CZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x224f 6DA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2250 6DB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2251 6DC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2252 6DD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2253 6DE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2254 6DF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2255 6DG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2256 6DH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2257 6DI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2258 6DJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2259 6DK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x225a 6DL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x225b 6DM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x225c 6DN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x225d 6DO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x225e 6DP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x225f 6DQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2260 6DR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2261 6DS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2262 6DT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2263 6DU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2264 6DV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2265 6DW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2266 6DX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2267 6DY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2268 6DZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2269 6EA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x226a 6EB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x226b 6EC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x226c 6ED EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x226d 6EE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x226e 6EF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x226f 6EG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2270 6EH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2271 6EI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2272 6EJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2273 6EK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2274 6EL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2275 6EM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2276 6EN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2277 6EO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2278 6EP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2279 6EQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x227a 6ER EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x227b 6ES EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x227c 6ET EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x227d 6EU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x227e 6EV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x227f 6EW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2280 6EX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2281 6EY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2282 6EZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2283 6FA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2284 6FB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2285 6FC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2286 6FD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2287 6FE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2288 6FF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2289 6FG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x228a 6FH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x228b 6FI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x228c 6FJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x228d 6FK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x228e 6FL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x228f 6FM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2290 6FN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2291 6FO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2292 6FP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2293 6FQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2294 6FR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2295 6FS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2296 6FT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2297 6FU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2298 6FV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x2299 6FW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x229a 6FX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x229b 6FY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x229c 6FZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x229d 6GA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x229e 6GB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x229f 6GC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22a0 6GD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22a1 6GE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22a2 6GF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22a3 6GG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22a4 6GH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22a5 6GI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22a6 6GJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22a7 6GK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22a8 6GL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22a9 6GM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22aa 6GN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22ab 6GO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22ac 6GP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22ad 6GQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22ae 6GR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22af 6GS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22b0 6GT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22b1 6GU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22b2 6GV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22b3 6GW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22b4 6GX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22b5 6GY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22b6 6GZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22b7 6HA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22b8 6HB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22b9 6HC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22ba 6HD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22bb 6HE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22bc 6HF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22bd 6HG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22be 6HH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22bf 6HI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22c0 6HJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22c1 6HK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22c2 6HL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22c3 6HM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22c4 6HN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22c5 6HO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22c6 6HP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22c7 6HQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22c8 6HR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22c9 6HS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22ca 6HT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22cb 6HU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22cc 6HV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22cd 6HW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22ce 6HX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22cf 6HY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22d0 6HZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22d1 6IA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22d2 6IB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22d3 6IC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22d4 6ID EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22d5 6IE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22d6 6IF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22d7 6IG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22d8 6IH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22d9 6II EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22da 6IJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22db 6IK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22dc 6IL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22dd 6IM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22de 6IN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22df 6IO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22e0 6IP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22e1 6IQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22e2 6IR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22e3 6IS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22e4 6IT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22e5 6IU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22e6 6IV EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22e7 6IW EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22e8 6IX EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22e9 6IY EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22ea 6IZ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22eb 6JA EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22ec 6JB EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22ed 6JC EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22ee 6JD EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22ef 6JE EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22f0 6JF EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22f1 6JG EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22f2 6JH EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22f3 6JI EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22f4 6JJ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22f5 6JK EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22f6 6JL EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22f7 6JM EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22f8 6JN EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22f9 6JO EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22fa 6JP EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22fb 6JQ EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22fc 6JR EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22fd 6JS EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22fe 6JT EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x22ff 6JU EMPTY NORMAL 3 Xo BGI SIMPLE CASTER (S1) paletted +0x3001 4AA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3002 4AB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3003 4AC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3004 4AD EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3005 4AE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3006 4AF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3007 4AG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3008 4AH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3009 4AI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x300a 4AJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x300b 4AK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x300c 4AL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x300d 4AM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x300e 4AN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x300f 4AO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3010 4AP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3011 4AQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3012 4AR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3013 4AS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3014 4AT EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3015 4AU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3016 4AV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3017 4AW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3018 4AX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3019 4AY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x301a 4AZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x301b 4BA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x301c 4BB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x301d 4BC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x301e 4BD EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x301f 4BE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3020 4BF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3021 4BG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3022 4BH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3023 4BI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3024 4BJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3025 4BK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3026 4BL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3027 4BM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3028 4BN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3029 4BO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x302a 4BP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x302b 4BQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x302c 4BR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x302d 4BS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x302e 4BT EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x302f 4BU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3030 4BV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3031 4BW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3032 4BX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3033 4BY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3034 4BZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3035 4CA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3036 4CB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3037 4CC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3038 4CD EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3039 4CE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x303a 4CF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x303b 4CG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x303c 4CH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x303d 4CI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x303e 4CJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x303f 4CK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3040 4CL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3041 4CM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3042 4CN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3043 4CO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3044 4CP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3045 4CQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3046 4CR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3047 4CS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3048 4CT EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3049 4CU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x304a 4CV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x304b 4CW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x304c 4CX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x304d 4CY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x304e 4CZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x304f 4DA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3050 4DB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3051 4DC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3052 4DD EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3053 4DE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3054 4DF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3055 4DG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3056 4DH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3057 4DI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3058 4DJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3059 4DK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x305a 4DL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x305b 4DM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x305c 4DN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x305d 4DO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x305e 4DP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x305f 4DQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3060 4DR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3061 4DS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3062 4DT EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3063 4DU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3064 4DV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3065 4DW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3066 4DX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3067 4DY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3068 4DZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3069 4EA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x306a 4EB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x306b 4EC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x306c 4ED EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x306d 4EE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x306e 4EF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x306f 4EG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3070 4EH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3071 4EI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3072 4EJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3073 4EK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3074 4EL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3075 4EM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3076 4EN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3077 4EO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3078 4EP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3079 4EQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x307a 4ER EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x307b 4ES EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x307c 4ET EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x307d 4EU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x307e 4EV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x307f 4EW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3080 4EX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3081 4EY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3082 4EZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3083 4FA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3084 4FB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3085 4FC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3086 4FD EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3087 4FE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3088 4FF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3089 4FG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x308a 4FH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x308b 4FI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x308c 4FJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x308d 4FK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x308e 4FL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x308f 4FM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3090 4FN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3091 4FO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3092 4FP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3093 4FQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3094 4FR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3095 4FS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3096 4FT EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3097 4FU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3098 4FV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3099 4FW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x309a 4FX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x309b 4FY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x309c 4FZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x309d 4GA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x309e 4GB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x309f 4GC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30a0 4GD EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30a1 4GE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30a2 4GF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30a3 4GG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30a4 4GH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30a5 4GI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30a6 4GJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30a7 4GK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30a8 4GL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30a9 4GM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30aa 4GN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30ab 4GO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30ac 4GP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30ad 4GQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30ae 4GR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30af 4GS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30b0 4GT EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30b1 4GU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30b2 4GV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30b3 4GW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30b4 4GX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30b5 4GY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30b6 4GZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30b7 4HA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30b8 4HB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30b9 4HC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30ba 4HD EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30bb 4HE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30bc 4HF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30bd 4HG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30be 4HH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30bf 4HI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30c0 4HJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30c1 4HK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30c2 4HL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30c3 4HM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30c4 4HN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30c5 4HO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30c6 4HP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30c7 4HQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30c8 4HR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30c9 4HS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30ca 4HT EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30cb 4HU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30cc 4HV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30cd 4HW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30ce 4HX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30cf 4HY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30d0 4HZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30d1 4IA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30d2 4IB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30d3 4IC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30d4 4ID EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30d5 4IE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30d6 4IF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30d7 4IG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30d8 4IH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30d9 4II EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30da 4IJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30db 4IK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30dc 4IL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30dd 4IM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30de 4IN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30df 4IO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30e0 4IP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30e1 4IQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30e2 4IR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30e3 4IS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30e4 4IT EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30e5 4IU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30e6 4IV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30e7 4IW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30e8 4IX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30e9 4IY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30ea 4IZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30eb 4JA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30ec 4JB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30ed 4JC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30ee 4JD EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30ef 4JE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30f0 4JF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30f1 4JG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30f2 4JH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30f3 4JI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30f4 4JJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30f5 4JK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30f6 4JL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30f7 4JM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30f8 4JN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30f9 4JO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30fa 4JP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30fb 4JQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30fc 4JR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30fd 4JS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30fe 4JT EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x30ff 4JU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3100 4JV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3101 4JW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3102 4JX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3103 4JY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3104 4JZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3105 4KA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3106 4KB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3107 4KC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3108 4KD EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3109 4KE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x310a 4KF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x310b 4KG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x310c 4KH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x310d 4KI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x310e 4KJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x310f 4KK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3110 4KL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3111 4KM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3112 4KN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3113 4KO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3114 4KP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3115 4KQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3116 4KR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3117 4KS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3118 4KT EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3119 4KU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x311a 4KV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x311b 4KW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x311c 4KX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x311d 4KY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x311e 4KZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x311f 4LA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3120 4LB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3121 4LC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3122 4LD EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3123 4LE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3124 4LF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3125 4LG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3126 4LH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3127 4LI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3128 4LJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3129 4LK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x312a 4LL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x312b 4LM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x312c 4LN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x312d 4LO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x312e 4LP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x312f 4LQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3130 4LR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3131 4LS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3132 4LT EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3133 4LU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3134 4LV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3135 4LW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3136 4LX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3137 4LY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3138 4LZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3139 4MA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x313a 4MB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x313b 4MC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x313c 4MD EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x313d 4ME EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x313e 4MF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x313f 4MG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3140 4MH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3141 4MI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3142 4MJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3143 4MK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3144 4ML EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3145 4MM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3146 4MN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3147 4MO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3148 4MP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3149 4MQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x314a 4MR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x314b 4MS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x314c 4MT EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x314d 4MU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x314e 4MV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x314f 4MW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3150 4MX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3151 4MY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3152 4MZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3153 4NA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3154 4NB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3155 4NC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3156 4ND EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3157 4NE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3158 4NF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3159 4NG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x315a 4NH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x315b 4NI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x315c 4NJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x315d 4NK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x315e 4NL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x315f 4NM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3160 4NN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3161 4NO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3162 4NP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3163 4NQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3164 4NR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3165 4NS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3166 4NT EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3167 4NU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3168 4NV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3169 4NW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x316a 4NX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x316b 4NY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x316c 4NZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x316d 4OA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x316e 4OB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x316f 4OC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3170 4OD EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3171 4OE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3172 4OF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3173 4OG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3174 4OH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3175 4OI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3176 4OJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3177 4OK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3178 4OL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3179 4OM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x317a 4ON EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x317b 4OO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x317c 4OP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x317d 4OQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x317e 4OR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x317f 4OS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3180 4OT EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3181 4OU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3182 4OV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3183 4OW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3184 4OX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3185 4OY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3186 4OZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3187 4PA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3188 4PB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3189 4PC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x318a 4PD EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x318b 4PE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x318c 4PF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x318d 4PG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x318e 4PH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x318f 4PI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3190 4PJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3191 4PK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3192 4PL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3193 4PM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3194 4PN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3195 4PO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3196 4PP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3197 4PQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3198 4PR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x3199 4PS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x319a 4PT EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x319b 4PU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x319c 4PV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x319d 4PW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x319e 4PX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x319f 4PY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31a0 4PZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31a1 4QA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31a2 4QB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31a3 4QC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31a4 4QD EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31a5 4QE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31a6 4QF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31a7 4QG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31a8 4QH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31a9 4QI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31aa 4QJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31ab 4QK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31ac 4QL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31ad 4QM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31ae 4QN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31af 4QO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31b0 4QP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31b1 4QQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31b2 4QR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31b3 4QS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31b4 4QT EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31b5 4QU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31b6 4QV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31b7 4QW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31b8 4QX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31b9 4QY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31ba 4QZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31bb 4RA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31bc 4RB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31bd 4RC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31be 4RD EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31bf 4RE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31c0 4RF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31c1 4RG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31c2 4RH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31c3 4RI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31c4 4RJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31c5 4RK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31c6 4RL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31c7 4RM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31c8 4RN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31c9 4RO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31ca 4RP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31cb 4RQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31cc 4RR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31cd 4RS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31ce 4RT EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31cf 4RU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31d0 4RV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31d1 4RW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31d2 4RX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31d3 4RY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31d4 4RZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31d5 4SA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31d6 4SB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31d7 4SC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31d8 4SD EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31d9 4SE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31da 4SF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31db 4SG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31dc 4SH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31dd 4SI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31de 4SJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31df 4SK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31e0 4SL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31e1 4SM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31e2 4SN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31e3 4SO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31e4 4SP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31e5 4SQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31e6 4SR EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31e7 4SS EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31e8 4ST EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31e9 4SU EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31ea 4SV EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31eb 4SW EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31ec 4SX EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31ed 4SY EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31ee 4SZ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31ef 4TA EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31f0 4TB EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31f1 4TC EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31f2 4TD EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31f3 4TE EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31f4 4TF EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31f5 4TG EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31f6 4TH EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31f7 4TI EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31f8 4TJ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31f9 4TK EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31fa 4TL EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31fb 4TM EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31fc 4TN EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31fd 4TO EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31fe 4TP EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x31ff 4TQ EMPTY LARGE 5 Xo BROKEN ANKHEG unpaletted +0x5000 3AA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5001 3AB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5002 3AC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5003 3AD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5004 3AE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5005 3AF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5006 3AG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5007 3AH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5008 3AI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5009 3AJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x500a 3AK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x500b 3AL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x500c 3AM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x500d 3AN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x500e 3AO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x500f 3AP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5010 3AQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5011 3AR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5012 3AS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5013 3AT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5014 3AU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5015 3AV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5016 3AW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5017 3AX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5018 3AY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5019 3AZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x501a 3BA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x501b 3BB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x501c 3BC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x501d 3BD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x501e 3BE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x501f 3BF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5020 3BG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5021 3BH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5022 3BI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5023 3BJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5024 3BK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5025 3BL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5026 3BM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5027 3BN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5028 3BO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5029 3BP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x502a 3BQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x502b 3BR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x502c 3BS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x502d 3BT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x502e 3BU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x502f 3BV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5030 3BW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5031 3BX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5032 3BY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5033 3BZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5034 3CA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5035 3CB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5036 3CC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5037 3CD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5038 3CE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5039 3CF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x503a 3CG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x503b 3CH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x503c 3CI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x503d 3CJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x503e 3CK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x503f 3CL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5040 3CM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5041 3CN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5042 3CO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5043 3CP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5044 3CQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5045 3CR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5046 3CS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5047 3CT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5048 3CU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5049 3CV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x504a 3CW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x504b 3CX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x504c 3CY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x504d 3CZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x504e 3DA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x504f 3DB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5050 3DC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5051 3DD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5052 3DE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5053 3DF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5054 3DG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5055 3DH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5056 3DI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5057 3DJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5058 3DK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5059 3DL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x505a 3DM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x505b 3DN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x505c 3DO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x505d 3DP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x505e 3DQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x505f 3DR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5060 3DS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5061 3DT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5062 3DU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5063 3DV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5064 3DW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5065 3DX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5066 3DY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5067 3DZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5068 3EA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5069 3EB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x506a 3EC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x506b 3ED EMPTY LARGE 5 Io IWD LARGE unpaletted +0x506c 3EE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x506d 3EF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x506e 3EG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x506f 3EH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5070 3EI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5071 3EJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5072 3EK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5073 3EL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5074 3EM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5075 3EN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5076 3EO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5077 3EP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5078 3EQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5079 3ER EMPTY LARGE 5 Io IWD LARGE unpaletted +0x507a 3ES EMPTY LARGE 5 Io IWD LARGE unpaletted +0x507b 3ET EMPTY LARGE 5 Io IWD LARGE unpaletted +0x507c 3EU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x507d 3EV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x507e 3EW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x507f 3EX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5080 3EY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5081 3EZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5082 3FA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5083 3FB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5084 3FC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5085 3FD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5086 3FE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5087 3FF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5088 3FG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5089 3FH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x508a 3FI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x508b 3FJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x508c 3FK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x508d 3FL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x508e 3FM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x508f 3FN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5090 3FO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5091 3FP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5092 3FQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5093 3FR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5094 3FS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5095 3FT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5096 3FU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5097 3FV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5098 3FW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5099 3FX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x509a 3FY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x509b 3FZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x509c 3GA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x509d 3GB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x509e 3GC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x509f 3GD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50a0 3GE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50a1 3GF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50a2 3GG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50a3 3GH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50a4 3GI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50a5 3GJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50a6 3GK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50a7 3GL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50a8 3GM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50a9 3GN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50aa 3GO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50ab 3GP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50ac 3GQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50ad 3GR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50ae 3GS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50af 3GT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50b0 3GU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50b1 3GV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50b2 3GW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50b3 3GX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50b4 3GY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50b5 3GZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50b6 3HA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50b7 3HB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50b8 3HC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50b9 3HD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50ba 3HE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50bb 3HF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50bc 3HG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50bd 3HH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50be 3HI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50bf 3HJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50c0 3HK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50c1 3HL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50c2 3HM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50c3 3HN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50c4 3HO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50c5 3HP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50c6 3HQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50c7 3HR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50c8 3HS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50c9 3HT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50ca 3HU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50cb 3HV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50cc 3HW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50cd 3HX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50ce 3HY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50cf 3HZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50d0 3IA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50d1 3IB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50d2 3IC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50d3 3ID EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50d4 3IE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50d5 3IF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50d6 3IG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50d7 3IH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50d8 3II EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50d9 3IJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50da 3IK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50db 3IL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50dc 3IM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50dd 3IN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50de 3IO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50df 3IP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50e0 3IQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50e1 3IR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50e2 3IS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50e3 3IT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50e4 3IU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50e5 3IV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50e6 3IW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50e7 3IX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50e8 3IY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50e9 3IZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50ea 3JA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50eb 3JB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50ec 3JC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50ed 3JD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50ee 3JE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50ef 3JF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50f0 3JG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50f1 3JH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50f2 3JI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50f3 3JJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50f4 3JK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50f5 3JL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50f6 3JM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50f7 3JN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50f8 3JO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50f9 3JP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50fa 3JQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50fb 3JR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50fc 3JS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50fd 3JT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50fe 3JU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x50ff 3JV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5100 3JW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5101 3JX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5102 3JY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5103 3JZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5104 3KA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5105 3KB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5106 3KC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5107 3KD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5108 3KE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5109 3KF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x510a 3KG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x510b 3KH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x510c 3KI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x510d 3KJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x510e 3KK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x510f 3KL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5110 3KM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5111 3KN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5112 3KO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5113 3KP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5114 3KQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5115 3KR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5116 3KS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5117 3KT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5118 3KU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5119 3KV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x511a 3KW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x511b 3KX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x511c 3KY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x511d 3KZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x511e 3LA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x511f 3LB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5120 3LC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5121 3LD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5122 3LE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5123 3LF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5124 3LG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5125 3LH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5126 3LI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5127 3LJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5128 3LK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5129 3LL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x512a 3LM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x512b 3LN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x512c 3LO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x512d 3LP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x512e 3LQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x512f 3LR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5130 3LS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5131 3LT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5132 3LU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5133 3LV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5134 3LW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5135 3LX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5136 3LY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5137 3LZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5138 3MA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5139 3MB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x513a 3MC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x513b 3MD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x513c 3ME EMPTY LARGE 5 Io IWD LARGE unpaletted +0x513d 3MF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x513e 3MG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x513f 3MH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5140 3MI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5141 3MJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5142 3MK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5143 3ML EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5144 3MM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5145 3MN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5146 3MO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5147 3MP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5148 3MQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5149 3MR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x514a 3MS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x514b 3MT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x514c 3MU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x514d 3MV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x514e 3MW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x514f 3MX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5150 3MY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5151 3MZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5152 3NA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5153 3NB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5154 3NC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5155 3ND EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5156 3NE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5157 3NF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5158 3NG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5159 3NH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x515a 3NI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x515b 3NJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x515c 3NK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x515d 3NL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x515e 3NM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x515f 3NN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5160 3NO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5161 3NP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5162 3NQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5163 3NR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5164 3NS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5165 3NT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5166 3NU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5167 3NV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5168 3NW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5169 3NX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x516a 3NY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x516b 3NZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x516c 3OA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x516d 3OB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x516e 3OC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x516f 3OD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5170 3OE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5171 3OF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5172 3OG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5173 3OH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5174 3OI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5175 3OJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5176 3OK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5177 3OL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5178 3OM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5179 3ON EMPTY LARGE 5 Io IWD LARGE unpaletted +0x517a 3OO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x517b 3OP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x517c 3OQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x517d 3OR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x517e 3OS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x517f 3OT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5180 3OU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5181 3OV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5182 3OW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5183 3OX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5184 3OY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5185 3OZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5186 3PA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5187 3PB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5188 3PC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5189 3PD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x518a 3PE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x518b 3PF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x518c 3PG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x518d 3PH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x518e 3PI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x518f 3PJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5190 3PK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5191 3PL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5192 3PM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5193 3PN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5194 3PO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5195 3PP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5196 3PQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5197 3PR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5198 3PS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5199 3PT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x519a 3PU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x519b 3PV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x519c 3PW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x519d 3PX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x519e 3PY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x519f 3PZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51a0 3QA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51a1 3QB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51a2 3QC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51a3 3QD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51a4 3QE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51a5 3QF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51a6 3QG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51a7 3QH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51a8 3QI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51a9 3QJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51aa 3QK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51ab 3QL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51ac 3QM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51ad 3QN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51ae 3QO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51af 3QP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51b0 3QQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51b1 3QR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51b2 3QS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51b3 3QT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51b4 3QU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51b5 3QV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51b6 3QW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51b7 3QX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51b8 3QY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51b9 3QZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51ba 3RA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51bb 3RB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51bc 3RC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51bd 3RD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51be 3RE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51bf 3RF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51c0 3RG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51c1 3RH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51c2 3RI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51c3 3RJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51c4 3RK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51c5 3RL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51c6 3RM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51c7 3RN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51c8 3RO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51c9 3RP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51ca 3RQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51cb 3RR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51cc 3RS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51cd 3RT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51ce 3RU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51cf 3RV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51d0 3RW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51d1 3RX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51d2 3RY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51d3 3RZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51d4 3SA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51d5 3SB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51d6 3SC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51d7 3SD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51d8 3SE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51d9 3SF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51da 3SG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51db 3SH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51dc 3SI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51dd 3SJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51de 3SK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51df 3SL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51e0 3SM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51e1 3SN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51e2 3SO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51e3 3SP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51e4 3SQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51e5 3SR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51e6 3SS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51e7 3ST EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51e8 3SU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51e9 3SV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51ea 3SW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51eb 3SX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51ec 3SY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51ed 3SZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51ee 3TA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51ef 3TB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51f0 3TC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51f1 3TD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51f2 3TE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51f3 3TF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51f4 3TG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51f5 3TH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51f6 3TI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51f7 3TJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51f8 3TK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51f9 3TL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51fa 3TM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51fb 3TN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51fc 3TO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51fd 3TP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51fe 3TQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x51ff 3TR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5200 3TS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5201 3TT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5202 3TU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5203 3TV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5204 3TW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5205 3TX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5206 3TY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5207 3TZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5208 3UA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5209 3UB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x520a 3UC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x520b 3UD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x520c 3UE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x520d 3UF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x520e 3UG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x520f 3UH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5210 3UI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5211 3UJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5212 3UK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5213 3UL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5214 3UM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5215 3UN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5216 3UO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5217 3UP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5218 3UQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5219 3UR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x521a 3US EMPTY LARGE 5 Io IWD LARGE unpaletted +0x521b 3UT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x521c 3UU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x521d 3UV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x521e 3UW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x521f 3UX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5220 3UY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5221 3UZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5222 3VA EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5223 3VB EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5224 3VC EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5225 3VD EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5226 3VE EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5227 3VF EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5228 3VG EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5229 3VH EMPTY LARGE 5 Io IWD LARGE unpaletted +0x522a 3VI EMPTY LARGE 5 Io IWD LARGE unpaletted +0x522b 3VJ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x522c 3VK EMPTY LARGE 5 Io IWD LARGE unpaletted +0x522d 3VL EMPTY LARGE 5 Io IWD LARGE unpaletted +0x522e 3VM EMPTY LARGE 5 Io IWD LARGE unpaletted +0x522f 3VN EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5230 3VO EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5231 3VP EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5232 3VQ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5233 3VR EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5234 3VS EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5235 3VT EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5236 3VU EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5237 3VV EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5238 3VW EMPTY LARGE 5 Io IWD LARGE unpaletted +0x5239 3VX EMPTY LARGE 5 Io IWD LARGE unpaletted +0x523a 3VY EMPTY LARGE 5 Io IWD LARGE unpaletted +0x523b 3VZ EMPTY LARGE 5 Io IWD LARGE unpaletted +0x523d 3AA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x523e 3AB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x523f 3AC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5240 3AD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5241 3AE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5242 3AF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5243 3AG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5244 3AH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5245 3AI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5246 3AJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5247 3AK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5248 3AL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5249 3AM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x524a 3AN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x524b 3AO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x524c 3AP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x524d 3AQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x524e 3AR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x524f 3AS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5250 3AT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5251 3AU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5252 3AV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5253 3AW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5254 3AX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5255 3AY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5256 3AZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5257 3BA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5258 3BB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5259 3BC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x525a 3BD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x525b 3BE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x525c 3BF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x525d 3BG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x525e 3BH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x525f 3BI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5260 3BJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5261 3BK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5262 3BL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5263 3BM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5264 3BN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5265 3BO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5266 3BP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5267 3BQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5268 3BR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5269 3BS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x526a 3BT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x526b 3BU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x526c 3BV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x526d 3BW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x526e 3BX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x526f 3BY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5270 3BZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5271 3CA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5272 3CB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5273 3CC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5274 3CD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5275 3CE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5276 3CF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5277 3CG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5278 3CH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5279 3CI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x527a 3CJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x527b 3CK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x527c 3CL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x527d 3CM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x527e 3CN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x527f 3CO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5280 3CP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5281 3CQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5282 3CR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5283 3CS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5284 3CT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5285 3CU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5286 3CV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5287 3CW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5288 3CX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5289 3CY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x528a 3CZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x528b 3DA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x528c 3DB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x528d 3DC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x528e 3DD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x528f 3DE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5290 3DF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5291 3DG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5292 3DH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5293 3DI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5294 3DJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5295 3DK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5296 3DL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5297 3DM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5298 3DN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5299 3DO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x529a 3DP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x529b 3DQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x529c 3DR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x529d 3DS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x529e 3DT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x529f 3DU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52a0 3DV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52a1 3DW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52a2 3DX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52a3 3DY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52a4 3DZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52a5 3EA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52a6 3EB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52a7 3EC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52a8 3ED EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52a9 3EE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52aa 3EF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52ab 3EG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52ac 3EH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52ad 3EI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52ae 3EJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52af 3EK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52b0 3EL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52b1 3EM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52b2 3EN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52b3 3EO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52b4 3EP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52b5 3EQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52b6 3ER EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52b7 3ES EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52b8 3ET EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52b9 3EU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52ba 3EV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52bb 3EW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52bc 3EX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52bd 3EY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52be 3EZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52bf 3FA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52c0 3FB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52c1 3FC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52c2 3FD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52c3 3FE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52c4 3FF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52c5 3FG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52c6 3FH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52c7 3FI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52c8 3FJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52c9 3FK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52ca 3FL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52cb 3FM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52cc 3FN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52cd 3FO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52ce 3FP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52cf 3FQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52d0 3FR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52d1 3FS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52d2 3FT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52d3 3FU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52d4 3FV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52d5 3FW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52d6 3FX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52d7 3FY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52d8 3FZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52d9 3GA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52da 3GB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52db 3GC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52dc 3GD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52dd 3GE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52de 3GF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52df 3GG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52e0 3GH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52e1 3GI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52e2 3GJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52e3 3GK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52e4 3GL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52e5 3GM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52e6 3GN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52e7 3GO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52e8 3GP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52e9 3GQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52ea 3GR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52eb 3GS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52ec 3GT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52ed 3GU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52ee 3GV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52ef 3GW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52f0 3GX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52f1 3GY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52f2 3GZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52f3 3HA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52f4 3HB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52f5 3HC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52f6 3HD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52f7 3HE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52f8 3HF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52f9 3HG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52fa 3HH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52fb 3HI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52fc 3HJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52fd 3HK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52fe 3HL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x52ff 3HM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5300 3HN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5301 3HO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5302 3HP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5303 3HQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5304 3HR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5305 3HS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5306 3HT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5307 3HU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5308 3HV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5309 3HW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x530a 3HX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x530b 3HY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x530c 3HZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x530d 3IA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x530e 3IB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x530f 3IC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5310 3ID EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5311 3IE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5312 3IF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5313 3IG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5314 3IH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5315 3II EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5316 3IJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5317 3IK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5318 3IL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5319 3IM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x531a 3IN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x531b 3IO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x531c 3IP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x531d 3IQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x531e 3IR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x531f 3IS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5320 3IT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5321 3IU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5322 3IV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5323 3IW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5324 3IX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5325 3IY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5326 3IZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5327 3JA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5328 3JB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5329 3JC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x532a 3JD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x532b 3JE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x532c 3JF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x532d 3JG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x532e 3JH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x532f 3JI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5330 3JJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5331 3JK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5332 3JL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5333 3JM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5334 3JN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5335 3JO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5336 3JP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5337 3JQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5338 3JR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5339 3JS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x533a 3JT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x533b 3JU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x533c 3JV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x533d 3JW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x533e 3JX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x533f 3JY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5340 3JZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5341 3KA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5342 3KB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5343 3KC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5344 3KD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5345 3KE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5346 3KF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5347 3KG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5348 3KH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5349 3KI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x534a 3KJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x534b 3KK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x534c 3KL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x534d 3KM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x534e 3KN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x534f 3KO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5350 3KP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5351 3KQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5352 3KR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5353 3KS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5354 3KT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5355 3KU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5356 3KV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5357 3KW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5358 3KX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5359 3KY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x535a 3KZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x535b 3LA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x535c 3LB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x535d 3LC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x535e 3LD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x535f 3LE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5360 3LF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5361 3LG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5362 3LH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5363 3LI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5364 3LJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5365 3LK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5366 3LL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5367 3LM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5368 3LN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5369 3LO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x536a 3LP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x536b 3LQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x536c 3LR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x536d 3LS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x536e 3LT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x536f 3LU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5370 3LV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5371 3LW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5372 3LX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5373 3LY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5374 3LZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5375 3MA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5376 3MB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5377 3MC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5378 3MD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5379 3ME EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x537a 3MF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x537b 3MG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x537c 3MH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x537d 3MI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x537e 3MJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x537f 3MK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5380 3ML EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5381 3MM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5382 3MN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5383 3MO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5384 3MP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5385 3MQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5386 3MR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5387 3MS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5388 3MT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5389 3MU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x538a 3MV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x538b 3MW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x538c 3MX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x538d 3MY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x538e 3MZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x538f 3NA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5390 3NB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5391 3NC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5392 3ND EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5393 3NE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5394 3NF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5395 3NG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5396 3NH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5397 3NI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5398 3NJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5399 3NK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x539a 3NL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x539b 3NM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x539c 3NN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x539d 3NO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x539e 3NP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x539f 3NQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53a0 3NR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53a1 3NS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53a2 3NT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53a3 3NU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53a4 3NV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53a5 3NW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53a6 3NX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53a7 3NY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53a8 3NZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53a9 3OA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53aa 3OB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53ab 3OC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53ac 3OD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53ad 3OE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53ae 3OF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53af 3OG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53b0 3OH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53b1 3OI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53b2 3OJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53b3 3OK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53b4 3OL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53b5 3OM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53b6 3ON EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53b7 3OO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53b8 3OP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53b9 3OQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53ba 3OR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53bb 3OS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53bc 3OT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53bd 3OU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53be 3OV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53bf 3OW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53c0 3OX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53c1 3OY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53c2 3OZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53c3 3PA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53c4 3PB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53c5 3PC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53c6 3PD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53c7 3PE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53c8 3PF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53c9 3PG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53ca 3PH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53cb 3PI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53cc 3PJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53cd 3PK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53ce 3PL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53cf 3PM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53d0 3PN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53d1 3PO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53d2 3PP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53d3 3PQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53d4 3PR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53d5 3PS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53d6 3PT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53d7 3PU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53d8 3PV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53d9 3PW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53da 3PX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53db 3PY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53dc 3PZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53dd 3QA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53de 3QB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53df 3QC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53e0 3QD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53e1 3QE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53e2 3QF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53e3 3QG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53e4 3QH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53e5 3QI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53e6 3QJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53e7 3QK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53e8 3QL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53e9 3QM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53ea 3QN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53eb 3QO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53ec 3QP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53ed 3QQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53ee 3QR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53ef 3QS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53f0 3QT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53f1 3QU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53f2 3QV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53f3 3QW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53f4 3QX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53f5 3QY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53f6 3QZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53f7 3RA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53f8 3RB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53f9 3RC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53fa 3RD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53fb 3RE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53fc 3RF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53fd 3RG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53fe 3RH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x53ff 3RI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5400 3RJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5401 3RK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5402 3RL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5403 3RM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5404 3RN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5405 3RO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5406 3RP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5407 3RQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5408 3RR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5409 3RS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x540a 3RT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x540b 3RU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x540c 3RV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x540d 3RW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x540e 3RX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x540f 3RY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5410 3RZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5411 3SA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5412 3SB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5413 3SC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5414 3SD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5415 3SE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5416 3SF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5417 3SG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5418 3SH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5419 3SI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x541a 3SJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x541b 3SK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x541c 3SL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x541d 3SM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x541e 3SN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x541f 3SO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5420 3SP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5421 3SQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5422 3SR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5423 3SS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5424 3ST EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5425 3SU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5426 3SV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5427 3SW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5428 3SX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5429 3SY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x542a 3SZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x542b 3TA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x542c 3TB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x542d 3TC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x542e 3TD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x542f 3TE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5430 3TF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5431 3TG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5432 3TH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5433 3TI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5434 3TJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5435 3TK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5436 3TL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5437 3TM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5438 3TN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5439 3TO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x543a 3TP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x543b 3TQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x543c 3TR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x543d 3TS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x543e 3TT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x543f 3TU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5440 3TV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5441 3TW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5442 3TX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5443 3TY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5444 3TZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5445 3UA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5446 3UB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5447 3UC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5448 3UD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5449 3UE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x544a 3UF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x544b 3UG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x544c 3UH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x544d 3UI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x544e 3UJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x544f 3UK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5450 3UL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5451 3UM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5452 3UN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5453 3UO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5454 3UP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5455 3UQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5456 3UR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5457 3US EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5458 3UT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5459 3UU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x545a 3UV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x545b 3UW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x545c 3UX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x545d 3UY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x545e 3UZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x545f 3VA EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5460 3VB EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5461 3VC EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5462 3VD EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5463 3VE EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5464 3VF EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5465 3VG EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5466 3VH EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5467 3VI EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5468 3VJ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5469 3VK EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x546a 3VL EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x546b 3VM EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x546c 3VN EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x546d 3VO EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x546e 3VP EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x546f 3VQ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5470 3VR EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5471 3VS EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5472 3VT EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5473 3VU EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5474 3VV EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5475 3VW EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5476 3VX EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5477 3VY EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x5478 3VZ EMPTY NORMAL 3 Io IWD SMALL unpaletted +0x547a 1AA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x547b 1AB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x547c 1AC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x547d 1AD EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x547e 1AE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x547f 1AF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5480 1AG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5481 1AH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5482 1AI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5483 1AJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5484 1AK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5485 1AL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5486 1AM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5487 1AN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5488 1AO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5489 1AP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x548a 1AQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x548b 1AR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x548c 1AS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x548d 1AT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x548e 1AU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x548f 1AV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5490 1AW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5491 1AX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5492 1AY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5493 1AZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5494 1BA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5495 1BB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5496 1BC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5497 1BD EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5498 1BE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5499 1BF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x549a 1BG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x549b 1BH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x549c 1BI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x549d 1BJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x549e 1BK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x549f 1BL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54a0 1BM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54a1 1BN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54a2 1BO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54a3 1BP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54a4 1BQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54a5 1BR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54a6 1BS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54a7 1BT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54a8 1BU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54a9 1BV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54aa 1BW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54ab 1BX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54ac 1BY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54ad 1BZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54ae 1CA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54af 1CB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54b0 1CC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54b1 1CD EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54b2 1CE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54b3 1CF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54b4 1CG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54b5 1CH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54b6 1CI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54b7 1CJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54b8 1CK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54b9 1CL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54ba 1CM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54bb 1CN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54bc 1CO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54bd 1CP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54be 1CQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54bf 1CR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54c0 1CS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54c1 1CT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54c2 1CU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54c3 1CV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54c4 1CW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54c5 1CX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54c6 1CY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54c7 1CZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54c8 1DA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54c9 1DB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54ca 1DC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54cb 1DD EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54cc 1DE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54cd 1DF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54ce 1DG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54cf 1DH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54d0 1DI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54d1 1DJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54d2 1DK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54d3 1DL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54d4 1DM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54d5 1DN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54d6 1DO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54d7 1DP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54d8 1DQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54d9 1DR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54da 1DS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54db 1DT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54dc 1DU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54dd 1DV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54de 1DW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54df 1DX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54e0 1DY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54e1 1DZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54e2 1EA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54e3 1EB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54e4 1EC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54e5 1ED EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54e6 1EE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54e7 1EF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54e8 1EG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54e9 1EH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54ea 1EI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54eb 1EJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54ec 1EK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54ed 1EL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54ee 1EM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54ef 1EN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54f0 1EO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54f1 1EP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54f2 1EQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54f3 1ER EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54f4 1ES EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54f5 1ET EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54f6 1EU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54f7 1EV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54f8 1EW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54f9 1EX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54fa 1EY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54fb 1EZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54fc 1FA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54fd 1FB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54fe 1FC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x54ff 1FD EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5500 1FE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5501 1FF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5502 1FG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5503 1FH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5504 1FI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5505 1FJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5506 1FK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5507 1FL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5508 1FM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5509 1FN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x550a 1FO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x550b 1FP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x550c 1FQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x550d 1FR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x550e 1FS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x550f 1FT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5510 1FU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5511 1FV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5512 1FW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5513 1FX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5514 1FY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5515 1FZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5516 1GA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5517 1GB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5518 1GC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5519 1GD EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x551a 1GE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x551b 1GF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x551c 1GG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x551d 1GH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x551e 1GI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x551f 1GJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5520 1GK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5521 1GL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5522 1GM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5523 1GN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5524 1GO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5525 1GP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5526 1GQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5527 1GR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5528 1GS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5529 1GT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x552a 1GU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x552b 1GV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x552c 1GW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x552d 1GX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x552e 1GY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x552f 1GZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5530 1HA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5531 1HB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5532 1HC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5533 1HD EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5534 1HE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5535 1HF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5536 1HG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5537 1HH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5538 1HI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5539 1HJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x553a 1HK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x553b 1HL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x553c 1HM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x553d 1HN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x553e 1HO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x553f 1HP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5540 1HQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5541 1HR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5542 1HS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5543 1HT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5544 1HU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5545 1HV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5546 1HW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5547 1HX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5548 1HY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5549 1HZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x554a 1IA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x554b 1IB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x554c 1IC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x554d 1ID EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x554e 1IE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x554f 1IF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5550 1IG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5551 1IH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5552 1II EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5553 1IJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5554 1IK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5555 1IL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5556 1IM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5557 1IN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5558 1IO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5559 1IP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x555a 1IQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x555b 1IR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x555c 1IS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x555d 1IT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x555e 1IU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x555f 1IV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5560 1IW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5561 1IX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5562 1IY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5563 1IZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5564 1JA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5565 1JB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5566 1JC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5567 1JD EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5568 1JE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5569 1JF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x556a 1JG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x556b 1JH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x556c 1JI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x556d 1JJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x556e 1JK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x556f 1JL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5570 1JM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5571 1JN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5572 1JO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5573 1JP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5574 1JQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5575 1JR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5576 1JS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5577 1JT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5578 1JU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x5579 1JV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x557a 1JW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER unpaletted +0x557b 1JX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x557c 1JY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x557d 1JZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x557e 1KA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x557f 1KB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5580 1KC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5581 1KD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5582 1KE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5583 1KF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5584 1KG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5585 1KH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5586 1KI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5587 1KJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5588 1KK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5589 1KL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x558a 1KM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x558b 1KN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x558c 1KO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x558d 1KP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x558e 1KQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x558f 1KR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5590 1KS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5591 1KT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5592 1KU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5593 1KV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5594 1KW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5595 1KX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5596 1KY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5597 1KZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5598 1LA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5599 1LB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x559a 1LC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x559b 1LD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x559c 1LE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x559d 1LF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x559e 1LG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x559f 1LH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55a0 1LI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55a1 1LJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55a2 1LK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55a3 1LL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55a4 1LM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55a5 1LN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55a6 1LO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55a7 1LP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55a8 1LQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55a9 1LR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55aa 1LS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55ab 1LT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55ac 1LU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55ad 1LV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55ae 1LW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55af 1LX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55b0 1LY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55b1 1LZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55b2 1MA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55b3 1MB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55b4 1MC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55b5 1MD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55b6 1ME EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55b7 1MF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55b8 1MG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55b9 1MH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55ba 1MI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55bb 1MJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55bc 1MK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55bd 1ML EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55be 1MM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55bf 1MN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55c0 1MO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55c1 1MP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55c2 1MQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55c3 1MR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55c4 1MS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55c5 1MT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55c6 1MU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55c7 1MV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55c8 1MW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55c9 1MX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55ca 1MY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55cb 1MZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55cc 1NA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55cd 1NB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55ce 1NC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55cf 1ND EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55d0 1NE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55d1 1NF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55d2 1NG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55d3 1NH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55d4 1NI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55d5 1NJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55d6 1NK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55d7 1NL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55d8 1NM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55d9 1NN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55da 1NO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55db 1NP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55dc 1NQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55dd 1NR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55de 1NS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55df 1NT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55e0 1NU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55e1 1NV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55e2 1NW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55e3 1NX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55e4 1NY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55e5 1NZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55e6 1OA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55e7 1OB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55e8 1OC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55e9 1OD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55ea 1OE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55eb 1OF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55ec 1OG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55ed 1OH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55ee 1OI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55ef 1OJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55f0 1OK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55f1 1OL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55f2 1OM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55f3 1ON EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55f4 1OO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55f5 1OP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55f6 1OQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55f7 1OR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55f8 1OS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55f9 1OT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55fa 1OU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55fb 1OV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55fc 1OW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55fd 1OX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55fe 1OY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x55ff 1OZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5600 1PA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5601 1PB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5602 1PC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5603 1PD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5604 1PE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5605 1PF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5606 1PG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5607 1PH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5608 1PI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5609 1PJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x560a 1PK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x560b 1PL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x560c 1PM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x560d 1PN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x560e 1PO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x560f 1PP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5610 1PQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5611 1PR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5612 1PS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5613 1PT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5614 1PU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5615 1PV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5616 1PW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5617 1PX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5618 1PY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5619 1PZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x561a 1QA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x561b 1QB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x561c 1QC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x561d 1QD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x561e 1QE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x561f 1QF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5620 1QG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5621 1QH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5622 1QI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5623 1QJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5624 1QK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5625 1QL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5626 1QM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5627 1QN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5628 1QO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5629 1QP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x562a 1QQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x562b 1QR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x562c 1QS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x562d 1QT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x562e 1QU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x562f 1QV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5630 1QW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5631 1QX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5632 1QY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5633 1QZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5634 1RA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5635 1RB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5636 1RC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5637 1RD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5638 1RE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5639 1RF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x563a 1RG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x563b 1RH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x563c 1RI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x563d 1RJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x563e 1RK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x563f 1RL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5640 1RM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5641 1RN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5642 1RO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5643 1RP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5644 1RQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5645 1RR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5646 1RS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5647 1RT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5648 1RU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5649 1RV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x564a 1RW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x564b 1RX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x564c 1RY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x564d 1RZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x564e 1SA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x564f 1SB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5650 1SC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5651 1SD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5652 1SE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5653 1SF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5654 1SG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5655 1SH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5656 1SI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5657 1SJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5658 1SK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5659 1SL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x565a 1SM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x565b 1SN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x565c 1SO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x565d 1SP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x565e 1SQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x565f 1SR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5660 1SS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5661 1ST EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5662 1SU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5663 1SV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5664 1SW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5665 1SX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5666 1SY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5667 1SZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5668 1TA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5669 1TB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x566a 1TC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x566b 1TD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x566c 1TE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x566d 1TF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x566e 1TG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x566f 1TH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5670 1TI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5671 1TJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5672 1TK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5673 1TL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5674 1TM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5675 1TN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5676 1TO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5677 1TP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5678 1TQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5679 1TR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x567a 1TS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x567b 1TT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x567c 1TU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x567d 1TV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x567e 1TW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x567f 1TX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5680 1TY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5681 1TZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5682 1UA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5683 1UB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5684 1UC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5685 1UD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5686 1UE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5687 1UF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5688 1UG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5689 1UH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x568a 1UI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x568b 1UJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x568c 1UK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x568d 1UL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x568e 1UM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x568f 1UN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5690 1UO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5691 1UP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5692 1UQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5693 1UR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5694 1US EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5695 1UT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5696 1UU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5697 1UV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5698 1UW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x5699 1UX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x569a 1UY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x569b 1UZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x569c 1VA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x569d 1VB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x569e 1VC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x569f 1VD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56a0 1VE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56a1 1VF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56a2 1VG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56a3 1VH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56a4 1VI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56a5 1VJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56a6 1VK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56a7 1VL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56a8 1VM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56a9 1VN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56aa 1VO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56ab 1VP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56ac 1VQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56ad 1VR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56ae 1VS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56af 1VT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56b0 1VU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56b1 1VV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56b2 1VW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56b3 1VX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56b4 1VY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56b5 1VZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56b6 1WA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER unpaletted +0x56b7 1AA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56b8 1AB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56b9 1AC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56ba 1AD EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56bb 1AE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56bc 1AF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56bd 1AG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56be 1AH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56bf 1AI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56c0 1AJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56c1 1AK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56c2 1AL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56c3 1AM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56c4 1AN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56c5 1AO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56c6 1AP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56c7 1AQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56c8 1AR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56c9 1AS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56ca 1AT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56cb 1AU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56cc 1AV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56cd 1AW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56ce 1AX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56cf 1AY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56d0 1AZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56d1 1BA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56d2 1BB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56d3 1BC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56d4 1BD EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56d5 1BE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56d6 1BF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56d7 1BG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56d8 1BH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56d9 1BI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56da 1BJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56db 1BK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56dc 1BL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56dd 1BM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56de 1BN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56df 1BO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56e0 1BP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56e1 1BQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56e2 1BR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56e3 1BS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56e4 1BT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56e5 1BU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56e6 1BV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56e7 1BW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56e8 1BX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56e9 1BY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56ea 1BZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56eb 1CA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56ec 1CB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56ed 1CC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56ee 1CD EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56ef 1CE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56f0 1CF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56f1 1CG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56f2 1CH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56f3 1CI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56f4 1CJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56f5 1CK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56f6 1CL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56f7 1CM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56f8 1CN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56f9 1CO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56fa 1CP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56fb 1CQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56fc 1CR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56fd 1CS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56fe 1CT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x56ff 1CU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5700 1CV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5701 1CW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5702 1CX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5703 1CY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5704 1CZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5705 1DA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5706 1DB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5707 1DC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5708 1DD EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5709 1DE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x570a 1DF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x570b 1DG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x570c 1DH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x570d 1DI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x570e 1DJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x570f 1DK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5710 1DL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5711 1DM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5712 1DN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5713 1DO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5714 1DP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5715 1DQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5716 1DR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5717 1DS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5718 1DT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5719 1DU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x571a 1DV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x571b 1DW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x571c 1DX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x571d 1DY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x571e 1DZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x571f 1EA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5720 1EB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5721 1EC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5722 1ED EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5723 1EE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5724 1EF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5725 1EG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5726 1EH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5727 1EI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5728 1EJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5729 1EK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x572a 1EL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x572b 1EM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x572c 1EN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x572d 1EO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x572e 1EP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x572f 1EQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5730 1ER EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5731 1ES EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5732 1ET EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5733 1EU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5734 1EV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5735 1EW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5736 1EX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5737 1EY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5738 1EZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5739 1FA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x573a 1FB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x573b 1FC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x573c 1FD EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x573d 1FE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x573e 1FF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x573f 1FG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5740 1FH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5741 1FI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5742 1FJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5743 1FK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5744 1FL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5745 1FM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5746 1FN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5747 1FO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5748 1FP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5749 1FQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x574a 1FR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x574b 1FS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x574c 1FT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x574d 1FU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x574e 1FV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x574f 1FW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5750 1FX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5751 1FY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5752 1FZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5753 1GA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5754 1GB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5755 1GC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5756 1GD EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5757 1GE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5758 1GF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5759 1GG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x575a 1GH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x575b 1GI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x575c 1GJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x575d 1GK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x575e 1GL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x575f 1GM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5760 1GN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5761 1GO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5762 1GP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5763 1GQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5764 1GR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5765 1GS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5766 1GT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5767 1GU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5768 1GV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5769 1GW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x576a 1GX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x576b 1GY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x576c 1GZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x576d 1HA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x576e 1HB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x576f 1HC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5770 1HD EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5771 1HE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5772 1HF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5773 1HG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5774 1HH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5775 1HI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5776 1HJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5777 1HK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5778 1HL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5779 1HM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x577a 1HN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x577b 1HO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x577c 1HP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x577d 1HQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x577e 1HR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x577f 1HS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5780 1HT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5781 1HU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5782 1HV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5783 1HW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5784 1HX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5785 1HY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5786 1HZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5787 1IA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5788 1IB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5789 1IC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x578a 1ID EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x578b 1IE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x578c 1IF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x578d 1IG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x578e 1IH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x578f 1II EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5790 1IJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5791 1IK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5792 1IL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5793 1IM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5794 1IN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5795 1IO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5796 1IP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5797 1IQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5798 1IR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x5799 1IS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x579a 1IT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x579b 1IU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x579c 1IV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x579d 1IW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x579e 1IX EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x579f 1IY EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57a0 1IZ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57a1 1JA EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57a2 1JB EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57a3 1JC EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57a4 1JD EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57a5 1JE EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57a6 1JF EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57a7 1JG EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57a8 1JH EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57a9 1JI EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57aa 1JJ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57ab 1JK EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57ac 1JL EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57ad 1JM EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57ae 1JN EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57af 1JO EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57b0 1JP EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57b1 1JQ EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57b2 1JR EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57b3 1JS EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57b4 1JT EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57b5 1JU EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57b6 1JV EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57b7 1JW EMPTY NORMAL 3 Xo BGI SIMPLE MONSTER paletted +0x57b8 1JX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57b9 1JY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57ba 1JZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57bb 1KA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57bc 1KB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57bd 1KC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57be 1KD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57bf 1KE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57c0 1KF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57c1 1KG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57c2 1KH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57c3 1KI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57c4 1KJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57c5 1KK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57c6 1KL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57c7 1KM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57c8 1KN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57c9 1KO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57ca 1KP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57cb 1KQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57cc 1KR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57cd 1KS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57ce 1KT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57cf 1KU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57d0 1KV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57d1 1KW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57d2 1KX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57d3 1KY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57d4 1KZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57d5 1LA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57d6 1LB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57d7 1LC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57d8 1LD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57d9 1LE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57da 1LF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57db 1LG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57dc 1LH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57dd 1LI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57de 1LJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57df 1LK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57e0 1LL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57e1 1LM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57e2 1LN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57e3 1LO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57e4 1LP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57e5 1LQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57e6 1LR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57e7 1LS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57e8 1LT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57e9 1LU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57ea 1LV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57eb 1LW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57ec 1LX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57ed 1LY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57ee 1LZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57ef 1MA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57f0 1MB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57f1 1MC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57f2 1MD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57f3 1ME EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57f4 1MF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57f5 1MG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57f6 1MH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57f7 1MI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57f8 1MJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57f9 1MK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57fa 1ML EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57fb 1MM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57fc 1MN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57fd 1MO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57fe 1MP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x57ff 1MQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5800 1MR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5801 1MS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5802 1MT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5803 1MU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5804 1MV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5805 1MW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5806 1MX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5807 1MY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5808 1MZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5809 1NA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x580a 1NB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x580b 1NC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x580c 1ND EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x580d 1NE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x580e 1NF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x580f 1NG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5810 1NH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5811 1NI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5812 1NJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5813 1NK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5814 1NL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5815 1NM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5816 1NN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5817 1NO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5818 1NP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5819 1NQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x581a 1NR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x581b 1NS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x581c 1NT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x581d 1NU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x581e 1NV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x581f 1NW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5820 1NX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5821 1NY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5822 1NZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5823 1OA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5824 1OB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5825 1OC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5826 1OD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5827 1OE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5828 1OF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5829 1OG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x582a 1OH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x582b 1OI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x582c 1OJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x582d 1OK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x582e 1OL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x582f 1OM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5830 1ON EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5831 1OO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5832 1OP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5833 1OQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5834 1OR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5835 1OS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5836 1OT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5837 1OU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5838 1OV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5839 1OW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x583a 1OX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x583b 1OY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x583c 1OZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x583d 1PA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x583e 1PB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x583f 1PC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5840 1PD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5841 1PE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5842 1PF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5843 1PG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5844 1PH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5845 1PI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5846 1PJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5847 1PK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5848 1PL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5849 1PM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x584a 1PN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x584b 1PO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x584c 1PP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x584d 1PQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x584e 1PR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x584f 1PS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5850 1PT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5851 1PU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5852 1PV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5853 1PW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5854 1PX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5855 1PY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5856 1PZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5857 1QA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5858 1QB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5859 1QC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x585a 1QD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x585b 1QE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x585c 1QF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x585d 1QG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x585e 1QH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x585f 1QI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5860 1QJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5861 1QK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5862 1QL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5863 1QM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5864 1QN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5865 1QO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5866 1QP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5867 1QQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5868 1QR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5869 1QS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x586a 1QT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x586b 1QU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x586c 1QV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x586d 1QW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x586e 1QX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x586f 1QY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5870 1QZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5871 1RA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5872 1RB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5873 1RC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5874 1RD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5875 1RE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5876 1RF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5877 1RG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5878 1RH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5879 1RI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x587a 1RJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x587b 1RK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x587c 1RL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x587d 1RM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x587e 1RN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x587f 1RO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5880 1RP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5881 1RQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5882 1RR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5883 1RS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5884 1RT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5885 1RU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5886 1RV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5887 1RW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5888 1RX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5889 1RY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x588a 1RZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x588b 1SA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x588c 1SB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x588d 1SC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x588e 1SD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x588f 1SE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5890 1SF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5891 1SG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5892 1SH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5893 1SI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5894 1SJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5895 1SK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5896 1SL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5897 1SM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5898 1SN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5899 1SO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x589a 1SP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x589b 1SQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x589c 1SR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x589d 1SS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x589e 1ST EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x589f 1SU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58a0 1SV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58a1 1SW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58a2 1SX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58a3 1SY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58a4 1SZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58a5 1TA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58a6 1TB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58a7 1TC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58a8 1TD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58a9 1TE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58aa 1TF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58ab 1TG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58ac 1TH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58ad 1TI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58ae 1TJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58af 1TK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58b0 1TL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58b1 1TM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58b2 1TN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58b3 1TO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58b4 1TP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58b5 1TQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58b6 1TR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58b7 1TS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58b8 1TT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58b9 1TU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58ba 1TV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58bb 1TW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58bc 1TX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58bd 1TY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58be 1TZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58bf 1UA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58c0 1UB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58c1 1UC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58c2 1UD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58c3 1UE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58c4 1UF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58c5 1UG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58c6 1UH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58c7 1UI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58c8 1UJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58c9 1UK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58ca 1UL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58cb 1UM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58cc 1UN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58cd 1UO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58ce 1UP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58cf 1UQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58d0 1UR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58d1 1US EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58d2 1UT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58d3 1UU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58d4 1UV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58d5 1UW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58d6 1UX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58d7 1UY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58d8 1UZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58d9 1VA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58da 1VB EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58db 1VC EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58dc 1VD EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58dd 1VE EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58de 1VF EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58df 1VG EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58e0 1VH EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58e1 1VI EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58e2 1VJ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58e3 1VK EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58e4 1VL EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58e5 1VM EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58e6 1VN EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58e7 1VO EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58e8 1VP EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58e9 1VQ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58ea 1VR EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58eb 1VS EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58ec 1VT EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58ed 1VU EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58ee 1VV EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58ef 1VW EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58f0 1VX EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58f1 1VY EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58f2 1VZ EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x58f3 1WA EMPTY LARGE 5 Xo BGI SIMPLE MONSTER paletted +0x5b00 3AA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b01 3AB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b02 3AC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b03 3AD EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b04 3AE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b05 3AF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b06 3AG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b07 3AH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b08 3AI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b09 3AJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b0a 3AK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b0b 3AL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b0c 3AM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b0d 3AN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b0e 3AO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b0f 3AP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b10 3AQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b11 3AR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b12 3AS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b13 3AT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b14 3AU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b15 3AV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b16 3AW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b17 3AX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b18 3AY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b19 3AZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b1a 3BA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b1b 3BB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b1c 3BC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b1d 3BD EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b1e 3BE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b1f 3BF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b20 3BG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b21 3BH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b22 3BI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b23 3BJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b24 3BK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b25 3BL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b26 3BM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b27 3BN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b28 3BO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b29 3BP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b2a 3BQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b2b 3BR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b2c 3BS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b2d 3BT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b2e 3BU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b2f 3BV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b30 3BW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b31 3BX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b32 3BY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b33 3BZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b34 3CA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b35 3CB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b36 3CC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b37 3CD EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b38 3CE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b39 3CF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b3a 3CG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b3b 3CH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b3c 3CI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b3d 3CJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b3e 3CK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b3f 3CL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b40 3CM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b41 3CN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b42 3CO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b43 3CP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b44 3CQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b45 3CR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b46 3CS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b47 3CT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b48 3CU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b49 3CV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b4a 3CW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b4b 3CX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b4c 3CY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b4d 3CZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b4e 3DA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b4f 3DB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b50 3DC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b51 3DD EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b52 3DE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b53 3DF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b54 3DG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b55 3DH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b56 3DI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b57 3DJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b58 3DK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b59 3DL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b5a 3DM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b5b 3DN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b5c 3DO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b5d 3DP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b5e 3DQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b5f 3DR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b60 3DS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b61 3DT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b62 3DU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b63 3DV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b64 3DW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b65 3DX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b66 3DY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b67 3DZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b68 3EA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b69 3EB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b6a 3EC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b6b 3ED EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b6c 3EE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b6d 3EF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b6e 3EG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b6f 3EH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b70 3EI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b71 3EJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b72 3EK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b73 3EL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b74 3EM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b75 3EN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b76 3EO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b77 3EP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b78 3EQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b79 3ER EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b7a 3ES EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b7b 3ET EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b7c 3EU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b7d 3EV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b7e 3EW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b7f 3EX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b80 3EY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b81 3EZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b82 3FA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b83 3FB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b84 3FC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b85 3FD EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b86 3FE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b87 3FF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b88 3FG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b89 3FH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b8a 3FI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b8b 3FJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b8c 3FK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b8d 3FL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b8e 3FM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b8f 3FN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b90 3FO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b91 3FP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b92 3FQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b93 3FR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b94 3FS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b95 3FT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b96 3FU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b97 3FV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b98 3FW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b99 3FX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b9a 3FY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b9b 3FZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b9c 3GA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b9d 3GB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b9e 3GC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5b9f 3GD EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5ba0 3GE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5ba1 3GF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5ba2 3GG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5ba3 3GH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5ba4 3GI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5ba5 3GJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5ba6 3GK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5ba7 3GL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5ba8 3GM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5ba9 3GN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5baa 3GO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bab 3GP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bac 3GQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bad 3GR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bae 3GS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5baf 3GT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bb0 3GU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bb1 3GV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bb2 3GW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bb3 3GX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bb4 3GY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bb5 3GZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bb6 3HA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bb7 3HB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bb8 3HC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bb9 3HD EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bba 3HE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bbb 3HF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bbc 3HG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bbd 3HH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bbe 3HI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bbf 3HJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bc0 3HK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bc1 3HL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bc2 3HM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bc3 3HN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bc4 3HO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bc5 3HP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bc6 3HQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bc7 3HR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bc8 3HS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bc9 3HT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bca 3HU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bcb 3HV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bcc 3HW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bcd 3HX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bce 3HY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bcf 3HZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bd0 3IA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bd1 3IB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bd2 3IC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bd3 3ID EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bd4 3IE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bd5 3IF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bd6 3IG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bd7 3IH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bd8 3II EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bd9 3IJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bda 3IK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bdb 3IL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bdc 3IM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bdd 3IN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bde 3IO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bdf 3IP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5be0 3IQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5be1 3IR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5be2 3IS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5be3 3IT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5be4 3IU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5be5 3IV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5be6 3IW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5be7 3IX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5be8 3IY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5be9 3IZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bea 3JA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5beb 3JB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bec 3JC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bed 3JD EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bee 3JE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bef 3JF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bf0 3JG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bf1 3JH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bf2 3JI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bf3 3JJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bf4 3JK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bf5 3JL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bf6 3JM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bf7 3JN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bf8 3JO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bf9 3JP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bfa 3JQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bfb 3JR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bfc 3JS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bfd 3JT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bfe 3JU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5bff 3JV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. unpaletted +0x5c00 3JW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c01 3JX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c02 3JY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c03 3JZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c04 3KA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c05 3KB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c06 3KC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c07 3KD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c08 3KE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c09 3KF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c0a 3KG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c0b 3KH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c0c 3KI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c0d 3KJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c0e 3KK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c0f 3KL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c10 3KM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c11 3KN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c12 3KO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c13 3KP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c14 3KQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c15 3KR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c16 3KS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c17 3KT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c18 3KU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c19 3KV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c1a 3KW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c1b 3KX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c1c 3KY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c1d 3KZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c1e 3LA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c1f 3LB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c20 3LC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c21 3LD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c22 3LE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c23 3LF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c24 3LG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c25 3LH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c26 3LI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c27 3LJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c28 3LK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c29 3LL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c2a 3LM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c2b 3LN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c2c 3LO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c2d 3LP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c2e 3LQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c2f 3LR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c30 3LS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c31 3LT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c32 3LU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c33 3LV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c34 3LW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c35 3LX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c36 3LY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c37 3LZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c38 3MA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c39 3MB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c3a 3MC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c3b 3MD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c3c 3ME EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c3d 3MF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c3e 3MG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c3f 3MH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c40 3MI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c41 3MJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c42 3MK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c43 3ML EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c44 3MM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c45 3MN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c46 3MO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c47 3MP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c48 3MQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c49 3MR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c4a 3MS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c4b 3MT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c4c 3MU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c4d 3MV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c4e 3MW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c4f 3MX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c50 3MY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c51 3MZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c52 3NA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c53 3NB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c54 3NC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c55 3ND EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c56 3NE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c57 3NF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c58 3NG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c59 3NH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c5a 3NI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c5b 3NJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c5c 3NK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c5d 3NL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c5e 3NM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c5f 3NN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c60 3NO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c61 3NP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c62 3NQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c63 3NR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c64 3NS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c65 3NT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c66 3NU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c67 3NV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c68 3NW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c69 3NX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c6a 3NY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c6b 3NZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c6c 3OA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c6d 3OB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c6e 3OC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c6f 3OD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c70 3OE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c71 3OF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c72 3OG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c73 3OH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c74 3OI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c75 3OJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c76 3OK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c77 3OL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c78 3OM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c79 3ON EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c7a 3OO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c7b 3OP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c7c 3OQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c7d 3OR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c7e 3OS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c7f 3OT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c80 3OU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c81 3OV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c82 3OW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c83 3OX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c84 3OY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c85 3OZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c86 3PA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c87 3PB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c88 3PC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c89 3PD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c8a 3PE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c8b 3PF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c8c 3PG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c8d 3PH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c8e 3PI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c8f 3PJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c90 3PK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c91 3PL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c92 3PM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c93 3PN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c94 3PO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c95 3PP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c96 3PQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c97 3PR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c98 3PS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c99 3PT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c9a 3PU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c9b 3PV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c9c 3PW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c9d 3PX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c9e 3PY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5c9f 3PZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ca0 3QA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ca1 3QB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ca2 3QC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ca3 3QD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ca4 3QE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ca5 3QF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ca6 3QG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ca7 3QH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ca8 3QI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ca9 3QJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5caa 3QK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cab 3QL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cac 3QM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cad 3QN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cae 3QO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5caf 3QP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cb0 3QQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cb1 3QR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cb2 3QS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cb3 3QT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cb4 3QU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cb5 3QV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cb6 3QW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cb7 3QX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cb8 3QY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cb9 3QZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cba 3RA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cbb 3RB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cbc 3RC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cbd 3RD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cbe 3RE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cbf 3RF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cc0 3RG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cc1 3RH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cc2 3RI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cc3 3RJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cc4 3RK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cc5 3RL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cc6 3RM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cc7 3RN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cc8 3RO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cc9 3RP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cca 3RQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ccb 3RR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ccc 3RS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ccd 3RT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cce 3RU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ccf 3RV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cd0 3RW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cd1 3RX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cd2 3RY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cd3 3RZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cd4 3SA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cd5 3SB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cd6 3SC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cd7 3SD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cd8 3SE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cd9 3SF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cda 3SG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cdb 3SH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cdc 3SI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cdd 3SJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cde 3SK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cdf 3SL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ce0 3SM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ce1 3SN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ce2 3SO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ce3 3SP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ce4 3SQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ce5 3SR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ce6 3SS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ce7 3ST EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ce8 3SU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ce9 3SV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cea 3SW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ceb 3SX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cec 3SY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5ced 3SZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cee 3TA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cef 3TB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cf0 3TC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cf1 3TD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cf2 3TE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cf3 3TF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cf4 3TG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cf5 3TH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cf6 3TI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cf7 3TJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cf8 3TK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cf9 3TL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cfa 3TM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cfb 3TN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cfc 3TO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cfd 3TP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cfe 3TQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5cff 3TR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d00 3TS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d01 3TT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d02 3TU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d03 3TV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d04 3TW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d05 3TX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d06 3TY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d07 3TZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d08 3UA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d09 3UB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d0a 3UC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d0b 3UD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d0c 3UE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d0d 3UF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d0e 3UG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d0f 3UH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d10 3UI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d11 3UJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d12 3UK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d13 3UL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d14 3UM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d15 3UN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d16 3UO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d17 3UP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d18 3UQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d19 3UR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d1a 3US EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d1b 3UT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d1c 3UU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d1d 3UV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d1e 3UW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d1f 3UX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d20 3UY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d21 3UZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d22 3VA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d23 3VB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d24 3VC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d25 3VD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d26 3VE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d27 3VF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d28 3VG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d29 3VH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d2a 3VI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d2b 3VJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d2c 3VK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d2d 3VL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d2e 3VM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d2f 3VN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d30 3VO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d31 3VP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d32 3VQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d33 3VR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d34 3VS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d35 3VT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d36 3VU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d37 3VV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d38 3VW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d39 3VX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d3a 3VY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d3b 3VZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. unpaletted +0x5d3d 3AA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d3e 3AB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d3f 3AC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d40 3AD EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d41 3AE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d42 3AF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d43 3AG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d44 3AH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d45 3AI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d46 3AJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d47 3AK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d48 3AL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d49 3AM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d4a 3AN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d4b 3AO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d4c 3AP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d4d 3AQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d4e 3AR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d4f 3AS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d50 3AT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d51 3AU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d52 3AV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d53 3AW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d54 3AX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d55 3AY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d56 3AZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d57 3BA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d58 3BB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d59 3BC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d5a 3BD EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d5b 3BE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d5c 3BF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d5d 3BG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d5e 3BH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d5f 3BI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d60 3BJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d61 3BK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d62 3BL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d63 3BM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d64 3BN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d65 3BO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d66 3BP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d67 3BQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d68 3BR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d69 3BS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d6a 3BT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d6b 3BU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d6c 3BV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d6d 3BW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d6e 3BX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d6f 3BY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d70 3BZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d71 3CA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d72 3CB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d73 3CC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d74 3CD EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d75 3CE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d76 3CF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d77 3CG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d78 3CH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d79 3CI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d7a 3CJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d7b 3CK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d7c 3CL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d7d 3CM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d7e 3CN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d7f 3CO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d80 3CP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d81 3CQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d82 3CR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d83 3CS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d84 3CT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d85 3CU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d86 3CV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d87 3CW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d88 3CX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d89 3CY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d8a 3CZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d8b 3DA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d8c 3DB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d8d 3DC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d8e 3DD EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d8f 3DE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d90 3DF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d91 3DG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d92 3DH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d93 3DI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d94 3DJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d95 3DK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d96 3DL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d97 3DM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d98 3DN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d99 3DO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d9a 3DP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d9b 3DQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d9c 3DR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d9d 3DS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d9e 3DT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5d9f 3DU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5da0 3DV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5da1 3DW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5da2 3DX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5da3 3DY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5da4 3DZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5da5 3EA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5da6 3EB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5da7 3EC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5da8 3ED EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5da9 3EE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5daa 3EF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dab 3EG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dac 3EH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dad 3EI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dae 3EJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5daf 3EK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5db0 3EL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5db1 3EM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5db2 3EN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5db3 3EO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5db4 3EP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5db5 3EQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5db6 3ER EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5db7 3ES EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5db8 3ET EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5db9 3EU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dba 3EV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dbb 3EW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dbc 3EX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dbd 3EY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dbe 3EZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dbf 3FA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dc0 3FB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dc1 3FC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dc2 3FD EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dc3 3FE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dc4 3FF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dc5 3FG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dc6 3FH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dc7 3FI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dc8 3FJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dc9 3FK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dca 3FL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dcb 3FM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dcc 3FN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dcd 3FO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dce 3FP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dcf 3FQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dd0 3FR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dd1 3FS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dd2 3FT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dd3 3FU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dd4 3FV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dd5 3FW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dd6 3FX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dd7 3FY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dd8 3FZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dd9 3GA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dda 3GB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5ddb 3GC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5ddc 3GD EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5ddd 3GE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dde 3GF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5ddf 3GG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5de0 3GH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5de1 3GI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5de2 3GJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5de3 3GK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5de4 3GL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5de5 3GM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5de6 3GN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5de7 3GO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5de8 3GP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5de9 3GQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dea 3GR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5deb 3GS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dec 3GT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5ded 3GU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dee 3GV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5def 3GW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5df0 3GX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5df1 3GY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5df2 3GZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5df3 3HA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5df4 3HB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5df5 3HC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5df6 3HD EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5df7 3HE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5df8 3HF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5df9 3HG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dfa 3HH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dfb 3HI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dfc 3HJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dfd 3HK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dfe 3HL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5dff 3HM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e00 3HN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e01 3HO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e02 3HP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e03 3HQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e04 3HR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e05 3HS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e06 3HT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e07 3HU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e08 3HV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e09 3HW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e0a 3HX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e0b 3HY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e0c 3HZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e0d 3IA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e0e 3IB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e0f 3IC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e10 3ID EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e11 3IE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e12 3IF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e13 3IG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e14 3IH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e15 3II EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e16 3IJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e17 3IK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e18 3IL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e19 3IM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e1a 3IN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e1b 3IO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e1c 3IP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e1d 3IQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e1e 3IR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e1f 3IS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e20 3IT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e21 3IU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e22 3IV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e23 3IW EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e24 3IX EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e25 3IY EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e26 3IZ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e27 3JA EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e28 3JB EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e29 3JC EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e2a 3JD EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e2b 3JE EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e2c 3JF EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e2d 3JG EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e2e 3JH EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e2f 3JI EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e30 3JJ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e31 3JK EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e32 3JL EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e33 3JM EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e34 3JN EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e35 3JO EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e36 3JP EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e37 3JQ EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e38 3JR EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e39 3JS EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e3a 3JT EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e3b 3JU EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e3c 3JV EMPTY NORMAL 3 Bo BGII UNSPLIT EXT. paletted +0x5e3d 3JW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e3e 3JX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e3f 3JY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e40 3JZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e41 3KA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e42 3KB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e43 3KC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e44 3KD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e45 3KE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e46 3KF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e47 3KG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e48 3KH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e49 3KI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e4a 3KJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e4b 3KK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e4c 3KL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e4d 3KM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e4e 3KN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e4f 3KO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e50 3KP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e51 3KQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e52 3KR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e53 3KS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e54 3KT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e55 3KU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e56 3KV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e57 3KW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e58 3KX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e59 3KY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e5a 3KZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e5b 3LA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e5c 3LB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e5d 3LC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e5e 3LD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e5f 3LE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e60 3LF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e61 3LG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e62 3LH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e63 3LI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e64 3LJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e65 3LK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e66 3LL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e67 3LM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e68 3LN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e69 3LO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e6a 3LP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e6b 3LQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e6c 3LR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e6d 3LS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e6e 3LT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e6f 3LU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e70 3LV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e71 3LW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e72 3LX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e73 3LY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e74 3LZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e75 3MA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e76 3MB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e77 3MC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e78 3MD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e79 3ME EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e7a 3MF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e7b 3MG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e7c 3MH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e7d 3MI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e7e 3MJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e7f 3MK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e80 3ML EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e81 3MM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e82 3MN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e83 3MO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e84 3MP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e85 3MQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e86 3MR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e87 3MS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e88 3MT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e89 3MU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e8a 3MV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e8b 3MW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e8c 3MX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e8d 3MY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e8e 3MZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e8f 3NA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e90 3NB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e91 3NC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e92 3ND EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e93 3NE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e94 3NF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e95 3NG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e96 3NH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e97 3NI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e98 3NJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e99 3NK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e9a 3NL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e9b 3NM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e9c 3NN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e9d 3NO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e9e 3NP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5e9f 3NQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ea0 3NR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ea1 3NS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ea2 3NT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ea3 3NU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ea4 3NV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ea5 3NW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ea6 3NX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ea7 3NY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ea8 3NZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ea9 3OA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eaa 3OB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eab 3OC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eac 3OD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ead 3OE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eae 3OF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eaf 3OG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eb0 3OH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eb1 3OI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eb2 3OJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eb3 3OK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eb4 3OL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eb5 3OM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eb6 3ON EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eb7 3OO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eb8 3OP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eb9 3OQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eba 3OR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ebb 3OS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ebc 3OT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ebd 3OU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ebe 3OV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ebf 3OW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ec0 3OX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ec1 3OY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ec2 3OZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ec3 3PA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ec4 3PB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ec5 3PC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ec6 3PD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ec7 3PE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ec8 3PF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ec9 3PG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eca 3PH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ecb 3PI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ecc 3PJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ecd 3PK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ece 3PL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ecf 3PM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ed0 3PN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ed1 3PO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ed2 3PP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ed3 3PQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ed4 3PR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ed5 3PS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ed6 3PT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ed7 3PU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ed8 3PV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ed9 3PW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eda 3PX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5edb 3PY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5edc 3PZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5edd 3QA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ede 3QB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5edf 3QC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ee0 3QD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ee1 3QE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ee2 3QF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ee3 3QG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ee4 3QH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ee5 3QI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ee6 3QJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ee7 3QK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ee8 3QL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ee9 3QM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eea 3QN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eeb 3QO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eec 3QP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eed 3QQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eee 3QR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eef 3QS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ef0 3QT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ef1 3QU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ef2 3QV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ef3 3QW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ef4 3QX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ef5 3QY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ef6 3QZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ef7 3RA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ef8 3RB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5ef9 3RC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5efa 3RD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5efb 3RE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5efc 3RF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5efd 3RG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5efe 3RH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5eff 3RI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f00 3RJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f01 3RK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f02 3RL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f03 3RM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f04 3RN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f05 3RO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f06 3RP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f07 3RQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f08 3RR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f09 3RS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f0a 3RT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f0b 3RU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f0c 3RV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f0d 3RW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f0e 3RX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f0f 3RY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f10 3RZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f11 3SA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f12 3SB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f13 3SC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f14 3SD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f15 3SE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f16 3SF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f17 3SG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f18 3SH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f19 3SI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f1a 3SJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f1b 3SK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f1c 3SL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f1d 3SM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f1e 3SN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f1f 3SO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f20 3SP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f21 3SQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f22 3SR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f23 3SS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f24 3ST EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f25 3SU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f26 3SV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f27 3SW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f28 3SX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f29 3SY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f2a 3SZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f2b 3TA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f2c 3TB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f2d 3TC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f2e 3TD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f2f 3TE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f30 3TF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f31 3TG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f32 3TH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f33 3TI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f34 3TJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f35 3TK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f36 3TL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f37 3TM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f38 3TN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f39 3TO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f3a 3TP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f3b 3TQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f3c 3TR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f3d 3TS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f3e 3TT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f3f 3TU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f40 3TV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f41 3TW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f42 3TX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f43 3TY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f44 3TZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f45 3UA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f46 3UB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f47 3UC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f48 3UD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f49 3UE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f4a 3UF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f4b 3UG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f4c 3UH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f4d 3UI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f4e 3UJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f4f 3UK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f50 3UL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f51 3UM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f52 3UN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f53 3UO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f54 3UP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f55 3UQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f56 3UR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f57 3US EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f58 3UT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f59 3UU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f5a 3UV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f5b 3UW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f5c 3UX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f5d 3UY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f5e 3UZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f5f 3VA EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f60 3VB EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f61 3VC EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f62 3VD EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f63 3VE EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f64 3VF EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f65 3VG EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f66 3VH EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f67 3VI EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f68 3VJ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f69 3VK EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f6a 3VL EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f6b 3VM EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f6c 3VN EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f6d 3VO EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f6e 3VP EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f6f 3VQ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f70 3VR EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f71 3VS EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f72 3VT EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f73 3VU EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f74 3VV EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f75 3VW EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f76 3VX EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f77 3VY EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f78 3VZ EMPTY LARGE 5 Bo BGII UNSPLIT EXT. paletted +0x5f7a 8AA EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f7b 8AB EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f7c 8AC EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f7d 8AD EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f7e 8AE EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f7f 8AF EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f80 8AG EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f81 8AH EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f82 8AI EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f83 8AJ EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f84 8AK EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f85 8AL EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f86 8AM EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f87 8AN EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f88 8AO EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f89 8AP EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f8a 8AQ EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f8b 8AR EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f8c 8AS EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f8d 8AT EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f8e 8AU EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f8f 8AV EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f90 8AW EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f91 8AX EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f92 8AY EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f93 8AZ EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f94 8BA EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f95 8BB EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f96 8BC EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f97 8BD EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f98 8BE EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f99 8BF EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f9a 8BG EMPTY NORMAL 3 Bo BGII SPLIT paletted +0x5f9b 8BH EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5f9c 8BI EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5f9d 8BJ EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5f9e 8BK EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5f9f 8BL EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fa0 8BM EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fa1 8BN EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fa2 8BO EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fa3 8BP EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fa4 8BQ EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fa5 8BR EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fa6 8BS EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fa7 8BT EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fa8 8BU EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fa9 8BV EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5faa 8BW EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fab 8BX EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fac 8BY EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fad 8BZ EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fae 8CA EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5faf 8CB EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fb0 8CC EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fb1 8CD EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fb2 8CE EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fb3 8CF EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fb4 8CG EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fb5 8CH EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fb6 8CI EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fb7 8CJ EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fb8 8CK EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fb9 8CL EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fba 8CM EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fbb 8CN EMPTY LARGE 5 Bo BGII SPLIT paletted +0x5fbc 8CO EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fbd 8CP EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fbe 8CQ EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fbf 8CR EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fc0 8CS EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fc1 8CT EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fc2 8CU EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fc3 8CV EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fc4 8CW EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fc5 8CX EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fc6 8CY EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fc7 8CZ EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fc8 8DA EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fc9 8DB EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fca 8DC EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fcb 8DD EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fcc 8DE EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fcd 8DF EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fce 8DG EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fcf 8DH EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fd0 8DI EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fd1 8DJ EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fd2 8DK EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fd3 8DL EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fd4 8DM EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fd5 8DN EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fd6 8DO EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fd7 8DP EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fd8 8DQ EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fd9 8DR EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fda 8DS EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fdb 8DT EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fdc 8DU EMPTY NORMAL 3 Bo BGII SPLIT unpaletted +0x5fdd 8DV EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fde 8DW EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fdf 8DX EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fe0 8DY EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fe1 8DZ EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fe2 8EA EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fe3 8EB EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fe4 8EC EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fe5 8ED EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fe6 8EE EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fe7 8EF EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fe8 8EG EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fe9 8EH EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fea 8EI EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5feb 8EJ EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fec 8EK EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fed 8EL EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fee 8EM EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fef 8EN EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5ff0 8EO EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5ff1 8EP EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5ff2 8EQ EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5ff3 8ER EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5ff4 8ES EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5ff5 8ET EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5ff6 8EU EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5ff7 8EV EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5ff8 8EW EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5ff9 8EX EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5ffa 8EY EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5ffb 8EZ EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5ffc 8FA EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5ffd 8FB EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5ffe 8FC EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x5fff 8FD EMPTY LARGE 5 Bo BGII SPLIT unpaletted +0x6600 0A3A EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6601 0A3B EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6602 0A3C EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6603 0A3D EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6604 0A3E EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6605 0A3F EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6606 0A3G EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6607 0A3H EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6608 0A3I EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6609 0A3J EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x660a 0A3K EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x660b 0A3L EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x660c 0A3M EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x660d 0A3N EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x660e 0A3O EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x660f 0A3P EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6610 0A3Q EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6611 0A3R EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6612 0A3S EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6613 0A3T EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6614 0A3U EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6615 0A3V EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6616 0A3W EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6617 0A3X EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6618 0A3Y EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6619 0A3Z EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x661a 0B3A EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x661b 0B3B EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x661c 0B3C EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x661d 0B3D EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x661e 0B3E EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x661f 0B3F EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6620 0B3G EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6621 0B3H EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6622 0B3I EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6623 0B3J EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6624 0B3K EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6625 0B3L EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6626 0B3M EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6627 0B3N EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6628 0B3O EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6629 0B3P EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x662a 0B3Q EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x662b 0B3R EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x662c 0B3S EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x662d 0B3T EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x662e 0B3U EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x662f 0B3V EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6630 0B3W EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6631 0B3X EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6632 0B3Y EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6633 0B3Z EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6634 0C3A EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6635 0C3B EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6636 0C3C EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6637 0C3D EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6638 0C3E EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6639 0C3F EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x663a 0C3G EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x663b 0C3H EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x663c 0C3I EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x663d 0C3J EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x663e 0C3K EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x663f 0C3L EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6640 0C3M EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6641 0C3N EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6642 0C3O EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6643 0C3P EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6644 0C3Q EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6645 0C3R EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6646 0C3S EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6647 0C3T EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6648 0C3U EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6649 0C3V EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x664a 0C3W EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x664b 0C3X EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x664c 0C3Y EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x664d 0C3Z EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x664e 0D3A EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x664f 0D3B EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6650 0D3C EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6651 0D3D EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6652 0D3E EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6653 0D3F EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6654 0D3G EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6655 0D3H EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6656 0D3I EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6657 0D3J EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6658 0D3K EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6659 0D3L EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x665a 0D3M EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x665b 0D3N EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x665c 0D3O EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x665d 0D3P EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x665e 0D3Q EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x665f 0D3R EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6660 0D3S EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6661 0D3T EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6662 0D3U EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6663 0D3V EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6664 0D3W EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6665 0D3X EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6666 0D3Y EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6667 0D3Z EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6668 0E3A EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6669 0E3B EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x666a 0E3C EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x666b 0E3D EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x666c 0E3E EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x666d 0E3F EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x666e 0E3G EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x666f 0E3H EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6670 0E3I EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6671 0E3J EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6672 0E3K EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6673 0E3L EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6674 0E3M EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6675 0E3N EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6676 0E3O EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6677 0E3P EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6678 0E3Q EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6679 0E3R EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x667a 0E3S EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x667b 0E3T EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x667c 0E3U EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x667d 0E3V EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x667e 0E3W EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x667f 0E3X EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6680 0E3Y EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6681 0E3Z EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6682 0F3A EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6683 0F3B EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6684 0F3C EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6685 0F3D EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6686 0F3E EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6687 0F3F EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6688 0F3G EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6689 0F3H EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x668a 0F3I EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x668b 0F3J EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x668c 0F3K EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x668d 0F3L EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x668e 0F3M EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x668f 0F3N EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6690 0F3O EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6691 0F3P EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6692 0F3Q EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6693 0F3R EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6694 0F3S EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6695 0F3T EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6696 0F3U EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6697 0F3V EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6698 0F3W EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6699 0F3X EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x669a 0F3Y EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x669b 0F3Z EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x669c 0G3A EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x669d 0G3B EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x669e 0G3C EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x669f 0G3D EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66a0 0G3E EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66a1 0G3F EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66a2 0G3G EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66a3 0G3H EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66a4 0G3I EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66a5 0G3J EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66a6 0G3K EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66a7 0G3L EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66a8 0G3M EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66a9 0G3N EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66aa 0G3O EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66ab 0G3P EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66ac 0G3Q EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66ad 0G3R EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66ae 0G3S EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66af 0G3T EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66b0 0G3U EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66b1 0G3V EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66b2 0G3W EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66b3 0G3X EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66b4 0G3Y EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66b5 0G3Z EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66b6 0H3A EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66b7 0H3B EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66b8 0H3C EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66b9 0H3D EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66ba 0H3E EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66bb 0H3F EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66bc 0H3G EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66bd 0H3H EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66be 0H3I EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66bf 0H3J EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66c0 0H3K EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66c1 0H3L EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66c2 0H3M EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66c3 0H3N EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66c4 0H3O EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66c5 0H3P EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66c6 0H3Q EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66c7 0H3R EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66c8 0H3S EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66c9 0H3T EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66ca 0H3U EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66cb 0H3V EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66cc 0H3W EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66cd 0H3X EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66ce 0H3Y EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66cf 0H3Z EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66d0 0I3A EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66d1 0I3B EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66d2 0I3C EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66d3 0I3D EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66d4 0I3E EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66d5 0I3F EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66d6 0I3G EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66d7 0I3H EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66d8 0I3I EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66d9 0I3J EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66da 0I3K EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66db 0I3L EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66dc 0I3M EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66dd 0I3N EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66de 0I3O EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66df 0I3P EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66e0 0I3Q EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66e1 0I3R EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66e2 0I3S EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66e3 0I3T EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66e4 0I3U EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66e5 0I3V EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66e6 0I3W EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66e7 0I3X EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66e8 0I3Y EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66e9 0I3Z EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66ea 0J3A EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66eb 0J3B EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66ec 0J3C EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66ed 0J3D EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66ee 0J3E EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66ef 0J3F EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66f0 0J3G EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66f1 0J3H EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66f2 0J3I EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66f3 0J3J EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66f4 0J3K EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66f5 0J3L EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66f6 0J3M EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66f7 0J3N EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66f8 0J3O EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66f9 0J3P EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66fa 0J3Q EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66fb 0J3R EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66fc 0J3S EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66fd 0J3T EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66fe 0J3U EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x66ff 0J3V EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6700 0J3W EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6701 0J3X EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6702 0J3Y EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6703 0J3Z EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6704 0K3A EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6705 0K3B EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6706 0K3C EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6707 0K3D EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6708 0K3E EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6709 0K3F EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x670a 0K3G EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x670b 0K3H EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x670c 0K3I EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x670d 0K3J EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x670e 0K3K EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x670f 0K3L EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6710 0K3M EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6711 0K3N EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6712 0K3O EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6713 0K3P EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6714 0K3Q EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6715 0K3R EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6716 0K3S EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6717 0K3T EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6718 0K3U EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6719 0K3V EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x671a 0K3W EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x671b 0K3X EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x671c 0K3Y EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x671d 0K3Z EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x671e 0L3A EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x671f 0L3B EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6720 0L3C EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6721 0L3D EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6722 0L3E EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6723 0L3F EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6724 0L3G EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6725 0L3H EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6726 0L3I EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6727 0L3J EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6728 0L3K EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6729 0L3L EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x672a 0L3M EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x672b 0L3N EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x672c 0L3O EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x672d 0L3P EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x672e 0L3Q EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x672f 0L3R EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6730 0L3S EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6731 0L3T EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6732 0L3U EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6733 0L3V EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6734 0L3W EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6735 0L3X EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6736 0L3Y EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6737 0L3Z EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6738 0M3A EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6739 0M3B EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x673a 0M3C EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x673b 0M3D EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x673c 0M3E EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x673d 0M3F EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x673e 0M3G EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x673f 0M3H EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6740 0M3I EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6741 0M3J EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6742 0M3K EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6743 0M3L EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6744 0M3M EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6745 0M3N EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6746 0M3O EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6747 0M3P EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6748 0M3Q EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6749 0M3R EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x674a 0M3S EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x674b 0M3T EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x674c 0M3U EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x674d 0M3V EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x674e 0M3W EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x674f 0M3X EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6750 0M3Y EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6751 0M3Z EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6752 0N3A EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6753 0N3B EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6754 0N3C EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6755 0N3D EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6756 0N3E EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6757 0N3F EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6758 0N3G EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6759 0N3H EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x675a 0N3I EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x675b 0N3J EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x675c 0N3K EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x675d 0N3L EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x675e 0N3M EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x675f 0N3N EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6760 0N3O EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6761 0N3P EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6762 0N3Q EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6763 0N3R EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6764 0N3S EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6765 0N3T EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6766 0N3U EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6767 0N3V EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6768 0N3W EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6769 0N3X EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x676a 0N3Y EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x676b 0N3Z EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x676c 0O3A EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x676d 0O3B EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x676e 0O3C EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x676f 0O3D EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6770 0O3E EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6771 0O3F EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6772 0O3G EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6773 0O3H EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6774 0O3I EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6775 0O3J EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6776 0O3K EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6777 0O3L EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6778 0O3M EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6779 0O3N EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x677a 0O3O EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x677b 0O3P EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x677c 0O3Q EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x677d 0O3R EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x677e 0O3S EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x677f 0O3T EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6780 0O3U EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6781 0O3V EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6782 0O3W EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6783 0O3X EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6784 0O3Y EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6785 0O3Z EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6786 0P3A EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6787 0P3B EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6788 0P3C EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6789 0P3D EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x678a 0P3E EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x678b 0P3F EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x678c 0P3G EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x678d 0P3H EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x678e 0P3I EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x678f 0P3J EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6790 0P3K EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6791 0P3L EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6792 0P3M EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6793 0P3N EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6794 0P3O EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6795 0P3P EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6796 0P3Q EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6797 0P3R EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6798 0P3S EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6799 0P3T EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x679a 0P3U EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x679b 0P3V EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x679c 0P3W EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x679d 0P3X EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x679e 0P3Y EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x679f 0P3Z EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67a0 0Q3A EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67a1 0Q3B EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67a2 0Q3C EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67a3 0Q3D EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67a4 0Q3E EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67a5 0Q3F EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67a6 0Q3G EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67a7 0Q3H EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67a8 0Q3I EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67a9 0Q3J EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67aa 0Q3K EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67ab 0Q3L EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67ac 0Q3M EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67ad 0Q3N EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67ae 0Q3O EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67af 0Q3P EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67b0 0Q3Q EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67b1 0Q3R EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67b2 0Q3S EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67b3 0Q3T EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67b4 0Q3U EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67b5 0Q3V EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67b6 0Q3W EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67b7 0Q3X EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67b8 0Q3Y EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67b9 0Q3Z EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67ba 0R3A EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67bb 0R3B EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67bc 0R3C EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67bd 0R3D EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67be 0R3E EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67bf 0R3F EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67c0 0R3G EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67c1 0R3H EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67c2 0R3I EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67c3 0R3J EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67c4 0R3K EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67c5 0R3L EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67c6 0R3M EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67c7 0R3N EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67c8 0R3O EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67c9 0R3P EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67ca 0R3Q EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67cb 0R3R EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67cc 0R3S EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67cd 0R3T EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67ce 0R3U EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67cf 0R3V EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67d0 0R3W EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67d1 0R3X EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67d2 0R3Y EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67d3 0R3Z EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67d4 0S3A EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67d5 0S3B EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67d6 0S3C EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67d7 0S3D EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67d8 0S3E EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67d9 0S3F EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67da 0S3G EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67db 0S3H EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67dc 0S3I EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67dd 0S3J EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67de 0S3K EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67df 0S3L EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67e0 0S3M EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67e1 0S3N EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67e2 0S3O EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67e3 0S3P EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67e4 0S3Q EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67e5 0S3R EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67e6 0S3S EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67e7 0S3T EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67e8 0S3U EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67e9 0S3V EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67ea 0S3W EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67eb 0S3X EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67ec 0S3Y EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67ed 0S3Z EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67ee 0T3A EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67ef 0T3B EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67f0 0T3C EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67f1 0T3D EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67f2 0T3E EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67f3 0T3F EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67f4 0T3G EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67f5 0T3H EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67f6 0T3I EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67f7 0T3J EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67f8 0T3K EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67f9 0T3L EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67fa 0T3M EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67fb 0T3N EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67fc 0T3O EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67fd 0T3P EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67fe 0T3Q EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x67ff 0T3R EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6800 0T3S EMPTY NORMAL 3 Co CHARACTER BGII paletted +0x6801 0T3T EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6802 0T3U EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6803 0T3V EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6804 0T3W EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6805 0T3X EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6806 0T3Y EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6807 0T3Z EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6808 0U3A EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6809 0U3B EMPTY LARGE 5 Co CHARACTER BGII paletted +0x680a 0U3C EMPTY LARGE 5 Co CHARACTER BGII paletted +0x680b 0U3D EMPTY LARGE 5 Co CHARACTER BGII paletted +0x680c 0U3E EMPTY LARGE 5 Co CHARACTER BGII paletted +0x680d 0U3F EMPTY LARGE 5 Co CHARACTER BGII paletted +0x680e 0U3G EMPTY LARGE 5 Co CHARACTER BGII paletted +0x680f 0U3H EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6810 0U3I EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6811 0U3J EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6812 0U3K EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6813 0U3L EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6814 0U3M EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6815 0U3N EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6816 0U3O EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6817 0U3P EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6818 0U3Q EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6819 0U3R EMPTY LARGE 5 Co CHARACTER BGII paletted +0x681a 0U3S EMPTY LARGE 5 Co CHARACTER BGII paletted +0x681b 0U3T EMPTY LARGE 5 Co CHARACTER BGII paletted +0x681c 0U3U EMPTY LARGE 5 Co CHARACTER BGII paletted +0x681d 0U3V EMPTY LARGE 5 Co CHARACTER BGII paletted +0x681e 0U3W EMPTY LARGE 5 Co CHARACTER BGII paletted +0x681f 0U3X EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6820 0U3Y EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6821 0U3Z EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6822 0V3A EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6823 0V3B EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6824 0V3C EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6825 0V3D EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6826 0V3E EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6827 0V3F EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6828 0V3G EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6829 0V3H EMPTY LARGE 5 Co CHARACTER BGII paletted +0x682a 0V3I EMPTY LARGE 5 Co CHARACTER BGII paletted +0x682b 0V3J EMPTY LARGE 5 Co CHARACTER BGII paletted +0x682c 0V3K EMPTY LARGE 5 Co CHARACTER BGII paletted +0x682d 0V3L EMPTY LARGE 5 Co CHARACTER BGII paletted +0x682e 0V3M EMPTY LARGE 5 Co CHARACTER BGII paletted +0x682f 0V3N EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6830 0V3O EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6831 0V3P EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6832 0V3Q EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6833 0V3R EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6834 0V3S EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6835 0V3T EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6836 0V3U EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6837 0V3V EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6838 0V3W EMPTY LARGE 5 Co CHARACTER BGII paletted +0x6839 0V3X EMPTY LARGE 5 Co CHARACTER BGII paletted +0x683a 0V3Y EMPTY LARGE 5 Co CHARACTER BGII paletted +0x683b 0V3Z EMPTY LARGE 5 Co CHARACTER BGII paletted +0x683c 0W3A EMPTY LARGE 5 Co CHARACTER BGII paletted +0x683d 0A0A EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x683e 0A0B EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x683f 0A0C EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x6840 0A0D EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x6841 0A0E EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6842 0A0F EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6843 0A0G EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6844 0A0H EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6845 0A0I EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6846 0A0J EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6847 0A0K EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6848 0A0L EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6849 0A0M EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x684a 0A0N EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x684b 0A0O EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x684c 0A0P EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x684d 0A0Q EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x684e 0A0R EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x684f 0A0S EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x6850 0A0T EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x6851 0A0U EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6852 0A0V EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6853 0A0W EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6854 0A0X EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6855 0A0Y EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6856 0A0Z EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6857 0B0A EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6858 0B0B EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6859 0B0C EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x685a 0B0D EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x685b 0B0E EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x685c 0B0F EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x685d 0B0G EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x685e 0B0H EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x685f 0B0I EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x6860 0B0J EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x6861 0B0K EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6862 0B0L EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6863 0B0M EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6864 0B0N EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6865 0B0O EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6866 0B0P EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6867 0B0Q EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6868 0B0R EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6869 0B0S EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x686a 0B0T EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x686b 0B0U EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x686c 0B0V EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x686d 0B0W EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x686e 0B0X EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x686f 0B0Y EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x6870 0B0Z EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x6871 0C0A EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6872 0C0B EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6873 0C0C EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6874 0C0D EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6875 0C0E EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6876 0C0F EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6877 0C0G EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6878 0C0H EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6879 0C0I EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x687a 0C0J EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x687b 0C0K EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x687c 0C0L EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x687d 0C0M EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x687e 0C0N EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x687f 0C0O EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x6880 0C0P EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x6881 0C0Q EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6882 0C0R EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6883 0C0S EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6884 0C0T EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6885 0C0U EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6886 0C0V EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6887 0C0W EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6888 0C0X EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6889 0C0Y EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x688a 0C0Z EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x688b 0D0A EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x688c 0D0B EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x688d 0D0C EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x688e 0D0D EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x688f 0D0E EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x6890 0D0F EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x6891 0D0G EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6892 0D0H EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6893 0D0I EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6894 0D0J EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x6895 0D0K EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6896 0D0L EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6897 0D0M EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6898 0D0N EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x6899 0D0O EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x689a 0D0P EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x689b 0D0Q EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x689c 0D0R EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x689d 0D0S EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x689e 0D0T EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x689f 0D0U EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x68a0 0D0V EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x68a1 0D0W EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x68a2 0D0X EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x68a3 0D0Y EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x68a4 0D0Z EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x68a5 0E0A EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x68a6 0E0B EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x68a7 0E0C EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x68a8 0E0D EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x68a9 0E0E EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x68aa 0E0F EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x68ab 0E0G EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x68ac 0E0H EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x68ad 0E0I EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x68ae 0E0J EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x68af 0E0K EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x68b0 0E0L EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x68b1 0E0M EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x68b2 0E0N EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x68b3 0E0O EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x68b4 0E0P EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x68b5 0E0Q EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x68b6 0E0R EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x68b7 0E0S EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x68b8 0E0T EMPTY NORMAL 3 Co CHARACTER BGII WQN paletted +0x68b9 0E0U EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x68ba 0E0V EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x68bb 0E0W EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x68bc 0E0X EMPTY NORMAL 3 Co CHARACTER BGII WQS paletted +0x68bd 0E0Y EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x68be 0E0Z EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x68bf 0F0A EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x68c0 0F0B EMPTY NORMAL 3 Co CHARACTER BGII WQL paletted +0x68c1 0F0C EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x68c2 0F0D EMPTY NORMAL 3 Co CHARACTER BGII WQM paletted +0x68c3 0A3A EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68c4 0A3B EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68c5 0A3C EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68c6 0A3D EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68c7 0A3E EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68c8 0A3F EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68c9 0A3G EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68ca 0A3H EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68cb 0A3I EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68cc 0A3J EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68cd 0A3K EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68ce 0A3L EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68cf 0A3M EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68d0 0A3N EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68d1 0A3O EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68d2 0A3P EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68d3 0A3Q EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68d4 0A3R EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68d5 0A3S EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68d6 0A3T EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68d7 0A3U EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68d8 0A3V EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68d9 0A3W EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68da 0A3X EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68db 0A3Y EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68dc 0A3Z EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68dd 0B3A EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68de 0B3B EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68df 0B3C EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68e0 0B3D EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68e1 0B3E EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68e2 0B3F EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68e3 0B3G EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68e4 0B3H EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68e5 0B3I EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68e6 0B3J EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68e7 0B3K EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68e8 0B3L EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68e9 0B3M EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68ea 0B3N EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68eb 0B3O EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68ec 0B3P EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68ed 0B3Q EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68ee 0B3R EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68ef 0B3S EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68f0 0B3T EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68f1 0B3U EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68f2 0B3V EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68f3 0B3W EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68f4 0B3X EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68f5 0B3Y EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68f6 0B3Z EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68f7 0C3A EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68f8 0C3B EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68f9 0C3C EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68fa 0C3D EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68fb 0C3E EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68fc 0C3F EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68fd 0C3G EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68fe 0C3H EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x68ff 0C3I EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6900 0C3J EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6901 0C3K EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6902 0C3L EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6903 0C3M EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6904 0C3N EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6905 0C3O EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6906 0C3P EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6907 0C3Q EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6908 0C3R EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6909 0C3S EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x690a 0C3T EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x690b 0C3U EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x690c 0C3V EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x690d 0C3W EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x690e 0C3X EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x690f 0C3Y EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6910 0C3Z EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6911 0D3A EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6912 0D3B EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6913 0D3C EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6914 0D3D EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6915 0D3E EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6916 0D3F EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6917 0D3G EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6918 0D3H EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6919 0D3I EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x691a 0D3J EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x691b 0D3K EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x691c 0D3L EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x691d 0D3M EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x691e 0D3N EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x691f 0D3O EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6920 0D3P EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6921 0D3Q EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6922 0D3R EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6923 0D3S EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6924 0D3T EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6925 0D3U EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6926 0D3V EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6927 0D3W EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6928 0D3X EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6929 0D3Y EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x692a 0D3Z EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x692b 0E3A EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x692c 0E3B EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x692d 0E3C EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x692e 0E3D EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x692f 0E3E EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6930 0E3F EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6931 0E3G EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6932 0E3H EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6933 0E3I EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6934 0E3J EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6935 0E3K EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6936 0E3L EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6937 0E3M EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6938 0E3N EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6939 0E3O EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x693a 0E3P EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x693b 0E3Q EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x693c 0E3R EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x693d 0E3S EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x693e 0E3T EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x693f 0E3U EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6940 0E3V EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6941 0E3W EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6942 0E3X EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6943 0E3Y EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6944 0E3Z EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6945 0F3A EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6946 0F3B EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6947 0F3C EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6948 0F3D EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6949 0F3E EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x694a 0F3F EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x694b 0F3G EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x694c 0F3H EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x694d 0F3I EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x694e 0F3J EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x694f 0F3K EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6950 0F3L EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6951 0F3M EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6952 0F3N EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6953 0F3O EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6954 0F3P EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6955 0F3Q EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6956 0F3R EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6957 0F3S EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6958 0F3T EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6959 0F3U EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x695a 0F3V EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x695b 0F3W EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x695c 0F3X EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x695d 0F3Y EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x695e 0F3Z EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x695f 0G3A EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6960 0G3B EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6961 0G3C EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6962 0G3D EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6963 0G3E EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6964 0G3F EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6965 0G3G EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6966 0G3H EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6967 0G3I EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6968 0G3J EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6969 0G3K EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x696a 0G3L EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x696b 0G3M EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x696c 0G3N EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x696d 0G3O EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x696e 0G3P EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x696f 0G3Q EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6970 0G3R EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6971 0G3S EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6972 0G3T EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6973 0G3U EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6974 0G3V EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6975 0G3W EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6976 0G3X EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6977 0G3Y EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6978 0G3Z EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6979 0H3A EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x697a 0H3B EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x697b 0H3C EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x697c 0H3D EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x697d 0H3E EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x697e 0H3F EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x697f 0H3G EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6980 0H3H EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6981 0H3I EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6982 0H3J EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6983 0H3K EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6984 0H3L EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6985 0H3M EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6986 0H3N EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6987 0H3O EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6988 0H3P EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6989 0H3Q EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x698a 0H3R EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x698b 0H3S EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x698c 0H3T EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x698d 0H3U EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x698e 0H3V EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x698f 0H3W EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6990 0H3X EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6991 0H3Y EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6992 0H3Z EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6993 0I3A EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6994 0I3B EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6995 0I3C EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6996 0I3D EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6997 0I3E EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6998 0I3F EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6999 0I3G EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x699a 0I3H EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x699b 0I3I EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x699c 0I3J EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x699d 0I3K EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x699e 0I3L EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x699f 0I3M EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69a0 0I3N EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69a1 0I3O EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69a2 0I3P EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69a3 0I3Q EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69a4 0I3R EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69a5 0I3S EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69a6 0I3T EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69a7 0I3U EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69a8 0I3V EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69a9 0I3W EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69aa 0I3X EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69ab 0I3Y EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69ac 0I3Z EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69ad 0J3A EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69ae 0J3B EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69af 0J3C EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69b0 0J3D EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69b1 0J3E EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69b2 0J3F EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69b3 0J3G EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69b4 0J3H EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69b5 0J3I EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69b6 0J3J EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69b7 0J3K EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69b8 0J3L EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69b9 0J3M EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69ba 0J3N EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69bb 0J3O EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69bc 0J3P EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69bd 0J3Q EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69be 0J3R EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69bf 0J3S EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69c0 0J3T EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69c1 0J3U EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69c2 0J3V EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69c3 0J3W EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69c4 0J3X EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69c5 0J3Y EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69c6 0J3Z EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69c7 0K3A EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69c8 0K3B EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69c9 0K3C EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69ca 0K3D EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69cb 0K3E EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69cc 0K3F EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69cd 0K3G EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69ce 0K3H EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69cf 0K3I EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69d0 0K3J EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69d1 0K3K EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69d2 0K3L EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69d3 0K3M EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69d4 0K3N EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69d5 0K3O EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69d6 0K3P EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69d7 0K3Q EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69d8 0K3R EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69d9 0K3S EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69da 0K3T EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69db 0K3U EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69dc 0K3V EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69dd 0K3W EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69de 0K3X EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69df 0K3Y EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69e0 0K3Z EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69e1 0L3A EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69e2 0L3B EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69e3 0L3C EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69e4 0L3D EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69e5 0L3E EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69e6 0L3F EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69e7 0L3G EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69e8 0L3H EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69e9 0L3I EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69ea 0L3J EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69eb 0L3K EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69ec 0L3L EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69ed 0L3M EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69ee 0L3N EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69ef 0L3O EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69f0 0L3P EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69f1 0L3Q EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69f2 0L3R EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69f3 0L3S EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69f4 0L3T EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69f5 0L3U EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69f6 0L3V EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69f7 0L3W EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69f8 0L3X EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69f9 0L3Y EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69fa 0L3Z EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69fb 0M3A EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69fc 0M3B EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69fd 0M3C EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69fe 0M3D EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x69ff 0M3E EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a00 0M3F EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a01 0M3G EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a02 0M3H EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a03 0M3I EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a04 0M3J EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a05 0M3K EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a06 0M3L EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a07 0M3M EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a08 0M3N EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a09 0M3O EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a0a 0M3P EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a0b 0M3Q EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a0c 0M3R EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a0d 0M3S EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a0e 0M3T EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a0f 0M3U EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a10 0M3V EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a11 0M3W EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a12 0M3X EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a13 0M3Y EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a14 0M3Z EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a15 0N3A EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a16 0N3B EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a17 0N3C EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a18 0N3D EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a19 0N3E EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a1a 0N3F EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a1b 0N3G EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a1c 0N3H EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a1d 0N3I EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a1e 0N3J EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a1f 0N3K EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a20 0N3L EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a21 0N3M EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a22 0N3N EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a23 0N3O EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a24 0N3P EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a25 0N3Q EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a26 0N3R EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a27 0N3S EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a28 0N3T EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a29 0N3U EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a2a 0N3V EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a2b 0N3W EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a2c 0N3X EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a2d 0N3Y EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a2e 0N3Z EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a2f 0O3A EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a30 0O3B EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a31 0O3C EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a32 0O3D EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a33 0O3E EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a34 0O3F EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a35 0O3G EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a36 0O3H EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a37 0O3I EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a38 0O3J EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a39 0O3K EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a3a 0O3L EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a3b 0O3M EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a3c 0O3N EMPTY NORMAL 3 Co CHARACTER BGII unpaletted +0x6a3d 0O3O EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a3e 0O3P EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a3f 0O3Q EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a40 0O3R EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a41 0O3S EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a42 0O3T EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a43 0O3U EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a44 0O3V EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a45 0O3W EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a46 0O3X EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a47 0O3Y EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a48 0O3Z EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a49 0P3A EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a4a 0P3B EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a4b 0P3C EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a4c 0P3D EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a4d 0P3E EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a4e 0P3F EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a4f 0P3G EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a50 0P3H EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a51 0P3I EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a52 0P3J EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a53 0P3K EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a54 0P3L EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a55 0P3M EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a56 0P3N EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a57 0P3O EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a58 0P3P EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a59 0P3Q EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a5a 0P3R EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a5b 0P3S EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a5c 0P3T EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a5d 0P3U EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a5e 0P3V EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a5f 0P3W EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a60 0P3X EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a61 0P3Y EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a62 0P3Z EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a63 0Q3A EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a64 0Q3B EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a65 0Q3C EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a66 0Q3D EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a67 0Q3E EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a68 0Q3F EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a69 0Q3G EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a6a 0Q3H EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a6b 0Q3I EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a6c 0Q3J EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a6d 0Q3K EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a6e 0Q3L EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a6f 0Q3M EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a70 0Q3N EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a71 0Q3O EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a72 0Q3P EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a73 0Q3Q EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a74 0Q3R EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a75 0Q3S EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a76 0Q3T EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a77 0Q3U EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a78 0Q3V EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a79 0Q3W EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a7a 0Q3X EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a7b 0Q3Y EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a7c 0Q3Z EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a7d 0R3A EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a7e 0R3B EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a7f 0R3C EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a80 0R3D EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a81 0R3E EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a82 0R3F EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a83 0R3G EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a84 0R3H EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a85 0R3I EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a86 0R3J EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a87 0R3K EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a88 0R3L EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a89 0R3M EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a8a 0R3N EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a8b 0R3O EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a8c 0R3P EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a8d 0R3Q EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a8e 0R3R EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a8f 0R3S EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a90 0R3T EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a91 0R3U EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a92 0R3V EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a93 0R3W EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a94 0R3X EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a95 0R3Y EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a96 0R3Z EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a97 0S3A EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a98 0S3B EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a99 0S3C EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a9a 0S3D EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a9b 0S3E EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a9c 0S3F EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a9d 0S3G EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a9e 0S3H EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6a9f 0S3I EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aa0 0S3J EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aa1 0S3K EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aa2 0S3L EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aa3 0S3M EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aa4 0S3N EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aa5 0S3O EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aa6 0S3P EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aa7 0S3Q EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aa8 0S3R EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aa9 0S3S EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aaa 0S3T EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aab 0S3U EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aac 0S3V EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aad 0S3W EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aae 0S3X EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aaf 0S3Y EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ab0 0S3Z EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ab1 0T3A EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ab2 0T3B EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ab3 0T3C EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ab4 0T3D EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ab5 0T3E EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ab6 0T3F EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ab7 0T3G EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ab8 0T3H EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ab9 0T3I EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aba 0T3J EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6abb 0T3K EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6abc 0T3L EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6abd 0T3M EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6abe 0T3N EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6abf 0T3O EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ac0 0T3P EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ac1 0T3Q EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ac2 0T3R EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ac3 0T3S EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ac4 0T3T EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ac5 0T3U EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ac6 0T3V EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ac7 0T3W EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ac8 0T3X EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ac9 0T3Y EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aca 0T3Z EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6acb 0U3A EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6acc 0U3B EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6acd 0U3C EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ace 0U3D EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6acf 0U3E EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ad0 0U3F EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ad1 0U3G EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ad2 0U3H EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ad3 0U3I EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ad4 0U3J EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ad5 0U3K EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ad6 0U3L EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ad7 0U3M EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ad8 0U3N EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ad9 0U3O EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ada 0U3P EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6adb 0U3Q EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6adc 0U3R EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6add 0U3S EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ade 0U3T EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6adf 0U3U EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ae0 0U3V EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ae1 0U3W EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ae2 0U3X EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ae3 0U3Y EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ae4 0U3Z EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ae5 0V3A EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ae6 0V3B EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ae7 0V3C EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ae8 0V3D EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6ae9 0V3E EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aea 0V3F EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aeb 0V3G EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aec 0V3H EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aed 0V3I EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aee 0V3J EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aef 0V3K EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6af0 0V3L EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6af1 0V3M EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6af2 0V3N EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6af3 0V3O EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6af4 0V3P EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6af5 0V3Q EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6af6 0V3R EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6af7 0V3S EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6af8 0V3T EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6af9 0V3U EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6afa 0V3V EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6afb 0V3W EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6afc 0V3X EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6afd 0V3Y EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6afe 0V3Z EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6aff 0W3A EMPTY LARGE 5 Co CHARACTER BGII unpaletted +0x6b00 0A3A EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b01 0A3B EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b02 0A3C EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b03 0A3D EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b04 0A3E EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b05 0A3F EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b06 0A3G EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b07 0A3H EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b08 0A3I EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b09 0A3J EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b0a 0A3K EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b0b 0A3L EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b0c 0A3M EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b0d 0A3N EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b0e 0A3O EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b0f 0A3P EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b10 0A3Q EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b11 0A3R EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b12 0A3S EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b13 0A3T EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b14 0A3U EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b15 0A3V EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b16 0A3W EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b17 0A3X EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b18 0A3Y EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b19 0A3Z EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b1a 0B3A EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b1b 0B3B EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b1c 0B3C EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b1d 0B3D EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b1e 0B3E EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b1f 0B3F EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b20 0B3G EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b21 0B3H EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b22 0B3I EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b23 0B3J EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b24 0B3K EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b25 0B3L EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b26 0B3M EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b27 0B3N EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b28 0B3O EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b29 0B3P EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b2a 0B3Q EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b2b 0B3R EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b2c 0B3S EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b2d 0B3T EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b2e 0B3U EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b2f 0B3V EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b30 0B3W EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b31 0B3X EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b32 0B3Y EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b33 0B3Z EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b34 0C3A EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b35 0C3B EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b36 0C3C EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b37 0C3D EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b38 0C3E EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b39 0C3F EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b3a 0C3G EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b3b 0C3H EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b3c 0C3I EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b3d 0C3J EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b3e 0C3K EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b3f 0C3L EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b40 0C3M EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b41 0C3N EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b42 0C3O EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b43 0C3P EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b44 0C3Q EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b45 0C3R EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b46 0C3S EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b47 0C3T EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b48 0C3U EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b49 0C3V EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b4a 0C3W EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b4b 0C3X EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b4c 0C3Y EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b4d 0C3Z EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b4e 0D3A EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b4f 0D3B EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b50 0D3C EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b51 0D3D EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b52 0D3E EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b53 0D3F EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b54 0D3G EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b55 0D3H EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b56 0D3I EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b57 0D3J EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b58 0D3K EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b59 0D3L EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b5a 0D3M EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b5b 0D3N EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b5c 0D3O EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b5d 0D3P EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b5e 0D3Q EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b5f 0D3R EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b60 0D3S EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b61 0D3T EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b62 0D3U EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b63 0D3V EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b64 0D3W EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b65 0D3X EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b66 0D3Y EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b67 0D3Z EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b68 0E3A EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b69 0E3B EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b6a 0E3C EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b6b 0E3D EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b6c 0E3E EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b6d 0E3F EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b6e 0E3G EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b6f 0E3H EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b70 0E3I EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b71 0E3J EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b72 0E3K EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b73 0E3L EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b74 0E3M EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b75 0E3N EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b76 0E3O EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b77 0E3P EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b78 0E3Q EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b79 0E3R EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b7a 0E3S EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b7b 0E3T EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b7c 0E3U EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b7d 0E3V EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b7e 0E3W EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b7f 0E3X EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b80 0E3Y EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b81 0E3Z EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b82 0F3A EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b83 0F3B EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b84 0F3C EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b85 0F3D EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b86 0F3E EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b87 0F3F EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b88 0F3G EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b89 0F3H EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b8a 0F3I EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b8b 0F3J EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b8c 0F3K EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b8d 0F3L EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b8e 0F3M EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b8f 0F3N EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b90 0F3O EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b91 0F3P EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b92 0F3Q EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b93 0F3R EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b94 0F3S EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b95 0F3T EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b96 0F3U EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b97 0F3V EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b98 0F3W EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b99 0F3X EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b9a 0F3Y EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b9b 0F3Z EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b9c 0G3A EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b9d 0G3B EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b9e 0G3C EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6b9f 0G3D EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ba0 0G3E EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ba1 0G3F EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ba2 0G3G EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ba3 0G3H EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ba4 0G3I EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ba5 0G3J EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ba6 0G3K EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ba7 0G3L EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ba8 0G3M EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ba9 0G3N EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6baa 0G3O EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bab 0G3P EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bac 0G3Q EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bad 0G3R EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bae 0G3S EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6baf 0G3T EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bb0 0G3U EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bb1 0G3V EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bb2 0G3W EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bb3 0G3X EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bb4 0G3Y EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bb5 0G3Z EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bb6 0H3A EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bb7 0H3B EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bb8 0H3C EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bb9 0H3D EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bba 0H3E EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bbb 0H3F EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bbc 0H3G EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bbd 0H3H EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bbe 0H3I EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bbf 0H3J EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bc0 0H3K EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bc1 0H3L EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bc2 0H3M EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bc3 0H3N EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bc4 0H3O EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bc5 0H3P EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bc6 0H3Q EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bc7 0H3R EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bc8 0H3S EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bc9 0H3T EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bca 0H3U EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bcb 0H3V EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bcc 0H3W EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bcd 0H3X EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bce 0H3Y EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bcf 0H3Z EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bd0 0I3A EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bd1 0I3B EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bd2 0I3C EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bd3 0I3D EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bd4 0I3E EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bd5 0I3F EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bd6 0I3G EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bd7 0I3H EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bd8 0I3I EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bd9 0I3J EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bda 0I3K EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bdb 0I3L EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bdc 0I3M EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bdd 0I3N EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bde 0I3O EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bdf 0I3P EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6be0 0I3Q EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6be1 0I3R EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6be2 0I3S EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6be3 0I3T EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6be4 0I3U EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6be5 0I3V EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6be6 0I3W EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6be7 0I3X EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6be8 0I3Y EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6be9 0I3Z EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bea 0J3A EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6beb 0J3B EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bec 0J3C EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bed 0J3D EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bee 0J3E EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bef 0J3F EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bf0 0J3G EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bf1 0J3H EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bf2 0J3I EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bf3 0J3J EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bf4 0J3K EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bf5 0J3L EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bf6 0J3M EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bf7 0J3N EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bf8 0J3O EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bf9 0J3P EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bfa 0J3Q EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bfb 0J3R EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bfc 0J3S EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bfd 0J3T EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bfe 0J3U EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6bff 0J3V EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c00 0J3W EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c01 0J3X EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c02 0J3Y EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c03 0J3Z EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c04 0K3A EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c05 0K3B EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c06 0K3C EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c07 0K3D EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c08 0K3E EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c09 0K3F EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c0a 0K3G EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c0b 0K3H EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c0c 0K3I EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c0d 0K3J EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c0e 0K3K EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c0f 0K3L EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c10 0K3M EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c11 0K3N EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c12 0K3O EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c13 0K3P EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c14 0K3Q EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c15 0K3R EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c16 0K3S EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c17 0K3T EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c18 0K3U EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c19 0K3V EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c1a 0K3W EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c1b 0K3X EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c1c 0K3Y EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c1d 0K3Z EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c1e 0L3A EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c1f 0L3B EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c20 0L3C EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c21 0L3D EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c22 0L3E EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c23 0L3F EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c24 0L3G EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c25 0L3H EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c26 0L3I EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c27 0L3J EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c28 0L3K EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c29 0L3L EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c2a 0L3M EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c2b 0L3N EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c2c 0L3O EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c2d 0L3P EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c2e 0L3Q EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c2f 0L3R EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c30 0L3S EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c31 0L3T EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c32 0L3U EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c33 0L3V EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c34 0L3W EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c35 0L3X EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c36 0L3Y EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c37 0L3Z EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c38 0M3A EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c39 0M3B EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c3a 0M3C EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c3b 0M3D EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c3c 0M3E EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c3d 0M3F EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c3e 0M3G EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c3f 0M3H EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c40 0M3I EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c41 0M3J EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c42 0M3K EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c43 0M3L EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c44 0M3M EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c45 0M3N EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c46 0M3O EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c47 0M3P EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c48 0M3Q EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c49 0M3R EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c4a 0M3S EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c4b 0M3T EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c4c 0M3U EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c4d 0M3V EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c4e 0M3W EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c4f 0M3X EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c50 0M3Y EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c51 0M3Z EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c52 0N3A EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c53 0N3B EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c54 0N3C EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c55 0N3D EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c56 0N3E EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c57 0N3F EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c58 0N3G EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c59 0N3H EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c5a 0N3I EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c5b 0N3J EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c5c 0N3K EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c5d 0N3L EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c5e 0N3M EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c5f 0N3N EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c60 0N3O EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c61 0N3P EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c62 0N3Q EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c63 0N3R EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c64 0N3S EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c65 0N3T EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c66 0N3U EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c67 0N3V EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c68 0N3W EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c69 0N3X EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c6a 0N3Y EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c6b 0N3Z EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c6c 0O3A EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c6d 0O3B EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c6e 0O3C EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c6f 0O3D EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c70 0O3E EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c71 0O3F EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c72 0O3G EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c73 0O3H EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c74 0O3I EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c75 0O3J EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c76 0O3K EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c77 0O3L EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c78 0O3M EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c79 0O3N EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c7a 0O3O EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c7b 0O3P EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c7c 0O3Q EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c7d 0O3R EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c7e 0O3S EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c7f 0O3T EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c80 0O3U EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c81 0O3V EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c82 0O3W EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c83 0O3X EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c84 0O3Y EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c85 0O3Z EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c86 0P3A EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c87 0P3B EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c88 0P3C EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c89 0P3D EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c8a 0P3E EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c8b 0P3F EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c8c 0P3G EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c8d 0P3H EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c8e 0P3I EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c8f 0P3J EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c90 0P3K EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c91 0P3L EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c92 0P3M EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c93 0P3N EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c94 0P3O EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c95 0P3P EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c96 0P3Q EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c97 0P3R EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c98 0P3S EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c99 0P3T EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c9a 0P3U EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c9b 0P3V EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c9c 0P3W EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c9d 0P3X EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c9e 0P3Y EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6c9f 0P3Z EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ca0 0Q3A EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ca1 0Q3B EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ca2 0Q3C EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ca3 0Q3D EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ca4 0Q3E EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ca5 0Q3F EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ca6 0Q3G EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ca7 0Q3H EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ca8 0Q3I EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ca9 0Q3J EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6caa 0Q3K EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cab 0Q3L EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cac 0Q3M EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cad 0Q3N EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cae 0Q3O EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6caf 0Q3P EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cb0 0Q3Q EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cb1 0Q3R EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cb2 0Q3S EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cb3 0Q3T EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cb4 0Q3U EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cb5 0Q3V EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cb6 0Q3W EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cb7 0Q3X EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cb8 0Q3Y EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cb9 0Q3Z EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cba 0R3A EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cbb 0R3B EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cbc 0R3C EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cbd 0R3D EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cbe 0R3E EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cbf 0R3F EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cc0 0R3G EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cc1 0R3H EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cc2 0R3I EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cc3 0R3J EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cc4 0R3K EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cc5 0R3L EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cc6 0R3M EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cc7 0R3N EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cc8 0R3O EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cc9 0R3P EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cca 0R3Q EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ccb 0R3R EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ccc 0R3S EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ccd 0R3T EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cce 0R3U EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ccf 0R3V EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cd0 0R3W EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cd1 0R3X EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cd2 0R3Y EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cd3 0R3Z EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cd4 0S3A EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cd5 0S3B EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cd6 0S3C EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cd7 0S3D EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cd8 0S3E EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cd9 0S3F EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cda 0S3G EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cdb 0S3H EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cdc 0S3I EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cdd 0S3J EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cde 0S3K EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cdf 0S3L EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ce0 0S3M EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ce1 0S3N EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ce2 0S3O EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ce3 0S3P EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ce4 0S3Q EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ce5 0S3R EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ce6 0S3S EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ce7 0S3T EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ce8 0S3U EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ce9 0S3V EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cea 0S3W EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ceb 0S3X EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cec 0S3Y EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6ced 0S3Z EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cee 0T3A EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cef 0T3B EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cf0 0T3C EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cf1 0T3D EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cf2 0T3E EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cf3 0T3F EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cf4 0T3G EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cf5 0T3H EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cf6 0T3I EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cf7 0T3J EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cf8 0T3K EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cf9 0T3L EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cfa 0T3M EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cfb 0T3N EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cfc 0T3O EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cfd 0T3P EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cfe 0T3Q EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6cff 0T3R EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6d00 0T3S EMPTY NORMAL 3 Co CHARACTER BGI paletted +0x6d01 0T3T EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d02 0T3U EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d03 0T3V EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d04 0T3W EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d05 0T3X EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d06 0T3Y EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d07 0T3Z EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d08 0U3A EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d09 0U3B EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d0a 0U3C EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d0b 0U3D EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d0c 0U3E EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d0d 0U3F EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d0e 0U3G EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d0f 0U3H EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d10 0U3I EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d11 0U3J EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d12 0U3K EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d13 0U3L EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d14 0U3M EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d15 0U3N EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d16 0U3O EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d17 0U3P EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d18 0U3Q EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d19 0U3R EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d1a 0U3S EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d1b 0U3T EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d1c 0U3U EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d1d 0U3V EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d1e 0U3W EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d1f 0U3X EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d20 0U3Y EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d21 0U3Z EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d22 0V3A EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d23 0V3B EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d24 0V3C EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d25 0V3D EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d26 0V3E EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d27 0V3F EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d28 0V3G EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d29 0V3H EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d2a 0V3I EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d2b 0V3J EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d2c 0V3K EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d2d 0V3L EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d2e 0V3M EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d2f 0V3N EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d30 0V3O EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d31 0V3P EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d32 0V3Q EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d33 0V3R EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d34 0V3S EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d35 0V3T EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d36 0V3U EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d37 0V3V EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d38 0V3W EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d39 0V3X EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d3a 0V3Y EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d3b 0V3Z EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d3c 0W3A EMPTY LARGE 5 Co CHARACTER BGI paletted +0x6d3d 0A0A EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d3e 0A0B EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d3f 0A0C EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d40 0A0D EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d41 0A0E EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d42 0A0F EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d43 0A0G EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d44 0A0H EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d45 0A0I EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d46 0A0J EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d47 0A0K EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d48 0A0L EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d49 0A0M EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d4a 0A0N EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d4b 0A0O EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d4c 0A0P EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d4d 0A0Q EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d4e 0A0R EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d4f 0A0S EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d50 0A0T EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d51 0A0U EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d52 0A0V EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d53 0A0W EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d54 0A0X EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d55 0A0Y EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d56 0A0Z EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d57 0B0A EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d58 0B0B EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d59 0B0C EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d5a 0B0D EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d5b 0B0E EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d5c 0B0F EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d5d 0B0G EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d5e 0B0H EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d5f 0B0I EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d60 0B0J EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d61 0B0K EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d62 0B0L EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d63 0B0M EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d64 0B0N EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d65 0B0O EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d66 0B0P EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d67 0B0Q EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d68 0B0R EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d69 0B0S EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d6a 0B0T EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d6b 0B0U EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d6c 0B0V EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d6d 0B0W EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d6e 0B0X EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d6f 0B0Y EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d70 0B0Z EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d71 0C0A EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d72 0C0B EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d73 0C0C EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d74 0C0D EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d75 0C0E EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d76 0C0F EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d77 0C0G EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d78 0C0H EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d79 0C0I EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d7a 0C0J EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d7b 0C0K EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d7c 0C0L EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d7d 0C0M EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d7e 0C0N EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d7f 0C0O EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d80 0C0P EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d81 0C0Q EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d82 0C0R EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d83 0C0S EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d84 0C0T EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d85 0C0U EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d86 0C0V EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d87 0C0W EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d88 0C0X EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d89 0C0Y EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d8a 0C0Z EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d8b 0D0A EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d8c 0D0B EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d8d 0D0C EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d8e 0D0D EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d8f 0D0E EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d90 0D0F EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d91 0D0G EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d92 0D0H EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d93 0D0I EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d94 0D0J EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d95 0D0K EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d96 0D0L EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d97 0D0M EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6d98 0D0N EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d99 0D0O EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d9a 0D0P EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d9b 0D0Q EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d9c 0D0R EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6d9d 0D0S EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d9e 0D0T EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6d9f 0D0U EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6da0 0D0V EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6da1 0D0W EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6da2 0D0X EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6da3 0D0Y EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6da4 0D0Z EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6da5 0E0A EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6da6 0E0B EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6da7 0E0C EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6da8 0E0D EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6da9 0E0E EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6daa 0E0F EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6dab 0E0G EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6dac 0E0H EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6dad 0E0I EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6dae 0E0J EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6daf 0E0K EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6db0 0E0L EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6db1 0E0M EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6db2 0E0N EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6db3 0E0O EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6db4 0E0P EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6db5 0E0Q EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6db6 0E0R EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6db7 0E0S EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6db8 0E0T EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6db9 0E0U EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6dba 0E0V EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6dbb 0E0W EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6dbc 0E0X EMPTY NORMAL 3 Co CHARACTER BGI WPS paletted +0x6dbd 0E0Y EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6dbe 0E0Z EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6dbf 0F0A EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6dc0 0F0B EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6dc1 0F0C EMPTY NORMAL 3 Co CHARACTER BGI WPL paletted +0x6dc2 0F0D EMPTY NORMAL 3 Co CHARACTER BGI WPM paletted +0x6dc3 0A3A EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dc4 0A3B EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dc5 0A3C EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dc6 0A3D EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dc7 0A3E EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dc8 0A3F EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dc9 0A3G EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dca 0A3H EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dcb 0A3I EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dcc 0A3J EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dcd 0A3K EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dce 0A3L EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dcf 0A3M EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dd0 0A3N EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dd1 0A3O EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dd2 0A3P EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dd3 0A3Q EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dd4 0A3R EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dd5 0A3S EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dd6 0A3T EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dd7 0A3U EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dd8 0A3V EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dd9 0A3W EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dda 0A3X EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ddb 0A3Y EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ddc 0A3Z EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ddd 0B3A EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dde 0B3B EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ddf 0B3C EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6de0 0B3D EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6de1 0B3E EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6de2 0B3F EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6de3 0B3G EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6de4 0B3H EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6de5 0B3I EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6de6 0B3J EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6de7 0B3K EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6de8 0B3L EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6de9 0B3M EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dea 0B3N EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6deb 0B3O EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dec 0B3P EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ded 0B3Q EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dee 0B3R EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6def 0B3S EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6df0 0B3T EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6df1 0B3U EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6df2 0B3V EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6df3 0B3W EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6df4 0B3X EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6df5 0B3Y EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6df6 0B3Z EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6df7 0C3A EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6df8 0C3B EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6df9 0C3C EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dfa 0C3D EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dfb 0C3E EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dfc 0C3F EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dfd 0C3G EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dfe 0C3H EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6dff 0C3I EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e00 0C3J EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e01 0C3K EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e02 0C3L EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e03 0C3M EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e04 0C3N EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e05 0C3O EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e06 0C3P EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e07 0C3Q EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e08 0C3R EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e09 0C3S EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e0a 0C3T EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e0b 0C3U EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e0c 0C3V EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e0d 0C3W EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e0e 0C3X EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e0f 0C3Y EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e10 0C3Z EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e11 0D3A EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e12 0D3B EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e13 0D3C EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e14 0D3D EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e15 0D3E EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e16 0D3F EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e17 0D3G EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e18 0D3H EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e19 0D3I EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e1a 0D3J EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e1b 0D3K EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e1c 0D3L EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e1d 0D3M EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e1e 0D3N EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e1f 0D3O EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e20 0D3P EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e21 0D3Q EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e22 0D3R EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e23 0D3S EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e24 0D3T EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e25 0D3U EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e26 0D3V EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e27 0D3W EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e28 0D3X EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e29 0D3Y EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e2a 0D3Z EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e2b 0E3A EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e2c 0E3B EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e2d 0E3C EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e2e 0E3D EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e2f 0E3E EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e30 0E3F EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e31 0E3G EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e32 0E3H EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e33 0E3I EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e34 0E3J EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e35 0E3K EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e36 0E3L EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e37 0E3M EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e38 0E3N EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e39 0E3O EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e3a 0E3P EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e3b 0E3Q EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e3c 0E3R EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e3d 0E3S EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e3e 0E3T EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e3f 0E3U EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e40 0E3V EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e41 0E3W EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e42 0E3X EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e43 0E3Y EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e44 0E3Z EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e45 0F3A EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e46 0F3B EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e47 0F3C EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e48 0F3D EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e49 0F3E EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e4a 0F3F EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e4b 0F3G EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e4c 0F3H EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e4d 0F3I EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e4e 0F3J EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e4f 0F3K EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e50 0F3L EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e51 0F3M EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e52 0F3N EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e53 0F3O EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e54 0F3P EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e55 0F3Q EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e56 0F3R EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e57 0F3S EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e58 0F3T EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e59 0F3U EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e5a 0F3V EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e5b 0F3W EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e5c 0F3X EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e5d 0F3Y EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e5e 0F3Z EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e5f 0G3A EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e60 0G3B EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e61 0G3C EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e62 0G3D EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e63 0G3E EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e64 0G3F EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e65 0G3G EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e66 0G3H EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e67 0G3I EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e68 0G3J EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e69 0G3K EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e6a 0G3L EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e6b 0G3M EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e6c 0G3N EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e6d 0G3O EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e6e 0G3P EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e6f 0G3Q EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e70 0G3R EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e71 0G3S EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e72 0G3T EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e73 0G3U EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e74 0G3V EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e75 0G3W EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e76 0G3X EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e77 0G3Y EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e78 0G3Z EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e79 0H3A EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e7a 0H3B EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e7b 0H3C EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e7c 0H3D EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e7d 0H3E EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e7e 0H3F EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e7f 0H3G EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e80 0H3H EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e81 0H3I EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e82 0H3J EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e83 0H3K EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e84 0H3L EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e85 0H3M EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e86 0H3N EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e87 0H3O EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e88 0H3P EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e89 0H3Q EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e8a 0H3R EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e8b 0H3S EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e8c 0H3T EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e8d 0H3U EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e8e 0H3V EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e8f 0H3W EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e90 0H3X EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e91 0H3Y EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e92 0H3Z EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e93 0I3A EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e94 0I3B EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e95 0I3C EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e96 0I3D EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e97 0I3E EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e98 0I3F EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e99 0I3G EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e9a 0I3H EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e9b 0I3I EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e9c 0I3J EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e9d 0I3K EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e9e 0I3L EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6e9f 0I3M EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ea0 0I3N EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ea1 0I3O EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ea2 0I3P EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ea3 0I3Q EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ea4 0I3R EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ea5 0I3S EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ea6 0I3T EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ea7 0I3U EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ea8 0I3V EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ea9 0I3W EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eaa 0I3X EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eab 0I3Y EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eac 0I3Z EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ead 0J3A EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eae 0J3B EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eaf 0J3C EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eb0 0J3D EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eb1 0J3E EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eb2 0J3F EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eb3 0J3G EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eb4 0J3H EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eb5 0J3I EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eb6 0J3J EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eb7 0J3K EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eb8 0J3L EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eb9 0J3M EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eba 0J3N EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ebb 0J3O EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ebc 0J3P EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ebd 0J3Q EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ebe 0J3R EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ebf 0J3S EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ec0 0J3T EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ec1 0J3U EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ec2 0J3V EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ec3 0J3W EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ec4 0J3X EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ec5 0J3Y EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ec6 0J3Z EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ec7 0K3A EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ec8 0K3B EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ec9 0K3C EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eca 0K3D EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ecb 0K3E EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ecc 0K3F EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ecd 0K3G EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ece 0K3H EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ecf 0K3I EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ed0 0K3J EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ed1 0K3K EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ed2 0K3L EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ed3 0K3M EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ed4 0K3N EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ed5 0K3O EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ed6 0K3P EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ed7 0K3Q EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ed8 0K3R EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ed9 0K3S EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eda 0K3T EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6edb 0K3U EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6edc 0K3V EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6edd 0K3W EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ede 0K3X EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6edf 0K3Y EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ee0 0K3Z EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ee1 0L3A EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ee2 0L3B EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ee3 0L3C EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ee4 0L3D EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ee5 0L3E EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ee6 0L3F EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ee7 0L3G EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ee8 0L3H EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ee9 0L3I EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eea 0L3J EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eeb 0L3K EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eec 0L3L EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eed 0L3M EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eee 0L3N EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eef 0L3O EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ef0 0L3P EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ef1 0L3Q EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ef2 0L3R EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ef3 0L3S EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ef4 0L3T EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ef5 0L3U EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ef6 0L3V EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ef7 0L3W EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ef8 0L3X EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6ef9 0L3Y EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6efa 0L3Z EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6efb 0M3A EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6efc 0M3B EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6efd 0M3C EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6efe 0M3D EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6eff 0M3E EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f00 0M3F EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f01 0M3G EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f02 0M3H EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f03 0M3I EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f04 0M3J EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f05 0M3K EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f06 0M3L EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f07 0M3M EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f08 0M3N EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f09 0M3O EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f0a 0M3P EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f0b 0M3Q EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f0c 0M3R EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f0d 0M3S EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f0e 0M3T EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f0f 0M3U EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f10 0M3V EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f11 0M3W EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f12 0M3X EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f13 0M3Y EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f14 0M3Z EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f15 0N3A EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f16 0N3B EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f17 0N3C EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f18 0N3D EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f19 0N3E EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f1a 0N3F EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f1b 0N3G EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f1c 0N3H EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f1d 0N3I EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f1e 0N3J EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f1f 0N3K EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f20 0N3L EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f21 0N3M EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f22 0N3N EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f23 0N3O EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f24 0N3P EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f25 0N3Q EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f26 0N3R EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f27 0N3S EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f28 0N3T EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f29 0N3U EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f2a 0N3V EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f2b 0N3W EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f2c 0N3X EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f2d 0N3Y EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f2e 0N3Z EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f2f 0O3A EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f30 0O3B EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f31 0O3C EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f32 0O3D EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f33 0O3E EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f34 0O3F EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f35 0O3G EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f36 0O3H EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f37 0O3I EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f38 0O3J EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f39 0O3K EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f3a 0O3L EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f3b 0O3M EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f3c 0O3N EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f3d 0O3O EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f3e 0O3P EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f3f 0O3Q EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f40 0O3R EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f41 0O3S EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f42 0O3T EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f43 0O3U EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f44 0O3V EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f45 0O3W EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f46 0O3X EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f47 0O3Y EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f48 0O3Z EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f49 0P3A EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f4a 0P3B EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f4b 0P3C EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f4c 0P3D EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f4d 0P3E EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f4e 0P3F EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f4f 0P3G EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f50 0P3H EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f51 0P3I EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f52 0P3J EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f53 0P3K EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f54 0P3L EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f55 0P3M EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f56 0P3N EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f57 0P3O EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f58 0P3P EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f59 0P3Q EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f5a 0P3R EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f5b 0P3S EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f5c 0P3T EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f5d 0P3U EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f5e 0P3V EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f5f 0P3W EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f60 0P3X EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f61 0P3Y EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f62 0P3Z EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f63 0Q3A EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f64 0Q3B EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f65 0Q3C EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f66 0Q3D EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f67 0Q3E EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f68 0Q3F EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f69 0Q3G EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f6a 0Q3H EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f6b 0Q3I EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f6c 0Q3J EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f6d 0Q3K EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f6e 0Q3L EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f6f 0Q3M EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f70 0Q3N EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f71 0Q3O EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f72 0Q3P EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f73 0Q3Q EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f74 0Q3R EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f75 0Q3S EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f76 0Q3T EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f77 0Q3U EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f78 0Q3V EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f79 0Q3W EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f7a 0Q3X EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f7b 0Q3Y EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f7c 0Q3Z EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f7d 0R3A EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f7e 0R3B EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f7f 0R3C EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f80 0R3D EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f81 0R3E EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f82 0R3F EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f83 0R3G EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f84 0R3H EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f85 0R3I EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f86 0R3J EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f87 0R3K EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f88 0R3L EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f89 0R3M EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f8a 0R3N EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f8b 0R3O EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f8c 0R3P EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f8d 0R3Q EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f8e 0R3R EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f8f 0R3S EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f90 0R3T EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f91 0R3U EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f92 0R3V EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f93 0R3W EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f94 0R3X EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f95 0R3Y EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f96 0R3Z EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f97 0S3A EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f98 0S3B EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f99 0S3C EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f9a 0S3D EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f9b 0S3E EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f9c 0S3F EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f9d 0S3G EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f9e 0S3H EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6f9f 0S3I EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fa0 0S3J EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fa1 0S3K EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fa2 0S3L EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fa3 0S3M EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fa4 0S3N EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fa5 0S3O EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fa6 0S3P EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fa7 0S3Q EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fa8 0S3R EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fa9 0S3S EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6faa 0S3T EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fab 0S3U EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fac 0S3V EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fad 0S3W EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fae 0S3X EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6faf 0S3Y EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fb0 0S3Z EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fb1 0T3A EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fb2 0T3B EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fb3 0T3C EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fb4 0T3D EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fb5 0T3E EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fb6 0T3F EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fb7 0T3G EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fb8 0T3H EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fb9 0T3I EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fba 0T3J EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fbb 0T3K EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fbc 0T3L EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fbd 0T3M EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fbe 0T3N EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fbf 0T3O EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fc0 0T3P EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fc1 0T3Q EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fc2 0T3R EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fc3 0T3S EMPTY NORMAL 3 Co CHARACTER BGI unpaletted +0x6fc4 0T3T EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fc5 0T3U EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fc6 0T3V EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fc7 0T3W EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fc8 0T3X EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fc9 0T3Y EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fca 0T3Z EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fcb 0U3A EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fcc 0U3B EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fcd 0U3C EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fce 0U3D EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fcf 0U3E EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fd0 0U3F EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fd1 0U3G EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fd2 0U3H EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fd3 0U3I EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fd4 0U3J EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fd5 0U3K EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fd6 0U3L EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fd7 0U3M EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fd8 0U3N EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fd9 0U3O EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fda 0U3P EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fdb 0U3Q EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fdc 0U3R EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fdd 0U3S EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fde 0U3T EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fdf 0U3U EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fe0 0U3V EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fe1 0U3W EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fe2 0U3X EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fe3 0U3Y EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fe4 0U3Z EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fe5 0V3A EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fe6 0V3B EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fe7 0V3C EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fe8 0V3D EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fe9 0V3E EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fea 0V3F EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6feb 0V3G EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fec 0V3H EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fed 0V3I EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fee 0V3J EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fef 0V3K EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6ff0 0V3L EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6ff1 0V3M EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6ff2 0V3N EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6ff3 0V3O EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6ff4 0V3P EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6ff5 0V3Q EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6ff6 0V3R EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6ff7 0V3S EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6ff8 0V3T EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6ff9 0V3U EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6ffa 0V3V EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6ffb 0V3W EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6ffc 0V3X EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6ffd 0V3Y EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6ffe 0V3Z EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0x6fff 0W3A EMPTY LARGE 5 Co CHARACTER BGI unpaletted +0xa001 2AA EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa002 2AB EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa003 2AC EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa004 2AD EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa005 2AE EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa006 2AF EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa007 2AG EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa008 2AH EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa009 2AI EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa00a 2AJ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa00b 2AK EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa00c 2AL EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa00d 2AM EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa00e 2AN EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa00f 2AO EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa010 2AP EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa011 2AQ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa012 2AR EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa013 2AS EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa014 2AT EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa015 2AU EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa016 2AV EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa017 2AW EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa018 2AX EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa019 2AY EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa01a 2AZ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa01b 2BA EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa01c 2BB EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa01d 2BC EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa01e 2BD EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa01f 2BE EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa020 2BF EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa021 2BG EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa022 2BH EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa023 2BI EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa024 2BJ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa025 2BK EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa026 2BL EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa027 2BM EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa028 2BN EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa029 2BO EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa02a 2BP EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa02b 2BQ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa02c 2BR EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa02d 2BS EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa02e 2BT EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa02f 2BU EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa030 2BV EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa031 2BW EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa032 2BX EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa033 2BY EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa034 2BZ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa035 2CA EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa036 2CB EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa037 2CC EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa038 2CD EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa039 2CE EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa03a 2CF EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa03b 2CG EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa03c 2CH EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa03d 2CI EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa03e 2CJ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa03f 2CK EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa040 2CL EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa041 2CM EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa042 2CN EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa043 2CO EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa044 2CP EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa045 2CQ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa046 2CR EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa047 2CS EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa048 2CT EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa049 2CU EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa04a 2CV EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa04b 2CW EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa04c 2CX EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa04d 2CY EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa04e 2CZ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa04f 2DA EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa050 2DB EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa051 2DC EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa052 2DD EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa053 2DE EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa054 2DF EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa055 2DG EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa056 2DH EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa057 2DI EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa058 2DJ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa059 2DK EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa05a 2DL EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa05b 2DM EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa05c 2DN EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa05d 2DO EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa05e 2DP EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa05f 2DQ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa060 2DR EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa061 2DS EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa062 2DT EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa063 2DU EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa064 2DV EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa065 2DW EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa066 2DX EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa067 2DY EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa068 2DZ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa069 2EA EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa06a 2EB EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa06b 2EC EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa06c 2ED EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa06d 2EE EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa06e 2EF EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa06f 2EG EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa070 2EH EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa071 2EI EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa072 2EJ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa073 2EK EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa074 2EL EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa075 2EM EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa076 2EN EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa077 2EO EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa078 2EP EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa079 2EQ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa07a 2ER EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa07b 2ES EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa07c 2ET EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa07d 2EU EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa07e 2EV EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa07f 2EW EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa080 2EX EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa081 2EY EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa082 2EZ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa083 2FA EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa084 2FB EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa085 2FC EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa086 2FD EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa087 2FE EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa088 2FF EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa089 2FG EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa08a 2FH EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa08b 2FI EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa08c 2FJ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa08d 2FK EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa08e 2FL EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa08f 2FM EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa090 2FN EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa091 2FO EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa092 2FP EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa093 2FQ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa094 2FR EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa095 2FS EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa096 2FT EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa097 2FU EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa098 2FV EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa099 2FW EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa09a 2FX EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa09b 2FY EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa09c 2FZ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa09d 2GA EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa09e 2GB EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa09f 2GC EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0a0 2GD EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0a1 2GE EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0a2 2GF EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0a3 2GG EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0a4 2GH EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0a5 2GI EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0a6 2GJ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0a7 2GK EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0a8 2GL EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0a9 2GM EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0aa 2GN EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0ab 2GO EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0ac 2GP EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0ad 2GQ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0ae 2GR EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0af 2GS EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0b0 2GT EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0b1 2GU EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0b2 2GV EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0b3 2GW EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0b4 2GX EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0b5 2GY EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0b6 2GZ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0b7 2HA EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0b8 2HB EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0b9 2HC EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0ba 2HD EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0bb 2HE EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0bc 2HF EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0bd 2HG EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0be 2HH EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0bf 2HI EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0c0 2HJ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0c1 2HK EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0c2 2HL EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0c3 2HM EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0c4 2HN EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0c5 2HO EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0c6 2HP EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0c7 2HQ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0c8 2HR EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0c9 2HS EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0ca 2HT EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0cb 2HU EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0cc 2HV EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0cd 2HW EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0ce 2HX EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0cf 2HY EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0d0 2HZ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0d1 2IA EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0d2 2IB EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0d3 2IC EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0d4 2ID EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0d5 2IE EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0d6 2IF EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0d7 2IG EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0d8 2IH EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0d9 2II EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0da 2IJ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0db 2IK EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0dc 2IL EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0dd 2IM EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0de 2IN EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0df 2IO EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0e0 2IP EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0e1 2IQ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0e2 2IR EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0e3 2IS EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0e4 2IT EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0e5 2IU EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0e6 2IV EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0e7 2IW EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0e8 2IX EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0e9 2IY EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0ea 2IZ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0eb 2JA EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0ec 2JB EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0ed 2JC EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0ee 2JD EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0ef 2JE EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0f0 2JF EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0f1 2JG EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0f2 2JH EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0f3 2JI EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0f4 2JJ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0f5 2JK EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0f6 2JL EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0f7 2JM EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0f8 2JN EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0f9 2JO EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0fa 2JP EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0fb 2JQ EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0fc 2JR EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0fd 2JS EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0fe 2JT EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa0ff 2JU EMPTY NORMAL 3 Xo BGI MONSTER LONG unpaletted +0xa101 2JW EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa102 2JX EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa103 2JY EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa104 2JZ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa105 2KA EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa106 2KB EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa107 2KC EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa108 2KD EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa109 2KE EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa10a 2KF EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa10b 2KG EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa10c 2KH EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa10d 2KI EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa10e 2KJ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa10f 2KK EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa110 2KL EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa111 2KM EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa112 2KN EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa113 2KO EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa114 2KP EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa115 2KQ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa116 2KR EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa117 2KS EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa118 2KT EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa119 2KU EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa11a 2KV EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa11b 2KW EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa11c 2KX EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa11d 2KY EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa11e 2KZ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa11f 2LA EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa120 2LB EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa121 2LC EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa122 2LD EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa123 2LE EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa124 2LF EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa125 2LG EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa126 2LH EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa127 2LI EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa128 2LJ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa129 2LK EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa12a 2LL EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa12b 2LM EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa12c 2LN EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa12d 2LO EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa12e 2LP EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa12f 2LQ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa130 2LR EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa131 2LS EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa132 2LT EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa133 2LU EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa134 2LV EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa135 2LW EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa136 2LX EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa137 2LY EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa138 2LZ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa139 2MA EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa13a 2MB EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa13b 2MC EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa13c 2MD EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa13d 2ME EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa13e 2MF EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa13f 2MG EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa140 2MH EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa141 2MI EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa142 2MJ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa143 2MK EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa144 2ML EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa145 2MM EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa146 2MN EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa147 2MO EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa148 2MP EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa149 2MQ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa14a 2MR EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa14b 2MS EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa14c 2MT EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa14d 2MU EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa14e 2MV EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa14f 2MW EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa150 2MX EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa151 2MY EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa152 2MZ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa153 2NA EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa154 2NB EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa155 2NC EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa156 2ND EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa157 2NE EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa158 2NF EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa159 2NG EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa15a 2NH EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa15b 2NI EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa15c 2NJ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa15d 2NK EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa15e 2NL EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa15f 2NM EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa160 2NN EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa161 2NO EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa162 2NP EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa163 2NQ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa164 2NR EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa165 2NS EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa166 2NT EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa167 2NU EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa168 2NV EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa169 2NW EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa16a 2NX EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa16b 2NY EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa16c 2NZ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa16d 2OA EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa16e 2OB EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa16f 2OC EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa170 2OD EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa171 2OE EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa172 2OF EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa173 2OG EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa174 2OH EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa175 2OI EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa176 2OJ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa177 2OK EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa178 2OL EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa179 2OM EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa17a 2ON EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa17b 2OO EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa17c 2OP EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa17d 2OQ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa17e 2OR EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa17f 2OS EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa180 2OT EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa181 2OU EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa182 2OV EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa183 2OW EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa184 2OX EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa185 2OY EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa186 2OZ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa187 2PA EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa188 2PB EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa189 2PC EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa18a 2PD EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa18b 2PE EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa18c 2PF EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa18d 2PG EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa18e 2PH EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa18f 2PI EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa190 2PJ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa191 2PK EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa192 2PL EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa193 2PM EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa194 2PN EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa195 2PO EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa196 2PP EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa197 2PQ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa198 2PR EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa199 2PS EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa19a 2PT EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa19b 2PU EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa19c 2PV EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa19d 2PW EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa19e 2PX EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa19f 2PY EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1a0 2PZ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1a1 2QA EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1a2 2QB EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1a3 2QC EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1a4 2QD EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1a5 2QE EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1a6 2QF EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1a7 2QG EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1a8 2QH EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1a9 2QI EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1aa 2QJ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1ab 2QK EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1ac 2QL EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1ad 2QM EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1ae 2QN EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1af 2QO EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1b0 2QP EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1b1 2QQ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1b2 2QR EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1b3 2QS EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1b4 2QT EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1b5 2QU EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1b6 2QV EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1b7 2QW EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1b8 2QX EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1b9 2QY EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1ba 2QZ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1bb 2RA EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1bc 2RB EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1bd 2RC EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1be 2RD EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1bf 2RE EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1c0 2RF EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1c1 2RG EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1c2 2RH EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1c3 2RI EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1c4 2RJ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1c5 2RK EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1c6 2RL EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1c7 2RM EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1c8 2RN EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1c9 2RO EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1ca 2RP EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1cb 2RQ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1cc 2RR EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1cd 2RS EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1ce 2RT EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1cf 2RU EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1d0 2RV EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1d1 2RW EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1d2 2RX EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1d3 2RY EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1d4 2RZ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1d5 2SA EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1d6 2SB EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1d7 2SC EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1d8 2SD EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1d9 2SE EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1da 2SF EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1db 2SG EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1dc 2SH EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1dd 2SI EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1de 2SJ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1df 2SK EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1e0 2SL EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1e1 2SM EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1e2 2SN EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1e3 2SO EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1e4 2SP EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1e5 2SQ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1e6 2SR EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1e7 2SS EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1e8 2ST EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1e9 2SU EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1ea 2SV EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1eb 2SW EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1ec 2SX EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1ed 2SY EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1ee 2SZ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1ef 2TA EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1f0 2TB EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1f1 2TC EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1f2 2TD EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1f3 2TE EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1f4 2TF EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1f5 2TG EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1f6 2TH EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1f7 2TI EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1f8 2TJ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1f9 2TK EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1fa 2TL EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1fb 2TM EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1fc 2TN EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1fd 2TO EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1fe 2TP EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted +0xa1ff 2TQ EMPTY LARGE 5 Xo BGI MONSTER LONG unpaletted diff --git a/src/org/infinity/resource/cre/decoder/tables/notes.txt b/src/org/infinity/resource/cre/decoder/tables/notes.txt new file mode 100644 index 000000000..fc607d06c --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/tables/notes.txt @@ -0,0 +1,73 @@ +Table format +~~~~~~~~~~~~ + +Non-PST: (all games except PST and PST:EE) +- header column: (int/hex, composite) + - contains the numeric animation id (usually in hexadecimal notation) + - may contain an optional mask value (separated from id by an underscore) + - indicates a slot range that uses the same definitions +- RESREF (string) + - the animation prefix +- TYPE (int) + - an index referring to a SpriteDecoder.AnimationType enum +- ELLIPSE (int) + - the selection circle radius +- SPACE (int) + - the personal space in search map units +- BLENDING (int) + - a bitfield: bit0=brightest, bit1=multiply_blend, bit2=light_source +- PALETTE (string) + - resref of a replacement palette + - meaning depends on animation type +- PALETTE2 (string) + - resref of a secondary replacement palette + - meaning depends on animation type +- TRANSLUCENT (boolean) + - whether animation is semi-transparent +- CLOWN (bool) + - whether false color replacement is enabled +- SPLIT (bool) + - whether animations are spread over multiple sub-BAMs + - animation type "effect": specifies "random_render" attribute +- HELMET (bool/int) + - animation types "character", "character_old": whether helmet overlay is drawn + - animation types "monster_layered", "monster_quadrant": specifies "caster" attribute + - animation type "effect": specifies BAM cycle +- WEAPON (bool) + - animation type "character_old": whether weapon overlay is drawn + - animation type "monster_iwd": specifies "weapon_left_handed" attribute + - animation type "monster_layered": specifies "dual_attack" attribute +- HEIGHT (string) + - animation type "character", "character_old": overlay prefix for weapons and helmets + - animation type "monster_layered": specifies "resref_weapon1" attribute + - animation type "effect": if SPLIT=1: specifies a second BAM resref to be used randomly +- HEIGHT_SHIELD (string) + - animation type "character": overlay prefix for shields + - animation type "monster_layered": specifies "resref_weapon2" attribute + + +PST: (PST and PST:EE only) +- header column: (int/hex) + - contains the base part of the numeric animation id (usually in hexadecimal notation) + - high byte value is determined by the animation class + - slots with id >= 0x1000 are handled specially +- RESREF (string) + - combination of prefix (one char), resref (3 chars) and suffix (one char) + - prefix "c" indicates slot range (PST: 0xE000, PSTEE: 0xF000) + - prefix "d" indicates slot range (PST: 0x6000, PSTEE: 0xF000) + - action prefix is determined by the animation class + - special animation slots are added verbatim +- RESREF2 (string) + - contains a secondary BAM resref used by a small number of special animation slots +- TYPE (int) + - an index referring to a SpriteDecoder.AnimationType enum +- ELLIPSE (int) + - the selection circle radius +- SPACE (int) + - the personal space in search map units +- CLOWN (bool) + - whether false color replacement is enabled +- ARMOR (int) + - (currently) unused by NI +- BESTIARY (int) + - (currently) unused by NI diff --git a/src/org/infinity/resource/key/BufferedResourceEntry.java b/src/org/infinity/resource/key/BufferedResourceEntry.java new file mode 100644 index 000000000..3473196ad --- /dev/null +++ b/src/org/infinity/resource/key/BufferedResourceEntry.java @@ -0,0 +1,136 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.key; + +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Locale; +import java.util.Objects; + +import org.infinity.gui.BrowserMenuBar; +import org.infinity.gui.BrowserMenuBar.OverrideMode; +import org.infinity.resource.Profile; +import org.infinity.resource.ResourceFactory; +import org.infinity.util.io.ByteBufferInputStream; +import org.infinity.util.io.StreamUtils; + +/** + * A ResourceEntry based on {@link ByteBuffer} data. + */ +public class BufferedResourceEntry extends ResourceEntry +{ + private final ByteBuffer buffer; + private final String fileName; + + public BufferedResourceEntry(ByteBuffer buffer, String fileName) + { + this.buffer = Objects.requireNonNull(buffer); + this.fileName = Objects.requireNonNull(fileName); + } + + @Override + protected Path getActualPath(boolean ignoreOverride) + { + return Paths.get(Profile.getProperty(Profile.Key.GET_GAME_ROOT_FOLDER), + Profile.getProperty(Profile.Key.GET_GLOBAL_OVERRIDE_NAME), + getResourceName()); + } + + @Override + public long getResourceSize(boolean ignoreOverride) + { + return buffer.limit(); + } + + @Override + public String getExtension() + { + return getResourceName().substring(getResourceName().lastIndexOf('.') + 1).toUpperCase(Locale.ENGLISH); + } + + @Override + public ByteBuffer getResourceBuffer(boolean ignoreOverride) throws Exception + { + return buffer; + } + + @Override + public InputStream getResourceDataAsStream(boolean ignoreOverride) throws Exception + { + return new ByteBufferInputStream(getResourceBuffer()); + } + + @Override + public int[] getResourceInfo(boolean ignoreOverride) throws Exception + { + ByteBuffer buffer = getResourceBuffer(); + String sig = "", ver = ""; + if (getResourceSize() >= 8) { + sig = StreamUtils.readString(buffer, 0, 4); + ver = StreamUtils.readString(buffer, 4, 4); + } + if ("TIS ".equals(sig) && "V1 ".equals(ver)) { + if (getResourceSize() > 16) { + int v1 = buffer.getInt(8); + int v2 = buffer.getInt(12); + return new int[] { v1, v2 }; + } else { + throw new Exception("Unexpected end of file"); + } + } else { + return new int[] { (int)getResourceSize() }; + } + } + + @Override + public String getResourceName() + { + return fileName; + } + + @Override + public String getResourceRef() + { + String fileName = getResourceName(); + int pos = fileName.lastIndexOf('.'); + if (pos >= 0) + fileName = fileName.substring(0, pos); + return fileName; + } + + @Override + public String getTreeFolderName() + { + if (BrowserMenuBar.getInstance() != null) { + final OverrideMode mode = BrowserMenuBar.getInstance().getOverrideMode(); + final Keyfile keyfile = ResourceFactory.getKeyfile(); + + if (keyfile.getExtensionType(getExtension()) != -1) { + if (mode == OverrideMode.InTree) { + return getExtension(); + } else if (mode == OverrideMode.Split && + keyfile.getResourceEntry(getResourceName()) != null) { + return getExtension(); + } + } + } + return Profile.getOverrideFolderName(); + } + + @Override + public ResourceTreeFolder getTreeFolder() + { + return ResourceFactory.getResourceTreeModel().getFolder(getTreeFolderName()); + } + + @Override + public boolean hasOverride() + { + return false; + } + +} From 998aaae150682d815eb4cf80c2516b77c4546669 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sat, 27 Feb 2021 21:24:00 +0100 Subject: [PATCH 019/158] Implement zero-based display of cycle and frame indices in BAM viewer --- src/org/infinity/resource/graphics/BamResource.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/org/infinity/resource/graphics/BamResource.java b/src/org/infinity/resource/graphics/BamResource.java index a971a10f2..e88449eff 100644 --- a/src/org/infinity/resource/graphics/BamResource.java +++ b/src/org/infinity/resource/graphics/BamResource.java @@ -793,13 +793,13 @@ private void showFrame() if (curCycle >= 0) { ((JLabel)buttonControlPanel.getControlByType(CtrlCycleLabel)) - .setText("Cycle: " + (curCycle + 1) + "/" + bamControl.cycleCount()); + .setText("Cycle: " + curCycle + "/" + (bamControl.cycleCount() - 1)); ((JLabel)buttonControlPanel.getControlByType(CtrlFrameLabel)) - .setText("Frame: " + (curFrame + 1) + "/" + bamControl.cycleFrameCount()); + .setText("Frame: " + curFrame + "/" + (bamControl.cycleFrameCount() - 1)); } else { ((JLabel)buttonControlPanel.getControlByType(CtrlCycleLabel)).setText("All frames"); ((JLabel)buttonControlPanel.getControlByType(CtrlFrameLabel)) - .setText("Frame: " + (curFrame + 1) + "/" + decoder.frameCount()); + .setText("Frame: " + curFrame + "/" + (decoder.frameCount() - 1)); } buttonControlPanel.getControlByType(CtrlPrevCycle).setEnabled(curCycle > -1); From 4fa2b764a22c6a50186aa5331747d1ddfbcb7145 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sun, 28 Feb 2021 23:54:31 +0100 Subject: [PATCH 020/158] Fix creature color preparation --- src/org/infinity/datatype/ColorValue.java | 119 +++++++++++------- .../resource/cre/decoder/SpriteUtils.java | 6 +- 2 files changed, 74 insertions(+), 51 deletions(-) diff --git a/src/org/infinity/datatype/ColorValue.java b/src/org/infinity/datatype/ColorValue.java index 179c6e8ba..8c840d80b 100644 --- a/src/org/infinity/datatype/ColorValue.java +++ b/src/org/infinity/datatype/ColorValue.java @@ -376,35 +376,6 @@ private void initEntries(int defaultWidth, int defaultHeight, ResourceEntry colo randomEntry = ResourceFactory.getResourceEntry("RANDCOLR.2DA"); } - // adding color gradients - if (colorsEntry != null) { - BufferedImage image = null; - try { - image = new GraphicsResource(colorsEntry).getImage(); - if (defaultWidth <= 0) { - // auto-calculate color width - defaultWidth = 192 / image.getWidth(); - } - for (int y = 0; y < image.getHeight(); y++) { - BufferedImage range = new BufferedImage(image.getWidth() * defaultWidth, defaultHeight, image.getType()); - Graphics2D g = range.createGraphics(); - try { - g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); -// g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); - g.drawImage(image, 0, 0, range.getWidth(), range.getHeight(), 0, y, image.getWidth(), y+1, null); - g.setColor(Color.BLACK); - g.setStroke(new BasicStroke(1.0f)); - g.drawRect(0, 0, range.getWidth() - 1, range.getHeight() - 1); - } finally { - g.dispose(); - } - colors.add(range); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - // collecting valid random color indices int maxValue = colors.size() - 1; if (randomEntry != null) { @@ -424,32 +395,84 @@ private void initEntries(int defaultWidth, int defaultHeight, ResourceEntry colo } } - // adding random/invalid color placeholder - maxValue = Math.max(maxValue, 255) + 1; - Color invalidColor = new Color(0xe0e0e0); - for (int i = colors.size(); i < maxValue; i++) { - boolean isRandom = randomColors.contains(Integer.valueOf(i)); - BufferedImage range = new BufferedImage(12 * defaultWidth, defaultHeight, BufferedImage.TYPE_INT_RGB); - Graphics2D g = range.createGraphics(); + // scanning range of colors + if (colorsEntry != null) { + BufferedImage image = null; + try { + image = new GraphicsResource(colorsEntry).getImage(); + maxValue = Math.max(maxValue, image.getHeight()); + if (defaultWidth <= 0) { + // auto-calculate color width + defaultWidth = 192 / image.getWidth(); + } + + for (int idx = 0; idx <= maxValue; idx++) { + BufferedImage range; + if (!randomColors.contains(Integer.valueOf(idx)) && idx < image.getHeight()) { + // fixed color + range = getFixedColor(image, idx, defaultWidth, defaultHeight); + } else { + // random color or invalid entry + range = getVirtualColor(idx, defaultWidth, defaultHeight); + } + colors.add(range); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + // Returns an image derived from the specified color range bitmap + private BufferedImage getFixedColor(BufferedImage colorRanges, int index, int width, int height) + { + BufferedImage retVal = null; + + if (colorRanges != null && index >= 0 && index < colorRanges.getHeight()) { + retVal = new BufferedImage(colorRanges.getWidth() * width, height, colorRanges.getType()); + Graphics2D g = retVal.createGraphics(); try { - g.setColor(isRandom ? Color.LIGHT_GRAY : invalidColor); - g.fillRect(0, 0, range.getWidth(), range.getHeight()); - g.setFont(new JLabel().getFont()); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); + g.drawImage(colorRanges, 0, 0, retVal.getWidth(), retVal.getHeight(), 0, index, colorRanges.getWidth(), index+1, null); g.setColor(Color.BLACK); g.setStroke(new BasicStroke(1.0f)); - g.drawRect(0, 0, range.getWidth() - 1, range.getHeight() - 1); - g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP); - String msg = isRandom ? "(Random)" : "(Invalid)"; - FontMetrics fm = g.getFontMetrics(); - Rectangle2D rect = fm.getStringBounds(msg, g); - g.drawString(msg, - (float)(range.getWidth() - rect.getWidth()) / 2.0f, - (float)(range.getHeight() - rect.getY()) / 2.0f); + g.drawRect(0, 0, retVal.getWidth() - 1, retVal.getHeight() - 1); } finally { g.dispose(); } - colors.add(range); } + + return retVal; + } + + // Returns an image describing a random color or invalid color entry + private BufferedImage getVirtualColor(int index, int width, int height) + { + BufferedImage retVal = null; + + Color invalidColor = new Color(0xe0e0e0); + boolean isRandom = randomColors.contains(Integer.valueOf(index)); + retVal = new BufferedImage(12 * width, height, BufferedImage.TYPE_INT_RGB); + Graphics2D g = retVal.createGraphics(); + try { + g.setColor(isRandom ? Color.LIGHT_GRAY : invalidColor); + g.fillRect(0, 0, retVal.getWidth(), retVal.getHeight()); + g.setFont(new JLabel().getFont()); + g.setColor(Color.BLACK); + g.setStroke(new BasicStroke(1.0f)); + g.drawRect(0, 0, retVal.getWidth() - 1, retVal.getHeight() - 1); + g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP); + String msg = isRandom ? "(Random)" : "(Invalid)"; + FontMetrics fm = g.getFontMetrics(); + Rectangle2D rect = fm.getStringBounds(msg, g); + g.drawString(msg, + (float)(retVal.getWidth() - rect.getWidth()) / 2.0f, + (float)(retVal.getHeight() - rect.getY()) / 2.0f); + } finally { + g.dispose(); + } + + return retVal; } } } diff --git a/src/org/infinity/resource/cre/decoder/SpriteUtils.java b/src/org/infinity/resource/cre/decoder/SpriteUtils.java index e29052052..f92bc7913 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteUtils.java +++ b/src/org/infinity/resource/cre/decoder/SpriteUtils.java @@ -1170,9 +1170,9 @@ public static int[] getColorGradient(SpriteDecoder.AnimationType animType, int i } } - int[] retVal = colorGradients.getOrDefault(index, null); - if (retVal == null && allowRandom) { - retVal = getRandomColorGradient(animType, index); + int[] retVal = allowRandom ? getRandomColorGradient(animType, index) : null; + if (retVal == null) { + retVal = colorGradients.getOrDefault(index, null); } return retVal; From f05f2355eec2fd9ba323051dbd831d72f6544ef6 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Mon, 1 Mar 2021 20:28:06 +0100 Subject: [PATCH 021/158] Improve error logs --- .../cre/decoder/internal/ItemInfo.java | 50 ++++++++++--------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java b/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java index b0abcdfc1..078be3a9f 100644 --- a/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java +++ b/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java @@ -95,7 +95,7 @@ private void init() throws IOException, Exception try (final InputStream is = itmEntry.getResourceDataAsStream()) { byte[] sig = new byte[8]; // offset = 0x00 - Misc.requireCondition(is.read(sig) == 8, "Could not read signature field"); + Misc.requireCondition(is.read(sig) == 8, "Could not read signature field: " + itmEntry); final String signature = new String(sig).toUpperCase(Locale.ENGLISH); switch (signature) { case "ITM V1 ": @@ -107,7 +107,7 @@ private void init() throws IOException, Exception } // flags (common) - Misc.requireCondition(is.skip(0x10) == 0x10, "Could not advance in data stream"); + Misc.requireCondition(is.skip(0x10) == 0x10, "Could not advance in data stream: " + itmEntry); // offset = 0x18 this.flags = StreamUtils.readInt(is); @@ -127,29 +127,29 @@ private void init() throws IOException, Exception this.unusableKits = 0; if (!"ITM V1.1".equals(signature)) { int v; - Misc.requireCondition(is.skip(5) == 5, "Could not advance in data stream"); + Misc.requireCondition(is.skip(5) == 5, "Could not advance in data stream: " + itmEntry); // offset = 0x29 - Misc.requireCondition((v = is.read()) != -1, "Could not read kits usability field"); + Misc.requireCondition((v = is.read()) != -1, "Could not read kits usability field: " + itmEntry); this.unusableKits |= (v << 24); - Misc.requireCondition(is.skip(1) == 1, "Could not advance in data stream"); + Misc.requireCondition(is.skip(1) == 1, "Could not advance in data stream: " + itmEntry); // offset = 0x2b - Misc.requireCondition((v = is.read()) != -1, "Could not read kits usability field"); + Misc.requireCondition((v = is.read()) != -1, "Could not read kits usability field: " + itmEntry); this.unusableKits |= (v << 16); - Misc.requireCondition(is.skip(1) == 1, "Could not advance in data stream"); + Misc.requireCondition(is.skip(1) == 1, "Could not advance in data stream: " + itmEntry); // offset = 0x2d - Misc.requireCondition((v = is.read()) != -1, "Could not read kits usability field"); + Misc.requireCondition((v = is.read()) != -1, "Could not read kits usability field: " + itmEntry); this.unusableKits |= (v << 8); - Misc.requireCondition(is.skip(1) == 1, "Could not advance in data stream"); + Misc.requireCondition(is.skip(1) == 1, "Could not advance in data stream: " + itmEntry); // offset = 0x2f - Misc.requireCondition((v = is.read()) != -1, "Could not read kits usability field"); + Misc.requireCondition((v = is.read()) != -1, "Could not read kits usability field: " + itmEntry); this.unusableKits |= v; } else { // to keep stream position synchronized - Misc.requireCondition(is.skip(0xc) == 0xc, "Could not advance in data stream"); + Misc.requireCondition(is.skip(0xc) == 0xc, "Could not advance in data stream: " + itmEntry); } // abilities (common: ofs, num, header) - Misc.requireCondition(is.skip(0x34) == 0x34, "Could not advance in data stream"); + Misc.requireCondition(is.skip(0x34) == 0x34, "Could not advance in data stream: " + itmEntry); // offset = 0x64 int ofsAbil = StreamUtils.readInt(is); // abilities offset int numAbil = StreamUtils.readShort(is); // abilities count @@ -162,25 +162,27 @@ private void init() throws IOException, Exception // reading global color effects (attempt 1) int skip; - if (ofsFx < ofsAbil) { + if (numFx > 0 && ofsFx < ofsAbil) { skip = ofsFx - curOfs; - Misc.requireCondition(is.skip(skip) == skip, "Could not advance in data stream"); + Misc.requireCondition(is.skip(skip) == skip, "Could not advance in data stream: " + itmEntry); curOfs += skip; // offset = [ofsFx] curOfs += readEffects(is, numFx); } // reading ability types - skip = ofsAbil - curOfs; - Misc.requireCondition(skip >= 0 && is.skip(skip) == skip, "Could not advance in data stream"); - curOfs += skip; - // offset = [ofsAbil] - curOfs += readAbilities(is, numAbil); + if (numAbil > 0) { + skip = ofsAbil - curOfs; + Misc.requireCondition(skip >= 0 && is.skip(skip) == skip, "Could not advance in data stream: " + itmEntry); + curOfs += skip; + // offset = [ofsAbil] + curOfs += readAbilities(is, numAbil); + } // reading global color effects (attempt 2) - if (ofsFx >= ofsAbil) { + if (numFx > 0 && ofsFx >= ofsAbil) { skip = ofsFx - curOfs; - Misc.requireCondition(skip >= 0 && is.skip(skip) == skip, "Could not advance in data stream"); + Misc.requireCondition(skip >= 0 && is.skip(skip) == skip, "Could not advance in data stream: " + itmEntry); curOfs += skip; // offset = [ofsFx] curOfs += readEffects(is, numFx); @@ -201,7 +203,7 @@ private int readEffects(InputStream is, int num) throws Exception int param1 = StreamUtils.readInt(is); int param2 = StreamUtils.readInt(is); int timing = is.read(); - Misc.requireCondition(is.skip(0x23) == 0x23, "Could not advance in data stream"); + Misc.requireCondition(is.skip(0x23) == 0x23, "Could not advance in data stream: " + itmEntry); if (target == 1 && timing ==2) { // self target; on equip SegmentDef.SpriteType type = null; @@ -229,7 +231,7 @@ private int readEffects(InputStream is, int num) throws Exception } } else { // sync stream offset - Misc.requireCondition(is.skip(0x2e) == 0x2e, "Could not advance in data stream"); + Misc.requireCondition(is.skip(0x2e) == 0x2e, "Could not advance in data stream: " + itmEntry); } retVal += 0x30; num--; @@ -244,7 +246,7 @@ private int readAbilities(InputStream is, int num) throws Exception while (num > 0) { this.abilities[this.abilities.length - num] = StreamUtils.readShort(is); retVal += 2; - Misc.requireCondition(is.skip(0x36) == 0x36, "Could not advance in data stream"); + Misc.requireCondition(is.skip(0x36) == 0x36, "Could not advance in data stream: " + itmEntry); retVal += 0x36; num--; } From e4aaa4a7a75f6fd1ce406cee041a8b106d22448b Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Mon, 1 Mar 2021 20:35:50 +0100 Subject: [PATCH 022/158] CRE decoder: Improve overlay handling --- .../infinity/resource/cre/decoder/ArmoredBaseDecoder.java | 7 ++----- .../infinity/resource/cre/decoder/CharacterOldDecoder.java | 6 +++--- .../resource/cre/decoder/MonsterLayeredDecoder.java | 3 +-- .../resource/cre/decoder/MonsterLayeredSpellDecoder.java | 3 +-- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/org/infinity/resource/cre/decoder/ArmoredBaseDecoder.java b/src/org/infinity/resource/cre/decoder/ArmoredBaseDecoder.java index fda3411c7..c2e7c0583 100644 --- a/src/org/infinity/resource/cre/decoder/ArmoredBaseDecoder.java +++ b/src/org/infinity/resource/cre/decoder/ArmoredBaseDecoder.java @@ -18,6 +18,7 @@ import org.infinity.resource.itm.ItmResource; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; +import org.infinity.util.Misc; /** * Common base for processing creature animations with different armor levels. @@ -96,11 +97,7 @@ public int getArmorCode() ItmResource itm = SpriteUtils.getEquippedArmor(getCreResource()); if (itm != null) { String code = ((IsTextual)itm.getAttribute(ItmResource.ITM_EQUIPPED_APPEARANCE)).getText(); - try { - retVal = Math.max(1, Math.min(getMaxArmorCode(), Integer.parseInt(code.substring(0, 1)))); - } catch (Exception e) { - e.printStackTrace(); - } + retVal = Math.max(1, Math.min(getMaxArmorCode(), Misc.toNumber(code.substring(0, 1), 1))); } return retVal; } diff --git a/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java b/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java index dd6e054d0..babe0ee95 100644 --- a/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java +++ b/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java @@ -300,7 +300,7 @@ protected SeqDef getSequenceDefinition(Sequence seq) if (isHelmetEquipped()) { String prefix = getHelmetHeightCode(); if (!prefix.isEmpty()) { - String code = SpriteUtils.getItemAppearance(SpriteUtils.getEquippedHelmet(getCreResource())); + String code = SpriteUtils.getItemAppearance(SpriteUtils.getEquippedHelmet(getCreResource())).trim(); if (code.length() == 2) { resrefList.add(Couple.with(prefix + code, SegmentDef.SpriteType.HELMET)); } @@ -312,7 +312,7 @@ protected SeqDef getSequenceDefinition(Sequence seq) String prefix = getHeightCode(); if (!prefix.isEmpty()) { ItmResource itm = SpriteUtils.getEquippedShield(getCreResource()); - String code = SpriteUtils.getItemAppearance(itm); + String code = SpriteUtils.getItemAppearance(itm).trim(); if (!code.isEmpty()) { resrefList.add(Couple.with(prefix + code, SegmentDef.SpriteType.SHIELD)); } @@ -321,7 +321,7 @@ protected SeqDef getSequenceDefinition(Sequence seq) // adding weapon overlay prefix = getHeightCode(); if (!prefix.isEmpty()) { - String code = SpriteUtils.getItemAppearance(itmWeapon); + String code = SpriteUtils.getItemAppearance(itmWeapon).trim(); if (code.length() == 2) { if (ResourceFactory.resourceExists(prefix + code + suffix + ".BAM")) { resrefList.add(Couple.with(prefix + code, SegmentDef.SpriteType.WEAPON)); diff --git a/src/org/infinity/resource/cre/decoder/MonsterLayeredDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterLayeredDecoder.java index 4337fc92b..dbbda5667 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterLayeredDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterLayeredDecoder.java @@ -175,11 +175,10 @@ protected SeqDef getSequenceDefinition(Sequence seq) // defining weapon overlay for current creature ItmResource itm = SpriteUtils.getEquippedWeapon(getCreResource()); if (itm != null) { - String weapon = SpriteUtils.getItemAppearance(itm); + String weapon = SpriteUtils.getItemAppearance(itm).trim(); if (!weapon.isEmpty()) { weapon = weapon.substring(0, 1); } - weapon = weapon.trim(); if (!getWeapon1Overlay().startsWith(weapon) && !getWeapon2Overlay().startsWith(weapon)) { weapon = ""; } diff --git a/src/org/infinity/resource/cre/decoder/MonsterLayeredSpellDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterLayeredSpellDecoder.java index 9b20122a2..338745d8b 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterLayeredSpellDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterLayeredSpellDecoder.java @@ -206,11 +206,10 @@ protected SeqDef getSequenceDefinition(Sequence seq) // defining weapon overlay for current creature ItmResource itm = SpriteUtils.getEquippedWeapon(getCreResource()); if (itm != null) { - String weapon = SpriteUtils.getItemAppearance(itm); + String weapon = SpriteUtils.getItemAppearance(itm).trim(); if (!weapon.isEmpty()) { weapon = weapon.substring(0, 1); } - weapon = weapon.trim(); if (!getWeapon1Overlay().startsWith(weapon) && !getWeapon2Overlay().startsWith(weapon)) { weapon = ""; } From 8ac6017ad093b6fb589ce1ab8e3095489dbbe9fc Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Mon, 1 Mar 2021 20:39:29 +0100 Subject: [PATCH 023/158] CRE decoder: add helper methods; add custom blending composite; various minor improvements --- .../resource/cre/decoder/SpriteDecoder.java | 131 +++++-- .../cre/decoder/internal/SegmentDef.java | 2 +- .../resource/graphics/BlendingComposite.java | 350 ++++++++++++++++++ 3 files changed, 456 insertions(+), 27 deletions(-) create mode 100644 src/org/infinity/resource/graphics/BlendingComposite.java diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index 0d51231e7..573d278a7 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -7,6 +7,7 @@ import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.Color; +import java.awt.Composite; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Point; @@ -50,6 +51,7 @@ import org.infinity.resource.graphics.ColorConvert; import org.infinity.resource.graphics.PseudoBamDecoder; import org.infinity.resource.graphics.BamV1Decoder.BamV1Control; +import org.infinity.resource.graphics.BlendingComposite; import org.infinity.resource.key.BufferedResourceEntry; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IdsMap; @@ -877,10 +879,11 @@ public String getAnimationSectionName() /** Recreates the creature animation based on the current creature resource. */ public void reset() throws Exception { + Direction[] directions = getDirectionMap().keySet().toArray(new Direction[getDirectionMap().keySet().size()]); discard(); // recreating current sequence if (getCurrentSequence() != Sequence.NONE) { - createSequence(getCurrentSequence()); + createSequence(getCurrentSequence(), directions); } } @@ -899,6 +902,20 @@ protected void discard() * @return whether the sequence was successfully loaded. */ public boolean loadSequence(Sequence seq) throws Exception + { + return loadSequence(seq, null); + } + + /** + * Loads selected directions of the specified sequence if available. Discards the currently active sequence. + * Call {@code reset()} instead to enforce reloading the same sequence with different + * creature attributes. + * @param seq the animation sequence to load. Specifying {@code Sequence.None} only discards the current sequence. + * @param directions array with directions allowed to be created. Specify {@code null} to create animations + * for all directions. + * @return whether the sequence was successfully loaded. + */ + public boolean loadSequence(Sequence seq, Direction[] directions) throws Exception { boolean retVal = true; @@ -907,7 +924,7 @@ public boolean loadSequence(Sequence seq) throws Exception discard(); try { - createSequence(seq); + createSequence(seq, directions); currentSequence = seq; } catch (NullPointerException e) { retVal = (seq != Sequence.NONE); @@ -929,6 +946,48 @@ public Sequence getCurrentSequence() /** Returns whether the specified animation sequence is available for the current creature animation. */ public abstract boolean isSequenceAvailable(Sequence seq); + /** + * Returns the closest available direction to the specified direction. + * @param dir the requested direction + * @return an available {@code Direction} that is closest to the specified direction. + * Returns {@code null} if no direction is available. + */ + public Direction getExistingDirection(Direction dir) + { + Direction retVal = null; + + if (dir == null) { + return retVal; + } + if (getDirectionMap().containsKey(dir)) { + return dir; + } + SeqDef sd = getSequenceDefinition(getCurrentSequence()); + if (sd == null || sd.isEmpty()) { + return retVal; + } + + int dirIdx = dir.getValue(); + int dirLen = Direction.values().length; + int maxRange = dirLen / 2; + for (int range = 1; range <= maxRange; range++) { + int dist = (dirIdx + range + dirLen) % dirLen; + Direction distDir = Direction.from(dist); + if (getDirectionMap().containsKey(distDir)) { + retVal = distDir; + break; + } + dist = (dirIdx - range + dirLen) % dirLen; + distDir = Direction.from(dist); + if (getDirectionMap().containsKey(distDir)) { + retVal = distDir; + break; + } + } + + return retVal; + } + /** Provides access to the {@link CreatureInfo} instance associated with the sprite decoder. */ public CreatureInfo getCreatureInfo() { @@ -1316,30 +1375,23 @@ public void resetAnimationChanged() animationChanged = false; } - // TODO: uncommment when Composite is implemented /** * Returns the preferred compositor for rendering the sprite on the target surface. */ -// public Composite getBlendingComposite() -// { -// Composite retVal; -// int blending = ((isBrightest() ? 1 : 0) << 0) | -// ((isMultiplyBlend() ? 1 : 0) << 1); -// switch (blending) { -// case 1: // brightest -// retVal = BlendingComposite.Brightest; -// break; -// case 2: // multiply -// retVal = BlendingComposite.Multiply; -// break; -// case 3: // brightest + multiply -// retVal = BlendingComposite.BrightestMultiply; -// break; -// default: -// retVal = BlendingComposite.SrcOver; -// } -// return retVal; -// } + public Composite getComposite() + { + int blending = ((isBrightest() ? 1 : 0) << 0) | ((isMultiplyBlend() ? 1 : 0) << 1); + switch (blending) { + case 1: // brightest + return BlendingComposite.Brightest; + case 2: // multiply + return BlendingComposite.Multiply; + case 3: // brightest + multiply + return BlendingComposite.BrightestMultiply; + default: + return AlphaComposite.SrcOver; + } + } /** Creates the BAM structure for the creature animation. */ protected abstract void init() throws Exception; @@ -1468,16 +1520,33 @@ protected int addDirection(Direction dir, int cycleIndex) /** * Loads the specified animation sequence into the SpriteDecoder. - * @param seq The sequence to load. + * @param seq the sequence to load. * @throws NullPointerException if specified sequence is not available. */ protected void createSequence(Sequence seq) throws Exception { SeqDef sd = Objects.requireNonNull(getSequenceDefinition(seq), "Sequence not available: " + (seq != null ? seq : "(null)")); - createAnimation(sd, FN_BEFORE_SRC_BAM, FN_BEFORE_SRC_FRAME, FN_AFTER_DST_FRAME); + createAnimation(sd, null, FN_BEFORE_SRC_BAM, FN_BEFORE_SRC_FRAME, FN_AFTER_DST_FRAME); + } + + /** + * Loads the specified animation sequence into the SpriteDecoder. + * Only directions listed in the given {@code Direction} array will be considered. + * @param seq the sequence to load. + * @param directions an array of {@code Direction} values. Only directions listed in the array + * are considered by the creation process. Specify {@code null} to allow all directions. + * @throws NullPointerException if specified sequence is not available. + */ + protected void createSequence(Sequence seq, Direction[] directions) throws Exception + { + SeqDef sd = Objects.requireNonNull(getSequenceDefinition(seq), "Sequence not available: " + (seq != null ? seq : "(null)")); + if (directions == null) { + directions = Direction.values(); + } + createAnimation(sd, Arrays.asList(directions), FN_BEFORE_SRC_BAM, FN_BEFORE_SRC_FRAME, FN_AFTER_DST_FRAME); } - protected void createAnimation(SeqDef definition, + protected void createAnimation(SeqDef definition, List directions, BiConsumer beforeSrcBam, Function beforeSrcImage, BiConsumer afterDstFrame) @@ -1487,7 +1556,17 @@ protected void createAnimation(SeqDef definition, ResourceEntry entry = null; definition = Objects.requireNonNull(definition, "Sequence definition cannot be null"); + if (directions == null) { + directions = Arrays.asList(Direction.values()); + } + if (directions.isEmpty()) { + return; + } + for (final DirDef dd : definition.getDirections()) { + if (!directions.contains(dd.getDirection())) { + continue; + } CycleDef cd = dd.getCycle(); int cycleIndex = dstCtrl.cycleAdd(); addDirection(dd.getDirection(), cycleIndex); diff --git a/src/org/infinity/resource/cre/decoder/internal/SegmentDef.java b/src/org/infinity/resource/cre/decoder/internal/SegmentDef.java index e8cf998f9..eb80dbec7 100644 --- a/src/org/infinity/resource/cre/decoder/internal/SegmentDef.java +++ b/src/org/infinity/resource/cre/decoder/internal/SegmentDef.java @@ -58,7 +58,7 @@ public enum Behavior { /** The same as {@link #CUT} but runs from end to start. */ REVERSE_CUT; - private static EnumMap opposites = new EnumMap(Behavior.class) {{ + private static final EnumMap opposites = new EnumMap(Behavior.class) {{ put(REPEAT, REVERSE_REPEAT); put(SINGLE, REVERSE_SINGLE); put(FREEZE, REVERSE_FREEZE); diff --git a/src/org/infinity/resource/graphics/BlendingComposite.java b/src/org/infinity/resource/graphics/BlendingComposite.java new file mode 100644 index 000000000..cd486702a --- /dev/null +++ b/src/org/infinity/resource/graphics/BlendingComposite.java @@ -0,0 +1,350 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.graphics; + +import java.awt.Composite; +import java.awt.CompositeContext; +import java.awt.RenderingHints; +import java.awt.image.ColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.DirectColorModel; +import java.awt.image.Raster; +import java.awt.image.RasterFormatException; +import java.awt.image.WritableRaster; +import java.util.Objects; + +/** + * This composite class implements blending modes that simulates the blending modes + * supported by the IE games. + * Note: Blending is done in software and results in a noticeably performance penalty when used + * in combination with hardware-accelerated image data. + */ +public class BlendingComposite implements Composite +{ + public enum BlendingMode { + /** + * This mode applied the OpenGL blending operation: + * src={@code GL_ONE_MINUS_DST_COLOR}, dst={@code GL_ONE}. + */ + BRIGHTEST { + @Override + void blend(int[] src, int[] dst, int[] result) { + result[0] = Math.min(255, (src[0] * (256 - dst[0]) + (dst[0] << 8)) >>> 8); + result[1] = Math.min(255, (src[1] * (256 - dst[1]) + (dst[1] << 8)) >>> 8); + result[2] = Math.min(255, (src[2] * (256 - dst[2]) + (dst[2] << 8)) >>> 8); + result[3] = Math.min(255, (src[3] * (256 - dst[3]) + (dst[3] << 8)) >>> 8); +// result[3] = Math.min(255, src[3] + dst[3] - (src[3] * dst[3]) / 255); + } + }, + + /** + * This mode applied the OpenGL blending operation: + * src={@code GL_DST_COLOR}, dst={@code GL_ONE}. + */ + MULTIPLY { + void blend(int[] src, int[] dst, int[] result) { + result[0] = Math.min(255, ((src[0] * dst[0]) + (dst[0] << 8)) >>> 8); + result[1] = Math.min(255, ((src[1] * dst[1]) + (dst[1] << 8)) >>> 8); + result[2] = Math.min(255, ((src[2] * dst[2]) + (dst[2] << 8)) >>> 8); + result[3] = Math.min(255, ((src[3] * dst[3]) + (dst[3] << 8)) >>> 8); +// result[3] = Math.min(255, src[3] + dst[3] - (src[3] * dst[3]) / 255); + } + }, + + /** + * This mode applied the OpenGL blending operation: + * src={@code GL_SRC_COLOR}, dst={@code GL_ONE}. + */ + BRIGHTEST_MULTIPLY { + void blend(int[] src, int[] dst, int[] result) { + result[0] = Math.min(255, ((src[0] * src[0]) + (dst[0] << 8)) >>> 8); + result[1] = Math.min(255, ((src[1] * src[1]) + (dst[1] << 8)) >>> 8); + result[2] = Math.min(255, ((src[2] * src[2]) + (dst[2] << 8)) >>> 8); + result[3] = Math.min(255, ((src[3] * src[3]) + (dst[3] << 8)) >>> 8); +// result[3] = Math.min(255, src[3] + dst[3] - (src[3] * dst[3]) / 255); + } + }; + + /** + * Blends input colors {@code src} and {@code dst}, and writes the result to {@code result}. + * @param src the source pixel (format: RGBA where R=0, ... A=3; all values in range [0, 255]) + * @param dst the destination pixel + * @param result the blended pixel + */ + abstract void blend(int[] src, int[] dst, int[] result); + } + + public static final BlendingComposite Brightest = new BlendingComposite(BlendingMode.BRIGHTEST); + public static final BlendingComposite Multiply = new BlendingComposite(BlendingMode.MULTIPLY); + public static final BlendingComposite BrightestMultiply = new BlendingComposite(BlendingMode.BRIGHTEST_MULTIPLY); + + private final float alpha; + private final BlendingMode mode; + + /** + * + * @param modes + */ + public BlendingComposite(BlendingMode mode) + { + this(1.0f, mode); + } + + /** + * + * @param alpha + * @param modes + */ + public BlendingComposite(float alpha, BlendingMode mode) + { + // filtering out null items + this.mode = Objects.requireNonNull(mode, "Blending mode cannot be null"); + + if (alpha < 0.0f || alpha > 1.0f) { + throw new IllegalArgumentException("alpha value must be between 0.0 and 1.0"); + } + + this.alpha = alpha; + } + + /** + * + * @param modes + * @return + */ + public static BlendingComposite getInstance(BlendingMode mode) + { + return new BlendingComposite(1.0f, mode); + } + + /** + * + * @param alpha + * @param modes + * @return + */ + public static BlendingComposite getInstance(float alpha, BlendingMode mode) + { + return new BlendingComposite(alpha, mode); + } + + /** + * + * @param modes + * @return + */ + public BlendingComposite derive(BlendingMode mode) + { + return (mode == this.mode) ? this : new BlendingComposite(this.alpha, mode); + } + + /** + * + * @param alpha + * @return + */ + public BlendingComposite derive(float alpha) + { + return (this.alpha == alpha) ? this : new BlendingComposite(this.alpha, this.mode); + } + + /** + * + * @return + */ + public float getAlpha() + { + return alpha; + } + + /** + * + * @return + */ + public BlendingMode getMode() + { + return this.mode; + } + + @Override + public int hashCode() + { + return Float.floatToIntBits(alpha) * 31 + mode.hashCode(); + } + + @Override + public boolean equals(Object obj) + { + if (obj == this) { + return true; + } + if (!(obj instanceof BlendingComposite)) { + return false; + } + BlendingComposite bc = (BlendingComposite)obj; + return alpha == bc.alpha && this.mode.equals(bc.mode); + } + + private static boolean isColorModelRGB(ColorModel cm) + { + if (cm instanceof DirectColorModel && cm.getTransferType() == DataBuffer.TYPE_INT) { + DirectColorModel dcm = (DirectColorModel)cm; + return dcm.getRedMask() == 0x00ff0000 && + dcm.getGreenMask() == 0x0000ff00 && + dcm.getBlueMask() == 0x000000ff && + (dcm.getNumColorComponents() == 3 || dcm.getAlphaMask() == 0xff000000); + } + return false; + } + + private static boolean isColorModelBGR(ColorModel cm) + { + if (cm instanceof DirectColorModel && cm.getTransferType() == DataBuffer.TYPE_INT) { + DirectColorModel dcm = (DirectColorModel)cm; + return dcm.getRedMask() == 0x000000ff && + dcm.getGreenMask() == 0x0000ff00 && + dcm.getBlueMask() == 0x00ff0000 && + (dcm.getNumColorComponents() == 3 || dcm.getAlphaMask() == 0xff000000); + } + return false; + } + + @Override + public CompositeContext createContext(ColorModel srcColorModel, ColorModel dstColorModel, RenderingHints hints) + { + Objects.requireNonNull(srcColorModel); + Objects.requireNonNull(dstColorModel); + + if (isColorModelRGB(srcColorModel) && isColorModelRGB(dstColorModel)) { + return new BlendingRGBContext(this); + } else if (isColorModelBGR(srcColorModel) && isColorModelBGR(dstColorModel)) { + return new BlendingBGRContext(this); + } + + throw new RasterFormatException("Incompatible color models:\n " + srcColorModel + "\n " + dstColorModel); + } + +//-------------------------- INNER CLASSES -------------------------- + + private static abstract class BlendingContext implements CompositeContext + { + protected final BlendingComposite composite; + + protected BlendingContext(BlendingComposite c) + { + this.composite = Objects.requireNonNull(c); + } + + @Override + public void dispose() { } + } + + private static class BlendingRGBContext extends BlendingContext + { + public BlendingRGBContext(BlendingComposite c) + { + super(c); + } + + @Override + public void compose(Raster src, Raster dstIn, WritableRaster dstOut) + { + int width = Math.min(src.getWidth(), dstIn.getWidth()); + int height = Math.min(src.getHeight(), dstIn.getHeight()); + + int alpha = (int)(composite.getAlpha() * 256.0f); + + int[] result = new int[4]; + int[] srcPixel = new int[4]; + int[] dstPixel = new int[4]; + int[] srcPixels = new int[width]; + int[] dstPixels = new int[width]; + + for (int y = 0; y < height; y++) { + src.getDataElements(0, y, width, 1, srcPixels); + dstIn.getDataElements(0, y, width, 1, dstPixels); + for (int x = 0; x < width; x++) { + // transforming pixels: INT_ARGB -> array [R, G, B, A] + int pixel = srcPixels[x]; + srcPixel[0] = (pixel >> 16) & 0xFF; + srcPixel[1] = (pixel >> 8) & 0xFF; + srcPixel[2] = (pixel ) & 0xFF; + srcPixel[3] = (pixel >> 24) & 0xFF; + + // transforming pixels: INT_ARGB -> array [R, G, B, A] + pixel = dstPixels[x]; + dstPixel[0] = (pixel >> 16) & 0xFF; + dstPixel[1] = (pixel >> 8) & 0xFF; + dstPixel[2] = (pixel ) & 0xFF; + dstPixel[3] = (pixel >> 24) & 0xFF; + + // reusing results from previous blending operation as source + composite.mode.blend(srcPixel, dstPixel, result); + + // mixing results and applying global alpha + dstPixels[x] = ((dstPixel[3] + (result[3] - ((dstPixel[3] * alpha) >> 8))) & 0xFF) << 24 | + ((dstPixel[0] + (result[0] - ((dstPixel[0] * alpha) >> 8))) & 0xFF) << 16 | + ((dstPixel[1] + (result[1] - ((dstPixel[1] * alpha) >> 8))) & 0xFF) << 8 | + (dstPixel[2] + (result[2] - ((dstPixel[2] * alpha) >> 8))) & 0xFF; + } + dstOut.setDataElements(0, y, width, 1, dstPixels); + } + } + } + + private static class BlendingBGRContext extends BlendingContext + { + public BlendingBGRContext(BlendingComposite c) + { + super(c); + } + + @Override + public void compose(Raster src, Raster dstIn, WritableRaster dstOut) + { + int width = Math.min(src.getWidth(), dstIn.getWidth()); + int height = Math.min(src.getHeight(), dstIn.getHeight()); + + int alpha = (int)(composite.getAlpha() * 256.0f); + + int[] result = new int[4]; + int[] srcPixel = new int[4]; + int[] dstPixel = new int[4]; + int[] srcPixels = new int[width]; + int[] dstPixels = new int[width]; + + for (int y = 0; y < height; y++) { + src.getDataElements(0, y, width, 1, srcPixels); + dstIn.getDataElements(0, y, width, 1, dstPixels); + for (int x = 0; x < width; x++) { + // transforming pixels: INT_ABGR -> array [R, G, B, A] + int pixel = srcPixels[x]; + srcPixel[0] = (pixel ) & 0xFF; + srcPixel[1] = (pixel >> 8) & 0xFF; + srcPixel[2] = (pixel >> 16) & 0xFF; + srcPixel[3] = (pixel >> 24) & 0xFF; + + // transforming pixels: INT_ABGR -> array [R, G, B, A] + pixel = dstPixels[x]; + dstPixel[0] = (pixel ) & 0xFF; + dstPixel[1] = (pixel >> 8) & 0xFF; + dstPixel[2] = (pixel >> 16) & 0xFF; + dstPixel[3] = (pixel >> 24) & 0xFF; + + // reusing results from previous blending operation as source + composite.mode.blend(srcPixel, dstPixel, result); + + // mixing results and applying global alpha + dstPixels[x] = ((dstPixel[3] + (result[3] - ((dstPixel[3] * alpha) >> 8))) & 0xFF) << 24 | + ((dstPixel[0] + (result[0] - ((dstPixel[0] * alpha) >> 8))) & 0xFF) << 16 | + ((dstPixel[1] + (result[1] - ((dstPixel[1] * alpha) >> 8))) & 0xFF) << 8 | + (dstPixel[2] + (result[2] - ((dstPixel[2] * alpha) >> 8))) & 0xFF; + } + dstOut.setDataElements(0, y, width, 1, dstPixels); + } + } + } + +} From 61250db2e53677797aac59465c52b4236935cf1c Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Mon, 1 Mar 2021 20:40:45 +0100 Subject: [PATCH 024/158] Add composite support to RenderCanvas component --- src/org/infinity/gui/RenderCanvas.java | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/org/infinity/gui/RenderCanvas.java b/src/org/infinity/gui/RenderCanvas.java index 3185d8950..205cc0239 100644 --- a/src/org/infinity/gui/RenderCanvas.java +++ b/src/org/infinity/gui/RenderCanvas.java @@ -4,6 +4,8 @@ package org.infinity.gui; +import java.awt.AlphaComposite; +import java.awt.Composite; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; @@ -29,6 +31,7 @@ public class RenderCanvas extends JComponent implements SwingConstants private boolean isScaling, isAutoScale; private int scaledWidth, scaledHeight; private int verticalAlignment, horizontalAlignment; + private Composite composite; public RenderCanvas() { @@ -62,6 +65,7 @@ public RenderCanvas(Image image, boolean scaled, Object interpolationType, this.verticalAlignment = this.horizontalAlignment = CENTER; setInterpolationType(interpolationType); setScalingEnabled(scaled); + setComposite(null); setHorizontalAlignment(horizontalAlign); setVerticalAlignment(verticalAlign); setImage(image); @@ -294,6 +298,23 @@ public void setInterpolationType(Object interpolationType) } } + /** + * Returns the {@link Composite} object used to draw the image on the canvas. + */ + public Composite getComposite() + { + return composite; + } + + /** + * Sets the {@link Composite} object that is used to draw the image on the canvas. + * @param c the {@code Composite} object. Specify {@code null} to use a default composite object. + */ + public void setComposite(Composite c) + { + composite = (c != null) ? c : AlphaComposite.SrcOver; + } + protected void update() { @@ -345,6 +366,8 @@ protected void paintCanvas(Graphics g) { if (currentImage != null && currentImage.getWidth(null) > 0 && currentImage.getHeight(null) > 0) { Graphics2D g2 = (Graphics2D)g; + Composite oldComposite = g2.getComposite(); + g2.setComposite(getComposite()); Rectangle rect = getCanvasSize(); if (isScaling) { g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, interpolationType); @@ -352,6 +375,9 @@ protected void paintCanvas(Graphics g) } else { g2.drawImage(currentImage, rect.x, rect.y, null); } + if (oldComposite != null) { + g2.setComposite(oldComposite); + } } } From 46a7b3f99777d22849b468a5604991af13f715fa Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Mon, 1 Mar 2021 20:56:57 +0100 Subject: [PATCH 025/158] Area Viewer: add support for rendering real creature animations --- .../gui/layeritem/AnimatedLayerItem.java | 17 + .../are/viewer/AbstractAnimationProvider.java | 123 +++++++ .../are/viewer/ActorAnimationProvider.java | 335 ++++++++++++++++++ .../resource/are/viewer/AreaViewer.java | 111 +++++- .../viewer/BackgroundAnimationProvider.java | 102 +----- .../resource/are/viewer/BasicLayer.java | 7 +- .../resource/are/viewer/LayerActor.java | 270 ++++++++++++++ .../resource/are/viewer/LayerAnimation.java | 21 +- .../resource/are/viewer/LayerManager.java | 79 ++++- .../resource/are/viewer/LayerObjectActor.java | 251 ++++++++++++- .../are/viewer/LayerObjectAnimation.java | 10 +- .../are/viewer/LayerObjectAreActor.java | 65 ++-- .../are/viewer/LayerObjectIniActor.java | 137 +++++-- .../resource/are/viewer/Settings.java | 62 +++- .../resource/are/viewer/SettingsDialog.java | 71 +++- .../are/viewer/SharedResourceCache.java | 18 +- .../resource/are/viewer/TilesetRenderer.java | 14 +- .../resource/are/viewer/ViewerConstants.java | 8 +- 18 files changed, 1480 insertions(+), 221 deletions(-) create mode 100644 src/org/infinity/resource/are/viewer/AbstractAnimationProvider.java create mode 100644 src/org/infinity/resource/are/viewer/ActorAnimationProvider.java diff --git a/src/org/infinity/gui/layeritem/AnimatedLayerItem.java b/src/org/infinity/gui/layeritem/AnimatedLayerItem.java index 469b1479f..7bfd1c737 100644 --- a/src/org/infinity/gui/layeritem/AnimatedLayerItem.java +++ b/src/org/infinity/gui/layeritem/AnimatedLayerItem.java @@ -8,6 +8,7 @@ import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; +import java.awt.Composite; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Point; @@ -177,6 +178,22 @@ public void setForcedInterpolation(boolean set) } } + /** + * Returns the {@link Composite} object assigned to the canvas. + */ + public Composite getComposite() + { + return rcCanvas.getComposite(); + } + + /** + * Sets the {@link Composite} object for the canvas. + */ + public void setComposite(Composite comp) + { + rcCanvas.setComposite(comp); + } + /** * Returns whether the animation will automatically restart after playing the last frame. * (Note: Merely returns the value provided by the attached BasicAnimationProvider object.) diff --git a/src/org/infinity/resource/are/viewer/AbstractAnimationProvider.java b/src/org/infinity/resource/are/viewer/AbstractAnimationProvider.java new file mode 100644 index 000000000..2cbf19d22 --- /dev/null +++ b/src/org/infinity/resource/are/viewer/AbstractAnimationProvider.java @@ -0,0 +1,123 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.are.viewer; + +import java.awt.Image; +import java.awt.image.BufferedImage; + +import org.infinity.gui.layeritem.BasicAnimationProvider; + +/** + * Provides base functionality for rendering animations. + */ +public abstract class AbstractAnimationProvider implements BasicAnimationProvider +{ + private BufferedImage image, working; + private boolean isActive, isActiveIgnored; + + protected AbstractAnimationProvider() + { + this.image = null; + this.working = null; + this.isActive = false; + } + + /** Returns the active/visibility state of the animation. */ + public boolean isActive() + { + return isActive; + } + + /** Specify whether to actually display the animation on screen. */ + public void setActive(boolean set) + { + if (set != isActive) { + isActive = set; + updateGraphics(); + } + } + + /** + * Returns whether to ignore the activation state of the animation. + */ + public boolean isActiveIgnored() + { + return isActiveIgnored; + } + + /** + * Specify whether to ignore the activation state of the animation and display it regardless. + */ + public void setActiveIgnored(boolean set) + { + if (set != isActiveIgnored) { + isActiveIgnored = set; + updateGraphics(); + } + } + + @Override + public Image getImage() + { + return image; + } + + protected void setImage(Image img) + { + if (img instanceof BufferedImage) { + this.image = (BufferedImage)img; + } + } + + @Override + public boolean isLooping() + { + return true; + } + + protected BufferedImage getWorkingImage() + { + return working; + } + + protected void setWorkingImage(BufferedImage img) + { + this.working = img; + } + + protected abstract void updateGraphics(); + + + // Applies lightning conditions to all pixels + protected void applyLighting(int[] buffer, int cw, int ch, int fw, int fh, int lighting) + { + if (buffer != null && cw > 0 && ch > 0 && fw > 0 && fh > 0) { + int maxOfs = fh*cw; + if (buffer.length >= maxOfs) { + if (lighting < ViewerConstants.LIGHTING_DAY) lighting = ViewerConstants.LIGHTING_DAY; + else if (lighting > ViewerConstants.LIGHTING_NIGHT) lighting = ViewerConstants.LIGHTING_NIGHT; + int ofs = 0; + while (ofs < maxOfs) { + for (int x = 0; x < fw; x++) { + int pixel = buffer[ofs+x]; + if ((pixel & 0xff000000) != 0) { + int r = (pixel >>> 16) & 0xff; + int g = (pixel >>> 8) & 0xff; + int b = pixel & 0xff; + r = (r*TilesetRenderer.LightingAdjustment[lighting][0]) >>> TilesetRenderer.LightingAdjustmentShift; + g = (g*TilesetRenderer.LightingAdjustment[lighting][1]) >>> TilesetRenderer.LightingAdjustmentShift; + b = (b*TilesetRenderer.LightingAdjustment[lighting][2]) >>> TilesetRenderer.LightingAdjustmentShift; + if (r > 255) r = 255; + if (g > 255) g = 255; + if (b > 255) b = 255; + buffer[ofs+x] = (pixel & 0xff000000) | (r << 16) | (g << 8) | b; + } + } + ofs += cw; + } + } + } + } +} diff --git a/src/org/infinity/resource/are/viewer/ActorAnimationProvider.java b/src/org/infinity/resource/are/viewer/ActorAnimationProvider.java new file mode 100644 index 000000000..bd522ceaa --- /dev/null +++ b/src/org/infinity/resource/are/viewer/ActorAnimationProvider.java @@ -0,0 +1,335 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.are.viewer; + +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; +import java.util.Arrays; +import java.util.Objects; + +import org.infinity.resource.cre.decoder.SpriteDecoder; +import org.infinity.resource.graphics.ColorConvert; +import org.infinity.resource.graphics.PseudoBamDecoder.PseudoBamControl; + +/** + * Implements functionality for properly displaying actor sprites. + */ +public class ActorAnimationProvider extends AbstractAnimationProvider +{ + private static final Color TransparentColor = new Color(0, true); + + private SpriteDecoder decoder; + private PseudoBamControl control; + private boolean isLooping, isSelectionCircleEnabled, isPersonalSpaceEnabled; + private int lighting, orientation, cycle, startFrame, endFrame; + private Rectangle imageRect; + + public ActorAnimationProvider(SpriteDecoder decoder) + { + setDefaults(); + setDecoder(decoder); + } + + /** Returns the BAM sprite decoder instance. */ + public SpriteDecoder getDecoder() + { + return decoder; + } + + public void setDecoder(SpriteDecoder decoder) + { + this.decoder = Objects.requireNonNull(decoder, "Sprite decoder cannot be null"); + control = this.decoder.createControl(); + control.setMode(PseudoBamControl.Mode.INDIVIDUAL); + control.setSharedPerCycle(false); + control.cycleSet(getCycle()); + resetFrame(); + + updateCanvas(); + updateGraphics(); + } + + /** Returns whether the selection circle underneath actor sprites is drawn. */ + public boolean isSelectionCircleEnabled() + { + return isSelectionCircleEnabled; + } + + /** Specify whether the selection circle underneath actor sprites should be drawn. */ + public void setSelectionCircleEnabled(boolean enable) + { + if (enable != isSelectionCircleEnabled) { + isSelectionCircleEnabled = enable; + decoder.setSelectionCircleEnabled(isSelectionCircleEnabled); + updateCanvas(); + updateGraphics(); + } + } + + /** Returns whether the personal space indicator underneath actor sprites is drawn. */ + public boolean isPersonalSpaceEnabled() + { + return isPersonalSpaceEnabled; + } + + /** Specify whether the selection circle underneath actor sprites should be drawn. */ + public void setPersonalSpaceEnabled(boolean enable) + { + if (enable != isPersonalSpaceEnabled) { + isPersonalSpaceEnabled = enable; + decoder.setPersonalSpaceVisible(isPersonalSpaceEnabled); + updateCanvas(); + updateGraphics(); + } + } + + /** Returns the lighting condition of the animation. */ + public int getLighting() + { + return lighting; + } + + /** Defines a new lighting condition for the animation. No change if the animation is self-illuminated. */ + public void setLighting(int state) + { + switch (state) { + case ViewerConstants.LIGHTING_DAY: + case ViewerConstants.LIGHTING_TWILIGHT: + case ViewerConstants.LIGHTING_NIGHT: + if (state != lighting) { + lighting = state; + if (!decoder.isLightSource()) { + updateGraphics(); + } + } + break; + } + } + + /** Returns the numeric orientation value of the actor sprite. */ + public int getOrientation() + { + return orientation; + } + + /** + * Sets the specified orientation value and updates the sprite cycle accordingly. + * Should be called after a sprite sequence has been loaded. + */ + public void setOrientation(int dir) + { + dir = Math.abs(dir) % SpriteDecoder.Direction.values().length; + if (dir != orientation) { + orientation = dir; + SpriteDecoder.Direction direction = getDecoder().getExistingDirection(SpriteDecoder.Direction.from(orientation)); + int idx = getDecoder().getDirectionMap().getOrDefault(direction, 0); + setCycle(idx); + } + } + + /** + * Returns the currently selected animation cycle. + */ + public int getCycle() + { + int idx = cycle < 0 ? (control.cycleCount() + cycle) : cycle; + return Math.max(0, Math.min(control.cycleCount() - 1, idx)); + } + + /** + * Sets the current BAM cycle. + * Specify positive values to set an absolute cycle index. + * Specify negative values to set the cycle relative to the cycle length + * where -1 indicates the last BAM cycle. + */ + public void setCycle(int cycleIdx) + { + cycleIdx = Math.min(control.cycleCount() - 1, cycleIdx); + if (cycleIdx != cycle) { + cycle = cycleIdx; + control.cycleSet(getCycle()); + updateCanvas(); + resetFrame(); + } + } + + /** Returns the first frame of the current BAM cycle to be displayed. */ + public int getStartFrame() + { + int idx = startFrame < 0 ? (control.cycleFrameCount() + startFrame) : startFrame; + return Math.max(0, Math.min(control.cycleFrameCount() - 1, idx)); + } + + /** + * Sets the first frame of the current BAM cycle to be displayed. + * Specify positive values to set an absolute frame index. + * Specify negative values to set the start frame relative to the number of frames in the cycle + * where -1 indicates the last frame of the cycle. + */ + public void setStartFrame(int frameIdx) + { + frameIdx = Math.min(control.cycleFrameCount() - 1, frameIdx); + if (frameIdx != startFrame) { + startFrame = frameIdx; + resetFrame(); + } + } + + /** Returns the frame index after which the animation sequence ends. */ + public int getFrameCap() + { + int idx = endFrame < 0 ? (control.cycleFrameCount() + endFrame) : endFrame; + return Math.max(0, Math.min(control.cycleFrameCount() - 1, idx)); + } + + /** + * Sets the frame index after which the animation sequence ends. + * Specify positive values to set an absolute frame index. + * Specify negative values to set the end frame relative to the number of frames in the cycle + * where -1 indicates the last frame of the cycle. + */ + public void setFrameCap(int frameIdx) + { + frameIdx = Math.min(control.cycleFrameCount() -1, frameIdx); + if (frameIdx != endFrame) { + endFrame = frameIdx; + resetFrame(); + } + } + + /** Sets a new looping state. */ + public void setLooping(boolean set) + { + if (set != isLooping) { + isLooping = set; + } + } + +//--------------------- Begin Interface BasicAnimationProvider --------------------- + + @Override + public boolean advanceFrame() + { + boolean retVal = control.cycleGetFrameIndex() < getFrameCap() - 1; + if (retVal) { + control.cycleNextFrame(); + } +// retVal = control.cycleNextFrame(); + updateGraphics(); + return retVal; + } + + @Override + public void resetFrame() + { + control.cycleSetFrameIndex(getStartFrame()); + updateGraphics(); + } + + @Override + public boolean isLooping() + { + return isLooping; + } + + @Override + public Point getLocationOffset() + { + return imageRect.getLocation(); + } + +//--------------------- End Interface BasicAnimationProvider --------------------- + + // Sets sane default values for all properties + private void setDefaults() + { + setImage(null); + setWorkingImage(null); + setActive(true); + isLooping = true; + isSelectionCircleEnabled = true; + isPersonalSpaceEnabled = false; + orientation = 0; // south + cycle = 0; + startFrame = 0; + endFrame = -1; + lighting = ViewerConstants.LIGHTING_TWILIGHT; + imageRect = null; + } + + // Updates the global image object to match the shared size of the current BAM (cycle). + private void updateCanvas() + { + imageRect = control.calculateSharedCanvas(false); + BufferedImage image = (BufferedImage)getImage(); + if (getWorkingImage() == null || image == null || + image.getWidth() != imageRect.width || image.getHeight() != imageRect.height) { + setImage(ColorConvert.createCompatibleImage(imageRect.width, imageRect.height, true)); + setWorkingImage(new BufferedImage(imageRect.width, imageRect.height, BufferedImage.TYPE_INT_ARGB)); + } + } + + // Renders the current frame + @Override + protected synchronized void updateGraphics() + { + BufferedImage image = (BufferedImage)getImage(); + if (image != null) { + if (isActive()) { + // clearing old content + Graphics2D g = image.createGraphics(); + try { + g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC)); + g.setColor(TransparentColor); + g.fillRect(0, 0, image.getWidth(), image.getHeight()); + + // rendering frame + // fetching frame data + BufferedImage working = getWorkingImage(); + int frameIndex = control.cycleGetFrameIndexAbsolute(); + int[] buffer = ((DataBufferInt)working.getRaster().getDataBuffer()).getData(); + Arrays.fill(buffer, 0); + decoder.frameGet(control, frameIndex, working); + + // post-processing frame + buffer = ((DataBufferInt)working.getRaster().getDataBuffer()).getData(); + int canvasWidth = working.getWidth(); + int canvasHeight = working.getHeight(); + int frameWidth = decoder.getFrameInfo(frameIndex).getWidth(); + int frameHeight = decoder.getFrameInfo(frameIndex).getHeight(); + + if (!decoder.isLightSource()) { + applyLighting(buffer, canvasWidth, canvasHeight, frameWidth, frameHeight, getLighting()); + } + buffer = null; + + // rendering frame + int left = -imageRect.x - decoder.getFrameInfo(frameIndex).getCenterX(); + int top = -imageRect.y - decoder.getFrameInfo(frameIndex).getCenterY(); + g.drawImage(working, left, top, left+frameWidth, top+frameHeight, 0, 0, frameWidth, frameHeight, null); + } finally { + g.dispose(); + g = null; + } + } else { + // draw placeholder instead + Graphics2D g = image.createGraphics(); + try { + g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC)); + g.setColor(TransparentColor); + g.fillRect(0, 0, image.getWidth(), image.getHeight()); + } finally { + g.dispose(); + g = null; + } + } + } + } +} diff --git a/src/org/infinity/resource/are/viewer/AreaViewer.java b/src/org/infinity/resource/are/viewer/AreaViewer.java index c0a760f89..c7e7d7f60 100644 --- a/src/org/infinity/resource/are/viewer/AreaViewer.java +++ b/src/org/infinity/resource/are/viewer/AreaViewer.java @@ -135,6 +135,7 @@ public class AreaViewer extends ChildFrame private final Point mapCoordinates = new Point(); private final String windowTitle; private final JCheckBox[] cbLayers = new JCheckBox[LayerManager.getLayerTypeCount()]; + private final JCheckBox[] cbLayerRealActor = new JCheckBox[2]; private final JCheckBox[] cbLayerRealAnimation = new JCheckBox[2]; private final JCheckBox[] cbMiniMaps = new JCheckBox[3]; // private final JToggleButton[] tbAddLayerItem = new JToggleButton[LayerManager.getLayerTypeCount()]; @@ -243,13 +244,13 @@ public void close() map.clear(); if (rcCanvas != null) { removeLayerItems(); - rcCanvas.clear(); - rcCanvas.setImage(null); + rcCanvas.dispose(); } if (layerManager != null) { layerManager.close(); layerManager = null; } + SharedResourceCache.clearCache(); dispose(); System.gc(); super.close(); @@ -384,7 +385,17 @@ private void init() cbLayers[i].addActionListener(getListeners()); t2 = new DefaultMutableTreeNode(cbLayers[i]); t.add(t2); - if (i == LayerManager.getLayerTypeIndex(LayerType.AMBIENT)) { + if (i == LayerManager.getLayerTypeIndex(LayerType.ACTOR)) { + // Initializing real creature animation checkboxes + cbLayerRealActor[0] = new JCheckBox("Show actor sprite"); + cbLayerRealActor[0].addActionListener(getListeners()); + t3 = new DefaultMutableTreeNode(cbLayerRealActor[0]); + t2.add(t3); + cbLayerRealActor[1] = new JCheckBox("Animate actor sprite"); + cbLayerRealActor[1].addActionListener(getListeners()); + t3 = new DefaultMutableTreeNode(cbLayerRealActor[1]); + t2.add(t3); + } else if (i == LayerManager.getLayerTypeIndex(LayerType.AMBIENT)) { // Initializing ambient sound range checkbox cbLayerAmbientRange = new JCheckBox("Show local sound ranges"); cbLayerAmbientRange.addActionListener(getListeners()); @@ -755,6 +766,17 @@ private void initGuiSettings() showLayer(LayerManager.getLayerType(i), cbLayers[i].isSelected()); } + // initializing actor sprites display + // Disabling animated frames for performance and safety reasons + if (Settings.ShowActorSprites == ViewerConstants.ANIM_SHOW_ANIMATED) { + Settings.ShowActorSprites = ViewerConstants.ANIM_SHOW_STILL; + } + ((LayerActor)layerManager.getLayer(LayerType.ACTOR)).setRealActorFrameState(Settings.ShowActorFrame); + cbLayerRealActor[0].setSelected(Settings.ShowActorSprites == ViewerConstants.ANIM_SHOW_STILL); + cbLayerRealActor[1].setSelected(false); + updateRealActors(); + updateRealActorsLighting(getDayTime()); + // Setting up ambient sound ranges LayerAmbient layerAmbient = (LayerAmbient)layerManager.getLayer(ViewerConstants.LayerType.AMBIENT); if (layerAmbient.getLayerObjectCount(ViewerConstants.AMBIENT_TYPE_LOCAL) > 0) { @@ -768,7 +790,7 @@ private void initGuiSettings() if (Settings.ShowRealAnimations == ViewerConstants.ANIM_SHOW_ANIMATED) { Settings.ShowRealAnimations = ViewerConstants.ANIM_SHOW_STILL; } - ((LayerAnimation)layerManager.getLayer(LayerType.ANIMATION)).setRealAnimationFrameState(Settings.ShowFrame); + ((LayerAnimation)layerManager.getLayer(LayerType.ANIMATION)).setRealAnimationFrameState(Settings.ShowAnimationFrame); cbLayerRealAnimation[0].setSelected(Settings.ShowRealAnimations == ViewerConstants.ANIM_SHOW_STILL); cbLayerRealAnimation[1].setSelected(false); updateRealAnimation(); @@ -1367,6 +1389,7 @@ private void showItemPopup(MouseEvent event) /** Updates all available layer items. */ private void reloadLayers() { + SharedResourceCache.clearCache(); rcCanvas.reload(true); reloadAreLayers(false); reloadWedLayers(false); @@ -1392,6 +1415,8 @@ private void reloadAreLayers(boolean order) } } } + updateRealActors(); + updateRealActorsLighting(getVisualState()); updateAmbientRange(); updateRealAnimation(); updateRealAnimationsLighting(getVisualState()); @@ -1553,6 +1578,19 @@ private void updateAmbientRange() } } + /** Applies the specified lighting condition to real actor items. */ + private void updateRealActorsLighting(int visualState) + { + if (layerManager != null) { + List list = layerManager.getLayerObjects(LayerType.ACTOR); + if (list != null) { + for (final LayerObject obj : list) { + ((LayerObjectActor)obj).setLighting(visualState); + } + } + } + } + /** Applies the specified lighting condition to real animation items. */ private void updateRealAnimationsLighting(int visualState) { @@ -1566,6 +1604,46 @@ private void updateRealAnimationsLighting(int visualState) } } + /** Updates the state of real actor checkboxes and their associated functionality. */ + private void updateRealActors() + { + if (layerManager != null) { + LayerActor layer = (LayerActor)layerManager.getLayer(LayerType.ACTOR); + if (layer != null) { + JCheckBox cb = cbLayers[LayerManager.getLayerTypeIndex(LayerType.ACTOR)]; + boolean enabled = cb.isEnabled() && cb.isSelected(); + cbLayerRealActor[0].setEnabled(enabled); + cbLayerRealActor[1].setEnabled(enabled); + boolean animEnabled = false; + boolean animPlaying = false; + if (enabled) { + if (cbLayerRealActor[0].isSelected()) { + animEnabled = true; + } else if (cbLayerRealActor[1].isSelected()) { + animEnabled = true; + animPlaying = true; + } + } + layer.setRealActorEnabled(animEnabled); + layer.setRealActorPlaying(animPlaying); + } else { + cbLayerRealActor[0].setEnabled(false); + cbLayerRealActor[1].setEnabled(false); + } + updateTreeNode(cbLayerRealActor[0]); + updateTreeNode(cbLayerRealActor[1]); + + // Storing settings + if (!cbLayerRealActor[0].isSelected() && !cbLayerRealActor[1].isSelected()) { + Settings.ShowActorSprites = ViewerConstants.ANIM_SHOW_NONE; + } else if (cbLayerRealActor[0].isSelected() && !cbLayerRealActor[1].isSelected()) { + Settings.ShowActorSprites = ViewerConstants.ANIM_SHOW_STILL; + } else if (!cbLayerRealActor[0].isSelected() && cbLayerRealActor[1].isSelected()) { + Settings.ShowActorSprites = ViewerConstants.ANIM_SHOW_ANIMATED; + } + } + } + /** Updates the state of real animation checkboxes and their associated functionality. */ private void updateRealAnimation() { @@ -1896,8 +1974,14 @@ private void applySettings() } if (layerManager != null) { + // applying actor frame settings + ((LayerActor)layerManager.getLayer(LayerType.ACTOR)).setRealActorFrameState(Settings.ShowActorFrame); + // applying actor selection circle visibility + ((LayerActor)layerManager.getLayer(LayerType.ACTOR)).setRealActorSelectionCircleEnabled(Settings.ShowActorSelectionCircle); + // applying actor personal space visibility + ((LayerActor)layerManager.getLayer(LayerType.ACTOR)).setRealActorPersonalSpaceEnabled(Settings.ShowActorPersonalSpace); // applying animation frame settings - ((LayerAnimation)layerManager.getLayer(LayerType.ANIMATION)).setRealAnimationFrameState(Settings.ShowFrame); + ((LayerAnimation)layerManager.getLayer(LayerType.ANIMATION)).setRealAnimationFrameState(Settings.ShowAnimationFrame); // applying animation active override settings ((LayerAnimation)layerManager.getLayer(LayerType.ANIMATION)).setRealAnimationActiveIgnored(Settings.OverrideAnimVisibility); // applying interpolation settings to animations @@ -1919,7 +2003,7 @@ private void applySettings() if (interval != timerOverlays.getDelay()) { timerOverlays.setDelay(interval); } - // applying frame rate to background animations + // applying frame rate to actor sprites and background animations layerManager.setRealAnimationFrameRate(Settings.FrameRateAnimations); } } @@ -2002,7 +2086,10 @@ public void actionPerformed(ActionEvent event) LayerType layer = getLayerType(cb); if (layer != null) { showLayer(layer, cb.isSelected()); - if (layer == LayerType.AMBIENT) { + if (layer == LayerType.ACTOR) { + // Taking care of real animation display + updateRealActors(); + } else if (layer == LayerType.AMBIENT) { // Taking care of local ambient ranges updateAmbientRange(); } else if (layer == LayerType.ANIMATION) { @@ -2010,6 +2097,16 @@ public void actionPerformed(ActionEvent event) updateRealAnimation(); } updateScheduledItems(); + } else if (cb == cbLayerRealActor[0]) { + if (cbLayerRealActor[0].isSelected()) { + cbLayerRealActor[1].setSelected(false); + } + updateRealActors(); + } else if (cb == cbLayerRealActor[1]) { + if (cbLayerRealActor[1].isSelected()) { + cbLayerRealActor[0].setSelected(false); + } + updateRealActors(); } else if (cb == cbLayerAmbientRange) { updateAmbientRange(); } else if (cb == cbLayerRealAnimation[0]) { diff --git a/src/org/infinity/resource/are/viewer/BackgroundAnimationProvider.java b/src/org/infinity/resource/are/viewer/BackgroundAnimationProvider.java index baf4b5dc2..95c150148 100644 --- a/src/org/infinity/resource/are/viewer/BackgroundAnimationProvider.java +++ b/src/org/infinity/resource/are/viewer/BackgroundAnimationProvider.java @@ -7,14 +7,12 @@ import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Graphics2D; -import java.awt.Image; import java.awt.Point; import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.awt.image.DataBufferInt; import java.util.Arrays; -import org.infinity.gui.layeritem.BasicAnimationProvider; import org.infinity.resource.graphics.BamDecoder; import org.infinity.resource.graphics.BamV1Decoder; import org.infinity.resource.graphics.ColorConvert; @@ -22,7 +20,7 @@ /** * Implements functionality for properly displaying background animations. */ -public class BackgroundAnimationProvider implements BasicAnimationProvider +public class BackgroundAnimationProvider extends AbstractAnimationProvider { private static final Color TransparentColor = new Color(0, true); @@ -39,13 +37,12 @@ public class BackgroundAnimationProvider implements BasicAnimationProvider private BamDecoder bam; private BamDecoder.BamControl control; - private boolean isActive, isActiveIgnored, isBlended, isMirrored, isLooping, isSelfIlluminated, + private boolean isBlended, isMirrored, isLooping, isSelfIlluminated, isMultiPart, isPaletteEnabled; private int lighting, baseAlpha; private int firstFrame, lastFrame; private int[] palette; // external palette private Rectangle imageRect; - private BufferedImage image, working; public BackgroundAnimationProvider() { @@ -116,40 +113,6 @@ public void setPaletteEnabled(boolean set) } } - /** Returns the active/visibility state of the animation. */ - public boolean isActive() - { - return isActive; - } - - /** Specify whether to actually display the animation on screen. */ - public void setActive(boolean set) - { - if (set != isActive) { - isActive = set; - updateGraphics(); - } - } - - /** - * Returns whether to ignore the activation state of the animation. - */ - public boolean isActiveIgnored() - { - return isActiveIgnored; - } - - /** - * Specify whether to ignore the activation state of the animation and display it regardless. - */ - public void setActiveIgnored(boolean set) - { - if (set != isActiveIgnored) { - isActiveIgnored = set; - updateGraphics(); - } - } - /** Returns whether the current frame of all available cycles are displayed simultanuously. */ public boolean isMultiPart() { @@ -340,12 +303,6 @@ public void setFrameCap(int frameIdx) //--------------------- Begin Interface BasicAnimationProvider --------------------- - @Override - public Image getImage() - { - return image; - } - @Override public boolean advanceFrame() { @@ -364,7 +321,7 @@ public boolean advanceFrame() @Override public void resetFrame() { - control.cycleSetFrameIndex(firstFrame); + control.cycleSetFrameIndex(getStartFrame()); updateGraphics(); } @@ -385,8 +342,11 @@ public Point getLocationOffset() // Sets sane default values for all properties private void setDefaults() { - isActive = true; - isActiveIgnored = false; + setImage(null); + setWorkingImage(null); + setActive(true); + setActiveIgnored(false); + imageRect = null; isBlended = false; isMirrored = false; isLooping = true; @@ -398,25 +358,25 @@ private void setDefaults() baseAlpha = 255; firstFrame = 0; lastFrame = -1; - imageRect = null; - image = null; - working = null; } // Updates the global image object to match the shared size of the current BAM (cycle). private void updateCanvas() { imageRect = control.calculateSharedCanvas(isMirrored()); - if (working == null || image == null || + BufferedImage image = (BufferedImage)getImage(); + if (getWorkingImage() == null || image == null || image.getWidth() != imageRect.width || image.getHeight() != imageRect.height) { - image = ColorConvert.createCompatibleImage(imageRect.width, imageRect.height, true); - working = new BufferedImage(imageRect.width, imageRect.height, BufferedImage.TYPE_INT_ARGB); + setImage(ColorConvert.createCompatibleImage(imageRect.width, imageRect.height, true)); + setWorkingImage(new BufferedImage(imageRect.width, imageRect.height, BufferedImage.TYPE_INT_ARGB)); } } // Renders the current frame - private synchronized void updateGraphics() + @Override + protected synchronized void updateGraphics() { + BufferedImage image = (BufferedImage)getImage(); if (image != null) { if (isActive() || isActiveIgnored()) { // preparing frames @@ -438,6 +398,7 @@ private synchronized void updateGraphics() g.fillRect(0, 0, image.getWidth(), image.getHeight()); // rendering frame + BufferedImage working = getWorkingImage(); for (int i = 0; i < frameIndices.length; i++) { // fetching frame data int[] buffer = ((DataBufferInt)working.getRaster().getDataBuffer()).getData(); @@ -571,35 +532,4 @@ private void applyBlending(int[] buffer, int cw, int ch, int fw, int fh) } } } - - // Applies lightning conditions to all pixels - private void applyLighting(int[] buffer, int cw, int ch, int fw, int fh, int lighting) - { - if (buffer != null && cw > 0 && ch > 0 && fw > 0 && fh > 0) { - int maxOfs = fh*cw; - if (buffer.length >= maxOfs) { - if (lighting < ViewerConstants.LIGHTING_DAY) lighting = ViewerConstants.LIGHTING_DAY; - else if (lighting > ViewerConstants.LIGHTING_NIGHT) lighting = ViewerConstants.LIGHTING_NIGHT; - int ofs = 0; - while (ofs < maxOfs) { - for (int x = 0; x < fw; x++) { - int pixel = buffer[ofs+x]; - if ((pixel & 0xff000000) != 0) { - int r = (pixel >>> 16) & 0xff; - int g = (pixel >>> 8) & 0xff; - int b = pixel & 0xff; - r = (r*TilesetRenderer.LightingAdjustment[lighting][0]) >>> TilesetRenderer.LightingAdjustmentShift; - g = (g*TilesetRenderer.LightingAdjustment[lighting][1]) >>> TilesetRenderer.LightingAdjustmentShift; - b = (b*TilesetRenderer.LightingAdjustment[lighting][2]) >>> TilesetRenderer.LightingAdjustmentShift; - if (r > 255) r = 255; - if (g > 255) g = 255; - if (b > 255) b = 255; - buffer[ofs+x] = (pixel & 0xff000000) | (r << 16) | (g << 8) | b; - } - } - ofs += cw; - } - } - } - } } diff --git a/src/org/infinity/resource/are/viewer/BasicLayer.java b/src/org/infinity/resource/are/viewer/BasicLayer.java index b57e9f3ca..1e3429100 100644 --- a/src/org/infinity/resource/are/viewer/BasicLayer.java +++ b/src/org/infinity/resource/are/viewer/BasicLayer.java @@ -158,9 +158,10 @@ public final int loadLayer(boolean forced) * * @param Type of the items on the layer */ - protected final void loadLayerItems(String offsetAttribute, String countAttribute, - Class itemClass, Function newLayerObject) + protected void loadLayerItems(String offsetAttribute, String countAttribute, + Class itemClass, Function newLayerObject) { +// long timeStart = System.nanoTime(); final SectionOffset so = (SectionOffset)parent.getAttribute(offsetAttribute); final SectionCount sc = (SectionCount )parent.getAttribute(countAttribute); if (so != null && sc != null) { @@ -173,6 +174,8 @@ protected final void loadLayerItems(String offsetAttribu } setInitialized(true); } +// long timeEnd = System.nanoTime(); +// System.out.printf("Area viewer > load layer items (%s): %,d µs\n", itemClass.getSimpleName(), (timeEnd - timeStart) / 1000); } /** Loads all available objects of this layer. */ diff --git a/src/org/infinity/resource/are/viewer/LayerActor.java b/src/org/infinity/resource/are/viewer/LayerActor.java index a29ab07ee..4b011477c 100644 --- a/src/org/infinity/resource/are/viewer/LayerActor.java +++ b/src/org/infinity/resource/are/viewer/LayerActor.java @@ -7,6 +7,9 @@ import java.util.List; import java.util.Locale; +import org.infinity.gui.layeritem.AbstractLayerItem; +import org.infinity.gui.layeritem.AnimatedLayerItem; +import org.infinity.gui.layeritem.IconLayerItem; import org.infinity.resource.ResourceFactory; import org.infinity.resource.are.Actor; import org.infinity.resource.are.AreResource; @@ -25,6 +28,11 @@ public class LayerActor extends BasicLayer { private static final String AvailableFmt = "Actors: %d"; + private boolean realEnabled, realPlaying, forcedInterpolation, selectionCircleEnabled, personalSpaceEnabled; + private int frameState; + private Object interpolationType = ViewerConstants.TYPE_NEAREST_NEIGHBOR; + private double frameRate = ViewerConstants.FRAME_AUTO; + public LayerActor(AreResource are, AreaViewer viewer) { super(are, ViewerConstants.LayerType.ACTOR, viewer); @@ -61,6 +69,9 @@ protected void loadLayer() } } } + + // sorting entries by vertical position to fix overlapping issues + getLayerObjects().sort((c1, c2) -> c2.location.y - c1.location.y); } @Override @@ -69,4 +80,263 @@ public String getAvailability() int cnt = getLayerObjectCount(); return String.format(AvailableFmt, cnt); } + + /** + * Sets the visibility state of all items in the layer. Takes enabled states of the different + * item types into account. + */ + @Override + public void setLayerVisible(boolean visible) + { + setVisibilityState(visible); + List list = getLayerObjects(); + for (int i = 0, size = list.size(); i < size; i++) { + boolean state = isLayerVisible() && (!isScheduleEnabled() || isScheduled(i)); + LayerObjectActor obj = list.get(i); + IconLayerItem iconItem = (IconLayerItem)obj.getLayerItem(ViewerConstants.ITEM_ICON); + if (iconItem != null) { + iconItem.setVisible(state && !realEnabled); + } + AnimatedLayerItem animItem = (AnimatedLayerItem)obj.getLayerItem(ViewerConstants.ITEM_REAL); + if (animItem != null) { + animItem.setVisible(state && realEnabled); + if (isRealActorEnabled() && isRealActorPlaying()) { + animItem.play(); + } else { + animItem.stop(); + } + } + } + } + + /** + * Returns the currently active interpolation type for real actors. + * @return Either one of ViewerConstants.TYPE_NEAREST_NEIGHBOR, ViewerConstants.TYPE_NEAREST_BILINEAR + * or ViewerConstants.TYPE_BICUBIC. + */ + public Object getRealActorInterpolation() + { + return interpolationType; + } + + /** + * Sets the interpolation type for real actors. + * @param interpolationType Either one of ViewerConstants.TYPE_NEAREST_NEIGHBOR, + * ViewerConstants.TYPE_NEAREST_BILINEAR or ViewerConstants.TYPE_BICUBIC. + */ + public void setRealActorInterpolation(Object interpolationType) + { + if (interpolationType != this.interpolationType) { + this.interpolationType = interpolationType; + for (final LayerObjectActor layer : getLayerObjects()) { + final AnimatedLayerItem item = (AnimatedLayerItem)layer.getLayerItem(ViewerConstants.ITEM_REAL); + if (item != null) { + item.setInterpolationType(interpolationType); + } + } + } + } + + /** + * Returns whether to force the specified interpolation type or use the best one available, depending + * on the current zoom factor. + */ + public boolean isRealActorForcedInterpolation() + { + return forcedInterpolation; + } + + /** + * Specify whether to force the specified interpolation type or use the best one available, depending + * on the current zoom factor. + */ + public void setRealActorForcedInterpolation(boolean forced) + { + if (forced != forcedInterpolation) { + forcedInterpolation = forced; + for (final LayerObjectActor layer : getLayerObjects()) { + final AnimatedLayerItem item = (AnimatedLayerItem)layer.getLayerItem(ViewerConstants.ITEM_REAL); + if (item != null) { + item.setForcedInterpolation(forced); + } + } + } + } + + /** + * Returns whether real actor items or iconic actor items are enabled. + * @return If {@code true}, real actor items are enabled. + * If {@code false}, iconic actor items are enabled. + */ + public boolean isRealActorEnabled() + { + return realEnabled; + } + + /** + * Specify whether iconic actor type or real actor type is enabled. + * @param enable If {@code true}, real actor items will be shown. + * If {@code false}, iconic actor items will be shown. + */ + public void setRealActorEnabled(boolean enable) + { + if (enable != realEnabled) { + realEnabled = enable; + if (isLayerVisible()) { + setLayerVisible(isLayerVisible()); + } + } + } + + /** + * Returns whether real actor items are enabled and animated. + */ + public boolean isRealActorPlaying() + { + return realEnabled && realPlaying; + } + + /** + * Specify whether real actor should be animated. Setting to {@code true} will enable + * real actors automatically. + */ + public void setRealActorPlaying(boolean play) + { + if (play != realPlaying) { + realPlaying = play; + if (realPlaying && !realEnabled) { + realEnabled = true; + } + if (isLayerVisible()) { + setLayerVisible(isLayerVisible()); + } + } + } + + /** + * Returns the current frame visibility. + * @return One of ViewerConstants.FRAME_NEVER, ViewerConstants.FRAME_AUTO or ViewerConstants.FRAME_ALWAYS. + */ + public int getRealActorFrameState() + { + return frameState; + } + + /** + * Specify the frame visibility for real actors + * @param state One of ViewerConstants.FRAME_NEVER, ViewerConstants.FRAME_AUTO or ViewerConstants.FRAME_ALWAYS. + */ + public void setRealActorFrameState(int state) + { + switch (state) { + case ViewerConstants.FRAME_NEVER: + case ViewerConstants.FRAME_AUTO: + case ViewerConstants.FRAME_ALWAYS: + { + frameState = state; + updateFrameState(); + break; + } + } + } + + /** + * Returns whether selection circle of actor sprites is enabled. + */ + public boolean isRealActorSelectionCircleEnabled() + { + return selectionCircleEnabled; + } + + /** + * Specify whether selection circle of actor sprites is enabled. + */ + public void setRealActorSelectionCircleEnabled(boolean enable) + { + if (enable != selectionCircleEnabled) { + selectionCircleEnabled = enable; + for (final LayerObjectActor layer : getLayerObjects()) { + final AnimatedLayerItem item = (AnimatedLayerItem)layer.getLayerItem(ViewerConstants.ITEM_REAL); + if (item.getAnimation() instanceof ActorAnimationProvider) { + ActorAnimationProvider aap = (ActorAnimationProvider)item.getAnimation(); + aap.setSelectionCircleEnabled(selectionCircleEnabled); + } + } + } + } + + /** + * Returns whether personal space indicator of actor sprites is enabled. + */ + public boolean isRealActorPersonalSpaceEnabled() + { + return personalSpaceEnabled; + } + + /** + * Specify whether personal space indicator of actor sprites is enabled. + */ + public void setRealActorPersonalSpaceEnabled(boolean enable) + { + if (enable != personalSpaceEnabled) { + personalSpaceEnabled = enable; + for (final LayerObjectActor layer : getLayerObjects()) { + final AnimatedLayerItem item = (AnimatedLayerItem)layer.getLayerItem(ViewerConstants.ITEM_REAL); + if (item.getAnimation() instanceof ActorAnimationProvider) { + ActorAnimationProvider aap = (ActorAnimationProvider)item.getAnimation(); + aap.setPersonalSpaceEnabled(personalSpaceEnabled); + } + } + } + } + + /** + * Returns the frame rate used for playing back actor sprites. + * @return Frame rate in frames/second. + */ + public double getRealActorFrameRate() + { + return frameRate; + } + + /** + * Specify a new frame rate for real actors. + * @param frameRate Frame rate in frames/second. + */ + public void setRealActorFrameRate(double frameRate) + { + frameRate = Math.min(Math.max(frameRate, 1.0), 30.0); + if (frameRate != this.frameRate) { + this.frameRate = frameRate; + for (final LayerObjectActor layer : getLayerObjects()) { + final AnimatedLayerItem item = (AnimatedLayerItem)layer.getLayerItem(ViewerConstants.ITEM_REAL); + if (item != null) { + item.setFrameRate(frameRate); + } + } + } + } + + private void updateFrameState() + { + for (final LayerObjectActor layer : getLayerObjects()) { + final AnimatedLayerItem item = (AnimatedLayerItem)layer.getLayerItem(ViewerConstants.ITEM_REAL); + if (item != null) { + switch (frameState) { + case ViewerConstants.FRAME_NEVER: + item.setFrameEnabled(AbstractLayerItem.ItemState.NORMAL, false); + item.setFrameEnabled(AbstractLayerItem.ItemState.HIGHLIGHTED, false); + break; + case ViewerConstants.FRAME_AUTO: + item.setFrameEnabled(AbstractLayerItem.ItemState.NORMAL, false); + item.setFrameEnabled(AbstractLayerItem.ItemState.HIGHLIGHTED, true); + break; + case ViewerConstants.FRAME_ALWAYS: + item.setFrameEnabled(AbstractLayerItem.ItemState.NORMAL, true); + item.setFrameEnabled(AbstractLayerItem.ItemState.HIGHLIGHTED, true); + break; + } + } + } + } } diff --git a/src/org/infinity/resource/are/viewer/LayerAnimation.java b/src/org/infinity/resource/are/viewer/LayerAnimation.java index 7f0ee4bda..cde7e7352 100644 --- a/src/org/infinity/resource/are/viewer/LayerAnimation.java +++ b/src/org/infinity/resource/are/viewer/LayerAnimation.java @@ -38,6 +38,7 @@ public LayerAnimation(AreResource are, AreaViewer viewer) @Override protected void loadLayer() { + // loading animations from ARE loadLayerItems(ARE_OFFSET_ANIMATIONS, ARE_NUM_ANIMATIONS, Animation.class, a -> new LayerObjectAnimation(parent, a)); @@ -83,11 +84,11 @@ public void setLayerVisible(boolean visible) for (int i = 0, size = list.size(); i < size; i++) { boolean state = isLayerVisible() && (!isScheduleEnabled() || isScheduled(i)); LayerObjectAnimation obj = list.get(i); - IconLayerItem iconItem = (IconLayerItem)obj.getLayerItem(ViewerConstants.ANIM_ITEM_ICON); + IconLayerItem iconItem = (IconLayerItem)obj.getLayerItem(ViewerConstants.ITEM_ICON); if (iconItem != null) { iconItem.setVisible(state && !realEnabled); } - AnimatedLayerItem animItem = (AnimatedLayerItem)obj.getLayerItem(ViewerConstants.ANIM_ITEM_REAL); + AnimatedLayerItem animItem = (AnimatedLayerItem)obj.getLayerItem(ViewerConstants.ITEM_REAL); if (animItem != null) { animItem.setVisible(state && realEnabled); if (isRealAnimationEnabled() && isRealAnimationPlaying()) { @@ -110,7 +111,7 @@ public Object getRealAnimationInterpolation() } /** - * Sets the interpolation type for real animations + * Sets the interpolation type for real animations. * @param interpolationType Either one of ViewerConstants.TYPE_NEAREST_NEIGHBOR, * ViewerConstants.TYPE_NEAREST_BILINEAR or ViewerConstants.TYPE_BICUBIC. */ @@ -119,7 +120,7 @@ public void setRealAnimationInterpolation(Object interpolationType) if (interpolationType != this.interpolationType) { this.interpolationType = interpolationType; for (final LayerObjectAnimation layer : getLayerObjects()) { - final AnimatedLayerItem item = (AnimatedLayerItem)layer.getLayerItem(ViewerConstants.ANIM_ITEM_REAL); + final AnimatedLayerItem item = (AnimatedLayerItem)layer.getLayerItem(ViewerConstants.ITEM_REAL); if (item != null) { item.setInterpolationType(interpolationType); } @@ -145,7 +146,7 @@ public void setRealAnimationForcedInterpolation(boolean forced) if (forced != forcedInterpolation) { forcedInterpolation = forced; for (final LayerObjectAnimation layer : getLayerObjects()) { - final AnimatedLayerItem item = (AnimatedLayerItem)layer.getLayerItem(ViewerConstants.ANIM_ITEM_REAL); + final AnimatedLayerItem item = (AnimatedLayerItem)layer.getLayerItem(ViewerConstants.ITEM_REAL); if (item != null) { item.setForcedInterpolation(forced); } @@ -249,7 +250,7 @@ public void setRealAnimationFrameRate(double frameRate) if (frameRate != this.frameRate) { this.frameRate = frameRate; for (final LayerObjectAnimation layer : getLayerObjects()) { - final AnimatedLayerItem item = (AnimatedLayerItem)layer.getLayerItem(ViewerConstants.ANIM_ITEM_REAL); + final AnimatedLayerItem item = (AnimatedLayerItem)layer.getLayerItem(ViewerConstants.ITEM_REAL); if (item != null) { item.setFrameRate(frameRate); } @@ -274,10 +275,10 @@ public void setRealAnimationActiveIgnored(boolean set) { isAnimActiveIgnored = set; for (final LayerObjectAnimation layer : getLayerObjects()) { - final AnimatedLayerItem item = (AnimatedLayerItem)layer.getLayerItem(ViewerConstants.ANIM_ITEM_REAL); + final AnimatedLayerItem item = (AnimatedLayerItem)layer.getLayerItem(ViewerConstants.ITEM_REAL); if (item != null) { - if (item.getAnimation() instanceof BackgroundAnimationProvider) { - ((BackgroundAnimationProvider)item.getAnimation()).setActiveIgnored(set); + if (item.getAnimation() instanceof AbstractAnimationProvider) { + ((AbstractAnimationProvider)item.getAnimation()).setActiveIgnored(set); } } } @@ -287,7 +288,7 @@ public void setRealAnimationActiveIgnored(boolean set) private void updateFrameState() { for (final LayerObjectAnimation layer : getLayerObjects()) { - final AnimatedLayerItem item = (AnimatedLayerItem)layer.getLayerItem(ViewerConstants.ANIM_ITEM_REAL); + final AnimatedLayerItem item = (AnimatedLayerItem)layer.getLayerItem(ViewerConstants.ITEM_REAL); if (item != null) { switch (frameState) { case ViewerConstants.FRAME_NEVER: diff --git a/src/org/infinity/resource/are/viewer/LayerManager.java b/src/org/infinity/resource/are/viewer/LayerManager.java index 240a1dbca..d0d138c9e 100644 --- a/src/org/infinity/resource/are/viewer/LayerManager.java +++ b/src/org/infinity/resource/are/viewer/LayerManager.java @@ -391,6 +391,29 @@ public void setDoorState(int state) ((LayerDoorPoly)getLayer(LayerType.DOOR_POLY)).setDoorState(state); } + /** + * Returns whether to show iconic representations of actors or the real thing. + */ + public boolean isRealActorEnabled() + { + LayerActor layer = (LayerActor)getLayer(ViewerConstants.LayerType.ACTOR); + if (layer != null) { + return layer.isRealActorEnabled(); + } + return false; + } + + /** + * Specify whether to show iconic representations of actors or the real thing. + */ + public void setRealActorEnabled(boolean enable) + { + LayerActor layer = (LayerActor)getLayer(ViewerConstants.LayerType.ACTOR); + if (layer != null) { + layer.setRealActorEnabled(enable); + } + } + /** * Returns whether to show iconic representations of background animations or the real thing. */ @@ -414,6 +437,30 @@ public void setRealAnimationEnabled(boolean enable) } } + /** + * Returns whether real actor sprites are enabled and animated. + */ + public boolean isRealActorPlaying() + { + LayerActor layer = (LayerActor)getLayer(ViewerConstants.LayerType.ACTOR); + if (layer != null) { + return layer.isRealActorPlaying(); + } + return false; + } + + /** + * Specify whether to animate real actor sprites. Setting to {@code true} implicitly + * enables real actors. + */ + public void setRealActorPlaying(boolean play) + { + LayerActor layer = (LayerActor)getLayer(ViewerConstants.LayerType.ACTOR); + if (layer != null) { + layer.setRealActorPlaying(play); + } + } + /** * Returns whether real animations are enabled and animated. */ @@ -449,7 +496,7 @@ public Object getRealAnimationInterpolation() } /** - * Sets the interpolation type for real animations + * Sets the interpolation type for real animations. * @param interpolationType Either one of ViewerConstants.TYPE_NEAREST_NEIGHBOR, * ViewerConstants.TYPE_NEAREST_BILINEAR or ViewerConstants.TYPE_BICUBIC. */ @@ -459,9 +506,13 @@ public void setRealAnimationInterpolation(Object interpolationType) interpolationType == ViewerConstants.TYPE_BILINEAR || interpolationType == ViewerConstants.TYPE_BICUBIC) { animInterpolationType = interpolationType; - LayerAnimation layer = (LayerAnimation)getLayer(LayerType.ANIMATION); - if (layer != null) { - layer.setRealAnimationInterpolation(animInterpolationType); + LayerAnimation layerAnim = (LayerAnimation)getLayer(LayerType.ANIMATION); + if (layerAnim != null) { + layerAnim.setRealAnimationInterpolation(animInterpolationType); + } + LayerActor layerActor = (LayerActor)getLayer(LayerType.ACTOR); + if (layerActor != null) { + layerActor.setRealActorInterpolation(animInterpolationType); } } } @@ -482,9 +533,13 @@ public boolean isRealAnimationForcedInterpolation() public void setRealAnimationForcedInterpolation(boolean forced) { animForcedInterpolation = forced; - LayerAnimation layer = (LayerAnimation)getLayer(LayerType.ANIMATION); - if (layer != null) { - layer.setRealAnimationForcedInterpolation(animForcedInterpolation); + LayerAnimation layerAnim = (LayerAnimation)getLayer(LayerType.ANIMATION); + if (layerAnim != null) { + layerAnim.setRealAnimationForcedInterpolation(animForcedInterpolation); + } + LayerActor layerActor = (LayerActor)getLayer(LayerType.ACTOR); + if (layerActor != null) { + layerActor.setRealActorForcedInterpolation(animForcedInterpolation); } } @@ -506,9 +561,13 @@ public void setRealAnimationFrameRate(double frameRate) frameRate = Math.min(Math.max(frameRate, 1.0), 30.0); if (frameRate != this.animFrameRate) { animFrameRate = frameRate; - LayerAnimation layer = (LayerAnimation)getLayer(LayerType.ANIMATION); - if (layer != null) { - layer.setRealAnimationFrameRate(animFrameRate); + LayerAnimation layerAnim = (LayerAnimation)getLayer(LayerType.ANIMATION); + if (layerAnim != null) { + layerAnim.setRealAnimationFrameRate(animFrameRate); + } + LayerActor layerActor = (LayerActor)getLayer(LayerType.ACTOR); + if (layerActor != null) { + layerActor.setRealActorFrameRate(animFrameRate); } } } diff --git a/src/org/infinity/resource/are/viewer/LayerObjectActor.java b/src/org/infinity/resource/are/viewer/LayerObjectActor.java index e8cb233ec..06d791369 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectActor.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectActor.java @@ -4,20 +4,70 @@ package org.infinity.resource.are.viewer; +import java.awt.Color; import java.awt.Point; +import java.util.Objects; +import org.infinity.datatype.IsNumeric; +import org.infinity.datatype.IsTextual; import org.infinity.gui.layeritem.AbstractLayerItem; -import org.infinity.gui.layeritem.IconLayerItem; +import org.infinity.gui.layeritem.AnimatedLayerItem; +import org.infinity.gui.layeritem.BasicAnimationProvider; import org.infinity.resource.AbstractStruct; +import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.SpriteDecoder; +import org.infinity.resource.graphics.BamDecoder; /** * Base class for layer type: Actor */ public abstract class LayerObjectActor extends LayerObject { + /** Available creature allegiance types. */ + protected enum Allegiance { + GOOD, + NEUTRAL, + ENEMY, + } + + protected static final Color COLOR_FRAME_NORMAL = new Color(0xA02020FF, true); + protected static final Color COLOR_FRAME_HIGHLIGHTED = new Color(0xFF2020FF, false); + + // Default animation sequence to load if available; fall back to the first available sequence if no default is available + private static final SpriteDecoder.Sequence[] DEFAULT_SEQUENCE = { + SpriteDecoder.Sequence.STAND, + SpriteDecoder.Sequence.STAND2, + SpriteDecoder.Sequence.STAND3, + SpriteDecoder.Sequence.STAND_EMERGED, + SpriteDecoder.Sequence.PST_STAND, + SpriteDecoder.Sequence.STANCE, + SpriteDecoder.Sequence.STANCE2, + SpriteDecoder.Sequence.PST_STANCE, + SpriteDecoder.Sequence.WALK, + SpriteDecoder.Sequence.PST_WALK, + }; + // Potential sequences for "death" state + private static final SpriteDecoder.Sequence[] DEATH_SEQUENCE = { + SpriteDecoder.Sequence.TWITCH, + SpriteDecoder.Sequence.DIE, + SpriteDecoder.Sequence.PST_DIE_FORWARD, + SpriteDecoder.Sequence.PST_DIE_BACKWARD, + SpriteDecoder.Sequence.PST_DIE_COLLAPSE, + }; + // Potential sequences for "unconscious" state + private static final SpriteDecoder.Sequence[] SLEEP_SEQUENCE = { + SpriteDecoder.Sequence.SLEEP, + SpriteDecoder.Sequence.SLEEP2, + SpriteDecoder.Sequence.TWITCH, + SpriteDecoder.Sequence.DIE, + SpriteDecoder.Sequence.PST_DIE_FORWARD, + SpriteDecoder.Sequence.PST_DIE_BACKWARD, + SpriteDecoder.Sequence.PST_DIE_COLLAPSE, + }; + protected final Point location = new Point(); + protected final AbstractLayerItem[] items = new AbstractLayerItem[2]; - protected IconLayerItem item; protected LayerObjectActor(Class classType, AbstractStruct parent) @@ -29,29 +79,214 @@ protected LayerObjectActor(Class classType, AbstractSt @Override public void close() { - // TODO: implement method super.close(); + // removing cached references + for (int i = 0; i < items.length; i++) { + Object key = items[i].getData(); + if (key != null) { + switch (i) { + case ViewerConstants.ITEM_ICON: + SharedResourceCache.remove(SharedResourceCache.Type.ICON, key); + break; + case ViewerConstants.ITEM_REAL: + SharedResourceCache.remove(SharedResourceCache.Type.ACTOR, key); + break; + } + } + } } + /** + * Returns the layer item of the specific state. (either ACTOR_ITEM_ICON or ACTOR_ITEM_REAL). + * @param type The state of the item to be returned. + * @return The desired layer item, or {@code null} if not available. + */ @Override public AbstractLayerItem getLayerItem(int type) { - return (type == 0) ? item : null; + type = (type == ViewerConstants.ITEM_REAL) ? ViewerConstants.ITEM_REAL : ViewerConstants.ITEM_ICON; + return items[type]; } @Override public AbstractLayerItem[] getLayerItems() { - return new AbstractLayerItem[]{item}; + return items; } @Override public void update(double zoomFactor) { - if (item != null) { - item.setItemLocation((int)(location.x*zoomFactor + (zoomFactor / 2.0)), - (int)(location.y*zoomFactor + (zoomFactor / 2.0))); + for (int i = 0; i < items.length; i++) { + items[i].setItemLocation((int)(location.x*zoomFactor + (zoomFactor / 2.0)), + (int)(location.y*zoomFactor + (zoomFactor / 2.0))); + if (i == ViewerConstants.ITEM_REAL) { + ((AnimatedLayerItem)items[i]).setZoomFactor(zoomFactor); + } + } + } + + /** + * Sets the lighting condition of the actor. Does nothing if the actor is flagged as + * self-illuminating. + * @param dayTime One of the constants: {@code TilesetRenderer.LIGHTING_DAY}, + * {@code TilesetRenderer.LIGHTING_TWILIGHT}, {@code TilesetRenderer.LIGHTING_NIGHT}. + */ + public void setLighting(int dayTime) + { + AnimatedLayerItem item = (AnimatedLayerItem)items[ViewerConstants.ITEM_REAL]; + BasicAnimationProvider provider = item.getAnimation(); + if (provider instanceof ActorAnimationProvider) { + ActorAnimationProvider anim = (ActorAnimationProvider)provider; + anim.setLighting(dayTime); + } + item.repaint(); + } + + /** Returns the allegiance of the specified EA value. */ + protected Allegiance getAllegiance(int ea) + { + if (ea >= 2 && ea <= 30) { + return Allegiance.GOOD; + } else if (ea >= 200) { + return Allegiance.ENEMY; + } else { + return Allegiance.NEUTRAL; } } + + /** + * Creates an {@code AnimationProvider} object and initializes it with the creature animation defined by the + * specified CRE resource. + * @param cre the CRE resource + * @return a initialized {@link ActorAnimationProvider} instance + * @throws Exception if animation provider could not be created or initialized. + */ + protected ActorAnimationProvider createAnimationProvider(CreResource cre) throws Exception + { + ActorAnimationProvider retVal = null; + SpriteDecoder decoder = null; + + final int maskDeath = 0xfc0; // actor is dead? + final int maskSleep = 0x1; // actor is unconscious? + int status = ((IsNumeric)cre.getAttribute(CreResource.CRE_STATUS)).getValue(); + boolean isDead = (status & maskDeath) != 0; + boolean isUnconscious = (status & maskSleep) != 0; + + // loading SpriteDecoder instance from cache if available + String key = createKey(cre); + if (!SharedResourceCache.contains(SharedResourceCache.Type.ACTOR, key)) { + // create new + decoder = SpriteDecoder.importSprite(cre); + decoder.setSelectionCircleEnabled(Settings.ShowActorSelectionCircle); + decoder.setPersonalSpaceVisible(Settings.ShowActorPersonalSpace); + + SpriteDecoder.Sequence sequence = null; + + // check for special animation sequence + if (isDead) { + sequence = getMatchingSequence(decoder, DEATH_SEQUENCE); + } else if (isUnconscious) { + sequence = getMatchingSequence(decoder, SLEEP_SEQUENCE); + } + + if (sequence == null) { + // improve visualization of flying creatures + if (decoder.getAnimationType() == SpriteDecoder.AnimationType.FLYING && + decoder.isSequenceAvailable(SpriteDecoder.Sequence.WALK)) { + sequence = SpriteDecoder.Sequence.WALK; + } + } + + if (sequence == null) { + // determine default animation sequence to load + sequence = getMatchingSequence(decoder, DEFAULT_SEQUENCE); + } + + if (sequence == null) { + // use first animation sequence if no default sequence is available + sequence = getMatchingSequence(decoder, null); + } + + if (sequence != null) { + decoder.loadSequence(sequence); + } else { + String creName = ""; + if (cre.getResourceEntry() != null) { + creName = cre.getResourceEntry().getResourceName(); + } else if (cre.getName() != null) { + creName = cre.getName(); + } + throw new UnsupportedOperationException("Could not find animation sequence for CRE: " + creName); + } + + SharedResourceCache.add(SharedResourceCache.Type.ACTOR, key, new ResourceAnimation(key, decoder)); + } else { + // use existing + SharedResourceCache.add(SharedResourceCache.Type.ACTOR, key); + BamDecoder bam = ((ResourceAnimation)SharedResourceCache.get(SharedResourceCache.Type.ACTOR, key)).getData(); + if (bam instanceof SpriteDecoder) { + decoder = (SpriteDecoder)bam; + } else { + throw new Exception("Could not load actor animation"); + } + } + + // initial settings + retVal = new ActorAnimationProvider(decoder); + retVal.setActive(true); + + if (isDead || isUnconscious) { + // using second last frame to avoid glitches for selected creature animations + retVal.setStartFrame(-2); + retVal.setFrameCap(-2); + retVal.setLooping(false); + } else { + retVal.setLooping(true); + } + + return retVal; + } + + /** Returns a key which is identical for all actors based on the same CRE resource. */ + protected static String createKey(CreResource cre) + { + String retVal; + Objects.requireNonNull(cre); + + if (cre.getResourceEntry() == null) { + retVal = ((IsTextual)cre.getAttribute(CreResource.CRE_SCRIPT_NAME)).getText(); + } else { + retVal = cre.getResourceEntry().getResourceName(); + } + + int status = ((IsNumeric)cre.getAttribute(CreResource.CRE_STATUS)).getValue(); + retVal += "?status=" + status; + + return retVal; + } + + /** Returns the first matching animation sequence listed in {@code sequences} that is available in the {@code SpriteDecoder} instance. */ + protected static SpriteDecoder.Sequence getMatchingSequence(SpriteDecoder decoder, SpriteDecoder.Sequence[] sequences) + { + SpriteDecoder.Sequence retVal = null; + if (sequences == null) { + sequences = SpriteDecoder.Sequence.values(); + } + + if (decoder == null || sequences.length == 0) { + return retVal; + } + + for (final SpriteDecoder.Sequence seq : sequences) { + if (decoder.isSequenceAvailable(seq)) { + retVal = seq; + break; + } + } + + return retVal; + } + //
} diff --git a/src/org/infinity/resource/are/viewer/LayerObjectAnimation.java b/src/org/infinity/resource/are/viewer/LayerObjectAnimation.java index 79a6160c2..d296ee818 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectAnimation.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectAnimation.java @@ -208,10 +208,10 @@ public void close() Object key = items[i].getData(); if (key != null) { switch (i) { - case ViewerConstants.ANIM_ITEM_ICON: + case ViewerConstants.ITEM_ICON: SharedResourceCache.remove(SharedResourceCache.Type.ICON, key); break; - case ViewerConstants.ANIM_ITEM_REAL: + case ViewerConstants.ITEM_REAL: SharedResourceCache.remove(SharedResourceCache.Type.ANIMATION, key); break; } @@ -233,7 +233,7 @@ public Viewable getViewable() @Override public AbstractLayerItem getLayerItem(int type) { - type = (type == ViewerConstants.ANIM_ITEM_REAL) ? ViewerConstants.ANIM_ITEM_REAL : ViewerConstants.ANIM_ITEM_ICON; + type = (type == ViewerConstants.ITEM_REAL) ? ViewerConstants.ITEM_REAL : ViewerConstants.ITEM_ICON; return items[type]; } @@ -249,7 +249,7 @@ public void update(double zoomFactor) for (int i = 0; i < items.length; i++) { items[i].setItemLocation((int)(location.x*zoomFactor + (zoomFactor / 2.0)), (int)(location.y*zoomFactor + (zoomFactor / 2.0))); - if (i == ViewerConstants.ANIM_ITEM_REAL) { + if (i == ViewerConstants.ITEM_REAL) { ((AnimatedLayerItem)items[i]).setZoomFactor(zoomFactor); } } @@ -274,7 +274,7 @@ public boolean isScheduled(int schedule) */ public void setLighting(int dayTime) { - AnimatedLayerItem item = (AnimatedLayerItem)items[ViewerConstants.ANIM_ITEM_REAL]; + AnimatedLayerItem item = (AnimatedLayerItem)items[ViewerConstants.ITEM_REAL]; BasicAnimationProvider provider = item.getAnimation(); if (provider instanceof BackgroundAnimationProvider) { BackgroundAnimationProvider anim = (BackgroundAnimationProvider)provider; diff --git a/src/org/infinity/resource/are/viewer/LayerObjectAreActor.java b/src/org/infinity/resource/are/viewer/LayerObjectAreActor.java index c17c560fc..31f50dd52 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectAreActor.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectAreActor.java @@ -6,6 +6,7 @@ import java.awt.Image; import java.awt.Point; +import java.util.EnumMap; import org.infinity.datatype.Flag; import org.infinity.datatype.IsNumeric; @@ -13,6 +14,7 @@ import org.infinity.datatype.ResourceRef; import org.infinity.datatype.TextString; import org.infinity.gui.layeritem.AbstractLayerItem; +import org.infinity.gui.layeritem.AnimatedLayerItem; import org.infinity.gui.layeritem.IconLayerItem; import org.infinity.icon.Icons; import org.infinity.resource.ResourceFactory; @@ -28,12 +30,14 @@ */ public class LayerObjectAreActor extends LayerObjectActor { - private static final Image[] ICONS_GOOD = {Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_ARE_ACTOR_G_1), - Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_ARE_ACTOR_G_2)}; - private static final Image[] ICONS_NEUTRAL = {Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_ARE_ACTOR_B_1), - Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_ARE_ACTOR_B_2)}; - private static final Image[] ICONS_EVIL = {Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_ARE_ACTOR_R_1), - Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_ARE_ACTOR_R_2)}; + private static EnumMap ICONS = new EnumMap(Allegiance.class) {{ + put(Allegiance.GOOD, new Image[] {Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_ARE_ACTOR_G_1), + Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_ARE_ACTOR_G_2)}); + put(Allegiance.NEUTRAL, new Image[] {Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_ARE_ACTOR_B_1), + Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_ARE_ACTOR_B_2)}); + put(Allegiance.ENEMY, new Image[] {Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_ARE_ACTOR_R_1), + Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_ARE_ACTOR_R_2)}); + }}; private static final Point CENTER = new Point(12, 40); private final Actor actor; @@ -43,14 +47,18 @@ public LayerObjectAreActor(AreResource parent, Actor actor) { super(Actor.class, parent); this.actor = actor; + String actorName = null; String actorCreName = null; - Image[] icons = ICONS_NEUTRAL; + Image[] icons = ICONS.get(Allegiance.NEUTRAL); int ea = 128; // default: neutral + ActorAnimationProvider sprite = null; try { + // initializations actorName = ((IsTextual)actor.getAttribute(Actor.ARE_ACTOR_NAME)).getText(); location.x = ((IsNumeric)actor.getAttribute(Actor.ARE_ACTOR_POS_X)).getValue(); location.y = ((IsNumeric)actor.getAttribute(Actor.ARE_ACTOR_POS_Y)).getValue(); + int orientation = ((IsNumeric)actor.getAttribute(Actor.ARE_ACTOR_ORIENTATION)).getValue(); scheduleFlags = ((Flag)actor.getAttribute(Actor.ARE_ACTOR_PRESENT_AT)); @@ -70,28 +78,41 @@ public LayerObjectAreActor(AreResource parent, Actor actor) actorCreName = cre.getAttribute(CreResource.CRE_NAME).toString(); ea = ((IsNumeric)cre.getAttribute(CreResource.CRE_ALLEGIANCE)).getValue(); } - if (ea >= 2 && ea <= 30) { - icons = ICONS_GOOD; - } else if (ea >= 200) { - icons = ICONS_EVIL; - } else { - icons = ICONS_NEUTRAL; - } + + sprite = createAnimationProvider(cre); + sprite.setOrientation(orientation); } catch (Exception e) { e.printStackTrace(); } // Using cached icons + icons = ICONS.get(getAllegiance(ea)); icons = getIcons(icons); - final String msg = actorCreName == null - ? actorName - : actorCreName + " (" + actorName + ')'; - item = new IconLayerItem(actor, msg, icons[0], CENTER); - item.setLabelEnabled(Settings.ShowLabelActorsAre); - item.setName(getCategory()); - item.setImage(AbstractLayerItem.ItemState.HIGHLIGHTED, icons[1]); - item.setVisible(isVisible()); + final String msg = (actorCreName == null) ? actorName : actorCreName + " (" + actorName + ')'; + + IconLayerItem item1 = new IconLayerItem(actor, msg, icons[0], CENTER); + item1.setLabelEnabled(Settings.ShowLabelActorsAre); + item1.setName(getCategory()); + item1.setToolTipText(msg); + item1.setImage(AbstractLayerItem.ItemState.HIGHLIGHTED, icons[1]); + item1.setVisible(isVisible()); + items[0] = item1; + + AnimatedLayerItem item2 = new AnimatedLayerItem(actor, msg, sprite); + item2.setName(getCategory()); + item2.setToolTipText(msg); + item2.setVisible(false); + item2.setFrameRate(10.0); + item2.setAutoPlay(false); + item2.setComposite(Settings.UseActorAccurateBlending ? sprite.getDecoder().getComposite() : null); + item2.setFrameColor(AbstractLayerItem.ItemState.NORMAL, COLOR_FRAME_NORMAL); + item2.setFrameWidth(AbstractLayerItem.ItemState.NORMAL, 2); + item2.setFrameEnabled(AbstractLayerItem.ItemState.NORMAL, false); + item2.setFrameColor(AbstractLayerItem.ItemState.HIGHLIGHTED, COLOR_FRAME_HIGHLIGHTED); + item2.setFrameWidth(AbstractLayerItem.ItemState.HIGHLIGHTED, 2); + item2.setFrameEnabled(AbstractLayerItem.ItemState.HIGHLIGHTED, true); + items[1] = item2; } // diff --git a/src/org/infinity/resource/are/viewer/LayerObjectIniActor.java b/src/org/infinity/resource/are/viewer/LayerObjectIniActor.java index d3aa792fd..66cab3f0a 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectIniActor.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectIniActor.java @@ -6,9 +6,12 @@ import java.awt.Image; import java.awt.Point; +import java.util.EnumMap; +import java.util.Objects; import org.infinity.datatype.IsNumeric; import org.infinity.gui.layeritem.AbstractLayerItem; +import org.infinity.gui.layeritem.AnimatedLayerItem; import org.infinity.gui.layeritem.IconLayerItem; import org.infinity.icon.Icons; import org.infinity.resource.ResourceFactory; @@ -25,44 +28,43 @@ */ public class LayerObjectIniActor extends LayerObjectActor { - private static final Image[] ICONS_GOOD = {Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_INI_ACTOR_G_1), - Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_INI_ACTOR_G_2)}; - private static final Image[] ICONS_NEUTRAL = {Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_INI_ACTOR_B_1), - Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_INI_ACTOR_B_2)}; - private static final Image[] ICONS_EVIL = {Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_INI_ACTOR_R_1), - Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_INI_ACTOR_R_2)}; + private static EnumMap ICONS = new EnumMap(Allegiance.class) {{ + put(Allegiance.GOOD, new Image[] {Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_INI_ACTOR_G_1), + Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_INI_ACTOR_G_2)}); + put(Allegiance.NEUTRAL, new Image[] {Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_INI_ACTOR_B_1), + Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_INI_ACTOR_B_2)}); + put(Allegiance.ENEMY, new Image[] {Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_INI_ACTOR_R_1), + Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_INI_ACTOR_R_2)}); + + }}; private static final Point CENTER = new Point(12, 40); private final PlainTextResource ini; + private final IniMapSection creData; + private final int creIndex; + /** + * Creates a new {@code LayerObjectIniActor} instance. + * @param ini INI resource containing actor definitiuons + * @param creData the INI section relevant for this actor definition + * @param creIndex the spawn point location index for this creature + * @throws IllegalArgumentException + */ public LayerObjectIniActor(PlainTextResource ini, IniMapSection creData, int creIndex) throws IllegalArgumentException { super(CreResource.class, null); - this.ini = ini; - // preparations - IniMapEntry entrySpec = creData.getEntry("spec"); - int[] object = (entrySpec != null) ? IniMapEntry.splitObjectValue(entrySpec.getValue()) : null; - - IniMapEntry entryPoint = creData.getEntry("spawn_point"); - if (entryPoint == null) { - throw new IllegalArgumentException(creData.getName() + ": Invalid spawn point - entry \"spawn_point\" not found in .INI"); - } - String[] position = IniMapEntry.splitValues(entryPoint.getValue(), IniMapEntry.REGEX_POSITION); - if (position == null || creIndex >= position.length) { - throw new IllegalArgumentException(creData.getName() + ": Invalid spawn point index (" + creIndex + ")"); - } - int[] pos = IniMapEntry.splitPositionValue(position[creIndex]); - if (pos == null || pos.length < 2) { - throw new IllegalArgumentException(creData.getName() + ": Invalid spawn point value #" + creIndex); - } + this.ini = Objects.requireNonNull(ini); + this.creData = Objects.requireNonNull(creData); + this.creIndex = creIndex; - String sectionName = creData.getName(); + // preparations String[] creNames = IniMapEntry.splitValues(creData.getEntry("cre_file").getValue()); String creName = (creNames.length > 0) ? (creNames[0] + ".cre") : null; ResourceEntry creEntry = ResourceFactory.getResourceEntry(creName); if (creEntry == null) { throw new IllegalArgumentException(creData.getName() + ": Invalid CRE resref (" + creName + ")"); } + ActorAnimationProvider sprite = null; CreResource cre = null; try { cre = new CreResource(creEntry); @@ -72,34 +74,67 @@ public LayerObjectIniActor(PlainTextResource ini, IniMapSection creData, int cre } // initializations + String sectionName = creData.getName(); final String msg = cre.getAttribute(CreResource.CRE_NAME).toString() + " [" + sectionName + "]"; - int ea = ((IsNumeric)cre.getAttribute(CreResource.CRE_ALLEGIANCE)).getValue(); + + int[] pos = getCreatureLocation(); location.x = pos[0]; location.y = pos[1]; + int orientation = (pos.length > 2) ? pos[2] : 0; - // checking for overridden allegiance + // setting creature allegiance + int ea = ((IsNumeric)cre.getAttribute(CreResource.CRE_ALLEGIANCE)).getValue(); + + IniMapEntry entrySpec = creData.getEntry("spec"); + int[] object = (entrySpec != null) ? IniMapEntry.splitObjectValue(entrySpec.getValue()) : null; if (object != null && object.length > 0 && object[0] != 0) { ea = object[0]; } - Image[] icons; - if (ea >= 2 && ea <= 30) { - icons = ICONS_GOOD; - } else if (ea >= 200) { - icons = ICONS_EVIL; - } else { - icons = ICONS_NEUTRAL; + try { + sprite = createAnimationProvider(cre); + sprite.setOrientation(orientation); + } catch (Exception e) { + e.printStackTrace(); } // Using cached icons + Image[] icons = ICONS.get(getAllegiance(ea)); icons = getIcons(icons); ini.setHighlightedLine(creData.getLine() + 1); - item = new IconLayerItem(ini, msg, icons[0], CENTER); - item.setLabelEnabled(Settings.ShowLabelActorsIni); - item.setName(getCategory()); - item.setImage(AbstractLayerItem.ItemState.HIGHLIGHTED, icons[1]); - item.setVisible(isVisible()); + IconLayerItem item1 = new IconLayerItem(ini, msg, icons[0], CENTER); + item1.setLabelEnabled(Settings.ShowLabelActorsIni); + item1.setName(getCategory()); + item1.setToolTipText(msg); + item1.setImage(AbstractLayerItem.ItemState.HIGHLIGHTED, icons[1]); + item1.setVisible(isVisible()); + items[0] = item1; + + AnimatedLayerItem item2 = new AnimatedLayerItem(ini, msg, sprite); + item2.setName(getCategory()); + item2.setToolTipText(msg); + item2.setVisible(false); + item2.setFrameRate(10.0); + item2.setAutoPlay(false); + item2.setComposite(Settings.UseActorAccurateBlending ? sprite.getDecoder().getComposite() : null); + item2.setFrameColor(AbstractLayerItem.ItemState.NORMAL, COLOR_FRAME_NORMAL); + item2.setFrameWidth(AbstractLayerItem.ItemState.NORMAL, 2); + item2.setFrameEnabled(AbstractLayerItem.ItemState.NORMAL, false); + item2.setFrameColor(AbstractLayerItem.ItemState.HIGHLIGHTED, COLOR_FRAME_HIGHLIGHTED); + item2.setFrameWidth(AbstractLayerItem.ItemState.HIGHLIGHTED, 2); + item2.setFrameEnabled(AbstractLayerItem.ItemState.HIGHLIGHTED, true); + items[1] = item2; + } + + public IniMapSection getCreatureData() + { + return creData; + } + + public int getCreatureIndex() + { + return creIndex; } // @@ -114,5 +149,31 @@ public boolean isScheduled(int schedule) { return true; // always active } + + // Returns position and orientation of the current creature + private int[] getCreatureLocation() throws IllegalArgumentException + { + int[] retVal = {0, 0, 0}; + + IniMapEntry entryPoint = creData.getEntry("spawn_point"); + if (entryPoint == null) { + throw new IllegalArgumentException(creData.getName() + ": Invalid spawn point - entry \"spawn_point\" not found in .INI"); + } + + String[] items = IniMapEntry.splitValues(entryPoint.getValue(), IniMapEntry.REGEX_POSITION); + if (items == null || creIndex >= items.length) { + throw new IllegalArgumentException(creData.getName() + ": Invalid spawn point index (" + creIndex + ")"); + } + + int[] pos = IniMapEntry.splitPositionValue(items[creIndex]); + if (pos == null || pos.length < 2) { + throw new IllegalArgumentException(creData.getName() + ": Invalid spawn point value #" + creIndex); + } + + System.arraycopy(pos, 0, retVal, 0, Math.min(retVal.length, pos.length)); + + return retVal; + } + // } diff --git a/src/org/infinity/resource/are/viewer/Settings.java b/src/org/infinity/resource/are/viewer/Settings.java index 834f33671..1849c083b 100644 --- a/src/org/infinity/resource/are/viewer/Settings.java +++ b/src/org/infinity/resource/are/viewer/Settings.java @@ -67,16 +67,26 @@ public class Settings public static boolean EnableSchedules = getDefaultEnableSchedules(); // Defines whether to ignore the "Is shown" flag of background animations public static boolean OverrideAnimVisibility = getDefaultOverrideAnimVisibility(); + // Defines whether to draw the selection circle underneath actor sprites + public static boolean ShowActorSelectionCircle = getDefaultActorSelectionCircle(); + // Defines whether to draw the personal space indicator underneath actor sprites + public static boolean ShowActorPersonalSpace = getDefaultActorPersonalSpace(); + // Defines whether accurate sprite filtering is used for actors with advanced blending modes + public static boolean UseActorAccurateBlending = getDefaultActorAccurateBlending(); // Bitmask that controls the collapsed/expanded state of the sidebar controls public static int SidebarControls = getDefaultSidebarControls(); + // Indicates whether to show frames around real actor sprites all the time + public static int ShowActorFrame = getDefaultShowActorFrame(); // Indicates whether to show frames around real background animations all the time - public static int ShowFrame = getDefaultShowFrame(); + public static int ShowAnimationFrame = getDefaultShowAnimationFrame(); // Interpolation state of map tileset public static int InterpolationMap = getDefaultInterpolationMap(); // Interpolation state of real background animations public static int InterpolationAnim = getDefaultInterpolationAnim(); // Bitmask defining the enabled state of layer items public static int LayerFlags = getDefaultLayerFlags(); + // The visibility state of real actor sprites (icons/still/animated) + public static int ShowActorSprites = getDefaultShowRealActors(); // The visibility state of real background animations (icons/still/animated) public static int ShowRealAnimations = getDefaultShowRealAnimations(); // The current time of day (in hours) @@ -112,11 +122,16 @@ public class Settings private static final String PREFS_DRAWOVERLAYS = "DrawOverlays"; private static final String PREFS_DRAWGRID = "DrawGrid"; private static final String PREFS_SIDEBARCONTROLS = "SidebarControls"; - private static final String PREFS_SHOWFRAME = "ShowFrame"; + private static final String PREFS_SHOWACTORFRAME = "ShowActorFrame"; + private static final String PREFS_SHOWACTORSELECTION = "ShowActorSelectionCircle"; + private static final String PREFS_SHOWACTORSPACE = "ShowActorPersonalSpace"; + private static final String PREFS_USEACTORBLENDING = "UseActorAccurateBlending"; + private static final String PREFS_SHOWANIMFRAME = "ShowFrame"; private static final String PREFS_SHOWAMBIENT = "ShowAmbientRanges"; private static final String PREFS_ENABLESCHEDULES = "EnableSchedules"; private static final String PREFS_OVERRIDEANIMVISIBILITY = "OverrideAnimVisibility"; private static final String PREFS_LAYERFLAGS = "LayerFlags"; + private static final String PREFS_SHOWREALACTORS = "ShowRealActors"; private static final String PREFS_SHOWREALANIMS = "ShowRealAnimations"; private static final String PREFS_TIMEOFDAY = "TimeOfDay"; private static final String PREFS_ZOOMFACTOR = "ZoomFactor"; @@ -155,7 +170,11 @@ public static void loadSettings(boolean force) ExportLayers = prefs.getBoolean(PREFS_EXPORTLAYERS,getDefaultExportLayers()); MouseWheelZoom = prefs.getBoolean(PREFS_MOUSEWHEELZOOM,getDefaultMouseWheelZoom()); OverrideAnimVisibility = prefs.getBoolean(PREFS_OVERRIDEANIMVISIBILITY, getDefaultOverrideAnimVisibility()); - ShowFrame = prefs.getInt(PREFS_SHOWFRAME, getDefaultShowFrame()); + ShowActorFrame = prefs.getInt(PREFS_SHOWACTORFRAME, getDefaultShowActorFrame()); + ShowActorSelectionCircle = prefs.getBoolean(PREFS_SHOWACTORSELECTION, getDefaultActorSelectionCircle()); + ShowActorPersonalSpace = prefs.getBoolean(PREFS_SHOWACTORSPACE, getDefaultActorPersonalSpace()); + UseActorAccurateBlending = prefs.getBoolean(PREFS_USEACTORBLENDING, getDefaultActorAccurateBlending()); + ShowAnimationFrame = prefs.getInt(PREFS_SHOWANIMFRAME, getDefaultShowAnimationFrame()); InterpolationMap = prefs.getInt(PREFS_INTERPOLATION_MAP, getDefaultInterpolationMap()); InterpolationAnim = prefs.getInt(PREFS_INTERPOLATION_ANIMS, getDefaultInterpolationAnim()); FrameRateOverlays = prefs.getDouble(PREFS_FRAMERATE_OVERLAYS, getDefaultFrameRateOverlays()); @@ -192,6 +211,7 @@ public static void loadSettings(boolean force) ShowAmbientRanges = prefs.getBoolean(PREFS_SHOWAMBIENT, getDefaultAmbientRanges()); SidebarControls = prefs.getInt(PREFS_SIDEBARCONTROLS, getDefaultSidebarControls()); LayerFlags = prefs.getInt(PREFS_LAYERFLAGS, getDefaultLayerFlags()); + ShowActorSprites = prefs.getInt(PREFS_SHOWREALACTORS, getDefaultShowRealActors()); ShowRealAnimations = prefs.getInt(PREFS_SHOWREALANIMS, getDefaultShowRealAnimations()); TimeOfDay = prefs.getInt(PREFS_TIMEOFDAY, getDefaultTimeOfDay()); ZoomFactor = prefs.getDouble(PREFS_ZOOMFACTOR, getDefaultZoomFactor()); @@ -217,7 +237,11 @@ public static void storeSettings(boolean force) prefs.putBoolean(PREFS_EXPORTLAYERS, ExportLayers); prefs.putBoolean(PREFS_MOUSEWHEELZOOM, MouseWheelZoom); prefs.putBoolean(PREFS_OVERRIDEANIMVISIBILITY, OverrideAnimVisibility); - prefs.putInt(PREFS_SHOWFRAME, ShowFrame); + prefs.putInt(PREFS_SHOWACTORFRAME, ShowActorFrame); + prefs.putBoolean(PREFS_SHOWACTORSELECTION, ShowActorSelectionCircle); + prefs.putBoolean(PREFS_SHOWACTORSPACE, ShowActorPersonalSpace); + prefs.putBoolean(PREFS_USEACTORBLENDING, UseActorAccurateBlending); + prefs.putInt(PREFS_SHOWANIMFRAME, ShowAnimationFrame); prefs.putInt(PREFS_INTERPOLATION_MAP, InterpolationMap); prefs.putInt(PREFS_INTERPOLATION_ANIMS, InterpolationAnim); prefs.putDouble(PREFS_FRAMERATE_OVERLAYS, FrameRateOverlays); @@ -248,6 +272,7 @@ public static void storeSettings(boolean force) prefs.putBoolean(PREFS_SHOWAMBIENT, ShowAmbientRanges); prefs.putInt(PREFS_SIDEBARCONTROLS, SidebarControls); prefs.putInt(PREFS_LAYERFLAGS, LayerFlags); + prefs.putInt(PREFS_SHOWREALACTORS, ShowActorSprites); prefs.putInt(PREFS_SHOWREALANIMS, ShowRealAnimations); prefs.putInt(PREFS_TIMEOFDAY, TimeOfDay); prefs.putDouble(PREFS_ZOOMFACTOR, ZoomFactor); @@ -269,6 +294,8 @@ private static void validateSettings() SidebarControls &= (ViewerConstants.SIDEBAR_VISUALSTATE | ViewerConstants.SIDEBAR_LAYERS | ViewerConstants.SIDEBAR_MINIMAPS); + ShowActorSprites = Math.min(Math.max(ShowActorSprites, ViewerConstants.ANIM_SHOW_NONE), + ViewerConstants.ANIM_SHOW_ANIMATED); ShowRealAnimations = Math.min(Math.max(ShowRealAnimations, ViewerConstants.ANIM_SHOW_NONE), ViewerConstants.ANIM_SHOW_ANIMATED); TimeOfDay = Math.min(Math.max(TimeOfDay, ViewerConstants.TIME_0), ViewerConstants.TIME_23); @@ -362,12 +389,32 @@ public static boolean getDefaultOverrideAnimVisibility() return false; } + public static boolean getDefaultActorSelectionCircle() + { + return true; + } + + public static boolean getDefaultActorPersonalSpace() + { + return false; + } + + public static boolean getDefaultActorAccurateBlending() + { + return true; + } + public static int getDefaultSidebarControls() { return ViewerConstants.SIDEBAR_VISUALSTATE | ViewerConstants.SIDEBAR_LAYERS | ViewerConstants.SIDEBAR_MINIMAPS; } - public static int getDefaultShowFrame() + public static int getDefaultShowActorFrame() + { + return ViewerConstants.FRAME_AUTO; + } + + public static int getDefaultShowAnimationFrame() { return ViewerConstants.FRAME_AUTO; } @@ -387,6 +434,11 @@ public static int getDefaultLayerFlags() return 0; } + public static int getDefaultShowRealActors() + { + return ViewerConstants.ANIM_SHOW_NONE; + } + public static int getDefaultShowRealAnimations() { return ViewerConstants.ANIM_SHOW_NONE; diff --git a/src/org/infinity/resource/are/viewer/SettingsDialog.java b/src/org/infinity/resource/are/viewer/SettingsDialog.java index 6b79fa7e0..8d5063e23 100644 --- a/src/org/infinity/resource/are/viewer/SettingsDialog.java +++ b/src/org/infinity/resource/are/viewer/SettingsDialog.java @@ -76,9 +76,9 @@ public class SettingsDialog extends JDialog private SimpleListModel modelLayers; private JList listLayers; private JButton bUp, bDown, bDefaultOrder; - private JComboBox cbFrames, cbQualityMap, cbQualityAnim; - private JCheckBox cbOverrideAnimVisibility, cbMouseWheelZoom, cbExportLayers, cbUseColorShades, - cbStoreSettings; + private JComboBox cbActorFrames, cbFrames, cbQualityMap, cbQualityAnim; + private JCheckBox cbShowActorSelectionCircle, cbShowActorPersonalSpace, cbActorAccurateBlending, + cbOverrideAnimVisibility, cbMouseWheelZoom, cbExportLayers, cbUseColorShades, cbStoreSettings; private JButton bDefaultSettings, bCancel, bOK; private JSpinner sOverlaysFps, sAnimationsFps; private JSlider sMiniMapAlpha; @@ -149,7 +149,12 @@ private void updateSettings() Settings.ShowLabelMapNotes = cbLabels[INDEX_LABEL_MAPNOTES].isSelected(); Settings.ShowLabelSpawnPoints = cbLabels[INDEX_LABEL_SPAWNPOINTS].isSelected(); - Settings.ShowFrame = cbFrames.getSelectedIndex(); + Settings.ShowActorFrame = cbActorFrames.getSelectedIndex(); + Settings.ShowActorSelectionCircle = cbShowActorSelectionCircle.isSelected(); + Settings.ShowActorPersonalSpace = cbShowActorPersonalSpace.isSelected(); + Settings.UseActorAccurateBlending = cbActorAccurateBlending.isSelected(); + + Settings.ShowAnimationFrame = cbFrames.getSelectedIndex(); Settings.OverrideAnimVisibility = cbOverrideAnimVisibility.isSelected(); Settings.InterpolationMap = cbQualityMap.getSelectedIndex(); @@ -202,7 +207,12 @@ private void resetDialogSettings() cbLabels[INDEX_LABEL_MAPNOTES].setSelected(Settings.getDefaultLabelMapNotes()); cbLabels[INDEX_LABEL_SPAWNPOINTS].setSelected(Settings.getDefaultLabelSpawnPoints()); - cbFrames.setSelectedIndex(Settings.getDefaultShowFrame()); + cbActorFrames.setSelectedIndex(Settings.getDefaultShowActorFrame()); + cbShowActorSelectionCircle.setSelected(Settings.getDefaultActorSelectionCircle()); + cbShowActorPersonalSpace.setSelected(Settings.getDefaultActorPersonalSpace()); + cbShowActorSelectionCircle.setSelected(Settings.getDefaultActorAccurateBlending()); + + cbFrames.setSelectedIndex(Settings.getDefaultShowAnimationFrame()); cbOverrideAnimVisibility.setSelected(Settings.getDefaultOverrideAnimVisibility()); cbQualityMap.setSelectedIndex(Settings.getDefaultInterpolationMap()); @@ -365,14 +375,43 @@ private void init() pShowLabels.add(cbLabels[idx], c); } + // Actor animation options + JPanel pShowActorFrame = new JPanel(new GridBagLayout()); + pShowActorFrame.setBorder(BorderFactory.createTitledBorder("Actor animations: ")); + JLabel lActorFrames = new JLabel("Show frame:"); + cbActorFrames = new JComboBox<>(AnimationFrames); + cbActorFrames.setSelectedIndex(Settings.ShowActorFrame); + cbShowActorSelectionCircle = new JCheckBox("Draw selection circle", Settings.ShowActorSelectionCircle); + cbShowActorSelectionCircle.setToolTipText("Requires a restart of the area viewer or a map update via toolbar button."); + cbShowActorPersonalSpace = new JCheckBox("Draw personal space indicator", Settings.ShowActorPersonalSpace); + cbShowActorPersonalSpace.setToolTipText("Requires a restart of the area viewer or a map update via toolbar button."); + cbActorAccurateBlending = new JCheckBox("Enable accurate color blending", Settings.UseActorAccurateBlending); + cbActorAccurateBlending.setToolTipText("Creature animations with special blending modes (such as gaseous forms or wisps)
" + + "can reduce overall performance of the area viewer. Disable to improve performance.
" + + "Requires a restart of the area viewer or a map update via toolbar button."); + c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.NONE, new Insets(4, 4, 4, 0), 0, 0); + pShowActorFrame.add(lActorFrames, c); + c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(4, 8, 0, 4), 0, 0); + pShowActorFrame.add(cbActorFrames, c); + c = ViewerUtil.setGBC(c, 0, 1, 2, 1, 1.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.NONE, new Insets(4, 4, 0, 4), 0, 0); + pShowActorFrame.add(cbShowActorSelectionCircle, c); + c = ViewerUtil.setGBC(c, 0, 2, 2, 1, 1.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.NONE, new Insets(4, 4, 0, 4), 0, 0); + pShowActorFrame.add(cbShowActorPersonalSpace, c); + c = ViewerUtil.setGBC(c, 0, 3, 2, 1, 1.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.NONE, new Insets(4, 4, 4, 4), 0, 0); + pShowActorFrame.add(cbActorAccurateBlending, c); // Background animation frame JPanel pShowFrame = new JPanel(new GridBagLayout()); pShowFrame.setBorder(BorderFactory.createTitledBorder("Background animations: ")); JLabel lFrames = new JLabel("Show frame:"); cbFrames = new JComboBox<>(AnimationFrames); - cbFrames.setSelectedIndex(Settings.ShowFrame); - cbOverrideAnimVisibility = new JCheckBox("Show animations regardless of their active state", Settings.OverrideAnimVisibility); + cbFrames.setSelectedIndex(Settings.ShowAnimationFrame); + cbOverrideAnimVisibility = new JCheckBox("Show background animations regardless of their active state", Settings.OverrideAnimVisibility); cbOverrideAnimVisibility.setToolTipText("Requires a restart of the area viewer or a map update via toolbar button."); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(4, 4, 4, 0), 0, 0); @@ -521,28 +560,28 @@ private void init() // putting options together JPanel pCol2 = new JPanel(new GridBagLayout()); -// c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, -// GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); -// pOptions.add(pShowLabels, c); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); - pCol2.add(pShowFrame, c); + pCol2.add(pShowActorFrame, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 0, 0); - pCol2.add(pQuality, c); + pCol2.add(pShowFrame, c); c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 0, 0); - pCol2.add(pFrameRates, c); + pCol2.add(pQuality, c); c = ViewerUtil.setGBC(c, 0, 3, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 0, 0); - pCol2.add(pMiniMap, c); + pCol2.add(pFrameRates, c); c = ViewerUtil.setGBC(c, 0, 4, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 0, 0); + pCol2.add(pMiniMap, c); + c = ViewerUtil.setGBC(c, 0, 5, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 0, 0); pCol2.add(pMisc, c); - c = ViewerUtil.setGBC(c, 0, 5, 1, 1, 1.0, 1.0, GridBagConstraints.LINE_START, + c = ViewerUtil.setGBC(c, 0, 6, 1, 1, 1.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0); pCol2.add(new JPanel(), c); - c = ViewerUtil.setGBC(c, 0, 6, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, + c = ViewerUtil.setGBC(c, 0, 7, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); pCol2.add(pButtons, c); diff --git a/src/org/infinity/resource/are/viewer/SharedResourceCache.java b/src/org/infinity/resource/are/viewer/SharedResourceCache.java index 2a4e07fa0..ec7901005 100644 --- a/src/org/infinity/resource/are/viewer/SharedResourceCache.java +++ b/src/org/infinity/resource/are/viewer/SharedResourceCache.java @@ -15,17 +15,27 @@ public class SharedResourceCache { // Identifies the type of cache object to retrieve public static enum Type { - ICON, ANIMATION/*, Creature*/ + ICON, + ANIMATION, + ACTOR } private static EnumMap> tables = new EnumMap>(Type.class); + static { - tables.put(Type.ICON, new HashMap()); - tables.put(Type.ANIMATION, new HashMap()); -// tables.put(Type.Creature, new HashMap()); + for (final Type type : Type.values()) { + tables.put(type, new HashMap()); + } } + /** Removes all entries from the cache. */ + public static void clearCache() + { + for (final Type type : Type.values()) { + tables.get(type).clear(); + } + } /** * Generates a simple key from the hash code of the specified object. diff --git a/src/org/infinity/resource/are/viewer/TilesetRenderer.java b/src/org/infinity/resource/are/viewer/TilesetRenderer.java index 256358ff6..af3b7b269 100644 --- a/src/org/infinity/resource/are/viewer/TilesetRenderer.java +++ b/src/org/infinity/resource/are/viewer/TilesetRenderer.java @@ -180,9 +180,12 @@ public WedResource getWed() /** * Removes the current map and all associated data from memory. */ - public void clear() + public void dispose() { release(true); + if (getImage() instanceof VolatileImage) { + ((VolatileImage)getImage()).flush(); + } } /** @@ -634,9 +637,12 @@ private void release(boolean forceUpdate) if (img != null) { if (forceUpdate) { Graphics2D g = (Graphics2D)img.getGraphics(); - g.setBackground(new Color(0, true)); - g.clearRect(0, 0, img.getWidth(null), img.getHeight(null)); - g.dispose(); + try { + g.setBackground(new Color(0, true)); + g.clearRect(0, 0, img.getWidth(null), img.getHeight(null)); + } finally { + g.dispose(); + } repaint(); } } diff --git a/src/org/infinity/resource/are/viewer/ViewerConstants.java b/src/org/infinity/resource/are/viewer/ViewerConstants.java index 3222fb988..34d4bde13 100644 --- a/src/org/infinity/resource/are/viewer/ViewerConstants.java +++ b/src/org/infinity/resource/are/viewer/ViewerConstants.java @@ -57,11 +57,11 @@ public static enum LayerStackingType {ACTOR, REGION, ENTRANCE, CONTAINER, AMBIEN public static final Object TYPE_BILINEAR = RenderingHints.VALUE_INTERPOLATION_BILINEAR; public static final Object TYPE_BICUBIC = RenderingHints.VALUE_INTERPOLATION_BICUBIC; - // Specifies the item type for animation objects (LayerObjectAnimation) - public static final int ANIM_ITEM_ICON = 0; - public static final int ANIM_ITEM_REAL = 1; + // Specifies the item type for objects with icons and real animations + public static final int ITEM_ICON = 0; + public static final int ITEM_REAL = 1; - // Different states of showing background animations (AreaViewer) + // Different states of showing real animations (AreaViewer) public static final int ANIM_SHOW_NONE = 0; public static final int ANIM_SHOW_STILL = 1; public static final int ANIM_SHOW_ANIMATED = 2; From c58770b68657f4786192d1226c8484c2a7bbcc18 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Tue, 2 Mar 2021 11:59:37 +0100 Subject: [PATCH 026/158] Area viewer: Speed up map loading by loading actor animations on demand --- src/org/infinity/gui/WindowBlocker.java | 4 +- .../gui/layeritem/AnimatedLayerItem.java | 47 +--------- .../are/viewer/AbstractAnimationProvider.java | 49 ++++++++++ .../resource/are/viewer/LayerActor.java | 93 +++++++++++++++---- .../resource/are/viewer/LayerObjectActor.java | 22 +++-- .../are/viewer/LayerObjectAreActor.java | 74 ++++++++++----- .../are/viewer/LayerObjectIniActor.java | 59 ++++++++---- .../resource/graphics/PseudoBamDecoder.java | 1 - 8 files changed, 236 insertions(+), 113 deletions(-) diff --git a/src/org/infinity/gui/WindowBlocker.java b/src/org/infinity/gui/WindowBlocker.java index afea324ce..eef3bd38c 100644 --- a/src/org/infinity/gui/WindowBlocker.java +++ b/src/org/infinity/gui/WindowBlocker.java @@ -61,7 +61,9 @@ public WindowBlocker(RootPaneContainer window) public void setBlocked(boolean blocked) { if (glassPane == null) return; - glassPane.setVisible(blocked); + if (blocked != glassPane.isVisible()) { + glassPane.setVisible(blocked); + } } } diff --git a/src/org/infinity/gui/layeritem/AnimatedLayerItem.java b/src/org/infinity/gui/layeritem/AnimatedLayerItem.java index 7bfd1c737..b5a4a0a0d 100644 --- a/src/org/infinity/gui/layeritem/AnimatedLayerItem.java +++ b/src/org/infinity/gui/layeritem/AnimatedLayerItem.java @@ -15,7 +15,6 @@ import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.awt.image.BufferedImage; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; @@ -25,6 +24,7 @@ import org.infinity.gui.RenderCanvas; import org.infinity.resource.Viewable; +import org.infinity.resource.are.viewer.AbstractAnimationProvider; import org.infinity.resource.are.viewer.ViewerConstants; import org.infinity.resource.graphics.ColorConvert; @@ -474,8 +474,8 @@ private void initAnimation(BasicAnimationProvider anim) if (anim != null) { animation = anim; } else { - if (!(animation instanceof DefaultAnimationProvider)) { - animation = new DefaultAnimationProvider(); + if (!(animation instanceof AbstractAnimationProvider.DefaultAnimationProvider)) { + animation = AbstractAnimationProvider.DEFAULT_ANIMATION_PROVIDER; } } @@ -637,45 +637,4 @@ public void setColor(Color color) this.color = color; } } - - - /** A pseudo animation provider that always returns a transparent image of 16x16 size. */ - private static final class DefaultAnimationProvider implements BasicAnimationProvider - { - private final BufferedImage image; - - public DefaultAnimationProvider() - { - image = ColorConvert.createCompatibleImage(16, 16, true); - } - - @Override - public Image getImage() - { - return image; - } - - @Override - public boolean advanceFrame() - { - return false; - } - - @Override - public void resetFrame() - { - } - - @Override - public boolean isLooping() - { - return false; - } - - @Override - public Point getLocationOffset() - { - return new Point(); - } - } } diff --git a/src/org/infinity/resource/are/viewer/AbstractAnimationProvider.java b/src/org/infinity/resource/are/viewer/AbstractAnimationProvider.java index 2cbf19d22..dd34b0ba8 100644 --- a/src/org/infinity/resource/are/viewer/AbstractAnimationProvider.java +++ b/src/org/infinity/resource/are/viewer/AbstractAnimationProvider.java @@ -5,15 +5,20 @@ package org.infinity.resource.are.viewer; import java.awt.Image; +import java.awt.Point; import java.awt.image.BufferedImage; import org.infinity.gui.layeritem.BasicAnimationProvider; +import org.infinity.resource.graphics.ColorConvert; /** * Provides base functionality for rendering animations. */ public abstract class AbstractAnimationProvider implements BasicAnimationProvider { + /** A default animation provider that can be used as placeholder. */ + public static final DefaultAnimationProvider DEFAULT_ANIMATION_PROVIDER = new DefaultAnimationProvider(); + private BufferedImage image, working; private boolean isActive, isActiveIgnored; @@ -120,4 +125,48 @@ protected void applyLighting(int[] buffer, int cw, int ch, int fw, int fh, int l } } } + + +//-------------------------- INNER CLASSES -------------------------- + + /** A pseudo animation provider that always returns a transparent image of 16x16 size. */ + public static final class DefaultAnimationProvider implements BasicAnimationProvider + { + private final BufferedImage image; + + public DefaultAnimationProvider() + { + image = ColorConvert.createCompatibleImage(16, 16, true); + } + + @Override + public Image getImage() + { + return image; + } + + @Override + public boolean advanceFrame() + { + return false; + } + + @Override + public void resetFrame() + { + } + + @Override + public boolean isLooping() + { + return false; + } + + @Override + public Point getLocationOffset() + { + return new Point(); + } + } + } diff --git a/src/org/infinity/resource/are/viewer/LayerActor.java b/src/org/infinity/resource/are/viewer/LayerActor.java index 4b011477c..69cecbe89 100644 --- a/src/org/infinity/resource/are/viewer/LayerActor.java +++ b/src/org/infinity/resource/are/viewer/LayerActor.java @@ -4,9 +4,15 @@ package org.infinity.resource.are.viewer; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import java.util.List; import java.util.Locale; +import javax.swing.ProgressMonitor; +import javax.swing.SwingWorker; + +import org.infinity.gui.WindowBlocker; import org.infinity.gui.layeritem.AbstractLayerItem; import org.infinity.gui.layeritem.AnimatedLayerItem; import org.infinity.gui.layeritem.IconLayerItem; @@ -24,7 +30,7 @@ /** * Manages actor layer objects. */ -public class LayerActor extends BasicLayer +public class LayerActor extends BasicLayer implements PropertyChangeListener { private static final String AvailableFmt = "Actors: %d"; @@ -32,6 +38,8 @@ public class LayerActor extends BasicLayer private int frameState; private Object interpolationType = ViewerConstants.TYPE_NEAREST_NEIGHBOR; private double frameRate = ViewerConstants.FRAME_AUTO; + private SwingWorker loadWorker; + private WindowBlocker blocker; public LayerActor(AreResource are, AreaViewer viewer) { @@ -89,26 +97,79 @@ public String getAvailability() public void setLayerVisible(boolean visible) { setVisibilityState(visible); - List list = getLayerObjects(); - for (int i = 0, size = list.size(); i < size; i++) { - boolean state = isLayerVisible() && (!isScheduleEnabled() || isScheduled(i)); - LayerObjectActor obj = list.get(i); - IconLayerItem iconItem = (IconLayerItem)obj.getLayerItem(ViewerConstants.ITEM_ICON); - if (iconItem != null) { - iconItem.setVisible(state && !realEnabled); - } - AnimatedLayerItem animItem = (AnimatedLayerItem)obj.getLayerItem(ViewerConstants.ITEM_REAL); - if (animItem != null) { - animItem.setVisible(state && realEnabled); - if (isRealActorEnabled() && isRealActorPlaying()) { - animItem.play(); - } else { - animItem.stop(); + + loadWorker = new SwingWorker() { + @Override + public Void doInBackground() + { + final List list = getLayerObjects(); + final ProgressMonitor progress = new ProgressMonitor(getViewer(), "Loading actor animations...", "0 %", 0, list.size()); + progress.setMillisToDecideToPopup(500); + progress.setMillisToPopup(1000); + + try { + for (int i = 0, size = list.size(); i < size; i++) { + boolean state = isLayerVisible() && (!isScheduleEnabled() || isScheduled(i)); + LayerObjectActor loa = list.get(i); + + IconLayerItem iconItem = (IconLayerItem)loa.getLayerItem(ViewerConstants.ITEM_ICON); + if (iconItem != null) { + iconItem.setVisible(state && !realEnabled); + } + + AnimatedLayerItem animItem = (AnimatedLayerItem)loa.getLayerItem(ViewerConstants.ITEM_REAL); + if (animItem != null) { + if (animItem.getAnimation() == AbstractAnimationProvider.DEFAULT_ANIMATION_PROVIDER && state && realEnabled) { + // real actor animations loaded on demand + if (blocker == null) { + blocker = new WindowBlocker(getViewer()); + blocker.setBlocked(true); + } + loa.loadAnimation(); + } + + animItem.setVisible(state && realEnabled); + if (isRealActorEnabled() && isRealActorPlaying()) { + animItem.play(); + } else { + animItem.stop(); + } + } + progress.setNote(((i + 1) * 100 / size) + " %"); + progress.setProgress(i + 1); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + progress.close(); + if (blocker != null) { + blocker.setBlocked(false); + blocker = null; + } } + return null; + } + }; + + loadWorker.addPropertyChangeListener(this); + loadWorker.execute(); + } + +//--------------------- Begin Interface PropertyChangeListener --------------------- + + @Override + public void propertyChange(PropertyChangeEvent e) + { + if (e.getSource() == loadWorker) { + if ("state".equals(e.getPropertyName()) && + SwingWorker.StateValue.DONE == e.getNewValue()) { + loadWorker = null; } } } +//--------------------- End Interface PropertyChangeListener --------------------- + /** * Returns the currently active interpolation type for real actors. * @return Either one of ViewerConstants.TYPE_NEAREST_NEIGHBOR, ViewerConstants.TYPE_NEAREST_BILINEAR diff --git a/src/org/infinity/resource/are/viewer/LayerObjectActor.java b/src/org/infinity/resource/are/viewer/LayerObjectActor.java index 06d791369..a3ab7c66d 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectActor.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectActor.java @@ -9,7 +9,6 @@ import java.util.Objects; import org.infinity.datatype.IsNumeric; -import org.infinity.datatype.IsTextual; import org.infinity.gui.layeritem.AbstractLayerItem; import org.infinity.gui.layeritem.AnimatedLayerItem; import org.infinity.gui.layeritem.BasicAnimationProvider; @@ -126,6 +125,11 @@ public void update(double zoomFactor) } } + /** + * Loads animation data if it hasn't been loaded yet. + */ + public abstract void loadAnimation(); + /** * Sets the lighting condition of the actor. Does nothing if the actor is flagged as * self-illuminating. @@ -144,7 +148,7 @@ public void setLighting(int dayTime) } /** Returns the allegiance of the specified EA value. */ - protected Allegiance getAllegiance(int ea) + protected static Allegiance getAllegiance(int ea) { if (ea >= 2 && ea <= 30) { return Allegiance.GOOD; @@ -162,8 +166,9 @@ protected Allegiance getAllegiance(int ea) * @return a initialized {@link ActorAnimationProvider} instance * @throws Exception if animation provider could not be created or initialized. */ - protected ActorAnimationProvider createAnimationProvider(CreResource cre) throws Exception + protected static ActorAnimationProvider createAnimationProvider(CreResource cre) throws Exception { + Objects.requireNonNull(cre); ActorAnimationProvider retVal = null; SpriteDecoder decoder = null; @@ -254,10 +259,15 @@ protected static String createKey(CreResource cre) String retVal; Objects.requireNonNull(cre); - if (cre.getResourceEntry() == null) { - retVal = ((IsTextual)cre.getAttribute(CreResource.CRE_SCRIPT_NAME)).getText(); - } else { + if (cre.getResourceEntry() != null) { + // regular CRE resource retVal = cre.getResourceEntry().getResourceName(); + } else if (cre.getParent() != null) { + // CRE attached to ARE > Actor + retVal = cre.getParent().getName(); + } else { + // failsafe + retVal = Integer.toString(cre.hashCode()); } int status = ((IsNumeric)cre.getAttribute(CreResource.CRE_STATUS)).getValue(); diff --git a/src/org/infinity/resource/are/viewer/LayerObjectAreActor.java b/src/org/infinity/resource/are/viewer/LayerObjectAreActor.java index 31f50dd52..9fee5cf2a 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectAreActor.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectAreActor.java @@ -41,6 +41,7 @@ public class LayerObjectAreActor extends LayerObjectActor private static final Point CENTER = new Point(12, 40); private final Actor actor; + private final CreResource cre; private Flag scheduleFlags; public LayerObjectAreActor(AreResource parent, Actor actor) @@ -48,64 +49,55 @@ public LayerObjectAreActor(AreResource parent, Actor actor) super(Actor.class, parent); this.actor = actor; - String actorName = null; - String actorCreName = null; - Image[] icons = ICONS.get(Allegiance.NEUTRAL); int ea = 128; // default: neutral - ActorAnimationProvider sprite = null; + CreResource cre = null; try { // initializations - actorName = ((IsTextual)actor.getAttribute(Actor.ARE_ACTOR_NAME)).getText(); location.x = ((IsNumeric)actor.getAttribute(Actor.ARE_ACTOR_POS_X)).getValue(); location.y = ((IsNumeric)actor.getAttribute(Actor.ARE_ACTOR_POS_Y)).getValue(); - int orientation = ((IsNumeric)actor.getAttribute(Actor.ARE_ACTOR_ORIENTATION)).getValue(); - scheduleFlags = ((Flag)actor.getAttribute(Actor.ARE_ACTOR_PRESENT_AT)); StructEntry obj = actor.getAttribute(Actor.ARE_ACTOR_CHARACTER); - CreResource cre = null; if (obj instanceof TextString) { // ARE in saved game cre = (CreResource)actor.getAttribute(Actor.ARE_ACTOR_CRE_FILE); - } else - if (obj instanceof ResourceRef) { + } + else if (obj instanceof ResourceRef) { final ResourceRef creRef = (ResourceRef)obj; if (!creRef.isEmpty()) { cre = new CreResource(ResourceFactory.getResourceEntry(creRef.getResourceName())); } } + if (cre != null) { - actorCreName = cre.getAttribute(CreResource.CRE_NAME).toString(); ea = ((IsNumeric)cre.getAttribute(CreResource.CRE_ALLEGIANCE)).getValue(); } - - sprite = createAnimationProvider(cre); - sprite.setOrientation(orientation); } catch (Exception e) { e.printStackTrace(); } + this.cre = cre; + // Using cached icons - icons = ICONS.get(getAllegiance(ea)); + Image[] icons = ICONS.get(getAllegiance(ea)); icons = getIcons(icons); - final String msg = (actorCreName == null) ? actorName : actorCreName + " (" + actorName + ')'; - - IconLayerItem item1 = new IconLayerItem(actor, msg, icons[0], CENTER); + String tooltip = getTooltip(); + IconLayerItem item1 = new IconLayerItem(actor, tooltip, icons[0], CENTER); item1.setLabelEnabled(Settings.ShowLabelActorsAre); item1.setName(getCategory()); - item1.setToolTipText(msg); + item1.setToolTipText(tooltip); item1.setImage(AbstractLayerItem.ItemState.HIGHLIGHTED, icons[1]); item1.setVisible(isVisible()); items[0] = item1; - AnimatedLayerItem item2 = new AnimatedLayerItem(actor, msg, sprite); + // payload is initialized on demand + AnimatedLayerItem item2 = new AnimatedLayerItem(actor, tooltip, AbstractAnimationProvider.DEFAULT_ANIMATION_PROVIDER); item2.setName(getCategory()); - item2.setToolTipText(msg); + item2.setToolTipText(tooltip); item2.setVisible(false); - item2.setFrameRate(10.0); + item2.setFrameRate(Settings.getDefaultFrameRateAnimations()); item2.setAutoPlay(false); - item2.setComposite(Settings.UseActorAccurateBlending ? sprite.getDecoder().getComposite() : null); item2.setFrameColor(AbstractLayerItem.ItemState.NORMAL, COLOR_FRAME_NORMAL); item2.setFrameWidth(AbstractLayerItem.ItemState.NORMAL, 2); item2.setFrameEnabled(AbstractLayerItem.ItemState.NORMAL, false); @@ -115,7 +107,6 @@ public LayerObjectAreActor(AreResource parent, Actor actor) items[1] = item2; } - // @Override public Viewable getViewable() { @@ -131,5 +122,38 @@ public boolean isScheduled(int schedule) return false; } } - // + + @Override + public synchronized void loadAnimation() + { + if (items[1] instanceof AnimatedLayerItem) { + AnimatedLayerItem item = (AnimatedLayerItem)items[1]; + if (item.getAnimation() == AbstractAnimationProvider.DEFAULT_ANIMATION_PROVIDER) { + if (cre != null) { + try { + int orientation = ((IsNumeric)actor.getAttribute(Actor.ARE_ACTOR_ORIENTATION)).getValue(); + ActorAnimationProvider sprite = createAnimationProvider(cre); + sprite.setOrientation(orientation); + + item.setAnimation(sprite); + item.setComposite(Settings.UseActorAccurateBlending ? sprite.getDecoder().getComposite() : null); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + } + + /** Tooltip for actor object. */ + private String getTooltip() + { + String retVal = null; + if (cre != null) { + retVal = ((IsTextual)cre.getAttribute(CreResource.CRE_NAME)).getText(); + } else { + retVal = ((IsTextual)actor.getAttribute(Actor.ARE_ACTOR_NAME)).getText(); + } + return retVal; + } } diff --git a/src/org/infinity/resource/are/viewer/LayerObjectIniActor.java b/src/org/infinity/resource/are/viewer/LayerObjectIniActor.java index 66cab3f0a..1267cdc70 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectIniActor.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectIniActor.java @@ -42,6 +42,7 @@ public class LayerObjectIniActor extends LayerObjectActor private final PlainTextResource ini; private final IniMapSection creData; private final int creIndex; + private final CreResource cre; /** * Creates a new {@code LayerObjectIniActor} instance. @@ -64,7 +65,7 @@ public LayerObjectIniActor(PlainTextResource ini, IniMapSection creData, int cre if (creEntry == null) { throw new IllegalArgumentException(creData.getName() + ": Invalid CRE resref (" + creName + ")"); } - ActorAnimationProvider sprite = null; + CreResource cre = null; try { cre = new CreResource(creEntry); @@ -73,14 +74,12 @@ public LayerObjectIniActor(PlainTextResource ini, IniMapSection creData, int cre throw new IllegalArgumentException(creData.getName() + ": Invalid CRE resource", e); } - // initializations - String sectionName = creData.getName(); - final String msg = cre.getAttribute(CreResource.CRE_NAME).toString() + " [" + sectionName + "]"; + this.cre = cre; + // initializations int[] pos = getCreatureLocation(); location.x = pos[0]; location.y = pos[1]; - int orientation = (pos.length > 2) ? pos[2] : 0; // setting creature allegiance int ea = ((IsNumeric)cre.getAttribute(CreResource.CRE_ALLEGIANCE)).getValue(); @@ -91,33 +90,28 @@ public LayerObjectIniActor(PlainTextResource ini, IniMapSection creData, int cre ea = object[0]; } - try { - sprite = createAnimationProvider(cre); - sprite.setOrientation(orientation); - } catch (Exception e) { - e.printStackTrace(); - } - // Using cached icons Image[] icons = ICONS.get(getAllegiance(ea)); icons = getIcons(icons); + String tooltip = getTooltip(); ini.setHighlightedLine(creData.getLine() + 1); - IconLayerItem item1 = new IconLayerItem(ini, msg, icons[0], CENTER); + + IconLayerItem item1 = new IconLayerItem(ini, tooltip, icons[0], CENTER); item1.setLabelEnabled(Settings.ShowLabelActorsIni); item1.setName(getCategory()); - item1.setToolTipText(msg); + item1.setToolTipText(tooltip); item1.setImage(AbstractLayerItem.ItemState.HIGHLIGHTED, icons[1]); item1.setVisible(isVisible()); items[0] = item1; - AnimatedLayerItem item2 = new AnimatedLayerItem(ini, msg, sprite); + // payload is initialized on demand + AnimatedLayerItem item2 = new AnimatedLayerItem(ini, tooltip, AbstractAnimationProvider.DEFAULT_ANIMATION_PROVIDER); item2.setName(getCategory()); - item2.setToolTipText(msg); + item2.setToolTipText(tooltip); item2.setVisible(false); - item2.setFrameRate(10.0); + item2.setFrameRate(Settings.getDefaultFrameRateAnimations()); item2.setAutoPlay(false); - item2.setComposite(Settings.UseActorAccurateBlending ? sprite.getDecoder().getComposite() : null); item2.setFrameColor(AbstractLayerItem.ItemState.NORMAL, COLOR_FRAME_NORMAL); item2.setFrameWidth(AbstractLayerItem.ItemState.NORMAL, 2); item2.setFrameEnabled(AbstractLayerItem.ItemState.NORMAL, false); @@ -137,7 +131,6 @@ public int getCreatureIndex() return creIndex; } - // @Override public Viewable getViewable() { @@ -175,5 +168,31 @@ private int[] getCreatureLocation() throws IllegalArgumentException return retVal; } - // + @Override + public synchronized void loadAnimation() + { + if (items[1] instanceof AnimatedLayerItem) { + AnimatedLayerItem item = (AnimatedLayerItem)items[1]; + if (item.getAnimation() == AbstractAnimationProvider.DEFAULT_ANIMATION_PROVIDER) { + try { + int[] pos = getCreatureLocation(); + int orientation = (pos.length > 2) ? pos[2] : 0; + ActorAnimationProvider sprite = createAnimationProvider(cre); + sprite.setOrientation(orientation); + + item.setAnimation(sprite); + item.setComposite(Settings.UseActorAccurateBlending ? sprite.getDecoder().getComposite() : null); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + + /** Tooltip for actor object. */ + private String getTooltip() + { + String sectionName = creData.getName(); + return cre.getAttribute(CreResource.CRE_NAME).toString() + " [" + sectionName + "]"; + } } diff --git a/src/org/infinity/resource/graphics/PseudoBamDecoder.java b/src/org/infinity/resource/graphics/PseudoBamDecoder.java index 0c9d6def1..a5068e96c 100644 --- a/src/org/infinity/resource/graphics/PseudoBamDecoder.java +++ b/src/org/infinity/resource/graphics/PseudoBamDecoder.java @@ -1971,7 +1971,6 @@ private void init() { currentCycle = currentFrame = -1; update(); - updateSharedBamSize(); } // Updates current cycle and frame pointers From 94bff8c988bc43d7e290923f7f5c7d2c69ae1aab Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Tue, 2 Mar 2021 15:37:59 +0100 Subject: [PATCH 027/158] SpriteDecoder: internal improvements --- .../resource/cre/decoder/SpriteDecoder.java | 132 ++++++++++++++---- .../resource/graphics/BlendingComposite.java | 3 + 2 files changed, 107 insertions(+), 28 deletions(-) diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index 573d278a7..a3be33167 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -32,9 +32,6 @@ import java.util.Map; import java.util.Objects; import java.util.TreeMap; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Function; import org.infinity.datatype.IsNumeric; import org.infinity.resource.Profile; @@ -624,11 +621,12 @@ public static Direction from(int value) { }}; /** - * A default operation that can be passed to the {@link #createAnimation(SeqDef, Consumer, Function, BiConsumer)} method. - * It is called once per source BAM resource. + * A default operation that can be passed to the + * {@link #createAnimation(SeqDef, List, BeforeSourceBam, BeforeSourceFrame, AfterSourceFrame, AfterDestFrame)} + * method. It is called once per source BAM resource. * Performed actions: palette replacement, shadow color fix, false color replacement, translucency */ - protected final BiConsumer FN_BEFORE_SRC_BAM = new BiConsumer() { + protected final BeforeSourceBam FN_BEFORE_SRC_BAM = new BeforeSourceBam() { @Override public void accept(BamV1Control control, SegmentDef sd) { @@ -649,29 +647,43 @@ public void accept(BamV1Control control, SegmentDef sd) }; /** - * A default operation that can be passed to the {@link #createAnimation(SeqDef, Consumer, Function, BiConsumer)} method. - * It is called for each source frame (segment) before being applied to the target frame. - * Performed actions: none + * A default operation that can be passed to the + * {@link #createAnimation(SeqDef, List, BeforeSourceBam, BeforeSourceFrame, AfterSourceFrame, AfterDestFrame)} + * method. It is called for each source frame (segment) before being applied to the target frame. */ - protected final Function FN_BEFORE_SRC_FRAME = new Function() { + protected final BeforeSourceFrame FN_BEFORE_SRC_FRAME = new BeforeSourceFrame() { @Override - public BufferedImage apply(BufferedImage image) + public BufferedImage apply(SegmentDef sd, BufferedImage image, Graphics2D g) { - // TODO: apply brightest, multiply_blend (?) + // nothing to do... return image; } }; /** - * A default action that can be passed to the {@link #createAnimation(SeqDef, Consumer, Function, BiConsumer)} method. - * It calculates an eastern direction frame by mirroring it horizontally if needed. + * A default operation that can be passed to the + * {@link #createAnimation(SeqDef, List, BeforeSourceBam, BeforeSourceFrame, AfterSourceFrame, AfterDestFrame)} + * method. It is called for each source frame (segment) after being applied to the target frame. */ - protected final BiConsumer FN_AFTER_DST_FRAME = new BiConsumer() { + protected final AfterSourceFrame FN_AFTER_SRC_FRAME = new AfterSourceFrame() { @Override - public void accept(DirDef dd, Integer idx) + public void accept(SegmentDef sd, Graphics2D g) + { + // nothing to do... + } + }; + + /** + * A default action that can be passed to the + * {@link #createAnimation(SeqDef, List, BeforeSourceBam, BeforeSourceFrame, AfterSourceFrame, AfterDestFrame)} + * method. It calculates an eastern direction frame by mirroring it horizontally if needed. + */ + protected final AfterDestFrame FN_AFTER_DST_FRAME = new AfterDestFrame() { + @Override + public void accept(DirDef dd, int frameIdx) { if (dd.isMirrored()) { - flipImageHorizontal(idx.intValue()); + flipImageHorizontal(frameIdx); } } }; @@ -1526,7 +1538,7 @@ protected int addDirection(Direction dir, int cycleIndex) protected void createSequence(Sequence seq) throws Exception { SeqDef sd = Objects.requireNonNull(getSequenceDefinition(seq), "Sequence not available: " + (seq != null ? seq : "(null)")); - createAnimation(sd, null, FN_BEFORE_SRC_BAM, FN_BEFORE_SRC_FRAME, FN_AFTER_DST_FRAME); + createAnimation(sd, null, FN_BEFORE_SRC_BAM, FN_BEFORE_SRC_FRAME, FN_AFTER_SRC_FRAME, FN_AFTER_DST_FRAME); } /** @@ -1543,13 +1555,14 @@ protected void createSequence(Sequence seq, Direction[] directions) throws Excep if (directions == null) { directions = Direction.values(); } - createAnimation(sd, Arrays.asList(directions), FN_BEFORE_SRC_BAM, FN_BEFORE_SRC_FRAME, FN_AFTER_DST_FRAME); + createAnimation(sd, Arrays.asList(directions), FN_BEFORE_SRC_BAM, FN_BEFORE_SRC_FRAME, FN_AFTER_SRC_FRAME, FN_AFTER_DST_FRAME); } protected void createAnimation(SeqDef definition, List directions, - BiConsumer beforeSrcBam, - Function beforeSrcImage, - BiConsumer afterDstFrame) + BeforeSourceBam beforeSrcBam, + BeforeSourceFrame beforeSrcFrame, + AfterSourceFrame afterSrcFrame, + AfterDestFrame afterDstFrame) { PseudoBamControl dstCtrl = createControl(); BamV1Control srcCtrl = null; @@ -1599,7 +1612,7 @@ protected void createAnimation(SeqDef definition, List directions, sd.advance(); } - int frameIndex = createFrame(frameInfo.toArray(new FrameInfo[frameInfo.size()]), beforeSrcImage); + int frameIndex = createFrame(frameInfo.toArray(new FrameInfo[frameInfo.size()]), beforeSrcFrame, afterSrcFrame); if (afterDstFrame != null) { afterDstFrame.accept(dd, Integer.valueOf(frameIndex)); } @@ -1613,11 +1626,13 @@ protected void createAnimation(SeqDef definition, List directions, * and adds it to the BAM frame list. Each source frame segment can be processed by the specified lambda function * before it is drawn onto to the target frame. * @param sourceFrames array of source frame segments to compose. - * @param beforeSrcImage optional function to be executed before a source frame segment is drawn onto the - * target frame. It is passed the {@code BufferedImage} object of the source frame segment. + * @param beforeSrcFrame optional function that is executed before a source frame segment is drawn onto the + * target frame. + * @param afterSrcFrame optional method that is executed right after a source frame segment has been drawn onto the + * target frame. * @return the absolute target BAM frame index. */ - protected int createFrame(FrameInfo[] sourceFrames, Function beforeSrcImage) + protected int createFrame(FrameInfo[] sourceFrames, BeforeSourceFrame beforeSrcFrame, AfterSourceFrame afterSrcFrame) { Rectangle rect; if (Objects.requireNonNull(sourceFrames, "Source frame info objects required").length > 0) { @@ -1668,8 +1683,8 @@ protected int createFrame(FrameInfo[] sourceFrames, Function Date: Tue, 2 Mar 2021 20:08:45 +0100 Subject: [PATCH 028/158] SpriteDecoder: Improve stroke and color definition for selection circle --- .../resource/cre/decoder/SpriteDecoder.java | 49 ++++++++++++++++--- .../cre/decoder/internal/CreatureInfo.java | 11 ++++- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index a3be33167..63164dda4 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -1647,7 +1647,7 @@ protected int createFrame(FrameInfo[] sourceFrames, BeforeSourceFrame beforeSrcF } // include selection circle in image size - float circleStrokeSize = (float)(Math.floor(Math.sqrt(getEllipse()) / 2.0)); // thickness relative to circle size + float circleStrokeSize = getSelectionCircleStrokeSize(); if (isSelectionCircleEnabled()) { Dimension dim = getSelectionCircleSize(); rect = updateFrameDimension(rect, new Dimension(2 * (dim.width + (int)circleStrokeSize), @@ -1824,6 +1824,20 @@ protected Dimension getSelectionCircleSize() return dim; } + /** Determines a circle stroke size relative to the circle size. Empty circles have no stroke size. */ + protected float getSelectionCircleStrokeSize() + { + float circleStrokeSize; + if (getEllipse() > 0) { + // thickness relative to circle size + circleStrokeSize = Math.max(1.0f, (float)(Math.floor(Math.sqrt(getEllipse()) / 2.0))); + } else { + circleStrokeSize = 0.0f; + } + + return circleStrokeSize; + } + /** * Draws a selection circle onto the specified graphics object. * @param g the {@code Graphics2D} instance of the image. @@ -1836,7 +1850,12 @@ protected void drawSelectionCircle(Graphics2D g, Point center, Color color, floa if (g != null) { Dimension dim = getSelectionCircleSize(); if (color == null) { - color = getAllegianceColor(getCreatureInfo().getAllegiance()); + if (getCreatureInfo().isStatusPanic()) { + // panic + color = getAllegianceColor(-1); + } else { + color = getAllegianceColor(getCreatureInfo().getAllegiance()); + } } Object oldHints = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING); g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); @@ -1959,11 +1978,21 @@ protected void flipImageHorizontal(int frameIndex) protected void applyTranslucency(BamV1Control control) { if (control != null) { - int alpha = getCreatureInfo().getTranslucency() << 24; + int alpha = getCreatureInfo().getTranslucency(); int[] palette = control.getCurrentPalette(); + + // shadow color (alpha relative to semi-transparency of shadow) + int alphaShadow = 255 - (palette[1] >>> 24); + alphaShadow = alpha * alphaShadow / 255; + alphaShadow <<= 24; // setting alpha mask + palette[1] = alphaShadow | (palette[1] & 0x00ffffff); + + // creature colors + alpha <<= 24; // setting alpha mask for (int i = 2; i < palette.length; i++) { palette[i] = alpha | (palette[i] & 0x00ffffff); } + control.setExternalPalette(palette); } } @@ -2044,18 +2073,22 @@ protected static Rectangle updateFrameDimension(Rectangle rect, Dimension dim) /** * Determines the right allegiance color for selection circles and returns it as {@code Color} object. - * @param value numeric allegiance value. - * @return a {@code Color} object with the associated allegiance color. + * A negative value will enable the "panic" color. + * @param value numeric allegiance value. Specify a negative value to override allegiance by the "panic" status. + * @return a {@code Color} object with the associated allegiance or status color. */ protected static Color getAllegianceColor(int value) { Color c = null; - if (value >= 2 && value <= 4 || value == 201) { + if (value < 0) { + // treat as panic + c = new Color(0xffff40, false); + } else if (value >= 2 && value <= 4 || value == 201) { // ally - c = new Color(0x00fa00, false); + c = new Color(0x20ff20, false); } else if (value == 255 || value == 254 || value == 28 || value == 6 || value == 5) { // enemy - c = new Color(0xff0020, false); + c = new Color(0xff2020, false); } else { // neutral c = new Color(0x40ffff, false); diff --git a/src/org/infinity/resource/cre/decoder/internal/CreatureInfo.java b/src/org/infinity/resource/cre/decoder/internal/CreatureInfo.java index 59a29393d..3e3adf59c 100644 --- a/src/org/infinity/resource/cre/decoder/internal/CreatureInfo.java +++ b/src/org/infinity/resource/cre/decoder/internal/CreatureInfo.java @@ -89,6 +89,15 @@ public CreatureInfo(SpriteDecoder decoder, CreResource cre) throws Exception /** Returns creature flags. */ public int getFlags() { return ((IsNumeric)cre.getAttribute(CreResource.CRE_FLAGS)).getValue(); } + /** Returns the creature status. */ + public int getStatus() { return ((IsNumeric)cre.getAttribute(CreResource.CRE_STATUS)).getValue(); } + + /** Returns whether the creature is panicked. */ + public boolean isStatusPanic() + { + return ((getStatus() & (1 << 2)) != 0); + } + /** Returns the creature animation id. */ public int getAnimationId() { return ((IsNumeric)cre.getAttribute(CreResource.CRE_ANIMATION)).getValue(); } @@ -105,7 +114,7 @@ public int getTranslucency() if (Profile.isEnhancedEdition()) { int v = ((IsNumeric)cre.getAttribute(CreResource.CRE_TRANSLUCENCY)).getValue(); if (v > 0) { - retVal = Math.min(v, 255); + retVal = 255 - Math.min(v, 255); } } return retVal; From 9c5678570e3dd60280e3e53bff41f378fcfad11f Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Tue, 2 Mar 2021 21:01:50 +0100 Subject: [PATCH 029/158] CRE quick viewer: Improve viewer title --- .../resource/cre/ViewerAnimation.java | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/org/infinity/resource/cre/ViewerAnimation.java b/src/org/infinity/resource/cre/ViewerAnimation.java index 67f88d8e2..632286443 100644 --- a/src/org/infinity/resource/cre/ViewerAnimation.java +++ b/src/org/infinity/resource/cre/ViewerAnimation.java @@ -34,6 +34,8 @@ import javax.swing.Timer; import org.infinity.NearInfinity; +import org.infinity.datatype.IsNumeric; +import org.infinity.datatype.IsTextual; import org.infinity.gui.ButtonPanel; import org.infinity.gui.Center; import org.infinity.gui.ChildFrame; @@ -43,7 +45,9 @@ import org.infinity.icon.Icons; import org.infinity.resource.cre.decoder.SpriteDecoder; import org.infinity.resource.cre.decoder.SpriteUtils; +import org.infinity.resource.gam.PartyNPC; import org.infinity.resource.graphics.PseudoBamDecoder.PseudoBamControl; +import org.infinity.util.StringTable; /** * A basic creature animation viewer. @@ -451,12 +455,34 @@ private void init() throws Exception GridBagConstraints.HORIZONTAL, new Insets(8, 0, 8, 0), 0, 0); add(buttonControlPanel, c); - String name = getCre().getName(); + // determining creature resource and name + String resName, name; + if (getCre().getResourceEntry() != null) { + resName = getCre().getResourceEntry().getResourceName(); + } else if (getCre().getParent() != null) { + resName = getCre().getParent().getName(); + } else { + resName = getCre().getName(); + } + + int strref = ((IsNumeric)getCre().getAttribute(CreResource.CRE_NAME)).getValue(); + if (!StringTable.isValidStringRef(strref)) { + strref = ((IsNumeric)getCre().getAttribute(CreResource.CRE_NAME)).getValue(); + } + if (StringTable.isValidStringRef(strref)) { + name = StringTable.getStringRef(strref); + } else if (getCre().getParent() instanceof PartyNPC) { + name = ((IsTextual)getCre().getParent().getAttribute(PartyNPC.GAM_NPC_NAME)).getText(); + } else { + name = ""; + } + if (!name.isEmpty()) { - setTitle(String.format("%s (%s)", getCre().getName(), getCre().getAttribute(CreResource.CRE_NAME))); + setTitle(String.format("%s (%s)", resName, name)); } else { - setTitle(getCre().getName()); + setTitle(resName); } + setSize(NearInfinity.getInstance().getPreferredSize()); Center.center(this, NearInfinity.getInstance().getBounds()); setExtendedState(NearInfinity.getInstance().getExtendedState() & ~ICONIFIED); From 26cce192a4e106e95dd8a75582ad4040b4209ef8 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Wed, 3 Mar 2021 16:55:38 +0100 Subject: [PATCH 030/158] Centralize item handling routines in ItemInfo class --- src/org/infinity/NearInfinity.java | 2 + .../cre/decoder/ArmoredBaseDecoder.java | 31 +- .../cre/decoder/CharacterDecoder.java | 25 +- .../cre/decoder/CharacterOldDecoder.java | 27 +- .../resource/cre/decoder/MonsterDecoder.java | 8 +- .../cre/decoder/MonsterIcewindDecoder.java | 9 +- .../cre/decoder/MonsterLayeredDecoder.java | 12 +- .../decoder/MonsterLayeredSpellDecoder.java | 10 +- .../resource/cre/decoder/SpriteUtils.java | 199 ++----- .../cre/decoder/internal/CreatureInfo.java | 2 +- .../cre/decoder/internal/ItemInfo.java | 556 +++++++++++++++--- 11 files changed, 581 insertions(+), 300 deletions(-) diff --git a/src/org/infinity/NearInfinity.java b/src/org/infinity/NearInfinity.java index 549d2d871..1491c6357 100644 --- a/src/org/infinity/NearInfinity.java +++ b/src/org/infinity/NearInfinity.java @@ -92,6 +92,7 @@ import org.infinity.resource.ViewableContainer; import org.infinity.resource.bcs.Signatures; import org.infinity.resource.cre.decoder.SpriteUtils; +import org.infinity.resource.cre.decoder.internal.ItemInfo; import org.infinity.resource.key.FileResourceEntry; import org.infinity.resource.key.ResourceEntry; import org.infinity.resource.key.ResourceTreeModel; @@ -1045,6 +1046,7 @@ private static void clearCache(boolean refreshOnly) ProRef.clearCache(); Signatures.clearCache(); SpriteUtils.clearCache(); + ItemInfo.clearCache(); } private static void showProgress(String msg, int max) diff --git a/src/org/infinity/resource/cre/decoder/ArmoredBaseDecoder.java b/src/org/infinity/resource/cre/decoder/ArmoredBaseDecoder.java index c2e7c0583..b19927517 100644 --- a/src/org/infinity/resource/cre/decoder/ArmoredBaseDecoder.java +++ b/src/org/infinity/resource/cre/decoder/ArmoredBaseDecoder.java @@ -4,18 +4,14 @@ package org.infinity.resource.cre.decoder; -import java.util.List; import java.util.Locale; import java.util.regex.Pattern; import org.infinity.datatype.IsNumeric; -import org.infinity.datatype.IsTextual; import org.infinity.resource.Profile; -import org.infinity.resource.StructEntry; import org.infinity.resource.cre.CreResource; import org.infinity.resource.cre.decoder.internal.DecoderAttribute; -import org.infinity.resource.itm.Ability; -import org.infinity.resource.itm.ItmResource; +import org.infinity.resource.cre.decoder.internal.ItemInfo; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; import org.infinity.util.Misc; @@ -94,9 +90,9 @@ protected void setHeightCode(String s) public int getArmorCode() { int retVal = 1; - ItmResource itm = SpriteUtils.getEquippedArmor(getCreResource()); + ItemInfo itm = SpriteUtils.getEquippedArmor(getCreResource()); if (itm != null) { - String code = ((IsTextual)itm.getAttribute(ItmResource.ITM_EQUIPPED_APPEARANCE)).getText(); + String code = itm.getAppearance(); retVal = Math.max(1, Math.min(getMaxArmorCode(), Misc.toNumber(code.substring(0, 1), 1))); } return retVal; @@ -109,7 +105,7 @@ public int getArmorCode() * @param preferTwoWeapon whether {@code AttackType.TwoWeapon} should be returned if a melee one-handed weapon is detected. * @return attack type associated with the item resource. */ - public AttackType getAttackType(ItmResource itm, int abilityIndex, boolean preferTwoWeapon) + public AttackType getAttackType(ItemInfo itm, int abilityIndex, boolean preferTwoWeapon) { AttackType retVal = AttackType.ONE_HANDED; if (itm == null) { @@ -117,25 +113,18 @@ public AttackType getAttackType(ItmResource itm, int abilityIndex, boolean prefe } // collecting data - int flags = ((IsNumeric)itm.getAttribute(ItmResource.ITM_FLAGS)).getValue(); - boolean isTwoHanded = (flags & (1 << 1)) != 0; + boolean isTwoHanded = (itm.getFlags() & (1 << 1)) != 0; if (Profile.isEnhancedEdition()) { // include fake two-handed weapons (e.g. monk fists) - isTwoHanded |= (flags & (1 << 12)) != 0; + isTwoHanded |= (itm.getFlags() & (1 << 12)) != 0; } - int cat = ((IsNumeric)itm.getAttribute(ItmResource.ITM_CATEGORY)).getValue(); int abilType = -1; - int numAbil = ((IsNumeric)itm.getAttribute(ItmResource.ITM_NUM_ABILITIES)).getValue(); - abilityIndex = Math.max(0, Math.min(numAbil - 1, abilityIndex)); - if (abilityIndex < numAbil) { - List list = itm.getFields(Ability.class); - if (list != null && abilityIndex < list.size() && list.get(abilityIndex) instanceof Ability) { - Ability abil = (Ability)list.get(abilityIndex); - abilType = ((IsNumeric)abil.getAttribute(Ability.ABILITY_TYPE)).getValue(); - } + abilityIndex = Math.max(0, Math.min(itm.getAbilityCount() - 1, abilityIndex)); + if (abilityIndex >= 0) { + abilType = itm.getAbility(abilityIndex).getAbilityType(); } - switch (cat) { + switch (itm.getCategory()) { case 15: // Bows retVal = AttackType.BOW; break; diff --git a/src/org/infinity/resource/cre/decoder/CharacterDecoder.java b/src/org/infinity/resource/cre/decoder/CharacterDecoder.java index 987305f22..f6d4d28c2 100644 --- a/src/org/infinity/resource/cre/decoder/CharacterDecoder.java +++ b/src/org/infinity/resource/cre/decoder/CharacterDecoder.java @@ -16,10 +16,10 @@ import org.infinity.resource.cre.CreResource; import org.infinity.resource.cre.decoder.internal.DecoderAttribute; import org.infinity.resource.cre.decoder.internal.DirDef; +import org.infinity.resource.cre.decoder.internal.ItemInfo; import org.infinity.resource.cre.decoder.internal.SegmentDef; import org.infinity.resource.cre.decoder.internal.SeqDef; import org.infinity.resource.cre.decoder.tables.SpriteTables; -import org.infinity.resource.itm.ItmResource; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; @@ -368,13 +368,15 @@ protected SeqDef getSequenceDefinition(Sequence seq) String prefixShield = getShieldHeightCode(); String codeShield = ""; if (!prefixShield.isEmpty()) { - ItmResource itm = SpriteUtils.getEquippedShield(getCreResource()); - codeShield = SpriteUtils.getItemAppearance(itm).trim(); - isLefthandedWeapon = !codeShield.isEmpty() && SpriteUtils.isLeftHandedWeapon(itm); + ItemInfo itmShield = SpriteUtils.getEquippedShield(getCreResource()); + if (itmShield != null) { + codeShield = itmShield.getAppearance().trim(); + isLefthandedWeapon = !codeShield.isEmpty() && ItemInfo.test(itmShield, ItemInfo.FILTER_WEAPON_MELEE_LEFT_HANDED); + } } // getting attack type - ItmResource itmWeapon = SpriteUtils.getEquippedWeapon(getCreResource()); + ItemInfo itmWeapon = SpriteUtils.getEquippedWeapon(getCreResource()); int itmAbility = SpriteUtils.getEquippedWeaponAbility(getCreResource()); AttackType attackType = getAttackType(itmWeapon, itmAbility, isLefthandedWeapon); @@ -405,9 +407,12 @@ protected SeqDef getSequenceDefinition(Sequence seq) if (isHelmetEquipped()) { prefix = getHelmetHeightCode(); if (!prefix.isEmpty()) { - String code = SpriteUtils.getItemAppearance(SpriteUtils.getEquippedHelmet(getCreResource())).trim(); - if (code.length() == 2) { - resrefList.add(Couple.with(prefix + code, SegmentDef.SpriteType.HELMET)); + ItemInfo itmHelmet = SpriteUtils.getEquippedHelmet(getCreResource()); + if (itmHelmet != null) { + String code = itmHelmet.getAppearance().trim(); + if (code.length() == 2) { + resrefList.add(Couple.with(prefix + code, SegmentDef.SpriteType.HELMET)); + } } } } @@ -419,8 +424,8 @@ protected SeqDef getSequenceDefinition(Sequence seq) // adding weapon overlay prefix = getHeightCode(); - if (!prefix.isEmpty()) { - String code = SpriteUtils.getItemAppearance(itmWeapon).trim(); + if (itmWeapon != null && !prefix.isEmpty()) { + String code = itmWeapon.getAppearance().trim(); if (code.length() == 2) { resrefList.add(Couple.with(prefix + code, SegmentDef.SpriteType.WEAPON)); } diff --git a/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java b/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java index babe0ee95..246336d2a 100644 --- a/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java +++ b/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java @@ -17,10 +17,10 @@ import org.infinity.resource.cre.decoder.internal.CycleDef; import org.infinity.resource.cre.decoder.internal.DecoderAttribute; import org.infinity.resource.cre.decoder.internal.DirDef; +import org.infinity.resource.cre.decoder.internal.ItemInfo; import org.infinity.resource.cre.decoder.internal.SegmentDef; import org.infinity.resource.cre.decoder.internal.SeqDef; import org.infinity.resource.cre.decoder.tables.SpriteTables; -import org.infinity.resource.itm.ItmResource; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; @@ -270,7 +270,7 @@ protected SeqDef getSequenceDefinition(Sequence seq) } // getting attack type - ItmResource itmWeapon = SpriteUtils.getEquippedWeapon(getCreResource()); + ItemInfo itmWeapon = SpriteUtils.getEquippedWeapon(getCreResource()); int itmAbility = SpriteUtils.getEquippedWeaponAbility(getCreResource()); AttackType attackType = getAttackType(itmWeapon, itmAbility, false); @@ -300,9 +300,12 @@ protected SeqDef getSequenceDefinition(Sequence seq) if (isHelmetEquipped()) { String prefix = getHelmetHeightCode(); if (!prefix.isEmpty()) { - String code = SpriteUtils.getItemAppearance(SpriteUtils.getEquippedHelmet(getCreResource())).trim(); - if (code.length() == 2) { - resrefList.add(Couple.with(prefix + code, SegmentDef.SpriteType.HELMET)); + ItemInfo itmHelmet = SpriteUtils.getEquippedHelmet(getCreResource()); + if (itmHelmet != null) { + String code = itmHelmet.getAppearance().trim(); + if (code.length() == 2) { + resrefList.add(Couple.with(prefix + code, SegmentDef.SpriteType.HELMET)); + } } } } @@ -311,17 +314,19 @@ protected SeqDef getSequenceDefinition(Sequence seq) // adding shield overlay String prefix = getHeightCode(); if (!prefix.isEmpty()) { - ItmResource itm = SpriteUtils.getEquippedShield(getCreResource()); - String code = SpriteUtils.getItemAppearance(itm).trim(); - if (!code.isEmpty()) { - resrefList.add(Couple.with(prefix + code, SegmentDef.SpriteType.SHIELD)); + ItemInfo itmShield = SpriteUtils.getEquippedShield(getCreResource()); + if (itmShield != null) { + String code = itmShield.getAppearance().trim(); + if (!code.isEmpty()) { + resrefList.add(Couple.with(prefix + code, SegmentDef.SpriteType.SHIELD)); + } } } // adding weapon overlay prefix = getHeightCode(); - if (!prefix.isEmpty()) { - String code = SpriteUtils.getItemAppearance(itmWeapon).trim(); + if (itmWeapon != null && !prefix.isEmpty()) { + String code = itmWeapon.getAppearance().trim(); if (code.length() == 2) { if (ResourceFactory.resourceExists(prefix + code + suffix + ".BAM")) { resrefList.add(Couple.with(prefix + code, SegmentDef.SpriteType.WEAPON)); diff --git a/src/org/infinity/resource/cre/decoder/MonsterDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterDecoder.java index db90e6a78..bc6e069e0 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterDecoder.java @@ -14,10 +14,10 @@ import org.infinity.resource.cre.CreResource; import org.infinity.resource.cre.decoder.internal.DecoderAttribute; import org.infinity.resource.cre.decoder.internal.DirDef; +import org.infinity.resource.cre.decoder.internal.ItemInfo; import org.infinity.resource.cre.decoder.internal.SegmentDef; import org.infinity.resource.cre.decoder.internal.SeqDef; import org.infinity.resource.cre.decoder.tables.SpriteTables; -import org.infinity.resource.itm.ItmResource; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; @@ -284,9 +284,9 @@ protected SeqDef getSequenceDefinition(Sequence seq) creResList.add(Couple.with(resref + suffix + ".BAM", SegmentDef.SpriteType.AVATAR)); // processing weapon overlay - ItmResource itm = SpriteUtils.getEquippedWeapon(getCreResource()); - if (itm != null) { - String weapon = SpriteUtils.getItemAppearance(itm).trim(); + ItemInfo itmWeapon = SpriteUtils.getEquippedWeapon(getCreResource()); + if (itmWeapon != null) { + String weapon = itmWeapon.getAppearance().trim(); if (!weapon.isEmpty()) { Couple wdata = suffixMapUnsplit.get(seq); if (wdata != null) { diff --git a/src/org/infinity/resource/cre/decoder/MonsterIcewindDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterIcewindDecoder.java index 0d5ca619d..e66f409fe 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterIcewindDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterIcewindDecoder.java @@ -8,15 +8,14 @@ import java.util.HashMap; import java.util.List; -import org.infinity.datatype.IsTextual; import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; import org.infinity.resource.cre.decoder.internal.DecoderAttribute; import org.infinity.resource.cre.decoder.internal.DirDef; +import org.infinity.resource.cre.decoder.internal.ItemInfo; import org.infinity.resource.cre.decoder.internal.SegmentDef; import org.infinity.resource.cre.decoder.internal.SeqDef; import org.infinity.resource.cre.decoder.tables.SpriteTables; -import org.infinity.resource.itm.ItmResource; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; @@ -170,9 +169,9 @@ protected SeqDef getSequenceDefinition(Sequence seq) // getting weapon code from CRE resource String weapon = ""; - ItmResource itm = SpriteUtils.getEquippedWeapon(getCreResource()); - if (itm != null) { - weapon = ((IsTextual)itm.getAttribute(ItmResource.ITM_EQUIPPED_APPEARANCE)).getText(); + ItemInfo itmWeapon = SpriteUtils.getEquippedWeapon(getCreResource()); + if (itmWeapon != null) { + weapon = itmWeapon.getAppearance(); if (!weapon.isEmpty()) { weapon = weapon.substring(0, 1).trim(); } diff --git a/src/org/infinity/resource/cre/decoder/MonsterLayeredDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterLayeredDecoder.java index dbbda5667..015bebe54 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterLayeredDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterLayeredDecoder.java @@ -12,10 +12,10 @@ import org.infinity.resource.cre.CreResource; import org.infinity.resource.cre.decoder.internal.DecoderAttribute; import org.infinity.resource.cre.decoder.internal.DirDef; +import org.infinity.resource.cre.decoder.internal.ItemInfo; import org.infinity.resource.cre.decoder.internal.SegmentDef; import org.infinity.resource.cre.decoder.internal.SeqDef; import org.infinity.resource.cre.decoder.tables.SpriteTables; -import org.infinity.resource.itm.ItmResource; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; @@ -152,12 +152,12 @@ protected SeqDef getSequenceDefinition(Sequence seq) switch (seq) { case ATTACK: case ATTACK_2: - if (SpriteUtils.isWeaponTwoHanded(SpriteUtils.getEquippedWeapon(getCreResource()), false)) { + if (ItemInfo.test(SpriteUtils.getEquippedWeapon(getCreResource()), ItemInfo.FILTER_WEAPON_2H)) { return retVal; } break; case ATTACK_2H: - if (!SpriteUtils.isWeaponTwoHanded(SpriteUtils.getEquippedWeapon(getCreResource()), false)) { + if (!ItemInfo.test(SpriteUtils.getEquippedWeapon(getCreResource()), ItemInfo.FILTER_WEAPON_2H)) { return retVal; } break; @@ -173,9 +173,9 @@ protected SeqDef getSequenceDefinition(Sequence seq) creResList.add(Couple.with(resref + suffix, SegmentDef.SpriteType.AVATAR)); // defining weapon overlay for current creature - ItmResource itm = SpriteUtils.getEquippedWeapon(getCreResource()); - if (itm != null) { - String weapon = SpriteUtils.getItemAppearance(itm).trim(); + ItemInfo itmWeapon = SpriteUtils.getEquippedWeapon(getCreResource()); + if (itmWeapon != null) { + String weapon = itmWeapon.getAppearance().trim(); if (!weapon.isEmpty()) { weapon = weapon.substring(0, 1); } diff --git a/src/org/infinity/resource/cre/decoder/MonsterLayeredSpellDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterLayeredSpellDecoder.java index 338745d8b..43e87bc47 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterLayeredSpellDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterLayeredSpellDecoder.java @@ -12,10 +12,10 @@ import org.infinity.resource.cre.CreResource; import org.infinity.resource.cre.decoder.internal.DecoderAttribute; import org.infinity.resource.cre.decoder.internal.DirDef; +import org.infinity.resource.cre.decoder.internal.ItemInfo; import org.infinity.resource.cre.decoder.internal.SegmentDef; import org.infinity.resource.cre.decoder.internal.SeqDef; import org.infinity.resource.cre.decoder.tables.SpriteTables; -import org.infinity.resource.itm.ItmResource; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; @@ -191,7 +191,8 @@ protected SeqDef getSequenceDefinition(Sequence seq) return retVal; } - if (seq == Sequence.ATTACK_2H && !SpriteUtils.isWeaponTwoHanded(SpriteUtils.getEquippedWeapon(getCreResource()), false)) { + ItemInfo itmWeapon = SpriteUtils.getEquippedWeapon(getCreResource()); + if (seq == Sequence.ATTACK_2H && ItemInfo.test(itmWeapon, ItemInfo.FILTER_WEAPON_2H)) { return retVal; } @@ -204,9 +205,8 @@ protected SeqDef getSequenceDefinition(Sequence seq) creResList.add(Couple.with(resref + suffix, SegmentDef.SpriteType.AVATAR)); // defining weapon overlay for current creature - ItmResource itm = SpriteUtils.getEquippedWeapon(getCreResource()); - if (itm != null) { - String weapon = SpriteUtils.getItemAppearance(itm).trim(); + if (itmWeapon != null) { + String weapon = itmWeapon.getAppearance().trim(); if (!weapon.isEmpty()) { weapon = weapon.substring(0, 1); } diff --git a/src/org/infinity/resource/cre/decoder/SpriteUtils.java b/src/org/infinity/resource/cre/decoder/SpriteUtils.java index f92bc7913..fab91c8ff 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteUtils.java +++ b/src/org/infinity/resource/cre/decoder/SpriteUtils.java @@ -7,8 +7,6 @@ import java.awt.image.BufferedImage; import java.io.IOException; import java.io.InputStream; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.HashMap; @@ -21,14 +19,12 @@ import org.infinity.resource.AbstractStruct; import org.infinity.resource.Profile; import org.infinity.resource.ResourceFactory; -import org.infinity.resource.StructEntry; import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.ItemInfo; import org.infinity.resource.graphics.BamV1Decoder; import org.infinity.resource.graphics.ColorConvert; import org.infinity.resource.graphics.GraphicsResource; import org.infinity.resource.graphics.BamV1Decoder.BamV1Control; -import org.infinity.resource.itm.Ability; -import org.infinity.resource.itm.ItmResource; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.Misc; import org.infinity.util.Table2da; @@ -41,8 +37,6 @@ */ public class SpriteUtils { - /** Cache for structured game resources */ - private static final HashMap resourceCache = new HashMap<>(); /** Cache for source BAM resources (decoder and attached controller). */ private static final HashMap> bamCache = new HashMap<>(); /** Cache for replacement palettes. */ @@ -60,7 +54,6 @@ public class SpriteUtils /** Clears cached resources. */ public static void clearCache() { - resourceCache.clear(); bamCache.clear(); paletteCache.clear(); colorGradients.clear(); @@ -341,131 +334,6 @@ private static ByteBuffer addPseudoCreItem(ByteBuffer buffer, int slot, String r return outBuffer; } - /** - * Attempts to fetch the requested resource structure from cache before creating a new instance. - * @param the desired type of the return value. - * @param classType the specific class type of the resource entry. - * @param entry the resource entry to load. - * @return a structured resource of type {@code T} if successful. Returns {@code null} otherwise. - * @throws Exception propagated exception from the instantiation process. - */ - @SuppressWarnings("unchecked") - public static T loadResource(Class classType, ResourceEntry entry) throws Exception - { - T retVal = null; - if (classType == null || entry == null) { - return retVal; - } - - AbstractStruct as = resourceCache.get(entry); - if (as == null) { - try { - Constructor ctor = classType.getConstructor(ResourceEntry.class); - if (ctor != null) { - as = ctor.newInstance(entry); - resourceCache.put(entry, as); - } - } catch (IllegalAccessException | InstantiationException | InvocationTargetException | NoSuchMethodException e) { - } - } - - if (classType.isAssignableFrom(as.getClass())) { - retVal = (T)as; - } - - return retVal; - } - - /** - * Returns whether the specified item is a two-handed weapon. - * @param itm the item resource - * @param meleeOnly indicates whether to consider only melee weapons. - * @return {@code true} if the item is a valid two-handed weapon. This also includes fake two-handed weapons in EE games. - * Returns {@code false} otherwise. - */ - public static boolean isWeaponTwoHanded(ItmResource itm, boolean meleeOnly) - { - boolean retVal = false; - if (itm == null) { - return retVal; - } - - int flags = ((IsNumeric)itm.getAttribute(ItmResource.ITM_FLAGS)).getValue(); - int mask = 1 << 1; // two-handed - if (Profile.isEnhancedEdition()) { - mask |= 1 << 12; // fake two-handed - } - retVal = (flags & mask) != 0; - - String appearance = ((IsTextual)itm.getAttribute(ItmResource.ITM_EQUIPPED_APPEARANCE)).getText().trim(); - retVal &= !appearance.isEmpty(); - - if (retVal && meleeOnly) { - int abilType = -1; - int numAbil = ((IsNumeric)itm.getAttribute(ItmResource.ITM_NUM_ABILITIES)).getValue(); - if (numAbil > 0) { - int ofsAbil = ((IsNumeric)itm.getAttribute(ItmResource.ITM_OFFSET_ABILITIES)).getValue(); - StructEntry se = itm.getAttribute(ofsAbil, true); - if (se instanceof IsNumeric) { - abilType = ((IsNumeric)se).getValue(); - } - } - retVal &= (abilType == 1); // ability type "melee" - } - - return retVal; - } - - /** - * Returns whether the specified item is a weapon that can be wielded with the left hand. - * @param itm the item resource - * @return {@code true} if the weapon can be wielded with the left hand. Returns {@code false} otherwise. - */ - public static boolean isLeftHandedWeapon(ItmResource itm) - { - boolean retVal = false; - if (itm != null) { - int flags = ((IsNumeric)itm.getAttribute(ItmResource.ITM_FLAGS)).getValue(); - boolean isTwoHanded = (flags & (1 << 1)) != 0; // two-handed - boolean allowLeftHanded = !Profile.isEnhancedEdition() || ((flags & (1 << 13)) == 0); - String appearance = ((IsTextual)itm.getAttribute(ItmResource.ITM_EQUIPPED_APPEARANCE)).getText().trim(); - int abilType = -1; - int numAbil = ((IsNumeric)itm.getAttribute(ItmResource.ITM_NUM_ABILITIES)).getValue(); - if (numAbil > 0) { - int ofsAbil = ((IsNumeric)itm.getAttribute(ItmResource.ITM_OFFSET_ABILITIES)).getValue(); - StructEntry se = itm.getAttribute(ofsAbil, true); - if (se instanceof IsNumeric) { - abilType = ((IsNumeric)se).getValue(); - } - // eligible left-handed weapons: not two-handed; left-handed allowed; has equipped appearance; ability type = Melee - retVal = !isTwoHanded && allowLeftHanded && !appearance.isEmpty() && (abilType == 1); - } - } - return retVal; - } - - - /** - * Returns the two-letter appearance code for the specified item. - * @param itm The item resource. - * @return A two-letter string indicating the item appearance for creature animations. Returns an empty string otherwise. - */ - public static String getItemAppearance(ItmResource itm) - { - String retVal = ""; - if (itm != null) { - retVal = ((IsTextual)itm.getAttribute(ItmResource.ITM_EQUIPPED_APPEARANCE)).getText().toUpperCase(Locale.ENGLISH); - if (retVal.length() > 2) { - retVal = retVal.substring(0, 2); - } else { - while (retVal.length() < 2) { - retVal += " "; - } - } - } - return retVal; - } - /** * Returns the index of the selected weapon ability. * @param cre the CRE resource. @@ -590,28 +458,23 @@ public static int getEffectiveWeaponIndex(CreResource cre, int slotIndex) } // loading referenced item - ItmResource itm = null; + ItemInfo info = null; int ofsItems = Objects.requireNonNull(cre).getExtraOffset() + ((IsNumeric)cre.getAttribute(CreResource.CRE_OFFSET_ITEMS)).getValue(); try { String itmResref = ((IsTextual)cre.getAttribute(ofsItems + itmIndex * 20, true)).getText(); - itm = loadResource(ItmResource.class, ResourceFactory.getResourceEntry(itmResref + ".ITM")); + info = ItemInfo.get(ResourceFactory.getResourceEntry(itmResref + ".ITM")); } catch (Exception e) { return retVal; } // check if item requires a launcher int abilityIndex = ((IsNumeric)cre.getAttribute(CreResource.CRE_SELECTED_WEAPON_ABILITY)).getValue(); - int numAbil = ((IsNumeric)Objects.requireNonNull(itm).getAttribute(ItmResource.ITM_NUM_ABILITIES)).getValue(); + int numAbil = info.getAbilityCount(); abilityIndex = Math.min(abilityIndex, numAbil - 1); if (abilityIndex < 0) { return retVal; } - int ofsAbil = ((IsNumeric)itm.getAttribute(ItmResource.ITM_OFFSET_ABILITIES)).getValue(); - Ability ability = itm.getAttribute(ofsAbil + abilityIndex * 0x38, Ability.class, false); - if (ability == null) { - return retVal; - } - int launcherType = ((IsNumeric)ability.getAttribute(Ability.ITM_ABIL_LAUNCHER_REQUIRED)).getValue(); + int launcherType = info.getAbility(abilityIndex).getLauncher(); if (launcherType == 0) { // item can be used directly retVal = slotIndex; @@ -656,12 +519,13 @@ public static int getEffectiveWeaponIndex(CreResource cre, int slotIndex) /** * Returns the active weapon of the specified creature. * @param cre The CRE resource. - * @return The ITM resource of the active weapon. Returns {@code null} if no weapon is active. + * @return The {@code ItemInfo} object for the item resource of the active weapon. + * Returns {@code null} if no weapon is active. * @throws NullPointerException if no creature resource specified. */ - public static ItmResource getEquippedWeapon(CreResource cre) + public static ItemInfo getEquippedWeapon(CreResource cre) { - ItmResource retVal = null; + ItemInfo retVal = null; // find selected weapon slot and determine the associated item int slotIndex = getWeaponSlotIndex(cre, ((IsNumeric)cre.getAttribute(CreResource.CRE_SELECTED_WEAPON_SLOT)).getValue()); @@ -686,20 +550,21 @@ public static ItmResource getEquippedWeapon(CreResource cre) /** * Returns the equipped helmet of the specified creature. * @param cre The CRE resource. - * @return The ITM resource of the helmet. Returns {@code null} if no helmet is equipped. + * @return The {@code ItemInfo} object for the item resource of the helmet. + * Returns {@code null} if no helmet is equipped. * @throws NullPointerException if no creature resource specified. */ - public static ItmResource getEquippedHelmet(CreResource cre) + public static ItemInfo getEquippedHelmet(CreResource cre) { if (Profile.getEngine() == Profile.Engine.PST || Profile.getGame() == Profile.Game.PSTEE) { // PST does not support helmets return null; } - ItmResource retVal = getInventoryItem(cre, 0); + ItemInfo retVal = getInventoryItem(cre, 0); if (retVal != null) { // checking item category - switch (((IsNumeric)retVal.getAttribute(ItmResource.ITM_CATEGORY)).getValue()) { + switch (retVal.getCategory()) { case 7: // Headgear case 72: // Hats break; @@ -713,12 +578,13 @@ public static ItmResource getEquippedHelmet(CreResource cre) /** * Returns the equipped shield or left-handed weapon of the specified creature. * @param cre The CRE resource. - * @return The ITM resource of the shield or left-handed weapon. Returns {@code null} if left hand is empty. + * @return The {@code ItemInfo} object for the item resource of the shield or left-handed weapon. + * Returns {@code null} if left hand is empty. * @throws NullPointerException if no creature resource specified. */ - public static ItmResource getEquippedShield(CreResource cre) + public static ItemInfo getEquippedShield(CreResource cre) { - ItmResource retVal = null; + ItemInfo retVal = null; if (Profile.getEngine() == Profile.Engine.PST || Profile.getGame() == Profile.Game.PSTEE) { // PST does not support shields return retVal; @@ -743,22 +609,23 @@ public static ItmResource getEquippedShield(CreResource cre) /** * Returns the equipped armor or robe of the specified creature. * @param cre The CRE resource. - * @return The ITM resource of armor or robe. Returns {@code null} if no armor is equipped. + * @return The {@code ItemInfo} object for the item resource of armor or robe. + * Returns {@code null} if no armor is equipped. */ - public static ItmResource getEquippedArmor(CreResource cre) + public static ItemInfo getEquippedArmor(CreResource cre) { return getInventoryItem(cre, 1); } /** - * Returns the item resource at the specified item slot of the given creature. + * Returns an {@link ItemInfo} instance of the item resource at the specified item slot of the given creature. * @param cre the CRE resource * @param slotIndex absolute slot index of the requested item - * @return the ITM resource. Returns {@code null} if item could not be determined. + * @return the {@code ItemInfo} object for the item resource. Returns {@code null} if item could not be determined. */ - public static ItmResource getInventoryItem(CreResource cre, int slotIndex) + public static ItemInfo getInventoryItem(CreResource cre, int slotIndex) { - ItmResource retVal = null; + ItemInfo retVal = null; if (cre == null || slotIndex < 0) { return retVal; } @@ -795,7 +662,8 @@ public static ItmResource getInventoryItem(CreResource cre, int slotIndex) if (itemIndex < numItems) { int ofsItems = cre.getExtraOffset() + ((IsNumeric)cre.getAttribute(CreResource.CRE_OFFSET_ITEMS)).getValue(); String itemRef = ((IsTextual)cre.getAttribute(ofsItems + itemIndex * 20, true)).getText(); - retVal = loadResource(ItmResource.class, ResourceFactory.getResourceEntry(itemRef + ".ITM")); + ResourceEntry entry = ResourceFactory.getResourceEntry(itemRef + ".ITM"); + retVal = ItemInfo.get(entry); } } } catch (Exception e) { @@ -805,10 +673,10 @@ public static ItmResource getInventoryItem(CreResource cre, int slotIndex) return retVal; } - /** Attempts to find the virtual "fist" weapon item used for barehanded attacks. */ - public static ItmResource getFistWeapon(CreResource cre) + /** Attempts to find and return the virtual "fist" weapon item used for barehanded attacks. */ + public static ItemInfo getFistWeapon(CreResource cre) { - ItmResource retVal = null; + ItemInfo retVal = null; int animId = ((IsNumeric)cre.getAttribute(CreResource.CRE_ANIMATION)).getValue(); if ((animId & 0xff00) == 0x6500) { @@ -822,11 +690,12 @@ public static ItmResource getFistWeapon(CreResource cre) String resref = table.get(level, 1); ResourceEntry entry = ResourceFactory.getResourceEntry(resref + ".ITM"); try { - retVal = loadResource(ItmResource.class, entry); + retVal = ItemInfo.get(entry); } catch (Exception e) { e.printStackTrace(); } } + if (retVal == null) { // 2. try hardcoded selection String resref = null; @@ -836,7 +705,7 @@ public static ItmResource getFistWeapon(CreResource cre) resref = "MFIST" + i; ResourceEntry entry = ResourceFactory.getResourceEntry(resref + ".ITM"); try { - retVal = loadResource(ItmResource.class, entry); + retVal = ItemInfo.get(entry); } catch (Exception e) { e.printStackTrace(); } @@ -849,7 +718,7 @@ public static ItmResource getFistWeapon(CreResource cre) // return default fist ResourceEntry entry = ResourceFactory.getResourceEntry("FIST.ITM"); try { - retVal = loadResource(ItmResource.class, entry); + retVal = ItemInfo.get(entry); } catch (Exception e) { e.printStackTrace(); } diff --git a/src/org/infinity/resource/cre/decoder/internal/CreatureInfo.java b/src/org/infinity/resource/cre/decoder/internal/CreatureInfo.java index 3e3adf59c..58b716a12 100644 --- a/src/org/infinity/resource/cre/decoder/internal/CreatureInfo.java +++ b/src/org/infinity/resource/cre/decoder/internal/CreatureInfo.java @@ -748,7 +748,7 @@ private void initEquipmentItem(ItemSlots slot, int itemIndex, List if (itmEntry != null) { try { - ItemInfo itemInfo = new ItemInfo(itmEntry); + ItemInfo itemInfo = ItemInfo.get(itmEntry); equipment.put(slot, itemInfo); } catch (Exception e) { e.printStackTrace(); diff --git a/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java b/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java index 078be3a9f..aa448c61c 100644 --- a/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java +++ b/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java @@ -6,11 +6,23 @@ import java.io.IOException; import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; import java.util.Locale; import java.util.Objects; +import java.util.function.Predicate; +import java.util.stream.Stream; +import org.infinity.resource.Profile; +import org.infinity.resource.ResourceFactory; import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.DynamicArray; import org.infinity.util.Misc; +import org.infinity.util.StringTable; import org.infinity.util.io.StreamUtils; /** @@ -18,16 +30,228 @@ */ public class ItemInfo { + /** + * This predicate returns {@code true} only if the item can be equipped and unequipped in the character inventory. + */ + public static final Predicate FILTER_EQUIPPABLE = new Predicate() { + @Override + public boolean test(ItemInfo info) + { + return (info.getFlags() & 0x04) == 0x04; // droppable + } + }; + + /** + * This predicate returns {@code true} only if the item can be equipped in a weapon slot. + */ + public static final Predicate FILTER_WEAPON = new Predicate() { + @Override + public boolean test(ItemInfo info) + { + boolean retVal = FILTER_EQUIPPABLE.test(info); + if (retVal && info.getAbilityCount() > 0) { + retVal &= (info.getAbility(0).getLocation() == 1); + } + return retVal; + } + }; + + /** + * This predicate returns {@code true} if the item is a two-handed weapon (melee or ranged). + */ + public static final Predicate FILTER_WEAPON_2H = new Predicate() { + @Override + public boolean test(ItemInfo info) + { + boolean retVal = FILTER_WEAPON.test(info); + if (retVal && info.getAbilityCount() > 0) { + int mask = Profile.isEnhancedEdition() ? 0x1002 : 0x2; // two-handed, fake two-handed + retVal &= (info.getFlags() & mask) != 0; + } + return retVal; + } + }; + + /** + * This predicate returns {@code true} only if the item can be placed in a weapon slot and the default ability + * is defined as melee type. + */ + public static final Predicate FILTER_WEAPON_MELEE = new Predicate() { + @Override + public boolean test(ItemInfo info) + { + boolean retVal = FILTER_WEAPON.test(info); + if (retVal && info.getAbilityCount() > 0) { + AbilityInfo ai = info.getAbility(0); + retVal = (ai.getAbilityType() == 1) && + (ai.getLauncher() == 0); + } + return retVal; + } + }; + + /** + * This predicate returns {@code true} only if {@link #FILTER_WEAPON_MELEE} passes the test and the item is flagged + * as two-handed or fake two-handed (e.g. monk fists). + */ + public static final Predicate FILTER_WEAPON_MELEE_2H = new Predicate() { + @Override + public boolean test(ItemInfo info) + { + boolean retVal = FILTER_WEAPON_MELEE.test(info); + if (retVal) { + int mask = Profile.isEnhancedEdition() ? 0x1002 : 0x2; + retVal &= (info.getFlags() & mask) != 0; + } + return retVal; + } + }; + + /** + * This predicate returns {@code true} only if {@link #FILTER_WEAPON_MELEE} passes the test and the item can be + * equipped in the shield slot. + */ + public static final Predicate FILTER_WEAPON_MELEE_LEFT_HANDED = new Predicate() { + @Override + public boolean test(ItemInfo info) + { + boolean retVal = FILTER_WEAPON_MELEE.test(info); + if (retVal) { + boolean isTwoHanded = (info.getFlags() & 2) != 0; + boolean allowLeftHanded = !Profile.isEnhancedEdition() || ((info.getFlags() & (1 << 13)) == 0); + retVal = !isTwoHanded && allowLeftHanded; + } + return retVal; + } + }; + + /** + * This predicate returns {@code true} only if the item can be placed in a weapon slot and the default ability + * is defined as ranged or launcher type. + */ + public static final Predicate FILTER_WEAPON_RANGED = new Predicate() { + @Override + public boolean test(ItemInfo info) + { + boolean retVal = FILTER_WEAPON.test(info); + if (retVal && info.getAbilityCount() > 0) { + AbilityInfo ai = info.getAbility(0); + retVal &= (ai.getLauncher() == 0); + retVal &= (ai.getAbilityType() == 2) || (ai.getAbilityType() == 4); + } + return retVal; + } + }; + + private static final HashMap ITEM_CACHE = new HashMap<>(); + private final ColorInfo colorInfo = new ColorInfo(); + private final List abilityInfo = new ArrayList<>(); + private final List effectsInfo = new ArrayList<>(); private final ResourceEntry itmEntry; + private String name; + private String nameIdentified; private int flags; private int category; private int unusable; private int unusableKits; private String appearance; - // list of item abilities (sorted by index): contains ability type (1=melee, 2=range, 3=magical, 4=launcher) - private int[] abilities; + private int proficiency; + private int enchantment; + + /** + * Returns the {@code ItemInfo} structure based on the specified item resource. Entries are retrieved from cache + * for improved performance. + * @param entry the ITM {@code ResourceEntry} + * @return the {@code ItemInfo} structure with relevant item details. + * @throws Exception if the ITM resource could not be loaded. + */ + public static ItemInfo get(ResourceEntry entry) throws Exception + { + ItemInfo retVal = null; + if (entry == null) { + return retVal; + } + synchronized (ITEM_CACHE) { + retVal = ITEM_CACHE.get(entry); + if (retVal == null) { + retVal = new ItemInfo(entry); + ITEM_CACHE.put(entry, retVal); + } + } + return retVal; + } + + /** + * Functions the same as {@link #get(ResourceEntry)} excepts that available cache entries will be updated with the + * new item data. + * @param entry the ITM {@code ResourceEntry} + * @return the {@code ItemInfo} structure with relevant item details. + * @throws Exception if the ITM resource could not be loaded. + */ + public static ItemInfo getValidated(ResourceEntry entry) throws Exception + { + ItemInfo retVal = null; + if (entry == null) { + return retVal; + } + synchronized (ITEM_CACHE) { + retVal = new ItemInfo(entry); + ITEM_CACHE.put(entry, retVal); + } + return retVal; + } + + /** Clears the item cache. */ + public static void clearCache() + { + synchronized (ITEM_CACHE) { + ITEM_CACHE.clear(); + } + } + + /** + * Returns an {@code ItemInfo} list filtered by the specified predicate. + * @param pred the predicate used to decide whether an item is included in the returned list. + * Specify {@code null} to return all available items. + * @param sorted whether the returned list is sorted by {@code ResourceEntry} in ascending order. + * @return list of matching {@code ItemInfo} structures. + */ + public static List getItemList(Predicate pred, boolean sorted) + { + List retVal = new ArrayList<>(); + + if (pred == null) { + pred = i -> true; + } + + List entries = ResourceFactory.getResources("ITM"); + for (Iterator iter = entries.iterator(); iter.hasNext(); ) { + try { + final ItemInfo ii = ItemInfo.get(iter.next()); + if (pred.test(ii)) { + retVal.add(ii); + } + } catch (Exception e) { + } + } + + if (sorted) { + Collections.sort(retVal, (i1, i2) -> i1.itmEntry.compareTo(i2.itmEntry)); + } + + return retVal; + } + + /** + * Convenience method: Returns {@code true} if the given item passes the specified test. + * Returns {@code false} if info is {@code null}. Returns {@code true} if pred is {@code null}. + */ + public static boolean test(ItemInfo info, Predicate pred) + { + return (info != null && pred != null) ? pred.test(info) : (info != null); + } /** * This is a convenience method to speed up the process. @@ -57,7 +281,7 @@ public static int getItemCategory(ResourceEntry itmEntry) return -1; } - public ItemInfo(ResourceEntry itmEntry) throws Exception + private ItemInfo(ResourceEntry itmEntry) throws Exception { this.itmEntry = Objects.requireNonNull(itmEntry, "ITM resource cannot be null"); init(); @@ -66,6 +290,12 @@ public ItemInfo(ResourceEntry itmEntry) throws Exception /** Returns the {@code ResourceEntry} instance of the item resource. */ public ResourceEntry getItemEntry() { return itmEntry; } + /** Returns the general name of the item. */ + public String getName() { return name; } + + /** Returns the identified name of the item. */ + public String getIdentifiedName() { return nameIdentified; } + /** Returns the item flags. */ public int getFlags() { return flags; } @@ -81,14 +311,35 @@ public ItemInfo(ResourceEntry itmEntry) throws Exception /** Returns the two-letter appearance code. */ public String getAppearance() { return appearance; } + /** Returns the proficiency id associated with the item. */ + public int getProficiency() { return proficiency; } + + /** Returns the enchantment value of the item. */ + public int getEnchantment() { return enchantment; } + /** Provides access to the {@link ColorInfo} instance associated with the item. */ public ColorInfo getColorInfo() { return colorInfo; } /** Returns number of defined item abilities. */ - public int getAbilityCount() { return abilities.length; } + public int getAbilityCount() { return abilityInfo.size(); } + + /** Returns the specified ability structure. */ + public AbilityInfo getAbility(int index) throws IndexOutOfBoundsException { return abilityInfo.get(index); } + + /** Returns a sequential {@link Stream} of the {@code AbilityInfo} list. */ + public Stream getAbilityStream() { return abilityInfo.stream(); } /** Returns the type of the specified ability. */ - public int getAbility(int index) { return (index >= 0 && index < abilities.length) ? abilities[index] : -1; } + public int getAbilityType(int index) { return (index >= 0 && index < abilityInfo.size()) ? abilityInfo.get(index).getAbilityType() : -1; } + + /** Returns the number of defined global item effects. */ + public int getGlobalEffectsCount() { return effectsInfo.size(); } + + /** Returns the specified global item effect. */ + public EffectInfo getGlobalEffect(int index) throws IndexOutOfBoundsException { return effectsInfo.get(index); } + + /** Returns a sequential {@link Stream} of the {@code EffectInfo} list. */ + public Stream getEffectStream() { return effectsInfo.stream(); } private void init() throws IOException, Exception { @@ -106,8 +357,22 @@ private void init() throws IOException, Exception throw new Exception("Not an item resource: " + itmEntry.getResourceName()); } + // general name + int strref = StreamUtils.readInt(is); + this.name = StringTable.isValidStringRef(strref) ? StringTable.getStringRef(strref) : ""; + + // identified name + strref = StreamUtils.readInt(is); + this.nameIdentified = StringTable.isValidStringRef(strref) ? StringTable.getStringRef(strref) : ""; + + if (this.nameIdentified.isEmpty()) { + this.nameIdentified = this.name; + } else if (this.name.isEmpty()) { + this.name = this.nameIdentified; + } + // flags (common) - Misc.requireCondition(is.skip(0x10) == 0x10, "Could not advance in data stream: " + itmEntry); + Misc.requireCondition(is.skip(0x8) == 0x8, "Could not advance in data stream: " + itmEntry); // offset = 0x18 this.flags = StreamUtils.readInt(is); @@ -123,7 +388,7 @@ private void init() throws IOException, Exception // offset = 0x22 this.appearance = StreamUtils.readString(is, 2); - // unusableKits (common for V1/V2.0 only) + // unusableKits and proficiency (common for V1/V2.0 only) this.unusableKits = 0; if (!"ITM V1.1".equals(signature)) { int v; @@ -143,13 +408,20 @@ private void init() throws IOException, Exception // offset = 0x2f Misc.requireCondition((v = is.read()) != -1, "Could not read kits usability field: " + itmEntry); this.unusableKits |= v; + + // proficiency + Misc.requireCondition(is.skip(0x1) == 0x1, "Could not advance in data stream: " + itmEntry); + this.proficiency = is.read(); } else { // to keep stream position synchronized - Misc.requireCondition(is.skip(0xc) == 0xc, "Could not advance in data stream: " + itmEntry); + Misc.requireCondition(is.skip(0xc) == 0xe, "Could not advance in data stream: " + itmEntry); } + // enchantment + Misc.requireCondition(is.skip(0x2e) == 0x2e, "Could not advance in data stream: " + itmEntry); + this.enchantment = StreamUtils.readInt(is); + // abilities (common: ofs, num, header) - Misc.requireCondition(is.skip(0x34) == 0x34, "Could not advance in data stream: " + itmEntry); // offset = 0x64 int ofsAbil = StreamUtils.readInt(is); // abilities offset int numAbil = StreamUtils.readShort(is); // abilities count @@ -159,6 +431,8 @@ private void init() throws IOException, Exception ofsFx += idxFx * 0x30; // adjusting global effects offset numFx -= idxFx; // adjusting global effects count int curOfs = 0x72; // end of main structure + byte[] effect = new byte[48]; // buffer for effect entry + byte[] ability = new byte[56]; // buffer for ability entry // reading global color effects (attempt 1) int skip; @@ -167,7 +441,12 @@ private void init() throws IOException, Exception Misc.requireCondition(is.skip(skip) == skip, "Could not advance in data stream: " + itmEntry); curOfs += skip; // offset = [ofsFx] - curOfs += readEffects(is, numFx); + for (int i = 0; i < numFx; i++) { + Misc.requireCondition(is.read(effect) == effect.length, "Could not read effect " + i + ": " + itmEntry); + curOfs += effect.length; + EffectInfo ei = new EffectInfo(effect); + parseEffect(ei); + } } // reading ability types @@ -176,7 +455,11 @@ private void init() throws IOException, Exception Misc.requireCondition(skip >= 0 && is.skip(skip) == skip, "Could not advance in data stream: " + itmEntry); curOfs += skip; // offset = [ofsAbil] - curOfs += readAbilities(is, numAbil); + for (int i = 0; i < numAbil; i++) { + Misc.requireCondition(is.read(ability) == ability.length, "Could not read ability " + i + ": " + itmEntry); + curOfs += ability.length; + abilityInfo.add(new AbilityInfo(ability)); + } } // reading global color effects (attempt 2) @@ -185,86 +468,61 @@ private void init() throws IOException, Exception Misc.requireCondition(skip >= 0 && is.skip(skip) == skip, "Could not advance in data stream: " + itmEntry); curOfs += skip; // offset = [ofsFx] - curOfs += readEffects(is, numFx); + for (int i = 0; i < numFx; i++) { + Misc.requireCondition(is.read(effect) == effect.length, "Could not read effect " + i + ": " + itmEntry); + curOfs += effect.length; + EffectInfo ei = new EffectInfo(effect); + parseEffect(ei); + } } } } - // Processes global effects: only "set color" effect is considered - private int readEffects(InputStream is, int num) throws Exception + // Processes a global effect: only "set color" effect is considered + private void parseEffect(EffectInfo info) { - int retVal = 0; - while (num > 0) { - int opcode = StreamUtils.readShort(is); - if (opcode == 7) { - // set color - int target = is.read(); - is.read(); // skip power - int param1 = StreamUtils.readInt(is); - int param2 = StreamUtils.readInt(is); - int timing = is.read(); - Misc.requireCondition(is.skip(0x23) == 0x23, "Could not advance in data stream: " + itmEntry); - if (target == 1 && timing ==2) { - // self target; on equip - SegmentDef.SpriteType type = null; - int location = param2 & 0xf; - switch ((param2 >> 4) & 0xf) { - case 0: + if (info.getOpcode() == 7) { + // set color + if (info.getTarget() == 1 && info.getTiming() == 2) { + // self target; on equip + SegmentDef.SpriteType type = null; + int location = info.getParameter2() & 0xf; + switch ((info.getParameter2() >> 4) & 0xf) { + case 0: + type = SegmentDef.SpriteType.AVATAR; + break; + case 1: + type = SegmentDef.SpriteType.WEAPON; + break; + case 2: + type = SegmentDef.SpriteType.SHIELD; + break; + case 3: + type = SegmentDef.SpriteType.HELMET; + break; + default: + if ((info.getParameter2() & 0xff) == 0xff) { type = SegmentDef.SpriteType.AVATAR; - break; - case 1: - type = SegmentDef.SpriteType.WEAPON; - break; - case 2: - type = SegmentDef.SpriteType.SHIELD; - break; - case 3: - type = SegmentDef.SpriteType.HELMET; - break; - default: - if ((param2 & 0xff) == 0xff) { - type = SegmentDef.SpriteType.AVATAR; - location = -1; - } - } - getColorInfo().add(type, location, param1); + location = -1; + } } - } else { - // sync stream offset - Misc.requireCondition(is.skip(0x2e) == 0x2e, "Could not advance in data stream: " + itmEntry); + getColorInfo().add(type, location, info.getParameter1()); } - retVal += 0x30; - num--; - } - return retVal; // returns number of bytes read or skipped - } - - private int readAbilities(InputStream is, int num) throws Exception - { - int retVal = 0; - this.abilities = new int[num]; - while (num > 0) { - this.abilities[this.abilities.length - num] = StreamUtils.readShort(is); - retVal += 2; - Misc.requireCondition(is.skip(0x36) == 0x36, "Could not advance in data stream: " + itmEntry); - retVal += 0x36; - num--; } - return retVal; // returns number of bytes read or skipped } @Override public int hashCode() { int hash = 7; - hash = 31 * hash + ((colorInfo == null) ? 0 : colorInfo.hashCode()); + hash = 31 * hash + colorInfo.hashCode(); + hash = 31 * hash + abilityInfo.hashCode(); hash = 31 * hash + ((itmEntry == null) ? 0 : itmEntry.hashCode()); hash = 31 * hash + flags; hash = 31 * hash + category; hash = 31 * hash + unusable; hash = 31 * hash + unusableKits; hash = 31 * hash + ((appearance == null) ? 0 : appearance.hashCode()); - hash = 31 * hash + ((abilities == null) ? 0 : abilities.hashCode()); return hash; } @@ -280,6 +538,8 @@ public boolean equals(Object o) ItemInfo other = (ItemInfo)o; boolean retVal = (this.colorInfo == null && other.colorInfo == null) || (this.colorInfo != null && this.colorInfo.equals(other.colorInfo)); + retVal &= (this.abilityInfo == null && other.abilityInfo == null || + this.abilityInfo != null && this.abilityInfo.equals(other.abilityInfo)); retVal &= (this.itmEntry == null && other.itmEntry == null) || (this.itmEntry != null && this.itmEntry.equals(other.itmEntry)); retVal &= this.flags == other.flags; @@ -288,8 +548,160 @@ public boolean equals(Object o) retVal &= this.unusableKits == other.unusableKits; retVal &= (this.appearance == null && other.appearance == null) || (this.appearance != null && this.appearance.equals(other.appearance)); - retVal &= (this.abilities == null && other.abilities == null) || - (this.abilities != null && this.abilities.equals(other.abilities)); return retVal; } + +//-------------------------- INNER CLASSES -------------------------- + + /** Storage class for relevant ability attributes. */ + public static class AbilityInfo + { + private final int type; + private final int location; + private final int target; + private final int launcher; + private final int damageType; + private final int flags; + private final int projectile; + private final int probabilitySlash; + private final int probabilityBackslash; + private final int probabilityJab; + private final boolean isArrow; + private final boolean isBolt; + private final boolean isBullet; + + /** Parses the item ability structure described by the byte array. */ + private AbilityInfo(byte[] ability) + { + Objects.requireNonNull(ability); + DynamicArray buf = DynamicArray.wrap(ability, DynamicArray.ElementType.BYTE); + this.type = buf.getByte(0x0); + this.location = buf.getByte(0x2); + this.target = buf.getByte(0xc); + this.launcher = buf.getByte(0x10); + this.damageType = buf.getShort(0x1c); + this.flags = buf.getInt(0x26); + this.projectile = buf.getShort(0x2a); + this.probabilitySlash = buf.getShort(0x2c); + this.probabilityBackslash = buf.getShort(0x2e); + this.probabilityJab = buf.getShort(0x30); + this.isArrow = buf.getShort(0x32) != 0; + this.isBolt = buf.getShort(0x34) != 0; + this.isBullet = buf.getShort(0x36) != 0; + } + + /** Returns the ability type (1=melee, 2=ranged, 3=magical, 4=launcher). */ + public int getAbilityType() { return type; } + + /** Returns the ability location slot (1=weapon, 2=spell, 3=item, 4=ability). */ + public int getLocation() { return location; } + + /** Returns the target (1=living actor, 2=inventory, 3=dead actor, 4=any point, 5=caster, 7=caster immediately). */ + public int getTarget() { return target; } + + /** Returns the required launcher type (0=none, 1=bow, 2=crossbow, 3=sling). */ + public int getLauncher() { return launcher; } + + /** + * Returns the damage type (1=piercing, 2=crushing, 3=slashing, 4=missile, 5=fist, 6=piercing/crushing, + * 7=piercing/slashing, 8=crushing/slashing, 9=blunt missile). + */ + public int getDamageType() { return damageType; } + + /** Returns ability flags. */ + public int getFlags() { return flags; } + + /** Returns the projectile id. */ + public int getProjectile() { return projectile; } + + /** Returns the probability of triggering the slash attack animation. */ + public int getProbabilitySlash() { return probabilitySlash; } + + /** Returns the probability of triggering the backslash attack animation. */ + public int getProbabilityBackslash() { return probabilityBackslash; } + + /** Returns the probability of triggering the jab attack animation. */ + public int getProbabilityJab() { return probabilityJab; } + + /** Indicates whether the ability is an arrow. */ + public boolean isArrow() { return this.isArrow; } + + /** Indicates whether the ability is a bolt. */ + public boolean isBolt() { return this.isBolt; } + + /** Indicates whether the ability is a bullet. */ + public boolean isBullet() { return this.isBullet; } + } + + /** Storage class for relevant global effects attributes. */ + public static class EffectInfo + { + private final int opcode; + private final int target; + private final int power; + private final int parameter1; + private final int parameter2; + private final int timing; + private final int dispelResist; + private final int duration; + private final String resource; + private final int savingThrowFlags; + private final int savingThrow; + private final int special; + + /** Parses the EFF V1 structure described by the byte array. */ + private EffectInfo(byte[] effect) + { + Objects.requireNonNull(effect); + DynamicArray buf = DynamicArray.wrap(effect, DynamicArray.ElementType.BYTE); + this.opcode = buf.getShort(0x0); + this.target = buf.getByte(0x2); + this.power = buf.getByte(0x3); + this.parameter1 = buf.getShort(0x4); + this.parameter2 = buf.getShort(0x8); + this.timing = buf.getByte(0xc); + this.dispelResist = buf.getByte(0xd); + this.duration = buf.getByte(0xe); + this.resource = DynamicArray.getString(effect, 0x14, 8, Charset.forName("windows-1252")); + this.savingThrowFlags = buf.getInt(0x24); + this.savingThrow = buf.getInt(0x28); + this.special = buf.getInt(0x2c); + } + + /** Returns the effect opcode. */ + public int getOpcode() { return opcode; } + + /** Returns the effect target. */ + public int getTarget() { return target; } + + /** Returns the effect power. */ + public int getPower() { return power; } + + /** Returns parameter1 of the effect (meaning depends on opcode). */ + public int getParameter1() { return parameter1; } + + /** Returns parameter2 of the effect (meaning depends on opcode). */ + public int getParameter2() { return parameter2; } + + /** Returns the timing mode. */ + public int getTiming() { return timing; } + + /** Returns the dispel and resistance mode. */ + public int getDispelResist() { return dispelResist; } + + /** Returns the effect duration. */ + public int getDuration() { return duration; } + + /** Returns the resource resref of the effect. Returns empty string otherwise. */ + public String getResource() { return resource; } + + /** Returns the saving throw flags. */ + public int getSavingThrowFlags() { return savingThrowFlags; } + + /** Returns the saving throw bonus. */ + public int getSavingThrow() { return savingThrow; } + + /** Returns the special parameter used by selected effects. */ + public int getSpecial() { return special; } + } } From d133f4533044834292380bc0bea5656469eb3aa8 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Wed, 3 Mar 2021 19:32:42 +0100 Subject: [PATCH 031/158] Fix ColorValue color names for random colors --- src/org/infinity/datatype/ColorValue.java | 106 +++++++++++++--------- 1 file changed, 62 insertions(+), 44 deletions(-) diff --git a/src/org/infinity/datatype/ColorValue.java b/src/org/infinity/datatype/ColorValue.java index 8c840d80b..270d13f4f 100644 --- a/src/org/infinity/datatype/ColorValue.java +++ b/src/org/infinity/datatype/ColorValue.java @@ -27,8 +27,9 @@ import java.io.OutputStream; import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.HashSet; +import java.util.HashMap; import java.util.List; +import java.util.Objects; import javax.swing.AbstractListModel; import javax.swing.BorderFactory; @@ -78,6 +79,8 @@ public class ColorValue extends Datatype implements Editable, IsNumeric private static final int DEFAULT_COLOR_WIDTH = 16; private static final int DEFAULT_COLOR_HEIGHT = 24; + private final HashMap randomColors = new HashMap<>(); + private int number; private JList colorList; private ResourceEntry colorEntry; // the source of color ranges @@ -91,13 +94,35 @@ public ColorValue(ByteBuffer buffer, int offset, int length, String name) public ColorValue(ByteBuffer buffer, int offset, int length, String name, String bmpFile) { super(offset, length, name); + init(bmpFile); + read(buffer, offset); + } + + private void init(String bmpFile) + { if (bmpFile != null && ResourceFactory.resourceExists(bmpFile)) { this.colorEntry = ResourceFactory.getResourceEntry(bmpFile); } if (ResourceFactory.resourceExists("CLOWNCLR.IDS")) { this.colorMap = IdsMapCache.get("CLOWNCLR.IDS"); } - read(buffer, offset); + + ResourceEntry randomEntry = null; + if (ResourceFactory.resourceExists("RANDCOLR.2DA")) { + randomEntry = ResourceFactory.getResourceEntry("RANDCOLR.2DA"); + } + + // collecting valid random color indices + if (randomEntry != null) { + Table2da table = Table2daCache.get(randomEntry); + for (int col = 1, count = table.getColCount(); col < count; col++) { + int index = Misc.toNumber(table.get(0, col), -1); + String name = Misc.prettifySymbol(table.getHeader(col)); + if (index >= 0 && index < 256) { + randomColors.put(index, name); + } + } + } } //--------------------- Begin Interface Editable --------------------- @@ -106,8 +131,9 @@ public ColorValue(ByteBuffer buffer, int offset, int length, String name, String public JComponent edit(ActionListener container) { int defaultColorWidth = (colorEntry == null) ? DEFAULT_COLOR_WIDTH : 0; - colorList = new JList<>(new ColorListModel(defaultColorWidth, DEFAULT_COLOR_HEIGHT, colorEntry)); - colorList.setCellRenderer(new ColorCellRenderer(colorMap)); + ColorListModel colorModel = new ColorListModel(this, defaultColorWidth, DEFAULT_COLOR_HEIGHT); + colorList = new JList<>(colorModel); + colorList.setCellRenderer(new ColorCellRenderer(this)); colorList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); colorList.setBorder(BorderFactory.createLineBorder(Color.GRAY)); colorList.addMouseListener(new MouseAdapter() { @@ -237,13 +263,26 @@ public int getValue() public String toString() { String retVal = "Color index " + Integer.toString(number); - String name = lookupColorName(colorMap, number, true); + String name = getColorName(number); if (name != null) { retVal += " (" + name + ")"; } return retVal; } + /** + * Returns the name associated with the specified color entry. + * Returns {@code null} if no name is available. + */ + public String getColorName(int index) + { + String retVal = randomColors.get(Integer.valueOf(index)); + if (retVal == null) { + retVal = lookupColorName(colorMap, index, true); + } + return retVal; + } + /** * Returns a symbolic color name based on the specified IDS lookup. * Returns {@code null} if no lookup table is available. @@ -277,12 +316,12 @@ private static final class ColorCellRenderer extends DefaultListCellRenderer { private static final Border DEFAULT_NO_FOCUS_BORDER = new EmptyBorder(1, 1, 1, 1); - private final IdsMap colorMap; + private final ColorValue colorValue; - public ColorCellRenderer(IdsMap colorMap) + public ColorCellRenderer(ColorValue cv) { super(); - this.colorMap = colorMap; + this.colorValue = Objects.requireNonNull(cv); setVerticalAlignment(SwingConstants.CENTER); setHorizontalTextPosition(SwingConstants.RIGHT); setVerticalTextPosition(SwingConstants.CENTER); @@ -302,7 +341,7 @@ public Component getListCellRendererComponent(JList list, Object value, int i } String label = "Index " + index; - String name = lookupColorName(colorMap, index, true); + String name = colorValue.getColorName(index); if (name != null) { label += " (" + name + ")"; } @@ -338,12 +377,13 @@ public Component getListCellRendererComponent(JList list, Object value, int i private static final class ColorListModel extends AbstractListModel { - private final HashSet randomColors = new HashSet<>(); private final List colors = new ArrayList<>(256); + private final ColorValue colorValue; - public ColorListModel(int defaultWidth, int defaultHeight, ResourceEntry colorsEntry) + public ColorListModel(ColorValue cv, int defaultWidth, int defaultHeight) { - initEntries(defaultWidth, defaultHeight, colorsEntry); + this.colorValue = Objects.requireNonNull(cv); + initEntries(defaultWidth, defaultHeight); } @Override @@ -361,46 +401,24 @@ public Image getElementAt(int index) return null; } - private void initEntries(int defaultWidth, int defaultHeight, ResourceEntry colorsEntry) + private void initEntries(int defaultWidth, int defaultHeight) { - if (colorsEntry == null) { + if (colorValue.colorEntry == null) { if (ResourceFactory.resourceExists("RANGES12.BMP")) { - colorsEntry = ResourceFactory.getResourceEntry("RANGES12.BMP"); + colorValue.colorEntry = ResourceFactory.getResourceEntry("RANGES12.BMP"); } else if (ResourceFactory.resourceExists("MPALETTE.BMP")) { - colorsEntry = ResourceFactory.getResourceEntry("MPALETTE.BMP"); + colorValue.colorEntry = ResourceFactory.getResourceEntry("MPALETTE.BMP"); } } - ResourceEntry randomEntry = null; - if (ResourceFactory.resourceExists("RANDCOLR.2DA")) { - randomEntry = ResourceFactory.getResourceEntry("RANDCOLR.2DA"); - } - - // collecting valid random color indices - int maxValue = colors.size() - 1; - if (randomEntry != null) { - Table2da table = Table2daCache.get(randomEntry); - for (int col = 1; col < table.getColCount(); col++) { - String s = table.get(0, col); - if (s != null) { - try { - Integer v = Integer.valueOf(Integer.parseInt(s)); - if (v >= colors.size() && v < 256 && !randomColors.contains(v)) { - randomColors.add(v); - maxValue = Math.max(maxValue, v.intValue()); - } - } catch (NumberFormatException e) { - } - } - } - } // scanning range of colors - if (colorsEntry != null) { + int maxValue = 255; // default size + if (colorValue.colorEntry != null) { BufferedImage image = null; try { - image = new GraphicsResource(colorsEntry).getImage(); - maxValue = Math.max(maxValue, image.getHeight()); + image = new GraphicsResource(colorValue.colorEntry).getImage(); + maxValue = Math.max(maxValue, image.getHeight() - 1); if (defaultWidth <= 0) { // auto-calculate color width defaultWidth = 192 / image.getWidth(); @@ -408,7 +426,7 @@ private void initEntries(int defaultWidth, int defaultHeight, ResourceEntry colo for (int idx = 0; idx <= maxValue; idx++) { BufferedImage range; - if (!randomColors.contains(Integer.valueOf(idx)) && idx < image.getHeight()) { + if (!colorValue.randomColors.containsKey(Integer.valueOf(idx)) && idx < image.getHeight()) { // fixed color range = getFixedColor(image, idx, defaultWidth, defaultHeight); } else { @@ -451,7 +469,7 @@ private BufferedImage getVirtualColor(int index, int width, int height) BufferedImage retVal = null; Color invalidColor = new Color(0xe0e0e0); - boolean isRandom = randomColors.contains(Integer.valueOf(index)); + boolean isRandom = colorValue.randomColors.containsKey(Integer.valueOf(index)); retVal = new BufferedImage(12 * width, height, BufferedImage.TYPE_INT_RGB); Graphics2D g = retVal.createGraphics(); try { From 8ca3223ba91347ce1b775b7fcb2346d43e218c60 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Thu, 4 Mar 2021 14:24:13 +0100 Subject: [PATCH 032/158] Improve method for prettifying text --- src/org/infinity/util/Misc.java | 69 ++++++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 18 deletions(-) diff --git a/src/org/infinity/util/Misc.java b/src/org/infinity/util/Misc.java index c3a326964..9e8e1b73c 100644 --- a/src/org/infinity/util/Misc.java +++ b/src/org/infinity/util/Misc.java @@ -410,27 +410,60 @@ public static int getScaledValue(int value) public static String prettifySymbol(String symbol) { if (symbol != null) { - StringBuilder sb = new StringBuilder(symbol); - boolean upper = true; - for (int idx = 0, len = sb.length(); idx < len; idx++) { - char ch = sb.charAt(idx); - if (upper) { - ch = Character.toUpperCase(ch); - upper = false; - } else { - ch = Character.toLowerCase(ch); - } + StringBuilder sb = new StringBuilder(); + boolean isUpper = false; + boolean isDigit = false; + boolean isPrevUpper = false; + boolean isPrevDigit = false; + boolean toUpper = true; + for (int idx = 0, len = symbol.length(); idx < len; idx++) { + char ch = symbol.charAt(idx); if (" ,-_".indexOf(ch) >= 0) { - if (ch == '_') ch = ' '; - if (ch == '-') { - sb.insert(idx, " -"); - ch = ' '; - idx += 2; - len += 2; + // improve spacing + switch (ch) { + case '_': + sb.append(' '); + break; + case '-': + sb.append(" - "); + break; + default: + sb.append(ch); + } + toUpper = true; + } else { + if (toUpper) { + ch = Character.toUpperCase(ch); + toUpper = false; + } + isPrevUpper = isUpper; + isPrevDigit = isDigit; + isUpper = Character.isUpperCase(ch); + isDigit = Character.isDigit(ch); + if (idx > 0) { + // detect word boundaries + char chPrev = sb.charAt(sb.length() - 1); + if (chPrev != ' ') { + if (isUpper && !isPrevUpper && !isPrevDigit) { + sb.append(' '); + } else if (isDigit && !isPrevDigit) { + sb.append(' '); + } + } + + chPrev = sb.charAt(sb.length() - 1); + if (isUpper && chPrev != ' ') { + // prevent upper case characters in the middle of words + ch = Character.toLowerCase(ch); + } + + if (!isUpper && chPrev == ' ') { + // new words start with upper case character + ch = Character.toUpperCase(ch); + } } - upper = true; + sb.append(ch); } - sb.setCharAt(idx, ch); } symbol = sb.toString(); } From a3fe60e6bfffac4bf442930d0f0603f6495ec8b6 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Thu, 4 Mar 2021 22:14:41 +0100 Subject: [PATCH 033/158] BAM Decoder: Add option to change frame center --- .../resource/graphics/BamDecoder.java | 3 +++ .../resource/graphics/BamV1Decoder.java | 16 +++++++++--- .../resource/graphics/BamV2Decoder.java | 16 +++++++++--- .../resource/graphics/PseudoBamDecoder.java | 26 ++++++++----------- 4 files changed, 38 insertions(+), 23 deletions(-) diff --git a/src/org/infinity/resource/graphics/BamDecoder.java b/src/org/infinity/resource/graphics/BamDecoder.java index 6e1e7305b..95a8ea98e 100644 --- a/src/org/infinity/resource/graphics/BamDecoder.java +++ b/src/org/infinity/resource/graphics/BamDecoder.java @@ -213,6 +213,9 @@ public interface FrameEntry public int getHeight(); public int getCenterX(); public int getCenterY(); + public void setCenterX(int x); + public void setCenterY(int y); + public void resetCenter(); } diff --git a/src/org/infinity/resource/graphics/BamV1Decoder.java b/src/org/infinity/resource/graphics/BamV1Decoder.java index 3aed6b2b1..71d339502 100644 --- a/src/org/infinity/resource/graphics/BamV1Decoder.java +++ b/src/org/infinity/resource/graphics/BamV1Decoder.java @@ -378,6 +378,7 @@ public boolean equals(Object o) public class BamV1FrameEntry implements BamDecoder.FrameEntry { private int width, height, centerX, centerY, ofsData; + private int overrideCenterX, overrideCenterY; private boolean compressed; private BamV1FrameEntry(ByteBuffer buffer, int ofs) @@ -385,8 +386,8 @@ private BamV1FrameEntry(ByteBuffer buffer, int ofs) if (buffer != null && ofs + 12 <= buffer.limit()) { width = buffer.getShort(ofs + 0) & 0xffff; height = buffer.getShort(ofs + 2) & 0xffff; - centerX = buffer.getShort(ofs + 4); - centerY = buffer.getShort(ofs + 6); + centerX = overrideCenterX = buffer.getShort(ofs + 4); + centerY = overrideCenterY = buffer.getShort(ofs + 6); ofsData = buffer.getInt(ofs + 8) & 0x7fffffff; compressed = (buffer.getInt(ofs + 8) & 0x80000000) == 0; } else { @@ -400,9 +401,16 @@ private BamV1FrameEntry(ByteBuffer buffer, int ofs) @Override public int getHeight() { return height; } @Override - public int getCenterX() { return centerX; } + public int getCenterX() { return overrideCenterX; } @Override - public int getCenterY() { return centerY; } + public int getCenterY() { return overrideCenterY; } + + @Override + public void setCenterX(int x) { overrideCenterX = Math.max(Short.MIN_VALUE, Math.min(Short.MAX_VALUE, x)); } + @Override + public void setCenterY(int y) { overrideCenterY = Math.max(Short.MIN_VALUE, Math.min(Short.MAX_VALUE, y)); } + @Override + public void resetCenter() { overrideCenterX = centerX; overrideCenterY = centerY; } public boolean isCompressed() { return compressed; } } diff --git a/src/org/infinity/resource/graphics/BamV2Decoder.java b/src/org/infinity/resource/graphics/BamV2Decoder.java index 6f8ad9a40..4cad8aec8 100644 --- a/src/org/infinity/resource/graphics/BamV2Decoder.java +++ b/src/org/infinity/resource/graphics/BamV2Decoder.java @@ -368,6 +368,7 @@ public class BamV2FrameEntry implements BamDecoder.FrameEntry private final int dataBlockSize = 0x1c; // size of a single data block private int width, height, centerX, centerY; + private int overrideCenterX, overrideCenterY; private BufferedImage frame; private BamV2FrameEntry(ByteBuffer buffer, int ofsFrame, int ofsBlocks) @@ -375,8 +376,8 @@ private BamV2FrameEntry(ByteBuffer buffer, int ofsFrame, int ofsBlocks) if (buffer != null && ofsFrame < buffer.limit() && ofsBlocks < buffer.limit()) { width = buffer.getShort(ofsFrame) & 0xffff; height = buffer.getShort(ofsFrame+2) & 0xffff; - centerX = buffer.getShort(ofsFrame+4); - centerY = buffer.getShort(ofsFrame+6); + centerX = overrideCenterX = buffer.getShort(ofsFrame+4); + centerY = overrideCenterY = buffer.getShort(ofsFrame+6); int blockStart = buffer.getShort(ofsFrame+8) & 0xffff; int blockCount = buffer.getShort(ofsFrame+10) & 0xffff; decodeImage(buffer, ofsBlocks, blockStart, blockCount); @@ -391,9 +392,16 @@ private BamV2FrameEntry(ByteBuffer buffer, int ofsFrame, int ofsBlocks) @Override public int getHeight() { return height; } @Override - public int getCenterX() { return centerX; } + public int getCenterX() { return overrideCenterX; } @Override - public int getCenterY() { return centerY; } + public int getCenterY() { return overrideCenterY; } + + @Override + public void setCenterX(int x) { overrideCenterX = Math.max(Short.MIN_VALUE, Math.min(Short.MAX_VALUE, x)); } + @Override + public void setCenterY(int y) { overrideCenterY = Math.max(Short.MIN_VALUE, Math.min(Short.MAX_VALUE, y)); } + @Override + public void resetCenter() { overrideCenterX = centerX; overrideCenterY = centerY; } public Image getImage() { return frame; } diff --git a/src/org/infinity/resource/graphics/PseudoBamDecoder.java b/src/org/infinity/resource/graphics/PseudoBamDecoder.java index a5068e96c..e56537095 100644 --- a/src/org/infinity/resource/graphics/PseudoBamDecoder.java +++ b/src/org/infinity/resource/graphics/PseudoBamDecoder.java @@ -1401,6 +1401,7 @@ public static class PseudoBamFrameEntry implements FrameEntry private final HashMap mapOptions = new HashMap(); private int width, height, centerX, centerY; + private int overrideCenterX, overrideCenterY; private BufferedImage frame; public PseudoBamFrameEntry(BufferedImage image, int centerX, int centerY) @@ -1408,6 +1409,8 @@ public PseudoBamFrameEntry(BufferedImage image, int centerX, int centerY) setFrame(image); setCenterX(centerX); setCenterY(centerY); + this.centerX = getCenterX(); + this.centerY = getCenterY(); } @Override @@ -1415,23 +1418,16 @@ public PseudoBamFrameEntry(BufferedImage image, int centerX, int centerY) @Override public int getHeight() { return height; } @Override - public int getCenterX() { return centerX; } + public int getCenterX() { return overrideCenterX; } @Override - public int getCenterY() { return centerY; } + public int getCenterY() { return overrideCenterY; } - public void setCenterX(int value) - { - if (value < Short.MIN_VALUE) value = Short.MIN_VALUE; - else if (value > Short.MAX_VALUE) value = Short.MAX_VALUE; - centerX = value; - } - - public void setCenterY(int value) - { - if (value < Short.MIN_VALUE) value = Short.MIN_VALUE; - else if (value > Short.MAX_VALUE) value = Short.MAX_VALUE; - centerY = value; - } + @Override + public void setCenterX(int x) { overrideCenterX = Math.max(Short.MIN_VALUE, Math.min(Short.MAX_VALUE, x)); } + @Override + public void setCenterY(int y) { overrideCenterY = Math.max(Short.MIN_VALUE, Math.min(Short.MAX_VALUE, y)); } + @Override + public void resetCenter() { overrideCenterX = centerX; overrideCenterY = centerY; } /** Returns the image object of this frame entry. */ public BufferedImage getFrame() From 4dbe1be106e6756a66258e882daa50069333f16a Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Thu, 4 Mar 2021 22:16:51 +0100 Subject: [PATCH 034/158] SpriteDecoder: Fix Pillar of Skulls shadow (PST) --- .../cre/decoder/MonsterPlanescapeDecoder.java | 79 +++++++++++++++++-- 1 file changed, 71 insertions(+), 8 deletions(-) diff --git a/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java index 51f9ce967..1b6fc9a53 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java @@ -19,9 +19,12 @@ import org.infinity.resource.cre.CreResource; import org.infinity.resource.cre.decoder.internal.DecoderAttribute; import org.infinity.resource.cre.decoder.internal.DirDef; +import org.infinity.resource.cre.decoder.internal.SegmentDef; import org.infinity.resource.cre.decoder.internal.SeqDef; import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.graphics.BamV1Decoder; import org.infinity.resource.graphics.BamV1Decoder.BamV1Control; +import org.infinity.resource.graphics.BamV1Decoder.BamV1FrameEntry; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; @@ -45,6 +48,48 @@ public class MonsterPlanescapeDecoder extends SpriteDecoder public static final DecoderAttribute KEY_SHADOW = DecoderAttribute.with("shadow", DecoderAttribute.DataType.STRING); public static final DecoderAttribute KEY_RESREF2 = DecoderAttribute.with("resref2", DecoderAttribute.DataType.STRING); + protected final BeforeSourceBam FN_BEFORE_SRC_BAM = new BeforeSourceBam() { + @Override + public void accept(BamV1Control control, SegmentDef sd) + { + String resref = sd.getEntry().getResourceRef(); + if (resref.equalsIgnoreCase("POSSHAD")) { + // fix hardcoded "Pillar of Skulls" shadow center: shift by: [193.59] + BamV1Decoder decoder = control.getDecoder(); + for (int idx = 0, count = decoder.frameCount(); idx < count; idx++) { + BamV1FrameEntry entry = decoder.getFrameInfo(idx); + entry.resetCenter(); + entry.setCenterX(entry.getCenterX() + 193); + entry.setCenterY(entry.getCenterY() + 59); + } +// } else if (resref.equalsIgnoreCase("IGARM")) { +// // TODO: shift Coaxmetal arm animation center by fixed amount +// // shift arm cycle=0 by: [-59.83] +// // shift arm cycle=1 by: [-60.233] +// Point pt; +// if (sd.getCycleIndex() == 0) { +// pt = new Point(-59, 83); +// } else { +// pt = new Point(-60, 233); +// } +// BamV1Decoder decoder = control.getDecoder(); +// for (int idx = 0, count = control.cycleFrameCount(sd.getCycleIndex()); idx < count; idx++) { +// int frameIdx = control.cycleGetFrameIndexAbsolute(sd.getCycleIndex(), idx); +// BamV1FrameEntry entry = decoder.getFrameInfo(frameIdx); +// entry.setCenterX(entry.getCenterX() + pt.x); +// entry.setCenterY(entry.getCenterY() + pt.y); +// } + } + SpriteUtils.fixShadowColor(control); + if (isFalseColor()) { + applyFalseColors(control, sd); + } + if (isTranslucent()) { + applyTranslucency(control); + } + } + }; + // available animation slot names private static final HashMap Slots = new HashMap() {{ put(Sequence.PST_ATTACK1, "attack1"); @@ -395,14 +440,13 @@ protected void init() throws Exception setAttribute(KEY_COLOR_LOCATION, setColorLocations(section)); // hardcoded stuff - // TODO: fix center positions of secondary BAM sources -// String resref = section.getAsString(Slots.get(Sequence.PST_Stand), ""); -// if ("POSMAIN".equalsIgnoreCase(resref)) { -// // pillar of skulls -// setAttribute(KEY_SHADOW, "POSSHAD"); -// } + String resref = section.getAsString(Slots.get(Sequence.PST_STAND), ""); + if ("POSMAIN".equalsIgnoreCase(resref)) { + // Pillar of Skulls: relocate shadow center + setAttribute(KEY_SHADOW, "POSSHAD"); + } // if ("IGHEAD".equalsIgnoreCase(resref)) { -// // "Coaxmetal" (iron golem) +// // "Coaxmetal" (iron golem): relocate center of arm segments // setAttribute(KEY_RESREF2, "IGARM"); // } if ((getAnimationId() & 0x0fff) == 0x000e) { @@ -412,6 +456,23 @@ protected void init() throws Exception } } + @Override + protected void createSequence(Sequence seq) throws Exception + { + SeqDef sd = Objects.requireNonNull(getSequenceDefinition(seq), "Sequence not available: " + (seq != null ? seq : "(null)")); + createAnimation(sd, null, FN_BEFORE_SRC_BAM, FN_BEFORE_SRC_FRAME, FN_AFTER_SRC_FRAME, FN_AFTER_DST_FRAME); + } + + @Override + protected void createSequence(Sequence seq, Direction[] directions) throws Exception + { + SeqDef sd = Objects.requireNonNull(getSequenceDefinition(seq), "Sequence not available: " + (seq != null ? seq : "(null)")); + if (directions == null) { + directions = Direction.values(); + } + createAnimation(sd, Arrays.asList(directions), FN_BEFORE_SRC_BAM, FN_BEFORE_SRC_FRAME, FN_AFTER_SRC_FRAME, FN_AFTER_DST_FRAME); + } + @Override protected SeqDef getSequenceDefinition(Sequence seq) { @@ -432,7 +493,9 @@ protected SeqDef getSequenceDefinition(Sequence seq) // Sprite resref String name = Slots.get(seq); resref = section.getAsString(name, ""); - if (!resref.isEmpty()) { + if (resref.isEmpty()) { + return retVal; + } else { ResourceEntry entry = ResourceFactory.getResourceEntry(resref.toUpperCase(Locale.ENGLISH) + "B.BAM"); if (entry == null) { entry = ResourceFactory.getResourceEntry(resref.toUpperCase(Locale.ENGLISH) + ".BAM"); From 90f5604c9a054254679ed66257c7d1e2218f61af Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 5 Mar 2021 11:23:23 +0100 Subject: [PATCH 035/158] SpriteDecoder: fix display of PST Pillar of Skulls --- .../cre/decoder/MonsterPlanescapeDecoder.java | 28 ++++++------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java index 1b6fc9a53..1fbfe71ab 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java @@ -53,8 +53,9 @@ public class MonsterPlanescapeDecoder extends SpriteDecoder public void accept(BamV1Control control, SegmentDef sd) { String resref = sd.getEntry().getResourceRef(); + + // fix hardcoded "Pillar of Skulls" shadow center: shift by: [193.59] if (resref.equalsIgnoreCase("POSSHAD")) { - // fix hardcoded "Pillar of Skulls" shadow center: shift by: [193.59] BamV1Decoder decoder = control.getDecoder(); for (int idx = 0, count = decoder.frameCount(); idx < count; idx++) { BamV1FrameEntry entry = decoder.getFrameInfo(idx); @@ -62,28 +63,17 @@ public void accept(BamV1Control control, SegmentDef sd) entry.setCenterX(entry.getCenterX() + 193); entry.setCenterY(entry.getCenterY() + 59); } -// } else if (resref.equalsIgnoreCase("IGARM")) { -// // TODO: shift Coaxmetal arm animation center by fixed amount -// // shift arm cycle=0 by: [-59.83] -// // shift arm cycle=1 by: [-60.233] -// Point pt; -// if (sd.getCycleIndex() == 0) { -// pt = new Point(-59, 83); -// } else { -// pt = new Point(-60, 233); -// } -// BamV1Decoder decoder = control.getDecoder(); -// for (int idx = 0, count = control.cycleFrameCount(sd.getCycleIndex()); idx < count; idx++) { -// int frameIdx = control.cycleGetFrameIndexAbsolute(sd.getCycleIndex(), idx); -// BamV1FrameEntry entry = decoder.getFrameInfo(frameIdx); -// entry.setCenterX(entry.getCenterX() + pt.x); -// entry.setCenterY(entry.getCenterY() + pt.y); -// } } - SpriteUtils.fixShadowColor(control); + + // "Pillar of Skulls" uses separate shadow animation + if (!resref.equalsIgnoreCase("POSMAIN")) { + SpriteUtils.fixShadowColor(control); + } + if (isFalseColor()) { applyFalseColors(control, sd); } + if (isTranslucent()) { applyTranslucency(control); } From 4fd7563359eab6b7918adf160638b2e486630f5b Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 5 Mar 2021 14:09:26 +0100 Subject: [PATCH 036/158] Improve area viewer label text --- src/org/infinity/resource/are/viewer/AreaViewer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/infinity/resource/are/viewer/AreaViewer.java b/src/org/infinity/resource/are/viewer/AreaViewer.java index c7e7d7f60..bf6331d81 100644 --- a/src/org/infinity/resource/are/viewer/AreaViewer.java +++ b/src/org/infinity/resource/are/viewer/AreaViewer.java @@ -387,11 +387,11 @@ private void init() t.add(t2); if (i == LayerManager.getLayerTypeIndex(LayerType.ACTOR)) { // Initializing real creature animation checkboxes - cbLayerRealActor[0] = new JCheckBox("Show actor sprite"); + cbLayerRealActor[0] = new JCheckBox("Show actor animation"); cbLayerRealActor[0].addActionListener(getListeners()); t3 = new DefaultMutableTreeNode(cbLayerRealActor[0]); t2.add(t3); - cbLayerRealActor[1] = new JCheckBox("Animate actor sprite"); + cbLayerRealActor[1] = new JCheckBox("Animate actor animation"); cbLayerRealActor[1].addActionListener(getListeners()); t3 = new DefaultMutableTreeNode(cbLayerRealActor[1]); t2.add(t3); From a550323e65338d4d2e6ac037aa2d6571710bf826 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 5 Mar 2021 14:11:30 +0100 Subject: [PATCH 037/158] Add various improvements to ItemInfo class --- .../cre/decoder/internal/ItemInfo.java | 253 ++++++++++++++++-- 1 file changed, 235 insertions(+), 18 deletions(-) diff --git a/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java b/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java index aa448c61c..233903ca0 100644 --- a/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java +++ b/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java @@ -28,12 +28,23 @@ /** * Provides useful information about equippable items. */ -public class ItemInfo +public class ItemInfo implements Comparable { + /** + * This predicate simply returns {@code true} for all items. + */ + public static final ItemPredicate FILTER_ALL = new ItemPredicate() { + @Override + public boolean test(ItemInfo info) + { + return true; + } + }; + /** * This predicate returns {@code true} only if the item can be equipped and unequipped in the character inventory. */ - public static final Predicate FILTER_EQUIPPABLE = new Predicate() { + public static final ItemPredicate FILTER_EQUIPPABLE = new ItemPredicate() { @Override public boolean test(ItemInfo info) { @@ -44,7 +55,7 @@ public boolean test(ItemInfo info) /** * This predicate returns {@code true} only if the item can be equipped in a weapon slot. */ - public static final Predicate FILTER_WEAPON = new Predicate() { + public static final ItemPredicate FILTER_WEAPON = new ItemPredicate() { @Override public boolean test(ItemInfo info) { @@ -59,7 +70,7 @@ public boolean test(ItemInfo info) /** * This predicate returns {@code true} if the item is a two-handed weapon (melee or ranged). */ - public static final Predicate FILTER_WEAPON_2H = new Predicate() { + public static final ItemPredicate FILTER_WEAPON_2H = new ItemPredicate() { @Override public boolean test(ItemInfo info) { @@ -76,7 +87,7 @@ public boolean test(ItemInfo info) * This predicate returns {@code true} only if the item can be placed in a weapon slot and the default ability * is defined as melee type. */ - public static final Predicate FILTER_WEAPON_MELEE = new Predicate() { + public static final ItemPredicate FILTER_WEAPON_MELEE = new ItemPredicate() { @Override public boolean test(ItemInfo info) { @@ -94,7 +105,7 @@ public boolean test(ItemInfo info) * This predicate returns {@code true} only if {@link #FILTER_WEAPON_MELEE} passes the test and the item is flagged * as two-handed or fake two-handed (e.g. monk fists). */ - public static final Predicate FILTER_WEAPON_MELEE_2H = new Predicate() { + public static final ItemPredicate FILTER_WEAPON_MELEE_2H = new ItemPredicate() { @Override public boolean test(ItemInfo info) { @@ -111,7 +122,7 @@ public boolean test(ItemInfo info) * This predicate returns {@code true} only if {@link #FILTER_WEAPON_MELEE} passes the test and the item can be * equipped in the shield slot. */ - public static final Predicate FILTER_WEAPON_MELEE_LEFT_HANDED = new Predicate() { + public static final ItemPredicate FILTER_WEAPON_MELEE_LEFT_HANDED = new ItemPredicate() { @Override public boolean test(ItemInfo info) { @@ -129,7 +140,7 @@ public boolean test(ItemInfo info) * This predicate returns {@code true} only if the item can be placed in a weapon slot and the default ability * is defined as ranged or launcher type. */ - public static final Predicate FILTER_WEAPON_RANGED = new Predicate() { + public static final ItemPredicate FILTER_WEAPON_RANGED = new ItemPredicate() { @Override public boolean test(ItemInfo info) { @@ -143,6 +154,101 @@ public boolean test(ItemInfo info) } }; + /** + * This predicate returns {@code true} only if the item can be placed in a weapon slot and is a + * ranged launcher-type weapon. + */ + public static final ItemPredicate FILTER_WEAPON_RANGED_LAUNCHER = new ItemPredicate() { + @Override + public boolean test(ItemInfo info) + { + boolean retVal = FILTER_WEAPON.test(info); + if (retVal && info.getAbilityCount() > 0) { + AbilityInfo ai = info.getAbility(0); + retVal &= (ai.getLauncher() == 0); + retVal &= (ai.getAbilityType() == 4); + } + return retVal; + } + }; + + /** + * This predicate returns {@code true} only if the item is a shield that can be placed in the shield slot. + */ + public static final ItemPredicate FILTER_SHIELD = new ItemPredicate() { + @Override + public boolean test(ItemInfo info) + { + boolean retVal = FILTER_EQUIPPABLE.test(info); + if (retVal) { + switch (info.getCategory()) { + case 12: // Shields + case 41: // Bucklers + case 47: // Large shields + case 49: // Medium shields + case 53: // Small shields + break; + default: + retVal = false; + } + } + return retVal; + } + }; + + /** + * This predicate returns {@code true} only if the item can be placed in the armor slot. + */ + public static final ItemPredicate FILTER_ARMOR = new ItemPredicate() { + @Override + public boolean test(ItemInfo info) + { + boolean retVal = FILTER_EQUIPPABLE.test(info); + if (retVal) { + switch (info.getCategory()) { + case 2: // Armor + case 60: // Leather armor + case 61: // Studded leather + case 62: // Chain mail + case 63: // Split mail + case 64: // Plate mail + case 65: // Full plate + case 66: // Hide armor + case 67: // Robes + case 68: // Scale mail + break; + default: + retVal = false; + } + } + return retVal; + } + }; + + /** + * This predicate returns {@code true} only if the item can be placed in the helmet slot. + */ + public static final ItemPredicate FILTER_HELMET = new ItemPredicate() { + @Override + public boolean test(ItemInfo info) + { + boolean retVal = FILTER_EQUIPPABLE.test(info); + if (retVal) { + switch (info.getCategory()) { + case 7: // Headgear + case 72: // Hats + break; + default: + retVal = false; + } + } + return retVal; + } + }; + + /** Predefined {@code ItemInfo} structure without associated ITM resource. */ + public static final ItemInfo EMPTY = new ItemInfo(); + private static final HashMap ITEM_CACHE = new HashMap<>(); private final ColorInfo colorInfo = new ColorInfo(); @@ -171,7 +277,7 @@ public static ItemInfo get(ResourceEntry entry) throws Exception { ItemInfo retVal = null; if (entry == null) { - return retVal; + return EMPTY; } synchronized (ITEM_CACHE) { retVal = ITEM_CACHE.get(entry); @@ -194,7 +300,7 @@ public static ItemInfo getValidated(ResourceEntry entry) throws Exception { ItemInfo retVal = null; if (entry == null) { - return retVal; + return EMPTY; } synchronized (ITEM_CACHE) { retVal = new ItemInfo(entry); @@ -218,7 +324,7 @@ public static void clearCache() * @param sorted whether the returned list is sorted by {@code ResourceEntry} in ascending order. * @return list of matching {@code ItemInfo} structures. */ - public static List getItemList(Predicate pred, boolean sorted) + public static List getItemList(ItemPredicate pred, boolean sorted) { List retVal = new ArrayList<>(); @@ -238,19 +344,52 @@ public static List getItemList(Predicate pred, boolean sorte } if (sorted) { - Collections.sort(retVal, (i1, i2) -> i1.itmEntry.compareTo(i2.itmEntry)); + Collections.sort(retVal); } return retVal; } /** - * Convenience method: Returns {@code true} if the given item passes the specified test. - * Returns {@code false} if info is {@code null}. Returns {@code true} if pred is {@code null}. + * Convenience method: Returns {@code true} if the given item passes all the specified tests. + * Returns {@code false} if info is {@code null}. + * Returns {@code true} if no predicate is specified. */ - public static boolean test(ItemInfo info, Predicate pred) + public static boolean testAll(ItemInfo info, ItemPredicate... pred) { - return (info != null && pred != null) ? pred.test(info) : (info != null); + boolean retVal = (info != null); + if (retVal) { + for (final ItemPredicate p : pred) { + if (p != null) { + retVal &= p.test(info); + } + if (!retVal) { + break; + } + } + } + return retVal; + } + + /** + * Convenience method: Returns {@code true} if the given item passes at least one of the specified tests. + * Returns {@code false} if info is {@code null}. + * Returns {@code true} if no predicate is specified. + */ + public static boolean testAny(ItemInfo info, ItemPredicate... pred) + { + boolean retVal = (info != null); + if (retVal && pred.length > 0) { + for (final ItemPredicate p : pred) { + if (p != null) { + retVal |= p.test(info); + } + if (retVal) { + break; + } + } + } + return retVal; } /** @@ -281,14 +420,23 @@ public static int getItemCategory(ResourceEntry itmEntry) return -1; } + private ItemInfo() + { + this.itmEntry = null; + initDefault(); + } + private ItemInfo(ResourceEntry itmEntry) throws Exception { - this.itmEntry = Objects.requireNonNull(itmEntry, "ITM resource cannot be null"); + this.itmEntry = itmEntry; init(); } + /** Returns whether this {@code ItemInfo} instance contains a valid item. */ + public boolean isEmpty() { return (itmEntry == null); } + /** Returns the {@code ResourceEntry} instance of the item resource. */ - public ResourceEntry getItemEntry() { return itmEntry; } + public ResourceEntry getResourceEntry() { return itmEntry; } /** Returns the general name of the item. */ public String getName() { return name; } @@ -341,8 +489,21 @@ private ItemInfo(ResourceEntry itmEntry) throws Exception /** Returns a sequential {@link Stream} of the {@code EffectInfo} list. */ public Stream getEffectStream() { return effectsInfo.stream(); } + /** Invoked for {@code null} items. */ + private void initDefault() + { + name = nameIdentified = appearance = ""; + flags = category = unusable = unusableKits = proficiency = enchantment = 0; + } + + /** Initializes relevant item attributes. */ private void init() throws IOException, Exception { + if (itmEntry == null) { + initDefault(); + return; + } + try (final InputStream is = itmEntry.getResourceDataAsStream()) { byte[] sig = new byte[8]; // offset = 0x00 @@ -511,6 +672,20 @@ private void parseEffect(EffectInfo info) } } +//--------------------- Begin Interface Comparable --------------------- + + @Override + public int compareTo(ItemInfo o) + { + if (itmEntry == null) { + return (o.itmEntry == null) ? 0 : -1; + } else { + return (o.itmEntry == null) ? 1 : itmEntry.compareTo(o.itmEntry); + } + } + +//--------------------- End Interface Comparable --------------------- + @Override public int hashCode() { @@ -551,6 +726,22 @@ public boolean equals(Object o) return retVal; } + @Override + public String toString() + { + if (isEmpty()) { + return "None"; + } else if (getIdentifiedName().isEmpty()) { + return getResourceEntry().getResourceName(); + } else { + String name = getIdentifiedName(); + if (name.length() > 80) { + name = name.substring(0, 80) + "..."; + } + return getResourceEntry().getResourceName() + " (" + name + ")"; + } + } + //-------------------------- INNER CLASSES -------------------------- /** Storage class for relevant ability attributes. */ @@ -704,4 +895,30 @@ private EffectInfo(byte[] effect) /** Returns the special parameter used by selected effects. */ public int getSpecial() { return special; } } + + /** + * Represents a {@link Predicate} about an {@code ItemInfo} object. + */ + public interface ItemPredicate extends Predicate + { + @Override + boolean test(ItemInfo info); + + default ItemPredicate and(ItemPredicate other) + { + Objects.requireNonNull(other); + return (t) -> test(t) && other.test(t); + } + + default ItemPredicate negate() + { + return (t) -> !test(t); + } + + default ItemPredicate or(ItemPredicate other) + { + Objects.requireNonNull(other); + return (t) -> test(t) || other.test(t); + } + } } From ab4a84922ac7f9a5513b71f4c7fa671cef97ecf6 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 5 Mar 2021 14:14:55 +0100 Subject: [PATCH 038/158] Relocate item-related methods from SpriteUtils to CreatureInfo class Improve performance of color-gradient-related methods --- .../cre/decoder/ArmoredBaseDecoder.java | 2 +- .../cre/decoder/CharacterDecoder.java | 10 +- .../cre/decoder/CharacterOldDecoder.java | 8 +- .../resource/cre/decoder/MonsterDecoder.java | 2 +- .../cre/decoder/MonsterIcewindDecoder.java | 2 +- .../cre/decoder/MonsterLayeredDecoder.java | 6 +- .../decoder/MonsterLayeredSpellDecoder.java | 4 +- .../resource/cre/decoder/SpriteUtils.java | 408 +----------------- .../cre/decoder/internal/CreatureInfo.java | 307 +++++++++---- 9 files changed, 258 insertions(+), 491 deletions(-) diff --git a/src/org/infinity/resource/cre/decoder/ArmoredBaseDecoder.java b/src/org/infinity/resource/cre/decoder/ArmoredBaseDecoder.java index b19927517..31f83322a 100644 --- a/src/org/infinity/resource/cre/decoder/ArmoredBaseDecoder.java +++ b/src/org/infinity/resource/cre/decoder/ArmoredBaseDecoder.java @@ -90,7 +90,7 @@ protected void setHeightCode(String s) public int getArmorCode() { int retVal = 1; - ItemInfo itm = SpriteUtils.getEquippedArmor(getCreResource()); + ItemInfo itm = getCreatureInfo().getEquippedArmor(); if (itm != null) { String code = itm.getAppearance(); retVal = Math.max(1, Math.min(getMaxArmorCode(), Misc.toNumber(code.substring(0, 1), 1))); diff --git a/src/org/infinity/resource/cre/decoder/CharacterDecoder.java b/src/org/infinity/resource/cre/decoder/CharacterDecoder.java index f6d4d28c2..83f6230fb 100644 --- a/src/org/infinity/resource/cre/decoder/CharacterDecoder.java +++ b/src/org/infinity/resource/cre/decoder/CharacterDecoder.java @@ -368,16 +368,16 @@ protected SeqDef getSequenceDefinition(Sequence seq) String prefixShield = getShieldHeightCode(); String codeShield = ""; if (!prefixShield.isEmpty()) { - ItemInfo itmShield = SpriteUtils.getEquippedShield(getCreResource()); + ItemInfo itmShield = getCreatureInfo().getEquippedShield(); if (itmShield != null) { codeShield = itmShield.getAppearance().trim(); - isLefthandedWeapon = !codeShield.isEmpty() && ItemInfo.test(itmShield, ItemInfo.FILTER_WEAPON_MELEE_LEFT_HANDED); + isLefthandedWeapon = !codeShield.isEmpty() && ItemInfo.testAll(itmShield, ItemInfo.FILTER_WEAPON_MELEE_LEFT_HANDED); } } // getting attack type - ItemInfo itmWeapon = SpriteUtils.getEquippedWeapon(getCreResource()); - int itmAbility = SpriteUtils.getEquippedWeaponAbility(getCreResource()); + ItemInfo itmWeapon = getCreatureInfo().getEquippedWeapon(); + int itmAbility = getCreatureInfo().getSelectedWeaponAbility(); AttackType attackType = getAttackType(itmWeapon, itmAbility, isLefthandedWeapon); EnumSet sequences = forbiddenSequences.get(attackType); @@ -407,7 +407,7 @@ protected SeqDef getSequenceDefinition(Sequence seq) if (isHelmetEquipped()) { prefix = getHelmetHeightCode(); if (!prefix.isEmpty()) { - ItemInfo itmHelmet = SpriteUtils.getEquippedHelmet(getCreResource()); + ItemInfo itmHelmet = getCreatureInfo().getEquippedHelmet(); if (itmHelmet != null) { String code = itmHelmet.getAppearance().trim(); if (code.length() == 2) { diff --git a/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java b/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java index 246336d2a..79a5cf897 100644 --- a/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java +++ b/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java @@ -270,8 +270,8 @@ protected SeqDef getSequenceDefinition(Sequence seq) } // getting attack type - ItemInfo itmWeapon = SpriteUtils.getEquippedWeapon(getCreResource()); - int itmAbility = SpriteUtils.getEquippedWeaponAbility(getCreResource()); + ItemInfo itmWeapon = getCreatureInfo().getEquippedWeapon(); + int itmAbility = getCreatureInfo().getSelectedWeaponAbility(); AttackType attackType = getAttackType(itmWeapon, itmAbility, false); EnumSet sequences = forbiddenSequences.get(attackType); @@ -300,7 +300,7 @@ protected SeqDef getSequenceDefinition(Sequence seq) if (isHelmetEquipped()) { String prefix = getHelmetHeightCode(); if (!prefix.isEmpty()) { - ItemInfo itmHelmet = SpriteUtils.getEquippedHelmet(getCreResource()); + ItemInfo itmHelmet = getCreatureInfo().getEquippedHelmet(); if (itmHelmet != null) { String code = itmHelmet.getAppearance().trim(); if (code.length() == 2) { @@ -314,7 +314,7 @@ protected SeqDef getSequenceDefinition(Sequence seq) // adding shield overlay String prefix = getHeightCode(); if (!prefix.isEmpty()) { - ItemInfo itmShield = SpriteUtils.getEquippedShield(getCreResource()); + ItemInfo itmShield = getCreatureInfo().getEquippedShield(); if (itmShield != null) { String code = itmShield.getAppearance().trim(); if (!code.isEmpty()) { diff --git a/src/org/infinity/resource/cre/decoder/MonsterDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterDecoder.java index bc6e069e0..2f7dd3990 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterDecoder.java @@ -284,7 +284,7 @@ protected SeqDef getSequenceDefinition(Sequence seq) creResList.add(Couple.with(resref + suffix + ".BAM", SegmentDef.SpriteType.AVATAR)); // processing weapon overlay - ItemInfo itmWeapon = SpriteUtils.getEquippedWeapon(getCreResource()); + ItemInfo itmWeapon = getCreatureInfo().getEquippedWeapon(); if (itmWeapon != null) { String weapon = itmWeapon.getAppearance().trim(); if (!weapon.isEmpty()) { diff --git a/src/org/infinity/resource/cre/decoder/MonsterIcewindDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterIcewindDecoder.java index e66f409fe..8363f2254 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterIcewindDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterIcewindDecoder.java @@ -169,7 +169,7 @@ protected SeqDef getSequenceDefinition(Sequence seq) // getting weapon code from CRE resource String weapon = ""; - ItemInfo itmWeapon = SpriteUtils.getEquippedWeapon(getCreResource()); + ItemInfo itmWeapon = getCreatureInfo().getEquippedWeapon(); if (itmWeapon != null) { weapon = itmWeapon.getAppearance(); if (!weapon.isEmpty()) { diff --git a/src/org/infinity/resource/cre/decoder/MonsterLayeredDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterLayeredDecoder.java index 015bebe54..4c92bc6d4 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterLayeredDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterLayeredDecoder.java @@ -152,12 +152,12 @@ protected SeqDef getSequenceDefinition(Sequence seq) switch (seq) { case ATTACK: case ATTACK_2: - if (ItemInfo.test(SpriteUtils.getEquippedWeapon(getCreResource()), ItemInfo.FILTER_WEAPON_2H)) { + if (ItemInfo.testAll(getCreatureInfo().getEquippedWeapon(), ItemInfo.FILTER_WEAPON_2H)) { return retVal; } break; case ATTACK_2H: - if (!ItemInfo.test(SpriteUtils.getEquippedWeapon(getCreResource()), ItemInfo.FILTER_WEAPON_2H)) { + if (!ItemInfo.testAll(getCreatureInfo().getEquippedWeapon(), ItemInfo.FILTER_WEAPON_2H)) { return retVal; } break; @@ -173,7 +173,7 @@ protected SeqDef getSequenceDefinition(Sequence seq) creResList.add(Couple.with(resref + suffix, SegmentDef.SpriteType.AVATAR)); // defining weapon overlay for current creature - ItemInfo itmWeapon = SpriteUtils.getEquippedWeapon(getCreResource()); + ItemInfo itmWeapon = getCreatureInfo().getEquippedWeapon(); if (itmWeapon != null) { String weapon = itmWeapon.getAppearance().trim(); if (!weapon.isEmpty()) { diff --git a/src/org/infinity/resource/cre/decoder/MonsterLayeredSpellDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterLayeredSpellDecoder.java index 43e87bc47..a216018ca 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterLayeredSpellDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterLayeredSpellDecoder.java @@ -191,8 +191,8 @@ protected SeqDef getSequenceDefinition(Sequence seq) return retVal; } - ItemInfo itmWeapon = SpriteUtils.getEquippedWeapon(getCreResource()); - if (seq == Sequence.ATTACK_2H && ItemInfo.test(itmWeapon, ItemInfo.FILTER_WEAPON_2H)) { + ItemInfo itmWeapon = getCreatureInfo().getEquippedWeapon(); + if (seq == Sequence.ATTACK_2H && ItemInfo.testAll(itmWeapon, ItemInfo.FILTER_WEAPON_2H)) { return retVal; } diff --git a/src/org/infinity/resource/cre/decoder/SpriteUtils.java b/src/org/infinity/resource/cre/decoder/SpriteUtils.java index fab91c8ff..9243fdfb8 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteUtils.java +++ b/src/org/infinity/resource/cre/decoder/SpriteUtils.java @@ -5,22 +5,14 @@ package org.infinity.resource.cre.decoder; import java.awt.image.BufferedImage; -import java.io.IOException; -import java.io.InputStream; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.HashMap; -import java.util.Locale; -import java.util.Objects; import java.util.Random; -import org.infinity.datatype.IsNumeric; -import org.infinity.datatype.IsTextual; -import org.infinity.resource.AbstractStruct; import org.infinity.resource.Profile; import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; -import org.infinity.resource.cre.decoder.internal.ItemInfo; import org.infinity.resource.graphics.BamV1Decoder; import org.infinity.resource.graphics.ColorConvert; import org.infinity.resource.graphics.GraphicsResource; @@ -334,400 +326,6 @@ private static ByteBuffer addPseudoCreItem(ByteBuffer buffer, int slot, String r return outBuffer; } - /** - * Returns the index of the selected weapon ability. - * @param cre the CRE resource. - * @return ability index. Returns 0 if ability could not be determined. - */ - public static int getEquippedWeaponAbility(CreResource cre) - { - if (cre != null) { - return ((IsNumeric)cre.getAttribute(CreResource.CRE_SELECTED_WEAPON_ABILITY)).getValue(); - } - return 0; - } - - /** - * Determines the absolute item slot index of the specified weapon-related slot id. - * Special slots that cannot be mapped to slot indices are returned as -1. - * Only weapon-related slots are considered, which includes the actual weapon slot ids as well as the ammo ids. - * @param cre the CRE resource - * @param slotId the slot id (as defined in SLOTS.IDS) - * @return absolute item slot index. Returns 1000 for fist slot. Returns -1 if slot id could not be mapped to a slot index. - */ - public static int getWeaponSlotIndex(CreResource cre, int slotId) - { - int retVal = -1; - if (slotId == 1000) { - // fist weapon - return slotId; - } - - String creVer = ((IsTextual)Objects.requireNonNull(cre).getAttribute(CreResource.COMMON_VERSION)).getText().toUpperCase(Locale.ENGLISH); - slotId += 35; // determine SLOTS.IDS value - switch (slotId) { - case 3: // IWD2: ammo1 - case 4: // IWD2: ammo2 - case 5: // IWD2: ammo3 - case 6: // IWD2: ammo4 - if (creVer.equals("V2.2")) { - retVal = slotId + 14; - } - break; - case 11: // ammo1 - case 12: // ammo2 - case 13: // ammo3 - case 14: // ammo4 - if (!creVer.equals("V2.2")) { - retVal = slotId + 2; - } - break; - case 15: - case 16: - if (creVer.equals("V1.2")) { - retVal = slotId + 2; - } - break; - case 34: // magically created weapon - { - switch (creVer) { - case "V1.2": - retVal = slotId + 11; - break; - case "V2.2": - retVal = slotId + 15; - break; - default: - { - if (Profile.getGame() == Profile.Game.PSTEE) { - // special: PSTEE party members have customized item slots - int numSlots = ((IsNumeric)cre.getAttribute(CreResource.CRE_NUM_ITEM_SLOTS)).getValue(); - if (numSlots > 0) { - Table2da table = Table2daCache.get("ITMSLOTS.2DA"); - if (table != null) { - for (int row = 0, rowCount = table.getRowCount(); row < rowCount; row++) { - if (Misc.toNumber(table.get(row, 1), -1) == 13) { // magic weapon slot? - retVal = Misc.toNumber(table.get(row, 5), -1); - } - } - } - } - } - if (retVal < 0) { - retVal = slotId + 3; - } - } - } - break; - } - case 35: // weapon1 - case 36: // weapon2 (IWD2: shield1) - case 37: // weapon3 (IWD2: weapon2) - case 38: // weapon4 (IWD2: shield2) - case 39: // IWD2: weapon3 - case 40: // IWD2: shield3 - case 41: // IWD2: weapon4 - case 42: // IWD2: shield4 - retVal = slotId - 26; - break; - } - return retVal; - } - - /** - * Analyses the item at the specified slot index and returns the index if the item can be used directly. - * If the item requires a launcher the method scans the weapon slots of the specified CRE resource and - * returns the slot index of a matching launcher item. - * @param cre the CRE resource - * @param slotIndex the absolute slot index of the item to check - * @return the absolute slot index of the effective weapon. Returns {@code -1} if no weapon could be determined. - */ - public static int getEffectiveWeaponIndex(CreResource cre, int slotIndex) - { - int retVal = -1; - if (slotIndex < 0) { - return retVal; - } - - // getting item entry index - int ofsSlots = cre.getExtraOffset() + ((IsNumeric)cre.getAttribute(CreResource.CRE_OFFSET_ITEM_SLOTS)).getValue(); - int itmIndex = ((IsNumeric)cre.getAttribute(ofsSlots + slotIndex * 2)).getValue(); - int numItems = ((IsNumeric)cre.getAttribute(CreResource.CRE_NUM_ITEMS)).getValue(); - if (itmIndex < 0 || itmIndex >= numItems) { - return retVal; - } - - // loading referenced item - ItemInfo info = null; - int ofsItems = Objects.requireNonNull(cre).getExtraOffset() + ((IsNumeric)cre.getAttribute(CreResource.CRE_OFFSET_ITEMS)).getValue(); - try { - String itmResref = ((IsTextual)cre.getAttribute(ofsItems + itmIndex * 20, true)).getText(); - info = ItemInfo.get(ResourceFactory.getResourceEntry(itmResref + ".ITM")); - } catch (Exception e) { - return retVal; - } - - // check if item requires a launcher - int abilityIndex = ((IsNumeric)cre.getAttribute(CreResource.CRE_SELECTED_WEAPON_ABILITY)).getValue(); - int numAbil = info.getAbilityCount(); - abilityIndex = Math.min(abilityIndex, numAbil - 1); - if (abilityIndex < 0) { - return retVal; - } - int launcherType = info.getAbility(abilityIndex).getLauncher(); - if (launcherType == 0) { - // item can be used directly - retVal = slotIndex; - } - - if (retVal < 0) { - // launcher required: find a weapon in weapon slots 1-4 with a matching item category - String creVer = ((IsTextual)Objects.requireNonNull(cre).getAttribute(CreResource.COMMON_VERSION)).getText().toUpperCase(Locale.ENGLISH); - int idxWeaponSlots = 9; - int slotGroupSize = creVer.equals("V2.2") ? 2 : 1; // IWD2 uses weapon/shield pairs - for (int i = 0; i < 4; i++) { - int ofs = ofsSlots + (idxWeaponSlots + i * slotGroupSize) * 2; - itmIndex = ((IsNumeric)cre.getAttribute(ofs)).getValue(); - if (itmIndex >= 0 && itmIndex < numItems) { - int cat = -1; - try { - String itmResref = ((IsTextual)cre.getAttribute(ofsItems + itmIndex * 20, true)).getText(); - ResourceEntry itmEntry = ResourceFactory.getResourceEntry(itmResref + ".ITM"); - if (itmEntry != null) { - try (InputStream is = itmEntry.getResourceDataAsStream()) { - Misc.requireCondition(is.skip(0x1c) == 0x1c, "Could not read item category", IOException.class); - cat = is.read(); - cat |= is.read() << 8; - } - } - // checking if launcher type corresponds with item category - if (launcherType == 1 && cat == 15 || // Bow - launcherType == 2 && cat == 27 || // Crossbow - launcherType == 3 && cat == 18) { // Sling - retVal = idxWeaponSlots + i * slotGroupSize; - break; - } - } catch (Exception e) { - } - } - } - } - - return retVal; - } - - /** - * Returns the active weapon of the specified creature. - * @param cre The CRE resource. - * @return The {@code ItemInfo} object for the item resource of the active weapon. - * Returns {@code null} if no weapon is active. - * @throws NullPointerException if no creature resource specified. - */ - public static ItemInfo getEquippedWeapon(CreResource cre) - { - ItemInfo retVal = null; - - // find selected weapon slot and determine the associated item - int slotIndex = getWeaponSlotIndex(cre, ((IsNumeric)cre.getAttribute(CreResource.CRE_SELECTED_WEAPON_SLOT)).getValue()); - if (slotIndex == 1000) { - // fist weapon - retVal = getFistWeapon(cre); - } else { - if (slotIndex < 0) { - return retVal; - } - - int weaponSlotIdx = getEffectiveWeaponIndex(cre, slotIndex); - if (weaponSlotIdx < 0) { - return retVal; - } - - retVal = getInventoryItem(cre, weaponSlotIdx); - } - return retVal; - } - - /** - * Returns the equipped helmet of the specified creature. - * @param cre The CRE resource. - * @return The {@code ItemInfo} object for the item resource of the helmet. - * Returns {@code null} if no helmet is equipped. - * @throws NullPointerException if no creature resource specified. - */ - public static ItemInfo getEquippedHelmet(CreResource cre) - { - if (Profile.getEngine() == Profile.Engine.PST || Profile.getGame() == Profile.Game.PSTEE) { - // PST does not support helmets - return null; - } - - ItemInfo retVal = getInventoryItem(cre, 0); - if (retVal != null) { - // checking item category - switch (retVal.getCategory()) { - case 7: // Headgear - case 72: // Hats - break; - default: - retVal = null; - } - } - return retVal; - } - - /** - * Returns the equipped shield or left-handed weapon of the specified creature. - * @param cre The CRE resource. - * @return The {@code ItemInfo} object for the item resource of the shield or left-handed weapon. - * Returns {@code null} if left hand is empty. - * @throws NullPointerException if no creature resource specified. - */ - public static ItemInfo getEquippedShield(CreResource cre) - { - ItemInfo retVal = null; - if (Profile.getEngine() == Profile.Engine.PST || Profile.getGame() == Profile.Game.PSTEE) { - // PST does not support shields - return retVal; - } - - String creVer = ((IsTextual)cre.getAttribute(AbstractStruct.COMMON_VERSION)).getText().toUpperCase(Locale.ENGLISH); - if ("V2.2".equals(creVer)) { - int selectedSlot = ((IsNumeric)cre.getAttribute(CreResource.CRE_SELECTED_WEAPON_SLOT)).getValue(); - int itemSlotIdx = getWeaponSlotIndex(cre, selectedSlot); - int weaponSlotIdx = getEffectiveWeaponIndex(cre, itemSlotIdx); - if (weaponSlotIdx >= 0 && weaponSlotIdx < 1000) { - // IWD2 uses weapon/shield slot combinations - retVal = getInventoryItem(cre, weaponSlotIdx + 1); - } - } else { - retVal = getInventoryItem(cre, 2); - } - - return retVal; - } - - /** - * Returns the equipped armor or robe of the specified creature. - * @param cre The CRE resource. - * @return The {@code ItemInfo} object for the item resource of armor or robe. - * Returns {@code null} if no armor is equipped. - */ - public static ItemInfo getEquippedArmor(CreResource cre) - { - return getInventoryItem(cre, 1); - } - - /** - * Returns an {@link ItemInfo} instance of the item resource at the specified item slot of the given creature. - * @param cre the CRE resource - * @param slotIndex absolute slot index of the requested item - * @return the {@code ItemInfo} object for the item resource. Returns {@code null} if item could not be determined. - */ - public static ItemInfo getInventoryItem(CreResource cre, int slotIndex) - { - ItemInfo retVal = null; - if (cre == null || slotIndex < 0) { - return retVal; - } - - // determining number of item slots - String creVer = ((IsTextual)cre.getAttribute(AbstractStruct.COMMON_VERSION)).getText().toUpperCase(Locale.ENGLISH); - int numSlots = 0; - switch (creVer) { - case "V1.2": - numSlots = 48; - break; - case "V2.2": - numSlots = 52; - break; - default: - if (Profile.getGame() == Profile.Game.PSTEE) { - numSlots = ((IsNumeric)cre.getAttribute(CreResource.CRE_NUM_ITEM_SLOTS)).getValue(); - } - if (numSlots == 0) { - numSlots = 40; - } - } - - if (numSlots == 0 || slotIndex >= numSlots) { - return retVal; - } - - // loading referenced item - try { - int ofsSlots = cre.getExtraOffset() + ((IsNumeric)cre.getAttribute(CreResource.CRE_OFFSET_ITEM_SLOTS)).getValue(); - int itemIndex = ((IsNumeric)cre.getAttribute(ofsSlots + slotIndex * 2)).getValue(); - if (itemIndex >= 0) { - int numItems = ((IsNumeric)cre.getAttribute(CreResource.CRE_NUM_ITEMS)).getValue(); - if (itemIndex < numItems) { - int ofsItems = cre.getExtraOffset() + ((IsNumeric)cre.getAttribute(CreResource.CRE_OFFSET_ITEMS)).getValue(); - String itemRef = ((IsTextual)cre.getAttribute(ofsItems + itemIndex * 20, true)).getText(); - ResourceEntry entry = ResourceFactory.getResourceEntry(itemRef + ".ITM"); - retVal = ItemInfo.get(entry); - } - } - } catch (Exception e) { - e.printStackTrace(); - } - - return retVal; - } - - /** Attempts to find and return the virtual "fist" weapon item used for barehanded attacks. */ - public static ItemInfo getFistWeapon(CreResource cre) - { - ItemInfo retVal = null; - - int animId = ((IsNumeric)cre.getAttribute(CreResource.CRE_ANIMATION)).getValue(); - if ((animId & 0xff00) == 0x6500) { - // monk: return fist item - int level = ((IsNumeric)cre.getAttribute(CreResource.CRE_LEVEL_FIRST_CLASS)).getValue(); - - // 1. try 2DA lookup - Table2da table = Table2daCache.get("MONKFIST.2DA"); - if (table != null) { - level = Math.max(1, Math.min(table.getRowCount(), level)); - String resref = table.get(level, 1); - ResourceEntry entry = ResourceFactory.getResourceEntry(resref + ".ITM"); - try { - retVal = ItemInfo.get(entry); - } catch (Exception e) { - e.printStackTrace(); - } - } - - if (retVal == null) { - // 2. try hardcoded selection - String resref = null; - int[] minLevels = { Integer.MIN_VALUE, 3, 6, 9, 12, 15, 19, 25, Integer.MAX_VALUE }; - for (int i = 0; i < minLevels.length && resref == null; i++) { - if (level < minLevels[i]) { - resref = "MFIST" + i; - ResourceEntry entry = ResourceFactory.getResourceEntry(resref + ".ITM"); - try { - retVal = ItemInfo.get(entry); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - } - } - - if (retVal == null) { - // return default fist - ResourceEntry entry = ResourceFactory.getResourceEntry("FIST.ITM"); - try { - retVal = ItemInfo.get(entry); - } catch (Exception e) { - e.printStackTrace(); - } - } - - return retVal; - } - - /** * Loads the decoder instance for the specified BAM V1 resource. Retrieves the decoder from cache if available. * @param entry the BAM resource entry. @@ -1036,6 +634,9 @@ public static int[] getColorGradient(SpriteDecoder.AnimationType animType, int i } catch (Exception e) { e.printStackTrace(); } + } else { + // dummy entry to skip continuous gradient initialization attempts if gradient bitmap isn't available + colorGradients.put(Integer.MIN_VALUE, null); } } @@ -1071,6 +672,9 @@ public static int[] getRandomColorGradient(SpriteDecoder.AnimationType animType, } } } + } else { + // dummy entry to skip continuous random gradient initialization attempts if RANDCOLR.2DA isn't available + randomGradientIndices.put(Integer.MIN_VALUE, null); } } diff --git a/src/org/infinity/resource/cre/decoder/internal/CreatureInfo.java b/src/org/infinity/resource/cre/decoder/internal/CreatureInfo.java index 58b716a12..a0cbf6f27 100644 --- a/src/org/infinity/resource/cre/decoder/internal/CreatureInfo.java +++ b/src/org/infinity/resource/cre/decoder/internal/CreatureInfo.java @@ -4,6 +4,8 @@ package org.infinity.resource.cre.decoder.internal; +import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.EnumMap; import java.util.HashMap; @@ -251,6 +253,46 @@ public int getSelectedWeaponAbility() return retVal; } + /** + * Returns the active weapon of the specified creature. + * @return The {@code ItemInfo} object for the item resource of the active weapon. + * Returns {@code null} if no weapon is active. + */ + public ItemInfo getEquippedWeapon() + { + return getItemInfo(ItemSlots.WEAPON); + } + + /** + * Returns the equipped helmet of the specified creature. + * @return The {@code ItemInfo} object for the item resource of the helmet. + * Returns {@code null} if no helmet is equipped. + */ + public ItemInfo getEquippedHelmet() + { + return getItemInfo(ItemSlots.HELMET); + } + + /** + * Returns the equipped shield or left-handed weapon of the specified creature. + * @return The {@code ItemInfo} object for the item resource of the shield or left-handed weapon. + * Returns {@code null} if left hand is empty. + */ + public ItemInfo getEquippedShield() + { + return getItemInfo(ItemSlots.SHIELD); + } + + /** + * Returns the equipped armor or robe. + * @return The {@code ItemInfo} object for the item resource of armor or robe. + * Returns {@code null} if no armor is equipped. + */ + public ItemInfo getEquippedArmor() + { + return getItemInfo(ItemSlots.ARMOR); + } + /** * Returns the {@link ItemInfo} instance associated with the specified item slot. * @param slot the slot where the item is equipped. @@ -497,16 +539,6 @@ private void initCommon() throws Exception // initialize BG/EE-style creature resources private void initV10() throws Exception { - int selectedWeaponSlot = 38; - if (Profile.getGame() == Profile.Game.PSTEE) { - Misc.requireCondition("V1.0".equalsIgnoreCase(((IsTextual)cre.getAttribute(CreResource.COMMON_VERSION)).getText()), - "CRE version not supported"); - int numSlots = ((IsNumeric)cre.getAttribute(CreResource.CRE_NUM_ITEM_SLOTS)).getValue(); - if (numSlots > 0) { - selectedWeaponSlot = numSlots - 1; - } - } - // initializing equipment HashMap slotMap = new HashMap() {{ put(ItemSlots.HELMET, 0); @@ -522,14 +554,13 @@ private void initV10() throws Exception put(ItemSlots.CLOAK, 17); }}; - initEquipment(slotMap, selectedWeaponSlot); + initEquipment(slotMap); } // initialize PST-style creature resources private void initV12() throws Exception { // initializing equipment - int selectedWeaponSlot = 46; HashMap slotMap = new HashMap() {{ put(ItemSlots.HELMET, 0); put(ItemSlots.ARMOR, 1); @@ -544,21 +575,34 @@ private void initV12() throws Exception put(ItemSlots.CLOAK, 19); }}; - initEquipment(slotMap, selectedWeaponSlot); + initEquipment(slotMap); } // initialize IWD2-style creature resources private void initV22() throws Exception { // initializing equipment - initEquipmentV22(); + HashMap slotMap = new HashMap() {{ + put(ItemSlots.HELMET, 0); + put(ItemSlots.ARMOR, 1); + put(ItemSlots.GAUNTLETS, 3); + put(ItemSlots.RING_LEFT, 4); + put(ItemSlots.RING_RIGHT, 5); + put(ItemSlots.AMULET, 6); + put(ItemSlots.BELT, 7); + put(ItemSlots.BOOTS, 8); + put(ItemSlots.WEAPON, 9); + put(ItemSlots.SHIELD, 10); + put(ItemSlots.CLOAK, 21); + }}; + + initEquipment(slotMap); } // initialize IWD-style creature resources private void initV90() throws Exception { // initializing equipment - int selectedWeaponSlot = 38; HashMap slotMap = new HashMap() {{ put(ItemSlots.HELMET, 0); put(ItemSlots.ARMOR, 1); @@ -573,7 +617,7 @@ private void initV90() throws Exception put(ItemSlots.CLOAK, 17); }}; - initEquipment(slotMap, selectedWeaponSlot); + initEquipment(slotMap); } private void initEffects() @@ -656,7 +700,7 @@ private void initEffect(AbstractStruct as) } } - private void initEquipment(HashMap slotMap, int selectedWeaponSlot) + private void initEquipment(HashMap slotMap) { List itemList = cre.getFields(Item.class); int ofsSlots = cre.getExtraOffset() + ((IsNumeric)cre.getAttribute(CreResource.CRE_OFFSET_ITEM_SLOTS)).getValue(); @@ -665,64 +709,16 @@ private void initEquipment(HashMap slotMap, int selectedWeap int slotIdx = entry.getValue().intValue(); int itemIdx = -1; if (slot == ItemSlots.WEAPON) { - // special: determine weapon slot - int weaponIdx = ((IsNumeric)cre.getAttribute(ofsSlots + selectedWeaponSlot * 2)).getValue(); + // special: determine active weapon slot + int selectedWeaponSlot = ((IsNumeric)cre.getAttribute(CreResource.CRE_SELECTED_WEAPON_SLOT)).getValue(); + int weaponIdx = getWeaponSlotIndex(selectedWeaponSlot); if (weaponIdx == 1000) { // selected weapon: fists itemIdx = weaponIdx; - } else { - weaponIdx = Math.max(0, Math.min(4, weaponIdx)); - for (int idx = weaponIdx; idx >= 0; idx--) { - itemIdx = ((IsNumeric)cre.getAttribute(ofsSlots + (slotIdx + idx) * 2)).getValue(); - if (itemIdx >= 0 && itemIdx < itemList.size()) { - break; - } - } - } - } else { - itemIdx = ((IsNumeric)cre.getAttribute(ofsSlots + slotIdx * 2)).getValue(); - } - - initEquipmentItem(slot, itemIdx, itemList); - } - } - - private void initEquipmentV22() - { - int selectedWeaponSlot = 50; - HashMap slotMap = new HashMap() {{ - put(ItemSlots.HELMET, 0); - put(ItemSlots.ARMOR, 1); - put(ItemSlots.GAUNTLETS, 3); - put(ItemSlots.RING_LEFT, 4); - put(ItemSlots.RING_RIGHT, 5); - put(ItemSlots.AMULET, 6); - put(ItemSlots.BELT, 7); - put(ItemSlots.BOOTS, 8); - put(ItemSlots.WEAPON, 9); - put(ItemSlots.SHIELD, 10); - put(ItemSlots.CLOAK, 21); - }}; - - List itemList = cre.getFields(Item.class); - int ofsSlots = cre.getExtraOffset() + ((IsNumeric)cre.getAttribute(CreResource.CRE_OFFSET_ITEM_SLOTS)).getValue(); - for (HashMap.Entry entry : slotMap.entrySet()) { - ItemSlots slot = entry.getKey(); - int slotIdx = entry.getValue().intValue(); - int itemIdx = -1; - if (slot == ItemSlots.WEAPON || slot == ItemSlots.SHIELD) { - // special: weapon/shield combinations are grouped and determined by selected weapon slot - int weaponIdx = ((IsNumeric)cre.getAttribute(ofsSlots + selectedWeaponSlot * 2)).getValue(); - if (weaponIdx == 1000) { - // selected weapon: fists - itemIdx = weaponIdx; - } else { - weaponIdx = Math.max(0, Math.min(4, weaponIdx)); - for (int idx = weaponIdx; idx >= 0; idx--) { - itemIdx = ((IsNumeric)cre.getAttribute(ofsSlots + (slotIdx + idx * 2) * 2)).getValue(); - if (itemIdx >= 0 && itemIdx < itemList.size()) { - break; - } + } else if (weaponIdx >= 0) { + weaponIdx = getEffectiveWeaponIndex(weaponIdx); + if (weaponIdx >= 0) { + itemIdx = ((IsNumeric)cre.getAttribute(ofsSlots + weaponIdx * 2)).getValue(); } } } else { @@ -791,6 +787,173 @@ private ResourceEntry getFistWeapon() return retVal; } + /** + * Analyses the item at the specified slot index and returns the same index if the item can be used directly. + * If the item requires a launcher the method scans the weapon slots of the specified CRE resource and + * returns the slot index of a matching launcher item. + * @param slotIndex the absolute slot index of the item to check + * @return the absolute slot index of the effective weapon. Returns {@code -1} if no weapon could be determined. + */ + private int getEffectiveWeaponIndex(int slotIndex) + { + int retVal = -1; + if (slotIndex < 0) { + return retVal; + } + + // getting item entry index + int ofsSlots = cre.getExtraOffset() + ((IsNumeric)cre.getAttribute(CreResource.CRE_OFFSET_ITEM_SLOTS)).getValue(); + int itmIndex = ((IsNumeric)cre.getAttribute(ofsSlots + slotIndex * 2)).getValue(); + int numItems = ((IsNumeric)cre.getAttribute(CreResource.CRE_NUM_ITEMS)).getValue(); + if (itmIndex < 0 || itmIndex >= numItems) { + return retVal; + } + + // loading referenced item + ItemInfo info = null; + int ofsItems = Objects.requireNonNull(cre).getExtraOffset() + ((IsNumeric)cre.getAttribute(CreResource.CRE_OFFSET_ITEMS)).getValue(); + try { + String itmResref = ((IsTextual)cre.getAttribute(ofsItems + itmIndex * 20, true)).getText(); + info = ItemInfo.get(ResourceFactory.getResourceEntry(itmResref + ".ITM")); + } catch (Exception e) { + return retVal; + } + + // check if item requires a launcher + int abilityIndex = ((IsNumeric)cre.getAttribute(CreResource.CRE_SELECTED_WEAPON_ABILITY)).getValue(); + int numAbil = info.getAbilityCount(); + abilityIndex = Math.min(abilityIndex, numAbil - 1); + if (abilityIndex < 0) { + return retVal; + } + int launcherType = info.getAbility(abilityIndex).getLauncher(); + if (launcherType == 0) { + // item can be used directly + retVal = slotIndex; + } + + if (retVal < 0) { + // launcher required: find a weapon in weapon slots 1-4 with a matching item category + String creVer = ((IsTextual)Objects.requireNonNull(cre).getAttribute(CreResource.COMMON_VERSION)).getText().toUpperCase(Locale.ENGLISH); + int idxWeaponSlots = 9; + int slotGroupSize = creVer.equals("V2.2") ? 2 : 1; // IWD2 uses weapon/shield pairs + for (int i = 0; i < 4; i++) { + int ofs = ofsSlots + (idxWeaponSlots + i * slotGroupSize) * 2; + itmIndex = ((IsNumeric)cre.getAttribute(ofs)).getValue(); + if (itmIndex >= 0 && itmIndex < numItems) { + int cat = -1; + try { + String itmResref = ((IsTextual)cre.getAttribute(ofsItems + itmIndex * 20, true)).getText(); + ResourceEntry itmEntry = ResourceFactory.getResourceEntry(itmResref + ".ITM"); + if (itmEntry != null) { + try (InputStream is = itmEntry.getResourceDataAsStream()) { + Misc.requireCondition(is.skip(0x1c) == 0x1c, "Could not read item category", IOException.class); + cat = is.read(); + cat |= is.read() << 8; + } + } + // checking if launcher type corresponds with item category + if (launcherType == 1 && cat == 15 || // Bow + launcherType == 2 && cat == 27 || // Crossbow + launcherType == 3 && cat == 18) { // Sling + retVal = idxWeaponSlots + i * slotGroupSize; + break; + } + } catch (Exception e) { + } + } + } + } + + return retVal; + } + + /** + * Determines the absolute item slot index of the specified weapon-related slot id. + * Special slots that cannot be mapped to slot indices are returned as -1. + * Only weapon-related slots are considered, which includes the actual weapon slot ids as well as the ammo ids. + * @param slotId the slot id (as defined in SLOTS.IDS) + * @return absolute item slot index. Returns 1000 for fist slot. Returns -1 if slot id could not be mapped to a slot index. + */ + private int getWeaponSlotIndex(int slotId) + { + int retVal = -1; + if (slotId == 1000) { + // fist weapon + return slotId; + } + + String creVer = ((IsTextual)Objects.requireNonNull(cre).getAttribute(CreResource.COMMON_VERSION)).getText().toUpperCase(Locale.ENGLISH); + slotId += 35; // determine SLOTS.IDS value + switch (slotId) { + case 3: // IWD2: ammo1 + case 4: // IWD2: ammo2 + case 5: // IWD2: ammo3 + case 6: // IWD2: ammo4 + if (creVer.equals("V2.2")) { + retVal = slotId + 14; + } + break; + case 11: // ammo1 + case 12: // ammo2 + case 13: // ammo3 + case 14: // ammo4 + if (!creVer.equals("V2.2")) { + retVal = slotId + 2; + } + break; + case 15: + case 16: + if (creVer.equals("V1.2")) { + retVal = slotId + 2; + } + break; + case 34: // magically created weapon + { + switch (creVer) { + case "V1.2": + retVal = slotId + 11; + break; + case "V2.2": + retVal = slotId + 15; + break; + default: + { + if (Profile.getGame() == Profile.Game.PSTEE) { + // special: PSTEE party members have customized item slots + int numSlots = ((IsNumeric)cre.getAttribute(CreResource.CRE_NUM_ITEM_SLOTS)).getValue(); + if (numSlots > 0) { + Table2da table = Table2daCache.get("ITMSLOTS.2DA"); + if (table != null) { + for (int row = 0, rowCount = table.getRowCount(); row < rowCount; row++) { + if (Misc.toNumber(table.get(row, 1), -1) == 13) { // magic weapon slot? + retVal = Misc.toNumber(table.get(row, 5), -1); + } + } + } + } + } + if (retVal < 0) { + retVal = slotId + 3; + } + } + } + break; + } + case 35: // weapon1 + case 36: // weapon2 (IWD2: shield1) + case 37: // weapon3 (IWD2: weapon2) + case 38: // weapon4 (IWD2: shield2) + case 39: // IWD2: weapon3 + case 40: // IWD2: shield3 + case 41: // IWD2: weapon4 + case 42: // IWD2: shield4 + retVal = slotId - 26; + break; + } + return retVal; + } + @Override public int hashCode() { From 28c98a5374a34cfbd1a13de24a447991570f0874 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sat, 6 Mar 2021 10:58:12 +0100 Subject: [PATCH 039/158] Bugfixes - ItemInfo: Fix ITM V1.1 parsing - SpriteDecoder: consider '# colors' field in CRE V1.1 resources --- .../resource/cre/decoder/MonsterPlanescapeDecoder.java | 6 +++--- src/org/infinity/resource/cre/decoder/SpriteUtils.java | 8 ++++++-- .../infinity/resource/cre/decoder/internal/ItemInfo.java | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java index 1fbfe71ab..38e951b57 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java @@ -359,12 +359,12 @@ private int[] setColorLocationsPST() { final HashSet usedColors = new HashSet<>(); CreResource cre = getCreResource(); - int[] retVal = new int[4]; + int numColors = Math.max(0, Math.min(7, ((IsNumeric)cre.getAttribute(CreResource.CRE_NUM_COLORS)).getValue())); + int[] retVal = new int[numColors]; int num = 0; for (int i = 0; i < retVal.length; i++) { int l = ((IsNumeric)cre.getAttribute(String.format(CreResource.CRE_COLOR_PLACEMENT_FMT, i + 1))).getValue(); - int c = ((IsNumeric)cre.getAttribute(String.format(CreResource.CRE_COLOR_FMT, i + 1))).getValue(); - if (l > 0 && c > 0 && !usedColors.contains(l & 0xf0)) { + if (l > 0 && !usedColors.contains(l & 0xf0)) { usedColors.add(l & 0xf0); retVal[num] = l; num++; diff --git a/src/org/infinity/resource/cre/decoder/SpriteUtils.java b/src/org/infinity/resource/cre/decoder/SpriteUtils.java index 9243fdfb8..a258c8205 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteUtils.java +++ b/src/org/infinity/resource/cre/decoder/SpriteUtils.java @@ -170,19 +170,23 @@ public static CreResource getPseudoCre(int animationId, HashMap buffer.position(0); buffer.put("CRE V1.2".getBytes(Misc.CHARSET_ASCII)); // creature colors + int numColors = 0; if (colors != null) { for (final HashMap.Entry e : colors.entrySet()) { + int value = e.getValue().intValue(); for (int i = 0; i < 7; i++) { String labelColor = String.format(CreResource.CRE_COLOR_FMT, i + 1); String labelColorPlacement = String.format(CreResource.CRE_COLOR_PLACEMENT_FMT, i + 1); if (labelColor.equals(e.getKey())) { - buffer.putShort(0x2e4 + (i * 2), e.getValue().shortValue()); + buffer.putShort(0x2e4 + (i * 2), (short)value); } else if (labelColorPlacement.equals(e.getKey())) { - buffer.put(0x2f5 + i, e.getValue().byteValue()); + buffer.put(0x2f5 + i, (byte)value); + numColors++; } } } } + buffer.put(0x2df, (byte)numColors); // Enemy-Ally buffer.put(0x314, (byte)128); // setting valid offsets diff --git a/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java b/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java index 233903ca0..996f20741 100644 --- a/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java +++ b/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java @@ -575,7 +575,7 @@ private void init() throws IOException, Exception this.proficiency = is.read(); } else { // to keep stream position synchronized - Misc.requireCondition(is.skip(0xc) == 0xe, "Could not advance in data stream: " + itmEntry); + Misc.requireCondition(is.skip(0xe) == 0xe, "Could not advance in data stream: " + itmEntry); } // enchantment From 4de1340fc8d57dd0f23b10c6f235508dc0e7b6fc Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Mon, 15 Mar 2021 11:46:42 +0100 Subject: [PATCH 040/158] PseudoBamDecoder: Fix incorrect frame size calculation when center is overridden --- .../resource/graphics/PseudoBamDecoder.java | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/org/infinity/resource/graphics/PseudoBamDecoder.java b/src/org/infinity/resource/graphics/PseudoBamDecoder.java index e56537095..76aac23b9 100644 --- a/src/org/infinity/resource/graphics/PseudoBamDecoder.java +++ b/src/org/infinity/resource/graphics/PseudoBamDecoder.java @@ -534,13 +534,13 @@ private void renderFrame(BamControl control, int frameIdx, Image canvas) int dstWidth = dstImage.getWidth(); int dstHeight = dstImage.getHeight(); - int srcWidth = listFrames.get(frameIdx).width; - int srcHeight = listFrames.get(frameIdx).height; + int srcWidth = listFrames.get(frameIdx).getWidth(); + int srcHeight = listFrames.get(frameIdx).getHeight(); if (control.getMode() == BamControl.Mode.SHARED) { // drawing on shared canvas Rectangle shared = control.getSharedRectangle(); - int srcCenterX = listFrames.get(frameIdx).centerX; - int srcCenterY = listFrames.get(frameIdx).centerY; + int srcCenterX = listFrames.get(frameIdx).getCenterX(); + int srcCenterY = listFrames.get(frameIdx).getCenterY(); int left = -shared.x - srcCenterX; int top = -shared.y - srcCenterY; int maxWidth = (dstWidth < srcWidth + left) ? dstWidth : srcWidth; @@ -680,9 +680,9 @@ public boolean exportBamV1(Path fileName, ProgressMonitor progress, int curProgr } // checking frame properties - if (entry.width <= 0 || entry.width > 65535 || entry.height <= 0 || entry.height > 65535 || - entry.centerX < Short.MIN_VALUE || entry.centerX > Short.MAX_VALUE || - entry.centerY < Short.MIN_VALUE || entry.centerY > Short.MAX_VALUE) { + if (entry.getWidth() <= 0 || entry.getWidth() > 65535 || entry.getHeight() <= 0 || entry.getHeight() > 65535 || + entry.getCenterX() < Short.MIN_VALUE || entry.getCenterX() > Short.MAX_VALUE || + entry.getCenterY() < Short.MIN_VALUE || entry.getCenterY() > Short.MAX_VALUE) { throw new Exception("Dimensions are out of range for frame index " + i); } @@ -813,10 +813,10 @@ public boolean exportBamV1(Path fileName, ProgressMonitor progress, int curProgr // adding frame entries int curOfs = ofsFrameEntries; for (int i = 0; i < listFrames.size(); i++) { - DynamicArray.putShort(bamData, curOfs, (short)listFrames.get(i).width); - DynamicArray.putShort(bamData, curOfs + 2, (short)listFrames.get(i).height); - DynamicArray.putShort(bamData, curOfs + 4, (short)listFrames.get(i).centerX); - DynamicArray.putShort(bamData, curOfs + 6, (short)listFrames.get(i).centerY); + DynamicArray.putShort(bamData, curOfs, (short)listFrames.get(i).getWidth()); + DynamicArray.putShort(bamData, curOfs + 2, (short)listFrames.get(i).getHeight()); + DynamicArray.putShort(bamData, curOfs + 4, (short)listFrames.get(i).getCenterX()); + DynamicArray.putShort(bamData, curOfs + 6, (short)listFrames.get(i).getCenterY()); DynamicArray.putInt(bamData, curOfs + 8, frameDataOffsets[i]); curOfs += FrameEntrySize; } @@ -943,7 +943,7 @@ public boolean exportBamV2(Path fileName, DxtEncoder.DxtType dxtType, int pvrzIn FrameDataV2 frame = listFrameData.get(idx); PseudoBamFrameEntry bfe = listFrames.get(idx); - PseudoBamFrameEntry entry = new PseudoBamFrameEntry(bfe.frame, bfe.centerX, bfe.centerY); + PseudoBamFrameEntry entry = new PseudoBamFrameEntry(bfe.frame, bfe.getCenterX(), bfe.getCenterY()); entry.setOption(OPTION_INT_BLOCKINDEX, Integer.valueOf(blockStartIndex)); entry.setOption(OPTION_INT_BLOCKCOUNT, Integer.valueOf(1)); listFrameEntries.add(entry); @@ -979,10 +979,10 @@ public boolean exportBamV2(Path fileName, DxtEncoder.DxtType dxtType, int pvrzIn short v; for (int i = 0; i < listFrameEntries.size(); i++) { PseudoBamFrameEntry fe = listFrameEntries.get(i); - DynamicArray.putShort(bamData, ofs, (short)fe.width); - DynamicArray.putShort(bamData, ofs + 2, (short)fe.height); - DynamicArray.putShort(bamData, ofs + 4, (short)fe.centerX); - DynamicArray.putShort(bamData, ofs + 6, (short)fe.centerY); + DynamicArray.putShort(bamData, ofs, (short)fe.getWidth()); + DynamicArray.putShort(bamData, ofs + 2, (short)fe.getHeight()); + DynamicArray.putShort(bamData, ofs + 4, (short)fe.getCenterX()); + DynamicArray.putShort(bamData, ofs + 6, (short)fe.getCenterY()); o = fe.getOption(OPTION_INT_BLOCKINDEX); v = (o != null) ? ((Integer)o).shortValue() : 0; DynamicArray.putShort(bamData, ofs + 8, v); @@ -1451,7 +1451,10 @@ public void setFrame(BufferedImage image) @Override public Object clone() { - return new PseudoBamFrameEntry(frame, centerX, centerY); + PseudoBamFrameEntry retVal = new PseudoBamFrameEntry(frame, centerX, centerY); + retVal.overrideCenterX = overrideCenterX; + retVal.overrideCenterY = overrideCenterY; + return retVal; } @Override From 0b89ebe28ababd3f1448da18966b80a300176004 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Mon, 15 Mar 2021 11:53:33 +0100 Subject: [PATCH 041/158] SpriteDecoder: Palette-related changes - Add global option to enabled/disable palette replacement - Fix (unused?) palette indices 2 and 3 in false color palettes - PST lookup tables: clown color column defines number of valid color ranges - Minor bugfixes and improvements --- .../cre/decoder/MonsterPlanescapeDecoder.java | 20 +++--- .../resource/cre/decoder/SpriteDecoder.java | 33 ++++++++-- .../resource/cre/decoder/SpriteUtils.java | 11 ++-- .../cre/decoder/tables/avatars-pst.2da | 62 +++++++++---------- .../cre/decoder/tables/avatars-pstee.2da | 56 ++++++++--------- .../resource/cre/decoder/tables/notes.txt | 4 +- 6 files changed, 107 insertions(+), 79 deletions(-) diff --git a/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java index 38e951b57..6726d70df 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java @@ -70,7 +70,7 @@ public void accept(BamV1Control control, SegmentDef sd) SpriteUtils.fixShadowColor(control); } - if (isFalseColor()) { + if (isPaletteReplacementEnabled() && isFalseColor()) { applyFalseColors(control, sd); } @@ -255,6 +255,9 @@ public static IniMap processTableData(String[] data) } if (clown != 0) { lines.add("clown=" + clown); + for (int i = 0; i < clown; i++) { + lines.add("color" + (i + 1) + "=" + (128 + i * 16)); + } } if (bestiary != 0) { lines.add("bestiary=" + bestiary); @@ -327,15 +330,18 @@ private int[] setColorLocations(IniMapSection section) { int[] retVal = null; if (Profile.getGame() == Profile.Game.PST) { - retVal = setColorLocationsPST(); + retVal = setColorLocationsIni(section); + if (retVal.length == 0) { + retVal = setColorLocationsCre(); + } } else { - retVal = setColorLocationsPSTEE(section); + retVal = setColorLocationsIni(section); } return retVal; } - /** Returns a list of all valid color locations. (PSTEE) */ - private int[] setColorLocationsPSTEE(IniMapSection section) + /** Returns a list of all valid color locations defined in the associated INI file. (PSTEE) */ + private int[] setColorLocationsIni(IniMapSection section) { final HashSet usedColors = new HashSet<>(); int[] retVal = new int[7]; @@ -354,8 +360,8 @@ private int[] setColorLocationsPSTEE(IniMapSection section) return retVal; } - /** Returns a list of all valid color locations. (original PST) */ - private int[] setColorLocationsPST() + /** Returns a list of all valid color locations from the CRE resource. (original PST) */ + private int[] setColorLocationsCre() { final HashSet usedColors = new HashSet<>(); CreResource cre = getCreResource(); diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index 63164dda4..17671d38d 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -630,16 +630,19 @@ public static Direction from(int value) { @Override public void accept(BamV1Control control, SegmentDef sd) { - if (sd.getSpriteType() == SegmentDef.SpriteType.AVATAR) { + if (isPaletteReplacementEnabled() && sd.getSpriteType() == SegmentDef.SpriteType.AVATAR) { int[] palette = getNewPaletteData(sd.getEntry()); if (palette != null) { SpriteUtils.applyNewPalette(control, palette); } } + SpriteUtils.fixShadowColor(control); - if (isFalseColor()) { + + if (isPaletteReplacementEnabled() && isFalseColor()) { applyFalseColors(control, sd); } + if (isTranslucent()) { applyTranslucency(control); } @@ -699,6 +702,7 @@ public void accept(DirDef dd, int frameIdx) private boolean showCircle; private boolean showPersonalSpace; private boolean showBoundingBox; + private boolean paletteReplacementEnabled; private boolean renderSpriteAvatar; private boolean renderSpriteWeapon; private boolean renderSpriteHelmet; @@ -812,6 +816,7 @@ protected SpriteDecoder(AnimationType type, CreResource cre) throws Exception this.showCircle = false; this.showPersonalSpace = false; this.showBoundingBox = false; + this.paletteReplacementEnabled = true; this.renderSpriteAvatar = true; this.renderSpriteWeapon = true; this.renderSpriteShield = true; @@ -860,7 +865,7 @@ protected void setAttribute(DecoderAttribute att, Object value) } /** Returns an iterator over the attribute keys. */ - protected Iterator getAttributeIterator() + public Iterator getAttributeIterator() { return attributesMap.keySet().iterator(); } @@ -1135,6 +1140,20 @@ public void setRenderHelmet(boolean b) } } + /** Returns whether any kind of palette replacement (full palette or false colors) is enabled. */ + public boolean isPaletteReplacementEnabled() + { + return paletteReplacementEnabled; + } + + /** Sets whether palette replacement (full palette or false colors) is enabled. */ + public void setPaletteReplacementEnabled(boolean b) + { + if (paletteReplacementEnabled != b) { + paletteReplacementEnabled = b; + spriteChanged(); + } + } /** Returns the moving speed of the creature animation. */ public double getMoveScale() @@ -1614,7 +1633,7 @@ protected void createAnimation(SeqDef definition, List directions, int frameIndex = createFrame(frameInfo.toArray(new FrameInfo[frameInfo.size()]), beforeSrcFrame, afterSrcFrame); if (afterDstFrame != null) { - afterDstFrame.accept(dd, Integer.valueOf(frameIndex)); + afterDstFrame.accept(dd, frameIndex); } dstCtrl.cycleAddFrames(cycleIndex, new int[] {frameIndex}); } @@ -1889,7 +1908,7 @@ protected int[] getColorData(int colorIndex) { int[] retVal = null; try { - retVal = SpriteUtils.getColorGradient(getAnimationType(), colorIndex, true); + retVal = SpriteUtils.getColorGradient(colorIndex, true); } catch (Exception e) { e.printStackTrace(); } @@ -1948,6 +1967,10 @@ protected void applyFalseColors(BamV1Control control, SegmentDef sd) palette = SpriteUtils.interpolateColors(palette, ofs1, ofs2, srcLen, ofs3, dstLen, false); } } + + // fixing special palette entries + palette[2] = 0xFF000000; + palette[3] = 0xFF000000; } control.setExternalPalette(palette); diff --git a/src/org/infinity/resource/cre/decoder/SpriteUtils.java b/src/org/infinity/resource/cre/decoder/SpriteUtils.java index a258c8205..ecff7ba27 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteUtils.java +++ b/src/org/infinity/resource/cre/decoder/SpriteUtils.java @@ -610,17 +610,16 @@ public static int[] interpolateColors(int[] palette, int srcOfs1, int srcOfs2, i /** * Returns the specified color gradient. Colors are retrieved from the game-specific gradient resource. * Optionally takes random color entry definitions into account. - * @param animType Indicates what type of color gradient should be loaded. * @param index Index of the color gradient. * @param allowRandom whether random color entries are taken into account. * @return the gradient as array of colors. Returns {@code null} if color index does not exist. */ - public static int[] getColorGradient(SpriteDecoder.AnimationType animType, int index, boolean allowRandom) + public static int[] getColorGradient(int index, boolean allowRandom) { if (colorGradients.isEmpty()) { // initializing color gradient map on demand ResourceEntry palFile = null; - if (animType == SpriteDecoder.AnimationType.MONSTER_PLANESCAPE) { + if (Profile.getGame() == Profile.Game.PST || Profile.getGame() == Profile.Game.PSTEE) { palFile = ResourceFactory.getResourceEntry("PAL32.BMP"); } else if (ResourceFactory.resourceExists("RANGES12.BMP")) { palFile = ResourceFactory.getResourceEntry("RANGES12.BMP"); @@ -644,7 +643,7 @@ public static int[] getColorGradient(SpriteDecoder.AnimationType animType, int i } } - int[] retVal = allowRandom ? getRandomColorGradient(animType, index) : null; + int[] retVal = allowRandom ? getRandomColorGradient(index) : null; if (retVal == null) { retVal = colorGradients.getOrDefault(index, null); } @@ -658,7 +657,7 @@ public static int[] getColorGradient(SpriteDecoder.AnimationType animType, int i * @return a randomly chosen color gradient from the random color entry list. * Returns {@code null} if no real color gradient could be determined. */ - public static int[] getRandomColorGradient(SpriteDecoder.AnimationType animType, int index) + public static int[] getRandomColorGradient(int index) { if (randomGradientIndices.isEmpty()) { if (ResourceFactory.resourceExists("RANDCOLR.2DA")) { @@ -691,7 +690,7 @@ public static int[] getRandomColorGradient(SpriteDecoder.AnimationType animType, // random color entries may refer to other random color entries indices = randomGradientIndices.getOrDefault(index, null); } else { - retVal = getColorGradient(animType, idx, false); + retVal = getColorGradient(idx, false); } } diff --git a/src/org/infinity/resource/cre/decoder/tables/avatars-pst.2da b/src/org/infinity/resource/cre/decoder/tables/avatars-pst.2da index 924cfbfc2..759be528a 100644 --- a/src/org/infinity/resource/cre/decoder/tables/avatars-pst.2da +++ b/src/org/infinity/resource/cre/decoder/tables/avatars-pst.2da @@ -7,12 +7,12 @@ 0x0003 cbaub * 18 16 3 * * 69 0x0004 ccamb * 18 22 5 * 2 6 0x0005 ccolb * 18 16 3 * 0 7 -0x0006 dcthb * 18 16 3 1 1 78 +0x0006 dcthb * 18 16 3 4 1 78 0x0007 ccorb * 18 20 4 * 5 8 0x0008 ccrnb * 18 12 3 * 5 9 0x0009 ccgdb * 18 16 3 * 2 10 -0x000a dctfb * 18 16 3 1 0 85 -0x000b dctmb * 18 16 3 1 0 11 +0x000a dctfb * 18 16 3 4 0 85 +0x000b dctmb * 18 16 3 4 0 11 0x000c cdabb * 18 16 3 * 0 12 0x000d ddkkb * 18 16 3 1 1 13 0x000e cdeib * 18 16 3 * 6 14 @@ -20,19 +20,19 @@ 0x0010 dhkmb * 18 20 4 1 2 16 0x0011 clkmb * 18 16 5 * 2 17 0x0012 cwizb * 18 16 3 * 0 18 -0x0013 ddtfb * 18 16 3 1 0 86 -0x0014 ddtmb * 18 16 3 1 0 19 +0x0013 ddtfb * 18 16 3 2 0 86 +0x0014 ddtmb * 18 16 3 2 0 19 0x0015 cebbb * 18 16 3 * 2 20 0x0016 cffgb * 18 16 3 * 0 21 0x0017 cfhjb * 18 20 4 * 5 23 0x0018 cgarb * 18 16 3 * 5 24 -0x0019 dghfb * 18 16 3 1 0 87 -0x001a dghmb * 18 16 3 1 0 25 +0x0019 dghfb * 18 16 3 4 0 87 +0x001a dghmb * 18 16 3 4 0 25 0x001b cgstb * 18 16 3 * 3 26 0x001c cacab * 18 16 3 * 0 27 -0x001d dgytb * 18 16 3 1 1 28 +0x001d dgytb * 18 16 3 4 1 28 0x001e cglab * 18 30 5 * 5 29 -0x001f dgodb * 18 16 3 1 1 30 +0x001f dgodb * 18 16 3 4 1 30 0x0020 cgdgb * 18 16 3 * 2 31 0x0021 cgrib * 18 16 3 * 5 32 0x0022 cgrkb * 18 30 5 * 5 33 @@ -43,7 +43,7 @@ 0x0027 clopb * 18 30 5 * 0 38 0x0028 clemb * 18 20 3 * 5 39 0x0029 clthb * 18 16 3 * 0 40 -0x002a dmhtb * 18 16 3 1 0 74 +0x002a dmhtb * 18 16 3 4 0 74 0x002b cmklb * 18 16 3 * 2 41 0x002c dmidb * 18 16 3 * 0 75 0x002d cmdrb * 18 16 3 * 7 42 @@ -56,16 +56,16 @@ 0x0034 cndmb * 18 16 3 * 7 44 0x0035 cnprb * 18 16 3 * 5 45 0x0036 cphdb * 18 16 3 * 0 46 -0x0037 dprob * 18 16 3 1 0 47 +0x0037 dprob * 18 16 3 4 0 47 0x0038 cpuzb * 18 16 3 * 0 48 0x0039 csm1b * 18 16 3 * 9 49 0x003a carmb * 18 20 4 * 2 52 -0x003b dsptb * 18 16 3 1 0 53 -0x003c dskwb * 18 16 3 1 4 54 -0x003d dhlfb * 18 16 3 1 0 72 -0x003e dhlmb * 18 16 3 1 0 73 -0x003f dhufb * 18 16 3 1 0 80 -0x0040 dhumb * 18 16 3 1 0 81 +0x003b dsptb * 18 16 3 3 0 53 +0x003c dskwb * 18 16 3 3 4 54 +0x003d dhlfb * 18 16 3 4 0 72 +0x003e dhlmb * 18 16 3 4 0 73 +0x003f dhufb * 18 16 3 4 0 80 +0x0040 dhumb * 18 16 3 4 0 81 0x0041 ctrzb * 18 20 4 * 6 55 0x0042 ctgtb * 18 16 3 * 5 56 0x0043 ctreb * 18 16 3 * 5 57 @@ -76,33 +76,33 @@ 0x0048 dwreb * 18 16 3 1 0 62 0x0049 cwrmb * 18 16 3 * 1 63 0x004a cwurb * 18 16 3 * 5 64 -0x004b dzmfb * 18 16 3 1 5 83 -0x004c dzmmb * 18 16 3 1 5 65 +0x004b dzmfb * 18 16 3 4 5 83 +0x004c dzmmb * 18 16 3 4 5 65 0x004d cfelb * 18 16 3 * 0 22 0x004e csm2b * 18 16 3 * 9 50 0x004f csm3b * 18 22 5 * 9 51 -0x0050 dthkb * 18 22 5 1 5 5 +0x0050 dthkb * 18 22 5 4 5 5 0x0051 dnosb * 18 16 3 * 8 76 0x0052 dnozb * 18 16 3 * 5 76 -0x0053 dtffb * 18 16 3 1 0 84 -0x0054 dtlrb * 18 16 3 1 1 77 -0x0055 dtfmb * 18 16 3 1 1 79 -0x0056 dbaub * 18 16 3 1 1 69 +0x0053 dtffb * 18 16 3 4 0 84 +0x0054 dtlrb * 18 16 3 4 1 77 +0x0055 dtfmb * 18 16 3 4 1 79 +0x0056 dbaub * 18 16 3 4 1 69 0x0057 charb * 18 16 5 * 2 71 -0x0058 dmidb * 18 16 3 1 0 75 -0x0059 daafb * 18 16 3 1 0 70 +0x0058 dmidb * 18 16 3 4 0 75 +0x0059 daafb * 18 16 3 4 0 70 0x005a cgabb * 18 20 4 * 5 1 0x005b crabb * 18 20 4 * 5 2 -0x005c dtwzb * 18 16 3 1 0 82 +0x005c dtwzb * 18 16 3 4 0 82 0x005d dnomb * 18 16 3 * 0 19 0x005e cuhdb * 18 16 3 * 3 * -0x005f dquib * 18 16 3 1 3 * +0x005f dquib * 18 16 3 4 3 * 0x0060 ctrdb * 18 16 5 * 5 58 0x0061 dnofb * 18 16 3 * 8 76 0x0062 dvhstdb * 18 16 3 1 2 * -0x0063 dctmb * 18 16 3 1 0 * -0x0064 daafb * 18 16 3 1 0 80 -0x0066 dctfb * 18 16 3 1 0 * +0x0063 dctmb * 18 16 3 4 0 * +0x0064 daafb * 18 16 3 4 0 80 +0x0066 dctfb * 18 16 3 4 0 * 0x022e carmb * 18 16 3 * 2 52 0x080e cdeib * 18 16 3 * 6 14 0x2000 GEARS1 GEARS2 18 60 13 * * * diff --git a/src/org/infinity/resource/cre/decoder/tables/avatars-pstee.2da b/src/org/infinity/resource/cre/decoder/tables/avatars-pstee.2da index 85ffcb27a..62d43422c 100644 --- a/src/org/infinity/resource/cre/decoder/tables/avatars-pstee.2da +++ b/src/org/infinity/resource/cre/decoder/tables/avatars-pstee.2da @@ -7,12 +7,12 @@ 0x0003 cbaub * 18 16 3 * * 69 0x0004 ccamb * 18 22 5 * 2 6 0x0005 ccolb * 18 16 3 * 0 7 -0x0006 dcthb * 18 16 3 1 1 78 +0x0006 dcthb * 18 16 3 4 1 78 0x0007 ccorb * 18 20 4 * 5 8 0x0008 ccrnb * 18 12 3 * 5 9 0x0009 ccgdb * 18 16 3 * 2 10 -0x000a dctfb * 18 16 3 1 0 85 -0x000b dctmb * 18 16 3 1 0 11 +0x000a dctfb * 18 16 3 4 0 85 +0x000b dctmb * 18 16 3 4 0 11 0x000c cdabb * 18 16 3 * 0 12 0x000d ddkkb * 18 16 3 1 1 13 0x000e cdeib * 18 16 3 * 6 14 @@ -20,19 +20,19 @@ 0x0010 dhkmb * 18 20 4 1 2 16 0x0011 clkmb * 18 16 5 * 2 17 0x0012 cwizb * 18 16 3 * 0 18 -0x0013 ddtfb * 18 16 3 1 0 86 -0x0014 ddtmb * 18 16 3 1 0 19 +0x0013 ddtfb * 18 16 3 2 0 86 +0x0014 ddtmb * 18 16 3 2 0 19 0x0015 cebbb * 18 16 3 * 2 20 0x0016 cffgb * 18 16 3 * 0 21 0x0017 cfhjb * 18 20 4 * 5 23 0x0018 cgarb * 18 16 3 * 5 24 -0x0019 dghfb * 18 16 3 1 0 87 -0x001a dghmb * 18 16 3 1 0 25 +0x0019 dghfb * 18 16 3 4 0 87 +0x001a dghmb * 18 16 3 4 0 25 0x001b cgstb * 18 16 3 * 3 26 0x001c cacab * 18 16 3 * 0 27 -0x001d dgytb * 18 16 3 1 1 28 +0x001d dgytb * 18 16 3 4 1 28 0x001e cglab * 18 30 5 * 5 29 -0x001f dgodb * 18 16 3 1 1 30 +0x001f dgodb * 18 16 3 4 1 30 0x0020 cgdgb * 18 16 3 * 2 31 0x0021 cgrib * 18 16 3 * 5 32 0x0022 cgrkb * 18 30 5 * 5 33 @@ -43,7 +43,7 @@ 0x0027 clopb * 18 30 5 * 0 38 0x0028 clemb * 18 20 3 * 5 39 0x0029 clthb * 18 16 3 * 0 40 -0x002a dmhtb * 18 16 3 1 0 74 +0x002a dmhtb * 18 16 3 4 0 74 0x002b cmklb * 18 16 3 * 2 41 0x002c dmidb * 18 16 3 * 0 75 0x002d cmdrb * 18 16 3 * 7 42 @@ -56,16 +56,16 @@ 0x0034 cndmb * 18 16 3 * 7 44 0x0035 cnprb * 18 16 3 * 5 45 0x0036 cphdb * 18 16 3 * 0 46 -0x0037 dprob * 18 16 3 1 0 47 +0x0037 dprob * 18 16 3 4 0 47 0x0038 cpuzb * 18 16 3 * 0 48 0x0039 csm1b * 18 16 3 * 9 49 0x003a carmb * 18 20 4 * 2 52 -0x003b dsptb * 18 16 3 1 0 53 -0x003c dskwb * 18 16 3 1 4 54 -0x003d dhlfb * 18 16 3 1 0 72 -0x003e dhlmb * 18 16 3 1 0 73 -0x003f dhufb * 18 16 3 1 0 80 -0x0040 dhumb * 18 16 3 1 0 81 +0x003b dsptb * 18 16 3 3 0 53 +0x003c dskwb * 18 16 3 3 4 54 +0x003d dhlfb * 18 16 3 4 0 72 +0x003e dhlmb * 18 16 3 4 0 73 +0x003f dhufb * 18 16 3 4 0 80 +0x0040 dhumb * 18 16 3 4 0 81 0x0041 ctrzb * 18 20 4 * 6 55 0x0042 ctgtb * 18 16 3 * 5 56 0x0043 ctreb * 18 16 3 * 5 57 @@ -76,27 +76,27 @@ 0x0048 dwreb * 18 16 3 1 0 62 0x0049 cwrmb * 18 16 3 * 1 63 0x004a cwurb * 18 16 3 * 5 64 -0x004b dzmfb * 18 16 3 1 5 83 -0x004c dzmmb * 18 16 3 1 5 65 +0x004b dzmfb * 18 16 3 4 5 83 +0x004c dzmmb * 18 16 3 4 5 65 0x004d cfelb * 18 16 3 * 0 22 0x004e csm2b * 18 16 3 * 9 50 0x004f csm3b * 18 22 5 * 9 51 -0x0050 dthkb * 18 22 5 1 5 5 +0x0050 dthkb * 18 22 5 4 5 5 0x0051 dnosb * 18 16 3 * 8 76 0x0052 dnozb * 18 16 3 * 5 76 -0x0053 dtffb * 18 16 3 1 0 84 -0x0054 dtlrb * 18 16 3 1 1 77 -0x0055 dtfmb * 18 16 3 1 1 79 -0x0056 dbaub * 18 16 3 1 1 69 +0x0053 dtffb * 18 16 3 4 0 84 +0x0054 dtlrb * 18 16 3 4 1 77 +0x0055 dtfmb * 18 16 3 4 1 79 +0x0056 dbaub * 18 16 3 4 1 69 0x0057 charb * 18 16 5 * 2 71 -0x0058 dmidb * 18 16 3 1 0 75 -0x0059 daafb * 18 16 3 1 0 70 +0x0058 dmidb * 18 16 3 4 0 75 +0x0059 daafb * 18 16 3 4 0 70 0x005a cgabb * 18 20 4 * 5 1 0x005b crabb * 18 20 4 * 5 2 -0x005c dtwzb * 18 16 3 1 0 82 +0x005c dtwzb * 18 16 3 4 0 82 0x005d dnomb * 18 16 3 * 0 19 0x005e cuhdb * 18 16 3 * 3 * -0x005f dquib * 18 16 3 1 3 * +0x005f dquib * 18 16 3 4 3 * 0x0060 ctrdb * 18 16 5 * 5 58 0x0061 dnofb * 18 16 3 * 8 76 0x0062 dvhstdb * 18 16 3 1 2 * diff --git a/src/org/infinity/resource/cre/decoder/tables/notes.txt b/src/org/infinity/resource/cre/decoder/tables/notes.txt index fc607d06c..00fbd4189 100644 --- a/src/org/infinity/resource/cre/decoder/tables/notes.txt +++ b/src/org/infinity/resource/cre/decoder/tables/notes.txt @@ -65,8 +65,8 @@ PST: (PST and PST:EE only) - the selection circle radius - SPACE (int) - the personal space in search map units -- CLOWN (bool) - - whether false color replacement is enabled +- CLOWN (int) + - number of false color ranges to replace (* indicates disabled false color replacement) - ARMOR (int) - (currently) unused by NI - BESTIARY (int) From 4d0ab87b6282c25c667533d4031074021373d612 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Tue, 16 Mar 2021 20:39:34 +0100 Subject: [PATCH 042/158] Improve equality checks in AbstractStruct and Datatype classes --- src/org/infinity/datatype/Bestiary.java | 20 +++++++++++++ src/org/infinity/datatype/Bitmap.java | 19 ++++++++++++ src/org/infinity/datatype/ColorPicker.java | 19 ++++++++++++ src/org/infinity/datatype/ColorValue.java | 19 ++++++++++++ src/org/infinity/datatype/Datatype.java | 26 +++++++++++++++++ src/org/infinity/datatype/DecNumber.java | 19 ++++++++++++ src/org/infinity/datatype/Flag.java | 19 ++++++++++++ src/org/infinity/datatype/FloatNumber.java | 19 ++++++++++++ src/org/infinity/datatype/HashBitmap.java | 19 ++++++++++++ src/org/infinity/datatype/IdsBitmap.java | 7 ++++- src/org/infinity/datatype/MultiNumber.java | 19 ++++++++++++ src/org/infinity/datatype/ResourceBitmap.java | 22 ++++++++++++++ src/org/infinity/datatype/ResourceRef.java | 23 +++++++++++++++ src/org/infinity/datatype/StringRef.java | 19 ++++++++++++ src/org/infinity/datatype/TextBitmap.java | 20 +++++++++++++ src/org/infinity/datatype/TextEdit.java | 21 ++++++++++++++ src/org/infinity/datatype/TextString.java | 20 +++++++++++++ src/org/infinity/datatype/Unknown.java | 22 ++++++++++++++ src/org/infinity/resource/AbstractStruct.java | 29 +++++++++++++++++++ 19 files changed, 380 insertions(+), 1 deletion(-) diff --git a/src/org/infinity/datatype/Bestiary.java b/src/org/infinity/datatype/Bestiary.java index c2bf27db2..94f5bc7f5 100644 --- a/src/org/infinity/datatype/Bestiary.java +++ b/src/org/infinity/datatype/Bestiary.java @@ -756,6 +756,26 @@ public String toString() return first ? "No known creatures" : sb.toString(); } + @Override + public int hashCode() + { + int hash = super.hashCode(); + hash = 31 * hash + ((known == null) ? 0 : known.hashCode()); + return hash; + } + + @Override + public boolean equals(Object o) + { + if (!super.equals(o) || !(o instanceof Bestiary)) { + return false; + } + Bestiary other = (Bestiary)o; + boolean retVal = (known == null && other.known == null) || + (known != null && known.equals(other.known)); + return retVal; + } + /** * Returns resource entry from which creatures will be read. * @return Pointer to resource with creatures in the bestiary. Never {@code null} diff --git a/src/org/infinity/datatype/Bitmap.java b/src/org/infinity/datatype/Bitmap.java index f4f35d240..3902e65ee 100644 --- a/src/org/infinity/datatype/Bitmap.java +++ b/src/org/infinity/datatype/Bitmap.java @@ -185,6 +185,25 @@ public String toString() return toString(value); } + @Override + public int hashCode() + { + int hash = super.hashCode(); + hash = 31 * hash + Integer.hashCode(value); + return hash; + } + + @Override + public boolean equals(Object o) + { + if (!super.equals(o) || !(o instanceof Bitmap)) { + return false; + } + Bitmap other = (Bitmap)o; + boolean retVal = (value == other.value); + return retVal; + } + /** Returns the unformatted description of the specified value. */ protected String getString(int nr) { diff --git a/src/org/infinity/datatype/ColorPicker.java b/src/org/infinity/datatype/ColorPicker.java index cab7150f2..a0e234439 100644 --- a/src/org/infinity/datatype/ColorPicker.java +++ b/src/org/infinity/datatype/ColorPicker.java @@ -441,6 +441,25 @@ public String toString() format.getRed(value), format.getGreen(value), format.getBlue(value)); } + @Override + public int hashCode() + { + int hash = super.hashCode(); + hash = 31 * hash + Integer.hashCode(value); + return hash; + } + + @Override + public boolean equals(Object o) + { + if (!super.equals(o) || !(o instanceof ColorPicker)) { + return false; + } + ColorPicker other = (ColorPicker)o; + boolean retVal = (value == other.value); + return retVal; + } + //--------------------- Begin Interface IsNumeric --------------------- @Override diff --git a/src/org/infinity/datatype/ColorValue.java b/src/org/infinity/datatype/ColorValue.java index 270d13f4f..1b24d1609 100644 --- a/src/org/infinity/datatype/ColorValue.java +++ b/src/org/infinity/datatype/ColorValue.java @@ -270,6 +270,25 @@ public String toString() return retVal; } + @Override + public int hashCode() + { + int hash = super.hashCode(); + hash = 31 * hash + Integer.hashCode(number); + return hash; + } + + @Override + public boolean equals(Object o) + { + if (!super.equals(o) || !(o instanceof ColorValue)) { + return false; + } + ColorValue other = (ColorValue)o; + boolean retVal = (number == other.number); + return retVal; + } + /** * Returns the name associated with the specified color entry. * Returns {@code null} if no name is available. diff --git a/src/org/infinity/datatype/Datatype.java b/src/org/infinity/datatype/Datatype.java index dd23ddd62..03370f6aa 100644 --- a/src/org/infinity/datatype/Datatype.java +++ b/src/org/infinity/datatype/Datatype.java @@ -64,6 +64,32 @@ public int compareTo(StructEntry o) // --------------------- End Interface Comparable --------------------- + @Override + public int hashCode() + { + int hash = 7; + hash = 31 * hash + length; + hash = 31 * hash + ((name == null) ? 0 : name.hashCode()); + hash = 31 * hash + offset; + return hash; + } + + @Override + public boolean equals(Object o) + { + if (o == this) { + return true; + } + if (!(o instanceof Datatype)) { + return false; + } + Datatype other = (Datatype)o; + boolean retVal = (length == other.length); + retVal &= (name == null && other.name == null) || + (name != null && name.equals(other.name)); + retVal &= (offset == other.offset); + return retVal; + } // --------------------- Begin Interface StructEntry --------------------- diff --git a/src/org/infinity/datatype/DecNumber.java b/src/org/infinity/datatype/DecNumber.java index bb931157e..e09d37753 100644 --- a/src/org/infinity/datatype/DecNumber.java +++ b/src/org/infinity/datatype/DecNumber.java @@ -146,6 +146,25 @@ public String toString() return Long.toString(number); } + @Override + public int hashCode() + { + int hash = super.hashCode(); + hash = 31 * hash + Long.hashCode(number); + return hash; + } + + @Override + public boolean equals(Object o) + { + if (!super.equals(o) || !(o instanceof DecNumber)) { + return false; + } + DecNumber other = (DecNumber)o; + boolean retVal = (number == other.number); + return retVal; + } + /** Attempts to parse the specified string into a decimal or, optionally, hexadecimal number. */ static long parseNumber(Object value, int size, boolean negativeAllowed, boolean hexAllowed) throws Exception { diff --git a/src/org/infinity/datatype/Flag.java b/src/org/infinity/datatype/Flag.java index 7cecb179a..6add3c075 100644 --- a/src/org/infinity/datatype/Flag.java +++ b/src/org/infinity/datatype/Flag.java @@ -219,6 +219,25 @@ public String toString() return sb.toString(); } + @Override + public int hashCode() + { + int hash = super.hashCode(); + hash = 31 * hash + Long.hashCode(value); + return hash; + } + + @Override + public boolean equals(Object o) + { + if (!super.equals(o) || !(o instanceof Flag)) { + return false; + } + Flag other = (Flag)o; + boolean retVal = (value == other.value); + return retVal; + } + /** * Returns label of flag {@code i} or {@code null}, if such flag does not exist. * diff --git a/src/org/infinity/datatype/FloatNumber.java b/src/org/infinity/datatype/FloatNumber.java index a68153c61..6b79aec96 100644 --- a/src/org/infinity/datatype/FloatNumber.java +++ b/src/org/infinity/datatype/FloatNumber.java @@ -96,6 +96,25 @@ public String toString() return Double.toString(value); } + @Override + public int hashCode() + { + int hash = super.hashCode(); + hash = 31 * hash + Double.hashCode(value); + return hash; + } + + @Override + public boolean equals(Object o) + { + if (!super.equals(o) || !(o instanceof FloatNumber)) { + return false; + } + FloatNumber other = (FloatNumber)o; + boolean retVal = (value == other.value); + return retVal; + } + public double getValue() { return value; diff --git a/src/org/infinity/datatype/HashBitmap.java b/src/org/infinity/datatype/HashBitmap.java index c4e133c7a..1213eaa4b 100644 --- a/src/org/infinity/datatype/HashBitmap.java +++ b/src/org/infinity/datatype/HashBitmap.java @@ -215,6 +215,25 @@ public String toString() return o == null ? "Unknown - " + value : o.toString(); } + @Override + public int hashCode() + { + int hash = super.hashCode(); + hash = 31 * hash + Long.hashCode(value); + return hash; + } + + @Override + public boolean equals(Object o) + { + if (!super.equals(o) || !(o instanceof HashBitmap)) { + return false; + } + HashBitmap other = (HashBitmap)o; + boolean retVal = (value == other.value); + return retVal; + } + // @Override public long getLongValue() diff --git a/src/org/infinity/datatype/IdsBitmap.java b/src/org/infinity/datatype/IdsBitmap.java index df980426d..45290c91b 100644 --- a/src/org/infinity/datatype/IdsBitmap.java +++ b/src/org/infinity/datatype/IdsBitmap.java @@ -15,7 +15,12 @@ public class IdsBitmap extends HashBitmap { public IdsBitmap(ByteBuffer buffer, int offset, int length, String name, String resource) { - super(buffer, offset, length, name, createResourceList(resource), true); + this(buffer, offset, length, name, resource, true); + } + + public IdsBitmap(ByteBuffer buffer, int offset, int length, String name, String resource, boolean sortByName) + { + super(buffer, offset, length, name, createResourceList(resource), sortByName); } @Override diff --git a/src/org/infinity/datatype/MultiNumber.java b/src/org/infinity/datatype/MultiNumber.java index c9a0c100d..aef86748b 100644 --- a/src/org/infinity/datatype/MultiNumber.java +++ b/src/org/infinity/datatype/MultiNumber.java @@ -212,6 +212,25 @@ public String toString() return sb.toString(); } + @Override + public int hashCode() + { + int hash = super.hashCode(); + hash = 31 * hash + Integer.hashCode(value); + return hash; + } + + @Override + public boolean equals(Object o) + { + if (!super.equals(o) || !(o instanceof MultiNumber)) { + return false; + } + MultiNumber other = (MultiNumber)o; + boolean retVal = (value == other.value); + return retVal; + } + /** Returns number of bits per value. */ public int getBits() { diff --git a/src/org/infinity/datatype/ResourceBitmap.java b/src/org/infinity/datatype/ResourceBitmap.java index 818f33acc..94a9e3910 100644 --- a/src/org/infinity/datatype/ResourceBitmap.java +++ b/src/org/infinity/datatype/ResourceBitmap.java @@ -268,6 +268,28 @@ public String toString() return String.format(formatString, resName, searchString, Long.toString(value)); } + @Override + public int hashCode() + { + int hash = super.hashCode(); + hash = 31 * hash + ((resources == null) ? 0 : resources.hashCode()); + hash = 31 * hash + Long.hashCode(value); + return hash; + } + + @Override + public boolean equals(Object o) + { + if (!super.equals(o) || !(o instanceof ResourceBitmap)) { + return false; + } + ResourceBitmap other = (ResourceBitmap)o; + boolean retVal = (resources == null && other.resources == null) || + (resources != null && resources.equals(other.resources)); + retVal &= (value == other.value); + return retVal; + } + //--------------------- Begin Interface IsNumeric --------------------- @Override diff --git a/src/org/infinity/datatype/ResourceRef.java b/src/org/infinity/datatype/ResourceRef.java index 4387d58c1..29ecbd954 100644 --- a/src/org/infinity/datatype/ResourceRef.java +++ b/src/org/infinity/datatype/ResourceRef.java @@ -312,6 +312,29 @@ public String toString() return getResourceName(); } + @Override + public int hashCode() + { + int hash = super.hashCode(); + hash = 31 * hash + ((type == null) ? 0 : type.hashCode()); + hash = 31 * hash + ((resname == null) ? 0 : resname.hashCode()); + return hash; + } + + @Override + public boolean equals(Object o) + { + if (!super.equals(o) || !(o instanceof ResourceRef)) { + return false; + } + ResourceRef other = (ResourceRef)o; + boolean retVal = (type == null && other.type == null) || + (type != null && type.equals(other.type)); + retVal &= (resname == null && other.resname == null) || + (resname != null && resname.equals(other.resname)); + return retVal; + } + // @Override public String getText() diff --git a/src/org/infinity/datatype/StringRef.java b/src/org/infinity/datatype/StringRef.java index 20655322a..9d06af6c4 100644 --- a/src/org/infinity/datatype/StringRef.java +++ b/src/org/infinity/datatype/StringRef.java @@ -312,6 +312,25 @@ public String toString(StringTable.Format fmt) return StringTable.getStringRef(value, fmt); } + @Override + public int hashCode() + { + int hash = super.hashCode(); + hash = 31 * hash + Integer.hashCode(value); + return hash; + } + + @Override + public boolean equals(Object o) + { + if (!super.equals(o) || !(o instanceof StringRef)) { + return false; + } + StringRef other = (StringRef)o; + boolean retVal = (value == other.value); + return retVal; + } + //--------------------- Begin Interface IsNumeric --------------------- @Override diff --git a/src/org/infinity/datatype/TextBitmap.java b/src/org/infinity/datatype/TextBitmap.java index 9bcf79fb2..dc9f26bac 100644 --- a/src/org/infinity/datatype/TextBitmap.java +++ b/src/org/infinity/datatype/TextBitmap.java @@ -176,6 +176,26 @@ public String toString() return text; } + @Override + public int hashCode() + { + int hash = super.hashCode(); + hash = 31 * hash + ((text == null) ? 0 : text.hashCode()); + return hash; + } + + @Override + public boolean equals(Object o) + { + if (!super.equals(o) || !(o instanceof TextBitmap)) { + return false; + } + TextBitmap other = (TextBitmap)o; + boolean retVal = (text == null && other.text == null) || + (text != null && text.equals(other.text)); + return retVal; + } + //--------------------- Begin Interface IsTextual --------------------- /** Returns the unprocessed textual symbol of fixed number of characters. */ diff --git a/src/org/infinity/datatype/TextEdit.java b/src/org/infinity/datatype/TextEdit.java index 279111763..f8831eda3 100644 --- a/src/org/infinity/datatype/TextEdit.java +++ b/src/org/infinity/datatype/TextEdit.java @@ -182,6 +182,27 @@ public String toString() return getText(); } + @Override + public int hashCode() + { + int hash = super.hashCode(); + hash = 31 * hash + ((text == null) ? 0 : text.hashCode()); + return hash; + } + + @Override + public boolean equals(Object o) + { + if (!super.equals(o) || !(o instanceof TextEdit)) { + return false; + } + TextEdit other = (TextEdit)o; + boolean retVal = (text == null && other.text == null) || + (text != null && text.equals(other.text)); + return retVal; + } + + public ByteBuffer toBuffer() { if (text != null) { diff --git a/src/org/infinity/datatype/TextString.java b/src/org/infinity/datatype/TextString.java index 269b7b6dd..fa9ad5d4b 100644 --- a/src/org/infinity/datatype/TextString.java +++ b/src/org/infinity/datatype/TextString.java @@ -117,6 +117,26 @@ public String toString() return getText(); } + @Override + public int hashCode() + { + int hash = super.hashCode(); + hash = 31 * hash + ((text == null) ? 0 : text.hashCode()); + return hash; + } + + @Override + public boolean equals(Object o) + { + if (!super.equals(o) || !(o instanceof TextString)) { + return false; + } + TextString other = (TextString)o; + boolean retVal = (text == null && other.text == null) || + (text != null && text.equals(other.text)); + return retVal; + } + private void setValue(String newValue) { final String oldValue = getText(); diff --git a/src/org/infinity/datatype/Unknown.java b/src/org/infinity/datatype/Unknown.java index f9bf7290e..31a993f70 100644 --- a/src/org/infinity/datatype/Unknown.java +++ b/src/org/infinity/datatype/Unknown.java @@ -188,6 +188,28 @@ public String toString() return ""; } + @Override + public int hashCode() + { + int hash = super.hashCode(); + hash = 31 * hash + ((buffer == null || buffer.array() == null) ? 0 : buffer.array().hashCode()); + return hash; + } + + @Override + public boolean equals(Object o) + { + if (!super.equals(o) || !(o instanceof Unknown)) { + return false; + } + Unknown other = (Unknown)o; + byte[] arr1 = (buffer != null && buffer.array() != null) ? buffer.array() : null; + byte[] arr2 = (other.buffer != null && other.buffer.array() != null) ? other.buffer.array() : null; + boolean retVal = (arr1 == null && arr2 == null) || + (arr1 != null && arr1.equals(arr2)); + return retVal; + } + protected void setValue(byte[] newValue) { buffer.position(0); diff --git a/src/org/infinity/resource/AbstractStruct.java b/src/org/infinity/resource/AbstractStruct.java index 681a11434..cb35e2be5 100644 --- a/src/org/infinity/resource/AbstractStruct.java +++ b/src/org/infinity/resource/AbstractStruct.java @@ -486,6 +486,35 @@ public String toString() return sb.toString(); } + @Override + public int hashCode() + { + int hash = 7; + hash = 31 * hash + ((fields == null) ? 0 : fields.hashCode()); + hash = 31 * hash + Integer.hashCode(startoffset); + hash = 31 * hash + Integer.hashCode(endoffset); + hash = 31 * hash + Integer.hashCode(extraoffset); + return hash; + } + + @Override + public boolean equals(Object o) + { + if (o == this) { + return true; + } + if (!(o instanceof AbstractStruct)) { + return false; + } + AbstractStruct other = (AbstractStruct)o; + boolean retVal = (fields == null && other.fields == null) || + (fields != null && fields.equals(other.fields)); + retVal &= (startoffset == other.startoffset); + retVal &= (endoffset == other.endoffset); + retVal &= (extraoffset == other.extraoffset); + return retVal; + } + /** Returns the table row index where the specified AddRemovable structure can be inserted. */ public int getDatatypeIndex(AddRemovable addedEntry) { From 6f21702d074f74d421867679cce97b110dcc5181 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Tue, 16 Mar 2021 20:42:43 +0100 Subject: [PATCH 043/158] EE: Use MAPNOTES.IDS values for ARE Automap Note colors --- src/org/infinity/resource/are/AutomapNote.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/org/infinity/resource/are/AutomapNote.java b/src/org/infinity/resource/are/AutomapNote.java index aa4dd1358..39cd27db6 100644 --- a/src/org/infinity/resource/are/AutomapNote.java +++ b/src/org/infinity/resource/are/AutomapNote.java @@ -8,10 +8,12 @@ import org.infinity.datatype.Bitmap; import org.infinity.datatype.DecNumber; +import org.infinity.datatype.IdsBitmap; import org.infinity.datatype.StringRef; import org.infinity.datatype.Unknown; import org.infinity.resource.AbstractStruct; import org.infinity.resource.AddRemovable; +import org.infinity.resource.ResourceFactory; import org.infinity.util.io.StreamUtils; public final class AutomapNote extends AbstractStruct implements AddRemovable @@ -56,7 +58,11 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new DecNumber(buffer, offset + 2, 2, ARE_AUTOMAP_LOCATION_Y)); addField(new StringRef(buffer, offset + 4, ARE_AUTOMAP_TEXT)); addField(new Bitmap(buffer, offset + 8, 2, ARE_AUTOMAP_TEXT_LOCATION, s_source)); - addField(new Bitmap(buffer, offset + 10, 2, ARE_AUTOMAP_MARKER_COLOR, s_flag)); + if (ResourceFactory.resourceExists("MAPNOTES.IDS")) { + addField(new IdsBitmap(buffer, offset + 10, 2, ARE_AUTOMAP_MARKER_COLOR, "MAPNOTES.IDS", false)); + } else { + addField(new Bitmap(buffer, offset + 10, 2, ARE_AUTOMAP_MARKER_COLOR, s_flag)); + } addField(new DecNumber(buffer, offset + 12, 4, ARE_AUTOMAP_CONTROL_ID)); addField(new Unknown(buffer, offset + 16, 36)); return offset + 52; From ceff02b00cc77b7248ab9ae1a3849c7e91c45024 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Tue, 16 Mar 2021 20:50:04 +0100 Subject: [PATCH 044/158] Opcode 253: Add support for param2 (Color) in EE and BG2 --- src/org/infinity/resource/EffectFactory.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/org/infinity/resource/EffectFactory.java b/src/org/infinity/resource/EffectFactory.java index a2f94470a..e9dc20d23 100644 --- a/src/org/infinity/resource/EffectFactory.java +++ b/src/org/infinity/resource/EffectFactory.java @@ -36,6 +36,7 @@ import org.infinity.datatype.Unknown; import org.infinity.datatype.UnsignDecNumber; import org.infinity.datatype.UpdateListener; +import org.infinity.resource.are.AutomapNote; import org.infinity.resource.itm.ItmResource; import org.infinity.util.IdsMapEntry; import org.infinity.util.LongIntegerHashMap; @@ -3751,6 +3752,16 @@ private String makeEffectParamsBG2(Datatype parent, ByteBuffer buffer, int offse break; case 253: // Set automap note + s.add(new StringRef(buffer, offset, "String")); + if (Profile.isEnhancedEdition() && ResourceFactory.resourceExists("MAPNOTES.IDS")) { + s.add(new IdsBitmap(buffer, offset + 4, 4, "Color", "MAPNOTES.IDS", false)); + } else if (Profile.getEngine() == Profile.Engine.BG2) { + s.add(new Bitmap(buffer, offset + 4, 4, "Color", AutomapNote.s_flag)); + } else { + s.add(new DecNumber(buffer, offset + 4, 4, AbstractStruct.COMMON_UNUSED)); + } + break; + case 254: // Remove automap note case 267: // Disable display string s.add(new StringRef(buffer, offset, "String")); From 23ab3528159394be66678d9c0ea45e63e59edabc Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Tue, 16 Mar 2021 23:14:48 +0100 Subject: [PATCH 045/158] Fix item filters in ItemInfo class --- .../cre/decoder/internal/ItemInfo.java | 251 ++++++++---------- 1 file changed, 110 insertions(+), 141 deletions(-) diff --git a/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java b/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java index 996f20741..f443a8316 100644 --- a/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java +++ b/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java @@ -30,220 +30,181 @@ */ public class ItemInfo implements Comparable { + /** + * This predicate simply returns {@code false} for all items. + */ + public static final ItemPredicate FILTER_NONE = (info) -> { return false; }; + /** * This predicate simply returns {@code true} for all items. */ - public static final ItemPredicate FILTER_ALL = new ItemPredicate() { - @Override - public boolean test(ItemInfo info) - { - return true; - } - }; + public static final ItemPredicate FILTER_ALL = (info) -> { return true; }; /** * This predicate returns {@code true} only if the item can be equipped and unequipped in the character inventory. */ - public static final ItemPredicate FILTER_EQUIPPABLE = new ItemPredicate() { - @Override - public boolean test(ItemInfo info) - { - return (info.getFlags() & 0x04) == 0x04; // droppable - } + public static final ItemPredicate FILTER_EQUIPPABLE = (info) -> { + return (info.getFlags() & 0x04) == 0x04; // droppable }; /** * This predicate returns {@code true} only if the item can be equipped in a weapon slot. */ - public static final ItemPredicate FILTER_WEAPON = new ItemPredicate() { - @Override - public boolean test(ItemInfo info) - { - boolean retVal = FILTER_EQUIPPABLE.test(info); - if (retVal && info.getAbilityCount() > 0) { - retVal &= (info.getAbility(0).getLocation() == 1); - } - return retVal; + public static final ItemPredicate FILTER_WEAPON = (info) -> { + boolean retVal = FILTER_EQUIPPABLE.test(info); + retVal &= (info.getAbilityCount() > 0); + if (retVal) { + retVal &= (info.getAbility(0).getLocation() == 1); // weapon slot + retVal &= (info.getAbility(0).getLauncher() == 0); // no launcher required } + return retVal; }; /** * This predicate returns {@code true} if the item is a two-handed weapon (melee or ranged). */ - public static final ItemPredicate FILTER_WEAPON_2H = new ItemPredicate() { - @Override - public boolean test(ItemInfo info) - { - boolean retVal = FILTER_WEAPON.test(info); - if (retVal && info.getAbilityCount() > 0) { - int mask = Profile.isEnhancedEdition() ? 0x1002 : 0x2; // two-handed, fake two-handed - retVal &= (info.getFlags() & mask) != 0; - } - return retVal; + public static final ItemPredicate FILTER_WEAPON_2H = (info) -> { + boolean retVal = FILTER_WEAPON.test(info); + retVal &= (info.getAbilityCount() > 0); + if (retVal) { + int mask = Profile.isEnhancedEdition() ? 0x1002 : 0x2; // two-handed, fake two-handed + retVal &= (info.getFlags() & mask) != 0; } + return retVal; }; /** * This predicate returns {@code true} only if the item can be placed in a weapon slot and the default ability * is defined as melee type. */ - public static final ItemPredicate FILTER_WEAPON_MELEE = new ItemPredicate() { - @Override - public boolean test(ItemInfo info) - { - boolean retVal = FILTER_WEAPON.test(info); - if (retVal && info.getAbilityCount() > 0) { - AbilityInfo ai = info.getAbility(0); - retVal = (ai.getAbilityType() == 1) && - (ai.getLauncher() == 0); - } - return retVal; + public static final ItemPredicate FILTER_WEAPON_MELEE = (info) -> { + boolean retVal = FILTER_WEAPON.test(info); + retVal &= (info.getAbilityCount() > 0); + if (retVal) { + AbilityInfo ai = info.getAbility(0); + retVal = (ai.getAbilityType() == 1) && + (ai.getLauncher() == 0); } + return retVal; }; /** * This predicate returns {@code true} only if {@link #FILTER_WEAPON_MELEE} passes the test and the item is flagged * as two-handed or fake two-handed (e.g. monk fists). */ - public static final ItemPredicate FILTER_WEAPON_MELEE_2H = new ItemPredicate() { - @Override - public boolean test(ItemInfo info) - { - boolean retVal = FILTER_WEAPON_MELEE.test(info); - if (retVal) { - int mask = Profile.isEnhancedEdition() ? 0x1002 : 0x2; - retVal &= (info.getFlags() & mask) != 0; - } - return retVal; + public static final ItemPredicate FILTER_WEAPON_MELEE_2H = (info) -> { + boolean retVal = FILTER_WEAPON_MELEE.test(info); + if (retVal) { + int mask = Profile.isEnhancedEdition() ? 0x1002 : 0x2; + retVal &= (info.getFlags() & mask) != 0; } + return retVal; }; /** * This predicate returns {@code true} only if {@link #FILTER_WEAPON_MELEE} passes the test and the item can be * equipped in the shield slot. */ - public static final ItemPredicate FILTER_WEAPON_MELEE_LEFT_HANDED = new ItemPredicate() { - @Override - public boolean test(ItemInfo info) - { - boolean retVal = FILTER_WEAPON_MELEE.test(info); - if (retVal) { - boolean isTwoHanded = (info.getFlags() & 2) != 0; - boolean allowLeftHanded = !Profile.isEnhancedEdition() || ((info.getFlags() & (1 << 13)) == 0); - retVal = !isTwoHanded && allowLeftHanded; - } - return retVal; + public static final ItemPredicate FILTER_WEAPON_MELEE_LEFT_HANDED = (info) -> { + boolean retVal = FILTER_WEAPON_MELEE.test(info); + if (retVal) { + boolean isTwoHanded = (info.getFlags() & 2) != 0; + boolean allowLeftHanded = !Profile.isEnhancedEdition() || ((info.getFlags() & (1 << 13)) == 0); + retVal = !isTwoHanded && allowLeftHanded; } + return retVal; }; /** * This predicate returns {@code true} only if the item can be placed in a weapon slot and the default ability * is defined as ranged or launcher type. */ - public static final ItemPredicate FILTER_WEAPON_RANGED = new ItemPredicate() { - @Override - public boolean test(ItemInfo info) - { - boolean retVal = FILTER_WEAPON.test(info); - if (retVal && info.getAbilityCount() > 0) { - AbilityInfo ai = info.getAbility(0); - retVal &= (ai.getLauncher() == 0); - retVal &= (ai.getAbilityType() == 2) || (ai.getAbilityType() == 4); - } - return retVal; + public static final ItemPredicate FILTER_WEAPON_RANGED = (info) -> { + boolean retVal = FILTER_WEAPON.test(info); + retVal &= (info.getAbilityCount() > 0); + if (retVal) { + AbilityInfo ai = info.getAbility(0); + retVal &= (ai.getLauncher() == 0); + retVal &= (ai.getAbilityType() == 2) || (ai.getAbilityType() == 4); } + return retVal; }; /** * This predicate returns {@code true} only if the item can be placed in a weapon slot and is a * ranged launcher-type weapon. */ - public static final ItemPredicate FILTER_WEAPON_RANGED_LAUNCHER = new ItemPredicate() { - @Override - public boolean test(ItemInfo info) - { - boolean retVal = FILTER_WEAPON.test(info); - if (retVal && info.getAbilityCount() > 0) { - AbilityInfo ai = info.getAbility(0); - retVal &= (ai.getLauncher() == 0); - retVal &= (ai.getAbilityType() == 4); - } - return retVal; + public static final ItemPredicate FILTER_WEAPON_RANGED_LAUNCHER = (info) -> { + boolean retVal = FILTER_WEAPON.test(info); + retVal &= (info.getAbilityCount() > 0); + if (retVal) { + AbilityInfo ai = info.getAbility(0); + retVal &= (ai.getLauncher() == 0); + retVal &= (ai.getAbilityType() == 4); } + return retVal; }; /** * This predicate returns {@code true} only if the item is a shield that can be placed in the shield slot. */ - public static final ItemPredicate FILTER_SHIELD = new ItemPredicate() { - @Override - public boolean test(ItemInfo info) - { - boolean retVal = FILTER_EQUIPPABLE.test(info); - if (retVal) { - switch (info.getCategory()) { - case 12: // Shields - case 41: // Bucklers - case 47: // Large shields - case 49: // Medium shields - case 53: // Small shields - break; - default: - retVal = false; - } + public static final ItemPredicate FILTER_SHIELD = (info) -> { + boolean retVal = FILTER_EQUIPPABLE.test(info); + if (retVal) { + switch (info.getCategory()) { + case 12: // Shields + case 41: // Bucklers + case 47: // Large shields + case 49: // Medium shields + case 53: // Small shields + break; + default: + retVal = false; } - return retVal; } + return retVal; }; /** * This predicate returns {@code true} only if the item can be placed in the armor slot. */ - public static final ItemPredicate FILTER_ARMOR = new ItemPredicate() { - @Override - public boolean test(ItemInfo info) - { - boolean retVal = FILTER_EQUIPPABLE.test(info); - if (retVal) { - switch (info.getCategory()) { - case 2: // Armor - case 60: // Leather armor - case 61: // Studded leather - case 62: // Chain mail - case 63: // Split mail - case 64: // Plate mail - case 65: // Full plate - case 66: // Hide armor - case 67: // Robes - case 68: // Scale mail - break; - default: - retVal = false; - } + public static final ItemPredicate FILTER_ARMOR = (info) -> { + boolean retVal = FILTER_EQUIPPABLE.test(info); + if (retVal) { + switch (info.getCategory()) { + case 2: // Armor + case 60: // Leather armor + case 61: // Studded leather + case 62: // Chain mail + case 63: // Split mail + case 64: // Plate mail + case 65: // Full plate + case 66: // Hide armor + case 67: // Robes + case 68: // Scale mail + break; + default: + retVal = false; } - return retVal; } + return retVal; }; /** * This predicate returns {@code true} only if the item can be placed in the helmet slot. */ - public static final ItemPredicate FILTER_HELMET = new ItemPredicate() { - @Override - public boolean test(ItemInfo info) - { - boolean retVal = FILTER_EQUIPPABLE.test(info); - if (retVal) { - switch (info.getCategory()) { - case 7: // Headgear - case 72: // Hats - break; - default: - retVal = false; - } + public static final ItemPredicate FILTER_HELMET = (info) -> { + boolean retVal = FILTER_EQUIPPABLE.test(info); + if (retVal) { + switch (info.getCategory()) { + case 7: // Headgear + case 72: // Hats + break; + default: + retVal = false; } - return retVal; } + return retVal; }; /** Predefined {@code ItemInfo} structure without associated ITM resource. */ @@ -264,6 +225,7 @@ public boolean test(ItemInfo info) private int unusableKits; private String appearance; private int proficiency; + private int stack; private int enchantment; /** @@ -462,6 +424,9 @@ private ItemInfo(ResourceEntry itmEntry) throws Exception /** Returns the proficiency id associated with the item. */ public int getProficiency() { return proficiency; } + /** Returns the max. stack amount available for the the item. */ + public int getStack() { return stack; } + /** Returns the enchantment value of the item. */ public int getEnchantment() { return enchantment; } @@ -493,7 +458,7 @@ private ItemInfo(ResourceEntry itmEntry) throws Exception private void initDefault() { name = nameIdentified = appearance = ""; - flags = category = unusable = unusableKits = proficiency = enchantment = 0; + flags = category = unusable = unusableKits = proficiency = stack = enchantment = 0; } /** Initializes relevant item attributes. */ @@ -578,8 +543,12 @@ private void init() throws IOException, Exception Misc.requireCondition(is.skip(0xe) == 0xe, "Could not advance in data stream: " + itmEntry); } + // stack amount + Misc.requireCondition(is.skip(0x6) == 0x6, "Could not advance in data stream: " + itmEntry); + this.stack = StreamUtils.readShort(is); + // enchantment - Misc.requireCondition(is.skip(0x2e) == 0x2e, "Could not advance in data stream: " + itmEntry); + Misc.requireCondition(is.skip(0x26) == 0x26, "Could not advance in data stream: " + itmEntry); this.enchantment = StreamUtils.readInt(is); // abilities (common: ofs, num, header) From 5f3f2cbdc26686a118e0a99172ea3574707b6c9c Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Tue, 16 Mar 2021 23:15:44 +0100 Subject: [PATCH 046/158] SpriteDecoder: Fix option to enable/disable palette replacement --- .../infinity/resource/cre/decoder/SpriteDecoder.java | 1 + .../infinity/resource/cre/decoder/SpriteUtils.java | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index 17671d38d..8e885886e 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -1151,6 +1151,7 @@ public void setPaletteReplacementEnabled(boolean b) { if (paletteReplacementEnabled != b) { paletteReplacementEnabled = b; + SpriteUtils.clearBamCache(); spriteChanged(); } } diff --git a/src/org/infinity/resource/cre/decoder/SpriteUtils.java b/src/org/infinity/resource/cre/decoder/SpriteUtils.java index ecff7ba27..2834d1d8f 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteUtils.java +++ b/src/org/infinity/resource/cre/decoder/SpriteUtils.java @@ -45,8 +45,20 @@ public class SpriteUtils /** Clears cached resources. */ public static void clearCache() + { + clearBamCache(); + clearColorCache(); + } + + /** Clears BAM cache only. */ + public static void clearBamCache() { bamCache.clear(); + } + + /** Clears all palette-related caches. */ + public static void clearColorCache() + { paletteCache.clear(); colorGradients.clear(); randomGradientIndices.clear(); From 7f810999529032e181345ff0058286e4bfa0a360 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Tue, 16 Mar 2021 23:16:50 +0100 Subject: [PATCH 047/158] SpriteDecoder: Fix shield height code in character decoder --- .../resource/cre/decoder/CharacterDecoder.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/org/infinity/resource/cre/decoder/CharacterDecoder.java b/src/org/infinity/resource/cre/decoder/CharacterDecoder.java index 83f6230fb..61628445e 100644 --- a/src/org/infinity/resource/cre/decoder/CharacterDecoder.java +++ b/src/org/infinity/resource/cre/decoder/CharacterDecoder.java @@ -231,7 +231,17 @@ public String getShieldHeightCode() return retVal; } - protected void setShieldHeightCode(String s) { setAttribute(KEY_HEIGHT_CODE_SHIELD, s); } + protected void setShieldHeightCode(String s) + { + if (s != null && !s.isEmpty()) { + // Discard if shield height code refers to non-existing overlays + List resList = ResourceFactory.getResources(s + "..G1\\.BAM"); + if (resList.isEmpty()) { + s = ""; + } + } + setAttribute(KEY_HEIGHT_CODE_SHIELD, s); + } /** Returns the paperdoll resref. */ public String getPaperdollResref() { return getAttribute(KEY_RESREF_PAPERDOLL); } From 2dd8123cb3a03324d9f98a4c0f3581c825bc2d23 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Tue, 16 Mar 2021 23:17:34 +0100 Subject: [PATCH 048/158] SpriteDecoder: Internal changes in planescape monster decoder --- .../cre/decoder/MonsterPlanescapeDecoder.java | 48 ++++++++----------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java index 6726d70df..04400b9d6 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java @@ -311,28 +311,28 @@ protected int getColorOffset(int locationIndex) /** Returns the number of available false color ranges. */ public int getColorLocationCount() { - int[] color = getAttribute(KEY_COLOR_LOCATION); - return color.length; + List color = getAttribute(KEY_COLOR_LOCATION); + return color.size(); } /** Returns the specified color location. */ - public int getColorLocation(int index) + public int getColorLocation(int index) throws IndexOutOfBoundsException { - int[] color = getAttribute(KEY_COLOR_LOCATION); - if (index < 0 || index > color.length) { + List color = getAttribute(KEY_COLOR_LOCATION); + if (index < 0 || index >= color.size()) { throw new IndexOutOfBoundsException(); } - return color[index]; + return color.get(index); } /** Returns a list of all valid color locations. */ - private int[] setColorLocations(IniMapSection section) + private List setColorLocations(IniMapSection section) { - int[] retVal = null; + List retVal = null; if (Profile.getGame() == Profile.Game.PST) { - retVal = setColorLocationsIni(section); - if (retVal.length == 0) { - retVal = setColorLocationsCre(); + retVal = setColorLocationsCre(); + if (retVal.isEmpty()) { + retVal = setColorLocationsIni(section); } } else { retVal = setColorLocationsIni(section); @@ -341,44 +341,34 @@ private int[] setColorLocations(IniMapSection section) } /** Returns a list of all valid color locations defined in the associated INI file. (PSTEE) */ - private int[] setColorLocationsIni(IniMapSection section) + private List setColorLocationsIni(IniMapSection section) { final HashSet usedColors = new HashSet<>(); - int[] retVal = new int[7]; - int num = 0; - for (int i = 0; i < retVal.length; i++) { + List retVal = new ArrayList<>(7); + for (int i = 0; i < 7; i++) { int v = section.getAsInteger(KEY_COLOR_LOCATION.getName() + (i + 1), 0); if (v >= 128 && v < 240 && !usedColors.contains(v & 0xf0)) { usedColors.add(v & 0xf0); - retVal[num] = v; - num++; + retVal.add(Integer.valueOf(v)); } } - if (num < retVal.length) { - retVal = Arrays.copyOf(retVal, num); - } return retVal; } /** Returns a list of all valid color locations from the CRE resource. (original PST) */ - private int[] setColorLocationsCre() + private List setColorLocationsCre() { final HashSet usedColors = new HashSet<>(); CreResource cre = getCreResource(); int numColors = Math.max(0, Math.min(7, ((IsNumeric)cre.getAttribute(CreResource.CRE_NUM_COLORS)).getValue())); - int[] retVal = new int[numColors]; - int num = 0; - for (int i = 0; i < retVal.length; i++) { + List retVal = new ArrayList<>(numColors); + for (int i = 0; i < numColors; i++) { int l = ((IsNumeric)cre.getAttribute(String.format(CreResource.CRE_COLOR_PLACEMENT_FMT, i + 1))).getValue(); if (l > 0 && !usedColors.contains(l & 0xf0)) { usedColors.add(l & 0xf0); - retVal[num] = l; - num++; + retVal.add(Integer.valueOf(l)); } } - if (num < retVal.length) { - retVal = Arrays.copyOf(retVal, num); - } return retVal; } From 7694db3038e8565009423b95f2b12fccc3ce69d6 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Tue, 16 Mar 2021 23:18:53 +0100 Subject: [PATCH 049/158] SpriteDecoder: Apply correct type to weapon overlay in monster decoder --- src/org/infinity/resource/cre/decoder/MonsterDecoder.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/org/infinity/resource/cre/decoder/MonsterDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterDecoder.java index 2f7dd3990..5a2f5c286 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterDecoder.java @@ -298,14 +298,15 @@ protected SeqDef getSequenceDefinition(Sequence seq) retVal = new SeqDef(seq); for (final Couple creInfo : creResList) { ResourceEntry entry = ResourceFactory.getResourceEntry(creInfo.getValue0()); + SegmentDef.SpriteType type = creInfo.getValue1(); if (SpriteUtils.bamCyclesExist(entry, ofs, SeqDef.DIR_FULL_W.length)) { - SeqDef tmp = SeqDef.createSequence(seq, SeqDef.DIR_FULL_W, false, entry, ofs, null, behavior); + SeqDef tmp = SeqDef.createSequence(seq, SeqDef.DIR_FULL_W, false, entry, ofs, type, behavior); retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); - tmp = SeqDef.createSequence(seq, SeqDef.DIR_FULL_E, true, entry, ofs + 1, null, behavior); + tmp = SeqDef.createSequence(seq, SeqDef.DIR_FULL_E, true, entry, ofs + 1, type, behavior); retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); } else if (entry != null && SpriteUtils.getBamCycles(entry) == 1) { // fallback solution: just use first bam cycle (required by a few animations) - SeqDef tmp = SeqDef.createSequence(seq, new Direction[] {Direction.S}, false, entry, 0, null, behavior); + SeqDef tmp = SeqDef.createSequence(seq, new Direction[] {Direction.S}, false, entry, 0, type, behavior); retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); } } From ada0853d9970741f581b7aee3de34a04fcd0a517 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Wed, 17 Mar 2021 15:28:18 +0100 Subject: [PATCH 050/158] Implement new feature: Creature Animation Viewer --- src/org/infinity/gui/BrowserMenuBar.java | 11 +- src/org/infinity/icon/CreViewer24.png | Bin 0 -> 759 bytes src/org/infinity/icon/Icons.java | 1 + .../resource/cre/ViewerAnimation.java | 27 +- .../resource/cre/decoder/SpriteDecoder.java | 2 +- .../resource/cre/viewer/AttributesPanel.java | 71 ++ .../cre/viewer/ColorSelectionModel.java | 419 +++++++++ .../resource/cre/viewer/CreUtils.java | 557 ++++++++++++ .../cre/viewer/CreatureAllegianceModel.java | 173 ++++ .../cre/viewer/CreatureAnimationModel.java | 321 +++++++ .../cre/viewer/CreatureControlModel.java | 600 +++++++++++++ .../cre/viewer/CreatureControlPanel.java | 664 +++++++++++++++ .../cre/viewer/CreatureSelectionModel.java | 290 +++++++ .../resource/cre/viewer/CreatureViewer.java | 335 ++++++++ .../viewer/DecoderAttributesTableModel.java | 218 +++++ .../cre/viewer/ItemSelectionModel.java | 166 ++++ .../resource/cre/viewer/MediaPanel.java | 804 ++++++++++++++++++ .../resource/cre/viewer/RenderPanel.java | 524 ++++++++++++ .../resource/cre/viewer/SettingsPanel.java | 553 ++++++++++++ .../resource/cre/viewer/bg/Backgrounds.java | 176 ++++ .../resource/cre/viewer/bg/bg_cave.jpg | Bin 0 -> 88247 bytes .../resource/cre/viewer/bg/bg_wilderness.jpg | Bin 0 -> 231202 bytes .../resource/cre/viewer/bg/pst_city.jpg | Bin 0 -> 223490 bytes .../resource/cre/viewer/icon/Icons.java | 106 +++ .../resource/cre/viewer/icon/btn_center.png | Bin 0 -> 733 bytes .../resource/cre/viewer/icon/btn_end.png | Bin 0 -> 380 bytes .../resource/cre/viewer/icon/btn_home.png | Bin 0 -> 375 bytes .../resource/cre/viewer/icon/btn_pause.png | Bin 0 -> 496 bytes .../resource/cre/viewer/icon/btn_play.png | Bin 0 -> 609 bytes .../resource/cre/viewer/icon/btn_resume.png | Bin 0 -> 470 bytes .../cre/viewer/icon/btn_step_back.png | Bin 0 -> 390 bytes .../cre/viewer/icon/btn_step_forward.png | Bin 0 -> 408 bytes .../resource/cre/viewer/icon/btn_stop.png | Bin 0 -> 372 bytes 33 files changed, 6010 insertions(+), 8 deletions(-) create mode 100644 src/org/infinity/icon/CreViewer24.png create mode 100644 src/org/infinity/resource/cre/viewer/AttributesPanel.java create mode 100644 src/org/infinity/resource/cre/viewer/ColorSelectionModel.java create mode 100644 src/org/infinity/resource/cre/viewer/CreUtils.java create mode 100644 src/org/infinity/resource/cre/viewer/CreatureAllegianceModel.java create mode 100644 src/org/infinity/resource/cre/viewer/CreatureAnimationModel.java create mode 100644 src/org/infinity/resource/cre/viewer/CreatureControlModel.java create mode 100644 src/org/infinity/resource/cre/viewer/CreatureControlPanel.java create mode 100644 src/org/infinity/resource/cre/viewer/CreatureSelectionModel.java create mode 100644 src/org/infinity/resource/cre/viewer/CreatureViewer.java create mode 100644 src/org/infinity/resource/cre/viewer/DecoderAttributesTableModel.java create mode 100644 src/org/infinity/resource/cre/viewer/ItemSelectionModel.java create mode 100644 src/org/infinity/resource/cre/viewer/MediaPanel.java create mode 100644 src/org/infinity/resource/cre/viewer/RenderPanel.java create mode 100644 src/org/infinity/resource/cre/viewer/SettingsPanel.java create mode 100644 src/org/infinity/resource/cre/viewer/bg/Backgrounds.java create mode 100644 src/org/infinity/resource/cre/viewer/bg/bg_cave.jpg create mode 100644 src/org/infinity/resource/cre/viewer/bg/bg_wilderness.jpg create mode 100644 src/org/infinity/resource/cre/viewer/bg/pst_city.jpg create mode 100644 src/org/infinity/resource/cre/viewer/icon/Icons.java create mode 100644 src/org/infinity/resource/cre/viewer/icon/btn_center.png create mode 100644 src/org/infinity/resource/cre/viewer/icon/btn_end.png create mode 100644 src/org/infinity/resource/cre/viewer/icon/btn_home.png create mode 100644 src/org/infinity/resource/cre/viewer/icon/btn_pause.png create mode 100644 src/org/infinity/resource/cre/viewer/icon/btn_play.png create mode 100644 src/org/infinity/resource/cre/viewer/icon/btn_resume.png create mode 100644 src/org/infinity/resource/cre/viewer/icon/btn_step_back.png create mode 100644 src/org/infinity/resource/cre/viewer/icon/btn_step_forward.png create mode 100644 src/org/infinity/resource/cre/viewer/icon/btn_stop.png diff --git a/src/org/infinity/gui/BrowserMenuBar.java b/src/org/infinity/gui/BrowserMenuBar.java index 0f70afd0d..9eefb0aa8 100644 --- a/src/org/infinity/gui/BrowserMenuBar.java +++ b/src/org/infinity/gui/BrowserMenuBar.java @@ -85,6 +85,7 @@ import org.infinity.resource.StructureFactory; import org.infinity.resource.Viewable; import org.infinity.resource.ViewableContainer; +import org.infinity.resource.cre.viewer.CreatureViewer; import org.infinity.resource.key.FileResourceEntry; import org.infinity.resource.key.Keyfile; import org.infinity.resource.key.ResourceEntry; @@ -1483,7 +1484,7 @@ private static final class ToolsMenu extends JMenu implements ActionListener { private static final String TOOLS_DEBUG_EXTRA_INFO = "DebugShowExtraInfo"; - private final JMenuItem toolInfinityAmp, toolCleanKeyfile, toolCheckAllDialog, toolCheckOverrideDialog; + private final JMenuItem toolInfinityAmp, toolCreatureViewer, toolCleanKeyfile, toolCheckAllDialog, toolCheckOverrideDialog; private final JMenuItem toolCheckResRef, toolIDSBrowser, toolDropZone, toolCheckCREInv; private final JMenuItem toolCheckIDSRef, toolCheckIDSBCSRef, toolCheckScripts, toolCheckStructs; private final JMenuItem toolCheckStringUse, toolCheckStringIndex, toolCheckFileUse, toolMassExport; @@ -1498,6 +1499,9 @@ private ToolsMenu() super("Tools"); setMnemonic(KeyEvent.VK_T); + toolCreatureViewer = makeMenuItem("Creature Animation Viewer", KeyEvent.VK_A, Icons.getIcon(Icons.ICON_CRE_VIEWER_24), -1, this); + add(toolCreatureViewer); + toolInfinityAmp = makeMenuItem("InfinityAmp", KeyEvent.VK_I, Icons.getIcon(Icons.ICON_VOLUME_16), -1, this); add(toolInfinityAmp); @@ -1685,7 +1689,10 @@ private void storePreferences() @Override public void actionPerformed(ActionEvent event) { - if (event.getSource() == toolInfinityAmp) { + if (event.getSource() == toolCreatureViewer) { + ChildFrame.show(CreatureViewer.class, () -> new CreatureViewer()); + } + else if (event.getSource() == toolInfinityAmp) { ChildFrame.show(InfinityAmp.class, () -> new InfinityAmp()); } else if (event.getSource() == toolIDSBrowser) { diff --git a/src/org/infinity/icon/CreViewer24.png b/src/org/infinity/icon/CreViewer24.png new file mode 100644 index 0000000000000000000000000000000000000000..cb1e7bff48a816514d71383a2f98fe9bf2924fee GIT binary patch literal 759 zcmV~Zi$ajxBaLsixOUBW&8`sKv9g+Nu+{zy2AMB>|y zkmncX)yb)e=K!n(PN#F&f6>>yORjVn17b9p5Q2LT?&uxu7oe(YK?Fhw2q9*@HII#t z;`-H_CN7`5xU8%!FQ_cbn4O(Pb8|Bq8yoY!qiNdzpCVBdF+DvEQ54O&a5!vv$Kh}k zC*b)%qJqJoSyxq6S+b!>mgT%_|5-sa8nt9elE4^)BuVzZ10yJ{uk}RR&ayN zP-X|*c9R7`KqwRf0E9vzlZ5B_g5cX)R8di38PCkjn6<~_v5alsrr8+2|FD0j_tCIr zr0Y8D{B(Nz0^I`xkCfiNfUOM}xR%Rq8{le1Q3SWU=Go!;Bl!H~69Bv)di?ZkF#=mn z-q_eUb^Dgsu(I;q07p~ParE{DKH5=|PXPg-2ZEQ+D2jTh=Tgtf*l4tkF?MAn5*zFC zcXm)p2OAoWy_My;eNToYLy4YGK`sFYfba5mo^EOJ+^s!W`(twI&511d3xmAN-`Pef z9jvRXPo`3-lI7)uenhYyH67+}ER9z+VB_51gkWXl-qE6|#uS?$S|SUQQ^b#N~21IF2g;P)aG?)HF?36lHU1 pX^FYrZbAsrlF6i&wP-WfqTd15D9@pcERg^J002ovPDHLkV1k92VKx8& literal 0 HcmV?d00001 diff --git a/src/org/infinity/icon/Icons.java b/src/org/infinity/icon/Icons.java index 26c225870..a6734b0b2 100644 --- a/src/org/infinity/icon/Icons.java +++ b/src/org/infinity/icon/Icons.java @@ -33,6 +33,7 @@ public class Icons public static final String ICON_COLLAPSE_ALL_24 = "CollapseAll24.png"; public static final String ICON_COLOR_16 = "Color16.gif"; public static final String ICON_COPY_16 = "Copy16.gif"; + public static final String ICON_CRE_VIEWER_24 = "CreViewer24.png"; public static final String ICON_CUT_16 = "Cut16.gif"; public static final String ICON_DELETE_16 = "Delete16.gif"; public static final String ICON_DOWN_16 = "Down16.gif"; diff --git a/src/org/infinity/resource/cre/ViewerAnimation.java b/src/org/infinity/resource/cre/ViewerAnimation.java index 632286443..6653197ba 100644 --- a/src/org/infinity/resource/cre/ViewerAnimation.java +++ b/src/org/infinity/resource/cre/ViewerAnimation.java @@ -45,6 +45,7 @@ import org.infinity.icon.Icons; import org.infinity.resource.cre.decoder.SpriteDecoder; import org.infinity.resource.cre.decoder.SpriteUtils; +import org.infinity.resource.cre.viewer.CreatureViewer; import org.infinity.resource.gam.PartyNPC; import org.infinity.resource.graphics.PseudoBamDecoder.PseudoBamControl; import org.infinity.util.StringTable; @@ -69,6 +70,7 @@ public class ViewerAnimation extends ChildFrame implements ActionListener private static final ButtonPanel.Control CtrlShowCircle = ButtonPanel.Control.CUSTOM_10; private static final ButtonPanel.Control CtrlShowSpace = ButtonPanel.Control.CUSTOM_11; private static final ButtonPanel.Control CtrlZoom = ButtonPanel.Control.CUSTOM_12; + private static final ButtonPanel.Control CtrlOpenViewer = ButtonPanel.Control.CUSTOM_13; // List of potential sequences to display when loading a new creature private static final List InitialSequences = new ArrayList() {{ @@ -277,7 +279,12 @@ protected boolean windowClosing(boolean forced) throws Exception @Override public void actionPerformed(ActionEvent event) { - if (buttonControlPanel.getControlByType(CtrlSequenceList) == event.getSource()) { + if (timer == event.getSource()) { + curFrame += 1; + curFrame %= getController().cycleFrameCount(); + showFrame(); + } + else if (buttonControlPanel.getControlByType(CtrlSequenceList) == event.getSource()) { JComboBox cb = (JComboBox)buttonControlPanel.getControlByType(CtrlSequenceList); SpriteDecoder.Sequence seq = (SpriteDecoder.Sequence)(cb).getSelectedItem(); try { @@ -369,10 +376,15 @@ else if (buttonControlPanel.getControlByType(CtrlPlay) == event.getSource()) { } updateControls(); } - else if (timer == event.getSource()) { - curFrame += 1; - curFrame %= getController().cycleFrameCount(); - showFrame(); + else if (buttonControlPanel.getControlByType(CtrlOpenViewer) == event.getSource()) { + CreatureViewer cv = ChildFrame.show(CreatureViewer.class, () -> new CreatureViewer(getCre())); + if (cv != null) { + if (getCre() != cv.getCreResource()) { + cv.setCreResource(getCre()); + } + cv.toFront(); + } + close(); } } @@ -433,6 +445,10 @@ private void init() throws Exception cbShowSpace.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0)); cbShowSpace.addActionListener(this); + JButton bOpenViewer = new JButton("Open in viewer", Icons.getIcon(Icons.ICON_CRE_VIEWER_24)); + bOpenViewer.setToolTipText("Open in Creature Animation Viewer"); + bOpenViewer.addActionListener(this); + buttonControlPanel.addControl(lCycle, CtrlCycleLabel); buttonControlPanel.addControl(bPrevCycle, CtrlPrevCycle); buttonControlPanel.addControl(bNextCycle, CtrlNextCycle); @@ -445,6 +461,7 @@ private void init() throws Exception buttonControlPanel.addControl(cbZoom, CtrlZoom); buttonControlPanel.addControl(cbShowCircle, CtrlShowCircle); buttonControlPanel.addControl(cbShowSpace, CtrlShowSpace); + buttonControlPanel.addControl(bOpenViewer, CtrlOpenViewer); setLayout(new GridBagLayout()); GridBagConstraints c; diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index 8e885886e..092efe93b 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -356,7 +356,7 @@ public boolean contains(Profile.Game game, int value) } boolean retVal = false; - // TODO: improve Infinity Animations check + // Infinity Animations sprite takes precedence over regular sprite AnimationType type = containsInfinityAnimations(value); if (type != null && type != this) { return retVal; diff --git a/src/org/infinity/resource/cre/viewer/AttributesPanel.java b/src/org/infinity/resource/cre/viewer/AttributesPanel.java new file mode 100644 index 000000000..3400a5c17 --- /dev/null +++ b/src/org/infinity/resource/cre/viewer/AttributesPanel.java @@ -0,0 +1,71 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.viewer; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Font; +import java.util.Objects; + +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.border.EtchedBorder; + +import org.infinity.resource.cre.decoder.SpriteDecoder; + +/** + * This panel provides a table view of creature animation attributes. + */ +public class AttributesPanel extends JPanel +{ + private final CreatureViewer viewer; + + private JTable tableAnimation; + private DecoderAttributesTableModel tableModel; + + public AttributesPanel(CreatureViewer viewer) + { + super(); + this.viewer = Objects.requireNonNull(viewer); + init(); + } + + /** Returns the associated {@code CreatureViewer} instance. */ + public CreatureViewer getViewer() { return viewer; } + + /** Discards and reloads the current settings and attributes list. */ + public void reset() + { + SpriteDecoder decoder = getViewer().getDecoder(); + tableModel.setDecoder(decoder); + } + + private void init() + { + JLabel label = new JLabel("Animation attributes:"); + tableModel = new DecoderAttributesTableModel(); + tableAnimation = new JTable(tableModel, + new DecoderAttributesTableModel.AttributesColumnModel(), + new DecoderAttributesTableModel.AttributesListSelectionModel()); + Dimension dim = new Dimension(0, 50); + for (int i = 0; i < tableAnimation.getColumnModel().getColumnCount(); i++) { + dim.width += tableAnimation.getColumnModel().getColumn(i).getPreferredWidth(); + } + tableAnimation.setPreferredScrollableViewportSize(dim); + tableAnimation.getTableHeader().setFont(tableAnimation.getTableHeader().getFont().deriveFont(Font.BOLD)); + tableAnimation.getTableHeader().setDefaultRenderer(new DecoderAttributesTableModel.AttributesHeaderRenderer(tableAnimation)); + tableAnimation.getTableHeader().setReorderingAllowed(false); + tableAnimation.setRowHeight(tableAnimation.getRowHeight() * 3 / 2); + tableAnimation.setIntercellSpacing(new Dimension(4, 0)); + JScrollPane scrollTable = new JScrollPane(tableAnimation); + scrollTable.setBorder(new EtchedBorder()); + + setLayout(new BorderLayout()); + add(label, BorderLayout.PAGE_START); + add(scrollTable, BorderLayout.CENTER); + } +} diff --git a/src/org/infinity/resource/cre/viewer/ColorSelectionModel.java b/src/org/infinity/resource/cre/viewer/ColorSelectionModel.java new file mode 100644 index 000000000..58a7dffa7 --- /dev/null +++ b/src/org/infinity/resource/cre/viewer/ColorSelectionModel.java @@ -0,0 +1,419 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.viewer; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Component; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.RenderingHints; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.stream.IntStream; + +import javax.swing.AbstractListModel; +import javax.swing.ComboBoxModel; +import javax.swing.DefaultListCellRenderer; +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.UIManager; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; + +import org.infinity.resource.Profile; +import org.infinity.resource.ResourceFactory; +import org.infinity.resource.graphics.GraphicsResource; +import org.infinity.resource.key.ResourceEntry; +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; + +/** + * {@code ComboBoxModel} for the color selection combo box used in the Creature Animation Viewer. + */ +public class ColorSelectionModel extends AbstractListModel +implements ComboBoxModel +{ + private static final HashMap randomColors = new HashMap<>(); + + private final List colorList = new ArrayList<>(256); + private final ColorCellRenderer renderer = new ColorCellRenderer(); + + private Object selectedItem; + private ResourceEntry colorEntry; + private IdsMap colorMap; + + public ColorSelectionModel() + { + this(null); + } + + public ColorSelectionModel(ResourceEntry bmpResource) + { + super(); + this.colorMap = ResourceFactory.resourceExists("CLOWNCLR.IDS") ? IdsMapCache.get("CLOWNCLR.IDS") : null; + setColorRangesEntry(bmpResource); + } + + /** Returns the {@code ListCellRenderer} instance associated with the list model. */ + public ColorCellRenderer getRenderer() { return renderer; } + + /** + * Returns the {@code ResourceEntry} instance of the BMP resource containing color ranges. + * Returns {@code null} if no BMP resource is available. + */ + public ResourceEntry getColorRangesEntry() { return colorEntry; } + + public void setColorRangesEntry(ResourceEntry bmpResource) + { + if (bmpResource == null) { + if (Profile.getGame() == Profile.Game.PST || Profile.getGame() == Profile.Game.PSTEE) { + bmpResource = ResourceFactory.getResourceEntry("PAL32.BMP"); + } else { + bmpResource = ResourceFactory.getResourceEntry("RANGES12.BMP"); + if (bmpResource == null) { + bmpResource = ResourceFactory.getResourceEntry("MPALETTE.BMP"); + } + } + } + + if ((colorEntry != null && !colorEntry.equals(bmpResource)) || + (colorEntry == null && bmpResource != null)) { + colorEntry = bmpResource; + init(); + } + } + + public void reload() + { + initRandomColors(true); + init(); + } + + /** + * Returns the index-position of the specified object in the list. + * @param anItem a {@code ColorEntry} object or {@code Number} object. + * @return an int representing the index position, where 0 is the first position. Returns -1 + * if the item could not be found in the list. + */ + public int getIndexOf(Object anItem) + { + if (anItem instanceof ColorEntry) { + return colorList.indexOf(anItem); + } else if (anItem instanceof Number) { + final int colIdx = ((Number)anItem).intValue(); + return IntStream + .range(0, colorList.size()) + .filter(i -> colorList.get(i).getIndex() == colIdx) + .findAny() + .orElse(-1); + } + return -1; + } + + /** Empties the list. */ + public void removeAllElements() + { + if (!colorList.isEmpty()) { + int oldSize = colorList.size(); + colorList.clear(); + selectedItem = null; + if (oldSize > 0) { + fireIntervalRemoved(this, 0, oldSize - 1); + } + } else { + selectedItem = null; + } + } + +//--------------------- Begin Interface ListModel --------------------- + + @Override + public int getSize() + { + return colorList.size(); + } + + @Override + public ColorEntry getElementAt(int index) + { + if (index >= 0 && index < colorList.size()) { + return colorList.get(index); + } else { + return null; + } + } + +//--------------------- End Interface ListModel --------------------- + +//--------------------- Begin Interface ComboBoxModel --------------------- + + @Override + public void setSelectedItem(Object anItem) + { + if ((selectedItem != null && !selectedItem.equals(anItem)) || + selectedItem == null && anItem != null) { + selectedItem = anItem; + fireContentsChanged(this, -1, -1); + } + } + + @Override + public Object getSelectedItem() + { + return selectedItem; + } + +//--------------------- End Interface ComboBoxModel --------------------- + + private void init() + { + removeAllElements(); + + initRandomColors(false); + + BufferedImage image = null; + try {image = new GraphicsResource(getColorRangesEntry()).getImage(); } catch (Exception e) {} + + int max = (image != null) ? image.getHeight() - 1 : 0; + if (!randomColors.isEmpty()) { + max = Math.max(max, Collections.max(randomColors.keySet())); + } + + for (int i = 0; i <= max; i++) { + String name = randomColors.get(Integer.valueOf(i)); + if (name != null) { + colorList.add(new ColorEntry(i, name, true)); + } else { + name = ""; + if (colorMap != null) { + IdsMapEntry idsEntry = colorMap.get(i); + name = (idsEntry != null) ? Misc.prettifySymbol(idsEntry.getSymbol()) : ""; + } + colorList.add(new ColorEntry(image, i, name)); + } + } + + if (!colorList.isEmpty()) { + fireIntervalAdded(this, 0, colorList.size() - 1); + } + + setSelectedItem(getElementAt(0)); + } + + private static synchronized void initRandomColors(boolean forced) + { + if (forced) { + randomColors.clear(); + } + + if (randomColors.isEmpty()) { + ResourceEntry randomEntry = ResourceFactory.getResourceEntry("RANDCOLR.2DA"); + + // collecting valid random color indices + if (randomEntry != null) { + Table2da table = Table2daCache.get(randomEntry); + for (int col = 1, count = table.getColCount(); col < count; col++) { + int index = Misc.toNumber(table.get(0, col), -1); + String name = Misc.prettifySymbol(table.getHeader(col)); + if (index >= 0 && index < 256) { + randomColors.put(index, name); + } + } + } + } + } + +//-------------------------- INNER CLASSES -------------------------- + + public static class ColorCellRenderer extends DefaultListCellRenderer + { + private static final Border DEFAULT_NO_FOCUS_BORDER = new EmptyBorder(1, 1, 1, 1); + + public ColorCellRenderer() + { + super(); + } + + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, + boolean isSelected, boolean cellHasFocus) + { + if (value == null || !(value instanceof ColorEntry)) { + return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + } + + ColorEntry entry = (ColorEntry)value; + if (isSelected) { + setBackground(list.getSelectionBackground()); + setForeground(list.getSelectionForeground()); + } else { + setBackground(list.getBackground()); + setForeground(list.getForeground()); + } + + setText(entry.toString()); + + if (entry.getImage() != null) { + setIcon(new ImageIcon(entry.getImage())); + setIconTextGap(8); + } else { + setIcon(null); + } + + setEnabled(list.isEnabled()); + setFont(list.getFont()); + + Border border = null; + if (cellHasFocus) { + if (isSelected) { + border = UIManager.getBorder("List.focusSelectedCellHighlightBorder"); + } + if (border == null) { + border = UIManager.getBorder("List.focusCellHighlightBorder"); + } + } else { + border = UIManager.getBorder("List.cellNoFocusBorder"); + if (border == null) { + border = DEFAULT_NO_FOCUS_BORDER; + } + } + setBorder(border); + + return this; + } + } + + public static class ColorEntry implements Comparable + { + public enum State { + /** Indicates a fixed color entry. */ + FIXED, + /** Indicates a randomized color entry. */ + RANDOM, + /** Indicates a non-existing color entry. */ + INVALID, + } + + private static final int DEFAULT_IMAGE_WIDTH = 128; // total width of color range + private static final int DEFAULT_IMAGE_HEIGHT = 24; + + private static final Color COLOR_INVALID = new Color(0xe0e0e0); + private static final Color COLOR_RANDOM = Color.LIGHT_GRAY; + + private static final String LABEL_INVALID = "(Invalid)"; + private static final String LABEL_RANDOM = "(Random)"; + + private final int index; + private final Image image; + private String name; + private State state; + + /** Creates a fixed color entry. */ + public ColorEntry(BufferedImage ranges, int index, String name) + { + this.index = Math.max(0, Math.min(255, index)); + this.name = (name != null) ? name.trim() : ""; + this.image = createFixedColor(ranges, index); + } + + /** + * Creates a random color or invalid color entry. + * @param name + * @param isRandom + */ + public ColorEntry(int index, String name, boolean isRandom) + { + this.index = Math.max(0, Math.min(255, index)); + this.name = (name != null) ? name.trim() : ""; + this.image = isRandom ? createRandomColor() : createInvalidColor(); + } + + public int getIndex() { return index; } + public Image getImage() { return image; } + public String getName() { return name; } + public State getState() { return state; } + + @Override + public int compareTo(ColorEntry o) + { + return getIndex() - o.getIndex(); + } + + @Override + public String toString() + { + if (getName().isEmpty()) { + return Integer.toString(getIndex()); + } else { + return getIndex() + " (" + getName() + ")"; + } + } + + private Image createFixedColor(BufferedImage image, int index) + { + if (image == null || index < 0 || index >= image.getHeight()) { + return createInvalidColor(); + } + + state = State.FIXED; + BufferedImage range = new BufferedImage(DEFAULT_IMAGE_WIDTH, DEFAULT_IMAGE_HEIGHT, image.getType()); + Graphics2D g = range.createGraphics(); + try { + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); + g.drawImage(image, 0, 0, range.getWidth(), range.getHeight(), 0, index, image.getWidth(), index + 1, null); + g.setColor(Color.BLACK); + g.setStroke(new BasicStroke(1.0f)); + g.drawRect(0, 0, range.getWidth() - 1, range.getHeight() - 1); + } finally { + g.dispose(); + } + return range; + } + + private Image createRandomColor() { return createCustomColor(COLOR_RANDOM, LABEL_RANDOM); } + + private Image createInvalidColor() { return createCustomColor(COLOR_INVALID, LABEL_INVALID); } + + private Image createCustomColor(Color color, String label) + { + state = (color != null) ? State.RANDOM : State.INVALID; + Color col = (color != null) ? color : COLOR_INVALID; + String text = (label != null) ? label : LABEL_INVALID; + BufferedImage range = new BufferedImage(DEFAULT_IMAGE_WIDTH, DEFAULT_IMAGE_HEIGHT, BufferedImage.TYPE_INT_RGB); + Graphics2D g = range.createGraphics(); + try { + g.setColor(col); + g.fillRect(0, 0, range.getWidth(), range.getHeight()); + g.setFont(new JLabel().getFont().deriveFont(12.0f)); + g.setColor(Color.BLACK); + g.setStroke(new BasicStroke(1.0f)); + g.drawRect(0, 0, range.getWidth() - 1, range.getHeight() - 1); + if (!text.isEmpty()) { + g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP); + FontMetrics fm = g.getFontMetrics(); + Rectangle2D rect = fm.getStringBounds(label, g); + g.drawString(text, + (float)(range.getWidth() - rect.getWidth()) / 2.0f, + (float)(range.getHeight() - rect.getY()) / 2.0f); + } + } finally { + g.dispose(); + } + return range; + } + } + +} diff --git a/src/org/infinity/resource/cre/viewer/CreUtils.java b/src/org/infinity/resource/cre/viewer/CreUtils.java new file mode 100644 index 000000000..d75f0c2ef --- /dev/null +++ b/src/org/infinity/resource/cre/viewer/CreUtils.java @@ -0,0 +1,557 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.viewer; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +import org.infinity.datatype.IsNumeric; +import org.infinity.datatype.IsReference; +import org.infinity.datatype.IsTextual; +import org.infinity.datatype.Readable; +import org.infinity.resource.AddRemovable; +import org.infinity.resource.Profile; +import org.infinity.resource.ResourceFactory; +import org.infinity.resource.StructEntry; +import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.Item; +import org.infinity.resource.cre.decoder.internal.ItemInfo; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.Misc; + +/** + * Collection of methods for creating and manipulating CRE resources. + */ +public class CreUtils +{ + private static ByteBuffer BUFFER; + + /** Returns the animation id for the specified CRE resource. */ + public static int getAnimation(CreResource cre) + { + return ((IsNumeric)Objects.requireNonNull(cre).getAttribute(CreResource.CRE_ANIMATION)).getValue(); + } + + /** Sets a new animation id for the specified CRE resource. */ + public static void setAnimation(CreResource cre, int animId) throws IllegalArgumentException + { + if (animId >= 0 && animId <= 0xffff) { + int oldValue = getAnimation(cre); + if (oldValue != animId) { + setFieldValue(Objects.requireNonNull(cre).getAttribute(CreResource.CRE_ANIMATION), null, animId); + } + } else { + throw new IllegalArgumentException(String.format("Invalid animation id specified: 0x%04X", animId)); + } + } + + /** Returns the allegiance value for the specified CRE resource. */ + public static int getAllegiance(CreResource cre) + { + return ((IsNumeric)Objects.requireNonNull(cre).getAttribute(CreResource.CRE_ALLEGIANCE)).getValue(); + } + + /** Sets a new allegiance value for the specified CRE resource. */ + public static void setAllegiance(CreResource cre, int ea) throws IllegalArgumentException + { + if (ea >= -128 && ea <= 255) { + ea &= 0xff; + int oldValue = getAllegiance(cre); + if (oldValue != ea) { + setFieldValue(Objects.requireNonNull(cre).getAttribute(CreResource.CRE_ALLEGIANCE), null, ea); + } + } else { + throw new IllegalArgumentException("Invalid allegiance value specified: " + ea); + } + } + + /** Returns whether the panic status is enabled for the specified CRE resource. */ + public static boolean getStatusPanic(CreResource cre) + { + int status = ((IsNumeric)Objects.requireNonNull(cre).getAttribute(CreResource.CRE_STATUS)).getValue(); + return (status & (1 << 2)) != 0; + } + + /** Sets the panic status for the specified CRE resource. */ + public static void setStatusPanic(CreResource cre, boolean b) + { + int oldStatus = ((IsNumeric)Objects.requireNonNull(cre).getAttribute(CreResource.CRE_STATUS)).getValue(); + int newStatus = oldStatus; + if (b) { + newStatus |= 1 << 2; + } else { + newStatus &= ~(1 << 2); + } + if (newStatus != oldStatus) { + setFieldValue(cre.getAttribute(CreResource.CRE_STATUS), null, newStatus); + } + } + + /** Returns the color value at the specified location index for the specified CRE resource. */ + public static int getColor(CreResource cre, int locationIdx) throws IllegalArgumentException + { + if (locationIdx >= 0 && locationIdx < 7) { + StructEntry se = getColorField(cre, locationIdx); + if (se instanceof IsNumeric) { + return ((IsNumeric)se).getValue(); + } else { + throw new IllegalArgumentException("Color location at index " + locationIdx + " does not exist"); + } + } else { + throw new IllegalArgumentException("Color location index out of bounds: " + locationIdx); + } + } + + /** Sets a new color value to the field specified by the location index. */ + public static void setColor(CreResource cre, int locationIdx, int value) throws IllegalArgumentException + { + if (locationIdx >= 0 && locationIdx < 7) { + StructEntry se = getColorField(cre, locationIdx); + if (se instanceof IsNumeric) { + setFieldValue(se, null, value); + } + } else { + throw new IllegalArgumentException("Invalid color location index specified: " + locationIdx); + } + } + + /** Returns the item equipped in the helmet slot of the specified CRE resource. Returns {@code null} if no item is equipped. */ + public static ItemInfo getEquipmentHelmet(CreResource cre) + { + Objects.requireNonNull(cre); + ItemInfo retVal = null; + boolean isPST = (Profile.getGame() == Profile.Game.PSTEE) || + ((IsTextual)cre.getAttribute(CreResource.COMMON_VERSION)).getText().equalsIgnoreCase("V1.2"); + String fieldName = isPST ? CreResource.CRE_ITEM_SLOT_RIGHT_EARRING : CreResource.CRE_ITEM_SLOT_HELMET; + ResourceEntry itemEntry = getEquippedItem(cre, fieldName); + if (itemEntry != null) { + try { + retVal = ItemInfo.get(itemEntry); + } catch (Exception e) { + e.printStackTrace(); + } + } + return retVal; + } + + /** + * Equips the given item in the helmet slot of the specified CRE resource. + * @param cre the CRE resource. + * @param item the {@code ItemInfo} object. Specify {@code null} to unequip the current helmet. + */ + public static void setEquipmentHelmet(CreResource cre, ItemInfo item) + { + Objects.requireNonNull(cre); + boolean isPST = (Profile.getGame() == Profile.Game.PSTEE) || + ((IsTextual)cre.getAttribute(CreResource.COMMON_VERSION)).getText().equalsIgnoreCase("V1.2"); + String fieldName = isPST ? CreResource.CRE_ITEM_SLOT_RIGHT_EARRING : CreResource.CRE_ITEM_SLOT_HELMET; + setEquippedItem(cre, item, fieldName); + } + + /** Returns the item equipped in the armor slot of the specified CRE resource. Returns {@code null} if no item is equipped. */ + public static ItemInfo getEquipmentArmor(CreResource cre) + { + Objects.requireNonNull(cre); + ItemInfo retVal = null; + boolean isPST = (Profile.getGame() == Profile.Game.PSTEE) || + ((IsTextual)cre.getAttribute(CreResource.COMMON_VERSION)).getText().equalsIgnoreCase("V1.2"); + String fieldName = isPST ? CreResource.CRE_ITEM_SLOT_CHEST : CreResource.CRE_ITEM_SLOT_ARMOR; + ResourceEntry itemEntry = getEquippedItem(cre, fieldName); + if (itemEntry != null) { + try { + retVal = ItemInfo.get(itemEntry); + } catch (Exception e) { + e.printStackTrace(); + } + } + return retVal; + } + + /** + * Equips the given item in the armor slot of the specified CRE resource. + * @param cre the CRE resource. + * @param item the {@code ItemInfo} object. Specify {@code null} to unequip the current helmet. + */ + public static void setEquipmentArmor(CreResource cre, ItemInfo item) + { + Objects.requireNonNull(cre); + boolean isPST = (Profile.getGame() == Profile.Game.PSTEE) || + ((IsTextual)cre.getAttribute(CreResource.COMMON_VERSION)).getText().equalsIgnoreCase("V1.2"); + String fieldName = isPST ? CreResource.CRE_ITEM_SLOT_CHEST : CreResource.CRE_ITEM_SLOT_ARMOR; + setEquippedItem(cre, item, fieldName); + } + + /** Returns the item equipped in the shield slot of the specified CRE resource. Returns {@code null} if no item is equipped. */ + public static ItemInfo getEquipmentShield(CreResource cre) + { + Objects.requireNonNull(cre); + ItemInfo retVal = null; + String creVer = ((IsTextual)cre.getAttribute(CreResource.COMMON_VERSION)).getText(); + boolean isPST = (Profile.getGame() == Profile.Game.PSTEE) || creVer.equalsIgnoreCase("V1.2"); + boolean isIWD2 = creVer.equalsIgnoreCase("V2.2"); + String fieldName; + if (isPST) + fieldName = CreResource.CRE_ITEM_SLOT_RIGHT_TATTOO_LOWER; + else if (isIWD2) { + fieldName = String.format(CreResource.CRE_ITEM_SLOT_SHIELD_FMT, 1); + } else { + fieldName = CreResource.CRE_ITEM_SLOT_SHIELD; + } + ResourceEntry itemEntry = getEquippedItem(cre, fieldName); + if (itemEntry != null) { + try { + retVal = ItemInfo.get(itemEntry); + } catch (Exception e) { + e.printStackTrace(); + } + } + return retVal; + } + + /** + * Equips the given item in the shield slot of the specified CRE resource. + * @param cre the CRE resource. + * @param item the {@code ItemInfo} object. Specify {@code null} to unequip the current helmet. + */ + public static void setEquipmentShield(CreResource cre, ItemInfo item) + { + Objects.requireNonNull(cre); + String creVer = ((IsTextual)cre.getAttribute(CreResource.COMMON_VERSION)).getText(); + boolean isPST = (Profile.getGame() == Profile.Game.PSTEE) || creVer.equalsIgnoreCase("V1.2"); + boolean isIWD2 = creVer.equalsIgnoreCase("V2.2"); + String fieldName; + if (isPST) + fieldName = CreResource.CRE_ITEM_SLOT_RIGHT_TATTOO_LOWER; + else if (isIWD2) { + fieldName = String.format(CreResource.CRE_ITEM_SLOT_SHIELD_FMT, 1); + } else { + fieldName = CreResource.CRE_ITEM_SLOT_SHIELD; + } + setEquippedItem(cre, item, fieldName); + } + + /** Returns the item equipped in the weapon slot of the specified CRE resource. Returns {@code null} if no item is equipped. */ + public static ItemInfo getEquipmentWeapon(CreResource cre) + { + Objects.requireNonNull(cre); + ItemInfo retVal = null; + String fieldName = String.format(CreResource.CRE_ITEM_SLOT_WEAPON_FMT, 1); + ResourceEntry itemEntry = getEquippedItem(cre, fieldName); + if (itemEntry != null) { + try { + retVal = ItemInfo.get(itemEntry); + } catch (Exception e) { + e.printStackTrace(); + } + } + return retVal; + } + + /** + * Equips the given item in the weapon slot of the specified CRE resource. + * @param cre the CRE resource. + * @param item the {@code ItemInfo} object. Specify {@code null} to unequip the current helmet. + */ + public static void setEquipmentWeapon(CreResource cre, ItemInfo item) + { + Objects.requireNonNull(cre); + String fieldName = String.format(CreResource.CRE_ITEM_SLOT_WEAPON_FMT, 1); + setEquippedItem(cre, item, fieldName); + // selecting first weapon slot + setFieldValue(cre.getAttribute(CreResource.CRE_SELECTED_WEAPON_SLOT), null, 0); + } + + /** + * Helper method: Equips the specified item to the item slot of given name. + * A previously equipped item will be overwritten. Otherwise a new item will be added and item slot indices are updated. + * @param cre the CRE resource + * @param item the item to equip. Specify {@code null} to unequip current item without equipping a new one. + * @param slotName the item slot name + */ + private static void setEquippedItem(CreResource cre, ItemInfo item, String slotName) + { + StructEntry se = Objects.requireNonNull(cre).getAttribute(slotName); + if (se == null) { + return; + } + + if (item != null && item.getResourceEntry() != null) { + // equip new item + addEquippedItem(cre, item, slotName); + } else { + // unequip current item + removeEquippedItem(cre, slotName); + } + } + + /** + * Helper method: Updates an existing equipped item in the specified slot. + * @param cre the CRE resource + * @param item the item to equip + * @param slotName the item slot name + * @return {@code true} if item slot could be updated. {@code false} otherwise. + */ + private static boolean updateEquippedItem(CreResource cre, ItemInfo item, String slotName) + { + boolean retVal = false; + if (item == null) { + return retVal; + } + + StructEntry se = Objects.requireNonNull(cre).getAttribute(slotName); + if (se == null) { + return retVal; + } + + int idx = ((IsNumeric)se).getValue(); + int numItems = ((IsNumeric)cre.getAttribute(CreResource.CRE_NUM_ITEMS)).getValue(); + if (idx < 0 || idx >= numItems) { + return retVal; + } + + List itemList = cre.getFields(Item.class); + if (idx < itemList.size() && itemList.get(idx) instanceof Item) { + Item curItem = (Item)itemList.get(idx); + String resref = item.getResourceEntry().getResourceRef(); + setFieldValue(curItem.getAttribute(Item.CRE_ITEM_RESREF), null, resref, 8); + retVal = true; + } + + return retVal; + } + + /** + * Helper method: Adds a new equipped item and assigns it to the specified slot. + * @param cre the CRE resource + * @param item the item to equip + * @param slotName the item slot name + */ + private static void addEquippedItem(CreResource cre, ItemInfo item, String slotName) + { + if (item == null) { + return; + } + + StructEntry se = Objects.requireNonNull(cre).getAttribute(slotName); + if (se == null) { + return; + } + + int idx = ((IsNumeric)se).getValue(); + int numItems = ((IsNumeric)cre.getAttribute(CreResource.CRE_NUM_ITEMS)).getValue(); + if (idx >= 0 && idx < numItems) { + // Old item exists? Update! + if (updateEquippedItem(cre, item, slotName)) { + return; + } + } + + ByteBuffer buf = getByteBuffer(); + buf.putLong(0L); + buf.putLong(0L); + buf.putInt(1); // flags: identified + try { + Item newItem = new Item(cre, getByteBuffer(), 0, numItems); + String resref = item.getResourceEntry().getResourceRef(); + setFieldValue(newItem.getAttribute(Item.CRE_ITEM_RESREF), buf, resref, 8); + cre.addDatatype(newItem); + setFieldValue(cre.getAttribute(slotName), null, numItems); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Helper method: Removes the currently equipped item from the specified slot. + * @param cre the CRE resource + * @param slotName the item slot name + */ + private static void removeEquippedItem(CreResource cre, String slotName) + { + StructEntry se = cre.getAttribute(slotName); + if (se == null) { + return; + } + + int idx = ((IsNumeric)se).getValue(); + if (idx < 0) { + return; + } + + // clearing item reference + setFieldValue(se, null, -1); + int numItems = ((IsNumeric)cre.getAttribute(CreResource.CRE_NUM_ITEMS)).getValue(); + if (idx < numItems) { + List itemList = cre.getFields(Item.class); + if (idx < itemList.size() && itemList.get(idx) instanceof AddRemovable) { + // removing item structure + cre.removeDatatype((AddRemovable)itemList.get(idx), false); + // updating slot indices + updateItemSlots(cre, idx + 1, -1); + } + } + } + + /** + * Helper method: Updates item slot references greater than or equal to {@code startIndex} by {@code diff}. + * @param cre the CRE resource + * @param startIndex lower bound of item index to update. + * @param diff this value is added to the item index. + */ + private static void updateItemSlots(CreResource cre, int startIndex, int diff) + { + int ofsItemSlots = Objects.requireNonNull(cre).getExtraOffset() + + ((IsNumeric)cre.getAttribute(CreResource.CRE_OFFSET_ITEM_SLOTS)).getValue(); + int numItemSlots = getItemSlotCount(cre, false); + + for (int i = 0; i < numItemSlots; i++) { + StructEntry se = cre.getAttribute(ofsItemSlots + i * 2); + int slotIdx = ((IsNumeric)se).getValue(); + if (slotIdx >= startIndex) { + slotIdx += diff; + setFieldValue(se, null, slotIdx); + } + } + } + + /** + * Helper method: Determines the number of available item slots. + * @param cre the CRE resource. + * @param includeSelectionSlots whether selected weapon slot and weapon ability should be included in the count. + * @return number of available item slots. + */ + private static int getItemSlotCount(CreResource cre, boolean includeSelectionSlots) + { + int retVal = 0; + if (cre != null) { + String ver = ((IsTextual)cre.getAttribute(CreResource.COMMON_VERSION)).getText().toUpperCase(); + switch (ver) { + case "V1.2": + retVal = 48; + break; + case "V2.2": + retVal = 52; + break; + default: + if (Profile.getGame() == Profile.Game.PSTEE) { + int numSlots = ((IsNumeric)cre.getAttribute(CreResource.CRE_NUM_ITEM_SLOTS)).getValue(); + if (numSlots > 0) { + retVal = numSlots; + } + } + if (retVal == 0) { + retVal = 40; + } + } + } + + if (!includeSelectionSlots) { + retVal -= 2; + } + + return retVal; + } + + /** Helper method: Returns the ITM resource entry referenced by the specified item slot. */ + private static ResourceEntry getEquippedItem(CreResource cre, String slotName) + { + ResourceEntry retVal = null; + int idx = ((IsNumeric)Objects.requireNonNull(cre).getAttribute(Objects.requireNonNull(slotName))).getValue(); + if (idx >= 0) { + int numItems = ((IsNumeric)cre.getAttribute(CreResource.CRE_NUM_ITEMS)).getValue(); + if (idx < numItems) { + List itemList = cre.getFields(Item.class); + if (idx < itemList.size() && itemList.get(idx) instanceof Item) { + Item item = (Item)itemList.get(idx); + retVal = ResourceFactory.getResourceEntry(((IsReference)item.getAttribute(Item.CRE_ITEM_RESREF)).getResourceName()); + } + } + } + return retVal; + } + + + /** Helper method: Returns the specified color field. */ + private static StructEntry getColorField(CreResource cre, int locationIdx) + { + StructEntry retVal = null; + if (locationIdx >= 0 && locationIdx < 7) { + boolean isV12 = ((IsTextual)Objects.requireNonNull(cre).getAttribute(CreResource.COMMON_VERSION)).getText().equalsIgnoreCase("V1.2"); + int ofsBase = isV12 ? 0x2e4 : 0x2c; + int size = isV12 ? 2 : 1; + retVal = cre.getAttribute(ofsBase + locationIdx * size); + } + return retVal; + } + + /** + * Attempts to write the specified numeric value into the given field. + * @param field the {@code Readable} instance to update. + * @param buf the {@code ByteBuffer} object to use as temporary storage. + * @param value the value to set. + * @return number of bytes written. + */ + private static int setFieldValue(Readable field, ByteBuffer buf, int value) + { + int retVal = 0; + if (field != null) { + if (buf == null) { + buf = getByteBuffer(); + } + int pos = buf.position(); + buf.putInt(value); + try { + retVal = field.read(buf, pos); + } catch (Exception e) { + e.printStackTrace(); + } + } + return retVal; + } + + /** + * Attempts to write the specified textual value into the given field. + * @param field the {@code Readable} instance to update. + * @param buf the {@code ByteBuffer} object to use as temporary storage. + * @param value the value to set. + * @param maxLen the maximum number of characters to write. String is truncated or padded to fit the required length. + * @return number of bytes written. + */ + private static int setFieldValue(Readable field, ByteBuffer buf, String value, int maxLen) + { + int retVal = 0; + if (field != null) { + maxLen = Math.max(0, Math.min(getByteBuffer().limit(), maxLen)); + byte[] arr = Arrays.copyOfRange(value.getBytes(Misc.CHARSET_DEFAULT), 0, maxLen); + if (buf == null) { + buf = getByteBuffer(); + } + int pos = buf.position(); + buf.put(arr); + try { + retVal = field.read(buf, pos); + } catch (Exception e) { + e.printStackTrace(); + } + } + return retVal; + } + + /** Returns a {@code ByteBuffer} instance for temporary storage with little endian byte order and position set to 0. */ + private static ByteBuffer getByteBuffer() + { + if (BUFFER == null) { + BUFFER = ByteBuffer.allocate(1024); + BUFFER.order(ByteOrder.LITTLE_ENDIAN); + } + BUFFER.position(0); + return BUFFER; + } + + private CreUtils() { } +} diff --git a/src/org/infinity/resource/cre/viewer/CreatureAllegianceModel.java b/src/org/infinity/resource/cre/viewer/CreatureAllegianceModel.java new file mode 100644 index 000000000..22df28569 --- /dev/null +++ b/src/org/infinity/resource/cre/viewer/CreatureAllegianceModel.java @@ -0,0 +1,173 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.viewer; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.stream.IntStream; + +import javax.swing.AbstractListModel; +import javax.swing.ComboBoxModel; + +import org.infinity.util.IdsMap; +import org.infinity.util.IdsMapCache; +import org.infinity.util.IdsMapEntry; + +/** + * {@code ComboBoxModel} for the creature allegiance combo box used in the Creature Animation Viewer. + */ +public class CreatureAllegianceModel extends AbstractListModel + implements ComboBoxModel +{ + private final List eaList = new ArrayList<>(); + + private Object selectedItem; + + public CreatureAllegianceModel() + { + super(); + init(); + } + + public void reload() + { + init(); + } + + /** + * Returns the index-position of the specified object in the list. + * @param anItem a {@code AllegianceEntry} object, {@code Number} object or {@code String} specifying an allegiance name. + * @return an int representing the index position, where 0 is the first position. Returns -1 + * if the item could not be found in the list. + */ + public int getIndexOf(Object anItem) + { + if (anItem instanceof AllegianceEntry) { + return eaList.indexOf(anItem); + } else if (anItem instanceof Number) { + final int eaValue = ((Number)anItem).intValue(); + return IntStream + .range(0, eaList.size()) + .filter(i -> eaList.get(i).value == eaValue) + .findAny() + .orElse(-1); + } else if (anItem != null) { + final String eaName = anItem.toString().trim(); + return IntStream + .range(0, eaList.size()) + .filter(i -> eaName.equalsIgnoreCase(eaList.get(i).name)) + .findAny() + .orElse(-1); + } + return -1; + } + + /** Empties the list. */ + public void removeAllElements() + { + if (!eaList.isEmpty()) { + int oldSize = eaList.size(); + eaList.clear(); + selectedItem = null; + if (oldSize > 0) { + fireIntervalRemoved(this, 0, oldSize - 1); + } + } else { + selectedItem = null; + } + } + +//--------------------- Begin Interface ListModel --------------------- + + @Override + public int getSize() + { + return eaList.size(); + } + + @Override + public AllegianceEntry getElementAt(int index) + { + if (index >= 0 && index < eaList.size()) { + return eaList.get(index); + } else { + return null; + } + } + +//--------------------- End Interface ListModel --------------------- + +//--------------------- Begin Interface ComboBoxModel --------------------- + + @Override + public void setSelectedItem(Object anItem) + { + if ((selectedItem != null && !selectedItem.equals(anItem)) || + selectedItem == null && anItem != null) { + selectedItem = anItem; + fireContentsChanged(this, -1, -1); + } + } + + @Override + public Object getSelectedItem() + { + return selectedItem; + } + +//--------------------- End Interface ComboBoxModel --------------------- + + private void init() + { + removeAllElements(); + + IdsMap map = IdsMapCache.get("EA.IDS"); + if (map != null) { + for (Iterator iter = map.getAllValues().iterator(); iter.hasNext(); ) { + IdsMapEntry entry = iter.next(); + eaList.add(new AllegianceEntry(entry)); + } + Collections.sort(eaList); + } + if (!eaList.isEmpty()) { + fireIntervalAdded(this, 0, eaList.size() - 1); + } + + setSelectedItem(getElementAt(0)); + } + +//-------------------------- INNER CLASSES -------------------------- + + public static class AllegianceEntry implements Comparable + { + private final int value; + private final String name; + + public AllegianceEntry(IdsMapEntry eaEntry) + { + this.value = (int)Objects.requireNonNull(eaEntry).getID(); + this.name = eaEntry.getSymbol(); + } + + public int getValue() { return value; } + + public String getName() { return name; } + + @Override + public String toString() + { + return value + " - " + name; + } + + @Override + public int compareTo(AllegianceEntry o) + { + return value - o.value; + } + } +} diff --git a/src/org/infinity/resource/cre/viewer/CreatureAnimationModel.java b/src/org/infinity/resource/cre/viewer/CreatureAnimationModel.java new file mode 100644 index 000000000..53046823e --- /dev/null +++ b/src/org/infinity/resource/cre/viewer/CreatureAnimationModel.java @@ -0,0 +1,321 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.viewer; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.stream.IntStream; + +import javax.swing.AbstractListModel; +import javax.swing.MutableComboBoxModel; + +import org.infinity.util.IdsMap; +import org.infinity.util.IdsMapCache; +import org.infinity.util.IdsMapEntry; + +/** + * {@code ComboBoxModel} for the creature animation combo box used in the Creature Animation Viewer. + */ +public class CreatureAnimationModel extends AbstractListModel + implements MutableComboBoxModel +{ + private final List animationList = new ArrayList<>(65536); + + private Object selectedItem; + + public CreatureAnimationModel() + { + super(); + init(); + } + + public void reload() + { + init(); + } + + /** + * Returns the numeric value associated with the selected item. + * It returns the animation id for a regular entry from the model. + * It attempts to detect and return a manually entered number. Use prefix "0x" or suffix "h" to indicate that + * number is entered in hexadecimal notation. + * @return the numeric value of the selected item. Returns -1 if numeric value could not be determined. + */ + public int getSelectedValue() + { + return parseValue(getSelectedItem()); + } + + /** + * Returns the index-position of the specified object in the list. + * @param anItem a {@code AnimateEntry} object, {@code Number} object or {@code String} object. + * @return an int representing the index position, where 0 is the first position. Returns -1 + * if the item could not be found in the list. + */ + public int getIndexOf(Object anItem) + { + if (anItem instanceof AnimateEntry) { + return animationList.indexOf(anItem); + } else { + int idx = -1; + if (anItem instanceof Number) { + idx = ((Number)anItem).intValue(); + } else { + idx = parseValue(anItem); + } + if (idx != -1) { + final int aidx = idx; + return IntStream + .range(0, animationList.size()) + .filter(i -> aidx == animationList.get(i).getValue()) + .findAny() + .orElse(-1); + } + } + + if (anItem != null) { + final String aname = anItem.toString().toUpperCase(Locale.ENGLISH); + return IntStream + .range(0, animationList.size()) + .filter(i -> animationList.get(i).getSymbol().toUpperCase(Locale.ENGLISH).startsWith(aname)) + .findAny() + .orElse(-1); + } + return -1; + } + + /** Empties the list. */ + public void removeAllElements() + { + if (!animationList.isEmpty()) { + int oldSize = animationList.size(); + animationList.clear(); + selectedItem = null; + if (oldSize > 0) { + fireIntervalRemoved(this, 0, oldSize - 1); + } + } else { + selectedItem = null; + } + } + +//--------------------- Begin Interface ListModel --------------------- + + @Override + public int getSize() + { + return animationList.size(); + } + + + @Override + public AnimateEntry getElementAt(int index) + { + if (index >= 0 && index < animationList.size()) { + return animationList.get(index); + } else { + return null; + } + } + +//--------------------- End Interface ListModel --------------------- + +//--------------------- Begin Interface ComboBoxModel --------------------- + + @Override + public void setSelectedItem(Object anItem) + { + if ((selectedItem != null && !selectedItem.equals(anItem)) || + selectedItem == null && anItem != null) { + selectedItem = anItem; + fireContentsChanged(this, -1, -1); + } + } + + + @Override + public Object getSelectedItem() + { + return selectedItem; + } + +//--------------------- End Interface ComboBoxModel --------------------- + +//--------------------- Begin Interface MutableComboBoxModel --------------------- + + @Override + public void addElement(AnimateEntry item) + { + animationList.add(Objects.requireNonNull(item)); + fireIntervalAdded(this, animationList.size() - 1, animationList.size() - 1); + if (animationList.size() == 1 && selectedItem == null) { + setSelectedItem(item); + } + } + + + @Override + public void removeElement(Object obj) + { + int index = animationList.indexOf(obj); + if (index != -1) { + removeElementAt(index); + } + } + + + @Override + public void insertElementAt(AnimateEntry item, int index) + { + animationList.add(index, Objects.requireNonNull(item)); + fireIntervalAdded(this, index, index); + } + + + @Override + public void removeElementAt(int index) + { + if (getElementAt(index) == selectedItem) { + if (index == 0) { + setSelectedItem(getSize() == 1 ? null : getElementAt(index + 1)); + } else { + setSelectedItem(getElementAt(index - 1)); + } + } + animationList.remove(index); + fireIntervalRemoved(this, index, index); + } + +//--------------------- End Interface MutableComboBoxModel --------------------- + + /** + * Helper method: Attempts to convert the specified argument into a meaning numeric value. + * An {@code AnimateEntry} object returns the associted animation id. + * A string is parsed and returns as decimal or hexadecimal number. + * Returns -1 if argument could not be converted. + */ + protected int parseValue(Object o) + { + int retVal = -1; + if (o instanceof AnimateEntry) { + retVal = ((AnimateEntry)o).getValue(); + } else if (o != null) { + String s = o.toString().trim().toLowerCase().replaceAll("\\s+", ""); + try { + // preserve sign + int factor = 1; + if (s.startsWith("-")) { + factor = -1; + s = s.substring(1); + } + + // determine base + int base = 10; + if (s.startsWith("0x")) { + base = 16; + s = s.substring(2); + } + if (s.endsWith("h")) { + base = 16; + s = s.substring(0, s.length() - 1); + } + + retVal = Integer.parseInt(s, base) * factor; + } catch (Exception e) { + } + } + return retVal; + } + + private void init() + { + removeAllElements(); + + IdsMap map = IdsMapCache.get("ANIMATE.IDS"); + if (map != null) { + for (Iterator iter = map.getAllValues().iterator(); iter.hasNext(); ) { + IdsMapEntry entry = iter.next(); + animationList.add(new AnimateEntry((int)entry.getID(), entry.getSymbol())); + } + } + Collections.sort(animationList); + if (!animationList.isEmpty()) { + fireIntervalAdded(this, 0, animationList.size() - 1); + } + + setSelectedItem(getElementAt(0)); + } + +//-------------------------- INNER CLASSES -------------------------- + + public static class AnimateEntry implements Comparable + { + private final int id; + private final String name; + + public AnimateEntry(int animId, String name) + { + this.id = Math.max(0, Math.min(0xffff, animId)); + this.name = (name != null) ? name : ""; + } + + /** Returns the numeric animation id. */ + public int getValue() { return id; } + + /** Returns the symbolic animation id if available. Returns empty string otherwise. */ + public String getSymbol() + { + if (name.isEmpty()) { + String idString = Integer.toString(getValue(), 16).toUpperCase(Locale.ENGLISH); + switch (idString.length()) { + case 1: + idString = "000" + idString; + break; + case 2: + idString = "00" + idString; + break; + case 3: + idString = "0" + idString; + break; + default: + } + return "UNKNOWN_" + idString; + } else { + return name; + } + } + + @Override + public int compareTo(AnimateEntry o) + { + return id - o.id; + } + + @Override + public String toString() + { + String idString = Integer.toString(getValue(), 16).toUpperCase(Locale.ENGLISH); + switch (idString.length()) { + case 1: + idString = "0x000" + idString; + break; + case 2: + idString = "0x00" + idString; + break; + case 3: + idString = "0x0" + idString; + break; + default: + idString = "0x" + idString; + } + + return getSymbol() + " - " + idString; + } + } +} diff --git a/src/org/infinity/resource/cre/viewer/CreatureControlModel.java b/src/org/infinity/resource/cre/viewer/CreatureControlModel.java new file mode 100644 index 000000000..5912028a9 --- /dev/null +++ b/src/org/infinity/resource/cre/viewer/CreatureControlModel.java @@ -0,0 +1,600 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.viewer; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; + +import javax.swing.JToggleButton.ToggleButtonModel; + +import org.infinity.resource.Profile; +import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.SpriteDecoder; +import org.infinity.resource.cre.decoder.internal.CreatureInfo; +import org.infinity.resource.cre.decoder.internal.ItemInfo; +import org.infinity.resource.cre.decoder.internal.ItemInfo.ItemPredicate; +import org.infinity.resource.cre.viewer.ColorSelectionModel.ColorEntry; +import org.infinity.resource.cre.viewer.CreatureAllegianceModel.AllegianceEntry; +import org.infinity.resource.cre.viewer.CreatureAnimationModel.AnimateEntry; +import org.infinity.resource.cre.viewer.CreatureSelectionModel.CreatureItem; +import org.infinity.resource.key.ResourceEntry; + +/** + * This model controls the relationships between creature controls and provides access to the various + * creature-specific options. + */ +public class CreatureControlModel +{ + private final List colorModels = new ArrayList<>(); + private final CreatureControlPanel panel; + + private CreatureSelectionModel modelCreSelection; + private CreatureAnimationModel modelCreAnimation; + private CreatureAllegianceModel modelCreAllegiance; + private ItemSelectionModel modelItemHelmet; + private ItemSelectionModel modelItemArmor; + private ItemSelectionModel modelItemShield; + private ItemSelectionModel modelItemWeapon; + private ToggleButtonModel modelPanic; + private int hashCreature; + private boolean canApply, canReset; + + private SpriteDecoder decoder; + + /** + * Creates a new {@code CreatureControlModel} object. + * @param panel the {@code CreatureControlPanel} instance associated with this model. + */ + public CreatureControlModel(CreatureControlPanel panel) + { + super(); + this.panel = Objects.requireNonNull(panel); + init(); + } + + /** + * Returns the {@link SpriteDecoder} instance associated with the currently selected creature animation. + * Returns {@code null} if no creature resource has been selected. + */ + public SpriteDecoder getDecoder() { return decoder; } + + /** Recreates the {@code SpriteDecoder} instance with the specified {@code CreResource}. */ + public void resetDecoder(CreResource cre) throws Exception + { + decoder = SpriteDecoder.importSprite(cre); + } + + /** + * Selects the specified CRE resource and initializes related fields. + * @param entry {@code ResourceEntry} instance of the creature + * @throws Exception if creature resource could not be initialized + */ + public void setSelectedCreature(ResourceEntry entry) throws Exception + { + if (getModelCreature().getSize() == 0) { + getModelCreature().reload(); + } + + int idx = Math.max(0, getModelCreature().getIndexOf(entry)); + getModelCreature().setSelectedItem(getModelCreature().getElementAt(idx)); + creSelectionChanged(); + } + + /** + * Selects the specified CRE resource and initializes related fields. + * @param entry {@code CreResource} instance of the creature + * @throws Exception if creature resource could not be initialized + */ + public void setSelectedCreature(CreResource cre) throws Exception + { + Object selection = cre; + if (selection == null) { + selection = CreatureSelectionModel.CreatureItem.getDefault(); + } + + if (getModelCreature().getSize() == 0) { + getModelCreature().reload(); + } + + int idx = getModelCreature().getIndexOf(selection); + if (idx >= 0) { + getModelCreature().setSelectedItem(getModelCreature().getElementAt(idx)); + } else { + getModelCreature().setSelectedItem(selection); + } + creSelectionChanged(); + } + + /** Selects the specified creature animation and updates related fields. */ + public void setSelectedAnimation(int value) + { + if (getModelAnimation().getSize() == 0) { + getModelAnimation().reload(); + } + + int idx = Math.max(0, getModelAnimation().getIndexOf(Integer.valueOf(value))); + getModelAnimation().setSelectedItem(getModelAnimation().getElementAt(idx)); + creAnimationChanged(); + } + + /** Selects the specified creature allegiance and updates related fields. */ + public void setSelectedAllegiance(int value) + { + if (getModelAllegiance().getSize() == 0) { + getModelAllegiance().reload(); + } + + int idx = Math.max(0, getModelAllegiance().getIndexOf(Integer.valueOf(value))); + getModelAllegiance().setSelectedItem(getModelAllegiance().getElementAt(idx)); + creAllegianceChanged(); + } + + /** Selects the specified helmet item and updates related fields. */ + public void setSelectedHelmet(ItemInfo item) + { + if (getModelHelmet().getSize() == 0) { + getModelHelmet().reload(); + } + + int idx = Math.max(0, getModelHelmet().getIndexOf(item)); + getModelHelmet().setSelectedItem(getModelHelmet().getElementAt(idx)); + itemHelmetChanged(); + } + + /** Selects the specified armor item and updates related fields. */ + public void setSelectedArmor(ItemInfo item) + { + if (getModelArmor().getSize() == 0) { + getModelArmor().reload(); + } + + int idx = Math.max(0, getModelArmor().getIndexOf(item)); + getModelArmor().setSelectedItem(getModelArmor().getElementAt(idx)); + itemArmorChanged(); + } + + /** + * Selects the specified shield or left-handed weapon item and updates related fields. + */ + public void setSelectedShield(ItemInfo item) + { + if (getModelShield().getSize() == 0) { + getModelShield().reload(); + } + + int idx = Math.max(0, getModelShield().getIndexOf(item)); + getModelShield().setSelectedItem(getModelShield().getElementAt(idx)); + itemShieldChanged(); + } + + /** + * Selects the specified weapon item and updates related fields. + * This method should be called BEFORE {@link #setSelectedShield(ItemInfo)} to ensure + * shield item list is compatible with the selected weapon. + */ + public void setSelectedWeapon(ItemInfo item) + { + if (getModelWeapon().getSize() == 0) { + getModelWeapon().reload(); + } + + int idx = Math.max(0, getModelWeapon().getIndexOf(item)); + getModelWeapon().setSelectedItem(getModelWeapon().getElementAt(idx)); + itemWeaponChanged(); + } + + /** + * Selects the specified color entry for the given location. + * @param index the color location index + * @param color the color value + */ + public void setSelectedColor(int index, int color) + { + final ColorSelectionModel model = getModelColor(index); + if (model == null) { + return; + } + + if (model.getSize() == 0) { + model.reload(); + } + + int idx = model.getIndexOf(Integer.valueOf(color)); + model.setSelectedItem(model.getElementAt(idx)); + colorChanged(index); + } + + /** Returns the control panel associated with the model. */ + public CreatureControlPanel getPanel() + { + return panel; + } + + /** Returns the model of the creature selection combobox. */ + public CreatureSelectionModel getModelCreature() + { + return modelCreSelection; + } + + /** Returns the model of the creature animation combobox. */ + public CreatureAnimationModel getModelAnimation() + { + return modelCreAnimation; + } + + /** Returns the model of the creature allegiance combobox. */ + public CreatureAllegianceModel getModelAllegiance() + { + return modelCreAllegiance; + } + + /** Returns the model of the panic checkbox. */ + public ToggleButtonModel getModelPanic() + { + return modelPanic; + } + + /** Returns the model of the helmet combobox. */ + public ItemSelectionModel getModelHelmet() + { + return modelItemHelmet; + } + + /** Returns the model of the armor combobox. */ + public ItemSelectionModel getModelArmor() + { + return modelItemArmor; + } + + /** Returns the model of the shield combobox. */ + public ItemSelectionModel getModelShield() + { + return modelItemShield; + } + + /** Returns the model of the weapon combobox. */ + public ItemSelectionModel getModelWeapon() + { + return modelItemWeapon; + } + + /** Returns the model of the specified color combobox. */ + public ColorSelectionModel getModelColor(int index) + { + if (index >= 0 && index < colorModels.size()) { + return colorModels.get(index); + } + return null; + } + + /** Returns an iterator over the list of color models. */ + public Iterator getColorModelIterator() + { + return colorModels.iterator(); + } + + /** + * Returns the {@code CreatureItem} instance of the currently selected CRE resource. + * Returns {@code null} if no creature is selected. + */ + public CreatureItem getSelectedCreature() + { + if (modelCreSelection != null && modelCreSelection.getSelectedItem() instanceof CreatureItem) { + return (CreatureItem)modelCreSelection.getSelectedItem(); + } else { + return null; + } + } + + /** + * Returns the {@code AnimateEntry} instance of the currently selected creature animation. + * Returns {@code null} if entry is not available. + */ + public AnimateEntry getSelectedAnimation() + { + if (modelCreAnimation != null && modelCreAnimation.getSelectedItem() instanceof AnimateEntry) { + return (AnimateEntry)modelCreAnimation.getSelectedItem(); + } else { + return null; + } + } + + /** + * Returns the {@code AnimateEntry} instance of the currently selected creature allegiance. + * Returns {@code null} if entry is not available. + */ + public AllegianceEntry getSelectedAllegiance() + { + if (modelCreAllegiance != null && modelCreAllegiance.getSelectedItem() instanceof AllegianceEntry) { + return (AllegianceEntry)modelCreAllegiance.getSelectedItem(); + } else { + return null; + } + } + + /** + * Returns the {@code ItemInfo} instance of the currently equipped helmet. + * Returns {@code null} if entry is not available. + */ + public ItemInfo getSelectedHelmet(ItemSelectionModel model) + { + return getItem(modelItemHelmet); + } + + /** + * Returns the {@code ItemInfo} instance of the currently equipped armor. + * Returns {@code null} if entry is not available. + */ + public ItemInfo getSelectedArmor(ItemSelectionModel model) + { + return getItem(modelItemArmor); + } + + /** + * Returns the {@code ItemInfo} instance of the currently equipped shield. + * Returns {@code null} if entry is not available. + */ + public ItemInfo getSelectedShield(ItemSelectionModel model) + { + return getItem(modelItemShield); + } + + /** + * Returns the {@code ItemInfo} instance of the currently equipped weapon. + * Returns {@code null} if entry is not available. + */ + public ItemInfo getSelectedWeapon(ItemSelectionModel model) + { + return getItem(modelItemWeapon); + } + + /** + * Returns the {@code ColorEntry} instance of the specified color location. + * Returns {@code null} if entry is not available. + * @param index color location index (range: [0, 7]) + */ + public ColorEntry getSelectedColor(int index) + { + try { + ColorSelectionModel model = colorModels.get(index); + if (model != null && model.getSelectedItem() instanceof ColorEntry) { + return (ColorEntry)model.getSelectedItem(); + } + } catch (IndexOutOfBoundsException e) { + } + return null; + } + + /** This method updates relevant settings when creature resource selection has changed. */ + public void creSelectionChanged() throws Exception + { + Object item = getModelCreature().getSelectedItem(); + if (item != null) { + CreResource cre; + if (item instanceof CreatureItem) { + cre = new CreResource(((CreatureItem)item).getResourceEntry()); + } else if (item instanceof CreResource) { + cre = (CreResource)item; + } else if (item instanceof ResourceEntry) { + cre = new CreResource((ResourceEntry)item); + } else { + throw new IllegalArgumentException("No valid creature resource selected"); + } + + resetDecoder(cre); + hashCreature = decoder.getCreResource().hashCode(); + final CreatureInfo creInfo = getDecoder().getCreatureInfo(); + + // setting animation + int animId = creInfo.getAnimationId(); + setSelectedAnimation(animId); + + // setting allegiance + int ea = creInfo.getAllegiance(); + setSelectedAllegiance(ea); + + // resetting panic option + getModelPanic().setSelected(false); + + // setting equipped helmet + ItemInfo helmet = creInfo.getEquippedHelmet(); + setSelectedHelmet(helmet); + + // setting equipped armor + ItemInfo armor = creInfo.getEquippedArmor(); + setSelectedArmor(armor); + + // setting equipped main-hand weapon + ItemInfo weapon = creInfo.getEquippedWeapon(); + setSelectedWeapon(weapon); + + // setting equipped shield or off-hand weapon + ItemInfo shield = creInfo.getEquippedShield(); + setSelectedShield(shield); + + // setting colors + String[] labels = CreatureControlPanel.createColorLabels(getDecoder()); + getPanel().setColorLabels(labels); + + for (int i = 0, numColors = creInfo.getColorCount(); i < colorModels.size(); i++) { + int value = (i < numColors) ? creInfo.getColorValue(i) : -1; + setSelectedColor(i, value); + } + } + + setModified(); + } + + /** This method updates relevant settings when creature animation selection has changed. */ + public void creAnimationChanged() + { + // nothing to do + setModified(); + } + + /** This method updates relevant settings when creature allegiance selection has changed. */ + public void creAllegianceChanged() + { + // nothing to do + setModified(); + } + + public void crePanicChanged() + { + // nothing to do + setModified(); + } + + /** This method updates relevant settings when helmet equipment has changed. */ + public void itemHelmetChanged() + { + // nothing to do + setModified(); + } + + /** This method updates relevant settings when armor equipment has changed. */ + public void itemArmorChanged() + { + // nothing to do + setModified(); + } + + /** This method updates relevant settings when shield/left-handed weapon equipment has changed. */ + public void itemShieldChanged() + { + // nothing to do + setModified(); + } + + /** This method updates relevant settings when weapon equipment has changed. */ + public void itemWeaponChanged() + { + // update shield equipment list depending on selected weapon type + if (getModelWeapon().getSelectedItem() instanceof ItemInfo) { + // determining valid filters for shield slot + ItemInfo info = (ItemInfo)getModelWeapon().getSelectedItem(); + boolean isMelee = (info.getAbilityCount() > 0) && (info.getAbility(0).getAbilityType() == 1); + boolean isTwoHanded = (info.getFlags() & (1 << 1)) != 0; + isTwoHanded |= !Profile.isEnhancedEdition() || (info.getFlags() & (1 << 12)) != 0; + ItemPredicate shieldPred = null; + if (!isTwoHanded) { + shieldPred = (shieldPred == null) ? ItemInfo.FILTER_SHIELD : shieldPred.or(ItemInfo.FILTER_SHIELD); + if (isMelee) { + shieldPred = (shieldPred == null) ? ItemInfo.FILTER_WEAPON_MELEE_LEFT_HANDED : shieldPred.or(ItemInfo.FILTER_WEAPON_MELEE_LEFT_HANDED); + } + } + if (shieldPred == null) { + shieldPred = ItemInfo.FILTER_NONE; + } + + // updating item list of shield slot + Object oldItem = getModelShield().getSelectedItem(); + getModelShield().setFilter(shieldPred); + getModelShield().reload(); + int idx = Math.max(0, getModelShield().getIndexOf(oldItem)); + getModelShield().setSelectedItem(getModelShield().getElementAt(idx)); + } + setModified(); + } + + /** This method updates relevant settings when specified color entry has changed. */ + public void colorChanged(int index) + { + // nothing to do + setModified(); + } + + /** Resets settings to the defaults as defined by the currently selected creature resource. */ + public void reset() + { + try { + creSelectionChanged(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** Returns whether the specified color entry is a random color. */ + public boolean isColorRandom(int index) + { + boolean retVal = false; + if (index >= 0 && index < colorModels.size()) { + Object item = colorModels.get(index).getSelectedItem(); + if (item instanceof ColorSelectionModel.ColorEntry) { + retVal = (((ColorSelectionModel.ColorEntry)item).getState() == ColorSelectionModel.ColorEntry.State.RANDOM); + } + } + return retVal; + } + + /** Returns whether settings have been modified since the last "Reset" or "Apply" operation. */ + public boolean canApply() + { + return canApply; + } + + /** Returns whether creature selection changed since the last "Reset" operation. */ + public boolean canReset() + { + return canReset; + } + + protected void setModified() + { + if (!canApply || !canReset) { + canApply = true; + canReset = isCreatureModified(); + getPanel().fireSettingsChanged(); + } + } + + protected void resetModified() + { + if (canApply || canReset) { + boolean randomColor = false; + for (int i = 0; i < colorModels.size() && !randomColor; i++) { + randomColor |= isColorRandom(i); + } + canApply = randomColor; + canReset = isCreatureModified(); + getPanel().fireSettingsChanged(); + } + } + + /** Returns whether modifications have been applied to the currently selected creature entry. */ + public boolean isCreatureModified() + { + int hash = getDecoder().getCreResource().hashCode(); + return (hashCreature != hash); + } + + /** Returns the currently selected item in the specified selection model. */ + private ItemInfo getItem(ItemSelectionModel model) + { + if (model != null && model.getSelectedItem() instanceof ItemInfo) { + return (ItemInfo)model.getSelectedItem(); + } else { + return null; + } + } + + private void init() + { + // perform lazy initialization: time-consuming initializations are performed on demand + modelCreSelection = new CreatureSelectionModel(false); + modelCreAnimation = new CreatureAnimationModel(); + modelCreAllegiance = new CreatureAllegianceModel(); + modelItemHelmet = new ItemSelectionModel(ItemInfo.FILTER_HELMET, false); + modelItemArmor = new ItemSelectionModel(ItemInfo.FILTER_ARMOR, false); + modelItemShield = new ItemSelectionModel(ItemInfo.FILTER_SHIELD.or(ItemInfo.FILTER_WEAPON_MELEE_LEFT_HANDED), false); + modelItemWeapon = new ItemSelectionModel(ItemInfo.FILTER_WEAPON, false); + for (int i = 0; i < 7; i++) { + colorModels.add(new ColorSelectionModel()); + } + modelPanic = new ToggleButtonModel(); + } +} diff --git a/src/org/infinity/resource/cre/viewer/CreatureControlPanel.java b/src/org/infinity/resource/cre/viewer/CreatureControlPanel.java new file mode 100644 index 000000000..1a1b464c5 --- /dev/null +++ b/src/org/infinity/resource/cre/viewer/CreatureControlPanel.java @@ -0,0 +1,664 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.viewer; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.border.EtchedBorder; +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; + +import org.infinity.gui.ViewerUtil; +import org.infinity.icon.Icons; +import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.MonsterPlanescapeDecoder; +import org.infinity.resource.cre.decoder.SpriteDecoder; +import org.infinity.resource.cre.decoder.internal.ItemInfo; +import org.infinity.resource.cre.viewer.ColorSelectionModel.ColorEntry; +import org.infinity.resource.cre.viewer.CreatureSelectionModel.CreatureItem; +import org.infinity.util.IdsMap; +import org.infinity.util.IdsMapCache; +import org.infinity.util.IdsMapEntry; +import org.infinity.util.Misc; + +/** + * This panel provides controls for customizing various aspects of a CRE resource. + */ +public class CreatureControlPanel extends JPanel +{ + private static final String[] defaultColorLabels = { "Metal color", "Minor color", "Major color", "Skin color", + "Leather color", "Armor color", "Hair color" }; + + // Labels for color selection lists + private final List colorLabels = new ArrayList<>(); + // Color selection list controls + private final List> colorControls = new ArrayList<>(); + private final Listeners listeners = new Listeners(); + + private final CreatureViewer viewer; + + private CreatureControlModel model; + private JComboBox cbCreSelection; + private JComboBox cbCreAnimation; + private JComboBox cbCreAllegiance; + private JComboBox cbItemHelmet; + private JComboBox cbItemArmor; + private JComboBox cbItemShield; + private JComboBox cbItemWeapon; + private JCheckBox cbPanic; + private JButton bReset; + private JButton bApply; + + public CreatureControlPanel(CreatureViewer viewer) + { + super(); + this.viewer = viewer; + init(); + } + + /** Returns the associated {@code CreatureViewer} instance. */ + public CreatureViewer getViewer() { return viewer; } + + public CreatureControlModel getControlModel() { return model; } + + /** + * Sets color labels to the specified values. Empty strings are considered as "unused". + * Specify empty array to set default labels. + */ + public void setColorLabels(String[] labels) + { + if (labels == null) { + labels = defaultColorLabels; + } + for (int i = 0; i < colorLabels.size(); i++) { + JLabel l = colorLabels.get(i); + if (i < labels.length && labels[i] != null && !labels[i].isEmpty()) { + l.setText(labels[i] + ":"); + } else { + l.setText("Unused color:"); + } + l.getParent().invalidate(); + } + validate(); + } + + /** Resets all CRE-related settings to the values provided by the selected CRE resource. */ + public void resetSettings() + { + getControlModel().reset(); + } + + /** Applies the current CRE-related settings to the creature animation. */ + public void applySettings() + { + CreResource cre = getControlModel().getDecoder().getCreResource(); + CreUtils.setAnimation(cre, getControlModel().getSelectedAnimation().getValue()); + CreUtils.setAllegiance(cre, getControlModel().getSelectedAllegiance().getValue()); + CreUtils.setStatusPanic(cre, getControlModel().getModelPanic().isSelected()); + CreUtils.setEquipmentHelmet(cre, getControlModel().getSelectedHelmet(getControlModel().getModelHelmet())); + CreUtils.setEquipmentArmor(cre, getControlModel().getSelectedArmor(getControlModel().getModelArmor())); + CreUtils.setEquipmentWeapon(cre, getControlModel().getSelectedWeapon(getControlModel().getModelWeapon())); + CreUtils.setEquipmentShield(cre, getControlModel().getSelectedShield(getControlModel().getModelShield())); + int idx = 0; + for (final Iterator iter = getControlModel().getColorModelIterator(); iter.hasNext(); ) { + ColorSelectionModel cm = iter.next(); + if (cm.getSelectedItem() instanceof ColorSelectionModel.ColorEntry) { + ColorSelectionModel.ColorEntry ce = (ColorSelectionModel.ColorEntry)cm.getSelectedItem(); + CreUtils.setColor(cre, idx, ce.getIndex()); + } + idx++; + } + try { + getControlModel().resetDecoder(cre); + } catch (Exception e) { + e.printStackTrace(); + getViewer().showErrorMessage("Could not load creature animation.\nError: " + e.getMessage()); + } + + getViewer().getSettingsPanel().reset(); + getViewer().getMediaPanel().reset(true); + getControlModel().resetModified(); + } + + /** Called whenever a setting has been changed by the user. */ + public void fireSettingsChanged() + { + bReset.setEnabled(getControlModel().canReset()); + bApply.setEnabled(getControlModel().canApply()); + } + + private void init() + { + model = new CreatureControlModel(this); + + GridBagConstraints c = new GridBagConstraints(); + + // first column + JLabel l1 = new JLabel("Select CRE resource:"); + cbCreSelection = new JComboBox<>(model.getModelCreature()); + // this is a good default width for all selection controls in this panel + cbCreSelection.setPrototypeDisplayValue(CreatureItem.getDefault()); + int defWidth = cbCreSelection.getPreferredSize().width * 5 / 4; + setPreferredWidth(cbCreSelection, defWidth); + cbCreSelection.addActionListener(listeners); +// model.getModelCreature().addListDataListener(listeners); + updateToolTip(cbCreSelection); + + JLabel l2 = new JLabel("Creature animation:"); + l2.setToolTipText("Supports manually entered numbers. Add \"0x\" prefix or \"h\" suffix to specify a hexadecimal number."); + cbCreAnimation = new JComboBox<>(model.getModelAnimation()); + setPreferredWidth(cbCreAnimation, defWidth); + cbCreAnimation.setEditable(true); + cbCreAnimation.addActionListener(listeners); + updateToolTip(cbCreAnimation); + + JLabel l3 = new JLabel("Allegiance:"); + cbCreAllegiance = new JComboBox<>(model.getModelAllegiance()); + setPreferredWidth(cbCreAllegiance, defWidth); + cbCreAllegiance.addActionListener(listeners); + updateToolTip(cbCreAllegiance); + + cbPanic = new JCheckBox("Panicked"); + cbPanic.setModel(model.getModelPanic()); + cbPanic.addActionListener(listeners); + + JPanel pColumn1 = new JPanel(new GridBagLayout()); + c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, + GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); + pColumn1.add(l1, c); + c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(0, 4, 0, 0), 0, 0); + pColumn1.add(cbCreSelection, c); + + c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, + GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); + pColumn1.add(l2, c); + c = ViewerUtil.setGBC(c, 1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 4, 0, 0), 0, 0); + pColumn1.add(cbCreAnimation, c); + + c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, + GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); + pColumn1.add(l3, c); + c = ViewerUtil.setGBC(c, 1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 4, 0, 0), 0, 0); + pColumn1.add(cbCreAllegiance, c); + + c = ViewerUtil.setGBC(c, 0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, + GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); + pColumn1.add(new JPanel(), c); + c = ViewerUtil.setGBC(c, 1, 3, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 4, 0, 0), 0, 0); + pColumn1.add(cbPanic, c); + + + // second column + l1 = new JLabel("Helmet:"); + cbItemHelmet = new JComboBox<>(model.getModelHelmet()); + setPreferredWidth(cbItemHelmet, defWidth); + cbItemHelmet.addActionListener(listeners); + updateToolTip(cbItemHelmet); + + l2 = new JLabel("Armor:"); + cbItemArmor = new JComboBox<>(model.getModelArmor()); + setPreferredWidth(cbItemArmor, defWidth); + cbItemArmor.addActionListener(listeners); + updateToolTip(cbItemArmor); + + l3 = new JLabel("Shield:"); + cbItemShield = new JComboBox<>(model.getModelShield()); + setPreferredWidth(cbItemShield, defWidth); + cbItemShield.addActionListener(listeners); + updateToolTip(cbItemShield); + + JLabel l4 = new JLabel("Weapon:"); + cbItemWeapon = new JComboBox<>(model.getModelWeapon()); + setPreferredWidth(cbItemWeapon, defWidth); + cbItemWeapon.addActionListener(listeners); + updateToolTip(cbItemWeapon); + + JPanel pColumn2 = new JPanel(new GridBagLayout()); + c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, + GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); + pColumn2.add(l1, c); + c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(0, 4, 0, 0), 0, 0); + pColumn2.add(cbItemHelmet, c); + + c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, + GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); + pColumn2.add(l2, c); + c = ViewerUtil.setGBC(c, 1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 4, 0, 0), 0, 0); + pColumn2.add(cbItemArmor, c); + + c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, + GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); + pColumn2.add(l3, c); + c = ViewerUtil.setGBC(c, 1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 4, 0, 0), 0, 0); + pColumn2.add(cbItemShield, c); + + c = ViewerUtil.setGBC(c, 0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, + GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); + pColumn2.add(l4, c); + c = ViewerUtil.setGBC(c, 1, 3, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 4, 0, 0), 0, 0); + pColumn2.add(cbItemWeapon, c); + + + // third column + JComboBox cb; + ColorSelectionModel cm = model.getModelColor(0); + + // determine suitable color entry for preferred combobox width + ColorSelectionModel.ColorEntry ce = cm.getElementAt(0); + for (int i = 0; i < cm.getSize(); i++) { + ColorSelectionModel.ColorEntry item = cm.getElementAt(i); + if (item != null && (ce == null || item.toString().length() > ce.toString().length())) { + ce = item; + } + } + + l1 = new JLabel("Metal color:"); + colorLabels.add(l1); + cm = model.getModelColor(0); + cb = new JComboBox<>(cm); + cb.setRenderer(cm.getRenderer()); + if (ce != null) { + cb.setPrototypeDisplayValue(ce); + defWidth = cb.getPreferredSize().width; + } + setPreferredWidth(cb, defWidth); + cb.addActionListener(listeners); + cm.addListDataListener(listeners); + updateToolTip(cb); + colorControls.add(cb); + + l1 = new JLabel("Minor color:"); + colorLabels.add(l1); + cm = model.getModelColor(1); + cb = new JComboBox<>(cm); + cb.setRenderer(cm.getRenderer()); + cb.setPrototypeDisplayValue((ColorSelectionModel.ColorEntry)cm.getSelectedItem()); + setPreferredWidth(cb, defWidth); + cb.addActionListener(listeners); + cm.addListDataListener(listeners); + updateToolTip(cb); + colorControls.add(cb); + + l1 = new JLabel("Major color:"); + colorLabels.add(l1); + cm = model.getModelColor(2); + cb = new JComboBox<>(cm); + cb.setRenderer(cm.getRenderer()); + cb.setPrototypeDisplayValue((ColorSelectionModel.ColorEntry)cm.getSelectedItem()); + setPreferredWidth(cb, defWidth); + cb.addActionListener(listeners); + cm.addListDataListener(listeners); + updateToolTip(cb); + colorControls.add(cb); + + l1 = new JLabel("Skin color:"); + colorLabels.add(l1); + cm = model.getModelColor(3); + cb = new JComboBox<>(cm); + cb.setRenderer(cm.getRenderer()); + cb.setPrototypeDisplayValue((ColorSelectionModel.ColorEntry)cm.getSelectedItem()); + setPreferredWidth(cb, defWidth); + cb.addActionListener(listeners); + cm.addListDataListener(listeners); + updateToolTip(cb); + colorControls.add(cb); + + JPanel pColumn3 = new JPanel(new GridBagLayout()); + c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, + GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); + pColumn3.add(colorLabels.get(0), c); + c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(0, 4, 0, 0), 0, 0); + pColumn3.add(colorControls.get(0), c); + + c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, + GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); + pColumn3.add(colorLabels.get(1), c); + c = ViewerUtil.setGBC(c, 1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 4, 0, 0), 0, 0); + pColumn3.add(colorControls.get(1), c); + + c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, + GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); + pColumn3.add(colorLabels.get(2), c); + c = ViewerUtil.setGBC(c, 1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 4, 0, 0), 0, 0); + pColumn3.add(colorControls.get(2), c); + + c = ViewerUtil.setGBC(c, 0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, + GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); + pColumn3.add(colorLabels.get(3), c); + c = ViewerUtil.setGBC(c, 1, 3, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 4, 0, 0), 0, 0); + pColumn3.add(colorControls.get(3), c); + + + // fourth column + l1 = new JLabel("Leather color:"); + colorLabels.add(l1); + cm = model.getModelColor(4); + cb = new JComboBox<>(cm); + cb.setRenderer(cm.getRenderer()); + cb.setPrototypeDisplayValue((ColorSelectionModel.ColorEntry)cm.getSelectedItem()); + setPreferredWidth(cb, defWidth); + cb.addActionListener(listeners); + cm.addListDataListener(listeners); + updateToolTip(cb); + colorControls.add(cb); + + l1 = new JLabel("Armor color:"); + colorLabels.add(l1); + cm = model.getModelColor(5); + cb = new JComboBox<>(cm); + cb.setRenderer(cm.getRenderer()); + cb.setPrototypeDisplayValue((ColorSelectionModel.ColorEntry)cm.getSelectedItem()); + setPreferredWidth(cb, defWidth); + cb.addActionListener(listeners); + cm.addListDataListener(listeners); + updateToolTip(cb); + colorControls.add(cb); + + l1 = new JLabel("Hair color:"); + colorLabels.add(l1); + cm = model.getModelColor(6); + cb = new JComboBox<>(cm); + cb.setRenderer(cm.getRenderer()); + cb.setPrototypeDisplayValue((ColorSelectionModel.ColorEntry)cm.getSelectedItem()); + setPreferredWidth(cb, defWidth); + cb.addActionListener(listeners); + cm.addListDataListener(listeners); + updateToolTip(cb); + colorControls.add(cb); + + Insets margin; + bReset = new JButton("Reset", Icons.getIcon(Icons.ICON_UNDO_16)); + margin = bReset.getMargin(); + margin.top += 4; margin.bottom += 4; + bReset.setMargin(margin); + bReset.setToolTipText("Revert to creature defaults"); + bReset.addActionListener(listeners); + bApply = new JButton("Apply", Icons.getIcon(Icons.ICON_REFRESH_16)); + margin = bApply.getMargin(); + margin.top += 4; margin.bottom += 4; + bApply.setMargin(margin); + bApply.addActionListener(listeners); + + JPanel pColumn4 = new JPanel(new GridBagLayout()); + c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, + GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); + pColumn4.add(colorLabels.get(4), c); + c = ViewerUtil.setGBC(c, 1, 0, 2, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(0, 4, 0, 0), 0, 0); + pColumn4.add(colorControls.get(4), c); + + c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, + GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); + pColumn4.add(colorLabels.get(5), c); + c = ViewerUtil.setGBC(c, 1, 1, 2, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 4, 0, 0), 0, 0); + pColumn4.add(colorControls.get(5), c); + + c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, + GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); + pColumn4.add(colorLabels.get(6), c); + c = ViewerUtil.setGBC(c, 1, 2, 2, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 4, 0, 0), 0, 0); + pColumn4.add(colorControls.get(6), c); + + c = ViewerUtil.setGBC(c, 0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); + pColumn4.add(new JPanel(), c); + c = ViewerUtil.setGBC(c, 1, 3, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.NONE, new Insets(8, 4, 0, 0), 0, 0); + pColumn4.add(bReset, c); + c = ViewerUtil.setGBC(c, 2, 3, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 8, 0, 0), 0, 0); + pColumn4.add(bApply, c); + + // combining columns + JPanel panelMain = new JPanel(new GridBagLayout()); + c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.NONE, new Insets(8, 8, 8, 0), 0, 0); + panelMain.add(pColumn1, c); + c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.NONE, new Insets(8, 16, 8, 0), 0, 0); + panelMain.add(pColumn2, c); + c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.NONE, new Insets(8, 16, 8, 0), 0, 0); + panelMain.add(pColumn3, c); + c = ViewerUtil.setGBC(c, 3, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.NONE, new Insets(8, 16, 8, 8), 0, 0); + panelMain.add(pColumn4, c); + + JScrollPane scroll = new JScrollPane(panelMain); + scroll.setBorder(new EtchedBorder()); + + setLayout(new BorderLayout()); + add(scroll, BorderLayout.CENTER); + + fireSettingsChanged(); + } + + /** + * Returns {@code true} if the pending reset can be performed. Shows a confirmation dialog if changes were made. + */ + private boolean confirmReset() + { + boolean retVal = true; + if (getControlModel().canReset()) { + retVal = (JOptionPane.showConfirmDialog(getViewer(), + "Creature settings have been modified. Do you want to revert these changes?", + "Revert changes", + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION); + } + return retVal; + } + + /** + * Generates an array of color location labels based on the creature animation provided + * by the specified {@code SpriteDecoder}. + */ + public static String[] createColorLabels(SpriteDecoder decoder) + { + String[] retVal = null; + if (decoder != null && decoder instanceof MonsterPlanescapeDecoder) { + MonsterPlanescapeDecoder mpd = (MonsterPlanescapeDecoder)decoder; + IdsMap map = IdsMapCache.get("CLOWNRGE.IDS"); + if (map != null) { + retVal = new String[mpd.getColorLocationCount()]; + for (int i = 0; i < retVal.length; i++) { + int location = mpd.getColorLocation(i) >>> 4; + if (location > 0) { + IdsMapEntry entry = map.get(location); + if (entry != null) { + retVal[i] = Misc.prettifySymbol(entry.getSymbol()) + " Color"; + } + } + } + } + } + + if (retVal == null) { + retVal = Arrays.copyOf(defaultColorLabels, defaultColorLabels.length); + } + + return retVal; + } + + /** Helper method: Updates preferred width of the specified {@code JComponent} instance. */ + private static void setPreferredWidth(JComponent c, int width) + { + if (c != null) { + Dimension dim = c.getPreferredSize(); + dim.width = width; + c.setPreferredSize(dim); + } + } + + /** Helper method: Updates the tooltip of the specified {@code JComboBox} object with the label of the selected item. */ + private static void updateToolTip(Object o) + { + if (o instanceof JComboBox) { + JComboBox cb = (JComboBox)o; + Object s = cb.getSelectedItem(); + if (s != null) { + cb.setToolTipText(s.toString()); + } else { + cb.setToolTipText(""); + } + } + } + + //-------------------------- INNER CLASSES -------------------------- + + /** + * Listeners are outsourced to this class for cleaner code. + */ + private class Listeners implements ActionListener, ListDataListener + { + private Listeners() + { + } + + //--------------------- Begin Interface ActionListener --------------------- + + @Override + public void actionPerformed(ActionEvent e) + { + if (e.getSource() == bApply) { + applySettings(); + } + else if (e.getSource() == bReset) { + if (confirmReset()) { + resetSettings(); + } + } + else if (e.getSource() == cbCreSelection) { + try { + getControlModel().creSelectionChanged(); + updateToolTip(cbCreSelection); + } catch (Exception ex) { + getViewer().showErrorMessage("Could not load the creature resource.\nError: " + ex.getMessage()); + } + } + else if (e.getSource() == cbCreAnimation) { + if (cbCreAnimation.getSelectedItem() != null) { + // find matching list entry + int idx = getControlModel().getModelAnimation().getIndexOf(cbCreAnimation.getSelectedItem()); + if (idx != -1) { + getControlModel().getModelAnimation().setSelectedItem(getControlModel().getModelAnimation().getElementAt(idx)); + } + } + getControlModel().creAnimationChanged(); + updateToolTip(cbCreAnimation); + } + else if (e.getSource() == cbCreAllegiance) { + getControlModel().creAllegianceChanged(); + updateToolTip(cbCreAllegiance); + } + else if (e.getSource() == cbItemHelmet) { + getControlModel().itemHelmetChanged(); + updateToolTip(cbItemHelmet); + } + else if (e.getSource() == cbItemArmor) { + getControlModel().itemArmorChanged(); + updateToolTip(cbItemArmor); + } + else if (e.getSource() == cbItemShield) { + getControlModel().itemShieldChanged(); + updateToolTip(cbItemShield); + } + else if (e.getSource() == cbItemWeapon) { + getControlModel().itemWeaponChanged(); + updateToolTip(cbItemWeapon); + } + else if (e.getSource() == cbPanic) { + getControlModel().crePanicChanged(); + } + else { + // color selection + int idx = colorControls.indexOf(e.getSource()); + if (idx >= 0) { + getControlModel().colorChanged(idx); + updateToolTip(colorControls.get(idx)); + } + } + } + + //--------------------- End Interface ActionListener --------------------- + + //--------------------- Begin Interface ListDataListener --------------------- + + @Override + public void intervalAdded(ListDataEvent e) + { + } + + @Override + public void intervalRemoved(ListDataEvent e) + { + } + + @Override + public void contentsChanged(ListDataEvent e) + { +// if (e.getSource() == getControlModel().getModelCreature()) { +// } +// else if (e.getSource() == getControlModel().getModelAnimation()) { +// } +// else if (e.getSource() == getControlModel().getModelAllegiance()) { +// } +// else if (e.getSource() == getControlModel().getModelHelmet()) { +// } +// else if (e.getSource() == getControlModel().getModelArmor()) { +// } +// else if (e.getSource() == getControlModel().getModelShield()) { +// } +// else if (e.getSource() == getControlModel().getModelWeapon()) { +// } +// else { + // color selections may be unused in PST/PSTEE + for (int idx = 0, cnt = colorControls.size(); idx < cnt; idx++) { + final JComboBox cb = colorControls.get(idx); + if (e.getSource() == colorControls.get(idx).getModel()) { + boolean enabled = (colorControls.get(idx).getSelectedItem() != null); + cb.setEnabled(enabled); + colorLabels.get(idx).setEnabled(enabled); + break; + } + } +// } + } + + //--------------------- End Interface ListDataListener --------------------- + } +} diff --git a/src/org/infinity/resource/cre/viewer/CreatureSelectionModel.java b/src/org/infinity/resource/cre/viewer/CreatureSelectionModel.java new file mode 100644 index 000000000..fd3270d92 --- /dev/null +++ b/src/org/infinity/resource/cre/viewer/CreatureSelectionModel.java @@ -0,0 +1,290 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.viewer; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.IntStream; + +import javax.swing.AbstractListModel; +import javax.swing.ComboBoxModel; + +import org.infinity.resource.Profile; +import org.infinity.resource.ResourceFactory; +import org.infinity.resource.StructureFactory; +import org.infinity.resource.StructureFactory.StructureException; +import org.infinity.resource.cre.CreResource; +import org.infinity.resource.key.BufferedResourceEntry; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.Misc; +import org.infinity.util.ResourceStructure; +import org.infinity.util.io.StreamUtils; +import org.infinity.util.tuples.Couple; + +/** + * {@code ComboBoxModel} for the creature selection combo box used in the Creature Animation Viewer. + */ +public class CreatureSelectionModel extends AbstractListModel + implements ComboBoxModel +{ + private final List creList = new ArrayList<>(); + + private Object selectedItem; + + /** + * Creates a new combobox model. + * @param autoInit whether the model is automatically populated with creature data. + */ + public CreatureSelectionModel(boolean autoInit) + { + super(); + if (autoInit) { + init(); + } + } + + /** Discards the current content and reloads creature data. */ + public void reload() + { + init(); + } + + /** + * Returns the index-position of the specified object in the list. + * @param anItem a {@code CreatureItem} object, {@code ResourceEntry} object or {@code String} specifying a resref. + * @return an int representing the index position, where 0 is the first position. + * Returns -1 if the item could not be found in the list. + */ + public int getIndexOf(Object anItem) + { + if (anItem instanceof CreatureItem) { + return creList.indexOf(anItem); + } else if (anItem instanceof CreResource) { + CreResource cre = (CreResource)anItem; + if (cre.getResourceEntry() != null) { + return getIndexOf(cre.getResourceEntry()); + } + } else if (anItem instanceof ResourceEntry) { + return IntStream + .range(0, creList.size()) + .filter(i -> creList.get(i).getResourceEntry().equals(anItem)) + .findAny() + .orElse(-1); + } else if (anItem != null) { + final String name = anItem.toString(); + return IntStream + .range(0, creList.size()) + .filter(i -> name.equalsIgnoreCase(creList.get(i).getResourceEntry().getResourceRef())) + .findAny() + .orElse(-1); + } + return -1; + } + + /** Empties the list. */ + public void removeAllElements() + { + if (!creList.isEmpty()) { + int oldSize = creList.size(); + creList.clear(); + selectedItem = null; + if (oldSize > 0) { + fireIntervalRemoved(this, 0, oldSize - 1); + } + } else { + selectedItem = null; + } + } + +//--------------------- Begin Interface ListModel --------------------- + + @Override + public int getSize() + { + return creList.size(); + } + + @Override + public CreatureItem getElementAt(int index) + { + if (index >= 0 && index < creList.size()) { + return creList.get(index); + } else { + return null; + } + } + +//--------------------- End Interface ListModel --------------------- + +//--------------------- Begin Interface ComboBoxModel --------------------- + + @Override + public void setSelectedItem(Object anItem) + { + if ((selectedItem != null && !selectedItem.equals(anItem)) || + selectedItem == null && anItem != null) { + selectedItem = anItem; + fireContentsChanged(this, -1, -1); + } + } + + @Override + public Object getSelectedItem() + { + return selectedItem; + } + +//--------------------- End Interface ComboBoxModel --------------------- + + private void init() + { + removeAllElements(); + + ResourceFactory.getResources("CRE").stream().forEach(re -> creList.add(new CreatureItem(re))); + Collections.sort(creList); + creList.add(0, CreatureItem.getDefault()); + if (!creList.isEmpty()) { + fireIntervalAdded(this, 0, creList.size() - 1); + } + + setSelectedItem(getElementAt(0)); + } + +//-------------------------- INNER CLASSES -------------------------- + + public static class CreatureItem implements Comparable + { + /** The default creature item referencing a pseudo creature. */ + private static final Couple DEFAULT_CREATURE = Couple.with(null, null); + + private final ResourceEntry entry; + + public CreatureItem(ResourceEntry entry) + { + this.entry = Objects.requireNonNull(entry); + } + + /** Returns the {@code ResourceEntry} associated with the item. */ + public ResourceEntry getResourceEntry() { return entry; } + + @Override + public String toString() + { + if (entry == DEFAULT_CREATURE.getValue1().getResourceEntry()) { + return "None (customize from scratch)"; + } else { + String resref = entry.getResourceName(); + String name = entry.getSearchString(); + if (name == null || name.isEmpty()) { + return resref; + } else { + return resref + " (" + name + ")"; + } + } + } + + //--------------------- Begin Interface Comparable --------------------- + + @Override + public int compareTo(CreatureItem o) + { + return entry.compareTo(o.entry); + } + + //--------------------- End Interface Comparable --------------------- + + /** Returns a default creature suitable for the current game. */ + public static CreatureItem getDefault() + { + if (DEFAULT_CREATURE.getValue0() != Profile.getGame()) { + DEFAULT_CREATURE.setValue0(Profile.getGame()); + DEFAULT_CREATURE.setValue1(new CreatureItem(createCreature())); + } + return DEFAULT_CREATURE.getValue1(); + } + + /** Helper method: Creates a default CRE resource from scratch based on the current game. */ + private static ResourceEntry createCreature() + { + ResourceEntry retVal = null; + try { + ResourceStructure res = StructureFactory.getInstance().createStructure(StructureFactory.ResType.RES_CRE, null, null); + ByteBuffer buf = res.getBuffer(); + + // setting default animation + switch (Profile.getGame()) { + case PST: + buf.putInt(0x28, 0xe03e); // Townie, LC Male + break; + case PSTEE: + buf.putInt(0x28, 0xf03e); // cl_3_Townie_LC_Male_2 + break; + default: + buf.putInt(0x28, 0x6100); // fighter_male_human + } + + // setting default colors + final byte[] colorsPst = {(byte)7, (byte)86, (byte)40, (byte)75}; + final byte[] locationsPst = {(byte)224, (byte)144, (byte)176, (byte)160}; + final byte[] colorsOther = {(byte)30, (byte)37, (byte)57, (byte)12, (byte)23, (byte)28, (byte)0}; + switch (Profile.getGame()) { + case PST: + case PSTEE: + { + int ofsColors = (Profile.getGame() == Profile.Game.PST) ? 0x2e4 : 0x2c; + int ofsLocations = (Profile.getGame() == Profile.Game.PST) ? 0x2f5 : -1; + int sizeColors = (Profile.getGame() == Profile.Game.PST) ? 2 : 1; + int cnt = Math.min(colorsPst.length, locationsPst.length); + if (ofsLocations > 0) { + buf.put(0x2df, (byte)cnt); // # colors + } + for (int i = 0; i < cnt; i++) { + buf.put(ofsColors + i * sizeColors, colorsPst[i]); + if (ofsLocations > 0) { + buf.put(ofsLocations + i, locationsPst[i]); + } + } + break; + } + default: + for (int i = 0; i < colorsOther.length; i++) { + buf.put(0x2c + i, colorsOther[i]); + } + } + + // setting default properties + // SEX=MALE, EA=NEUTRAL, GENERAL=HUMANOID, RACE=HUMAN, CLASS=FIGHTER, GENDER=MALE, ALIGNMENT=NEUTRAL + byte[] properties = {(byte)1, (byte)128, (byte)1, (byte)1, (byte)2, (byte)1, (byte)0x22}; + int[] offsets; + String ver = StreamUtils.readString(buf, 4, 4, Misc.CHARSET_DEFAULT).toUpperCase(); + switch (ver) { + case "V1.2": + offsets = new int[] {0x237, 0x314, 0x315, 0x316, 0x317, 0x319, 0x31f}; + break; + case "V2.2": + offsets = new int[] {0x389, 0x384, 0x385, 0x386, 0x387, 0x389, 0x38f}; + break; + case "V9.0": + offsets = new int[] {0x237, 0x2d8, 0x2d9, 0x2da, 0x2db, 0x2dd, 0x2e3}; + break; + default: + offsets = new int[] {0x237, 0x270, 0x271, 0x272, 0x273, 0x275, 0x27b}; + } + for (int i = 0; i < properties.length; i++) { + buf.put(offsets[i], properties[i]); + } + + retVal = new BufferedResourceEntry(buf, "*.CRE"); + } catch (StructureException e) { + e.printStackTrace(); + } + + return retVal; + } + } +} diff --git a/src/org/infinity/resource/cre/viewer/CreatureViewer.java b/src/org/infinity/resource/cre/viewer/CreatureViewer.java new file mode 100644 index 000000000..873feac76 --- /dev/null +++ b/src/org/infinity/resource/cre/viewer/CreatureViewer.java @@ -0,0 +1,335 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.viewer; + +import java.awt.BorderLayout; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.Objects; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutionException; + +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.ProgressMonitor; +import javax.swing.SwingWorker; +import javax.swing.border.EtchedBorder; + +import org.infinity.NearInfinity; +import org.infinity.gui.Center; +import org.infinity.gui.ChildFrame; +import org.infinity.gui.WindowBlocker; +import org.infinity.icon.Icons; +import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.SpriteDecoder; +import org.infinity.resource.cre.decoder.SpriteUtils; + +/** + * The Creature Viewer implements a highly customizable viewer for creature animations. + */ +public class CreatureViewer extends ChildFrame +{ + private final ConcurrentLinkedQueue actionQueue = new ConcurrentLinkedQueue<>(); + private final Listeners listeners = new Listeners(); + + private CreatureControlPanel panelCreature; + private SettingsPanel panelSettings; + private MediaPanel panelMedia; + private RenderPanel panelCanvas; + private CreResource cre; + private SwingWorker worker; + + /** + * Creates an instance of the creature viewer with a (virtual) default creature. + */ + public CreatureViewer() + { + this(null); + } + + /** + * Creates an instance of the creature viewer and loads the specified CRE resource. + * @param cre the CRE resource to load + */ + public CreatureViewer(CreResource cre) + { + super(""); + init(); + setCreResource(cre); + } + + /** Returns the currently active CRE resource. */ + public CreResource getCreResource() + { + return cre; + } + + /** + * Discards the current creature data and loads the specified creature. + * @param cre the CRE resource to load. Specify {@code null} to load the (virtual) default creature. + */ + public void setCreResource(CreResource cre) + { + if ((this.cre == null && cre != null) || + this.cre != null && !this.cre.equals(cre) || + cre == null) { + this.cre = cre; + performBackgroundTask(this::taskSetCreResource, this::postTaskDefault, true); + } + } + + /** Returns the active {@code SpriteDecoder} instance. Returns {@code null} otherwise. */ + public SpriteDecoder getDecoder() { return getControlPanel().getControlModel().getDecoder(); } + + /** Returns the attached {@code CreatureControlPanel} instance. */ + public CreatureControlPanel getControlPanel() { return panelCreature; } + + /** Returns the attached {@code SettingsPanel} instance. */ + public SettingsPanel getSettingsPanel() { return panelSettings; } + + public AttributesPanel getAttributesPanel() { return getSettingsPanel().getAttributesPanel(); } + + /** Returns the attached {@code MediaPanel} instance. */ + public MediaPanel getMediaPanel() { return panelMedia; } + + /** Returns the attached {@code RenderPanel} instance. */ + public RenderPanel getRenderPanel() { return panelCanvas; } + + /** Shows an error dialog with the specified message. */ + public void showErrorMessage(String msg) + { + if (msg == null || msg.isEmpty()) { + msg = "An error has occurred."; + } + JOptionPane.showMessageDialog(this, msg, "Error", JOptionPane.ERROR_MESSAGE); + } + +//--------------------- Begin Class ChildFrame --------------------- + + @Override + protected boolean windowClosing(boolean forced) throws Exception + { + cleanup(); + return true; + } + +//--------------------- End Class ChildFrame --------------------- + + private void init() + { + setIconImages(NearInfinity.getInstance().getIconImages()); + + // *** CRE customization panel *** + panelCreature = new CreatureControlPanel(this); + + // *** Settings panel *** + panelSettings = new SettingsPanel(this); + + // *** Animation viewer panel *** + panelCanvas = new RenderPanel(this); + + JPanel viewerPanel = new JPanel(new BorderLayout()); + viewerPanel.add(panelCanvas, BorderLayout.CENTER); + viewerPanel.setBorder(new EtchedBorder()); + + // *** Controls panel *** + // sub panel for playback controls + panelMedia = new MediaPanel(this); + + // *** Top-level viewer panel *** + JPanel viewerMainPanel = new JPanel(new BorderLayout()); + viewerMainPanel.add(viewerPanel, BorderLayout.CENTER); + viewerMainPanel.add(panelMedia, BorderLayout.PAGE_END); + + // main panel to hold settings panel and main viewer panel + JPanel mainCentralPanel = new JPanel(new BorderLayout()); + mainCentralPanel.add(panelSettings, BorderLayout.LINE_END); + mainCentralPanel.add(viewerMainPanel, BorderLayout.CENTER); + + getContentPane().setLayout(new BorderLayout()); + add(panelCreature, BorderLayout.PAGE_START); + add(mainCentralPanel, BorderLayout.CENTER); + + setIconImage(Icons.getImage(Icons.ICON_CRE_VIEWER_24)); + setTitle("Creature Animation Viewer"); + setSize(NearInfinity.getInstance().getPreferredSize()); + Center.center(this, NearInfinity.getInstance().getBounds()); + setExtendedState(NearInfinity.getInstance().getExtendedState() & ~ICONIFIED); + setVisible(true); + } + + private void cleanup() + { + SpriteUtils.clearCache(); + } + + /** Background task: Loads the selected creature and initializes the viewer. */ + private Object taskSetCreResource() throws Exception + { + ProgressMonitor pm = new ProgressMonitor(this, "Initializing controls...", " ", 0, 2); + pm.setMillisToDecideToPopup(0); + pm.setMillisToPopup(0); + try { + pm.setProgress(1); + getControlPanel().getControlModel().setSelectedCreature(getCreResource()); + getControlPanel().getControlModel().resetModified(); + getSettingsPanel().reset(); + getMediaPanel().reset(false); + pm.setProgress(2); + } finally { + pm.close(); + } + return null; + } + + /** A generic catch-all operation that can be used to evaluate exceptions thrown in a background task. */ + private void postTaskDefault(Object o, Exception e) + { + if (e != null) { + e.printStackTrace(); + JOptionPane.showMessageDialog(this, "Error: " + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); + } + } + + /** + * A method that performs a lengthy GUI-interaction task in a background thread. + * @param action a {@link Task} to execute in the background. + * @param postAction an optional {@link PostTask} that is executed when the + * background task is completed. Return value of the {@code Task} as well as + * unhandled exceptions thrown in "action" are passed to the {@code PostTask}. + * @param block whether user interaction in the window is blocked during execution of the background thread. + */ + public synchronized void performBackgroundTask(Task action, PostTask postAction, boolean block) + { + actionQueue.add(new TaskInfo(Objects.requireNonNull(action), postAction, block)); + performBackgroundTask(); + } + + private synchronized void performBackgroundTask() + { + final TaskInfo taskInfo = actionQueue.poll(); + if (worker == null && taskInfo != null) { + worker = new SwingWorker() { + @Override + protected TaskInfo doInBackground() throws Exception + { + TaskInfo retVal = taskInfo; + WindowBlocker blocker = null; + try { + if (retVal.blockWindow) { + blocker = new WindowBlocker(CreatureViewer.this); + blocker.setBlocked(true); + } + retVal.result = retVal.action.get(); + } catch (Exception e) { + if (retVal.postAction != null) { + retVal.exception = e; + } else { + e.printStackTrace(); + } + } finally { + if (blocker != null) { + blocker.setBlocked(false); + blocker = null; + } + } + return retVal; + } + }; + worker.addPropertyChangeListener(listeners); + worker.execute(); + } + } + +//-------------------------- INNER CLASSES -------------------------- + + private class Listeners implements PropertyChangeListener + { + public Listeners() + { + } + + //--------------------- Begin Interface PropertyChangeListener --------------------- + + @Override + public void propertyChange(PropertyChangeEvent event) + { + if (event.getSource() == worker) { + if ("state".equals(event.getPropertyName()) && + SwingWorker.StateValue.DONE == event.getNewValue()) { + TaskInfo retVal = null; + try { + retVal = worker.get(); + } catch (ExecutionException | InterruptedException e) { + } + if (retVal != null) { + if (retVal.postAction != null) { + retVal.postAction.accept(retVal.result, retVal.exception); + } + } + worker = null; + performBackgroundTask(); + } + } + } + + //--------------------- End Interface PropertyChangeListener --------------------- + } + + /** Represents a supplier of results. */ + public static interface Task + { + /** Gets a result. */ + Object get() throws Exception; + } + + /** + * Represents an operation that accepts two arguments: + * {@code Object} returned by and potential {@code Exception} thrown in the previous {@code Task} operation. + */ + public static interface PostTask + { + void accept(Object o, Exception e); + + default PostTask andThen(PostTask after) { + Objects.requireNonNull(after); + + return (o, e) -> { + accept(o, e); + after.accept(o, e); + }; + } + + } + + /** + * Helper structure for data related to executing tasks in the background. + */ + private static class TaskInfo + { + /** Task executed in background thread. */ + public final Task action; + /** + * Optional task executed when {@code action} thread completed. + * Parameters: {@code Object} is the result of the {@code action} task, + * {@code Exception} is an exception thrown in the {@code action} task. + */ + public final PostTask postAction; + /** Indicates whether window should be blocked during background thread execution. */ + public final boolean blockWindow; + + /** The return value of {@code action}. */ + public Object result; + /** Unhandled exceptions thrown in the background task are forwarded to the post action task. */ + public Exception exception; + + public TaskInfo(Task action, PostTask postAction, boolean block) + { + this.action = Objects.requireNonNull(action); + this.postAction = postAction; + this.blockWindow = block; + } + } +} diff --git a/src/org/infinity/resource/cre/viewer/DecoderAttributesTableModel.java b/src/org/infinity/resource/cre/viewer/DecoderAttributesTableModel.java new file mode 100644 index 000000000..3fd4e1904 --- /dev/null +++ b/src/org/infinity/resource/cre/viewer/DecoderAttributesTableModel.java @@ -0,0 +1,218 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.viewer; + +import java.awt.Component; +import java.util.Iterator; +import java.util.TreeMap; + +import javax.swing.DefaultListSelectionModel; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.SwingConstants; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.DefaultTableColumnModel; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; + +import org.infinity.resource.cre.decoder.SpriteDecoder; +import org.infinity.resource.cre.decoder.internal.DecoderAttribute; +import org.infinity.util.Misc; + +/** + * A table model that handles key/value pairs of animation attributes stored in {@code SpriteDecoder} instances. + */ +public class DecoderAttributesTableModel extends AbstractTableModel +{ + private final TreeMap attributes = new TreeMap<>(); + + private SpriteDecoder decoder; + // working array for map keys to speed up lookup + private String[] keys; + + public DecoderAttributesTableModel() + { + this(null); + } + + public DecoderAttributesTableModel(SpriteDecoder decoder) + { + super(); + setDecoder(decoder); + } + + /** Returns the currently assigned {@code SpriteDecoder}. */ + public SpriteDecoder getDecoder() { return decoder; } + + /** + * Assigns a {@code SpriteDecoder} instance to the table model and updates the list of available + * animation attributes. + * @param decoder the {@code SpriteDecoder} instance + */ + public void setDecoder(SpriteDecoder decoder) + { + if (this.decoder == null && decoder != null || + this.decoder != null && !this.decoder.equals(decoder)) { + this.decoder = decoder; + reload(); + } + } + + /** + * Discards the current list of animation attributes and loads a new list from the + * defined {@code SpriteDecoder} instance. + */ + public void reload() + { + int oldSize = attributes.size(); + attributes.clear(); + if (oldSize > 0) { + fireTableRowsDeleted(0, oldSize - 1); + } + + if (decoder != null) { + // Special: add animation id to list of attributes + attributes.put(Misc.prettifySymbol("animation_id"), String.format("0x%04x (%d)", decoder.getAnimationId(), decoder.getAnimationId())); + + for (final Iterator iter = decoder.getAttributeIterator(); iter.hasNext(); ) { + DecoderAttribute att = iter.next(); + + // skip selected attributes + if (SpriteDecoder.KEY_ANIMATION_TYPE.equals(att)) { + continue; + } + + String key = ""; + String value = ""; + switch (att.getType()) { + case BOOLEAN: + { + Boolean b = decoder.getAttribute(att); + if (b != null) { + key = att.getName(); + value = b.toString(); + } + break; + } + case DECIMAL: + { + Double d = decoder.getAttribute(att); + if (d != null) { + key = att.getName(); + value = d.toString(); + } + break; + } + case INT: + { + Integer n = decoder.getAttribute(att); + if (n != null) { + key = att.getName(); + value = n.toString(); + } + break; + } + case STRING: + { + String s = decoder.getAttribute(att); + if (s != null) { + key = att.getName(); + value = s; + } + break; + } + default: + { + Object o = decoder.getAttribute(att); + if (o != null) { + key = att.getName(); + value = o.toString(); + } + } + } + + if (!key.isEmpty()) { + attributes.put(Misc.prettifySymbol(key), value); + } + } + keys = attributes.keySet().toArray(new String[attributes.keySet().size()]); + fireTableRowsInserted(0, keys.length - 1); + } + } + + @Override + public int getRowCount() + { + return attributes.size(); + } + + @Override + public int getColumnCount() + { + return 2; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) + { + if (rowIndex >= 0 && rowIndex < keys.length && columnIndex >= 0 && columnIndex < 2) { + String key = keys[rowIndex]; + switch (columnIndex) { + case 0: + return key; + case 1: + return attributes.get(key); + } + } + return ""; + } + +//-------------------------- INNER CLASSES -------------------------- + + public static class AttributesColumnModel extends DefaultTableColumnModel + { + public AttributesColumnModel() + { + super(); + TableColumn column1 = new TableColumn(0, 125, new DefaultTableCellRenderer(), null); + column1.setHeaderValue("Name"); + column1.setMinWidth(16); + addColumn(column1); + TableColumn column2 = new TableColumn(1, 125, new DefaultTableCellRenderer(), null); + column2.setHeaderValue("Value"); + column2.setMinWidth(16); + addColumn(column2); + } + } + + public static class AttributesListSelectionModel extends DefaultListSelectionModel + { + public AttributesListSelectionModel() + { + super(); + setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + } + } + + public static class AttributesHeaderRenderer implements TableCellRenderer + { + private final DefaultTableCellRenderer renderer; + + public AttributesHeaderRenderer(JTable table) + { + super(); + this.renderer = (DefaultTableCellRenderer)table.getTableHeader().getDefaultRenderer(); + this.renderer.setHorizontalAlignment(SwingConstants.LEADING); + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, + int row, int column) + { + return renderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + } + } +} diff --git a/src/org/infinity/resource/cre/viewer/ItemSelectionModel.java b/src/org/infinity/resource/cre/viewer/ItemSelectionModel.java new file mode 100644 index 000000000..caa8ff636 --- /dev/null +++ b/src/org/infinity/resource/cre/viewer/ItemSelectionModel.java @@ -0,0 +1,166 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.viewer; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.IntStream; + +import javax.swing.AbstractListModel; +import javax.swing.ComboBoxModel; + +import org.infinity.resource.cre.decoder.internal.ItemInfo; +import org.infinity.resource.cre.decoder.internal.ItemInfo.ItemPredicate; +import org.infinity.resource.key.ResourceEntry; + +/** + * {@code ComboBoxModel} for item selection combo boxes used in the Creature Animation Viewer. + * The model allows to filter items by custom criteria. + */ +public class ItemSelectionModel extends AbstractListModel implements ComboBoxModel +{ + private final List itemList = new ArrayList<>(100); + + private boolean autoInit; + private ItemPredicate filter; + private Object selectedItem; + + public ItemSelectionModel(ItemPredicate filter, boolean autoInit) + { + super(); + this.autoInit = autoInit; + setFilter(filter); + } + + /** Returns whether content is automatically updated when the item filter is updated. */ + public boolean isAutoInit() { return autoInit; } + + /** Specify whether content is automatically updated when the item filter is updated. */ + public void setAutoInit(boolean b) + { + if (b != autoInit) { + autoInit = b; + } + } + + /** Returns the current item filter. */ + public ItemPredicate getFilter() { return filter; } + + /** Sets a new item filter. Item list will be updated automatically. */ + public void setFilter(ItemPredicate filter) + { + if (filter == null) { + filter = ItemInfo.FILTER_ALL; + } + if (!filter.equals(this.filter)) { + this.filter = filter; + if (isAutoInit()) { + reload(); + } + } + } + + /** Discards the current content and loads new content based on current settings. */ + public void reload() + { + init(); + } + + /** + * Returns the index-position of the specified object in the list. + * @param anItem a {@code ItemInfo} object, {@code ResourceEntry} object or {@code String} specifying a ITM resref. + * @return an int representing the index position, where 0 is the first position. Returns -1 + * if the item could not be found in the list. + */ + public int getIndexOf(Object anItem) + { + if (anItem instanceof ItemInfo) { + return itemList.indexOf(anItem); + } else if (anItem instanceof ResourceEntry) { + final ResourceEntry entry = (ResourceEntry)anItem; + return IntStream + .range(0, itemList.size()) + .filter(i -> entry.equals(itemList.get(i).getResourceEntry())) + .findAny() + .orElse(-1); + } else if (anItem != null) { + final String itemName = anItem.toString().trim(); + return IntStream + .range(0, itemList.size()) + .filter(i -> itemName.equalsIgnoreCase(itemList.get(i).getResourceEntry().getResourceRef())) + .findAny() + .orElse(-1); + } + return -1; + } + + /** Empties the list. */ + public void removeAllElements() + { + if (!itemList.isEmpty()) { + int oldSize = itemList.size(); + itemList.clear(); + selectedItem = null; + if (oldSize > 0) { + fireIntervalRemoved(this, 0, oldSize - 1); + } + } else { + selectedItem = null; + } + } + +//--------------------- Begin Interface ListModel --------------------- + + @Override + public int getSize() + { + return itemList.size(); + } + + @Override + public ItemInfo getElementAt(int index) + { + if (index >= 0 && index < itemList.size()) { + return itemList.get(index); + } else { + return null; + } + } + +//--------------------- End Interface ListModel --------------------- + +//--------------------- Begin Interface ComboBoxModel --------------------- + + @Override + public void setSelectedItem(Object anItem) + { + if ((selectedItem != null && !selectedItem.equals(anItem)) || + selectedItem == null && anItem != null) { + selectedItem = anItem; + fireContentsChanged(this, -1, -1); + } + } + + @Override + public Object getSelectedItem() + { + return selectedItem; + } + +//--------------------- End Interface ComboBoxModel --------------------- + + private void init() + { + removeAllElements(); + + itemList.add(ItemInfo.EMPTY); + itemList.addAll(ItemInfo.getItemList(getFilter(), true)); + if (!itemList.isEmpty()) { + fireIntervalAdded(this, 0, itemList.size() - 1); + } + + setSelectedItem(getElementAt(0)); + } +} diff --git a/src/org/infinity/resource/cre/viewer/MediaPanel.java b/src/org/infinity/resource/cre/viewer/MediaPanel.java new file mode 100644 index 000000000..3e43c6030 --- /dev/null +++ b/src/org/infinity/resource/cre/viewer/MediaPanel.java @@ -0,0 +1,804 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.viewer; + +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.List; +import java.util.Objects; + +import javax.swing.DefaultComboBoxModel; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSlider; +import javax.swing.SwingConstants; +import javax.swing.Timer; +import javax.swing.border.EtchedBorder; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.infinity.gui.ViewerUtil; +import org.infinity.gui.WindowBlocker; +import org.infinity.resource.cre.decoder.SpriteDecoder; +import org.infinity.resource.cre.viewer.icon.Icons; +import org.infinity.resource.graphics.PseudoBamDecoder.PseudoBamControl; + +/** + * This panel provides controls for animation playback and related visual options. + */ +public class MediaPanel extends JPanel +{ + // List of potential sequences to display when loading a new creature + private static final List DEFAULT_SEQUENCES = new ArrayList() {{ + add(SpriteDecoder.Sequence.STAND); + add(SpriteDecoder.Sequence.STAND2); + add(SpriteDecoder.Sequence.STAND3); + add(SpriteDecoder.Sequence.STAND_EMERGED); + add(SpriteDecoder.Sequence.PST_STAND); + add(SpriteDecoder.Sequence.STANCE); + add(SpriteDecoder.Sequence.STANCE2); + add(SpriteDecoder.Sequence.PST_STANCE); + add(SpriteDecoder.Sequence.WALK); + add(SpriteDecoder.Sequence.PST_WALK); + }}; + + private static boolean isLoop; + + static { + isLoop = true; + } + + // mapping of slider value to direction + private final HashMap directionMap = new HashMap<>(); + private final Listeners listeners = new Listeners(); + private final CreatureViewer viewer; + + private JButton bHome, bEnd, bStepBack, bStepForward, bPlay, bStop; + private DefaultComboBoxModel modelSequences; + private JComboBox cbSequences; + private JCheckBox cbLoop; + private JSlider slDirection; + private JLabel lDirection; + private JLabel lFrameCur, lFrameMax; + private PseudoBamControl controller; + private Timer timer; + private int curFrame, curCycle; + + public MediaPanel(CreatureViewer viewer) + { + super(); + this.viewer = Objects.requireNonNull(viewer); + init(); + } + + /** Returns the associated {@code CreatureViewer} instance. */ + public CreatureViewer getViewer() { return viewer; } + + /** + * Discards the current animation state and initializes a new animation. + * @param preserveState whether current media control states (sequence, frame index, ...) should be preserved if possible. + */ + public void reset(boolean preserveState) + { + SpriteDecoder.Sequence oldSequence = preserveState ? getSequence() : null; + SpriteDecoder.Direction oldDir = preserveState ? getDirection(getCurrentDirection()) : null; + int oldFrameIdx = preserveState ? getCurrentFrame() : 0; + boolean isRunning = preserveState ? isRunning() : false; + + stop(); + modelSequences.removeAllElements(); + setCurrentDirection(0); + slDirection.setMinimum(0); + slDirection.setMaximum(0); + directionMap.clear(); + controller = null; + curFrame = curCycle = -1; + updateControls(); + + SpriteDecoder decoder = getViewer().getDecoder(); + if (decoder == null) { + return; + } + + SettingsPanel settings = getViewer().getSettingsPanel(); + decoder.setAutoApplyChanges(false); + decoder.setPaletteReplacementEnabled(settings.isPaletteReplacementEnabled()); + decoder.setSelectionCircleEnabled(settings.isSelectionCircleEnabled()); + decoder.setPersonalSpaceVisible(settings.isPersonalSpaceEnabled()); + decoder.setRenderAvatar(settings.isAvatarVisible()); + decoder.setRenderHelmet(settings.isHelmetVisible()); + decoder.setRenderShield(settings.isShieldVisible()); + decoder.setRenderWeapon(settings.isWeaponVisible()); + decoder.setBoundingBoxVisible(settings.isOverlayBordersVisible()); + decoder.applyAnimationChanges(); + decoder.setAutoApplyChanges(true); + + setController(decoder.createControl()); + getViewer().getRenderPanel().setComposite(getViewer().getSettingsPanel().getComposite()); + initSequences(decoder, oldSequence); + initDirection(decoder, oldDir); + setCurrentFrame(oldFrameIdx); + updateControls(); + setRunning(isRunning); + } + + /** Returns the currently assigned BAM controller for the creature animation. */ + public PseudoBamControl getController() { return controller; } + + /** Sets the BAM controller for the creature animation. */ + protected void setController(PseudoBamControl controller) + { + if (this.controller == null && controller != null || + this.controller != null && !this.controller.equals(controller)) { + pause(); + this.controller = controller; + if (this.controller != null) { + this.controller.setMode(PseudoBamControl.Mode.SHARED); + this.controller.setSharedPerCycle(false); + } + } + } + + /** Returns the current frame rate. Rounding errors may occur. */ + public int getFrameRate() + { + int retVal = 0; + if (timer != null && timer.getDelay() > 0) { + retVal = 1000 / timer.getDelay(); + } + return retVal; + } + + /** + * Sets the frame rate for playback. + * @param fps the frame rate in frames per seconds. + * @throws IllegalArgumentException if frame rate lies outside of supported range [1, 60]. + */ + public void setFrameRate(int fps) throws IllegalArgumentException + { + if (fps < 1 || fps > 60) { + throw new IllegalArgumentException("Unsupported frame rate: " + fps + " fps"); + } + int delay = 1000 / fps; + if (timer == null) { + timer = new Timer(delay, listeners); + } else { + timer.setDelay(delay); + } + } + + /** Returns the currently displayed BAM frame. */ + public int getCurrentFrame() + { + if (getController() != null) { + return getController().cycleGetFrameIndex(); + } + return -1; + } + + /** + * Sets the current BAM frame and updates display accordingly. Does nothing if no BAM controller is available. + * @param frameIdx the new frame index in the current cycle. Frame index is capped to the available range of cycle frames. + */ + public void setCurrentFrame(int frameIdx) + { + if (getController() != null) { + if (curFrame != frameIdx || curCycle != getController().cycleGet()) { + frameIdx = Math.max(0, Math.min(getController().cycleFrameCount() - 1, frameIdx)); + getController().cycleSetFrameIndex(frameIdx); + curFrame = getController().cycleGetFrameIndex(); + getViewer().getRenderPanel().setFrame(getController()); + getViewer().getRenderPanel().updateCanvas(); + updateLabels(); + } + } + } + + /** Returns the total number of frames in the current cycle. */ + public int getMaxFrame() + { + if (getController() != null) { + return getController().cycleFrameCount(); + } + return -1; + } + + /** Returns the selected cycle index. */ + public int getCurrentCycle() + { + if (getController() != null) { + return getController().cycleGet(); + } + return -1; + } + + /** + * Sets the active cycle and updates display accordingly. + * @param cycleIdx the new cycle index. + * @throws IndexOutOfBoundsException if the cycle index does not exist. + */ + public void setCurrentCycle(int cycleIdx) throws IndexOutOfBoundsException + { + if (getController() != null) { + if (curCycle != cycleIdx) { + if (!getController().cycleSet(cycleIdx)) { + throw new IndexOutOfBoundsException("Cycle index is out of bounds: " + cycleIdx); + } + setCurrentFrame(curFrame); + curCycle = getController().cycleGet(); + } + } + } + + /** Returns the current direction slider position as numeric value. */ + public int getCurrentDirection() { return slDirection.getValue(); } + + /** Sets the specified direction index if available. Throws an {@code IndexOutOfBoundsException} otherwise. */ + public void setCurrentDirection(int pos) throws IndexOutOfBoundsException + { + if (pos >= slDirection.getMinimum() && pos <= slDirection.getMaximum()) { + slDirection.setValue(pos); + } else { + throw new IndexOutOfBoundsException("Direction value out of bounds: " + pos); + } + } + + /** Sets the specified direction if available. Throws an {@code IllegalArgumentException} otherwise. */ + public void setCurrentDirection(SpriteDecoder.Direction dir) throws IllegalArgumentException + { + if (dir != null) { + for (int i = 0; i < slDirection.getMaximum(); i++) { + if (dir.equals(getDirection(i))) { + slDirection.setValue(i); + return; + } + } + throw new IllegalArgumentException("Requested direction is not available: " + dir); + } else { + slDirection.setValue(0); + } + } + + /** Returns the currently selected animation sequence. Returns {@code null} if no sequence is active. */ + public SpriteDecoder.Sequence getSequence() + { + return modelSequences.getElementAt(cbSequences.getSelectedIndex()); + } + + /** Sets the specified sequence and loads the associated animation. */ + public void setSequence(SpriteDecoder.Sequence seq) throws IllegalArgumentException + { + int oldIdx = cbSequences.getSelectedIndex(); + int idx = modelSequences.getIndexOf(seq); + if (idx >= 0) { + if (idx != oldIdx) { + curCycle = curFrame = -1; + cbSequences.setSelectedIndex(idx); + } + } else { + throw new IllegalArgumentException("Specified sequence is not available"); + } + } + + public void loadSequence(SpriteDecoder.Sequence seq) throws IllegalArgumentException + { + SpriteDecoder decoder = getViewer().getDecoder(); + RenderPanel renderer = getViewer().getRenderPanel(); + SpriteDecoder.Direction oldDir = getDirection(getCurrentDirection()); + boolean isRunning = isRunning(); + pause(); + try { + if (!getViewer().getDecoder().loadSequence(seq)) { + throw new Exception(); + } + } catch (Exception e) { + e.printStackTrace(); + throw new IllegalArgumentException("Could not load animation sequence"); + } + + setController(decoder.createControl()); + initDirection(decoder, oldDir); + if (curCycle >= 0 && curCycle < getController().cycleCount()) { + getController().cycleSet(curCycle); + if (curFrame >= 0 && curFrame < getController().cycleFrameCount()) { + getController().cycleSetFrameIndex(curFrame); + } + } + curCycle = getController().cycleGet(); + curFrame = getController().cycleGetFrameIndex(); + + renderer.setFrame(getController()); + renderer.updateCanvas(); + setRunning(isRunning); + } + + /** Returns whether playback is repeated when the end of the animation is reached. */ + public boolean isLooping() { return cbLoop.isSelected(); } + + /** Returns whether playback is enabled. */ + public boolean isRunning() + { + if (timer != null) { + return timer.isRunning(); + } + return false; + } + + /** Enables or disables playback of the currently active animation sequence. */ + public void setRunning(boolean b) + { + if (b != isRunning()) { + if (b) { + timer.start(); + } else { + timer.stop(); + } + String iconName = isRunning() ? Icons.ICON_PAUSE : Icons.ICON_PLAY; + bPlay.setIcon(Icons.getIcon(iconName)); + updateControls(); + } + } + + /** Advances current animation by one frame. Takes loop into account. Updates controls. */ + public void advanceFrame() + { + if (getMaxFrame() > 0) { + int frameIdx = getCurrentFrame() + 1; + if (isLooping()) { + frameIdx %= getMaxFrame(); + } + + if (frameIdx < getMaxFrame()) { + setCurrentFrame(frameIdx); + } else { + pause(); + } + } + } + + /** Starts playback of the active BAM cycle. Does nothing if playback is already enabled. */ + public void play() + { + if (!isRunning()) { + setRunning(true); + updateControls(); + } + } + + /** + * Stops playback of the active BAM cycle without resetting frame position. + * Does nothing if playback is already disabled. + */ + public void pause() + { + if (isRunning()) { + setRunning(false); + updateControls(); + } + } + + /** + * Stops playback of the active BAM cycle and resets frame position. + */ + public void stop() + { + setRunning(false); + setCurrentFrame(0); + updateControls(); + } + + private void init() + { + GridBagConstraints c = new GridBagConstraints(); + + // frame info + JLabel l1 = new JLabel("Frame:"); + JLabel l2 = new JLabel("/"); + lFrameCur = new JLabel("0"); + lFrameMax = new JLabel("0"); + + JPanel pRow1 = new JPanel(new GridBagLayout()); + c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); + pRow1.add(new JPanel(), c); + c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); + pRow1.add(l1, c); + c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); + pRow1.add(lFrameCur, c); + c = ViewerUtil.setGBC(c, 3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); + pRow1.add(l2, c); + c = ViewerUtil.setGBC(c, 4, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); + pRow1.add(lFrameMax, c); + c = ViewerUtil.setGBC(c, 5, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); + pRow1.add(new JPanel(), c); + + + // playback controls + bHome = new JButton(Icons.getIcon(Icons.ICON_HOME)); + bHome.addActionListener(listeners); + + bStepBack = new JButton(Icons.getIcon(Icons.ICON_STEP_BACK)); + bStepBack.addActionListener(listeners); + + bPlay = new JButton(Icons.getIcon(Icons.ICON_PLAY)); + bPlay.addActionListener(listeners); + + bStop = new JButton(Icons.getIcon(Icons.ICON_STOP)); + bStop.addActionListener(listeners); + + bStepForward= new JButton(Icons.getIcon(Icons.ICON_STEP_FORWARD)); + bStepForward.addActionListener(listeners); + + bEnd = new JButton(Icons.getIcon(Icons.ICON_END)); + bEnd.addActionListener(listeners); + + JPanel pRow2 = new JPanel(new GridBagLayout()); + c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); + pRow2.add(bHome, c); + c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.NONE, new Insets(0, 8, 0, 0), 0, 0); + pRow2.add(bStepBack, c); + c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.NONE, new Insets(0, 8, 0, 0), 0, 0); + pRow2.add(bPlay, c); + c = ViewerUtil.setGBC(c, 3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.NONE, new Insets(0, 8, 0, 0), 0, 0); + pRow2.add(bStop, c); + c = ViewerUtil.setGBC(c, 4, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.NONE, new Insets(0, 8, 0, 0), 0, 0); + pRow2.add(bStepForward, c); + c = ViewerUtil.setGBC(c, 5, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.NONE, new Insets(0, 8, 0, 0), 0, 0); + pRow2.add(bEnd, c); + + + // direction controls + slDirection = new JSlider(SwingConstants.HORIZONTAL); + slDirection.setSnapToTicks(true); + slDirection.setPaintLabels(true); + slDirection.setPaintTicks(true); + slDirection.setSnapToTicks(true); + slDirection.addChangeListener(listeners); + l1 = new JLabel("Direction:"); + lDirection = new JLabel("S"); + + JPanel pRow3 = new JPanel(new GridBagLayout()); + c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); + pRow3.add(new JPanel(), c); + c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.NONE, new Insets(4, 0, 0, 0), 0, 0); + pRow3.add(l1, c); + c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.NONE, new Insets(4, 4, 0, 0), 0, 0); + pRow3.add(lDirection, c); + c = ViewerUtil.setGBC(c, 3, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); + pRow3.add(new JPanel(), c); + c = ViewerUtil.setGBC(c, 0, 1, 4, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 0, 0); + pRow3.add(slDirection, c); + + + // combining all rows + JPanel pColumn1 = new JPanel(new GridBagLayout()); + c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); + pColumn1.add(pRow1, c); + c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); + pColumn1.add(pRow2, c); + c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(16, 0, 0, 0), 0, 0); + pColumn1.add(pRow3, c); + + + // sequence selection, loop option + l1 = new JLabel("Sequence:"); + modelSequences = new DefaultComboBoxModel<>(); + cbSequences = new JComboBox<>(modelSequences); + cbSequences.setPrototypeDisplayValue(SpriteDecoder.Sequence.ATTACK_BACKSLASH_1H); + cbSequences.addItemListener(listeners); + + cbLoop = new JCheckBox("Loop", isLoop); + + JPanel pColumn2 = new JPanel(new GridBagLayout()); + c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); + pColumn2.add(l1, c); + c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 0, 0, 0), 0, 0); + pColumn2.add(cbSequences, c); + c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); + pColumn2.add(cbLoop, c); + + + // combining panels + JPanel panelMain = new JPanel(new GridBagLayout()); + c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); + panelMain.add(new JPanel(), c); + c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 0, 8, 0), 0, 0); + panelMain.add(pColumn1, c); + c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 32, 8, 0), 0, 0); + panelMain.add(pColumn2, c); + c = ViewerUtil.setGBC(c, 3, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); + panelMain.add(new JPanel(), c); + + JScrollPane scrollMedia = new JScrollPane(panelMain); + scrollMedia.setBorder(new EtchedBorder()); + + setLayout(new BorderLayout()); + add(scrollMedia, BorderLayout.CENTER); + + // default settings + curFrame = curCycle = -1; + setFrameRate(15); + updateControls(); + } + + /** Updates media controls based on the current animation state. */ + private void updateControls() + { + boolean loaded = getSequence() != null; + boolean running = isRunning(); + bHome.setEnabled(loaded && getCurrentFrame() > 0); + bEnd.setEnabled(loaded && getCurrentFrame() < getMaxFrame() - 1); + bStepBack.setEnabled(loaded && getCurrentFrame() > 0); + bStepForward.setEnabled(loaded && getCurrentFrame() < getMaxFrame() - 1); + bPlay.setEnabled(loaded); + bStop.setEnabled(running); + cbSequences.setEnabled(modelSequences.getSize() > 0); + cbLoop.setEnabled(loaded); + slDirection.setEnabled(loaded && slDirection.getMaximum() > slDirection.getMinimum()); + updateLabels(); + } + + /** Updates labels bvased on current control and animation state. */ + private void updateLabels() + { + lFrameCur.setText(Integer.toString(getCurrentFrame())); + lFrameMax.setText(Integer.toString(getMaxFrame() - 1)); + lDirection.setText(getDirection(getCurrentDirection()).toString()); + } + + /** Initializes the sequence list with available animation sequences and preselects a suitable default sequence. */ + private void initSequences(SpriteDecoder decoder, SpriteDecoder.Sequence defSeq) + { + modelSequences.removeAllElements(); + if (decoder != null) { + for (final SpriteDecoder.Sequence seq : SpriteDecoder.Sequence.values()) { + if (decoder.isSequenceAvailable(seq)) { + modelSequences.addElement(seq); + } + } + } + cbSequences.setEnabled(modelSequences.getSize() > 0); + + if (modelSequences.getSize() > 0) { + int idx = -1; + if (defSeq != null) { + // try given default sequence first + idx = modelSequences.getIndexOf(defSeq); + } + if (idx < 0) { + // try default sequence from list + for (final SpriteDecoder.Sequence seq : DEFAULT_SEQUENCES) { + idx = modelSequences.getIndexOf(seq); + if (idx >= 0) { + break; + } + } + } + if (idx < 0) { + // fall back to first available sequence + idx = 0; + } + cbSequences.setSelectedIndex(idx); + } + } + + /** Initializes the direction slider and label with available directions. */ + private void initDirection(SpriteDecoder decoder, SpriteDecoder.Direction defDir) + { + // discarding old data + slDirection.setMinimum(0); + slDirection.setMaximum(0); + directionMap.clear(); + + if (decoder != null) { + // collecting directions + List directions = new ArrayList<>(SpriteDecoder.Direction.values().length * 2 + 1); + for (final SpriteDecoder.Direction dir : decoder.getDirectionMap().keySet()) { + directions.add(Integer.valueOf(dir.getValue())); + } + + // sorting in descending order: maps relative slider positions to more natural directions + Collections.sort(directions, (a, b) -> b - a); + + int min = -directions.size() + 1; + int max = directions.size(); + // duplicate list entries + directions.addAll(new ArrayList(directions)); + // remove excess entries from left (negative) side + while (directions.size() > 1 && directions.get(0) > SpriteDecoder.Direction.N.getValue()) { + directions.remove(0); + min++; + } + // remove excess entries from right (positive) side + while (directions.size() > 1 && directions.get(directions.size() - 1) < SpriteDecoder.Direction.N.getValue()) { + directions.remove(directions.size() - 1); + max--; + } + + for (int i = min; i <= max; i++) { + int dirVal = directions.get(i - min); + SpriteDecoder.Direction dir = SpriteDecoder.Direction.from(dirVal); + directionMap.put(Integer.valueOf(i), dir); + } + + // initializing slider + slDirection.setMinimum(min); + slDirection.setMaximum(max); + // defining major ticks distance + Integer d1 = decoder.getDirectionMap().get(SpriteDecoder.Direction.S); + if (d1 != null) { + Integer d2 = decoder.getDirectionMap().get(SpriteDecoder.Direction.W); + if (d2 != null) { + slDirection.setMajorTickSpacing(d2 - d1); + } else { + d2 = decoder.getDirectionMap().get(SpriteDecoder.Direction.N); + if (d2 != null) { + slDirection.setMajorTickSpacing((d2 - d1) / 2); + } + } + } + slDirection.setMinorTickSpacing(1); + + // defining direction labels + Hashtable labels = new Hashtable<>(); + for (int i = min; i <= max; i++) { + SpriteDecoder.Direction dir = getDirection(i); + if (dir != null && (dir.getValue() % 4) == 0) { + labels.put(Integer.valueOf(i), new JLabel(dir.toString())); + } + } + slDirection.setLabelTable(labels); + + // restoring default direction + int value = 0; + defDir = decoder.getExistingDirection(defDir); + if (defDir != null) { + for (int i = slDirection.getMinimum(); i <= slDirection.getMaximum(); i++) { + if (defDir.equals(getDirection(i))) { + value = i; + break; + } + } + } + slDirection.setValue(value); + } + } + + /** Returns the {@code Direction} of the specified direction slider position. Defaults to {@code Direction.S}. */ + private SpriteDecoder.Direction getDirection(int index) + { + return directionMap.getOrDefault(Integer.valueOf(index), SpriteDecoder.Direction.S); + } + +//-------------------------- INNER CLASSES -------------------------- + + /** + * Listeners are outsourced to this class for cleaner code. + */ + private class Listeners implements ActionListener, ChangeListener, ItemListener + { + public Listeners() + { + } + + //--------------------- Begin Interface ActionListener --------------------- + + @Override + public void actionPerformed(ActionEvent e) + { + if (e.getSource() == timer) { + advanceFrame(); + } + else if (e.getSource() == bHome) { + setCurrentFrame(0); + updateControls(); + } + else if (e.getSource() == bEnd) { + setCurrentFrame(getMaxFrame() - 1); + updateControls(); + } + else if (e.getSource() == bStepBack) { + if (getCurrentFrame() > 0) { + setCurrentFrame(getCurrentFrame() - 1); + updateControls(); + } + } + else if (e.getSource() == bStepForward) { + if (getCurrentFrame() < getMaxFrame() - 1) { + setCurrentFrame(getCurrentFrame() + 1); + updateControls(); + } + } + else if (e.getSource() == bPlay) { + setRunning(!isRunning()); + } + else if (e.getSource() == bStop) { + stop(); + } + } + + //--------------------- End Interface ActionListener --------------------- + + //--------------------- Begin Interface ChangeListener --------------------- + @Override + public void stateChanged(ChangeEvent e) + { + if (e.getSource() == slDirection) { + SpriteDecoder.Direction dir = getDirection(slDirection.getValue()); + lDirection.setText(dir.toString()); + int cycle = getViewer().getDecoder().getDirectionMap().getOrDefault(dir, -1).intValue(); + if (cycle >= 0) { + setCurrentCycle(cycle); + } + updateControls(); + } + } + + //--------------------- End Interface ChangeListener --------------------- + + //--------------------- Begin Interface ItemListener --------------------- + @Override + public void itemStateChanged(ItemEvent e) + { + if (e.getSource() == cbSequences) { + if (e.getStateChange() == ItemEvent.SELECTED && + e.getItem() instanceof SpriteDecoder.Sequence) { + final SpriteDecoder.Sequence seq = (SpriteDecoder.Sequence)e.getItem(); + try { + WindowBlocker.blockWindow(getViewer(), true); + loadSequence(seq); + } catch (Exception ex) { + ex.printStackTrace(); + getViewer().showErrorMessage("Could not load animation sequence.\nError: " + ex.getMessage()); + } finally { + WindowBlocker.blockWindow(getViewer(), false); + } + } + } + } + + //--------------------- End Interface ItemListener --------------------- + } +} diff --git a/src/org/infinity/resource/cre/viewer/RenderPanel.java b/src/org/infinity/resource/cre/viewer/RenderPanel.java new file mode 100644 index 000000000..18103295c --- /dev/null +++ b/src/org/infinity/resource/cre/viewer/RenderPanel.java @@ -0,0 +1,524 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.viewer; + +import java.awt.AlphaComposite; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Composite; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Image; +import java.awt.Insets; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.util.Objects; + +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.UIManager; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.infinity.gui.RenderCanvas; +import org.infinity.gui.ViewerUtil; +import org.infinity.resource.graphics.ColorConvert; +import org.infinity.resource.graphics.PseudoBamDecoder.PseudoBamControl; + +/** + * This panel handles drawing background and creature animations. + */ +public class RenderPanel extends JPanel +{ + private static final Color COLOR_TRANSPARENT = new Color(0, true); + private static final float POS_REL_X = 0.5f; + private static final float POS_REL_Y = 2.0f / 3.0f; + + private final Listeners listeners = new Listeners(); + private final CreatureViewer viewer; + // storage for scroll pane view size to track view size changes + private Dimension viewSize = new Dimension(); + + private JPanel pCanvas; + private RenderCanvas rcCanvas; + private JScrollPane spCanvas; + private Image backgroundImage; + private Color backgroundColor; + private Point backgroundCenter; + private Image frame; + private Rectangle frameBounds; + private Composite composite; + private float zoom; + private boolean isFiltering; + private boolean backgroundChanged; + // rectangle (position and size) of the BAM frame + private Rectangle frameRect; + // storage for background image content that is overwritten by the animation frame + private Image frameBackground; + + public RenderPanel(CreatureViewer viewer) + { + super(); + this.viewer = Objects.requireNonNull(viewer); + init(); + } + + /** Returns the associated {@code CreatureViewer} instance. */ + public CreatureViewer getViewer() + { + return viewer; + } + + /** Returns the current background color of the panel. */ + public Color getBackgroundColor() + { + return (backgroundColor != null) ? backgroundColor : getDefaultBackgroundColor(); + } + + /** Sets the background color for the panel. */ + public void setBackgroundColor(Color c) + { + if (backgroundColor == null && c != null || + backgroundColor != null && !backgroundColor.equals(c)) { + this.backgroundColor = c; + pCanvas.setBackground(getBackgroundColor()); + backgroundChanged = true; + } + } + + /** Returns the background image used by the panel. Returns {@code null} if no image is defined. */ + public Image getBackgroundImage() + { + return backgroundImage; + } + + /** Sets the background image used by the panel. Specify {@code null} if no image should be displayed. */ + public void setBackgroundImage(Image image) + { + if (backgroundImage == null && image != null || + backgroundImage != null && !backgroundImage.equals(image)) { + if (backgroundImage != null) { + backgroundImage.flush(); + } + backgroundImage = image; + backgroundChanged = true; + + // clear frame background info + frameRect = null; + if (frameBackground != null) { + frameBackground.flush(); + } + frameBackground = null; + } + } + + /** + * Returns the center position for the creature animation on the background image. + * Returns a default center position if no explicit center is defined. + */ + public Point getBackgroundCenter() + { + if (backgroundCenter == null) { + if (backgroundImage != null) { + return new Point((int)(backgroundImage.getWidth(null) * POS_REL_X), + (int)(backgroundImage.getHeight(null) * POS_REL_Y)); + } else { + return new Point(); + } + } else { + return backgroundCenter; + } + } + + /** Sets the center position for the creature animation on the background image. */ + public void setBackgroundCenter(Point pt) + { + if (backgroundCenter == null && pt != null || + backgroundCenter != null && !backgroundCenter.equals(pt)) { + backgroundCenter = (pt != null) ? new Point(pt) : null; + } + } + + /** Returns the current zoom factor. */ + public float getZoom() { return zoom; } + + /** Sets a new zoom factor. Valid range: [0.01, 10.0] */ + public void setZoom(float zoom) throws IllegalArgumentException + { + if (zoom < 0.01f || zoom > 10.0f) { + throw new IllegalArgumentException("Zoom factor is out of range: " + zoom); + } + if (zoom != this.zoom) { + this.zoom = zoom; + } + } + + /** Returns whether bilinear filtering is enabled. */ + public boolean isFilterEnabled() + { + return isFiltering; + } + + /** Sets whether bilinear filtering is enabled {@code true} or nearest neighbor filtering is enabled {@code false}. */ + public void setFilterEnabled(boolean b) + { + if (b != isFiltering) { + isFiltering = b; + Object type = isFiltering ? RenderCanvas.TYPE_BILINEAR : RenderCanvas.TYPE_NEAREST_NEIGHBOR; + rcCanvas.setInterpolationType(type); + updateCanvas(); + } + } + + /** Returns the {@link Composite} object used to render the creature animation. */ + public Composite getComposite() + { + return (composite != null) ? composite : AlphaComposite.SrcOver; + } + + /** + * Sets the {@link Composite} object used to render the creature animation. + * Specify {@code null} to use the default {@code Composite}. + */ + public void setComposite(Composite c) + { + if (composite == null && c != null || + composite != null && !composite.equals(c)) { + composite = c; + } + } + + /** Stores the active BAM frame and frame center from the specified {@code PseudoBamControl} object internally for display. */ + public void setFrame(PseudoBamControl ctrl) + { + if (ctrl != null) { + if (frameBounds == null) { + frameBounds = new Rectangle(); + } + frameBounds.setSize(ctrl.getSharedDimension()); + frameBounds.setLocation(ctrl.getSharedOrigin()); + if (frame == null || + frame.getWidth(null) != frameBounds.width || + frame.getHeight(null) != frameBounds.height) { + frame = ColorConvert.createCompatibleImage(frameBounds.width, frameBounds.height, true); + } else { + // clear old content + Graphics2D g = (Graphics2D)frame.getGraphics(); + try { + g.setComposite(AlphaComposite.Src); + g.setColor(COLOR_TRANSPARENT); + g.fillRect(0, 0, frame.getWidth(null), frame.getHeight(null)); + } finally { + g.dispose(); + } + } + ctrl.cycleGetFrame(frame); + } else { + frameBounds = null; + if (frame != null) { + frame.flush(); + } + frame = null; + } + } + + /** + * Attempts to center the viewport on the animation sprite. + * Does nothing if the whole canvas fits into the viewport. + */ + public void centerOnSprite() + { + Dimension dimExtent = spCanvas.getViewport().getExtentSize(); + Dimension dimCanvas = rcCanvas.getSize(); + if (dimCanvas.width > dimExtent.width || + dimCanvas.height > dimExtent.height) { + // calculating center point + int x = 0; + int y = 0; + if (backgroundCenter != null) { + x = backgroundCenter.x; + y = backgroundCenter.y; + } else if (frameBounds != null) { + x = -frameBounds.x; + y = -frameBounds.y; + } + if (frameBounds != null) { + // shift viewport center to frame center + x += frameBounds.x + frameBounds.width / 2; + y += frameBounds.y + frameBounds.height / 2; + } + + x = (int)(x * getZoom()); + y = (int)(y * getZoom()); + + // adjusting viewport + int cx = Math.max(0, x - (dimExtent.width / 2)); + int cy = Math.max(0, y - (dimExtent.height / 2)); + spCanvas.getViewport().setViewPosition(new Point(cx, cy)); + } + } + + /** Calculates the canvas dimension to fit background image and sprite frame. */ + private Dimension calculateImageDimension() + { + Dimension retVal = new Dimension(1, 1); + if (frameBounds != null) { + retVal.width = Math.max(retVal.width, frameBounds.width); + retVal.height = Math.max(retVal.height, frameBounds.height); + } + if (backgroundImage != null) { + retVal.width = Math.max(retVal.width, backgroundImage.getWidth(null)); + retVal.height = Math.max(retVal.height, backgroundImage.getHeight(null)); + } + return retVal; + } + + /** Ensures that the image associated with the {@code RenderCanvas} exists and is properly initialized. */ + private void ensureCanvasImage() + { + Image img; + Dimension dim = calculateImageDimension(); + if (rcCanvas.getImage() == null || + rcCanvas.getImage().getWidth(null) != dim.width || + rcCanvas.getImage().getHeight(null) != dim.height) { + img = ColorConvert.createCompatibleImage(dim.width, dim.height, true); + backgroundChanged = true; + } else { + img = rcCanvas.getImage(); + } + + if (backgroundChanged) { + Graphics2D g = (Graphics2D)img.getGraphics(); + try { + g.setComposite(AlphaComposite.Src); + int x = (backgroundImage != null) ? (dim.width - backgroundImage.getWidth(null)) / 2 : 0; + int y = (backgroundImage != null) ? (dim.height - backgroundImage.getHeight(null)) / 2 : 0; + if (backgroundImage == null || x > 0 || y > 0) { + g.setColor(getBackgroundColor()); + g.fillRect(0, 0, img.getWidth(null), img.getHeight(null)); + } + if (backgroundImage != null) { + g.drawImage(backgroundImage, x, y, null); + } + } finally { + g.dispose(); + } + rcCanvas.setImage(img); + frameRect = null; + frameBackground = null; + backgroundChanged = false; + } + + rcCanvas.setImage(img); + } + + /** Restores the background area overwritten by the previous animation frame. */ + private void restoreFrameBackground() + { + if (frameRect != null && frameBackground != null) { + Graphics2D g = (Graphics2D)rcCanvas.getImage().getGraphics(); + try { + g.setComposite(AlphaComposite.Src); + g.drawImage(frameBackground, frameRect.x, frameRect.y, null); + } finally { + g.dispose(); + } + } + } + + /** Stores the background area to be overwritten by the current animation frame. */ + private void storeFrameBackground() + { + if (frameBounds != null) { + if (frameRect == null) { + frameRect = new Rectangle(); + } + int x = (backgroundCenter != null) ? backgroundCenter.x : -frameBounds.x; + int y = (backgroundCenter != null) ? backgroundCenter.y : -frameBounds.y; + frameRect.x = x + frameBounds.x; + frameRect.y = y + frameBounds.y; + frameRect.width = frameBounds.width; + frameRect.height = frameBounds.height; + if (frameBackground == null || + frameBackground.getWidth(null) != frameRect.width || + frameBackground.getHeight(null) != frameRect.height) { + frameBackground = ColorConvert.createCompatibleImage(frameRect.width, frameRect.height, true); + } + Graphics2D g = (Graphics2D)frameBackground.getGraphics(); + try { + g.setComposite(AlphaComposite.Src); + g.drawImage(rcCanvas.getImage(), 0, 0, frameRect.width, frameRect.height, + frameRect.x, frameRect.y, frameRect.x + frameRect.width, frameRect.y + frameRect.height, + null); + } finally { + g.dispose(); + } + } else { + frameRect = null; + if (frameBackground != null) { + frameBackground.flush(); + } + frameBackground = null; + } + } + + /** Draws the current frame if available. */ + private void drawFrame() + { + if (frame != null) { + int x = (backgroundCenter != null) ? backgroundCenter.x : -frameBounds.x; + int y = (backgroundCenter != null) ? backgroundCenter.y : -frameBounds.y; + + Graphics2D g = (Graphics2D)rcCanvas.getImage().getGraphics(); + try { + g.setComposite(getComposite()); + g.drawImage(frame, x + frameBounds.x, y + frameBounds.y, null); + } finally { + g.dispose(); + } + } + } + + /** Updates the display with the current background and BAM frame. */ + public void updateCanvas() + { + updateCanvas(false); + + // redraw canvas + repaint(); + } + + /** Internally used to update the canvas control. */ + protected void updateCanvas(boolean restore) + { + backgroundChanged |= restore; + + // recreate the canvas image if necessary + ensureCanvasImage(); + + // restore frame rect portion of background if necessary + restoreFrameBackground(); + + // store new frame rect portion of background + storeFrameBackground(); + + // draw frame + drawFrame(); + + // apply zoom factor + if (rcCanvas.getImage() != null) { + Dimension dim = new Dimension(rcCanvas.getImage().getWidth(null), rcCanvas.getImage().getHeight(null)); + dim.width *= getZoom(); + dim.height *= getZoom(); + rcCanvas.setPreferredSize(dim); + rcCanvas.setSize(dim); + } + + revalidate(); + } + + /** Returns the L&F-specific default background color of the {@code JPanel}. */ + private static Color getDefaultBackgroundColor() + { + return UIManager.getColor("Panel.background"); + } + + private void init() + { + GridBagConstraints c = new GridBagConstraints(); + + pCanvas = new JPanel(new GridBagLayout()); + rcCanvas = new RenderCanvas(null, true); + rcCanvas.addComponentListener(listeners); + + c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, + GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0); + pCanvas.add(rcCanvas, c); + + spCanvas = new JScrollPane(pCanvas); + spCanvas.setBorder(pCanvas.getBorder()); + spCanvas.getHorizontalScrollBar().setUnitIncrement(16); + spCanvas.getVerticalScrollBar().setUnitIncrement(16); + spCanvas.addComponentListener(listeners); + spCanvas.getViewport().addChangeListener(listeners); + + setLayout(new BorderLayout()); + add(spCanvas, BorderLayout.CENTER); + + // default settings + backgroundColor = null; + backgroundImage = null; + backgroundCenter = null; + frame = null; + frameBounds = null; + composite = null; + zoom = 1.0f; + isFiltering = false; + frameRect = null; + frameBackground = null; + } + +//-------------------------- INNER CLASSES -------------------------- + + private class Listeners implements ComponentListener, ChangeListener + { + public Listeners() + { + } + + //--------------------- Begin Interface ComponentListener --------------------- + + @Override + public void componentResized(ComponentEvent e) + { + if (e.getSource() == rcCanvas) { + // recenter view relative to sprite frame position and dimension + } + } + + @Override + public void componentMoved(ComponentEvent e) + { + } + + @Override + public void componentShown(ComponentEvent e) + { + } + + @Override + public void componentHidden(ComponentEvent e) + { + } + + //--------------------- End Interface ComponentListener --------------------- + + //--------------------- Begin Interface ChangeListener --------------------- + + @Override + public void stateChanged(ChangeEvent e) + { + if (e.getSource() == spCanvas.getViewport()) { + Dimension dimView = spCanvas.getViewport().getViewSize(); + if (!dimView.equals(viewSize)) { + // recenter view + viewSize.width = dimView.width; + viewSize.height = dimView.height; + centerOnSprite(); + } else { + viewSize.width = dimView.width; + viewSize.height = dimView.height; + } + } + } + + //--------------------- End Interface ChangeListener --------------------- + + } +} diff --git a/src/org/infinity/resource/cre/viewer/SettingsPanel.java b/src/org/infinity/resource/cre/viewer/SettingsPanel.java new file mode 100644 index 000000000..dc40996e1 --- /dev/null +++ b/src/org/infinity/resource/cre/viewer/SettingsPanel.java @@ -0,0 +1,553 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.viewer; + +import java.awt.AlphaComposite; +import java.awt.BorderLayout; +import java.awt.Composite; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.List; +import java.util.Objects; +import java.util.Vector; + +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.border.EtchedBorder; + +import org.infinity.gui.ViewerUtil; +import org.infinity.resource.Profile; +import org.infinity.resource.cre.viewer.bg.Backgrounds; +import org.infinity.resource.cre.viewer.icon.Icons; + +/** + * This panel provides controls for visual settings and includes a table view of creature animation attributes. + */ +public class SettingsPanel extends JPanel +{ + // Available items for zoom selection list + private static final Vector> zoomList = new Vector>() {{ + add(ItemString.with("50 %", 50)); + add(ItemString.with("100 % (original)", 100)); + add(ItemString.with("200 %", 200)); + add(ItemString.with("300 %", 300)); + add(ItemString.with("400 %", 400)); + }}; + + // Available items for frame rate selection list + private static final Vector> frameRateList = new Vector>() {{ + add(ItemString.with("1 frames/sec.", 1)); + add(ItemString.with("5 frames/sec.", 5)); + add(ItemString.with("10 frames/sec.", 10)); + add(ItemString.with("15 frames/sec. (original)", 15)); + add(ItemString.with("20 frames/sec.", 20)); + add(ItemString.with("25 frames/sec.", 25)); + add(ItemString.with("30 frames/sec.", 30)); + }}; + + private static int indexZoom, indexFrameRate, indexBackground; + private static boolean isFiltering, isBlending, isSelectionCircle, isPersonalSpace, isPaletteReplacementEnabled, + isShowAvatar, isShowHelmet, isShowShield, isShowWeapon, isShowBorders; + + static { + indexZoom = 1; // 100 % (original) + indexFrameRate = 3; // 15 fps (original) + indexBackground = 0; // System color + isFiltering = false; + isBlending = true; + isSelectionCircle = false; + isPersonalSpace = false; + isShowAvatar = true; + isShowHelmet = true; + isShowShield = true; + isShowWeapon = true; + isShowBorders = false; + isPaletteReplacementEnabled = true; + } + + private final Listeners listeners = new Listeners(); + private final CreatureViewer viewer; + + private JComboBox> cbZoom; + private JComboBox> cbFrameRate; + private JComboBox cbBackground; + private JButton bCenter; + private JCheckBox cbFiltering, cbBlending, cbSelectionCircle, cbPersonalSpace, cbPaletteReplacementEnabled, + cbShowAvatar, cbShowHelmet, cbShowShield, cbShowWeapon, cbShowBorders; + private AttributesPanel panelAttributes; + + public SettingsPanel(CreatureViewer viewer) + { + super(); + this.viewer = Objects.requireNonNull(viewer); + init(); + } + + /** Returns the associated {@code CreatureViewer} instance. */ + public CreatureViewer getViewer() { return viewer; } + + /** Discards and reloads the current settings and attributes list. */ + public void reset() + { + applyBackgroundInfo(); + getAttributesPanel().reset(); + } + + /** Returns the currently selected zoom (in percent). */ + public int getZoom() + { + int retVal = 100; + ItemString is = cbZoom.getModel().getElementAt(indexZoom); + if (is != null) { + retVal = is.getData().intValue(); + } + return retVal; + } + + private void setZoomIndex(int index) + { + if (index != indexZoom) { + if (index >= 0 && index < cbZoom.getModel().getSize()) { + indexZoom = index; + cbZoom.setSelectedIndex(indexZoom); + getViewer().getRenderPanel().setZoom((float)getZoom() / 100.0f); + getViewer().getRenderPanel().updateCanvas(); + } else { + throw new IndexOutOfBoundsException(); + } + } + } + + /** Returns the currently selected frame rate. */ + public int getFrameRate() + { + int retVal = 15; + ItemString is = cbFrameRate.getModel().getElementAt(indexFrameRate); + if (is != null) { + retVal = is.getData().intValue(); + } + return retVal; + } + + private void setFrameRateIndex(int index) + { + if (index != indexFrameRate) { + if (index >= 0 && index < cbFrameRate.getModel().getSize()) { + indexFrameRate = index; + cbFrameRate.setSelectedIndex(indexFrameRate); + getViewer().getMediaPanel().setFrameRate(getFrameRate()); + } else { + throw new IndexOutOfBoundsException(); + } + } + } + + /** Returns the selected {@code BackgroundInfo} object. */ + public Backgrounds.BackgroundInfo getBackgroundInfo() { return cbBackground.getModel().getElementAt(indexBackground); } + + private void setBackgroundInfoIndex(int index) + { + if (index != indexBackground) { + if (index >= 0 && index < cbBackground.getModel().getSize()) { + indexBackground = index; + cbBackground.setSelectedIndex(indexBackground); + applyBackgroundInfo(); + } else { + throw new IndexOutOfBoundsException(); + } + } + } + + private void applyBackgroundInfo() + { + Backgrounds.BackgroundInfo info = getBackgroundInfo(); + if (info != null) { + getViewer().getRenderPanel().setBackgroundColor(info.getColor()); + getViewer().getRenderPanel().setBackgroundImage(info.getImage()); + getViewer().getRenderPanel().setBackgroundCenter(info.getCenter()); + getViewer().getRenderPanel().updateCanvas(); + } + } + + /** Returns whether bilinear filtering is enabled for sprite display. */ + public boolean isFilteringEnabled() { return isFiltering; } + + private void setFilteringEnabled(boolean b) + { + if (isFiltering != b) { + isFiltering = b; + cbFiltering.setSelected(isFiltering); + getViewer().getRenderPanel().setFilterEnabled(isFiltering); + } + } + + /** Returns whether special blending effects are enabled for selected creature animations. */ + public boolean isBlendingEnabled() { return isBlending; } + + private void setBlendingEnabled(boolean b) + { + if (isBlending != b) { + isBlending = b; + cbBlending.setSelected(isBlending); + getViewer().getRenderPanel().setComposite(getComposite()); + } + } + + /** Returns whether the selection circle is visible. */ + public boolean isSelectionCircleEnabled() { return isSelectionCircle; } + + private void setSelectionCircleEnabled(boolean b) + { + if (isSelectionCircle != b) { + isSelectionCircle = b; + cbSelectionCircle.setSelected(isSelectionCircle); + getViewer().getMediaPanel().reset(true); + } + } + + /** Returns whether personal space is visible. */ + public boolean isPersonalSpaceEnabled() { return isPersonalSpace; } + + private void setPersonalSpaceEnabled(boolean b) + { + if (isPersonalSpace != b) { + isPersonalSpace = b; + cbPersonalSpace.setSelected(isPersonalSpace); + getViewer().getMediaPanel().reset(true); + } + } + + /** Returns whether palette replacement (full palette or false colors) is enabled. */ + public boolean isPaletteReplacementEnabled() { return isPaletteReplacementEnabled; } + + private void setPaletteReplacementEnabled(boolean b) + { + if (isPaletteReplacementEnabled != b) { + isPaletteReplacementEnabled = b; + cbPaletteReplacementEnabled.setSelected(isPaletteReplacementEnabled); + getViewer().getMediaPanel().reset(true); + } + } + + /** Returns whether the creature animation avatar is drawn. */ + public boolean isAvatarVisible() { return isShowAvatar; } + + private void setAvatarVisible(boolean b) + { + if (isShowAvatar != b) { + isShowAvatar = b; + cbShowAvatar.setSelected(isShowAvatar); + getViewer().getMediaPanel().reset(true); + } + } + + /** Returns whether the helmet overlay is drawn. */ + public boolean isHelmetVisible() { return isShowHelmet; } + + private void setHelmetVisible(boolean b) + { + if (isShowHelmet != b) { + isShowHelmet = b; + cbShowHelmet.setSelected(isShowHelmet); + getViewer().getMediaPanel().reset(true); + } + } + + /** Returns whether the shield overlay is drawn. */ + public boolean isShieldVisible() { return isShowShield; } + + private void setShieldVisible(boolean b) + { + if (isShowShield != b) { + isShowShield = b; + cbShowShield.setSelected(isShowShield); + getViewer().getMediaPanel().reset(true); + } + } + + /** Returns whether the weapon overlay is drawn. */ + public boolean isWeaponVisible() { return isShowWeapon; } + + private void setWeaponVisible(boolean b) + { + if (isShowWeapon != b) { + isShowWeapon = b; + cbShowWeapon.setSelected(isShowWeapon); + getViewer().getMediaPanel().reset(true); + } + } + + /** Returns whether colorered borders are drawn around sprite avatar and overlays. */ + public boolean isOverlayBordersVisible() { return isShowBorders; } + + private void setOverlayBordersVisible(boolean b) + { + if (isShowBorders != b) { + isShowBorders = b; + cbShowBorders.setSelected(isShowBorders); + getViewer().getMediaPanel().reset(true); + } + } + + /** Returns the {@link Composite} object valid for the currently loaded {@code SpriteDecoder} instance. */ + public Composite getComposite() + { + Composite retVal = AlphaComposite.SrcOver; + if (getViewer().getDecoder() != null && isBlendingEnabled()) { + retVal = getViewer().getDecoder().getComposite(); + } + return retVal; + } + + /** Provides access to the creature animation attributes panel. */ + public AttributesPanel getAttributesPanel() { return panelAttributes; } + + private void init() + { + GridBagConstraints c = new GridBagConstraints(); + + // selection controls + JLabel l1 = new JLabel("Zoom:"); + cbZoom = new JComboBox>(zoomList); + cbZoom.setPrototypeDisplayValue(zoomList.get(1)); + cbZoom.setSelectedIndex(indexZoom); + cbZoom.addActionListener(listeners); + + bCenter = new JButton(Icons.getIcon(Icons.ICON_CENTER)); + bCenter.setToolTipText("Center display on creature animation."); + bCenter.addActionListener(listeners); + + JLabel l2 = new JLabel("Frame rate:"); + cbFrameRate = new JComboBox>(frameRateList); + cbFrameRate.setPrototypeDisplayValue(frameRateList.get(2)); + cbFrameRate.setSelectedIndex(indexFrameRate); + cbFrameRate.addActionListener(listeners); + + JLabel l3 = new JLabel("Background:"); + List bgList = Backgrounds.getBackgrounds(Profile.getGame()); + cbBackground = new JComboBox(bgList.toArray(new Backgrounds.BackgroundInfo[bgList.size()])); + cbBackground.setPrototypeDisplayValue(Backgrounds.BackgroundList.get(1)); + cbBackground.setSelectedIndex(indexBackground); + cbBackground.addActionListener(listeners); + + JPanel panel1 = new JPanel(new GridBagLayout()); + c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_END, + GridBagConstraints.VERTICAL, new Insets(0, 0, 0, 0), 0, 0); + panel1.add(l1, c); + c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(0, 4, 0, 0), 0, 0); + panel1.add(cbZoom, c); + c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 1.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.VERTICAL, new Insets(0, 8, 0, 0), 0, 0); + panel1.add(bCenter, c); + + c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_END, + GridBagConstraints.VERTICAL, new Insets(8, 0, 0, 0), 0, 0); + panel1.add(l2, c); + c = ViewerUtil.setGBC(c, 1, 1, 2, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 4, 0, 0), 0, 0); + panel1.add(cbFrameRate, c); + + c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_END, + GridBagConstraints.VERTICAL, new Insets(8, 0, 0, 0), 0, 0); + panel1.add(l3, c); + c = ViewerUtil.setGBC(c, 1, 2, 2, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 4, 0, 0), 0, 0); + panel1.add(cbBackground, c); + + + // checkbox controls + cbFiltering = new JCheckBox("Enable filtering", isFiltering); + cbFiltering.addActionListener(listeners); + + cbBlending= new JCheckBox("Enable blending", isBlending); + cbBlending.setToolTipText("Affects only creature animations with special blending attributes (e.g. nishruus or wisps)"); + cbBlending.addActionListener(listeners); + + cbSelectionCircle = new JCheckBox("Show selection circle", isSelectionCircle); + cbSelectionCircle.addActionListener(listeners); + + cbPersonalSpace = new JCheckBox("Show personal space", isPersonalSpace); + cbPersonalSpace.addActionListener(listeners); + + cbShowAvatar = new JCheckBox("Show avatar overlay", isShowAvatar); + cbShowAvatar.addActionListener(listeners); + + cbShowHelmet = new JCheckBox("Show helmet overlay", isShowHelmet); + cbShowHelmet.addActionListener(listeners); + + cbShowShield = new JCheckBox("Show shield overlay", isShowShield); + cbShowShield.addActionListener(listeners); + + cbShowWeapon = new JCheckBox("Show weapon overlay", isShowWeapon); + cbShowWeapon.addActionListener(listeners); + + cbShowBorders = new JCheckBox("Show overlay borders", isShowBorders); + cbShowBorders.setToolTipText("Draw borders around individual segments of the creature animation."); + cbShowBorders.addActionListener(listeners); + + cbPaletteReplacementEnabled = new JCheckBox("Palette replacement", isPaletteReplacementEnabled); + cbPaletteReplacementEnabled.setToolTipText("Enable full palette or false color palette replacement."); + cbPaletteReplacementEnabled.addActionListener(listeners); + + JPanel panel2 = new JPanel(new GridBagLayout()); + c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); + panel2.add(cbFiltering, c); + c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(0, 16, 0, 0), 0, 0); + panel2.add(cbBlending, c); + + c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 0, 0, 0), 0, 0); + panel2.add(cbSelectionCircle, c); + c = ViewerUtil.setGBC(c, 1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 16, 0, 0), 0, 0); + panel2.add(cbPersonalSpace, c); + + c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 0, 0, 0), 0, 0); + panel2.add(cbShowAvatar, c); + c = ViewerUtil.setGBC(c, 1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 16, 0, 0), 0, 0); + panel2.add(cbShowHelmet, c); + + c = ViewerUtil.setGBC(c, 0, 3, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 0, 0, 0), 0, 0); + panel2.add(cbShowWeapon, c); + c = ViewerUtil.setGBC(c, 1, 3, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 16, 0, 0), 0, 0); + panel2.add(cbShowShield, c); + + c = ViewerUtil.setGBC(c, 0, 4, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 0, 0, 0), 0, 0); + panel2.add(cbShowBorders, c); + c = ViewerUtil.setGBC(c, 1, 4, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 16, 0, 0), 0, 0); + panel2.add(cbPaletteReplacementEnabled, c); + + + // attributes table panel + panelAttributes = new AttributesPanel(getViewer()); + + + // combining panels + JPanel panelMain = new JPanel(new GridBagLayout()); + c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 8, 0, 8), 0, 0); + panelMain.add(panel1, c); + + c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(16, 8, 0, 8), 0, 0); + panelMain.add(panel2, c); + + c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.BOTH, new Insets(16, 8, 8, 8), 0, 0); + panelMain.add(panelAttributes, c); + + JScrollPane scroll = new JScrollPane(panelMain); + scroll.setBorder(new EtchedBorder()); + + setLayout(new BorderLayout()); + add(scroll, BorderLayout.CENTER); + } + +//-------------------------- INNER CLASSES -------------------------- + + private class Listeners implements ActionListener + { + public Listeners() + { + } + + //--------------------- Begin Interface ActionListener --------------------- + + @Override + public void actionPerformed(ActionEvent e) + { + if (e.getSource() == bCenter) { + getViewer().getRenderPanel().centerOnSprite(); + } + else if (e.getSource() == cbZoom) { + setZoomIndex(cbZoom.getSelectedIndex()); + } + else if (e.getSource() == cbFrameRate) { + setFrameRateIndex(cbFrameRate.getSelectedIndex()); + } + else if (e.getSource() == cbBackground) { + setBackgroundInfoIndex(cbBackground.getSelectedIndex()); + } + else if (e.getSource() == cbFiltering) { + setFilteringEnabled(cbFiltering.isSelected()); + } + else if (e.getSource() == cbBlending) { + setBlendingEnabled(cbBlending.isSelected()); + } + else if (e.getSource() == cbSelectionCircle) { + setSelectionCircleEnabled(cbSelectionCircle.isSelected()); + } + else if (e.getSource() == cbPersonalSpace) { + setPersonalSpaceEnabled(cbPersonalSpace.isSelected()); + } + else if (e.getSource() == cbShowAvatar) { + setAvatarVisible(cbShowAvatar.isSelected()); + } + else if (e.getSource() == cbShowHelmet) { + setHelmetVisible(cbShowHelmet.isSelected()); + } + else if (e.getSource() == cbShowShield) { + setShieldVisible(cbShowShield.isSelected()); + } + else if (e.getSource() == cbShowWeapon) { + setWeaponVisible(cbShowWeapon.isSelected()); + } + else if (e.getSource() == cbShowBorders) { + setOverlayBordersVisible(cbShowBorders.isSelected()); + } + else if (e.getSource() == cbPaletteReplacementEnabled) { + setPaletteReplacementEnabled(cbPaletteReplacementEnabled.isSelected()); + } + } + + //--------------------- Begin Interface ActionListener --------------------- + } + + /** + * Helper class: String with attached user-defined data. + * + * @param type of the user-defined data + */ + private static class ItemString + { + private final T data; + private final String text; + + public static ItemString with(String text, T data) + { + return new ItemString(text, data); + } + + public ItemString(String text, T data) + { + this.text = (text != null) ? text : ""; + this.data = data; + } + + public T getData() { return data; } + + @Override + public String toString() + { + return text; + } + } +} diff --git a/src/org/infinity/resource/cre/viewer/bg/Backgrounds.java b/src/org/infinity/resource/cre/viewer/bg/Backgrounds.java new file mode 100644 index 000000000..36b9d85f4 --- /dev/null +++ b/src/org/infinity/resource/cre/viewer/bg/Backgrounds.java @@ -0,0 +1,176 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.viewer.bg; + +import java.awt.Color; +import java.awt.Image; +import java.awt.Point; +import java.net.URL; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import javax.imageio.ImageIO; +import javax.swing.UIManager; + +import org.infinity.resource.Profile; + +/** + * Backgrounds for the creature animation viewer. + */ +public final class Backgrounds +{ + public static final BackgroundInfo BG_WILDERNESS = new BackgroundInfo("Sword Coast wilderness tileset", new Color(0x282f12), + "bg_wilderness.jpg", new Point(640, 632), + EnumSet.complementOf(EnumSet.of(Profile.Game.PST, Profile.Game.PSTEE))); + public static final BackgroundInfo BG_CAVE = new BackgroundInfo("Cave tileset", new Color(0x010101), + "bg_cave.jpg", new Point(640, 554), + EnumSet.complementOf(EnumSet.of(Profile.Game.PST, Profile.Game.PSTEE))); + public static final BackgroundInfo BG_CITY_PST = new BackgroundInfo("Sigil city tileset", new Color(0x494131), + "pst_city.jpg", new Point(640, 580), + EnumSet.of(Profile.Game.PST, Profile.Game.PSTEE)); + public static final BackgroundInfo BG_COLOR_NONE = new BackgroundInfo("System color", null); + public static final BackgroundInfo BG_COLOR_WHITE = new BackgroundInfo("White color", Color.WHITE); + public static final BackgroundInfo BG_COLOR_BLACK = new BackgroundInfo("Black color", Color.BLACK); + public static final BackgroundInfo BG_COLOR_LIGHT_GRAY = new BackgroundInfo("Light gray color", Color.LIGHT_GRAY); + public static final BackgroundInfo BG_COLOR_GRAY = new BackgroundInfo("Gray color", Color.GRAY); + public static final BackgroundInfo BG_COLOR_RED = new BackgroundInfo("Red color", Color.RED); + public static final BackgroundInfo BG_COLOR_GREEN = new BackgroundInfo("Green color", Color.GREEN); + public static final BackgroundInfo BG_COLOR_BLUE = new BackgroundInfo("Blue color", Color.BLUE); + public static final BackgroundInfo BG_COLOR_YELLOW = new BackgroundInfo("Yellow color", Color.YELLOW); + public static final BackgroundInfo BG_COLOR_MAGENTA = new BackgroundInfo("Magenta color", Color.MAGENTA); + public static final BackgroundInfo BG_COLOR_CYAN = new BackgroundInfo("Cyan color", Color.CYAN); + + public static final List BackgroundList = new ArrayList() {{ + add(BG_COLOR_NONE); add(BG_WILDERNESS); add(BG_CAVE); add(BG_CITY_PST); + add(BG_COLOR_WHITE); add(BG_COLOR_BLACK); add(BG_COLOR_LIGHT_GRAY); add(BG_COLOR_GRAY); + add(BG_COLOR_RED); add(BG_COLOR_GREEN); add(BG_COLOR_BLUE); add(BG_COLOR_YELLOW); + add(BG_COLOR_MAGENTA); add(BG_COLOR_CYAN); + }}; + + /** Returns a list of background info instances available for the specified game. */ + public static List getBackgrounds(Profile.Game game) + { + return BackgroundList + .stream() + .filter(bi -> bi.games.contains((game != null) ? game : Profile.getGame())) + .collect(Collectors.toList()); + } + + /** + * Returns an {@link Image} object of the specified graphics filename. + * @param fileName The graphics filename. + * @return The {@link Image} object, or {@code null} on error. + */ + public static Image getImage(String fileName) + { + return getImage(null, fileName); + } + + /** + * Returns an {@link Image} object of the specified graphics filename. + * @param c A class located in the same package as the specified graphics file. The full package name + * of the class will be used to determine the correct path of the graphics file. + * @param fileName The graphics filename. + * @return The {@link Image} object, or {@code null} on error. + */ + public static Image getImage(Class c, String fileName) + { + Image retVal = null; + if (fileName == null || fileName.isEmpty()) { + return retVal; + } + + try { + URL url = getValidURL(c, fileName); + retVal = ImageIO.read(url); + } catch (Exception e) { + e.printStackTrace(); + } + return retVal; + } + + // Returns a URL instance that points to the specified filename + private static URL getValidURL(Class c, String fileName) + { + URL retVal = null; + if (fileName != null && !fileName.isEmpty()) { + if (c == null) { + retVal = ClassLoader.getSystemResource(fileName); + } + if (retVal == null) { + if (c == null) { + c = Backgrounds.class; + } + String basePath = c.getPackage().getName().replace('.', '/'); + String separator = (fileName.charAt(0) == '/') ? "" : "/"; + retVal = ClassLoader.getSystemResource(basePath + separator + fileName); + } + } + return retVal; + } + + private Backgrounds() {} + +//-------------------------- INNER CLASSES -------------------------- + + public static class BackgroundInfo + { + private final EnumSet games; + private final String label; + private final String imageName; + private final Color color; + private final Point center; + + public BackgroundInfo(String label, Color bgColor) + { + this(label, bgColor, null, null, null); + } + + public BackgroundInfo(String label, Color bgColor, String imageName, Point center, EnumSet games) + { + this.label = Objects.requireNonNull(label, "Description label required"); + this.color = bgColor; + this.imageName = imageName; + this.center = center; + this.games = (games != null) ? games : EnumSet.allOf(Profile.Game.class); + } + + /** Returns the set of games which are compatible with this {@code BackgroundInfo}. */ + public EnumSet getGames() { return games; } + + /** Returns the descriptive label of the background entry. */ + public String getLabel() { return label; } + + /** Returns the background image filename. Returns {@code null} if background image has not been defined. */ + public String getImageName() { return imageName; } + + /** Returns the background image. Returns {@code null} if no background image is defined. */ + public Image getImage() { return hasImage() ? Backgrounds.getImage(imageName) : null; } + + /** Returns the background color. */ + public Color getColor() { + Color retVal = (color != null) ? color : UIManager.getColor("Panel.background"); + if (retVal == null) { + retVal = Color.LIGHT_GRAY; + } + return retVal; + } + + /** Returns the center point of the associated image. Returns {@code null} if center has not been defined. */ + public Point getCenter() { return center; } + + /** Returns whether the background is available for the current game. */ + public boolean isAvailable() { return (games == null) || games.contains(Profile.getGame()); } + + /** Returns whether the background provides an image. */ + public boolean hasImage() { return imageName != null; } + + @Override + public String toString() { return label; } + } +} diff --git a/src/org/infinity/resource/cre/viewer/bg/bg_cave.jpg b/src/org/infinity/resource/cre/viewer/bg/bg_cave.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ddaa6b5260f59dde8f77088b62a51c89e483c1d5 GIT binary patch literal 88247 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�Q)x#g`X9ha0{w=StD|AC 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%0DavVA9dX`m4-rM=B(ewUoiQ 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%0Dfhh4H38>EffJPA%CMK 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


`o}gBbYGJX@tF3)OK9i3yytUq7K8FZ#=`jps;*- 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 z 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

Neps#JRf%i9n?vn1=0vsL1s`8H-SE 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

!LS!)M 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�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�BxIni9_r@!DnN z&&dSdZ2~vX1{lS_o#bfE8gN>dl;-0kC$R4c73dsbOX(ay5#Ljc9!;H?lTc={=nl7w zBjUxpHSdgoRUk!9dYA(y*J;9jGf^8w{{VdA8n{Q_yaY5F9&iC5Fn+QnE#=J(ihLIr z9Y6tn1O3hxsa?5^+i?jFTHG#Kij$|VHu?L)sXB}``Rf>7inm1=K1;+}o zleg;PAWO#=kH!k{Cly(V$xik|73ni$e_0y_C!hSt(ct4*0#ydTE?Ey~F^$@$j~J4h zJeZn6S3#_4V3emvtdV0%oz^!rq)X^Otk|Eh){x^`d&DpavK6iVQOIFi9-C(tAnf

-dgzHorD zvKQ}uaYMmF@2%u%B7~f;B=MShcWpo~8Zs!ZTH+0kg(>FDEsC0Jn9r*+21Zv>Kzran z=2;F#seZY_fEJ)#>P$wBBDFu>v3rZ5Q1YwJv9Cv}AUPyX0U%G#9P?=9Uz~A^Xp~=P z{^L=A-9mH*vyUDo{zeQ)-~yir6DeJmC_J!hkWVZGd~)GJga=^%0DNFXLEK1j^~-`H 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 literal 0 HcmV?d00001 diff --git a/src/org/infinity/resource/cre/viewer/icon/Icons.java b/src/org/infinity/resource/cre/viewer/icon/Icons.java new file mode 100644 index 000000000..466c3c5c5 --- /dev/null +++ b/src/org/infinity/resource/cre/viewer/icon/Icons.java @@ -0,0 +1,106 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2005 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.viewer.icon; + +import java.awt.Image; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + +import javax.swing.ImageIcon; + +public class Icons +{ + public static final String ICON_END = "btn_end.png"; + public static final String ICON_HOME = "btn_home.png"; + public static final String ICON_PAUSE = "btn_pause.png"; + public static final String ICON_PLAY = "btn_play.png"; + public static final String ICON_RESUME = "btn_resume.png"; + public static final String ICON_STEP_BACK = "btn_step_back.png"; + public static final String ICON_STEP_FORWARD = "btn_step_forward.png"; + public static final String ICON_STOP = "btn_stop.png"; + public static final String ICON_CENTER = "btn_center.png"; + + private static final Map ICONMAP = new HashMap(12); + + /** + * Returns an ImageIcon object of the specified graphics filename. + * @param name The graphics filename, can include a path relative to the current class path. + * @return The ImageIcon object, or {@code null} on error. + */ + public static ImageIcon getIcon(String name) + { + return getIcon(null, name); + } + + /** + * Returns an ImageIcon object of the specified graphics filename. + * @param c A class located in the same package as the specified graphics file. The full package name + * of the class will be used to determine the correct path of the graphics file. + * @param fileName The graphics filename. + * @return The ImageIcon object, or {@code null} on error. + */ + public static ImageIcon getIcon(Class c, String fileName) + { + URL url = getValidURL(c, fileName); + if (url != null) { + ImageIcon icon = ICONMAP.get(url); + if (icon == null) { + icon = new ImageIcon(url); + ICONMAP.put(url, icon); + } + return icon; + } + return null; + } + + /** + * Returns an Image object of the specified graphics filename. + * @param fileName The graphics filename, can include a path relative to the current class path. + * @return The Image object, or {@code null} on error. + */ + public static Image getImage(String fileName) + { + return getImage(null, fileName); + } + + /** + * Returns an Image object of the specified graphics filename. + * @param c A class located in the same package as the specified graphics file. The full package name + * of the class will be used to determine the correct path of the graphics file. + * @param fileName The graphics filename. + * @return The Image object, or {@code null} on error. + */ + public static Image getImage(Class c, String fileName) + { + ImageIcon icon = getIcon(c, fileName); + if (icon != null) { + return icon.getImage(); + } + return null; + } + + // Returns a URL instance that points to the specified filename + private static URL getValidURL(Class c, String fileName) + { + URL retVal = null; + if (fileName != null && !fileName.isEmpty()) { + if (c == null) { + retVal = ClassLoader.getSystemResource(fileName); + } + if (retVal == null) { + if (c == null) { + c = Icons.class; + } + String basePath = c.getPackage().getName().replace('.', '/'); + String separator = (fileName.charAt(0) == '/') ? "" : "/"; + retVal = ClassLoader.getSystemResource(basePath + separator + fileName); + } + } + return retVal; + } + + protected Icons(){} +} diff --git a/src/org/infinity/resource/cre/viewer/icon/btn_center.png b/src/org/infinity/resource/cre/viewer/icon/btn_center.png new file mode 100644 index 0000000000000000000000000000000000000000..2ee4dd60a638696a6bce0dd0bbdbfc4c0dc4ed64 GIT binary patch literal 733 zcmV<30wVp1P)R843TK^T3}-9X9B)6qZ(e$e*Cwa5AQ2=8-69PxA)p5(2fNeH%f^nu z38fzl!+i7JR;%^Y>-BDtP@~Z}v%kOJLqu3xTbpk2Baz5+sH*y;l>k7gRBFNDaD3n1-X0PO z?(FOgc6D|ANTpH>JU14LErN)U$z)W10{|6^#j7I0QmJ&M9j|FxoQMF^G(kj2r_=w4 z=5o0ik>JM0#tk03yu5s$h+vu~z!<~c-k!_r^?v3-KA&%0B>KXI!LF}4;W)owOVx+3WZ*~-Tu*LvjM;*0Ji|x0K5fIRutvK=;&xeQIuMLf4?Ei zvMEW@ZvYUsgcVBrT>-e(k=H?RkQ0dknBmr!Pa?Q(ktcB!VJr7W#5)CXoVI3@%N%)zLOjo00RGclu)NaY>Y7&hS5{0R4$mNDeJoK+}zysNRm`36bkPF zi~xvQZSxL5X?%SAdb8Oa3IqaQyeqK z8+*lL-EQ}n-QC^ujIpDyr)k<99+b^yZ;J%;`TQi0U0GR)|NVNF1$T!CxA1z2$m zE&#)h#i+0Yny`66R;F~qv=Npnr%9fl?K(~!o8qJ+<>JhE5a$iR7aI3N?@hKCftkiZ z>u!rz5m@MHfR7ZTzyaKKCGLIzXZw{>FQIID$z_E$eXP5=>BFvZc zO7lPa(&htTtxcamX8`9}T73^D5v(=eOVFAKfP6XUg?t1kt1|8NBVmj3TgU>w3sH2t2!q*VFy=m`^TyJL1AGt!8ut zDgOy~rz$FHwf|P8)ec&fR#COtRcqI_nl6B+kr&R@whEmYgH2&6&w)efqH{6XrSEG0 awtNG@#Jz#y8&iP*0000^@R7l6gmOXC5Koo?(wUtOvKn{^RP~{So(1lBI3oemdfSNnx z2q=V(0+}L3f^C|y^tAY&C0WU`clXVkd87TYNZ*iF*;);5Xy1uliUMMn%&yj}75t*L`u9OF!su%LN2_ Vz6nVe-HreN002ovPDHLkV1nQ@nm+&l literal 0 HcmV?d00001 diff --git a/src/org/infinity/resource/cre/viewer/icon/btn_pause.png b/src/org/infinity/resource/cre/viewer/icon/btn_pause.png new file mode 100644 index 0000000000000000000000000000000000000000..0186495743eda8bc9c01363eccbe62a884258505 GIT binary patch literal 496 zcmV5;78|xuwTf?7HDtRlWj!xqk$fK;B3cfn#9gn)zefhULl0 zmzZ-9%+5yAT2b#JdZJUaS(m6EDE z7E4K{%Fh1&r_+=IF-q?~mf8Skz;|F>1a^Sa+B!f)@V@tEcegoH9l0|8B6oBcEcLws zk|qKGY4Uo?IZf`W0K;IZR6i0o8^lcG_#3YB!Lb$C3Ty@bM**MV`8%;c@Nt@oU?FIZ zQHfFV#<!+t*i`DA+GDe9L!vR>7`Ccnt7wod+ zsD?w%!@_LjT9lXJYHb@hKmT^!b;G@?984_pp;#sxRa^u2z`?{aAKEr7+cp50$iFoG mvbI$0t+nN?h%;-;2>b?WO||?8!Jnc40000t@m3uhlAmI z%<(gDA9xAqEJT73HL_eSGrBlpDJ=aaP}BqIARnylHG8+~Q0JKAN#F(W8n^+xv$T05 z%zXp^ZDxY4IUqUQnMx{xgoxk`@B;7_7y);H-GV3^*_Z$zNEn~H@(pw{MNzM)Q#9G{ z6-9Rx-BfgFaqPa%d5FN?tjkqmf)9Xez(?RJaJ(>r@6yH1Y>Wv&0!{!AfF0m+5qHy= z?q6+Ph@5~^z!NjVxk3{+FG2ZS>8A6*8#BUbi^(DcB~iQE6C=QdT4 z5;y{nd&+g{cz#m4%baEMhejKs#t=1%0rfSU)YovL#&Nk0wvYg7S3h5^RyS6vmGzk8 v7htoaWu{qLrilEt>DLCdaMPFn{kP>8K1}ASZrZn{00000NkvXXu0mjfS$-6K literal 0 HcmV?d00001 diff --git a/src/org/infinity/resource/cre/viewer/icon/btn_resume.png b/src/org/infinity/resource/cre/viewer/icon/btn_resume.png new file mode 100644 index 0000000000000000000000000000000000000000..ceea20d316393d739a0eeed59e9938178515728e GIT binary patch literal 470 zcmV;{0V)28P)XdI6RZ$Vt?g;WA$1FwRHw@>M-v zEb=K8N0RF~UIuVm=B&0T=_+=ECwPNv0kSg$Kzgb=K+L6Ow+Bms^=k)Rv=S8+F> zykC_;R6UQg1Uv?$1czA1YvNbd5yVGY!d0Ru1y6&o@st6t&0qx&1L@YSuZ)07f&!Og zfcGoFvjnj`<)nMfL`&4jBTw`*ZRAI5q*XN1?1r`L9=3klu5*vy!9Lb9Q!Sllo7lrb k&Gh@*kvXZM-_n$Q1KT;?2IyNPwEzGB07*qoM6N<$f>RZ&H~;_u literal 0 HcmV?d00001 diff --git a/src/org/infinity/resource/cre/viewer/icon/btn_step_forward.png b/src/org/infinity/resource/cre/viewer/icon/btn_step_forward.png new file mode 100644 index 0000000000000000000000000000000000000000..a53ccf2e1bc29d622767d4364bc9523975c46860 GIT binary patch literal 408 zcmV;J0cZY+P)xsaBh@16Uu$yN9-PeZ7;OJ z(FeW+IG6bfSeEl8na|{GD)YOjiIVhzPXR3q$r#u)cBisGGfL*bu?e}0DX2UpuM&48 zbV{JkDAA=!jxvRyHsC2R1Wr5xwO40ufR&~a2oYG458x?aV@W2!mPwK*f$l9D_Wegm z-hiD{1(u}BAh`!h*J2y0Z2|skh+6&0xoLWxlcu?ab`dMI&RRt)%?{u;v0bYJkE!ii z57vMqU@vg1>3RT+fQ`Vl+wGOW(r{d0000 zzAl$1n$X%E{+J?nH`52N&ij{?+)?md>_=s`9{8&=)hd%$Ub&(4TW|KDIn`YMwtNG=gO3DY Sr-P9I0000 Date: Wed, 17 Mar 2021 15:49:21 +0100 Subject: [PATCH 051/158] Creature Animation Viewer: Fix loading sprites with unlisted animation ids --- .../cre/viewer/CreatureAnimationModel.java | 4 ++- .../cre/viewer/CreatureControlModel.java | 26 ++++++++++++++----- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/org/infinity/resource/cre/viewer/CreatureAnimationModel.java b/src/org/infinity/resource/cre/viewer/CreatureAnimationModel.java index 53046823e..ff795f0a1 100644 --- a/src/org/infinity/resource/cre/viewer/CreatureAnimationModel.java +++ b/src/org/infinity/resource/cre/viewer/CreatureAnimationModel.java @@ -200,11 +200,13 @@ public void removeElementAt(int index) * A string is parsed and returns as decimal or hexadecimal number. * Returns -1 if argument could not be converted. */ - protected int parseValue(Object o) + public int parseValue(Object o) { int retVal = -1; if (o instanceof AnimateEntry) { retVal = ((AnimateEntry)o).getValue(); + } else if (o instanceof Number) { + retVal = ((Number)o).intValue(); } else if (o != null) { String s = o.toString().trim().toLowerCase().replaceAll("\\s+", ""); try { diff --git a/src/org/infinity/resource/cre/viewer/CreatureControlModel.java b/src/org/infinity/resource/cre/viewer/CreatureControlModel.java index 5912028a9..dd0b0721a 100644 --- a/src/org/infinity/resource/cre/viewer/CreatureControlModel.java +++ b/src/org/infinity/resource/cre/viewer/CreatureControlModel.java @@ -116,8 +116,12 @@ public void setSelectedAnimation(int value) getModelAnimation().reload(); } - int idx = Math.max(0, getModelAnimation().getIndexOf(Integer.valueOf(value))); - getModelAnimation().setSelectedItem(getModelAnimation().getElementAt(idx)); + int idx = getModelAnimation().getIndexOf(Integer.valueOf(value)); + if (idx >= 0) { + getModelAnimation().setSelectedItem(getModelAnimation().getElementAt(idx)); + } else { + getModelAnimation().setSelectedItem(new AnimateEntry(value, "(Unknown)")); + } creAnimationChanged(); } @@ -292,15 +296,23 @@ public CreatureItem getSelectedCreature() /** * Returns the {@code AnimateEntry} instance of the currently selected creature animation. - * Returns {@code null} if entry is not available. + * Returns {@code null} if valid entry is not available. */ public AnimateEntry getSelectedAnimation() { - if (modelCreAnimation != null && modelCreAnimation.getSelectedItem() instanceof AnimateEntry) { - return (AnimateEntry)modelCreAnimation.getSelectedItem(); - } else { - return null; + AnimateEntry retVal = null; + if (modelCreAnimation != null && modelCreAnimation.getSelectedItem() != null) { + Object o = modelCreAnimation.getSelectedItem(); + if (o instanceof AnimateEntry) { + retVal = (AnimateEntry)o; + } else { + int value = modelCreAnimation.parseValue(o); + if (value >= 0) { + retVal = new AnimateEntry(value, "(Unknown)"); + } + } } + return retVal; } /** From cb8ae62370c2d29ae14b0f2e30d586b4e4ec1e86 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Wed, 17 Mar 2021 16:43:23 +0100 Subject: [PATCH 052/158] Creature Animation Viewer: Update display when toggling blending option --- src/org/infinity/resource/cre/viewer/RenderPanel.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/org/infinity/resource/cre/viewer/RenderPanel.java b/src/org/infinity/resource/cre/viewer/RenderPanel.java index 18103295c..a894d930d 100644 --- a/src/org/infinity/resource/cre/viewer/RenderPanel.java +++ b/src/org/infinity/resource/cre/viewer/RenderPanel.java @@ -191,6 +191,7 @@ public void setComposite(Composite c) if (composite == null && c != null || composite != null && !composite.equals(c)) { composite = c; + updateCanvas(); } } From 136dac562aa6a3d927d366e1b3383dd0f387f5cb Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Wed, 17 Mar 2021 16:44:13 +0100 Subject: [PATCH 053/158] Creature Animation Viewer: Tooltip shows current animation cycle index --- src/org/infinity/resource/cre/viewer/MediaPanel.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/org/infinity/resource/cre/viewer/MediaPanel.java b/src/org/infinity/resource/cre/viewer/MediaPanel.java index 3e43c6030..ccdaac11f 100644 --- a/src/org/infinity/resource/cre/viewer/MediaPanel.java +++ b/src/org/infinity/resource/cre/viewer/MediaPanel.java @@ -585,7 +585,14 @@ private void updateLabels() { lFrameCur.setText(Integer.toString(getCurrentFrame())); lFrameMax.setText(Integer.toString(getMaxFrame() - 1)); - lDirection.setText(getDirection(getCurrentDirection()).toString()); + SpriteDecoder.Direction dir = getDirection(getCurrentDirection()); + lDirection.setText(dir.toString()); + String text = ""; + if (getController() != null) { + int cycle = ((SpriteDecoder)getController().getDecoder()).getDirectionMap().getOrDefault(dir, -1); + text = "Cycle: " + cycle; + } + lDirection.setToolTipText(text); } /** Initializes the sequence list with available animation sequences and preselects a suitable default sequence. */ From d8a06957ab46e8b01e415790cdf2a7cdc8a10d88 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Wed, 17 Mar 2021 18:17:25 +0100 Subject: [PATCH 054/158] SpriteDecoder: Include translucency effect from equipment --- .../cre/decoder/MonsterIcewindDecoder.java | 15 -- .../resource/cre/decoder/SpriteDecoder.java | 4 +- .../cre/decoder/internal/CreatureInfo.java | 132 +++++++++++------- .../cre/decoder/internal/ItemInfo.java | 2 + 4 files changed, 82 insertions(+), 71 deletions(-) diff --git a/src/org/infinity/resource/cre/decoder/MonsterIcewindDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterIcewindDecoder.java index 8363f2254..6b269486d 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterIcewindDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterIcewindDecoder.java @@ -121,21 +121,6 @@ public List getAnimationFiles(boolean essential) } } } -// final String[] wovl; -// final String[] seqs; -// if (essential) { -// wovl = new String[] { "" }; -// seqs = new String[] { "DE", "GH", "SD", "WK" }; -// } else { -// wovl = new String[] { "", "A", "B", "C", "D", "F", "H", "M", "Q", "S", "W" }; -// seqs = new String[] { "A1", "A2", "A3", "A4", "CA", "DE", "GH", "GU", "SC", "SD", "SL", "SP", "TW", "WK" }; -// } -// for (final String wpn : wovl) { -// for (final String seq : seqs) { -// String bamFile = resref + wpn + seq + ".BAM"; -// retVal.add(resref + wpn + seq + "E.BAM"); -// } -// } return retVal; } diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index 092efe93b..88ab653ae 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -1296,7 +1296,7 @@ protected void setFalseColor(boolean b) /** Returns whether creature animation is translucent. */ public boolean isTranslucent() { - return getCreatureInfo().getTranslucency() > 0; + return getCreatureInfo().getEffectiveTranslucency() > 0; } /** Sets whether creature animation is translucent. */ @@ -2002,7 +2002,7 @@ protected void flipImageHorizontal(int frameIndex) protected void applyTranslucency(BamV1Control control) { if (control != null) { - int alpha = getCreatureInfo().getTranslucency(); + int alpha = getCreatureInfo().getEffectiveTranslucency(); int[] palette = control.getCurrentPalette(); // shadow color (alpha relative to semi-transparency of shadow) diff --git a/src/org/infinity/resource/cre/decoder/internal/CreatureInfo.java b/src/org/infinity/resource/cre/decoder/internal/CreatureInfo.java index a0cbf6f27..5bde83e07 100644 --- a/src/org/infinity/resource/cre/decoder/internal/CreatureInfo.java +++ b/src/org/infinity/resource/cre/decoder/internal/CreatureInfo.java @@ -28,6 +28,7 @@ import org.infinity.resource.cre.Item; import org.infinity.resource.cre.decoder.MonsterPlanescapeDecoder; import org.infinity.resource.cre.decoder.SpriteDecoder; +import org.infinity.resource.cre.decoder.internal.ItemInfo.EffectInfo; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.Misc; import org.infinity.util.Table2da; @@ -39,7 +40,7 @@ public class CreatureInfo { /** Value to disable allegiance override. */ - public static final int ALLEGIANCE_OVERRIDE_NONE = -1; + public static final int OVERRIDE_NONE = -1; /** * Identifies the equipment slot for an item. @@ -81,7 +82,7 @@ public CreatureInfo(SpriteDecoder decoder, CreResource cre) throws Exception { this.decoder = Objects.requireNonNull(decoder, "SpriteDecoder instance cannot be null"); this.cre = Objects.requireNonNull(cre, "CRE resource cannot be null"); - this.allegianceOverride = ALLEGIANCE_OVERRIDE_NONE; + this.allegianceOverride = OVERRIDE_NONE; init(); } @@ -122,6 +123,41 @@ public int getTranslucency() return retVal; } + /** + * Returns the translucency strength of the creature after evaluating all potential sources of translucency, + * which includes creature animation properties, creature properties (EE only) and item effects. + * Values can range from 0 (fully opaque) to 255 (fully transparent). + */ + public int getEffectiveTranslucency() + { + int retVal = -1; + + // getting translucency from item effect + for (final ItemSlots slot : ItemSlots.values()) { + ItemInfo info = getItemInfo(slot); + if (info != null) { + EffectInfo effectInfo = info.getEffectStream() + .filter(ei -> ei.getOpcode() == 66 && // translucency + ei.getTiming() == 2 && // when equipped + ei.getParameter2() == 0) // draw instantly + .findAny() + .orElse(null); + if (effectInfo != null) { + // we need inverted amount + int amount = Math.max(0, Math.min(255, effectInfo.getParameter1())); + retVal = Math.max(retVal, 255 - amount); + } + } + } + + if (retVal < 0) { + // falling back to default translucency + retVal = getTranslucency(); + } + + return retVal; + } + /** * Returns the (average or highest) class level of the creature. Returns 0 if level could not be determined. * @param highestOnly specify {@code false} to determine the average level of all (active and inactive) classes. @@ -206,17 +242,17 @@ public int getAllegiance() */ public int getAllegiance(boolean allowOverride) { - int retVal = ALLEGIANCE_OVERRIDE_NONE; + int retVal = OVERRIDE_NONE; if (allowOverride) { retVal = getAllegianceOverride(); } - if (retVal == ALLEGIANCE_OVERRIDE_NONE) { + if (retVal == OVERRIDE_NONE) { retVal = ((IsNumeric)cre.getAttribute(CreResource.CRE_ALLEGIANCE)).getValue(); } return retVal; } - /** Returns the overridden allegiance. Returns {@link #ALLEGIANCE_OVERRIDE_NONE} if allegiance has not been overridden. */ + /** Returns the overridden allegiance. Returns {@link #OVERRIDE_NONE} if allegiance has not been overridden. */ public int getAllegianceOverride() { return allegianceOverride; @@ -225,11 +261,11 @@ public int getAllegianceOverride() /** * Overrides the creature's allegiance. * @param allegiance new allegiance of the creature. Uses the same values as defined in EA.IDS. - * Specify {@link #ALLEGIANCE_OVERRIDE_NONE} to disable. + * Specify {@link #OVERRIDE_NONE} to disable. */ public void setAllegianceOverride(int allegiance) { - allegiance = Math.max(ALLEGIANCE_OVERRIDE_NONE, Math.min(255, allegiance)); + allegiance = Math.max(OVERRIDE_NONE, Math.min(255, allegiance)); if (allegianceOverride != allegiance) { allegianceOverride = allegiance; decoder.allegianceChanged(); @@ -645,57 +681,45 @@ private void initEffect(AbstractStruct as) } int fxType = (as instanceof Effect2) ? 1 : 0; + int ofsParam1 = (fxType == 1) ? 0x14 : 0x04; + int ofsParam2 = (fxType == 1) ? 0x18 : 0x08; +// int ofsSpecial = (fxType == 1) ? 0x40 : 0x2c; int opcode = ((EffectType)se).getValue(); - if (opcode == 7) { - int param1 = -1; - int param2 = -1; - if (fxType == 1) { - // EFF V2 - se = as.getAttribute(0x14); - if (se instanceof IsNumeric) { - param1 = ((IsNumeric)se).getValue(); - } - se = as.getAttribute(0x18); - if (se instanceof IsNumeric) { - param2 = ((IsNumeric)se).getValue(); - } - } else { - // EFF V1 - se = as.getAttribute(0x4); - if (se instanceof IsNumeric) { - param1 = ((IsNumeric)se).getValue(); - } - se = as.getAttribute(0x8); - if (se instanceof IsNumeric) { - param2 = ((IsNumeric)se).getValue(); - } - } - - if (param1 != -1 && param2 != -1) { - SegmentDef.SpriteType type = null; - int location = param2 & 0xf; - switch ((param2 >> 4) & 0xf) { - case 0: - type = SegmentDef.SpriteType.AVATAR; - break; - case 1: - type = SegmentDef.SpriteType.WEAPON; - break; - case 2: - type = SegmentDef.SpriteType.SHIELD; - break; - case 3: - type = SegmentDef.SpriteType.HELMET; - break; - default: - if ((param2 & 0xff) == 0xff) { - // affect all sprite colors + switch (opcode) { + case 7: // Set color + { + se = as.getAttribute(ofsParam1); + int param1 = (se instanceof IsNumeric) ? ((IsNumeric)se).getValue() : -1; + se = as.getAttribute(ofsParam2); + int param2 = (se instanceof IsNumeric) ? ((IsNumeric)se).getValue() : -1; + + if (param1 != -1 && param2 != -1) { + SegmentDef.SpriteType type = null; + int location = param2 & 0xf; + switch ((param2 >> 4) & 0xf) { + case 0: type = SegmentDef.SpriteType.AVATAR; - location = -1; - } + break; + case 1: + type = SegmentDef.SpriteType.WEAPON; + break; + case 2: + type = SegmentDef.SpriteType.SHIELD; + break; + case 3: + type = SegmentDef.SpriteType.HELMET; + break; + default: + if ((param2 & 0xff) == 0xff) { + // affect all sprite colors + type = SegmentDef.SpriteType.AVATAR; + location = -1; + } + } + getColorInfo().add(type, location, param1); } - getColorInfo().add(type, location, param1); + break; } } } diff --git a/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java b/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java index f443a8316..e16e5877b 100644 --- a/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java +++ b/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java @@ -575,6 +575,7 @@ private void init() throws IOException, Exception Misc.requireCondition(is.read(effect) == effect.length, "Could not read effect " + i + ": " + itmEntry); curOfs += effect.length; EffectInfo ei = new EffectInfo(effect); + effectsInfo.add(ei); parseEffect(ei); } } @@ -602,6 +603,7 @@ private void init() throws IOException, Exception Misc.requireCondition(is.read(effect) == effect.length, "Could not read effect " + i + ": " + itmEntry); curOfs += effect.length; EffectInfo ei = new EffectInfo(effect); + effectsInfo.add(ei); parseEffect(ei); } } From 9f6bf2b524d02cc7b856354dc0a7f0e82f2de95f Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Wed, 17 Mar 2021 20:37:35 +0100 Subject: [PATCH 055/158] Creature Animation Viewer: Fix shield slot selection --- src/org/infinity/resource/cre/viewer/CreatureControlModel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/infinity/resource/cre/viewer/CreatureControlModel.java b/src/org/infinity/resource/cre/viewer/CreatureControlModel.java index dd0b0721a..4fdb0984b 100644 --- a/src/org/infinity/resource/cre/viewer/CreatureControlModel.java +++ b/src/org/infinity/resource/cre/viewer/CreatureControlModel.java @@ -491,7 +491,7 @@ public void itemWeaponChanged() ItemInfo info = (ItemInfo)getModelWeapon().getSelectedItem(); boolean isMelee = (info.getAbilityCount() > 0) && (info.getAbility(0).getAbilityType() == 1); boolean isTwoHanded = (info.getFlags() & (1 << 1)) != 0; - isTwoHanded |= !Profile.isEnhancedEdition() || (info.getFlags() & (1 << 12)) != 0; + isTwoHanded |= Profile.isEnhancedEdition() && ((info.getFlags() & (1 << 12)) != 0); ItemPredicate shieldPred = null; if (!isTwoHanded) { shieldPred = (shieldPred == null) ? ItemInfo.FILTER_SHIELD : shieldPred.or(ItemInfo.FILTER_SHIELD); From 9a67966b5bec48409d19101516be0667faa57cae Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Wed, 17 Mar 2021 21:42:12 +0100 Subject: [PATCH 056/158] Creature Animation Viewer: Improve item filters; optimizations --- .../cre/decoder/internal/ItemInfo.java | 60 ++++++++++++++++++- .../cre/viewer/CreatureControlPanel.java | 1 + .../cre/viewer/CreatureSelectionModel.java | 3 +- 3 files changed, 60 insertions(+), 4 deletions(-) diff --git a/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java b/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java index e16e5877b..03ab04c72 100644 --- a/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java +++ b/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java @@ -41,10 +41,66 @@ public class ItemInfo implements Comparable public static final ItemPredicate FILTER_ALL = (info) -> { return true; }; /** - * This predicate returns {@code true} only if the item can be equipped and unequipped in the character inventory. + * This predicate returns {@code true} only if the item can be equipped in an equipment slot (except item slots). */ public static final ItemPredicate FILTER_EQUIPPABLE = (info) -> { - return (info.getFlags() & 0x04) == 0x04; // droppable + switch (info.getCategory()) { + case 1: // Amulets + case 2: // Armor + case 3: // Belts + case 4: // Boots + case 5: // Arrows + case 6: // Bracers + case 7: // Headgear + case 10: // Rings + case 12: // Shields + case 14: // Bullets + case 15: // Bows + case 16: // Daggers + case 17: // Maces + case 18: // Slings + case 19: // Small swords + case 20: // Large swords + case 21: // Hammers + case 22: // Morning stars + case 23: // Flails + case 24: // Darts + case 25: // Axes + case 26: // Quarterstaves + case 27: // Crossbows + case 28: // Hand-to-hand weapons + case 29: // Spears + case 30: // Halberds + case 31: // Bolts + case 32: // Cloaks/Robes + case 39: // Tattoos + case 41: // Bucklers + case 44: // Clubs + case 47: // Large shields + case 49: // Medium shields + case 53: // Small shields + case 57: // Greatswords + case 60: // Leather armor + case 61: // Studded leather + case 62: // Chain mail + case 63: // Splint mail + case 64: // Plate mail + case 65: // Full plate + case 66: // Hide armor + case 67: // Robes + case 68: // Scale mail + case 69: // Bastard swords + case 70: // Scarves + case 72: // Hats + case 73: // Gloves + case 74: // Eyeballs + case 75: // Earrings + case 76: // Teeth + case 77: // Bracelets + return true; + default: + return false; + } }; /** diff --git a/src/org/infinity/resource/cre/viewer/CreatureControlPanel.java b/src/org/infinity/resource/cre/viewer/CreatureControlPanel.java index 1a1b464c5..80498ee1b 100644 --- a/src/org/infinity/resource/cre/viewer/CreatureControlPanel.java +++ b/src/org/infinity/resource/cre/viewer/CreatureControlPanel.java @@ -567,6 +567,7 @@ else if (e.getSource() == cbCreSelection) { getControlModel().creSelectionChanged(); updateToolTip(cbCreSelection); } catch (Exception ex) { + ex.printStackTrace(); getViewer().showErrorMessage("Could not load the creature resource.\nError: " + ex.getMessage()); } } diff --git a/src/org/infinity/resource/cre/viewer/CreatureSelectionModel.java b/src/org/infinity/resource/cre/viewer/CreatureSelectionModel.java index fd3270d92..417a231ee 100644 --- a/src/org/infinity/resource/cre/viewer/CreatureSelectionModel.java +++ b/src/org/infinity/resource/cre/viewer/CreatureSelectionModel.java @@ -144,6 +144,7 @@ public Object getSelectedItem() private void init() { removeAllElements(); + selectedItem = null; ResourceFactory.getResources("CRE").stream().forEach(re -> creList.add(new CreatureItem(re))); Collections.sort(creList); @@ -151,8 +152,6 @@ private void init() if (!creList.isEmpty()) { fireIntervalAdded(this, 0, creList.size() - 1); } - - setSelectedItem(getElementAt(0)); } //-------------------------- INNER CLASSES -------------------------- From 9a7e01e80a36760a98dba05a95828d9ff1b53a7a Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Wed, 17 Mar 2021 23:19:26 +0100 Subject: [PATCH 057/158] Creature Animation Viewer: Add option to toggle translucency --- .../resource/cre/decoder/SpriteDecoder.java | 22 +++++- .../resource/cre/viewer/MediaPanel.java | 1 + .../resource/cre/viewer/SettingsPanel.java | 68 +++++++++++++------ 3 files changed, 70 insertions(+), 21 deletions(-) diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index 88ab653ae..76f4db7fd 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -643,7 +643,7 @@ public void accept(BamV1Control control, SegmentDef sd) applyFalseColors(control, sd); } - if (isTranslucent()) { + if (isTranslucencyEnabled() && isTranslucent()) { applyTranslucency(control); } } @@ -702,6 +702,7 @@ public void accept(DirDef dd, int frameIdx) private boolean showCircle; private boolean showPersonalSpace; private boolean showBoundingBox; + private boolean translucencyEnabled; private boolean paletteReplacementEnabled; private boolean renderSpriteAvatar; private boolean renderSpriteWeapon; @@ -816,6 +817,7 @@ protected SpriteDecoder(AnimationType type, CreResource cre) throws Exception this.showCircle = false; this.showPersonalSpace = false; this.showBoundingBox = false; + this.translucencyEnabled = true; this.paletteReplacementEnabled = true; this.renderSpriteAvatar = true; this.renderSpriteWeapon = true; @@ -1140,6 +1142,24 @@ public void setRenderHelmet(boolean b) } } + /** Returns whether translucency effect is applied to the creature animation. */ + public boolean isTranslucencyEnabled() + { + return translucencyEnabled; + } + + /** Sets whether translucency effect is applied to the creature animation. */ + public void setTranslucencyEnabled(boolean b) + { + if (translucencyEnabled != b) { + translucencyEnabled = b; + if (isTranslucent()) { + SpriteUtils.clearBamCache(); + spriteChanged(); + } + } + } + /** Returns whether any kind of palette replacement (full palette or false colors) is enabled. */ public boolean isPaletteReplacementEnabled() { diff --git a/src/org/infinity/resource/cre/viewer/MediaPanel.java b/src/org/infinity/resource/cre/viewer/MediaPanel.java index ccdaac11f..28a6ec11a 100644 --- a/src/org/infinity/resource/cre/viewer/MediaPanel.java +++ b/src/org/infinity/resource/cre/viewer/MediaPanel.java @@ -119,6 +119,7 @@ public void reset(boolean preserveState) SettingsPanel settings = getViewer().getSettingsPanel(); decoder.setAutoApplyChanges(false); decoder.setPaletteReplacementEnabled(settings.isPaletteReplacementEnabled()); + decoder.setTranslucencyEnabled(settings.isTranslucencyEnabled()); decoder.setSelectionCircleEnabled(settings.isSelectionCircleEnabled()); decoder.setPersonalSpaceVisible(settings.isPersonalSpaceEnabled()); decoder.setRenderAvatar(settings.isAvatarVisible()); diff --git a/src/org/infinity/resource/cre/viewer/SettingsPanel.java b/src/org/infinity/resource/cre/viewer/SettingsPanel.java index dc40996e1..12335569c 100644 --- a/src/org/infinity/resource/cre/viewer/SettingsPanel.java +++ b/src/org/infinity/resource/cre/viewer/SettingsPanel.java @@ -55,8 +55,9 @@ public class SettingsPanel extends JPanel }}; private static int indexZoom, indexFrameRate, indexBackground; - private static boolean isFiltering, isBlending, isSelectionCircle, isPersonalSpace, isPaletteReplacementEnabled, - isShowAvatar, isShowHelmet, isShowShield, isShowWeapon, isShowBorders; + private static boolean isFiltering, isBlending, isTranslucent, isSelectionCircle, isPersonalSpace, + isPaletteReplacementEnabled, isShowAvatar, isShowHelmet, isShowShield, isShowWeapon, + isShowBorders; static { indexZoom = 1; // 100 % (original) @@ -64,6 +65,7 @@ public class SettingsPanel extends JPanel indexBackground = 0; // System color isFiltering = false; isBlending = true; + isTranslucent = true; isSelectionCircle = false; isPersonalSpace = false; isShowAvatar = true; @@ -81,8 +83,8 @@ public class SettingsPanel extends JPanel private JComboBox> cbFrameRate; private JComboBox cbBackground; private JButton bCenter; - private JCheckBox cbFiltering, cbBlending, cbSelectionCircle, cbPersonalSpace, cbPaletteReplacementEnabled, - cbShowAvatar, cbShowHelmet, cbShowShield, cbShowWeapon, cbShowBorders; + private JCheckBox cbFiltering, cbBlending, cbTranslucent, cbSelectionCircle, cbPersonalSpace, + cbPaletteReplacementEnabled, cbShowAvatar, cbShowHelmet, cbShowShield, cbShowWeapon, cbShowBorders; private AttributesPanel panelAttributes; public SettingsPanel(CreatureViewer viewer) @@ -202,6 +204,18 @@ private void setBlendingEnabled(boolean b) } } + /** Returns whether translucency is enabled for selected creature animations. */ + public boolean isTranslucencyEnabled() { return isTranslucent; } + + private void setTranslucencyEnabled(boolean b) + { + if (isTranslucent != b) { + isTranslucent = b; + cbTranslucent.setSelected(isTranslucent); + getViewer().getMediaPanel().reset(true); + } + } + /** Returns whether the selection circle is visible. */ public boolean isSelectionCircleEnabled() { return isSelectionCircle; } @@ -369,10 +383,18 @@ private void init() cbFiltering = new JCheckBox("Enable filtering", isFiltering); cbFiltering.addActionListener(listeners); - cbBlending= new JCheckBox("Enable blending", isBlending); + cbBlending = new JCheckBox("Enable blending", isBlending); cbBlending.setToolTipText("Affects only creature animations with special blending attributes (e.g. nishruus or wisps)"); cbBlending.addActionListener(listeners); + cbTranslucent = new JCheckBox("Enable translucency", isTranslucent); + cbTranslucent.setToolTipText("Affects only creature animations with translucency effect (e.g. ghosts or air elementals)"); + cbTranslucent.addActionListener(listeners); + + cbPaletteReplacementEnabled = new JCheckBox("Palette replacement", isPaletteReplacementEnabled); + cbPaletteReplacementEnabled.setToolTipText("Enable full palette or false color palette replacement."); + cbPaletteReplacementEnabled.addActionListener(listeners); + cbSelectionCircle = new JCheckBox("Show selection circle", isSelectionCircle); cbSelectionCircle.addActionListener(listeners); @@ -395,10 +417,6 @@ private void init() cbShowBorders.setToolTipText("Draw borders around individual segments of the creature animation."); cbShowBorders.addActionListener(listeners); - cbPaletteReplacementEnabled = new JCheckBox("Palette replacement", isPaletteReplacementEnabled); - cbPaletteReplacementEnabled.setToolTipText("Enable full palette or false color palette replacement."); - cbPaletteReplacementEnabled.addActionListener(listeners); - JPanel panel2 = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); @@ -409,31 +427,38 @@ private void init() c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 0, 0, 0), 0, 0); - panel2.add(cbSelectionCircle, c); + panel2.add(cbTranslucent, c); c = ViewerUtil.setGBC(c, 1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 16, 0, 0), 0, 0); - panel2.add(cbPersonalSpace, c); + panel2.add(cbPaletteReplacementEnabled, c); c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 0, 0, 0), 0, 0); - panel2.add(cbShowAvatar, c); + panel2.add(cbSelectionCircle, c); c = ViewerUtil.setGBC(c, 1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 16, 0, 0), 0, 0); - panel2.add(cbShowHelmet, c); + panel2.add(cbPersonalSpace, c); c = ViewerUtil.setGBC(c, 0, 3, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 0, 0, 0), 0, 0); - panel2.add(cbShowWeapon, c); + panel2.add(cbShowAvatar, c); c = ViewerUtil.setGBC(c, 1, 3, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 16, 0, 0), 0, 0); - panel2.add(cbShowShield, c); + panel2.add(cbShowHelmet, c); c = ViewerUtil.setGBC(c, 0, 4, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 0, 0, 0), 0, 0); - panel2.add(cbShowBorders, c); + panel2.add(cbShowWeapon, c); c = ViewerUtil.setGBC(c, 1, 4, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 16, 0, 0), 0, 0); - panel2.add(cbPaletteReplacementEnabled, c); + panel2.add(cbShowShield, c); + + c = ViewerUtil.setGBC(c, 0, 5, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 0, 0, 0), 0, 0); + panel2.add(cbShowBorders, c); + c = ViewerUtil.setGBC(c, 1, 5, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 16, 0, 0), 0, 0); + panel2.add(new JPanel(), c); // attributes table panel @@ -492,6 +517,12 @@ else if (e.getSource() == cbFiltering) { else if (e.getSource() == cbBlending) { setBlendingEnabled(cbBlending.isSelected()); } + else if (e.getSource() == cbTranslucent) { + setTranslucencyEnabled(cbTranslucent.isSelected()); + } + else if (e.getSource() == cbPaletteReplacementEnabled) { + setPaletteReplacementEnabled(cbPaletteReplacementEnabled.isSelected()); + } else if (e.getSource() == cbSelectionCircle) { setSelectionCircleEnabled(cbSelectionCircle.isSelected()); } @@ -513,9 +544,6 @@ else if (e.getSource() == cbShowWeapon) { else if (e.getSource() == cbShowBorders) { setOverlayBordersVisible(cbShowBorders.isSelected()); } - else if (e.getSource() == cbPaletteReplacementEnabled) { - setPaletteReplacementEnabled(cbPaletteReplacementEnabled.isSelected()); - } } //--------------------- Begin Interface ActionListener --------------------- From 3afab15ac78b9500b7eb8546f1f161fb982c22c9 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Thu, 18 Mar 2021 11:32:57 +0100 Subject: [PATCH 058/158] Replace class: Pair by Couple --- src/org/infinity/gui/BrowserMenuBar.java | 61 +++---- src/org/infinity/gui/GameProperties.java | 30 ++-- src/org/infinity/gui/StructViewer.java | 8 +- .../infinity/gui/converter/ConvertToBam.java | 72 ++++---- .../infinity/resource/chu/ChuResource.java | 12 +- .../resource/graphics/PseudoBamDecoder.java | 14 +- src/org/infinity/search/SearchOptions.java | 36 ++-- src/org/infinity/search/SearchResource.java | 96 +++++------ src/org/infinity/updater/UpdateInfo.java | 14 +- src/org/infinity/util/BinPack2D.java | 102 +++++------ src/org/infinity/util/Pair.java | 163 ------------------ 11 files changed, 221 insertions(+), 387 deletions(-) delete mode 100644 src/org/infinity/util/Pair.java diff --git a/src/org/infinity/gui/BrowserMenuBar.java b/src/org/infinity/gui/BrowserMenuBar.java index 9eefb0aa8..0ac0f4d8c 100644 --- a/src/org/infinity/gui/BrowserMenuBar.java +++ b/src/org/infinity/gui/BrowserMenuBar.java @@ -16,7 +16,6 @@ import java.awt.KeyEventDispatcher; import java.awt.Toolkit; import java.awt.event.ActionEvent; -import static java.awt.event.ActionEvent.ALT_MASK; import java.awt.event.ActionListener; import java.awt.event.InputEvent; import java.awt.event.ItemEvent; @@ -102,11 +101,11 @@ import org.infinity.util.MassExporter; import org.infinity.util.Misc; import org.infinity.util.ObjectString; -import org.infinity.util.Pair; import org.infinity.util.Platform; import org.infinity.util.StringTable; import org.infinity.util.io.FileEx; import org.infinity.util.io.FileManager; +import org.infinity.util.tuples.Couple; public final class BrowserMenuBar extends JMenuBar implements KeyEventDispatcher { @@ -1409,12 +1408,12 @@ private SearchMenu() advancedSearch = makeMenuItem("Advanced search...", KeyEvent.VK_A, Icons.getIcon(Icons.ICON_FIND_16), -1, this); - advancedSearch.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, CTRL_MASK | ALT_MASK)); + advancedSearch.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, CTRL_MASK | ActionEvent.ALT_MASK)); advancedSearch.setToolTipText("A powerful and highly flexible search for structured resources of all kinds."); menuAdvanced.add(advancedSearch); searchResource = makeMenuItem("Legacy extended search...", KeyEvent.VK_X, Icons.getIcon(Icons.ICON_FIND_16), -1, this); - searchResource.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, CTRL_MASK | ALT_MASK)); + searchResource.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, CTRL_MASK | ActionEvent.ALT_MASK)); searchResource.setToolTipText("The original \"Extended Search\"."); menuAdvanced.add(searchResource); @@ -1635,7 +1634,7 @@ private ToolsMenu() add(toolConsole); dumpDebugInfo = new JMenuItem("Print debug info to Console", Icons.getIcon(Icons.ICON_PROPERTIES_16)); dumpDebugInfo.setToolTipText("Output to console class of current top-level window, resource and selected field in the structure viewer"); - dumpDebugInfo.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D, CTRL_MASK | ALT_MASK)); + dumpDebugInfo.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D, CTRL_MASK | ActionEvent.ALT_MASK)); dumpDebugInfo.addActionListener(this); dumpDebugInfo.setEnabled(getPrefs().getBoolean(TOOLS_DEBUG_EXTRA_INFO, false)); dumpDebugInfo.setVisible(dumpDebugInfo.isEnabled()); @@ -2719,9 +2718,9 @@ private int getSelectedButtonIndex(AbstractButton[] items, int defaultIndex) } /** Extracts entries of Game/Language pairs from the given argument. */ - private List> extractGameLanguages(String definition) + private List> extractGameLanguages(String definition) { - List> list = new ArrayList>(); + List> list = new ArrayList<>(); if (definition != null && !definition.isEmpty()) { String[] entries = definition.split(";"); if (entries != null) { @@ -2731,22 +2730,18 @@ private List> extractGameLanguages(String definition) Profile.Game game = Profile.gameFromString(elements[0]); if (game != Profile.Game.Unknown) { String lang = elements[1].trim(); - Pair pair = null; + Couple pair = null; if (lang.equalsIgnoreCase(LANGUAGE_AUTODETECT)) { - pair = new Pair(); - pair.setFirst(game.toString()); - pair.setSecond(LANGUAGE_AUTODETECT); + pair = Couple.with(game.toString(), LANGUAGE_AUTODETECT); } else if (lang.matches("[a-z]{2}_[A-Z]{2}")) { - pair = new Pair(); - pair.setFirst(game.toString()); - pair.setSecond(lang); + pair = Couple.with(game.toString(), lang); } // check if game/language pair is already in the list if (pair != null) { - for (final Pair curPair: list) { - if (curPair.getFirst().equalsIgnoreCase(pair.getFirst())) { - curPair.setSecond(pair.getSecond()); + for (final Couple curPair: list) { + if (curPair.getValue0().equalsIgnoreCase(pair.getValue0())) { + curPair.setValue1(pair.getValue1()); pair = null; break; } @@ -2765,13 +2760,13 @@ private List> extractGameLanguages(String definition) } /** Creates a formatted string out of the Game/Language pairs included in the given list. */ - private String createGameLanguages(List> list) + private String createGameLanguages(List> list) { StringBuilder sb = new StringBuilder(); if (list != null) { - for (Iterator> iter = list.iterator(); iter.hasNext();) { - Pair pair = iter.next(); - sb.append(String.format("%s=%s", pair.getFirst(), pair.getSecond())); + for (final Iterator> iter = list.iterator(); iter.hasNext();) { + Couple pair = iter.next(); + sb.append(String.format("%s=%s", pair.getValue0(), pair.getValue1())); if (iter.hasNext()) { sb.append(';'); } @@ -2781,14 +2776,14 @@ private String createGameLanguages(List> list) } /** Adds or updates the Game/Language pair in the formatted "definition" string. */ - private String updateGameLanguages(String definition, Pair pair) + private String updateGameLanguages(String definition, Couple pair) { - List> list = extractGameLanguages(definition); - if (pair != null && pair.getFirst() != null && pair.getSecond() != null) { + List> list = extractGameLanguages(definition); + if (pair != null && pair.getValue0() != null && pair.getValue1() != null) { // attempt to update existing entry first - for (final Pair curPair: list) { - if (curPair.getFirst().equalsIgnoreCase(pair.getFirst())) { - curPair.setSecond(pair.getSecond()); + for (final Couple curPair: list) { + if (curPair.getValue0().equalsIgnoreCase(pair.getValue0())) { + curPair.setValue1(pair.getValue1()); pair = null; break; } @@ -2808,12 +2803,12 @@ private String updateGameLanguages(String definition, Pair pair) private String getGameLanguage(String definition, Profile.Game game) { if (game != null && game != Profile.Game.Unknown) { - List> list = extractGameLanguages(definition); - for (Iterator> iter = list.iterator(); iter.hasNext();) { - Pair pair = iter.next(); - Profile.Game curGame = Profile.gameFromString(pair.getFirst()); + List> list = extractGameLanguages(definition); + for (final Iterator> iter = list.iterator(); iter.hasNext();) { + Couple pair = iter.next(); + Profile.Game curGame = Profile.gameFromString(pair.getValue0()); if (curGame == game) { - return pair.getSecond(); + return pair.getValue1(); } } } @@ -2850,7 +2845,7 @@ private void switchGameLanguage(String newLanguage) if (Profile.updateGameLanguage(newLanguageCode)) { languageDefinition = updateGameLanguages(languageDefinition, - new Pair(Profile.getGame().toString(), newLanguage)); + Couple.with(Profile.getGame().toString(), newLanguage)); NearInfinity.getInstance().refreshGame(); success = true; } else { diff --git a/src/org/infinity/gui/GameProperties.java b/src/org/infinity/gui/GameProperties.java index 92a2dc839..3877dafb2 100644 --- a/src/org/infinity/gui/GameProperties.java +++ b/src/org/infinity/gui/GameProperties.java @@ -41,8 +41,8 @@ import org.infinity.icon.Icons; import org.infinity.resource.Profile; import org.infinity.resource.ResourceFactory; -import org.infinity.util.Pair; import org.infinity.util.io.FileEx; +import org.infinity.util.tuples.Couple; /** * Display verbose information about the currently selected game. @@ -161,7 +161,7 @@ private void init() setLayout(new BorderLayout()); GridBagConstraints gbc = new GridBagConstraints(); - List> listControls = new ArrayList>(); + List> listControls = new ArrayList<>(); JLabel l; JTextField tf; String s; @@ -173,25 +173,25 @@ private void init() } l = new JLabel("Game type:"); tf = createReadOnlyField(s, true); - listControls.add(new Pair(l, tf)); + listControls.add(Couple.with(l, tf)); // Entry: profile name s = Profile.getProperty(Profile.Key.GET_GAME_DESC); l = new JLabel("Profile name:"); tf = createReadOnlyField((s != null) ? s : "n/a", true); - listControls.add(new Pair(l, tf)); + listControls.add(Couple.with(l, tf)); // Entry: game folder s = (Profile.getGameRoot()).toString(); l = new JLabel("Game folder:"); tf = createReadOnlyField(s, true); - listControls.add(new Pair(l, tf)); + listControls.add(Couple.with(l, tf)); if (Profile.isEnhancedEdition()) { // Entry: home folder s = Profile.getHomeRoot().toString(); l = new JLabel("Home folder:"); tf = createReadOnlyField(s, true); - listControls.add(new Pair(l, tf)); + listControls.add(Couple.with(l, tf)); // Entry: detected DLC List dlcPaths = Profile.getProperty(Profile.Key.GET_GAME_DLC_FOLDERS_AVAILABLE); @@ -205,7 +205,7 @@ private void init() } l = new JLabel("DLC archives:"); tf = createReadOnlyField(sb.toString(), true); - listControls.add(new Pair(l, tf)); + listControls.add(Couple.with(l, tf)); } // Entry: available languages @@ -224,19 +224,19 @@ private void init() } l = new JLabel("Available languages:"); tf = createReadOnlyField(sb.toString(), true); - listControls.add(new Pair(l, tf)); + listControls.add(Couple.with(l, tf)); // Entry: language s = getLanguageName(Profile.getProperty(Profile.Key.GET_GAME_LANG_FOLDER_NAME)); l = new JLabel("Current language:"); tf = createReadOnlyField(s, true); - listControls.add(new Pair(l, tf)); + listControls.add(Couple.with(l, tf)); } // Entry: Use female TLK file l = new JLabel("Uses female TLK file:"); tf = createReadOnlyField(Boolean.toString((Profile.getProperty(Profile.Key.GET_GAME_DIALOGF_FILE) != null)), true); - listControls.add(new Pair(l, tf)); + listControls.add(Couple.with(l, tf)); // Entry: game's ini file l = new JLabel("Game's INI file:"); @@ -253,19 +253,19 @@ private void init() gbc = ViewerUtil.setGBC(gbc, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); pIni.add(bEdit, gbc); - listControls.add(new Pair(l, pIni)); + listControls.add(Couple.with(l, pIni)); // adding controls from listControls to dialog JPanel pFixed = new JPanel(new GridBagLayout()); int row = 0; - for (Iterator> iter = listControls.iterator(); iter.hasNext();) { - Pair pair = iter.next(); + for (final Iterator> iter = listControls.iterator(); iter.hasNext();) { + Couple pair = iter.next(); gbc = ViewerUtil.setGBC(gbc, 0, row, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 0, 0, 0), 0, 0); - pFixed.add(pair.getFirst(), gbc); + pFixed.add(pair.getValue0(), gbc); gbc = ViewerUtil.setGBC(gbc, 1, row, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 8, 0, 0), 0, 0); - pFixed.add(pair.getSecond(), gbc); + pFixed.add(pair.getValue1(), gbc); row++; } diff --git a/src/org/infinity/gui/StructViewer.java b/src/org/infinity/gui/StructViewer.java index 70fdb85c0..c9960416c 100644 --- a/src/org/infinity/gui/StructViewer.java +++ b/src/org/infinity/gui/StructViewer.java @@ -101,8 +101,8 @@ import org.infinity.search.advanced.AdvancedSearch; import org.infinity.search.advanced.SearchOptions; import org.infinity.util.Misc; -import org.infinity.util.Pair; import org.infinity.util.StructClipboard; +import org.infinity.util.tuples.Couple; import org.infinity.util.io.ByteBufferOutputStream; import org.infinity.util.io.StreamUtils; @@ -184,7 +184,7 @@ public final class StructViewer extends JPanel implements ListSelectionListener, private JSplitPane splitv; private boolean splitterSet; private int oldSplitterHeight; - private Pair storedSelection; + private Couple storedSelection; private static JMenuItem createMenuItem(String cmd, String text, Icon icon, ActionListener l) { @@ -920,7 +920,7 @@ public void storeCurrentSelection() min = Math.min(min, idx); max = Math.max(max, idx); } - storedSelection = new Pair(min, max); + storedSelection = Couple.with(min, max); } } } @@ -929,7 +929,7 @@ public void storeCurrentSelection() public void restoreCurrentSelection() { if (storedSelection != null) { - table.setRowSelectionInterval(storedSelection.getFirst(), storedSelection.getSecond()); + table.setRowSelectionInterval(storedSelection.getValue0(), storedSelection.getValue1()); storedSelection = null; } } diff --git a/src/org/infinity/gui/converter/ConvertToBam.java b/src/org/infinity/gui/converter/ConvertToBam.java index 5af69c6b5..918788c81 100644 --- a/src/org/infinity/gui/converter/ConvertToBam.java +++ b/src/org/infinity/gui/converter/ConvertToBam.java @@ -115,11 +115,11 @@ import org.infinity.util.IniMapEntry; import org.infinity.util.IniMapSection; import org.infinity.util.Misc; -import org.infinity.util.Pair; import org.infinity.util.SimpleListModel; import org.infinity.util.io.FileEx; import org.infinity.util.io.FileManager; import org.infinity.util.io.StreamUtils; +import org.infinity.util.tuples.Couple; public class ConvertToBam extends ChildFrame implements ActionListener, PropertyChangeListener, FocusListener, ChangeListener, @@ -1855,10 +1855,10 @@ private void updateStatus() private void updateFramesList() { // updating button states - Pair bounds = getIndexBounds(listFrames.getSelectedIndices()); - bFramesUp.setEnabled(!modelFrames.isEmpty() && bounds.getFirst() > 0); - bFramesDown.setEnabled(!modelFrames.isEmpty() && bounds.getFirst() >= 0 && - bounds.getSecond() < modelFrames.getSize() - 1); + Couple bounds = getIndexBounds(listFrames.getSelectedIndices()); + bFramesUp.setEnabled(!modelFrames.isEmpty() && bounds.getValue0() > 0); + bFramesDown.setEnabled(!modelFrames.isEmpty() && bounds.getValue0() >= 0 && + bounds.getValue1() < modelFrames.getSize() - 1); miFramesRemove.setEnabled(!modelFrames.isEmpty() && !listFrames.isSelectionEmpty()); miFramesRemoveAll.setEnabled(!modelFrames.isEmpty()); miFramesDropUnused.setEnabled(!modelFrames.isEmpty()); @@ -1879,16 +1879,16 @@ private void updateFramesList() private void updateCyclesList() { listCycles.repaint(); - Pair bounds = getIndexBounds(listCycles.getSelectedIndices()); - int idx = (bounds.getFirst().compareTo(bounds.getSecond()) == 0) ? bounds.getFirst() : -1; + Couple bounds = getIndexBounds(listCycles.getSelectedIndices()); + int idx = (bounds.getValue0().compareTo(bounds.getValue1()) == 0) ? bounds.getValue0() : -1; if (idx >= 0) { listCycles.ensureIndexIsVisible(idx); } // updating button states - bCyclesUp.setEnabled(!modelCycles.isEmpty() && bounds.getFirst() > 0); - bCyclesDown.setEnabled(!modelCycles.isEmpty() && bounds.getFirst() >= 0 && - bounds.getSecond() < modelCycles.getSize() - 1); + bCyclesUp.setEnabled(!modelCycles.isEmpty() && bounds.getValue0() > 0); + bCyclesDown.setEnabled(!modelCycles.isEmpty() && bounds.getValue0() >= 0 && + bounds.getValue1() < modelCycles.getSize() - 1); bCyclesRemove.setEnabled(!listCycles.isSelectionEmpty()); bCyclesRemoveAll.setEnabled(!modelCycles.isEmpty()); @@ -1910,10 +1910,10 @@ private void updateCyclesList() private void updateCurrentCycle() { // updating button states - Pair bounds = getIndexBounds(listCurCycle.getSelectedIndices()); - bCurCycleUp.setEnabled(!modelCurCycle.isEmpty() && bounds.getFirst() > 0); - bCurCycleDown.setEnabled(!modelCurCycle.isEmpty() && bounds.getFirst() >= 0 && - bounds.getSecond() < modelCurCycle.getSize() - 1); + Couple bounds = getIndexBounds(listCurCycle.getSelectedIndices()); + bCurCycleUp.setEnabled(!modelCurCycle.isEmpty() && bounds.getValue0() > 0); + bCurCycleDown.setEnabled(!modelCurCycle.isEmpty() && bounds.getValue0() >= 0 && + bounds.getValue1() < modelCurCycle.getSize() - 1); bCurCycleAdd.setEnabled(!listFramesAvail.isSelectionEmpty()); bCurCycleRemove.setEnabled(!listCurCycle.isSelectionEmpty()); listFramesAvail.invalidate(); @@ -1923,16 +1923,16 @@ private void updateCurrentCycle() private void initCurrentCycle(int cycleIdx) { - initCurrentCycle(new Pair(cycleIdx, cycleIdx)); + initCurrentCycle(Couple.with(cycleIdx, cycleIdx)); } /** Initializes the "Current cycle" section of the Cycles tab. */ - private void initCurrentCycle(Pair cycleIndices) + private void initCurrentCycle(Couple cycleIndices) { if (cycleIndices != null) { - if (cycleIndices.getFirst().compareTo(cycleIndices.getSecond()) == 0 && - cycleIndices.getFirst() >= 0 && cycleIndices.getFirst() < modelCycles.getSize()) { - int cycleIdx = cycleIndices.getFirst(); + if (cycleIndices.getValue0().compareTo(cycleIndices.getValue1()) == 0 && + cycleIndices.getValue0() >= 0 && cycleIndices.getValue0() < modelCycles.getSize()) { + int cycleIdx = cycleIndices.getValue0(); // enabling components listFramesAvail.setEnabled(true); @@ -1960,7 +1960,7 @@ private void initCurrentCycle(Pair cycleIndices) listCurCycle.setSelectedIndices(new int[]{}); listCurCycle.setEnabled(false); - if (cycleIndices.getFirst() < 0 || cycleIndices.getSecond() < 0) { + if (cycleIndices.getValue0() < 0 || cycleIndices.getValue1() < 0) { pCurrentCycle.setBorder(BorderFactory.createTitledBorder("No cycle selected ")); } else { pCurrentCycle.setBorder(BorderFactory.createTitledBorder("Too many cycles selected ")); @@ -2067,12 +2067,12 @@ private void updateQuickPreview(RenderCanvas target, int[] indices, boolean show g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); float ratioX = (float)imgWidth / (float)image.getWidth(null); float ratioY = (float)imgHeight / (float)image.getHeight(null); - Pair minMaxRatio = new Pair(Math.min(ratioX, ratioY), Math.max(ratioX, ratioY)); - if ((float)image.getWidth(null)*minMaxRatio.getSecond() < (float)imgWidth && - (float)image.getHeight(null)*minMaxRatio.getSecond() < (float)imgHeight) { - ratio = minMaxRatio.getSecond(); + Couple minMaxRatio = Couple.with(Math.min(ratioX, ratioY), Math.max(ratioX, ratioY)); + if ((float)image.getWidth(null)*minMaxRatio.getValue1() < (float)imgWidth && + (float)image.getHeight(null)*minMaxRatio.getValue1() < (float)imgHeight) { + ratio = minMaxRatio.getValue1(); } else { - ratio = minMaxRatio.getFirst(); + ratio = minMaxRatio.getValue0(); } int newWidth = (int)((float)image.getWidth(null)*ratio); int newHeight = (int)((float)image.getHeight(null)*ratio); @@ -3151,11 +3151,11 @@ private void currentCycleAdd() { int[] indices = listFramesAvail.getSelectedIndices(); if (indices != null && indices.length > 0) { - Pair dstBounds = getIndexBounds(listCurCycle.getSelectedIndices()); - int dstIdx = dstBounds.getSecond() + 1; + Couple dstBounds = getIndexBounds(listCurCycle.getSelectedIndices()); + int dstIdx = dstBounds.getValue1() + 1; modelCurCycle.insert(dstIdx, indices); modelCycles.contentChanged(modelCurCycle.getCycle()); - listFramesAvail.setSelectedIndices(new int[]{getIndexBounds(indices).getSecond()}); + listFramesAvail.setSelectedIndices(new int[]{getIndexBounds(indices).getValue1()}); listCurCycle.setSelectedIndex(dstIdx + indices.length - 1); updateCurrentCycle(); } @@ -3811,18 +3811,18 @@ private Path setBamOutput() /** Returns the min/max values from the specified array of indices in a Pair object. */ - private Pair getIndexBounds(int[] indices) + private Couple getIndexBounds(int[] indices) { - Pair retVal = new Pair(Integer.valueOf(-1), Integer.valueOf(-1)); + Couple retVal = Couple.with(Integer.valueOf(-1), Integer.valueOf(-1)); if (indices != null && indices.length > 0) { - retVal.setFirst(Integer.valueOf(Integer.MAX_VALUE)); - retVal.setSecond(Integer.valueOf(Integer.MIN_VALUE)); + retVal.setValue0(Integer.valueOf(Integer.MAX_VALUE)); + retVal.setValue1(Integer.valueOf(Integer.MIN_VALUE)); for (int i = 0; i < indices.length; i++) { - if (indices[i] < retVal.getFirst()) { - retVal.setFirst(Integer.valueOf(indices[i])); + if (indices[i] < retVal.getValue0()) { + retVal.setValue0(Integer.valueOf(indices[i])); } - if (indices[i] > retVal.getSecond()) { - retVal.setSecond(Integer.valueOf(indices[i])); + if (indices[i] > retVal.getValue1()) { + retVal.setValue1(Integer.valueOf(indices[i])); } } } diff --git a/src/org/infinity/resource/chu/ChuResource.java b/src/org/infinity/resource/chu/ChuResource.java index f65b74599..6b4d937c2 100644 --- a/src/org/infinity/resource/chu/ChuResource.java +++ b/src/org/infinity/resource/chu/ChuResource.java @@ -27,7 +27,7 @@ import org.infinity.resource.graphics.BamResource; import org.infinity.resource.graphics.MosResource; import org.infinity.resource.key.ResourceEntry; -import org.infinity.util.Pair; +import org.infinity.util.tuples.Couple; /** * This resource describes the layout of the GUI screens (the graphics for the @@ -43,7 +43,7 @@ public final class ChuResource extends AbstractStruct implements Resource, HasVi public static final String CHU_OFFSET_CONTROLS = "Controls offset"; public static final String CHU_OFFSET_PANELS = "Panels offset"; - private List> listControls; + private List> listControls; private int ofsPanels, numPanels, sizePanels, ofsControls, numControls; private Viewer detailViewer; private StructHexViewer hexViewer; @@ -163,7 +163,7 @@ public int getControlCount() public int getControlOffset(int index) { if (index >= 0 && index < listControls.size()) { - return listControls.get(index).getFirst(); + return listControls.get(index).getValue0(); } else { return 0; } @@ -173,7 +173,7 @@ public int getControlOffset(int index) public int getControlSize(int index) { if (index >= 0 && index < listControls.size()) { - return listControls.get(index).getSecond(); + return listControls.get(index).getValue1(); } else { return 0; } @@ -303,11 +303,11 @@ private void initData(ByteBuffer buffer, int offset) for (int i = 0; i < numControls; i++, curOfs += 8) { ofs = buffer.getInt(curOfs); len = buffer.getInt(curOfs + 4); - listControls.add(new Pair<>(Integer.valueOf(ofs), Integer.valueOf(len))); + listControls.add(Couple.with(Integer.valueOf(ofs), Integer.valueOf(len))); } // adding virtual entry for determining the true size of the last control entry ofs = Math.max(ofs + len, buffer.limit()); - listControls.add(new Pair<>(Integer.valueOf(ofs), Integer.valueOf(0))); + listControls.add(Couple.with(Integer.valueOf(ofs), Integer.valueOf(0))); } } diff --git a/src/org/infinity/resource/graphics/PseudoBamDecoder.java b/src/org/infinity/resource/graphics/PseudoBamDecoder.java index 76aac23b9..45d23cb60 100644 --- a/src/org/infinity/resource/graphics/PseudoBamDecoder.java +++ b/src/org/infinity/resource/graphics/PseudoBamDecoder.java @@ -32,9 +32,9 @@ import org.infinity.resource.Profile; import org.infinity.util.BinPack2D; import org.infinity.util.DynamicArray; -import org.infinity.util.Pair; import org.infinity.util.io.FileManager; import org.infinity.util.io.StreamUtils; +import org.infinity.util.tuples.Couple; /** * A decoder that takes individual images as input and simulates a BAM structure. @@ -926,15 +926,15 @@ public boolean exportBamV2(Path fileName, DxtEncoder.DxtType dxtType, int pvrzIn // generating remaining info blocks List listFrameDataBlocks = new ArrayList(); List listFrameEntries = new ArrayList(); - List> listCycleData = new ArrayList>(listCycles.size()); + List> listCycleData = new ArrayList<>(listCycles.size()); int frameStartIndex = 0; // keeps track of current start index of frame entries int blockStartIndex = 0; // keeps track of current start index of frame data blocks for (int i = 0; i < listCycles.size(); i++) { List cycleFrames = listCycles.get(i).frames; // generating cycle entries - Pair cycle = new Pair(Short.valueOf((short)cycleFrames.size()), - Short.valueOf((short)frameStartIndex)); + Couple cycle = Couple.with(Short.valueOf((short)cycleFrames.size()), + Short.valueOf((short)frameStartIndex)); listCycleData.add(cycle); for (int j = 0; j < cycleFrames.size(); j++) { @@ -994,9 +994,9 @@ public boolean exportBamV2(Path fileName, DxtEncoder.DxtType dxtType, int pvrzIn // writing cycle entries for (int i = 0; i < listCycleData.size(); i++) { - Pair entry = listCycleData.get(i); - DynamicArray.putShort(bamData, ofs, entry.getFirst().shortValue()); - DynamicArray.putShort(bamData, ofs + 2, entry.getSecond().shortValue()); + Couple entry = listCycleData.get(i); + DynamicArray.putShort(bamData, ofs, entry.getValue0().shortValue()); + DynamicArray.putShort(bamData, ofs + 2, entry.getValue1().shortValue()); ofs += CycleEntrySize; } diff --git a/src/org/infinity/search/SearchOptions.java b/src/org/infinity/search/SearchOptions.java index dac86327a..ffe445361 100644 --- a/src/org/infinity/search/SearchOptions.java +++ b/src/org/infinity/search/SearchOptions.java @@ -19,8 +19,8 @@ import org.infinity.resource.AbstractStruct; import org.infinity.resource.ResourceFactory; import org.infinity.resource.StructEntry; -import org.infinity.util.Pair; import org.infinity.util.io.StreamUtils; +import org.infinity.util.tuples.Couple; /** * Stores a list of search options specified in SearchResource (Extended search) for use in the @@ -552,10 +552,10 @@ public static boolean matchFlags(StructEntry flag, Object value) int v; boolean isExact; - if (value instanceof Pair && ((Pair)value).getFirst() instanceof Integer && - ((Pair)value).getSecond() instanceof Boolean) { - v = (Integer)((Pair)value).getFirst(); - isExact = (Boolean)((Pair)value).getSecond(); + if (value instanceof Couple && ((Couple)value).getValue0() instanceof Integer && + ((Couple)value).getValue1() instanceof Boolean) { + v = (Integer)((Couple)value).getValue0(); + isExact = (Boolean)((Couple)value).getValue1(); } else if (value instanceof Integer) { v = (Integer)value; isExact = false; @@ -589,11 +589,11 @@ public static boolean matchNumber(StructEntry number, Object value) int n1, n2, n3; if (value instanceof Integer) { n1 = n2 = (Integer)value; - } else if (value instanceof Pair && - ((Pair)value).getFirst() instanceof Integer && - ((Pair)value).getSecond() instanceof Integer) { - n1 = (Integer)((Pair)value).getFirst(); - n2 = (Integer)((Pair)value).getSecond(); + } else if (value instanceof Couple && + ((Couple)value).getValue0() instanceof Integer && + ((Couple)value).getValue1() instanceof Integer) { + n1 = (Integer)((Couple)value).getValue0(); + n2 = (Integer)((Couple)value).getValue1(); if (n1 > n2) { int tmp = n1; n1 = n2; n2 = tmp; } } else { return false; @@ -614,10 +614,10 @@ public static boolean matchNumber(StructEntry number, Object value) public static boolean matchCustomFilter(AbstractStruct struct, Object match) { - if (struct != null && match != null && match instanceof Pair && - ((Pair)match).getFirst() instanceof String) { - String fieldName = (String)((Pair)match).getFirst(); - Object value = ((Pair)match).getSecond(); + if (struct != null && match != null && match instanceof Couple && + ((Couple)match).getValue0() instanceof String) { + String fieldName = (String)((Couple)match).getValue0(); + Object value = ((Couple)match).getValue1(); if (!fieldName.isEmpty() && value != null) { boolean bRet = false; final List structList = struct.getFlatFields(); @@ -630,11 +630,11 @@ public static boolean matchCustomFilter(AbstractStruct struct, Object match) // field name matches if (value instanceof String) { bRet |= matchResourceRef(entry, value, false) || matchString(entry, value, false, false); - } else if (value instanceof Pair) { - if (((Pair)value).getFirst() instanceof Integer) { - if (((Pair)value).getSecond() instanceof Integer) { + } else if (value instanceof Couple) { + if (((Couple)value).getValue0() instanceof Integer) { + if (((Couple)value).getValue1() instanceof Integer) { bRet |= matchNumber(entry, value); - } else if (((Pair)value).getSecond() instanceof Boolean) { + } else if (((Couple)value).getValue1() instanceof Boolean) { bRet |= matchFlags(entry, value); } } diff --git a/src/org/infinity/search/SearchResource.java b/src/org/infinity/search/SearchResource.java index 5f39fc9c4..253f5b723 100644 --- a/src/org/infinity/search/SearchResource.java +++ b/src/org/infinity/search/SearchResource.java @@ -98,9 +98,9 @@ import org.infinity.util.Debugging; import org.infinity.util.IdsMapEntry; import org.infinity.util.Misc; -import org.infinity.util.Pair; import org.infinity.util.SimpleListModel; import org.infinity.util.io.StreamUtils; +import org.infinity.util.tuples.Couple; public class SearchResource extends ChildFrame implements ActionListener, PropertyChangeListener, Runnable @@ -1535,17 +1535,17 @@ public SearchOptions getOptions() return retVal; } - public Pair getOptionEffect() + public Couple getOptionEffect() { return Utils.getRangeValues(cbOptions[ID_Effect], sOpcode); } - public Pair getOptionParam1() + public Couple getOptionParam1() { return Utils.getRangeValues(cbOptions[ID_Param1], sParam1); } - public Pair getOptionParam2() + public Couple getOptionParam2() { return Utils.getRangeValues(cbOptions[ID_Param2], sParam2); } @@ -1965,12 +1965,12 @@ public String getOptionAppearance() return Utils.getObjectFromString(cbOptions[ID_Appearance], cbAppearance).toString(); } - public Pair getOptionPrice() + public Couple getOptionPrice() { return Utils.getRangeValues(cbOptions[ID_Price], sPrice); } - public Pair getOptionEnchantment() + public Couple getOptionEnchantment() { return Utils.getRangeValues(cbOptions[ID_Enchantment], sEnchantment); } @@ -2299,17 +2299,17 @@ public int getOptionType() return (Integer)Utils.getObjectFromString(cbOptions[ID_Type], cbType); } - public Pair getOptionSpeed() + public Couple getOptionSpeed() { return Utils.getRangeValues(cbOptions[ID_Speed], sSpeed); } - public Pair getOptionTrapSize() + public Couple getOptionTrapSize() { return Utils.getRangeValues(cbOptions[ID_TrapSize], sTrapSize); } - public Pair getOptionExplosionSize() + public Couple getOptionExplosionSize() { return Utils.getRangeValues(cbOptions[ID_ExplosionSize], sExplosionSize); } @@ -2684,7 +2684,7 @@ public int getOptionSecondaryType() return (Integer)Utils.getObjectFromString(cbOptions[ID_SecondaryType], cbSecondary); } - public Pair getOptionLevel() + public Couple getOptionLevel() { return Utils.getRangeValues(cbOptions[ID_Level], sLevel); } @@ -3047,27 +3047,27 @@ public int getOptionType() return (Integer)Utils.getObjectFromString(cbOptions[ID_Type], cbType); } - public Pair getOptionDepreciationRate() + public Couple getOptionDepreciationRate() { return Utils.getRangeValues(cbOptions[ID_Depreciation], sDepreciation); } - public Pair getOptionSellMarkup() + public Couple getOptionSellMarkup() { return Utils.getRangeValues(cbOptions[ID_SellMarkup], sSellMarkup); } - public Pair getOptionBuyMarkup() + public Couple getOptionBuyMarkup() { return Utils.getRangeValues(cbOptions[ID_BuyMarkup], sBuyMarkup); } - public Pair getOptionStealing() + public Couple getOptionStealing() { return Utils.getRangeValues(cbOptions[ID_Stealing], sStealing); } - public Pair getOptionCapacity() + public Couple getOptionCapacity() { return Utils.getRangeValues(cbOptions[ID_Capacity], sCapacity); } @@ -3501,9 +3501,9 @@ public void actionPerformed(ActionEvent event) } // - public Pair getOptionFlags() + public Couple getOptionFlags() { - return new Pair<>(getFlagData(), isExact()); + return Couple.with(getFlagData(), isExact()); } private boolean isExact() @@ -3650,7 +3650,7 @@ public boolean isActive(int id) return cbLabel[id].isSelected(); } - public Pair getOptionEffect(int id) + public Couple getOptionEffect(int id) { return Utils.getRangeValues(cbLabel[id], sEffects[id]); } @@ -3810,23 +3810,23 @@ public boolean isActive(int id) return (cbLabel[id].isSelected() && !tfFieldName[id].getText().isEmpty()); } - public Pair getOptionFilter(int id) + public Couple getOptionFilter(int id) { if (id < 0) id = 0; else if (id >= entryCount) id = entryCount - 1; final String name = tfFieldName[id].getText(); if (cbLabel[id].isSelected() && !name.isEmpty()) { switch (cbFilterType[id].getSelectedIndex()) { case FILTER_STRING: - return new Pair<>(name, tfFieldValueString[id].getText()); + return Couple.with(name, tfFieldValueString[id].getText()); case FILTER_NUMBER: final Integer min = (Integer)sFieldValueNumber[id][0].getValue(); final Integer max = (Integer)sFieldValueNumber[id][1].getValue(); - return new Pair<>(name, new Pair<>(min, max)); + return Couple.with(name, Couple.with(min, max)); case FILTER_RESOURCE: final ResourceEntry entry = (ResourceEntry)cbFieldValueResource[id].getSelectedItem(); - return new Pair<>(name, entry.getResourceName()); + return Couple.with(name, entry.getResourceName()); case FILTER_FLAGS: - return new Pair<>(name, pFieldValueFlags[id].getOptionFlags()); + return Couple.with(name, pFieldValueFlags[id].getOptionFlags()); } } return null; @@ -4038,7 +4038,7 @@ public int getOptionMode() ((IndexedString)cbMode.getSelectedItem()).index : 0; } - public Pair getOptionDuration() + public Couple getOptionDuration() { return Utils.getRangeValues(cbTiming[TIMING_DURATION], sDuration); } @@ -4123,7 +4123,7 @@ public boolean isActive(int classIdx) return cbLevel[classIdx].isSelected(); } - public Pair getOptionLevel(int classIdx) + public Couple getOptionLevel(int classIdx) { return Utils.getRangeValues(cbLevel[classIdx], sLevel[classIdx]); } @@ -4231,7 +4231,7 @@ public boolean isActive(int id) return cbLevel[id].isSelected(); } - public Pair getOptionLevel(int id) + public Couple getOptionLevel(int id) { return Utils.getRangeValues(cbLevel[id], sLevel[id]); } @@ -4454,10 +4454,10 @@ public boolean isActive(int id) return cbLabel[id].isSelected(); } - public Pair getOptionFlags(int id) + public Couple getOptionFlags(int id) { if (id < 0) id = 0; else if (id >= LABELS.length) id = LABELS.length - 1; - return cbLabel[id].isSelected() ? pFlags[id].getOptionFlags() : new Pair<>(0, false); + return cbLabel[id].isSelected() ? pFlags[id].getOptionFlags() : Couple.with(0, false); } @@ -4640,9 +4640,9 @@ public boolean isActive(int id) return cbLabel[id].isSelected(); } - public Pair getOptionFlags(int id) + public Couple getOptionFlags(int id) { - return cbLabel[id].isSelected() ? pFlags[id].getOptionFlags() : new Pair<>(0, false); + return cbLabel[id].isSelected() ? pFlags[id].getOptionFlags() : Couple.with(0, false); } @@ -4768,7 +4768,7 @@ public boolean isActive(int statID) return false; } - public Pair getOptionValue(int statID) + public Couple getOptionValue(int statID) { return Utils.getRangeValues(cbStats[statID], sStats[statID]); } @@ -4988,39 +4988,39 @@ public int getOptionDamageType() ((IndexedString)cbDamageType.getSelectedItem()).index : 0; } - public Pair getOptionRange() + public Couple getOptionRange() { return Utils.getRangeValues(cbItems[ITEM_RANGE], sRange); } - public Pair getOptionSpeed() + public Couple getOptionSpeed() { return Utils.getRangeValues(cbItems[ITEM_SPEED], sSpeed); } - public Pair getOptionDiceCount() + public Couple getOptionDiceCount() { return Utils.getRangeValues(cbItems[ITEM_DICECOUNT], sDiceCount); } - public Pair getOptionDiceSize() + public Couple getOptionDiceSize() { return Utils.getRangeValues(cbItems[ITEM_DICESIZE], sDiceSize); } - public Pair getOptionCharges() + public Couple getOptionCharges() { return Utils.getRangeValues(cbItems[ITEM_CHARGES], sCharges); } - public Pair getOptionEffects(int idx) + public Couple getOptionEffects(int idx) { - return cbItems[ITEM_EFFECTS].isSelected() ? pEffects.getOptionEffect(idx) : new Pair<>(0, 0); + return cbItems[ITEM_EFFECTS].isSelected() ? pEffects.getOptionEffect(idx) : Couple.with(0, 0); } - public Pair getOptionFlags() + public Couple getOptionFlags() { - return cbItems[ITEM_FLAGS].isSelected() ? flagsPanel.getOptionFlags() : new Pair<>(0, false); + return cbItems[ITEM_FLAGS].isSelected() ? flagsPanel.getOptionFlags() : Couple.with(0, false); } private void init() @@ -5295,17 +5295,17 @@ public int getOptionTarget() ((IndexedString)cbTarget.getSelectedItem()).index : 0; } - public Pair getOptionRange() + public Couple getOptionRange() { return Utils.getRangeValues(cbSpells[SPELL_RANGE], sRange); } - public Pair getOptionLevel() + public Couple getOptionLevel() { return Utils.getRangeValues(cbSpells[SPELL_LEVEL], sLevel); } - public Pair getOptionSpeed() + public Couple getOptionSpeed() { return Utils.getRangeValues(cbSpells[SPELL_SPEED], sSpeed); } @@ -5316,9 +5316,9 @@ public int getOptionProjectile() ((IndexedString)cbProjectile.getSelectedItem()).index : 0; } - public Pair getOptionEffects(int effectIdx) + public Couple getOptionEffects(int effectIdx) { - return cbSpells[SPELL_EFFECTS].isSelected() ? pEffects.getOptionEffect(effectIdx) : new Pair<>(0, 0); + return cbSpells[SPELL_EFFECTS].isSelected() ? pEffects.getOptionEffect(effectIdx) : Couple.with(0, 0); } @@ -5812,11 +5812,11 @@ public static Object getObjectFromString(JCheckBox enabled, JComboBox getRangeValues(JCheckBox enabled, JSpinner[] spinner) + public static Couple getRangeValues(JCheckBox enabled, JSpinner[] spinner) { return enabled.isSelected() - ? new Pair<>((Integer)spinner[0].getValue(), (Integer)spinner[1].getValue()) - : new Pair<>(0, 0); + ? Couple.with((Integer)spinner[0].getValue(), (Integer)spinner[1].getValue()) + : Couple.with(0, 0); } /** Returns a protype dimension object based on the height of @(code c} and the width of (@code prototype}. */ diff --git a/src/org/infinity/updater/UpdateInfo.java b/src/org/infinity/updater/UpdateInfo.java index 4aca4687e..b53a2603b 100644 --- a/src/org/infinity/updater/UpdateInfo.java +++ b/src/org/infinity/updater/UpdateInfo.java @@ -22,8 +22,8 @@ import javax.xml.parsers.DocumentBuilderFactory; import org.infinity.util.Misc; -import org.infinity.util.Pair; import org.infinity.util.io.StreamUtils; +import org.infinity.util.tuples.Couple; import org.w3c.dom.Document; import org.w3c.dom.DocumentType; import org.w3c.dom.Element; @@ -280,7 +280,7 @@ private void parseGeneral(Element elemGeneral) throws Exception } List serverList = new ArrayList(); - List> infoList = new ArrayList>(); + List> infoList = new ArrayList<>(); NodeList children = elemGeneral.getChildNodes(); for (int idx = 0, size = children.getLength(); idx < size; idx++) { @@ -322,7 +322,7 @@ private void parseGeneral(Element elemGeneral) throws Exception try { String name = n1.getTextContent().trim(); URL url = new URL(n2.getTextContent().trim()); - infoList.add(new Pair(name, url.toExternalForm())); + infoList.add(Couple.with(name, url.toExternalForm())); } catch (MalformedURLException e) { // don't add invalid URLs } @@ -428,9 +428,9 @@ private void parseRelease(Element elemRelease) throws Exception public static class General { private final List servers = new ArrayList(); - private final List> information = new ArrayList>(); + private final List> information = new ArrayList<>(); - private General(List servers, List> information) throws Exception + private General(List servers, List> information) throws Exception { if (servers != null) { this.servers.addAll(servers); @@ -460,7 +460,7 @@ public String getServer(int index) public String getInformationName(int index) { if (index >= 0 && index < getInformationCount()) { - return information.get(index).getFirst(); + return information.get(index).getValue0(); } else { return null; } @@ -470,7 +470,7 @@ public String getInformationName(int index) public String getInformationLink(int index) { if (index >= 0 && index < getInformationCount()) { - return information.get(index).getSecond(); + return information.get(index).getValue1(); } else { return null; } diff --git a/src/org/infinity/util/BinPack2D.java b/src/org/infinity/util/BinPack2D.java index cc99d4c70..5b71d45fb 100644 --- a/src/org/infinity/util/BinPack2D.java +++ b/src/org/infinity/util/BinPack2D.java @@ -10,6 +10,8 @@ import java.util.Iterator; import java.util.List; +import org.infinity.util.tuples.Couple; + /** * Implementation of a two-dimensional rectangle bin packing algorithm. * (Port of Jukka Jylänki's C++ implementation of RectangleBinPack->MaxRectsBinPack.) @@ -159,14 +161,14 @@ public void insert(List rects, List dst, HeuristicRules ru int bestRectIndex = -1; Rectangle bestNode = null; - Pair score = new Pair(0, 0); + Couple score = Couple.with(0, 0); for (int i = 0, size = rects.size(); i < size; i++) { Dimension d = rects.get(i); Rectangle newNode = scoreRect(d.width, d.height, rule, score); - if (score.getFirst() < bestScore1 || - (score.getFirst() == bestScore1 && score.getSecond() < bestScore2)) { - bestScore1 = score.getFirst(); - bestScore2 = score.getSecond(); + if (score.getValue0() < bestScore1 || + (score.getValue0() == bestScore1 && score.getValue1() < bestScore2)) { + bestScore1 = score.getValue0(); + bestScore2 = score.getValue1(); bestNode = newNode; bestRectIndex = i; } @@ -253,13 +255,13 @@ public float getOccupancy() * @param score (out) Returns the primary and secondary placement score. * @return This struct identifies where the rectangle would be placed if it were placed. */ - private Rectangle scoreRect(int width, int height, HeuristicRules rule, Pair score) + private Rectangle scoreRect(int width, int height, HeuristicRules rule, Couple score) { - if (score == null) { score = new Pair(0, 0); } + if (score == null) { score = Couple.with(0, 0); } Rectangle newNode = null; - score.setFirst(Integer.MAX_VALUE); - score.setSecond(Integer.MAX_VALUE); + score.setValue0(Integer.MAX_VALUE); + score.setValue1(Integer.MAX_VALUE); switch (rule) { case BEST_SHORT_SIDE_FIT: newNode = findPositionForNewNodeBestShortSideFit(width, height, score); @@ -269,7 +271,7 @@ private Rectangle scoreRect(int width, int height, HeuristicRules rule, Pair bestPos) + // bestPos.getValue0(): bestY, bestPos.getValue1(): bestX + private Rectangle findPositionForNewNodeBottomLeft(int width, int height, Couple bestPos) { - if (bestPos == null) { bestPos = new Pair(0, 0); } + if (bestPos == null) { bestPos = Couple.with(0, 0); } Rectangle bestNode = new Rectangle(); - bestPos.setFirst(Integer.MAX_VALUE); + bestPos.setValue0(Integer.MAX_VALUE); for (int i = 0, size = freeRectangles.size(); i < size; i++) { // Try to place the rectangle in upright (non-flipped) orientation. Rectangle r = freeRectangles.get(i); if (r.width >= width && r.height >= height) { int topSideY = r.y + height; - if (topSideY < bestPos.getFirst() || - (topSideY == bestPos.getFirst() && r.x < bestPos.getSecond())) { + if (topSideY < bestPos.getValue0() || + (topSideY == bestPos.getValue0() && r.x < bestPos.getValue1())) { bestNode.x = r.x; bestNode.y = r.y; bestNode.width = width; bestNode.height = height; - bestPos.setSecond(r.x); - bestPos.setFirst(topSideY); + bestPos.setValue1(r.x); + bestPos.setValue0(topSideY); } } } @@ -361,13 +363,13 @@ private Rectangle findPositionForNewNodeBottomLeft(int width, int height, Pair bestFit) + // bestFit.getValue0(): short side, bestFit.getValue1(): long side + private Rectangle findPositionForNewNodeBestShortSideFit(int width, int height, Couple bestFit) { - if (bestFit == null) { bestFit = new Pair(0, 0); } + if (bestFit == null) { bestFit = Couple.with(0, 0); } Rectangle bestNode = new Rectangle(); - bestFit.setFirst(Integer.MAX_VALUE); + bestFit.setValue0(Integer.MAX_VALUE); for (int i = 0, size = freeRectangles.size(); i < size; i++) { // Try to place the rectangle in upright (non-flipped) orientation. Rectangle r = freeRectangles.get(i); @@ -377,14 +379,14 @@ private Rectangle findPositionForNewNodeBestShortSideFit(int width, int height, int shortSideFit = Math.min(leftoverHoriz, leftoverVert); int longSideFit = Math.max(leftoverHoriz, leftoverVert); - if (shortSideFit < bestFit.getFirst() || - (shortSideFit == bestFit.getFirst() && longSideFit < bestFit.getSecond())) { + if (shortSideFit < bestFit.getValue0() || + (shortSideFit == bestFit.getValue0() && longSideFit < bestFit.getValue1())) { bestNode.x = r.x; bestNode.y = r.y; bestNode.width = width; bestNode.height = height; - bestFit.setFirst(shortSideFit); - bestFit.setSecond(longSideFit); + bestFit.setValue0(shortSideFit); + bestFit.setValue1(longSideFit); } } } @@ -392,13 +394,13 @@ private Rectangle findPositionForNewNodeBestShortSideFit(int width, int height, return bestNode; } - // bestFit.getFirst(): short side, bestFit.getSecond(): long side - private Rectangle findPositionForNewNodeBestLongSideFit(int width, int height, Pair bestFit) + // bestFit.getValue0(): short side, bestFit.getValue1(): long side + private Rectangle findPositionForNewNodeBestLongSideFit(int width, int height, Couple bestFit) { - if (bestFit == null) { bestFit = new Pair(0, 0); } + if (bestFit == null) { bestFit = Couple.with(0, 0); } Rectangle bestNode = new Rectangle(); - bestFit.setSecond(Integer.MAX_VALUE); + bestFit.setValue1(Integer.MAX_VALUE); for (int i = 0, size = freeRectangles.size(); i < size; i++) { // Try to place the rectangle in upright (non-flipped) orientation. Rectangle r = freeRectangles.get(i); @@ -408,14 +410,14 @@ private Rectangle findPositionForNewNodeBestLongSideFit(int width, int height, P int shortSideFit = Math.min(leftoverHoriz, leftoverVert); int longSideFit = Math.max(leftoverHoriz, leftoverVert); - if (longSideFit < bestFit.getSecond() || - (longSideFit == bestFit.getSecond() && shortSideFit < bestFit.getFirst())) { + if (longSideFit < bestFit.getValue1() || + (longSideFit == bestFit.getValue1() && shortSideFit < bestFit.getValue0())) { bestNode.x = r.x; bestNode.y = r.y; bestNode.width = width; bestNode.height = height; - bestFit.setFirst(shortSideFit); - bestFit.setSecond(longSideFit); + bestFit.setValue0(shortSideFit); + bestFit.setValue1(longSideFit); } } } @@ -423,13 +425,13 @@ private Rectangle findPositionForNewNodeBestLongSideFit(int width, int height, P return bestNode; } - // bestFit.getFirst(): area, bestFit.getSecond(): short side - private Rectangle findPositionForNewNodeBestAreaFit(int width, int height, Pair bestFit) + // bestFit.getValue0(): area, bestFit.getValue1(): short side + private Rectangle findPositionForNewNodeBestAreaFit(int width, int height, Couple bestFit) { - if (bestFit == null) { bestFit = new Pair(0, 0); } + if (bestFit == null) { bestFit = Couple.with(0, 0); } Rectangle bestNode = new Rectangle(); - bestFit.setFirst(Integer.MAX_VALUE); + bestFit.setValue0(Integer.MAX_VALUE); for (int i = 0, size = freeRectangles.size(); i < size; i++) { Rectangle r = freeRectangles.get(i); int areaFit = r.width*r.height - width*height; @@ -440,14 +442,14 @@ private Rectangle findPositionForNewNodeBestAreaFit(int width, int height, Pair< int leftoverVert = Math.abs(r.height - height); int shortSideFit = Math.min(leftoverHoriz, leftoverVert); - if (areaFit < bestFit.getFirst() || - (areaFit == bestFit.getFirst() && shortSideFit < bestFit.getSecond())) { + if (areaFit < bestFit.getValue0() || + (areaFit == bestFit.getValue0() && shortSideFit < bestFit.getValue1())) { bestNode.x = r.x; bestNode.y = r.y; bestNode.width = width; bestNode.height = height; - bestFit.setFirst(areaFit); - bestFit.setSecond(shortSideFit); + bestFit.setValue0(areaFit); + bestFit.setValue1(shortSideFit); } } } @@ -455,25 +457,25 @@ private Rectangle findPositionForNewNodeBestAreaFit(int width, int height, Pair< return bestNode; } - // bestScore.getFirst(): contact, bestScore.getSecond(): unused - private Rectangle findPositionForNewNodeContactPoint(int width, int height, Pair bestScore) + // bestScore.getValue0(): contact, bestScore.getValue1(): unused + private Rectangle findPositionForNewNodeContactPoint(int width, int height, Couple bestScore) { - if (bestScore == null) { bestScore = new Pair(0, 0); } + if (bestScore == null) { bestScore = Couple.with(0, 0); } Rectangle bestNode = new Rectangle(); - bestScore.setFirst(-1); + bestScore.setValue0(-1); for (int i = 0, size = freeRectangles.size(); i < size; i++) { // Try to place the rectangle in upright (non-flipped) orientation. Rectangle r = freeRectangles.get(i); if (r.width >= width && r.height >= height) { int score = contactPointScoreNode(r.x, r.y, width, height); - if (score > bestScore.getFirst()) + if (score > bestScore.getValue0()) { bestNode.x = r.x; bestNode.y = r.y; bestNode.width = width; bestNode.height = height; - bestScore.setFirst(score); + bestScore.setValue0(score); } } } diff --git a/src/org/infinity/util/Pair.java b/src/org/infinity/util/Pair.java deleted file mode 100644 index 33e21337d..000000000 --- a/src/org/infinity/util/Pair.java +++ /dev/null @@ -1,163 +0,0 @@ -// Near Infinity - An Infinity Engine Browser and Editor -// Copyright (C) 2001 - 2005 Jon Olav Hauglid -// See LICENSE.txt for license information - -package org.infinity.util; - -/** - * A simple Pair class to store arbitrary objects A and B. - * Both objects must share a common base class. - * @param The common base class for both objects. - */ -public class Pair implements Cloneable -{ - private V first, second; - - /** - * Creates a pair object with both elements initialized to {@code null}. - */ - public Pair() - { - this.first = null; - this.second = null; - } - - /** - * Creates a pair object, setting the first element to the specified value and - * setting the second element to {@code null}. - * @param first The value of the first element. - */ - public Pair(V first) - { - this.first = first; - this.second = null; - } - - /** - * Creates a pair object, setting both elements to the specified values. - * @param first The value of the first element. - * @param second The value of the second element. - */ - public Pair(V first, V second) - { - this.first = first; - this.second = second; - } - - /** - * Returns the value of the specified element. - * @param index Specifies the element to return (0=first, 1=second). - * @return The value specified by index. - */ - public V get(int index) - { - return ((index & 1) == 0) ? first : second; - } - - /** - * Sets the value of the element specified by index. - * @param index Specifies the element to set (0=first, 1=second). - * @param value The value to set to the specified element. - * @return The old value of the specified element. - */ - public V set(int index, V value) - { - V retVal; - if ((index & 1) == 0) { - retVal = first; - first = value; - } else { - retVal = second; - second = value; - } - return retVal; - } - - /** - * Returns the value of the first element. - * @return The value of the first element. - */ - public V getFirst() - { - return first; - } - - /** - * Sets the value of the first element. - * @param value The value for the first element. - * @return The old value of the first element. - */ - public V setFirst(V value) - { - V retVal = first; - first = value; - return retVal; - } - - /** - * Returns the value of the second element. - * @return The value of the second element. - */ - public V getSecond() - { - return second; - } - - /** - * Sets the value of the second element. - * @param value The value for the second element. - * @return The old value of the second element. - */ - public V setSecond(V value) - { - V retVal = second; - second = value; - return retVal; - } - - /** - * Swaps the content of both elements. - * After the swap the first element contains the value of the second element - * and the second element contains the value of the first element. - * @return A reference to this object. Useful for chaining actions. - */ - public Pair swap() - { - V tmp = first; - first = second; - second = tmp; - return this; - } - - /** - * Returns a shallow copy of this Pair<V> instance. (The elements themselves are not cloned.) - * @return a clone of this Pair<V> instance - */ - @Override - public Object clone() - { - return new Pair(getFirst(), getSecond()); - } - - @Override - public boolean equals(Object o) - { - if (o != null && o instanceof Pair) { - boolean retVal = true; - if (getFirst() != null) { - retVal &= getFirst().equals(((Pair)o).getFirst()); - } - if (getSecond() != null) { - retVal &= getSecond().equals(((Pair)o).getSecond()); - } - return retVal; - } - return false; - } - - @Override - public String toString() - { - return String.format("[%s, %s]", getFirst(), getSecond()); - } -} From 6473d12b6c42fc0408a86d0da34f2459f5a573c0 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Thu, 18 Mar 2021 12:34:06 +0100 Subject: [PATCH 059/158] Improve tooltip of blending option in area viewer and creature viewer --- src/org/infinity/resource/are/viewer/SettingsDialog.java | 2 +- src/org/infinity/resource/cre/viewer/SettingsPanel.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/infinity/resource/are/viewer/SettingsDialog.java b/src/org/infinity/resource/are/viewer/SettingsDialog.java index 8d5063e23..020fd6bc3 100644 --- a/src/org/infinity/resource/are/viewer/SettingsDialog.java +++ b/src/org/infinity/resource/are/viewer/SettingsDialog.java @@ -386,7 +386,7 @@ private void init() cbShowActorPersonalSpace = new JCheckBox("Draw personal space indicator", Settings.ShowActorPersonalSpace); cbShowActorPersonalSpace.setToolTipText("Requires a restart of the area viewer or a map update via toolbar button."); cbActorAccurateBlending = new JCheckBox("Enable accurate color blending", Settings.UseActorAccurateBlending); - cbActorAccurateBlending.setToolTipText("Creature animations with special blending modes (such as gaseous forms or wisps)
" + + cbActorAccurateBlending.setToolTipText("Creature animations with special blending modes (such as movanic devas or wisps)
" + "can reduce overall performance of the area viewer. Disable to improve performance.
" + "Requires a restart of the area viewer or a map update via toolbar button."); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, diff --git a/src/org/infinity/resource/cre/viewer/SettingsPanel.java b/src/org/infinity/resource/cre/viewer/SettingsPanel.java index 12335569c..a6bc7f079 100644 --- a/src/org/infinity/resource/cre/viewer/SettingsPanel.java +++ b/src/org/infinity/resource/cre/viewer/SettingsPanel.java @@ -384,7 +384,7 @@ private void init() cbFiltering.addActionListener(listeners); cbBlending = new JCheckBox("Enable blending", isBlending); - cbBlending.setToolTipText("Affects only creature animations with special blending attributes (e.g. nishruus or wisps)"); + cbBlending.setToolTipText("Affects only creature animations with special blending attributes (e.g. movanic devas or wisps)"); cbBlending.addActionListener(listeners); cbTranslucent = new JCheckBox("Enable translucency", isTranslucent); From cc8654c74cec30086b20272aa60da44941dd1036 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Thu, 18 Mar 2021 12:52:25 +0100 Subject: [PATCH 060/158] Improve icon for Creature Animation Viewer --- src/org/infinity/icon/CreViewer24.png | Bin 759 -> 814 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/org/infinity/icon/CreViewer24.png b/src/org/infinity/icon/CreViewer24.png index cb1e7bff48a816514d71383a2f98fe9bf2924fee..df068df1879bcf6264d3a5b592fcda9fae69a27c 100644 GIT binary patch delta 769 zcmV+c1OEK?1+E5=Ie*|uL_t(Y$F)^UOcPNQJ+Cd5@=#c~X-pAQ@m-rP7$-C!(?u1ciXIP)bYrNQxF>V&X{IpQ4t6sAcTa* z8~wmgA8y~g8^V=r-kg+_6c%)(sSIwn8*;fE7n+8`uHiT?vdFPam3acljJ`%J7RN4A ziDW%yTWg3)$bXMy3p@artdkMKrI9R|)kY9J*|(M zx7&|;1T&uJ5zkMj-ejz4Z-2tnn~ZTCAc!kyb`gN(48xpPDAJy1XC1}IPZI#}uB-Fe zxmW?>5`TGhb@lZ9dk^v!7u`W{s8q*MZ!(U@lS#M?2ms!A{n{CZVU@L4YEN1FETw+G z|3(osq~^78WEOinuYJniZZX2OTdU~>Qw07s;IC~-I9JV{rBkD>yrWS zm%-rGhN@CZX-7szmdoSuBrGh<8+y&w_W-!S6MB{+QJ|`%D}Pt^Ih~(#H-L`-*b5*P zKx(hq`qu09-I|%1PFq^?$d{M@Bm$5E5SNsc$f8js3Y=1$oSaN3r9>u^iA5q&0)RwH z=^DpzJi{<+v$L~)g+f6HA)L$Q;sO+{ZKmiSk-;HNnv;LS00000NkvXXu0mjfL!oQ_ delta 714 zcmV;*0yX`v2KNP!Ie)21L_t(Y$F)^WOcOyEeY0IEWl*?q;7}wnA?QVr$VDYhj3<)~ z$3~DuN>O53hzEaSbkl?Q3lZIerL-i7i9bM04PMkJR}9%B1TMB5LGC1sU@J4a2-Frh-)&5<=J%9iD<;1v!KvmWLNH~f_ z;@ggp=NIPH$*GCw0IUQ~r*qhU(bv68u5=g!Vlx z;cyfu;Q2qIf`7rFSyxq6S+b!>mgT%_|5-sa8nt9elE4^)BuVzZ10yJ{uk}R zR&ayNP-X|*c9R7`KqwRf0E9vzlZ5B_g5cX)R8di38Gp~r%$T*u Date: Thu, 18 Mar 2021 19:32:02 +0100 Subject: [PATCH 061/158] Creature Animation Viewer: Cosmetic improvements to media panel --- src/org/infinity/resource/cre/viewer/MediaPanel.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/org/infinity/resource/cre/viewer/MediaPanel.java b/src/org/infinity/resource/cre/viewer/MediaPanel.java index 28a6ec11a..e119e0bbb 100644 --- a/src/org/infinity/resource/cre/viewer/MediaPanel.java +++ b/src/org/infinity/resource/cre/viewer/MediaPanel.java @@ -569,10 +569,10 @@ private void updateControls() { boolean loaded = getSequence() != null; boolean running = isRunning(); - bHome.setEnabled(loaded && getCurrentFrame() > 0); - bEnd.setEnabled(loaded && getCurrentFrame() < getMaxFrame() - 1); - bStepBack.setEnabled(loaded && getCurrentFrame() > 0); - bStepForward.setEnabled(loaded && getCurrentFrame() < getMaxFrame() - 1); + bHome.setEnabled(running || loaded && getCurrentFrame() > 0); + bEnd.setEnabled(running || loaded && getCurrentFrame() < getMaxFrame() - 1); + bStepBack.setEnabled(running || loaded && getCurrentFrame() > 0); + bStepForward.setEnabled(running || loaded && getCurrentFrame() < getMaxFrame() - 1); bPlay.setEnabled(loaded); bStop.setEnabled(running); cbSequences.setEnabled(modelSequences.getSize() > 0); From f792cd488b02db5d637ce957e090a96e52162d16 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 19 Mar 2021 18:57:33 +0100 Subject: [PATCH 062/158] Refactor datatypes; improve datatype presentation; bugfixes - Implemented common base class"AbstractBitmap" for Bitmap, HashBitmap, IdsBitmap and ResourceBitmap datatypes - Added more customization options to AbstractBitmap class: show hexadecimal numbers, preserve sign, sort by name - Replaced ObjectString by DataString class with generic type support - Improved visualization of numeric values for selected datatypes - Added custom formatter support to Tuple classes - Fixed IDS and 2DA parser: preserve sign of numeric values --- src/org/infinity/check/IDSRefChecker.java | 2 +- src/org/infinity/datatype/AbstractBitmap.java | 534 ++++++++++++++++++ src/org/infinity/datatype/AnimateBitmap.java | 43 +- src/org/infinity/datatype/Bitmap.java | 229 ++------ src/org/infinity/datatype/HashBitmap.java | 322 +---------- src/org/infinity/datatype/IdsBitmap.java | 48 +- src/org/infinity/datatype/IdsTargetType.java | 4 +- src/org/infinity/datatype/IsNumeric.java | 3 + src/org/infinity/datatype/IwdRef.java | 22 +- src/org/infinity/datatype/KitIdsBitmap.java | 7 + src/org/infinity/datatype/ResourceBitmap.java | 333 +++-------- src/org/infinity/datatype/Song2daBitmap.java | 18 +- src/org/infinity/gui/BrowserMenuBar.java | 96 ++-- src/org/infinity/gui/OpenResourceDialog.java | 25 +- .../infinity/gui/hexview/StructHexViewer.java | 7 +- .../infinity/resource/AbstractVariable.java | 4 +- src/org/infinity/resource/EffectFactory.java | 21 +- src/org/infinity/resource/Profile.java | 16 +- .../resource/are/viewer/TilesetRenderer.java | 33 +- src/org/infinity/resource/chu/Control.java | 3 +- src/org/infinity/resource/chu/Viewer.java | 59 +- src/org/infinity/resource/chu/Window.java | 13 +- .../infinity/resource/pro/ProResource.java | 5 +- .../infinity/resource/spl/SplResource.java | 8 +- .../infinity/resource/sto/StoResource.java | 9 +- src/org/infinity/search/SearchResource.java | 42 +- .../search/SongReferenceSearcher.java | 2 +- .../infinity/search/advanced/FilterInput.java | 89 +-- .../search/advanced/NumberFormatterEx.java | 20 +- src/org/infinity/util/DataString.java | 232 ++++++++ src/org/infinity/util/IdsMap.java | 3 +- src/org/infinity/util/ObjectString.java | 171 ------ src/org/infinity/util/tuples/Tuple.java | 30 +- 33 files changed, 1268 insertions(+), 1185 deletions(-) create mode 100644 src/org/infinity/datatype/AbstractBitmap.java create mode 100644 src/org/infinity/util/DataString.java delete mode 100644 src/org/infinity/util/ObjectString.java diff --git a/src/org/infinity/check/IDSRefChecker.java b/src/org/infinity/check/IDSRefChecker.java index ff248d608..3ac8bb258 100644 --- a/src/org/infinity/check/IDSRefChecker.java +++ b/src/org/infinity/check/IDSRefChecker.java @@ -57,7 +57,7 @@ private void search(ResourceEntry entry, AbstractStruct struct) if (e instanceof IdsBitmap) { final IdsBitmap ref = (IdsBitmap)e; final long value = ref.getLongValue(); - if (value != 0L && ref.getValueOf(value) == null) { + if (value != 0L && ref.getDataOf(value) == null) { synchronized (hitFrame) { hitFrame.addHit(entry, entry.getSearchString(), ref); } diff --git a/src/org/infinity/datatype/AbstractBitmap.java b/src/org/infinity/datatype/AbstractBitmap.java new file mode 100644 index 000000000..35a81109f --- /dev/null +++ b/src/org/infinity/datatype/AbstractBitmap.java @@ -0,0 +1,534 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.datatype; + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; + +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; + +import org.infinity.gui.StructViewer; +import org.infinity.gui.TextListPanel; +import org.infinity.gui.ViewerUtil; +import org.infinity.icon.Icons; +import org.infinity.resource.AbstractStruct; +import org.infinity.util.LongIntegerHashMap; +import org.infinity.util.Misc; + +/** + * Common base class for fields that represent an integer enumeration of some values. + * @param type of the symbolic representation of the numeric value + */ +public class AbstractBitmap extends Datatype implements Editable, IsNumeric +{ + // The default formatter if none is specified in the constructor + private final BiFunction formatterDefault = (value, item) -> { + String s; + if (isShowAsHex()) { + switch (getSize()) { + case 1: + s = String.format("0x%02X", value); + break; + case 2: + s = String.format("0x%04X", value); + break; + case 4: + s = String.format("0x%08X", value); + break; + default: + s = String.format("0x%X", value); + } + } else { + s = value.toString(); + } + if (item != null) { + return item.toString() + " - " + s; + } else { + return "Unknown - " + s; + } + }; + + private final LongIntegerHashMap itemMap; + private final List buttonList; + + private BiFunction formatter; + private TextListPanel> list; + private JButton bUpdate; + private long value; + private boolean signed; + private boolean sortByName; + private boolean showAsHex; + + /** + * Constructs a new field that represents an integer enumeration using a default formatter. + * By default value is treated as unsigned and shown in decimal notation. List is sorted by value. + * @param buffer the buffer object + * @param offset offset of data in the buffer + * @param length size of data object in bytes + * @param name field name + * @param items a collection of number-to-symbol mappings + */ + public AbstractBitmap(ByteBuffer buffer, int offset, int length, String name, LongIntegerHashMap items) + { + this(buffer, offset, length, name, items, null, false); + } + + /** + * Constructs a new field that represents an integer enumeration. + * By default value is treated as unsigned and shown in decimal notation. List is sorted by value. + * @param buffer the buffer object + * @param offset offset of data in the buffer + * @param length size of data object in bytes + * @param name field name + * @param items a collection of number-to-symbol mappings + * @param formatter a function that is used to produce the textual output + */ + public AbstractBitmap(ByteBuffer buffer, int offset, int length, String name, LongIntegerHashMap items, + BiFunction formatter) + { + this(buffer, offset, length, name, items, formatter, false); + } + + /** + * Constructs a new field that represents an integer enumeration. + * By default value is shown in decimal notation. List is sorted by value. + * @param buffer the buffer object + * @param offset offset of data in the buffer + * @param length size of data object in bytes + * @param name field name + * @param items a collection of number-to-symbol mappings + * @param formatter a function that is used to produce the textual output + * @param signed indicates whether numeric value is treated as a signed value + */ + public AbstractBitmap(ByteBuffer buffer, int offset, int length, String name, LongIntegerHashMap items, + BiFunction formatter, boolean signed) + { + super(offset, length, name); + this.itemMap = items; + this.signed = signed; + this.formatter = (formatter != null) ? formatter : formatterDefault; + this.buttonList = new ArrayList<>(); + this.bUpdate = new JButton("Update value", Icons.getIcon(Icons.ICON_REFRESH_16)); + this.buttonList.add(this.bUpdate); + + read(buffer, offset); + } + + //--------------------- Begin Interface Writeable --------------------- + + @Override + public void write(OutputStream os) throws IOException + { + writeLong(os, value); + } + + //--------------------- End Interface Writeable --------------------- + + //--------------------- Begin Interface Readable --------------------- + + @Override + public int read(ByteBuffer buffer, int offset) + { + buffer.position(offset); + switch (getSize()) { + case 1: + value = buffer.get() & 0xffL; + break; + case 2: + value = buffer.getShort() & 0xffffL; + break; + case 4: + value = buffer.getInt() & 0xffffffffL; + break; + default: + throw new IllegalArgumentException(); + } + setValue(value); // called to ensure correct signedness + return offset + getSize(); + } + + //--------------------- End Interface Readable --------------------- + + //--------------------- Begin Interface IsNumeric --------------------- + + @Override + public long getLongValue() + { + return value; + } + + @Override + public int getValue() + { + return (int)value; + } + + //--------------------- End Interface IsNumeric --------------------- + + //--------------------- Begin Interface Editable --------------------- + + @Override + public JComponent edit(ActionListener container) + { + FormattedData selected = null; + final List> items = new ArrayList<>(itemMap.size()); + for (final Long key : itemMap.keySet()) { + FormattedData item = new FormattedData<>(key, itemMap.get(key), formatter); + items.add(item); + if (key.intValue() == value) { + selected = item; + } + } + + list = new TextListPanel<>(items, sortByName); + list.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent event) + { + if (event.getClickCount() == 2) { + container.actionPerformed(new ActionEvent(this, 0, StructViewer.UPDATE_VALUE)); + } + } + }); + list.addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent event) + { + if (!event.getValueIsAdjusting()) { + listItemChanged(); + } + } + }); + + if (selected != null) { + list.setSelectedValue(selected, true); + } + + bUpdate.addActionListener(container); + bUpdate.setActionCommand(StructViewer.UPDATE_VALUE); + + // lay out controls + JPanel panel = new JPanel(new GridBagLayout()); + GridBagConstraints c = new GridBagConstraints(); + + c = ViewerUtil.setGBC(c, 0, 0, 1, buttonList.size() + 2, 1.0, 1.0, GridBagConstraints.CENTER, + GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0); + panel.add(list, c); + + c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 1.0, GridBagConstraints.LINE_START, + GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0); + panel.add(new JPanel(), c); + for (int i = 0; i < buttonList.size(); i++) { + c = ViewerUtil.setGBC(c, 1, 1 + i, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(0, 6, 8, 0), 0, 0); + panel.add(buttonList.get(i), c); + } + c = ViewerUtil.setGBC(c, 1, 1 + buttonList.size(), 1, 1, 1.0, 1.0, GridBagConstraints.LINE_START, + GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0); + panel.add(new JPanel(), c); + + panel.setMinimumSize(Misc.getScaledDimension(DIM_MEDIUM)); + return panel; + } + + @Override + public void select() + { + list.ensureIndexIsVisible(list.getSelectedIndex()); + } + + @Override + public boolean updateValue(AbstractStruct struct) + { + long oldValue = getLongValue(); + + // updating value + FormattedData item = list.getSelectedValue(); + if (item != null) { + setValue(item.getValue().longValue()); + } else { + return false; + } + + // notifying listeners + if (getLongValue() != oldValue) { + fireValueUpdated(new UpdateEvent(this, struct)); + } + + return true; + } + + //--------------------- End Interface Editable --------------------- + + @Override + public String toString() + { + return toString(value); + } + + @Override + public int hashCode() + { + int hash = super.hashCode(); + + // taking care of signedness + long mask = (1L << (getSize() * 8)) - 1L; + hash = 31 * hash + Long.hashCode(value & mask); + + return hash; + } + + @Override + public boolean equals(Object o) + { + if (!super.equals(o) || !(o instanceof AbstractBitmap)) { + return false; + } + AbstractBitmap other = (AbstractBitmap)o; + + // taking care of signedness + long mask = (1L << (getSize() * 8)) - 1L; + boolean retVal = ((value & mask) == (other.value & mask)); + + return retVal; + } + + /** Returns the TextListPanel control used by this datatype. */ + public JComponent getUiControl() + { + return list; + } + + /** + * Returns the bitmap table used to associated numeric values with symbolic data. + */ + public LongIntegerHashMap getBitmap() + { + return itemMap; + } + + /** + * Returns the numeric value of the selected entry in the list control. + * Returns {@code null} if no valid list item is selected. + */ + public Long getSelectedValue() + { + Long retVal = null; + FormattedData item = list.getSelectedValue(); + if (item != null) { + retVal = item.getValue(); + } + return retVal; + } + + /** + * Returns the data object associated with the specified argument. + * @param value a numeric value + * @return the data object associated with the numeric value, {@code null} otherwise. + */ + public T getDataOf(long value) + { + return itemMap.get(Long.valueOf(value)); + } + + /** + * Calls the formatter on the data associated with the specified argument and returns the result. + * @param value a numeric value + * @return a {@code String} based on the data associated with the specified argument + */ + public String toString(long value) + { + Long number = Long.valueOf(value); + T data = itemMap.get(Long.valueOf(value)); + return formatter.apply(number, data); + } + + /** Returns the function object that is used to create the return value of the {@link #toString()} method. */ + public BiFunction getFormatter() + { + return formatter; + } + + /** + * Assigns a new formatter function object that is used to create the textual representation of the field value. + * @param formatter the new formatter function object. Specify {@code null} too use the default formatter. + */ + public void setFormatter(BiFunction formatter) + { + if (formatter == null) { + formatter = formatterDefault; + } + this.formatter = formatter; + } + + /** Returns whether the value is treated as a signed number. */ + public boolean isSigned() + { + return signed; + } + + /** Sets whether the value is treated as a signed number. The current value is converted accordingly. */ + public void setSigned(boolean b) + { + if (signed != b) { + signed = b; + setValue(getLongValue()); + } + } + + /** Returns whether whether entries are sorted by name instead of value. */ + public boolean isSortByName() + { + return sortByName; + } + + /** Sets whether whether entries are sorted by name instead of value. + * Does nothing if this method is called after the UI control is created. */ + public void setSortByName(boolean b) + { + if (list == null) { + sortByName = b; + } + } + + /** Returns whether numeric value is shown in hexadecimal notation. */ + public boolean isShowAsHex() + { + return showAsHex; + } + + /** Sets whether numeric value is shown in hexadecimal notation. */ + public void setShowAsHex(boolean b) + { + showAsHex = b; + } + + /** + * Adds the specified data to the item list. Existing entries will be overwritten. + * @param value the numeric value + * @param data data associated with the value + */ + protected void putItem(long value, T data) + { + itemMap.put(Long.valueOf(value), data); + } + + /** Assigns a new value. */ + protected void setValue(long newValue) + { + final long oldValue = getLongValue(); + + // ensure correct signedness + switch (getSize()) { + case 1: + newValue = isSigned() ? ((newValue << 56) >> 56) : (newValue & 0xffL); + break; + case 2: + newValue = isSigned() ? ((newValue << 48) >> 48) : (newValue & 0xffffL); + break; + case 4: + newValue = isSigned() ? ((newValue << 32) >> 32) : (newValue & 0xffffffffL); + break; + } + + this.value = newValue; + if (oldValue != this.value) { + firePropertyChange(oldValue, newValue); + } + } + + /** + * Allows derived classes to add custom buttons to the UI control. + * Only effective if called before the UI control is created. + */ + protected void addButtons(JButton... buttons) + { + // It makes no sense to register new buttons if the list panel has already been created + if (list == null) { + for (final JButton btn : buttons) { + if (btn != null) { + buttonList.add(btn); + } + } + } + } + + /** Called whenever the user selects a new list item. */ + protected void listItemChanged() + { + } + + /** + * Helper method: returns the specified number in hexadecimal notation. + * @param value the value to return as hexadecimal representation. + * @param size size of the value in bytes. + * @return String containing hexadecimal notation of the specified value. + */ + protected String getHexValue(long value) + { + switch (getSize()) { + case 1: + return String.format("0x%02X", value); + case 2: + return String.format("0x%04X", value); + case 4: + return String.format("0x%08X", value); + default: + return String.format("0x%X", value); + } + } + + //-------------------------- INNER CLASSES -------------------------- + + /** + * A helper class used to encapsulate the formatter function object. + */ + protected static class FormattedData + { + private final Long value; + private final T data; + private final BiFunction formatter; + + public FormattedData(long value, T data, BiFunction formatter) + { + this.value = Long.valueOf(value); + this.data = data; + this.formatter = formatter; + } + + public Long getValue() { return value; } + + public T getData() { return data; } + + public BiFunction getFormatter() { return formatter; } + + @Override + public String toString() + { + if (formatter != null) { + return formatter.apply(value, data); + } else if (data != null) { + return data.toString(); + } else { + return "(null)"; + } + } + } +} diff --git a/src/org/infinity/datatype/AnimateBitmap.java b/src/org/infinity/datatype/AnimateBitmap.java index 93d48e38f..35b37331c 100644 --- a/src/org/infinity/datatype/AnimateBitmap.java +++ b/src/org/infinity/datatype/AnimateBitmap.java @@ -7,8 +7,10 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.nio.ByteBuffer; +import java.util.function.BiFunction; import javax.swing.JButton; +import javax.swing.JComponent; import org.infinity.gui.ViewFrame; import org.infinity.icon.Icons; @@ -24,13 +26,28 @@ */ public class AnimateBitmap extends IdsBitmap implements ActionListener { + private final BiFunction formatterAnimateBitmap = (value, item) -> { + String number; + if (isShowAsHex()) { + number = String.format("0x%04X", value); + } else { + number = value.toString(); + } + if (item != null) { + return item.getSymbol() + " - " + number; + } else { + return "UNKNOWN - " + number; + } + }; + private final JButton showIni; private IdsMap idsMap; private boolean useIni; public AnimateBitmap(ByteBuffer buffer, int offset, int length, String name) { - super(buffer, offset, length, name, "ANIMATE.IDS"); + super(buffer, offset, length, name, "ANIMATE.IDS", true, true); + setFormatter(formatterAnimateBitmap); if (Profile.isEnhancedEdition() || ResourceFactory.resourceExists("ANISND.IDS")) { showIni = new JButton("View/Edit", Icons.getIcon(Icons.ICON_ZOOM_16)); @@ -52,27 +69,45 @@ public AnimateBitmap(ByteBuffer buffer, int offset, int length, String name) } } + //--------------------- Begin Interface Editable --------------------- + + @Override + public JComponent edit(ActionListener container) + { + if (getDataOf(getLongValue()) == null) { + putItem(getLongValue(), new IdsMapEntry(getLongValue(), "UNKNOWN")); + } + + return super.edit(container); + } + + //--------------------- End Interface Editable --------------------- + + //--------------------- Begin Interface ActionListener --------------------- + @Override public void actionPerformed(ActionEvent e) { if (e.getSource() == showIni) { - final Long value = getCurrentValue(); + final Long value = getSelectedValue(); String animRes = value == null ? null : getAnimResource(value.longValue()); if (animRes != null) { ResourceEntry entry = ResourceFactory.getResourceEntry(animRes); if (entry != null) { - new ViewFrame(getListPanel().getTopLevelAncestor(), ResourceFactory.getResource(entry)); + new ViewFrame(getUiControl().getTopLevelAncestor(), ResourceFactory.getResource(entry)); } } } } + //--------------------- End Interface ActionListener --------------------- + @Override protected void listItemChanged() { if (showIni != null) { boolean b = false; - final Long value = getCurrentValue(); + final Long value = getSelectedValue(); if (value != null) { b = (getAnimResource(value) != null); } diff --git a/src/org/infinity/datatype/Bitmap.java b/src/org/infinity/datatype/Bitmap.java index 3902e65ee..9c9e47a85 100644 --- a/src/org/infinity/datatype/Bitmap.java +++ b/src/org/infinity/datatype/Bitmap.java @@ -4,28 +4,14 @@ package org.infinity.datatype; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.beans.PropertyChangeEvent; -import java.io.IOException; -import java.io.OutputStream; import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; +import java.util.function.BiFunction; -import javax.swing.JButton; import javax.swing.JComponent; -import javax.swing.JPanel; -import org.infinity.gui.StructViewer; -import org.infinity.gui.TextListPanel; -import org.infinity.icon.Icons; import org.infinity.resource.AbstractStruct; -import org.infinity.util.Misc; +import org.infinity.util.LongIntegerHashMap; /** * Field that represents an integer enumeration of some values. @@ -40,208 +26,61 @@ *
  • Value meaning: value of the enumeration
  • * */ -public class Bitmap extends Datatype implements Editable, IsNumeric +public class Bitmap extends AbstractBitmap { - private final String[] table; - - private TextListPanel list; - private int base; // numeric value of first item index - private int value; - - public Bitmap(ByteBuffer buffer, int offset, int length, String name, String[] table) - { - super(offset, length, name); - this.base = 0; - this.table = table; - read(buffer, offset); - } - - // - @Override - public JComponent edit(final ActionListener container) - { - base = 0; - int capacity = table.length; - if (value < 0) { - capacity += Math.abs(value); - base = value; - } else if (value >= table.length) { - capacity = value + 1; - } - final List values = new ArrayList<>(capacity); - for (int i = 0; i < capacity; i++) { - values.add(toString(base + i)); - } - list = new TextListPanel<>(values, false); - list.addMouseListener(new MouseAdapter() - { - @Override - public void mouseClicked(MouseEvent event) - { - if (event.getClickCount() == 2) { - container.actionPerformed(new ActionEvent(this, 0, StructViewer.UPDATE_VALUE)); - } - } - }); - if (value >= base && value < list.getModel().getSize() + base) { - int index = 0; - while (!list.getModel().getElementAt(index).equals(toString(value))) { - index++; - } - list.setSelectedIndex(index); + private final BiFunction formatterBitmap = (value, item) -> { + String number; + if (isShowAsHex()) { + number = getHexValue(value.longValue()); + } else { + number = value.toString(); } - - JButton bUpdate = new JButton("Update value", Icons.getIcon(Icons.ICON_REFRESH_16)); - bUpdate.addActionListener(container); - bUpdate.setActionCommand(StructViewer.UPDATE_VALUE); - - GridBagLayout gbl = new GridBagLayout(); - GridBagConstraints gbc = new GridBagConstraints(); - JPanel panel = new JPanel(gbl); - - gbc.weightx = 1.0; - gbc.weighty = 1.0; - gbc.fill = GridBagConstraints.BOTH; - gbl.setConstraints(list, gbc); - panel.add(list); - - gbc.weightx = 0.0; - gbc.fill = GridBagConstraints.NONE; - gbc.insets.left = 6; - gbl.setConstraints(bUpdate, gbc); - panel.add(bUpdate); - - panel.setMinimumSize(Misc.getScaledDimension(DIM_MEDIUM)); - return panel; - } - - @Override - public void select() - { - list.ensureIndexIsVisible(list.getSelectedIndex()); - } - - @Override - public boolean updateValue(AbstractStruct struct) - { - // updating value - setValue(calcValue()); - - // notifying listeners - fireValueUpdated(new UpdateEvent(this, struct)); - - return true; - } - // - - // - @Override - public void write(OutputStream os) throws IOException - { - writeInt(os, value); - } - // - - // - @Override - public int read(ByteBuffer buffer, int offset) - { - buffer.position(offset); - switch (getSize()) { - case 1: - value = buffer.get(); - break; - case 2: - value = buffer.getShort(); - break; - case 4: - value = buffer.getInt(); - break; - default: - throw new IllegalArgumentException(); + if (item != null) { + return item + " (" + number + ")"; + } else { + return "Unknown (" + number + ")"; } + }; - return offset + getSize(); - } - // - - // - @Override - public long getLongValue() + public Bitmap(ByteBuffer buffer, int offset, int length, String name, String[] table) { - return (long)value & 0xffffffffL; + this(buffer, offset, length, name, table, true, false); } - @Override - public int getValue() + public Bitmap(ByteBuffer buffer, int offset, int length, String name, String[] table, boolean signed) { - return value; + this(buffer, offset, length, name, table, true, false); } - // - @Override - public String toString() + public Bitmap(ByteBuffer buffer, int offset, int length, String name, String[] table, boolean signed, boolean showAsHex) { - return toString(value); + super(buffer, offset, length, name, createMap(table), null, signed); + setShowAsHex(showAsHex); + setFormatter(formatterBitmap); } - @Override - public int hashCode() - { - int hash = super.hashCode(); - hash = 31 * hash + Integer.hashCode(value); - return hash; - } + //--------------------- Begin Interface Editable --------------------- @Override - public boolean equals(Object o) + public JComponent edit(ActionListener container) { - if (!super.equals(o) || !(o instanceof Bitmap)) { - return false; + if (getDataOf(getLongValue()) == null) { + putItem(getLongValue(), "Unknown"); } - Bitmap other = (Bitmap)o; - boolean retVal = (value == other.value); - return retVal; - } - /** Returns the unformatted description of the specified value. */ - protected String getString(int nr) - { - if (nr >= 0 && nr < table.length) { - return table[nr]; - } - return null; + return super.edit(container); } - /** Returns a formatted description of the specified value. */ - private String toString(int nr) - { - if (nr < 0 || nr >= table.length) { - return "Unknown (" + nr + ')'; - } else if (table[nr] == null || table[nr].isEmpty()) { - return "Unknown (" + nr + ')'; - } else { - return table[nr] + " (" + nr + ')'; - } - } + //--------------------- End Interface Editable --------------------- - private void setValue(int newValue) + private static LongIntegerHashMap createMap(String[] symbols) { - final int oldValue = value; - value = newValue; - if (oldValue != newValue) { - firePropertyChange(oldValue, newValue); + LongIntegerHashMap retVal = new LongIntegerHashMap<>(); + for (int i = 0; i < symbols.length; i++) { + String symbol = (symbols[i] != null) ? symbols[i] : "(Unlabeled)"; + retVal.put(Long.valueOf(i), symbol); } - } - private int calcValue() - { - final String svalue = list.getSelectedValue(); - int val = base; - //FIXME: Ineffective code - while (!svalue.equals(toString(val))) { - val++; - } - return val; + return retVal; } } diff --git a/src/org/infinity/datatype/HashBitmap.java b/src/org/infinity/datatype/HashBitmap.java index 1213eaa4b..f01ae07cc 100644 --- a/src/org/infinity/datatype/HashBitmap.java +++ b/src/org/infinity/datatype/HashBitmap.java @@ -4,32 +4,11 @@ package org.infinity.datatype; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.io.IOException; -import java.io.OutputStream; import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.util.function.BiFunction; -import javax.swing.JButton; -import javax.swing.JComponent; -import javax.swing.JPanel; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; - -import org.infinity.gui.StructViewer; -import org.infinity.gui.TextListPanel; -import org.infinity.icon.Icons; import org.infinity.resource.AbstractStruct; import org.infinity.util.LongIntegerHashMap; -import org.infinity.util.Misc; -import org.infinity.util.ObjectString; /** * Field that represents an integer enumeration of some values. @@ -44,294 +23,45 @@ *
  • Value meaning: numerical value of this field
  • * */ -public class HashBitmap extends Datatype implements Editable, IsNumeric//TODO: try to unify with Bitmap +public class HashBitmap extends AbstractBitmap { - private final LongIntegerHashMap idsmap; - private final List buttonList; - private final JButton bUpdate; - private final boolean sortByName; - private TextListPanel list; - private long value; - - public HashBitmap(ByteBuffer buffer, int offset, int length, String name, - LongIntegerHashMap idsmap) - { - this(buffer, offset, length, name, idsmap, true); - } - - public HashBitmap(ByteBuffer buffer, int offset, int length, String name, - LongIntegerHashMap idsmap, boolean sortByName) - { - super(offset, length, name); - this.idsmap = normalizeHashMap(idsmap); - this.sortByName = sortByName; - this.bUpdate = new JButton("Update value", Icons.getIcon(Icons.ICON_REFRESH_16)); - this.buttonList = new ArrayList<>(); - this.buttonList.add(bUpdate); - - read(buffer, offset); - } - - // - @Override - public JComponent edit(final ActionListener container) - { - final List items = new ArrayList<>(idsmap.size()); - for (Object o : idsmap.values()) { - if (o != null) {//TODO: It seems that map never contains nulls and this check can be removed - items.add(o); - } - } - list = new TextListPanel<>(items, sortByName); - list.addMouseListener(new MouseAdapter() - { - @Override - public void mouseClicked(MouseEvent event) - { - if (event.getClickCount() == 2) - container.actionPerformed(new ActionEvent(this, 0, StructViewer.UPDATE_VALUE)); - } - }); - list.addListSelectionListener(new ListSelectionListener() { - @Override - public void valueChanged(ListSelectionEvent e) - { - if (!e.getValueIsAdjusting()) { - listItemChanged(); - } - } - }); - Object selected = idsmap.get(value); - if (selected != null) { - list.setSelectedValue(selected, true); - } - - bUpdate.addActionListener(container); - bUpdate.setActionCommand(StructViewer.UPDATE_VALUE); - - GridBagLayout gbl = new GridBagLayout(); - GridBagConstraints gbc = new GridBagConstraints(); - JPanel panel = new JPanel(gbl); - - gbc.weightx = 1.0; - gbc.weighty = 1.0; - gbc.gridheight = buttonList.size() + 2; - gbc.fill = GridBagConstraints.BOTH; - gbl.setConstraints(list, gbc); - panel.add(list); - - gbc.weightx = 0.0; - gbc.gridheight = 1; - gbc.gridx = 1; - gbc.gridy = 0; - gbc.insets.left = 6; - gbc.insets.top = 4; - gbc.insets.bottom = 4; - // dummy component to center list of buttons vertically - JPanel p = new JPanel(); - gbl.setConstraints(p, gbc); - panel.add(p); - ++gbc.gridy; - gbc.fill = GridBagConstraints.HORIZONTAL; - gbc.weighty = 0.0; - for (final JButton btn: buttonList) { - gbl.setConstraints(btn, gbc); - panel.add(btn); - ++gbc.gridy; - } - // dummy component to center list of buttons vertically - gbc.weighty = 1.0; - gbc.fill = GridBagConstraints.BOTH; - p = new JPanel(); - gbl.setConstraints(p, gbc); - panel.add(p); - - panel.setMinimumSize(Misc.getScaledDimension(DIM_MEDIUM)); - return panel; - } - - @Override - public void select() - { - list.ensureIndexIsVisible(list.getSelectedIndex()); - } - - @Override - public boolean updateValue(AbstractStruct struct) - { - long oldValue = getLongValue(); - // updating value - Long number = getValueOfItem(list.getSelectedValue()); - if (number != null) { - setValue(number.longValue()); + private final BiFunction formatterHashBitmap = (value, item) -> { + String number; + if (isShowAsHex()) { + number = getHexValue(value.longValue()); } else { - return false; - } - - // notifying listeners - if (getLongValue() != oldValue) { - fireValueUpdated(new UpdateEvent(this, struct)); - } - - return true; - } - - // - @Override - public void write(OutputStream os) throws IOException - { - writeLong(os, value); - } - // - - // - @Override - public int read(ByteBuffer buffer, int offset) - { - buffer.position(offset); - switch (getSize()) { - case 1: - value = buffer.get() & 0xffL; - break; - case 2: - value = buffer.getShort() & 0xffffL; - break; - case 4: - value = buffer.getInt() & 0xffffffffL; - break; - default: - throw new IllegalArgumentException(); - } - - return offset + getSize(); - } - // - // - - @Override - public String toString() - { - final Object o = getValueOf(value); - return o == null ? "Unknown - " + value : o.toString(); - } - - @Override - public int hashCode() - { - int hash = super.hashCode(); - hash = 31 * hash + Long.hashCode(value); - return hash; - } - - @Override - public boolean equals(Object o) - { - if (!super.equals(o) || !(o instanceof HashBitmap)) { - return false; + number = value.toString(); } - HashBitmap other = (HashBitmap)o; - boolean retVal = (value == other.value); - return retVal; - } - - // - @Override - public long getLongValue() - { - return value; - } - - @Override - public int getValue() - { - return (int)value; - } - // - - protected void setValue(long newValue) - { - final long oldValue = value; - this.value = newValue; - if (oldValue != newValue) { - firePropertyChange(oldValue, newValue); - } - } - - /** Called whenever the user selects a new list item. */ - protected void listItemChanged() - { - } - - /** - * Can be used to register one or more custom buttons to the bitmap control. - * Only effective if called before the UI control is created. - */ - protected void addButtons(JButton... buttons) - { - if (list == null) { - for (final JButton button: buttons) { - if (button != null) { - buttonList.add(button); - } - } + if (item != null) { + return item.toString() + " - " + number; + } else { + return "Unknown - " + number; } - } - - /** Returns the TextListPanel control used by this datatype. */ - public TextListPanel getListPanel() - { - return list; - } + }; - /** Returns the textual representation of the specified IDS value. */ - public Object getValueOf(long key) + public HashBitmap(ByteBuffer buffer, int offset, int length, String name, LongIntegerHashMap idsmap) { - return idsmap.get(Long.valueOf(key)); + this(buffer, offset, length, name, idsmap, true, false, false); } - protected Long getCurrentValue() + public HashBitmap(ByteBuffer buffer, int offset, int length, String name, LongIntegerHashMap idsmap, + boolean sortByName) { - return getValueOfItem(list.getSelectedValue()); + this(buffer, offset, length, name, idsmap, sortByName, false, false); } - /** Attempts to extract the IDS value from the specified list item. */ - private Long getValueOfItem(Object item) + public HashBitmap(ByteBuffer buffer, int offset, int length, String name, LongIntegerHashMap idsmap, + boolean sortByName, boolean signed) { - Long retVal = null; - if (item != null) { - if (item instanceof ObjectString && ((ObjectString)item).getObject() instanceof Number) { - retVal = ((Number)((ObjectString)item).getObject()).longValue(); - } else { - int i = item.toString().lastIndexOf(" - ");//FIXME: Smell code - try { - retVal = Long.parseLong(item.toString().substring(i + 3)); - } catch (NumberFormatException e) { - retVal = null; - } - } - } - return retVal; + this(buffer, offset, length, name, idsmap, sortByName, signed, false); } - protected LongIntegerHashMap getHashBitmap() + public HashBitmap(ByteBuffer buffer, int offset, int length, String name, LongIntegerHashMap idsmap, + boolean sortByName, boolean signed, boolean showAsHex) { - return idsmap; - } - - private static LongIntegerHashMap normalizeHashMap(LongIntegerHashMap map) - { - //TODO: The smelling code. It seems that there is a check on the fact that the map contains String's - if (map != null && !map.isEmpty() && map.firstEntry().getValue() instanceof String) { - final LongIntegerHashMap retVal = new LongIntegerHashMap<>(); - for (Map.Entry e : map.entrySet()) { - retVal.put(e.getKey(), new ObjectString(e.getValue().toString(), e.getKey(), - ObjectString.FMT_OBJECT_HYPHEN)); - } - return retVal; - } else if (map == null) { - return new LongIntegerHashMap(); - } else { - return map; - } + super(buffer, offset, length, name, idsmap, null, signed); + setSortByName(sortByName); + setShowAsHex(showAsHex); + setFormatter(formatterHashBitmap); } } diff --git a/src/org/infinity/datatype/IdsBitmap.java b/src/org/infinity/datatype/IdsBitmap.java index 45290c91b..c80226b57 100644 --- a/src/org/infinity/datatype/IdsBitmap.java +++ b/src/org/infinity/datatype/IdsBitmap.java @@ -4,32 +4,66 @@ package org.infinity.datatype; +import java.awt.event.ActionListener; import java.nio.ByteBuffer; +import java.util.function.BiFunction; + +import javax.swing.JComponent; import org.infinity.util.IdsMap; import org.infinity.util.IdsMapCache; import org.infinity.util.IdsMapEntry; import org.infinity.util.LongIntegerHashMap; -public class IdsBitmap extends HashBitmap +public class IdsBitmap extends AbstractBitmap { + private final BiFunction formatterIdsBitmap = (value, item) -> { + String number; + if (isShowAsHex()) { + number = getHexValue(value.longValue()); + } else { + number = value.toString(); + } + if (item != null) { + return item.getSymbol() + " - " + number; + } else { + return "Unknown - " + number; + } + }; + public IdsBitmap(ByteBuffer buffer, int offset, int length, String name, String resource) { - this(buffer, offset, length, name, resource, true); + this(buffer, offset, length, name, resource, true, false); } public IdsBitmap(ByteBuffer buffer, int offset, int length, String name, String resource, boolean sortByName) { - super(buffer, offset, length, name, createResourceList(resource), sortByName); + this(buffer, offset, length, name, resource, sortByName, false); } + public IdsBitmap(ByteBuffer buffer, int offset, int length, String name, String resource, boolean sortByName, + boolean showAsHex) + { + super(buffer, offset, length, name, createResourceList(resource), null, true); + setSortByName(sortByName); + setShowAsHex(showAsHex); + setFormatter(formatterIdsBitmap); + } + + //--------------------- Begin Interface Editable --------------------- + @Override - @SuppressWarnings("unchecked") - public LongIntegerHashMap getHashBitmap() + public JComponent edit(ActionListener container) { - return (LongIntegerHashMap)super.getHashBitmap(); + if (getDataOf(getLongValue()) == null) { + putItem(getLongValue(), new IdsMapEntry(getLongValue(), "Unknown")); + } + + return super.edit(container); } + //--------------------- End Interface Editable --------------------- + /** * Add to bitmap specified entry, id entry with such key not yet registered, * otherwise do nothing. @@ -38,7 +72,7 @@ public LongIntegerHashMap getHashBitmap() */ public void addIdsMapEntry(IdsMapEntry entry) { - getHashBitmap().putIfAbsent(entry.getID(), entry); + getBitmap().putIfAbsent(entry.getID(), entry); } private static LongIntegerHashMap createResourceList(String resource) diff --git a/src/org/infinity/datatype/IdsTargetType.java b/src/org/infinity/datatype/IdsTargetType.java index 6db486e77..980c62bbe 100644 --- a/src/org/infinity/datatype/IdsTargetType.java +++ b/src/org/infinity/datatype/IdsTargetType.java @@ -130,7 +130,7 @@ public StructEntry createIdsValueFromType(ByteBuffer buffer, int offset) public StructEntry createIdsValueFromType(ByteBuffer buffer, int offset, int size, String name) { int value = getValue(); - String type = getString(value); + String type = getDataOf(value); if (type != null) { if (ResourceFactory.resourceExists(type)) { return new IdsBitmap(buffer, offset, size, createFieldName(name, index, DEFAULT_NAME_VALUE), type); @@ -155,7 +155,7 @@ public StructEntry createResourceFromType(ByteBuffer buffer, int offset) public StructEntry createResourceFromType(ByteBuffer buffer, int offset, String name) { int value = getValue(); - String type = getString(value); + String type = getDataOf(value); if (type != null) { if (value == 11 && !type.isEmpty() && diff --git a/src/org/infinity/datatype/IsNumeric.java b/src/org/infinity/datatype/IsNumeric.java index d50251f40..366d1d5e0 100644 --- a/src/org/infinity/datatype/IsNumeric.java +++ b/src/org/infinity/datatype/IsNumeric.java @@ -6,6 +6,9 @@ public interface IsNumeric { + /** Returns the current value as {@code long}. */ long getLongValue(); + + /** Returns the current value as {@code int}. */ int getValue(); } diff --git a/src/org/infinity/datatype/IwdRef.java b/src/org/infinity/datatype/IwdRef.java index a1c42bc6f..83b28ad51 100644 --- a/src/org/infinity/datatype/IwdRef.java +++ b/src/org/infinity/datatype/IwdRef.java @@ -25,12 +25,14 @@ public IwdRef(ByteBuffer buffer, int offset, String name, String idsFile) public long getValue(String ref) { if (ref != null && !ref.isEmpty()) { - ref = ref.toUpperCase(Locale.ENGLISH); - List list = getResourceList(); - for (final RefEntry entry: list) { - if (entry.getResourceName().equals(ref)) { - return entry.getValue(); - } + RefEntry entry = getBitmap() + .values() + .parallelStream() + .filter(re -> re.getResourceName().equalsIgnoreCase(ref)) + .findAny() + .orElse(null); + if (entry != null) { + return entry.getValue(); } } return -1L; @@ -47,11 +49,9 @@ public String getValueRef() public String getValueRef(long id) { - List list = getResourceList(); - for (final RefEntry entry: list) { - if (entry.getValue() == id) { - return entry.getResourceName(); - } + RefEntry result = getBitmap().values().parallelStream().filter(re -> re.getValue() == id).findAny().orElse(null); + if (result != null) { + return result.getResourceName(); } return NONE; } diff --git a/src/org/infinity/datatype/KitIdsBitmap.java b/src/org/infinity/datatype/KitIdsBitmap.java index cfc7e74b3..6920da8e5 100644 --- a/src/org/infinity/datatype/KitIdsBitmap.java +++ b/src/org/infinity/datatype/KitIdsBitmap.java @@ -18,6 +18,7 @@ public class KitIdsBitmap extends IdsBitmap public KitIdsBitmap(ByteBuffer buffer, int offset, String name) { super(buffer, offset, 4, name, "KIT.IDS"); + setShowAsHex(true); // adding "No Kit" value if needed addIdsMapEntry(new IdsMapEntry(0L, "NO_KIT")); @@ -35,6 +36,12 @@ public void write(OutputStream os) throws IOException //--------------------- End Interface Writeable --------------------- + @Override + protected String getHexValue(long value) + { + return String.format("0x%04X", value); + } + /** Swaps position of the two lower words. */ private static long swapWords(long value) { diff --git a/src/org/infinity/datatype/ResourceBitmap.java b/src/org/infinity/datatype/ResourceBitmap.java index 94a9e3910..817bce18b 100644 --- a/src/org/infinity/datatype/ResourceBitmap.java +++ b/src/org/infinity/datatype/ResourceBitmap.java @@ -4,34 +4,21 @@ package org.infinity.datatype; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.io.IOException; -import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; +import java.util.function.BiFunction; import javax.swing.JButton; -import javax.swing.JComponent; -import javax.swing.JPanel; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; -import org.infinity.gui.StructViewer; -import org.infinity.gui.TextListPanel; import org.infinity.gui.ViewFrame; import org.infinity.icon.Icons; import org.infinity.resource.AbstractStruct; import org.infinity.resource.ResourceFactory; import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.LongIntegerHashMap; import org.infinity.util.Misc; /** @@ -47,271 +34,106 @@ *
  • Value meaning: index of the resource in the predefined list
  • * */ -public class ResourceBitmap extends Datatype - implements Editable, IsNumeric, IsReference, ActionListener, ListSelectionListener +public class ResourceBitmap extends AbstractBitmap implements IsReference, ActionListener { /** Print resource reference together with search name in parentheses and value after hyphen. */ - public static final String FMT_REF_NAME_VALUE = "%1$s (%2$s) - %3$s"; + public static final String FMT_REF_NAME_VALUE = "%s (%s) - %s"; /** Print search name together with resource reference in parentheses and value after hyphen. */ public static final String FMT_NAME_REF_VALUE = "%2$s (%1$s) - %3$s"; /** Print resource reference together with its search name after hyphen. */ - public static final String FMT_REF_HYPHEN_NAME = "%1$s - %2$s"; + public static final String FMT_REF_HYPHEN_NAME = "%s - %s"; /** Print search name together with its resource reference after hyphen. */ public static final String FMT_NAME_HYPHEN_REF = "%2$s - %1$s"; /** Print resource reference together with value after hyphen. */ public static final String FMT_REF_HYPHEN_VALUE = "%1$s - %3$s"; /** Print resource reference together with its search name. */ - public static final String FMT_REF_NAME = "%1$s (%2$s)"; + public static final String FMT_REF_NAME = "%s (%s)"; /** Print search name together with its resource reference. */ public static final String FMT_NAME_REF = "%2$s (%1$s)"; /** Print resource reference together with value in parentheses. */ public static final String FMT_REF_VALUE = "%1$s (%3$s)"; /** Print resource reference only. */ - public static final String FMT_REF_ONLY = "%1$s"; + public static final String FMT_REF_ONLY = "%s"; /** Print only the search name of the resource. */ public static final String FMT_NAME_ONLY = "%2$s"; /** Print resource value only. */ public static final String FMT_VALUE_ONLY = "%3$s"; - private final List resources; - private final String defaultLabel; - private final String formatString; - private JButton bView; - private TextListPanel list; - private long value; - - public ResourceBitmap(ByteBuffer buffer, int offset, int length, String name, - List resources, String defLabel, String fmt) - { - super(offset, length, name); - this.formatString = (fmt != null) ? fmt : FMT_REF_VALUE; - this.defaultLabel = (defLabel != null) ? defLabel : "Unknown"; - - this.resources = new ArrayList<>((resources != null) ? resources.size() : 10); - if (resources != null) { - for (final RefEntry entry: resources) { - entry.setFormatString(this.formatString); - this.resources.add(entry); - } + private final BiFunction formatterResourceBitmap = (value, item) -> { + String number; + if (isShowAsHex()) { + number = getHexValue(value.longValue()); + } else { + number = value.toString(); } - Collections.sort(this.resources); - - read(buffer, offset); - } - -//--------------------- Begin Interface ActionListener --------------------- - @Override - public void actionPerformed(ActionEvent event) - { - if (event.getSource() == bView) { - final RefEntry selected = list.getSelectedValue(); - if (selected != null) { - final ResourceEntry entry = selected.getResourceEntry(); - new ViewFrame(list.getTopLevelAncestor(), entry == null ? null : ResourceFactory.getResource(entry)); - } + String resName, searchString; + if (item != null) { + resName = item.getResourceName(); + searchString = item.getSearchString(); + } else { + resName = getDefaultLabel(); + searchString = ""; } - } -//--------------------- End Interface ActionListener --------------------- + return String.format(getFormatString(), resName, searchString, number); + }; -//--------------------- Begin Interface ListSelectionListener --------------------- + private final String defaultLabel; + private final String formatString; + private final JButton bView; - @Override - public void valueChanged(ListSelectionEvent e) + public ResourceBitmap(ByteBuffer buffer, int offset, int length, String name, List resources, + String defLabel, String fmt) { - final RefEntry selected = list.getSelectedValue(); - bView.setEnabled(selected != null && selected.isResource()); + this(buffer, offset, length, name, resources, defLabel, fmt, false); } -//--------------------- End Interface ListSelectionListener --------------------- - -//--------------------- Begin Interface Editable --------------------- - - @Override - public JComponent edit(final ActionListener container) + public ResourceBitmap(ByteBuffer buffer, int offset, int length, String name, List resources, + String defLabel, String fmt, boolean signed) { - list = new TextListPanel<>(resources, false); - list.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent event) - { - if (event.getClickCount() == 2) - container.actionPerformed(new ActionEvent(this, 0, StructViewer.UPDATE_VALUE)); - } - }); + super(buffer, offset, length, name, createMap(resources), null, true); + this.formatString = (fmt != null) ? fmt : FMT_REF_VALUE; + this.defaultLabel = (defLabel != null) ? defLabel : "Unknown"; + this.setSortByName(true); + this.setFormatter(formatterResourceBitmap); - RefEntry curEntry = getRefEntry(value); + RefEntry curEntry = getDataOf(getLongValue()); if (curEntry == null) { - curEntry = getRefEntry(0L); + curEntry = getDataOf(0L); } - if (curEntry == null && resources.size() > 0) { + if (curEntry == null && resources != null && resources.size() > 0) { curEntry = resources.get(0); } - if (curEntry != null) { - list.setSelectedValue(curEntry, true); - } - - JButton bUpdate = new JButton("Update value", Icons.getIcon(Icons.ICON_REFRESH_16)); - bUpdate.addActionListener(container); - bUpdate.setActionCommand(StructViewer.UPDATE_VALUE); - bView = new JButton("View/Edit", Icons.getIcon(Icons.ICON_ZOOM_16)); - bView.addActionListener(this); - bView.setEnabled(curEntry != null && curEntry.isResource()); - list.addListSelectionListener(this); - - GridBagLayout gbl = new GridBagLayout(); - GridBagConstraints gbc = new GridBagConstraints(); - JPanel panel = new JPanel(gbl); - - gbc.weightx = 1.0; - gbc.weighty = 1.0; - gbc.fill = GridBagConstraints.BOTH; - gbc.gridheight = 2; - gbl.setConstraints(list, gbc); - panel.add(list); - - gbc.gridheight = 1; - gbc.weightx = 0.0; - gbc.fill = GridBagConstraints.HORIZONTAL; - gbc.insets = new Insets(3, 6, 3, 0); - gbc.anchor = GridBagConstraints.SOUTH; - gbc.gridwidth = GridBagConstraints.REMAINDER; - gbl.setConstraints(bUpdate, gbc); - panel.add(bUpdate); - - gbc.gridx = 1; - gbc.gridy = 1; - gbc.anchor = GridBagConstraints.NORTH; - gbl.setConstraints(bView, gbc); - panel.add(bView); - - panel.setMinimumSize(Misc.getScaledDimension(DIM_MEDIUM)); - return panel; - } - - @Override - public void select() - { - list.ensureIndexIsVisible(list.getSelectedIndex()); - } - - @Override - public boolean updateValue(AbstractStruct struct) - { - final RefEntry selected = list.getSelectedValue(); - if (selected == null) { - return false; - } - - long oldValue = getLongValue(); - setValue(selected.getValue()); - - // notifying listeners - if (getLongValue() != oldValue) { - fireValueUpdated(new UpdateEvent(this, struct)); - } - - return true; - } - -//--------------------- End Interface Editable --------------------- - -//--------------------- Begin Interface Writeable --------------------- - - @Override - public void write(OutputStream os) throws IOException - { - writeLong(os, value); - } - -//--------------------- End Interface Writeable --------------------- - -//--------------------- Begin Interface Readable --------------------- - - @Override - public int read(ByteBuffer buffer, int offset) - { - buffer.position(offset); - switch (getSize()) { - case 1: - value = buffer.get() & 0xff; - break; - case 2: - value = buffer.getShort() & 0xffff; - break; - case 4: - value = buffer.getInt() & 0xffffffff; - break; - default: - throw new IllegalArgumentException(); - } - - return offset + getSize(); + this.bView = new JButton("View/Edit", Icons.getIcon(Icons.ICON_ZOOM_16)); + this.bView.addActionListener(this); + this.bView.setEnabled(curEntry != null && curEntry.isResource()); + addButtons(this.bView); } -//--------------------- End Interface Readable --------------------- + //--------------------- Begin Interface ActionListener --------------------- @Override - public String toString() + public void actionPerformed(ActionEvent e) { - String resName, searchString; - RefEntry ref = getRefEntry(value); - if (ref != null) { - resName = ref.getResourceName(); - searchString = ref.getSearchString(); - } else { - resName = defaultLabel; - searchString = ""; - } - - return String.format(formatString, resName, searchString, Long.toString(value)); - } - - @Override - public int hashCode() - { - int hash = super.hashCode(); - hash = 31 * hash + ((resources == null) ? 0 : resources.hashCode()); - hash = 31 * hash + Long.hashCode(value); - return hash; - } - - @Override - public boolean equals(Object o) - { - if (!super.equals(o) || !(o instanceof ResourceBitmap)) { - return false; + if (e.getSource() == bView) { + Long value = getSelectedValue(); + final RefEntry selected = (value != null) ? getDataOf(value.longValue()) : null; + if (selected != null) { + final ResourceEntry entry = selected.getResourceEntry(); + new ViewFrame(getUiControl().getTopLevelAncestor(), ResourceFactory.getResource(entry)); + } } - ResourceBitmap other = (ResourceBitmap)o; - boolean retVal = (resources == null && other.resources == null) || - (resources != null && resources.equals(other.resources)); - retVal &= (value == other.value); - return retVal; } - //--------------------- Begin Interface IsNumeric --------------------- - - @Override - public int getValue() - { - return (int)value; - } - - @Override - public long getLongValue() - { - return value; - } - -//--------------------- End Interface IsNumeric --------------------- + //--------------------- End Interface ActionListener --------------------- //--------------------- Begin Interface IsReference --------------------- @Override public String getResourceName() { - RefEntry entry = getRefEntry(value); + RefEntry entry = getDataOf(getLongValue()); if (entry != null) { return entry.getResourceName(); } else { @@ -321,33 +143,35 @@ public String getResourceName() //--------------------- End Interface IsReference --------------------- - public List getResourceList() + @Override + protected void listItemChanged() { - return resources; + Long value = getSelectedValue(); + RefEntry selected = (value != null) ? getDataOf(value.longValue()) : null; + bView.setEnabled(selected != null && selected.isResource()); } - public String getFormatString() + protected String getFormatString() { return formatString; } - private RefEntry getRefEntry(long value) + protected String getDefaultLabel() { - for (final RefEntry entry : resources) { - if (entry.getValue() == value) { - return entry; - } - } - return null; + return defaultLabel; } - private void setValue(long newValue) +private static LongIntegerHashMap createMap(List resources) { - final long oldValue = value; - value = newValue; - if (oldValue != newValue) { - firePropertyChange(oldValue, newValue); + LongIntegerHashMap retVal = new LongIntegerHashMap<>(); + if (resources != null) { + for (final RefEntry entry : resources) { + if (entry != null) { + retVal.put(Long.valueOf(entry.getValue()), entry); + } + } } + return retVal; } //-------------------------- INNER CLASSES -------------------------- @@ -362,8 +186,6 @@ public static final class RefEntry implements Comparable private final ResourceEntry entry; /** Resource-dependent search string. */ private final String searchString; - /** Format string for textual representation. */ - private String fmt; /** Cached textual output for {@link #toString()} method. */ private String desc; @@ -388,8 +210,7 @@ public RefEntry(long value, String ref, String search, List searchDirs) this.searchString = (search != null) ? search : ""; this.name = ref; } - this.fmt = FMT_REF_VALUE; - this.desc = String.format(fmt, getResourceName(), getSearchString(), Long.toString(value)); + this.desc = String.format(FMT_REF_VALUE, getResourceName(), getSearchString(), Long.toString(value)); } @Override @@ -429,17 +250,5 @@ public String getSearchString() { return searchString; } - - public String getFormatString() { return fmt; } - - public void setFormatString(String fmt) - { - if (fmt != null) { - this.fmt = fmt; - } else { - this.fmt = FMT_REF_VALUE; - } - this.desc = String.format(fmt, getResourceName(), getSearchString(), Long.toString(value)); - } } } diff --git a/src/org/infinity/datatype/Song2daBitmap.java b/src/org/infinity/datatype/Song2daBitmap.java index ed1afcef5..f250fe489 100644 --- a/src/org/infinity/datatype/Song2daBitmap.java +++ b/src/org/infinity/datatype/Song2daBitmap.java @@ -37,7 +37,7 @@ public Song2daBitmap(ByteBuffer buffer, int offset, int length) public Song2daBitmap(ByteBuffer buffer, int offset, int length, String name) { - super(buffer, offset, length, name, createSongList(), "Unknown", FormatString); + super(buffer, offset, length, name, createSongList(), "Unknown", FormatString, true); } public static synchronized List createSongList() @@ -79,8 +79,8 @@ private static List createSongList_SONGLIST(List searchDirs) Table2da table = Table2daCache.get(TableName); String defValue = table.getDefaultValue(); SongList.ensureCapacity(2 + table.getRowCount()); - SongList.add(new RefEntry(0xfffffffeL, "Special", "Continue area music")); - SongList.add(new RefEntry(0xffffffffL, "Special", "Continue outside music")); + SongList.add(new RefEntry(-2L, "Special", "Continue area music")); + SongList.add(new RefEntry(-1L, "Special", "Continue outside music")); for (int i = 0, size = table.getRowCount(); i < size; i++) { String name = table.get(i, 1); @@ -100,8 +100,8 @@ private static List createSongList_MUSIC(List searchDirs) if (SongList.isEmpty()) { IdsMap map = IdsMapCache.get(TableName); SongList.ensureCapacity(2 + map.size()); - SongList.add(new RefEntry(0xfffffffeL, "Continue area music")); - SongList.add(new RefEntry(0xffffffffL, "Continue outside music")); + SongList.add(new RefEntry(-2L, "Continue area music")); + SongList.add(new RefEntry(-1L, "Continue outside music")); for (final IdsMapEntry e: map.getAllValues()) { String name = e.getSymbol(); @@ -167,8 +167,8 @@ private static List createSongList_PST(List searchDirs) }; SongList.ensureCapacity(2 + map.length); - SongList.add(new RefEntry(0xfffffffeL, "Special", "Continue area music")); - SongList.add(new RefEntry(0xffffffffL, "Special", "Continue outside music")); + SongList.add(new RefEntry(-2L, "Special", "Continue area music")); + SongList.add(new RefEntry(-1L, "Special", "Continue outside music")); for (int i = 0; i < map.length; i+=2) { final String ref = map[i]; @@ -230,8 +230,8 @@ private static List createSongList_BG1(List searchDirs) /*38L,*/ "DEATH.MUS", }; - SongList.add(new RefEntry(0xfffffffeL, "Continue area music")); - SongList.add(new RefEntry(0xffffffffL, "Continue outside music")); + SongList.add(new RefEntry(-2L, "Continue area music")); + SongList.add(new RefEntry(-1L, "Continue outside music")); for (int i = 0; i < map.length; ++i) { SongList.add(new RefEntry(i, map[i], null, searchDirs)); diff --git a/src/org/infinity/gui/BrowserMenuBar.java b/src/org/infinity/gui/BrowserMenuBar.java index 0ac0f4d8c..09ca2b485 100644 --- a/src/org/infinity/gui/BrowserMenuBar.java +++ b/src/org/infinity/gui/BrowserMenuBar.java @@ -98,9 +98,9 @@ import org.infinity.updater.Updater; import org.infinity.updater.UpdaterSettings; import org.infinity.util.CharsetDetector; +import org.infinity.util.DataString; import org.infinity.util.MassExporter; import org.infinity.util.Misc; -import org.infinity.util.ObjectString; import org.infinity.util.Platform; import org.infinity.util.StringTable; import org.infinity.util.io.FileEx; @@ -3302,44 +3302,44 @@ private void displayAbout() // title string final String versionString = "Near Infinity " + VERSION; // list of current links - final ObjectString[] currentLinks = { - new ObjectString("Active branch", "https://github.com/Argent77/NearInfinity/"), - new ObjectString("Main branch", "https://github.com/NearInfinityBrowser/NearInfinity/"), - new ObjectString("Wiki page", wikiUrl), - }; + final List> currentLinks = new ArrayList>() {{ + add(DataString.with("Active branch", "https://github.com/Argent77/NearInfinity/")); + add(DataString.with("Main branch", "https://github.com/NearInfinityBrowser/NearInfinity/")); + add(DataString.with("Wiki page", wikiUrl)); + }}; // original author final String originalVersion = "From Near Infinity 1.32.1 beta 24"; final String originalCopyright = "Copyright (\u00A9) 2001-2005 - Jon Olav Hauglid"; - final ObjectString originalLink = new ObjectString("Website", "http://www.idi.ntnu.no/~joh/ni/"); + final DataString originalLink = DataString.with("Website", "http://www.idi.ntnu.no/~joh/ni/"); // List of contributors (sorted alphabetically) - final String[] contributors = { - "Argent77", - "Bubb", - "devSin", - "Fredrik Lindgren (aka Wisp)", - "FredSRichardson", - "Mingun", - "Taimon", - "Valerio Bigiani (aka The Bigg)", - "winterheart", - }; + final List contributors = new ArrayList() {{ + add("Argent77"); + add("Bubb"); + add("devSin"); + add("Fredrik Lindgren (aka Wisp)"); + add("FredSRichardson"); + add("Mingun"); + add("Taimon"); + add("Valerio Bigiani (aka The Bigg)"); + add("winterheart"); + }}; // More contributors, in separate block - final String[] contributorsMisc = { - "Near Infinity logo/icon by Cuv and Troodon80", - }; + final List contributorsMisc = new ArrayList() {{ + add("Near Infinity logo/icon by Cuv and Troodon80"); + }}; // copyright message - final String[] copyNearInfinityText = { - "This program is free and may be distributed according to the terms of ", - "the GNU Lesser General Public License." - }; + final List copyNearInfinityText = new ArrayList() {{ + add("This program is free and may be distributed according to the terms of "); + add("the GNU Lesser General Public License."); + }}; // Third-party copyright messages - final String[] copyThirdPartyText = { - "Most icons (\u00A9) eclipse.org - Common Public License.", - "RSyntaxTextArea (\u00A9) Fifesoft - Berkeley Software Distribution License.", - "Monte Media Library by Werner Randelshofer - GNU Lesser General Public License.", - "JOrbis (\u00A9) JCraft Inc. - GNU Lesser General Public License.", - "JHexView by Sebastian Porst - GNU General Public License.", - }; + final List copyThirdPartyText = new ArrayList() {{ + add("Most icons (\u00A9) eclipse.org - Common Public License."); + add("RSyntaxTextArea (\u00A9) Fifesoft - Berkeley Software Distribution License."); + add("Monte Media Library by Werner Randelshofer - GNU Lesser General Public License."); + add("JOrbis (\u00A9) JCraft Inc. - GNU Lesser General Public License."); + add("JHexView by Sebastian Porst - GNU General Public License."); + }}; // Fixed elements final Font defaultfont = UIManager.getFont("Label.font"); @@ -3357,11 +3357,11 @@ private void displayAbout() { int row = 0; // current links - for (int i = 0; i < currentLinks.length; i++, row++) { + for (int i = 0; i < currentLinks.size(); i++, row++) { int top = (i > 0) ? 4 : 0; - JLabel lTitle = new JLabel(currentLinks[i].getString() + ":"); + JLabel lTitle = new JLabel(currentLinks.get(i).getString() + ":"); lTitle.setFont(font); - String link = currentLinks[i].getObject(); + String link = currentLinks.get(i).getData(); JLabel lLink = ViewerUtil.createUrlLabel(link); lLink.setFont(font); gbc = ViewerUtil.setGBC(gbc, 0, row, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, @@ -3390,7 +3390,7 @@ private void displayAbout() gbc = ViewerUtil.setGBC(gbc, 0, row, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 0, 0); pLinks.add(label, gbc); - String link = originalLink.getObject(); + String link = originalLink.getData(); label = ViewerUtil.createUrlLabel(link); label.setFont(font); gbc = ViewerUtil.setGBC(gbc, 1, row, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, @@ -3405,8 +3405,8 @@ private void displayAbout() // trying to limit line width to a certain maximum FontMetrics fm = getFontMetrics(font); double maxWidth = 0.0; - for (int i = 0; i < currentLinks.length; i++) { - String s = currentLinks[i].getString() + ": " + currentLinks[i].getObject(); + for (int i = 0; i < currentLinks.size(); i++) { + String s = currentLinks.get(i).getString() + ": " + currentLinks.get(i).getData(); maxWidth = Math.max(maxWidth, fm.getStringBounds(s, getGraphics()).getWidth()); } @@ -3421,15 +3421,15 @@ private void displayAbout() // adding names StringBuilder sb = new StringBuilder(); - for (int i = 0; i < contributors.length; i++) { + for (int i = 0; i < contributors.size(); i++) { if (i > 0) { - if (i+1 == contributors.length) { + if (i+1 == contributors.size()) { sb.append(" and "); } else { sb.append(", "); } } - String s = sb.toString() + contributors[i]; + String s = sb.toString() + contributors.get(i); if (fm.getStringBounds(s, getGraphics()).getWidth() > maxWidth) { label = new JLabel(sb.toString()); label.setFont(smallFont); @@ -3439,7 +3439,7 @@ private void displayAbout() row++; sb = new StringBuilder(); } - sb.append(contributors[i]); + sb.append(contributors.get(i)); } label = new JLabel(sb.toString()); label.setFont(smallFont); @@ -3449,8 +3449,8 @@ private void displayAbout() row++; // Adding misc. contributors - for (int i = 0; i < contributorsMisc.length; i++) { - label = new JLabel(contributorsMisc[i]); + for (int i = 0; i < contributorsMisc.size(); i++) { + label = new JLabel(contributorsMisc.get(i)); label.setFont(smallFont); gbc = ViewerUtil.setGBC(gbc, 0, row, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(i == 0 ? 4 : 0, 0, 0, 0), 0, 0); @@ -3470,8 +3470,8 @@ private void displayAbout() pLicense.add(label, gbc); row++; - for (int i = 0; i < copyNearInfinityText.length; i++) { - label = new JLabel(copyNearInfinityText[i]); + for (int i = 0; i < copyNearInfinityText.size(); i++) { + label = new JLabel(copyNearInfinityText.get(i)); label.setFont(smallFont); gbc = ViewerUtil.setGBC(gbc, 0, row, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); @@ -3491,8 +3491,8 @@ private void displayAbout() pMiscLicenses.add(label, gbc); row++; - for (int i = 0; i < copyThirdPartyText.length; i++) { - label = new JLabel(copyThirdPartyText[i]); + for (int i = 0; i < copyThirdPartyText.size(); i++) { + label = new JLabel(copyThirdPartyText.get(i)); label.setFont(smallFont); gbc = ViewerUtil.setGBC(gbc, 0, row, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); diff --git a/src/org/infinity/gui/OpenResourceDialog.java b/src/org/infinity/gui/OpenResourceDialog.java index 68ebcaa76..3b532680f 100644 --- a/src/org/infinity/gui/OpenResourceDialog.java +++ b/src/org/infinity/gui/OpenResourceDialog.java @@ -46,7 +46,7 @@ import org.infinity.resource.Profile; import org.infinity.resource.ResourceFactory; import org.infinity.resource.key.ResourceEntry; -import org.infinity.util.ObjectString; +import org.infinity.util.DataString; import org.infinity.util.SimpleListModel; /** @@ -59,10 +59,10 @@ public class OpenResourceDialog extends JDialog private final List> resources = new ArrayList<>(); private ResourceEntry[] result; - private ObjectString[] extensions; + private List> extensions; private JList list; private SimpleListModel listModel; - private JComboBox cbType; + private JComboBox> cbType; private JTextField tfSearch; private PlainDocument searchDoc; private JButton bOpen, bCancel; @@ -190,10 +190,10 @@ protected void setExtensions(String[] extList) { if (extList != null && extList.length > 0) { int extra = (extList.length > 1) ? 1 : 0; - extensions = new ObjectString[extList.length + extra]; + extensions = new ArrayList<>();// ObjectString[extList.length + extra]; for (int i = 0; i < extList.length; i++) { final String s = extList[i].trim().toUpperCase(Locale.ENGLISH); - extensions[i + extra] = new ObjectString(s + " resources", s, ObjectString.FMT_STRING_ONLY); + extensions.add(DataString.with(s + " resources", s, DataString.FMT_STRING_ONLY)); } // adding an extra entry which combines all listed extensions @@ -206,10 +206,11 @@ protected void setExtensions(String[] extList) final String s = extList[i].trim().toUpperCase(Locale.ENGLISH); sb.append(s); } - extensions[0] = new ObjectString("Supported resources", sb.toString(), ObjectString.FMT_STRING_ONLY); + extensions.add(0, DataString.with("Supported resources", sb.toString(), DataString.FMT_STRING_ONLY)); } } else { - extensions = new ObjectString[]{new ObjectString("All resources", "", ObjectString.FMT_STRING_ONLY)}; + extensions = new ArrayList<>(); + extensions.add(DataString.with("All resources", "", DataString.FMT_STRING_ONLY)); } updateResources(); @@ -218,7 +219,7 @@ protected void setExtensions(String[] extList) /** Returns a list of resource types defined for this dialog. */ protected String[] getExtensions() { - final String str = extensions[0].getObject().toString(); + final String str = extensions.get(0).getData(); if (!str.isEmpty()) { return str.split(";"); } @@ -273,8 +274,8 @@ private void cancel() private void updateResources() { resources.clear(); - for (final ObjectString extension : extensions) { - final String data = extension.getObject(); + for (final DataString extension : extensions) { + final String data = extension.getData(); final String[] types; if (data.isEmpty()) { types = Profile.getAvailableResourceTypes(); @@ -294,10 +295,10 @@ private void updateResources() /** Initializes type combobox. */ private void updateGui() { - DefaultComboBoxModel model = (DefaultComboBoxModel)cbType.getModel(); + DefaultComboBoxModel> model = (DefaultComboBoxModel>)cbType.getModel(); model.removeAllElements(); if (extensions != null) { - for (final ObjectString os: extensions) { + for (final DataString os: extensions) { model.addElement(os); } } diff --git a/src/org/infinity/gui/hexview/StructHexViewer.java b/src/org/infinity/gui/hexview/StructHexViewer.java index 3791f37fd..f9430dc98 100644 --- a/src/org/infinity/gui/hexview/StructHexViewer.java +++ b/src/org/infinity/gui/hexview/StructHexViewer.java @@ -43,16 +43,14 @@ import javax.swing.table.TableModel; import org.infinity.NearInfinity; -import org.infinity.datatype.Bitmap; +import org.infinity.datatype.AbstractBitmap; import org.infinity.datatype.ColorPicker; import org.infinity.datatype.ColorValue; import org.infinity.datatype.DecNumber; import org.infinity.datatype.Flag; import org.infinity.datatype.FloatNumber; -import org.infinity.datatype.HashBitmap; import org.infinity.datatype.MultiNumber; import org.infinity.datatype.ProRef; -import org.infinity.datatype.ResourceBitmap; import org.infinity.datatype.ResourceRef; import org.infinity.datatype.SectionCount; import org.infinity.datatype.SectionOffset; @@ -144,8 +142,7 @@ public static String getTypeDesc(StructEntry type) } else if (type instanceof DecNumber || type instanceof MultiNumber || type instanceof FloatNumber) { return "Number"; - } else if (type instanceof Bitmap || type instanceof HashBitmap || - type instanceof ResourceBitmap) { + } else if (type instanceof AbstractBitmap) { return "Numeric type or identifier"; } else if (type instanceof TextBitmap || type instanceof TextEdit || type instanceof TextString) { diff --git a/src/org/infinity/resource/AbstractVariable.java b/src/org/infinity/resource/AbstractVariable.java index 5dd8e7aa6..69d1ea6c2 100644 --- a/src/org/infinity/resource/AbstractVariable.java +++ b/src/org/infinity/resource/AbstractVariable.java @@ -117,7 +117,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception */ public Type getType() { - final Bitmap type = (Bitmap)getAttribute(VAR_TYPE, false); + final IsNumeric type = (IsNumeric)getAttribute(VAR_TYPE, false); return Type.values()[type.getValue()]; } /** @@ -136,7 +136,7 @@ public Type getType() */ public Object getValue(Type type) { - final Bitmap t = (Bitmap)getAttribute(VAR_TYPE, false); + final IsNumeric t = (IsNumeric)getAttribute(VAR_TYPE, false); return type.ordinal() == t.getValue() ? type.getValue(this) : null; } } diff --git a/src/org/infinity/resource/EffectFactory.java b/src/org/infinity/resource/EffectFactory.java index e9dc20d23..b75a2535d 100644 --- a/src/org/infinity/resource/EffectFactory.java +++ b/src/org/infinity/resource/EffectFactory.java @@ -12,6 +12,7 @@ import java.util.List; import java.util.stream.Stream; +import org.infinity.datatype.AnimateBitmap; import org.infinity.datatype.Bitmap; import org.infinity.datatype.ColorPicker; import org.infinity.datatype.ColorValue; @@ -800,7 +801,7 @@ private static boolean updateOpcode1(AbstractStruct struct) throws Exception if (Profile.isEnhancedEdition()) { int opcode = ((EffectType)getEntry(struct, EffectEntry.IDX_OPCODE)).getValue(); if (opcode == 1) { - int param2 = ((Bitmap)getEntry(struct, EffectEntry.IDX_PARAM2)).getValue(); + int param2 = ((IsNumeric)getEntry(struct, EffectEntry.IDX_PARAM2)).getValue(); if (param2 == 2) { // Set % of replaceEntry(struct, EffectEntry.IDX_PARAM1, EffectEntry.OFS_PARAM1, new DecNumber(getEntryData(struct, EffectEntry.IDX_PARAM1), 0, 4, "Value")); @@ -824,7 +825,7 @@ private static boolean updateOpcode15(AbstractStruct struct) throws Exception Profile.isEnhancedEdition() || (boolean)Profile.getProperty(Profile.Key.IS_GAME_TOBEX)) { boolean isV1 = (getEntry(struct, EffectEntry.IDX_OPCODE).getSize() == 2); int opcode = ((EffectType)getEntry(struct, EffectEntry.IDX_OPCODE)).getValue(); - int param2 = ((Bitmap)getEntry(struct, EffectEntry.IDX_PARAM2)).getValue(); + int param2 = ((IsNumeric)getEntry(struct, EffectEntry.IDX_PARAM2)).getValue(); if (opcode == 15) { if (isV1 && param2 == 3) { replaceEntry(struct, EffectEntry.IDX_PARAM1, EffectEntry.OFS_PARAM1, @@ -848,7 +849,7 @@ private static boolean updateOpcode23(AbstractStruct struct) throws Exception if (Profile.isEnhancedEdition()) { int opcode = ((EffectType)getEntry(struct, EffectEntry.IDX_OPCODE)).getValue(); if (opcode == 23) { - int special = ((Bitmap)getEntry(struct, EffectEntry.IDX_SPECIAL)).getValue(); + int special = ((IsNumeric)getEntry(struct, EffectEntry.IDX_SPECIAL)).getValue(); if (special == 0 ) { // Activate BG2 mode replaceEntry(struct, EffectEntry.IDX_PARAM1, EffectEntry.OFS_PARAM1, @@ -878,7 +879,7 @@ private static boolean updateOpcode44(AbstractStruct struct) throws Exception Profile.isEnhancedEdition() || (boolean)Profile.getProperty(Profile.Key.IS_GAME_TOBEX)) { boolean isV1 = (getEntry(struct, EffectEntry.IDX_OPCODE).getSize() == 2); int opcode = ((EffectType)getEntry(struct, EffectEntry.IDX_OPCODE)).getValue(); - int param2 = ((Bitmap)getEntry(struct, EffectEntry.IDX_PARAM2)).getValue(); + int param2 = ((IsNumeric)getEntry(struct, EffectEntry.IDX_PARAM2)).getValue(); if (opcode == 44) { if (isV1 && param2 == 3) { replaceEntry(struct, EffectEntry.IDX_PARAM1, EffectEntry.OFS_PARAM1, @@ -902,7 +903,7 @@ private static boolean updateOpcode78(AbstractStruct struct) throws Exception if (Profile.isEnhancedEdition()) { int opcode = ((EffectType)getEntry(struct, EffectEntry.IDX_OPCODE)).getValue(); if (opcode == 78) { - int param2 = ((Bitmap)getEntry(struct, EffectEntry.IDX_PARAM2)).getValue(); + int param2 = ((IsNumeric)getEntry(struct, EffectEntry.IDX_PARAM2)).getValue(); switch (param2) { case 11: // Mold Touch/Single case 12: // Mold Touch/Decrement @@ -930,7 +931,7 @@ private static boolean updateOpcode232(AbstractStruct struct) throws Exception if (Profile.getEngine() == Profile.Engine.BG2 || Profile.isEnhancedEdition()) { int opcode = ((EffectType)getEntry(struct, EffectEntry.IDX_OPCODE)).getValue(); if (opcode == 232) { - int param2 = ((Bitmap)getEntry(struct, EffectEntry.IDX_PARAM2)).getValue(); + int param2 = ((IsNumeric)getEntry(struct, EffectEntry.IDX_PARAM2)).getValue(); switch (param2) { case 13: // Time of day replaceEntry(struct, EffectEntry.IDX_SPECIAL, EffectEntry.OFS_SPECIAL, @@ -989,7 +990,7 @@ private static boolean updateOpcode319(AbstractStruct struct) throws Exception if (opcode == 319) { boolean isEEex = Profile.getProperty(Profile.Key.IS_GAME_EEEX); if (isEEex) { - int power = ((Bitmap)getEntry(struct, EffectEntry.IDX_POWER)).getValue(); + int power = ((IsNumeric)getEntry(struct, EffectEntry.IDX_POWER)).getValue(); if (power == 2 || power == 3) { SpellProtType param2 = new SpellProtType(getEntryData(struct, EffectEntry.IDX_PARAM2), 0, 4); param2.setName("EEex: " + param2.getName()); @@ -1031,7 +1032,7 @@ private static boolean updateOpcode328(AbstractStruct struct) throws Exception if (Profile.isEnhancedEdition()) { int opcode = ((EffectType)getEntry(struct, EffectEntry.IDX_OPCODE)).getValue(); if (opcode == 328) { - int special = ((Bitmap)getEntry(struct, EffectEntry.IDX_SPECIAL)).getValue(); + int special = ((IsNumeric)getEntry(struct, EffectEntry.IDX_SPECIAL)).getValue(); if (special == 1 && ResourceFactory.resourceExists("SPLSTATE.IDS")) { // Activate IWD2 mode replaceEntry(struct, EffectEntry.IDX_PARAM2, EffectEntry.OFS_PARAM2, @@ -2500,7 +2501,7 @@ private String makeEffectParamsGeneric(Datatype parent, ByteBuffer buffer, int o break; case 53: // Animation change - s.add(new IdsBitmap(buffer, offset, 4, "Morph into", "ANIMATE.IDS")); + s.add(new AnimateBitmap(buffer, offset, 4, "Morph into")); s.add(new Bitmap(buffer, offset + 4, 4, "Morph type", new String[]{"Temporary change", "Remove temporary change", "Permanent change"})); @@ -2923,7 +2924,7 @@ private String makeEffectParamsGeneric(Datatype parent, ByteBuffer buffer, int o break; case 135: // Polymorph - s.add(new IdsBitmap(buffer, offset, 4, "Animation", "ANIMATE.IDS")); + s.add(new AnimateBitmap(buffer, offset, 4, "Animation")); s.add(new Bitmap(buffer, offset + 4, 4, "Polymorph type", new String[]{"Change into", "Appearance only", "Appearance only", "Appearance only"})); diff --git a/src/org/infinity/resource/Profile.java b/src/org/infinity/resource/Profile.java index 5a56af799..b3d76abbb 100644 --- a/src/org/infinity/resource/Profile.java +++ b/src/org/infinity/resource/Profile.java @@ -49,7 +49,7 @@ import org.infinity.resource.key.ResourceEntry; import org.infinity.resource.key.ResourceTreeFolder; import org.infinity.resource.key.ResourceTreeModel; -import org.infinity.util.ObjectString; +import org.infinity.util.DataString; import org.infinity.util.Platform; import org.infinity.util.Table2da; import org.infinity.util.Table2daCache; @@ -2440,20 +2440,20 @@ private List initDlc(Path rootDir, Path homeDir) return retVal; } - List gameFolders = new ArrayList<>(); + List> gameFolders = new ArrayList<>(); // Getting potential DLC folders (search order is important) if (rootDir != null && FileEx.create(rootDir).isDirectory()) { - gameFolders.add(new ObjectString("mod", rootDir.resolve("workshop"))); - gameFolders.add(new ObjectString("zip", rootDir.resolve("dlc"))); - gameFolders.add(new ObjectString("zip", rootDir)); + gameFolders.add(DataString.with("mod", rootDir.resolve("workshop"))); + gameFolders.add(DataString.with("zip", rootDir.resolve("dlc"))); + gameFolders.add(DataString.with("zip", rootDir)); } if (homeDir != null && FileEx.create(homeDir).isDirectory()) { - gameFolders.add(new ObjectString("zip", homeDir)); + gameFolders.add(DataString.with("zip", homeDir)); } - for (final ObjectString root: gameFolders) { + for (final DataString root: gameFolders) { String ext = root.getString(); - Path dir = root.getObject(); + Path dir = root.getData(); if (dir != null && FileEx.create(dir).isDirectory()) { List list = new ArrayList<>(); try (DirectoryStream dstream = Files.newDirectoryStream(dir)) { diff --git a/src/org/infinity/resource/are/viewer/TilesetRenderer.java b/src/org/infinity/resource/are/viewer/TilesetRenderer.java index af3b7b269..9df90d92e 100644 --- a/src/org/infinity/resource/are/viewer/TilesetRenderer.java +++ b/src/org/infinity/resource/are/viewer/TilesetRenderer.java @@ -16,14 +16,11 @@ import java.util.List; import java.util.Locale; -import org.infinity.datatype.Bitmap; -import org.infinity.datatype.DecNumber; -import org.infinity.datatype.Flag; -import org.infinity.datatype.HexNumber; +import org.infinity.datatype.IsNumeric; +import org.infinity.datatype.IsTextual; import org.infinity.datatype.ResourceRef; import org.infinity.datatype.SectionCount; import org.infinity.datatype.SectionOffset; -import org.infinity.datatype.TextString; import org.infinity.gui.RenderCanvas; import org.infinity.resource.Profile; import org.infinity.resource.ResourceFactory; @@ -685,20 +682,20 @@ private boolean initWed(WedResource wed) // loading door structures so = (SectionOffset)wed.getAttribute(WedResource.WED_OFFSET_DOORS); sc = (SectionCount)wed.getAttribute(WedResource.WED_NUM_DOORS); - HexNumber lookupOfs = (HexNumber)wed.getAttribute(WedResource.WED_OFFSET_DOOR_TILEMAP_LOOKUP); + IsNumeric lookupOfs = (IsNumeric)wed.getAttribute(WedResource.WED_OFFSET_DOOR_TILEMAP_LOOKUP); if (so != null && sc != null && lookupOfs != null) { for (int i = 0, count = sc.getValue(), curOfs = so.getValue(); i < count; i++) { Door door = (Door)wed.getAttribute(curOfs, false); if (door != null) { - String name = ((TextString)door.getAttribute(Door.WED_DOOR_NAME)).toString(); - boolean isClosed = ((Bitmap)door.getAttribute(Door.WED_DOOR_IS_DOOR)).getValue() == 1; + String name = ((IsTextual)door.getAttribute(Door.WED_DOOR_NAME)).getText(); + boolean isClosed = ((IsNumeric)door.getAttribute(Door.WED_DOOR_IS_DOOR)).getValue() == 1; final int tileSize = 2; - int tileIdx = ((DecNumber)door.getAttribute(Door.WED_DOOR_TILEMAP_LOOKUP_INDEX)).getValue(); + int tileIdx = ((IsNumeric)door.getAttribute(Door.WED_DOOR_TILEMAP_LOOKUP_INDEX)).getValue(); int tileCount = ((SectionCount)door.getAttribute(Door.WED_DOOR_NUM_TILEMAP_INDICES)).getValue(); if (tileCount < 0) tileCount = 0; int[] indices = new int[tileCount]; for (int j = 0; j < tileCount; j++) { - indices[j] = ((DecNumber)door.getAttribute(lookupOfs.getValue() + (tileIdx+j)*tileSize, false)).getValue(); + indices[j] = ((IsNumeric)door.getAttribute(lookupOfs.getValue() + (tileIdx+j)*tileSize, false)).getValue(); } listDoorTileIndices.add(new DoorInfo(name, isClosed, indices)); curOfs += door.getSize(); @@ -1136,10 +1133,10 @@ private void init(WedResource wed, Overlay ovl) } // storing tile information - tilesX = ((DecNumber)ovl.getAttribute(Overlay.WED_OVERLAY_WIDTH)).getValue(); - tilesY = ((DecNumber)ovl.getAttribute(Overlay.WED_OVERLAY_HEIGHT)).getValue(); + tilesX = ((IsNumeric)ovl.getAttribute(Overlay.WED_OVERLAY_WIDTH)).getValue(); + tilesY = ((IsNumeric)ovl.getAttribute(Overlay.WED_OVERLAY_HEIGHT)).getValue(); int mapOfs = ((SectionOffset)ovl.getAttribute(Overlay.WED_OVERLAY_OFFSET_TILEMAP)).getValue(); - int idxOfs = ((DecNumber)ovl.getAttribute(Overlay.WED_OVERLAY_OFFSET_TILEMAP_LOOKUP)).getValue(); + int idxOfs = ((IsNumeric)ovl.getAttribute(Overlay.WED_OVERLAY_OFFSET_TILEMAP_LOOKUP)).getValue(); int tileCount = tilesX * tilesY; for (int i = 0, curOfs = mapOfs; i < tileCount; i++) { Tilemap tile = (Tilemap)ovl.getAttribute(curOfs, false); @@ -1150,13 +1147,13 @@ private void init(WedResource wed, Overlay ovl) if (tile != null) { // initializing list of primary tile indices final int idxSize = 2; - int index = ((DecNumber)tile.getAttribute(Tilemap.WED_TILEMAP_TILE_INDEX_PRI)).getValue(); - int count = ((DecNumber)tile.getAttribute(Tilemap.WED_TILEMAP_TILE_COUNT_PRI)).getValue(); + int index = ((IsNumeric)tile.getAttribute(Tilemap.WED_TILEMAP_TILE_INDEX_PRI)).getValue(); + int count = ((IsNumeric)tile.getAttribute(Tilemap.WED_TILEMAP_TILE_COUNT_PRI)).getValue(); if (count < 0) count = 0; int[] tileIdx = new int[count]; for (int j = 0; j < count; j++) { if (index >= 0) { - DecNumber dn = (DecNumber)ovl.getAttribute(idxOfs + (index+j)*idxSize, false); + IsNumeric dn = (IsNumeric)ovl.getAttribute(idxOfs + (index+j)*idxSize, false); if (dn != null) { tileIdx[j] = dn.getValue(); } else { @@ -1168,10 +1165,10 @@ private void init(WedResource wed, Overlay ovl) } // initializing secondary tile index - int tileIdx2 = ((DecNumber)tile.getAttribute(Tilemap.WED_TILEMAP_TILE_INDEX_SEC)).getValue(); + int tileIdx2 = ((IsNumeric)tile.getAttribute(Tilemap.WED_TILEMAP_TILE_INDEX_SEC)).getValue(); // initializing overlay flags - Flag drawOverlays = (Flag)tile.getAttribute(Tilemap.WED_TILEMAP_DRAW_OVERLAYS); + IsNumeric drawOverlays = (IsNumeric)tile.getAttribute(Tilemap.WED_TILEMAP_DRAW_OVERLAYS); int flags = (int)drawOverlays.getValue() & 255; listTiles.add(new Tile(x, y, count, tileIdx, tileIdx2, flags, isTisPalette)); diff --git a/src/org/infinity/resource/chu/Control.java b/src/org/infinity/resource/chu/Control.java index d301f6746..20bad1d84 100644 --- a/src/org/infinity/resource/chu/Control.java +++ b/src/org/infinity/resource/chu/Control.java @@ -16,6 +16,7 @@ import org.infinity.datatype.DecNumber; import org.infinity.datatype.Flag; import org.infinity.datatype.HexNumber; +import org.infinity.datatype.IsNumeric; import org.infinity.datatype.ResourceRef; import org.infinity.datatype.StringRef; import org.infinity.datatype.TextString; @@ -155,7 +156,7 @@ public Dimension getControlDimensions() /** Returns the control type. */ public int getControlType() { - return ((Bitmap)getAttribute(CHU_CONTROL_TYPE)).getValue(); + return ((IsNumeric)getAttribute(CHU_CONTROL_TYPE)).getValue(); } diff --git a/src/org/infinity/resource/chu/Viewer.java b/src/org/infinity/resource/chu/Viewer.java index 12339cdbe..0328b19ac 100644 --- a/src/org/infinity/resource/chu/Viewer.java +++ b/src/org/infinity/resource/chu/Viewer.java @@ -44,13 +44,10 @@ import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; -import org.infinity.datatype.Bitmap; -import org.infinity.datatype.ColorPicker; -import org.infinity.datatype.DecNumber; import org.infinity.datatype.Flag; +import org.infinity.datatype.IsNumeric; +import org.infinity.datatype.IsTextual; import org.infinity.datatype.ResourceRef; -import org.infinity.datatype.StringRef; -import org.infinity.datatype.TextString; import org.infinity.gui.RenderCanvas; import org.infinity.gui.StructViewer; import org.infinity.gui.ViewerUtil; @@ -1433,17 +1430,17 @@ public void updateImage() // loading BAM String resName = ((ResourceRef)getResource().getAttribute(Control.CHU_CONTROL_BTN_RESREF)).getResourceName(); // getting specified cycle index - int cycleIdx = ((DecNumber)getResource().getAttribute(Control.CHU_CONTROL_BTN_ANIMATION_INDEX)).getValue(); + int cycleIdx = ((IsNumeric)getResource().getAttribute(Control.CHU_CONTROL_BTN_ANIMATION_INDEX)).getValue(); int frameIdx = 0; // getting specified cycle frame index if (isUnpressed()) { - frameIdx = ((DecNumber)getResource().getAttribute(Control.CHU_CONTROL_BTN_FRAME_INDEX_UNPRESSED)).getValue(); + frameIdx = ((IsNumeric)getResource().getAttribute(Control.CHU_CONTROL_BTN_FRAME_INDEX_UNPRESSED)).getValue(); } else if (isPressed()) { - frameIdx = ((DecNumber)getResource().getAttribute(Control.CHU_CONTROL_BTN_FRAME_INDEX_PRESSED)).getValue(); + frameIdx = ((IsNumeric)getResource().getAttribute(Control.CHU_CONTROL_BTN_FRAME_INDEX_PRESSED)).getValue(); } else if (isSelected()) { - frameIdx = ((DecNumber)getResource().getAttribute(Control.CHU_CONTROL_BTN_FRAME_INDEX_SELECTED)).getValue(); + frameIdx = ((IsNumeric)getResource().getAttribute(Control.CHU_CONTROL_BTN_FRAME_INDEX_SELECTED)).getValue(); } else if (isDisabled()) { - frameIdx = ((DecNumber)getResource().getAttribute(Control.CHU_CONTROL_BTN_FRAME_INDEX_DISABLED)).getValue(); + frameIdx = ((IsNumeric)getResource().getAttribute(Control.CHU_CONTROL_BTN_FRAME_INDEX_DISABLED)).getValue(); } if (!isResourceIgnored(resName, cycleIdx, frameIdx)) { BamDecoder bam = BamDecoder.loadBam(ResourceFactory.getResourceEntry(resName)); @@ -1556,23 +1553,23 @@ public void updateImage() BamControl bamCtrl = bam.createControl(); bamCtrl.setMode(BamControl.Mode.INDIVIDUAL); // getting specified cycle index - int cycleIdx = ((DecNumber)getResource().getAttribute(Control.CHU_CONTROL_SLD_ANIMATION_INDEX)).getValue(); + int cycleIdx = ((IsNumeric)getResource().getAttribute(Control.CHU_CONTROL_SLD_ANIMATION_INDEX)).getValue(); cycleIdx = Math.min(bamCtrl.cycleCount()-1, Math.max(0, cycleIdx)); bamCtrl.cycleSet(cycleIdx); int frameIdx; // getting specified cycle frame index if (isGrabbed()) { - frameIdx = ((DecNumber)getResource().getAttribute(Control.CHU_CONTROL_SLD_FRAME_INDEX_GRABBED)).getValue(); + frameIdx = ((IsNumeric)getResource().getAttribute(Control.CHU_CONTROL_SLD_FRAME_INDEX_GRABBED)).getValue(); } else { - frameIdx = ((DecNumber)getResource().getAttribute(Control.CHU_CONTROL_SLD_FRAME_INDEX_UNGRABBED)).getValue(); + frameIdx = ((IsNumeric)getResource().getAttribute(Control.CHU_CONTROL_SLD_FRAME_INDEX_UNGRABBED)).getValue(); } frameIdx = Math.min(bamCtrl.cycleFrameCount()-1, Math.max(0, frameIdx)); bamCtrl.cycleSetFrameIndex(frameIdx); // drawing frame Image knob = bamCtrl.cycleGetFrame(); - int knobX = ((DecNumber)getResource().getAttribute(Control.CHU_CONTROL_SLD_KNOB_POSITION_X)).getValue(); - int knobY = ((DecNumber)getResource().getAttribute(Control.CHU_CONTROL_SLD_KNOB_POSITION_Y)).getValue(); + int knobX = ((IsNumeric)getResource().getAttribute(Control.CHU_CONTROL_SLD_KNOB_POSITION_X)).getValue(); + int knobY = ((IsNumeric)getResource().getAttribute(Control.CHU_CONTROL_SLD_KNOB_POSITION_Y)).getValue(); g.drawImage(knob, knobX, knobY, knob.getWidth(null), knob.getHeight(null), null); } } @@ -1661,18 +1658,18 @@ public void updateImage() // 2.2. drawing text if (isVisible()) { - int caretX = ((DecNumber)getResource().getAttribute(Control.CHU_CONTROL_TF_CARET_POSITION_X)).getValue(); - int caretY = ((DecNumber)getResource().getAttribute(Control.CHU_CONTROL_TF_CARET_POSITION_Y)).getValue(); + int caretX = ((IsNumeric)getResource().getAttribute(Control.CHU_CONTROL_TF_CARET_POSITION_X)).getValue(); + int caretY = ((IsNumeric)getResource().getAttribute(Control.CHU_CONTROL_TF_CARET_POSITION_Y)).getValue(); - String text = ((TextString)getResource().getAttribute(Control.CHU_CONTROL_TF_TEXT)).toString(); + String text = ((IsTextual)getResource().getAttribute(Control.CHU_CONTROL_TF_TEXT)).getText(); if (!text.isEmpty()) { resName = ((ResourceRef)getResource().getAttribute(Control.CHU_CONTROL_TF_FONT)).getResourceName(); resName = resName.toUpperCase(Locale.ENGLISH).replace(".FNT", ".BAM"); BamDecoder bam = BamDecoder.loadBam(ResourceFactory.getResourceEntry(resName)); if (bam != null) { - int maxLen = ((DecNumber)getResource().getAttribute(Control.CHU_CONTROL_TF_FIELD_LENGTH)).getValue(); + int maxLen = ((IsNumeric)getResource().getAttribute(Control.CHU_CONTROL_TF_FIELD_LENGTH)).getValue(); if (text.length() > maxLen) text = text.substring(0, maxLen); - int flags = ((Bitmap)getResource().getAttribute(Control.CHU_CONTROL_TF_ALLOWED_CSE)).getValue(); + int flags = ((IsNumeric)getResource().getAttribute(Control.CHU_CONTROL_TF_ALLOWED_CSE)).getValue(); text = convertText(text, flags); Image textImage = drawText(text, bam, null, false); if (textImage != null) { @@ -1690,11 +1687,11 @@ public void updateImage() BamControl bamCtrl = bam.createControl(); bamCtrl.setMode(BamControl.Mode.INDIVIDUAL); // getting specified cycle index - int cycleIdx = ((DecNumber)getResource().getAttribute(Control.CHU_CONTROL_TF_ANIMATION_INDEX)).getValue(); + int cycleIdx = ((IsNumeric)getResource().getAttribute(Control.CHU_CONTROL_TF_ANIMATION_INDEX)).getValue(); cycleIdx = Math.min(bamCtrl.cycleCount()-1, Math.max(0, cycleIdx)); bamCtrl.cycleSet(cycleIdx); // getting specified cycle frame index - int frameIdx = ((DecNumber)getResource().getAttribute(Control.CHU_CONTROL_TF_FRAME_INDEX)).getValue(); + int frameIdx = ((IsNumeric)getResource().getAttribute(Control.CHU_CONTROL_TF_FRAME_INDEX)).getValue(); frameIdx = Math.min(bamCtrl.cycleFrameCount()-1, Math.max(0, frameIdx)); bamCtrl.cycleSetFrameIndex(frameIdx); @@ -1816,7 +1813,7 @@ public void updateImage() // 2. drawing control if (isVisible()) { - String text = StringTable.getStringRef(((StringRef)getResource().getAttribute(Control.CHU_CONTROL_LBL_TEXT)).getValue()); + String text = StringTable.getStringRef(((IsNumeric)getResource().getAttribute(Control.CHU_CONTROL_LBL_TEXT)).getValue()); if (text != null) { String resName = ((ResourceRef)getResource().getAttribute(Control.CHU_CONTROL_LBL_FONT)).getResourceName(); resName = resName.toUpperCase(Locale.ENGLISH).replace(".FNT", ".BAM"); @@ -1825,7 +1822,7 @@ public void updateImage() Flag flags = (Flag)getResource().getAttribute(Control.CHU_CONTROL_LBL_FLAGS); Color col = null; if (flags.isFlagSet(0)) { - col = new Color(((ColorPicker)getResource().getAttribute(Control.CHU_CONTROL_LBL_COLOR_1)).getValue()); + col = new Color(((IsNumeric)getResource().getAttribute(Control.CHU_CONTROL_LBL_COLOR_1)).getValue()); } Image textImage = drawText(text, bam, col, flags.isFlagSet(1)); if (textImage != null) { @@ -1926,19 +1923,19 @@ public void updateImage() String resName = ((ResourceRef)getResource().getAttribute(Control.CHU_CONTROL_SB_GRAPHICS)).getResourceName(); BamDecoder bam = BamDecoder.loadBam(ResourceFactory.getResourceEntry(resName)); if (bam != null) { - int cycleIdx = ((DecNumber)getResource().getAttribute(Control.CHU_CONTROL_SB_ANIMATION_INDEX)).getValue(); - int frameTroughIdx = ((DecNumber)getResource().getAttribute(Control.CHU_CONTROL_SB_FRAME_INDEX_TROUGH)).getValue(); - int frameSliderIdx = ((DecNumber)getResource().getAttribute(Control.CHU_CONTROL_SB_FRAME_INDEX_SLIDER)).getValue(); + int cycleIdx = ((IsNumeric)getResource().getAttribute(Control.CHU_CONTROL_SB_ANIMATION_INDEX)).getValue(); + int frameTroughIdx = ((IsNumeric)getResource().getAttribute(Control.CHU_CONTROL_SB_FRAME_INDEX_TROUGH)).getValue(); + int frameSliderIdx = ((IsNumeric)getResource().getAttribute(Control.CHU_CONTROL_SB_FRAME_INDEX_SLIDER)).getValue(); int frameUpIdx, frameDownIdx; if (isUpArrowPressed()) { - frameUpIdx = ((DecNumber)getResource().getAttribute(Control.CHU_CONTROL_SB_FRAME_INDEX_UP_PRESSED)).getValue(); + frameUpIdx = ((IsNumeric)getResource().getAttribute(Control.CHU_CONTROL_SB_FRAME_INDEX_UP_PRESSED)).getValue(); } else { - frameUpIdx = ((DecNumber)getResource().getAttribute(Control.CHU_CONTROL_SB_FRAME_INDEX_UP_UNPRESSED)).getValue(); + frameUpIdx = ((IsNumeric)getResource().getAttribute(Control.CHU_CONTROL_SB_FRAME_INDEX_UP_UNPRESSED)).getValue(); } if (isDownArrowPressed()) { - frameDownIdx = ((DecNumber)getResource().getAttribute(Control.CHU_CONTROL_SB_FRAME_INDEX_DOWN_PRESSED)).getValue(); + frameDownIdx = ((IsNumeric)getResource().getAttribute(Control.CHU_CONTROL_SB_FRAME_INDEX_DOWN_PRESSED)).getValue(); } else { - frameDownIdx = ((DecNumber)getResource().getAttribute(Control.CHU_CONTROL_SB_FRAME_INDEX_DOWN_UNPRESSED)).getValue(); + frameDownIdx = ((IsNumeric)getResource().getAttribute(Control.CHU_CONTROL_SB_FRAME_INDEX_DOWN_UNPRESSED)).getValue(); } BamControl ctrl = bam.createControl(); ctrl.cycleSet(cycleIdx); diff --git a/src/org/infinity/resource/chu/Window.java b/src/org/infinity/resource/chu/Window.java index 0e2cfc048..e66386eec 100644 --- a/src/org/infinity/resource/chu/Window.java +++ b/src/org/infinity/resource/chu/Window.java @@ -14,6 +14,7 @@ import org.infinity.datatype.Bitmap; import org.infinity.datatype.DecNumber; import org.infinity.datatype.Flag; +import org.infinity.datatype.IsNumeric; import org.infinity.datatype.ResourceRef; import org.infinity.datatype.TextString; import org.infinity.datatype.Unknown; @@ -91,27 +92,27 @@ public Control getControl(int index) /** Returns the panel id. */ public int getWindowId() { - return ((DecNumber)getAttribute(CHU_WINDOW_ID)).getValue(); + return ((IsNumeric)getAttribute(CHU_WINDOW_ID)).getValue(); } /** Returns the x and y positions of the panel. */ public Point getWindowPosition() { - return new Point(((DecNumber)getAttribute(CHU_WINDOW_POSITION_X)).getValue(), - ((DecNumber)getAttribute(CHU_WINDOW_POSITION_Y)).getValue()); + return new Point(((IsNumeric)getAttribute(CHU_WINDOW_POSITION_X)).getValue(), + ((IsNumeric)getAttribute(CHU_WINDOW_POSITION_Y)).getValue()); } /** Returns width and height of the panel. */ public Dimension getWindowDimension() { - return new Dimension(((DecNumber)getAttribute(CHU_WINDOW_WIDTH)).getValue(), - ((DecNumber)getAttribute(CHU_WINDOW_HEIGHT)).getValue()); + return new Dimension(((IsNumeric)getAttribute(CHU_WINDOW_WIDTH)).getValue(), + ((IsNumeric)getAttribute(CHU_WINDOW_HEIGHT)).getValue()); } /** Returns whether the panel references a background MOS. */ public boolean hasBackgroundImage() { - return ((Bitmap)getAttribute(CHU_WINDOW_HAS_BACKGROUND)).getValue() == 1; + return ((IsNumeric)getAttribute(CHU_WINDOW_HAS_BACKGROUND)).getValue() == 1; } /** Returns the background MOS for the panel. */ diff --git a/src/org/infinity/resource/pro/ProResource.java b/src/org/infinity/resource/pro/ProResource.java index c6df6b946..ebd249a14 100644 --- a/src/org/infinity/resource/pro/ProResource.java +++ b/src/org/infinity/resource/pro/ProResource.java @@ -14,6 +14,7 @@ import org.infinity.datatype.Flag; import org.infinity.datatype.HashBitmap; import org.infinity.datatype.IdsTargetType; +import org.infinity.datatype.IsNumeric; import org.infinity.datatype.ProRef; import org.infinity.datatype.ResourceBitmap; import org.infinity.datatype.ResourceRef; @@ -149,9 +150,9 @@ public boolean valueUpdated(UpdateEvent event) } return bRet; } - else if (event.getSource() instanceof HashBitmap && + else if (event.getSource() instanceof IsNumeric && ((StructEntry)event.getSource()).getName().equals(PRO_TYPE)) { - HashBitmap proType = (HashBitmap)event.getSource(); + IsNumeric proType = (IsNumeric)event.getSource(); AbstractStruct struct = event.getStructure(); // add/remove extended sections in the parent structure depending on the current value if (struct instanceof Resource && struct instanceof HasChildStructs) { diff --git a/src/org/infinity/resource/spl/SplResource.java b/src/org/infinity/resource/spl/SplResource.java index 33bd44a25..6a523c06e 100644 --- a/src/org/infinity/resource/spl/SplResource.java +++ b/src/org/infinity/resource/spl/SplResource.java @@ -16,9 +16,11 @@ import javax.swing.JComponent; import javax.swing.JScrollPane; +import org.infinity.datatype.AbstractBitmap; import org.infinity.datatype.Bitmap; import org.infinity.datatype.DecNumber; import org.infinity.datatype.Flag; +import org.infinity.datatype.IsNumeric; import org.infinity.datatype.PriTypeBitmap; import org.infinity.datatype.ResourceRef; import org.infinity.datatype.SecTypeBitmap; @@ -238,11 +240,11 @@ public void write(OutputStream os) throws IOException @Override public boolean valueUpdated(UpdateEvent event) { - if (event.getSource() instanceof Bitmap && - SPL_TYPE.equals(((Bitmap)event.getSource()).getName())) { + if (event.getSource() instanceof AbstractBitmap && + SPL_TYPE.equals(((AbstractBitmap)event.getSource()).getName())) { Flag curFlags = (Flag)getAttribute(SPL_EXCLUSION_FLAGS); if (curFlags != null) { - int type = ((Bitmap)event.getSource()).getValue(); + int type = ((IsNumeric)event.getSource()).getValue(); int size = curFlags.getSize(); int offset = curFlags.getOffset(); ByteBuffer b = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN).putInt(curFlags.getValue()); diff --git a/src/org/infinity/resource/sto/StoResource.java b/src/org/infinity/resource/sto/StoResource.java index 7ed6894ff..57bdd19f4 100644 --- a/src/org/infinity/resource/sto/StoResource.java +++ b/src/org/infinity/resource/sto/StoResource.java @@ -15,6 +15,7 @@ import org.infinity.datatype.Bitmap; import org.infinity.datatype.DecNumber; import org.infinity.datatype.Flag; +import org.infinity.datatype.IsNumeric; import org.infinity.datatype.ResourceRef; import org.infinity.datatype.SectionCount; import org.infinity.datatype.SectionOffset; @@ -349,8 +350,8 @@ public static boolean matchSearchOptions(ResourceEntry entry, SearchOptions sear Object o; // preparations - DecNumber ofs = (DecNumber)sto.getAttribute(STO_OFFSET_ITEMS_FOR_SALE, false); - DecNumber cnt = (DecNumber)sto.getAttribute(STO_NUM_ITEMS_FOR_SALE, false); + IsNumeric ofs = (IsNumeric)sto.getAttribute(STO_OFFSET_ITEMS_FOR_SALE, false); + IsNumeric cnt = (IsNumeric)sto.getAttribute(STO_NUM_ITEMS_FOR_SALE, false); if (ofs != null && ofs.getValue() > 0 && cnt != null && cnt.getValue() > 0) { String itemLabel = SearchOptions.getResourceName(SearchOptions.STO_Item_Item1); items = new ResourceRef[cnt.getValue()]; @@ -376,8 +377,8 @@ public static boolean matchSearchOptions(ResourceEntry entry, SearchOptions sear items = new ResourceRef[0]; } - ofs = (DecNumber)sto.getAttribute(STO_OFFSET_ITEMS_PURCHASED, false); - cnt = (DecNumber)sto.getAttribute(STO_NUM_ITEMS_PURCHASED, false); + ofs = (IsNumeric)sto.getAttribute(STO_OFFSET_ITEMS_PURCHASED, false); + cnt = (IsNumeric)sto.getAttribute(STO_NUM_ITEMS_PURCHASED, false); if (ofs != null && ofs.getValue() > 0 && cnt != null && cnt.getValue() > 0) { purchases = new Bitmap[cnt.getValue()]; for (int i = 0; i < cnt.getValue(); i++) { diff --git a/src/org/infinity/search/SearchResource.java b/src/org/infinity/search/SearchResource.java index 253f5b723..ffcb23728 100644 --- a/src/org/infinity/search/SearchResource.java +++ b/src/org/infinity/search/SearchResource.java @@ -60,15 +60,13 @@ import javax.swing.text.PlainDocument; import org.infinity.NearInfinity; -import org.infinity.datatype.Bitmap; -import org.infinity.datatype.HashBitmap; import org.infinity.datatype.IdsBitmap; -import org.infinity.datatype.IwdRef; +import org.infinity.datatype.IsNumeric; import org.infinity.datatype.KitIdsBitmap; import org.infinity.datatype.PriTypeBitmap; import org.infinity.datatype.ProRef; +import org.infinity.datatype.ResourceBitmap; import org.infinity.datatype.SecTypeBitmap; -import org.infinity.datatype.Song2daBitmap; import org.infinity.datatype.TextBitmap; import org.infinity.gui.ButtonPopupWindow; import org.infinity.gui.ChildFrame; @@ -5038,10 +5036,12 @@ private void init() final IndexedString[] pro; if (ResourceFactory.resourceExists("PROJECTL.IDS")) { ProRef proRef = new ProRef(StreamUtils.getByteBuffer(2), 0, "Projectile"); - pro = new IndexedString[proRef.getResourceList().size()]; - for (int i = 0; i < pro.length; i++) { - long id = proRef.getResourceList().get(i).getValue(); - pro[i] = new IndexedString(proRef.getResourceList().get(i).getResourceName(), (int)id); + pro = new IndexedString[proRef.getBitmap().size()]; + int i = 0; + for (final ResourceBitmap.RefEntry entry : proRef.getBitmap().values()) { + long id = entry.getValue(); + pro[i] = new IndexedString(entry.getResourceName(), (int)id); + i++; } } else if (Profile.getEngine() == Profile.Engine.PST) { pro = IndexedString.createArray(AbstractAbility.s_proj_pst, 0, 0); @@ -5336,10 +5336,12 @@ private void init() final IndexedString[] pro; if (ResourceFactory.resourceExists("PROJECTL.IDS")) { ProRef proRef = new ProRef(StreamUtils.getByteBuffer(2), 0, "Projectile"); - pro = new IndexedString[proRef.getResourceList().size()]; - for (int i = 0; i < pro.length; i++) { - long id = proRef.getResourceList().get(i).getValue(); - pro[i] = new IndexedString(proRef.getResourceList().get(i).getResourceName(), (int)id); + pro = new IndexedString[proRef.getBitmap().size()]; + int i = 0; + for (final ResourceBitmap.RefEntry entry : proRef.getBitmap().values()) { + long id = entry.getValue(); + pro[i] = new IndexedString(entry.getResourceName(), (int)id); + i++; } } else if (Profile.getEngine() == Profile.Engine.PST) { pro = IndexedString.createArray(AbstractAbility.s_proj_pst, 1, 0); @@ -5768,7 +5770,7 @@ public static JComboBox getIdsMapEntryList(int bufSize, String name public static JComboBox getIdsMapEntryList(IdsBitmap ids) { - final SortedMap map = ids.getHashBitmap(); + final SortedMap map = ids.getBitmap(); final IdsMapEntry[] list = map.values().toArray(new IdsMapEntry[map.size()]); Arrays.sort(list); return defaultWidth(new AutoComboBox<>(list), 160); @@ -5982,18 +5984,8 @@ private boolean compareItem(Object item, String pattern) // Check against specific datatypes for more accurate results if (curItem instanceof IdsMapEntry) { id = Long.toString(((IdsMapEntry)curItem).getID()); - } else if (curItem instanceof Bitmap) { - id = Integer.toString(((Bitmap)curItem).getValue()); - } else if (curItem instanceof HashBitmap) { - id = Long.toString(((HashBitmap)curItem).getValue()); - } else if (curItem instanceof IdsBitmap) { - id = Long.toString(((HashBitmap)curItem).getValue()); - } else if (curItem instanceof IwdRef) { - id = Long.toString(((IwdRef)curItem).getValue()); - } else if (curItem instanceof ProRef) { - id = Long.toString(((ProRef)curItem).getValue()); - } else if (curItem instanceof Song2daBitmap) { - id = Long.toString(((Song2daBitmap)curItem).getValue()); + } else if (curItem instanceof IsNumeric) { + id = Long.toString(((IsNumeric)curItem).getValue()); } else if (curItem instanceof TextBitmap) { id = ((TextBitmap)curItem).getDescription(); } else { diff --git a/src/org/infinity/search/SongReferenceSearcher.java b/src/org/infinity/search/SongReferenceSearcher.java index 310dd1539..3a4d2b9c2 100644 --- a/src/org/infinity/search/SongReferenceSearcher.java +++ b/src/org/infinity/search/SongReferenceSearcher.java @@ -50,7 +50,7 @@ public SongReferenceSearcher(ResourceEntry musPlaylist, Component parent) new boolean[]{true, true, false, true}, parent); long songId = -1L; final Song2daBitmap songBitmap = new Song2daBitmap(StreamUtils.getByteBuffer(4), 0, 4); - for (final RefEntry refEntry : songBitmap.getResourceList()) { + for (final RefEntry refEntry : songBitmap.getBitmap().values()) { final ResourceEntry entry = refEntry.getResourceEntry(); if (entry != null && entry.equals(musPlaylist)) { songId = refEntry.getValue(); diff --git a/src/org/infinity/search/advanced/FilterInput.java b/src/org/infinity/search/advanced/FilterInput.java index 24e04b6c4..06702cf03 100644 --- a/src/org/infinity/search/advanced/FilterInput.java +++ b/src/org/infinity/search/advanced/FilterInput.java @@ -68,7 +68,7 @@ import org.infinity.resource.Profile; import org.infinity.resource.key.ResourceEntry; import org.infinity.resource.ui.ResourceListModel; -import org.infinity.util.ObjectString; +import org.infinity.util.DataString; /** * Input dialog for a advanced search filter definition. @@ -105,14 +105,15 @@ public class FilterInput extends ChildFrame private JPanel pFieldInput; private CardLayout clFieldInput, clValueInput; - private JComboBox cbFieldType; + private JComboBox> cbFieldType; private JTextField tfFieldName, tfValueStringInput; private JFormattedTextField ftfFieldOffsetInput; private JCheckBox cbFieldNameCase, cbFieldNameRegex; private JPopupMenu menuFieldOffset; private JPanel pValueInput; - private JComboBox cbValueType, cbValueBitfieldMode; + private JComboBox> cbValueType; + private JComboBox> cbValueBitfieldMode; private JComboBox cbValueResourceInput; private JComboBox cbValueResourceType; private JFormattedTextField ftfValueInputMin, ftfValueInputMax; @@ -323,11 +324,13 @@ private JPanel initFieldInput() { GridBagConstraints c = new GridBagConstraints(); JLabel lFieldType = new JLabel("Search field by:"); - cbFieldType = new JComboBox<>(new ObjectString[] { - new ObjectString("Name", SearchOptions.FieldMode.ByName, "%s"), - new ObjectString("Relative Offset", SearchOptions.FieldMode.ByRelativeOffset, "%s"), - new ObjectString("Absolute Offset", SearchOptions.FieldMode.ByAbsoluteOffset, "%s"), - }); + DefaultComboBoxModel> modelFieldType = + new DefaultComboBoxModel>() {{ + addElement(DataString.with("Name", SearchOptions.FieldMode.ByName, "%s")); + addElement(DataString.with("Relative Offset", SearchOptions.FieldMode.ByRelativeOffset, "%s")); + addElement(DataString.with("Absolute Offset", SearchOptions.FieldMode.ByAbsoluteOffset, "%s")); + }}; + cbFieldType = new JComboBox<>(modelFieldType); cbFieldType.setSelectedIndex(0); cbFieldType.addActionListener(listeners); JPanel pFieldType = new JPanel(new GridBagLayout()); @@ -384,12 +387,14 @@ private JPanel initValueInput() { GridBagConstraints c = new GridBagConstraints(); JLabel lValueType = new JLabel("Value type:"); - cbValueType = new JComboBox<>(new ObjectString[] { - new ObjectString("Text", SearchOptions.ValueType.Text, "%s"), - new ObjectString("Number", SearchOptions.ValueType.Number, "%s"), - new ObjectString("Resource", SearchOptions.ValueType.Resource, "%s"), - new ObjectString("Bitfield", SearchOptions.ValueType.Bitfield, "%s"), - }); + DefaultComboBoxModel> modelValueType = + new DefaultComboBoxModel>() {{ + addElement(DataString.with("Text", SearchOptions.ValueType.Text, "%s")); + addElement(DataString.with("Number", SearchOptions.ValueType.Number, "%s")); + addElement(DataString.with("Resource", SearchOptions.ValueType.Resource, "%s")); + addElement(DataString.with("Bitfield", SearchOptions.ValueType.Bitfield, "%s")); + }}; + cbValueType = new JComboBox<>(modelValueType); cbValueType.setSelectedIndex(0); cbValueType.addActionListener(listeners); @@ -454,12 +459,14 @@ private JPanel initValueInput() bpwValueBitfield = new ButtonPopupWindow("Set options...", new FlagsPanel(4, flagsDesc)); JLabel lValueBitfieldMode = new JLabel("Mode:"); - cbValueBitfieldMode = new JComboBox<>(new ObjectString[] { - new ObjectString("Exact match", SearchOptions.BitFieldMode.Exact, "%s"), - new ObjectString("Match all set bits (AND)", SearchOptions.BitFieldMode.And, "%s"), - new ObjectString("Match any set bits (OR)", SearchOptions.BitFieldMode.Or, "%s"), - new ObjectString("Match one set bit (XOR)", SearchOptions.BitFieldMode.Xor, "%s"), - }); + DefaultComboBoxModel> modelValueBitfieldMode = + new DefaultComboBoxModel>() {{ + addElement(DataString.with("Exact match", SearchOptions.BitFieldMode.Exact, "%s")); + addElement(DataString.with("Match all set bits (AND)", SearchOptions.BitFieldMode.And, "%s")); + addElement(DataString.with("Match any set bits (OR)", SearchOptions.BitFieldMode.Or, "%s")); + addElement(DataString.with("Match one set bit (XOR)", SearchOptions.BitFieldMode.Xor, "%s")); + }}; + cbValueBitfieldMode = new JComboBox<>(modelValueBitfieldMode); JPanel pValueBitfield = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 2, 1, 1, 0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); pValueBitfield.add(bpwValueBitfield, c); @@ -548,11 +555,12 @@ private void importOptions(SearchOptions so) // field input { - DefaultComboBoxModel model = (DefaultComboBoxModel)cbFieldType.getModel(); + DefaultComboBoxModel> model = + (DefaultComboBoxModel>)cbFieldType.getModel(); int idx = 0; for (int i = 0; i < model.getSize(); i++) { - ObjectString os = model.getElementAt(i); - if (so.getSearchType().equals(os.getObject())) { + DataString os = model.getElementAt(i); + if (so.getSearchType().equals(os.getData())) { idx = i; break; } @@ -567,11 +575,12 @@ private void importOptions(SearchOptions so) // value input { - DefaultComboBoxModel modelValue = (DefaultComboBoxModel)cbValueType.getModel(); + DefaultComboBoxModel> modelValue = + (DefaultComboBoxModel>)cbValueType.getModel(); int idx = 0; for (int i = 0; i < modelValue.getSize(); i++) { - ObjectString os = modelValue.getElementAt(i); - if (so.getValueType().equals(os.getObject())) { + DataString os = modelValue.getElementAt(i); + if (so.getValueType().equals(os.getData())) { idx = i; break; } @@ -612,11 +621,12 @@ private void importOptions(SearchOptions so) FlagsPanel fp = (FlagsPanel)bpwValueBitfield.getContent(); fp.setValue(so.getValueBitfield()); - modelValue = (DefaultComboBoxModel)cbValueBitfieldMode.getModel(); + DefaultComboBoxModel> modelMode = + (DefaultComboBoxModel>)cbValueBitfieldMode.getModel(); idx = 0; for (int i = 0; i < modelValue.getSize(); i++) { - ObjectString os = modelValue.getElementAt(i); - if (so.getBitfieldMode().equals(os.getObject())) { + DataString os = modelMode.getElementAt(i); + if (so.getBitfieldMode().equals(os.getData())) { idx = i; break; } @@ -639,9 +649,9 @@ private void exportOptions(SearchOptions so) // value input { - ObjectString os = cbValueType.getModel().getElementAt(cbValueType.getSelectedIndex()); + DataString os = cbValueType.getModel().getElementAt(cbValueType.getSelectedIndex()); if (os != null) { - SearchOptions.ValueType type = (SearchOptions.ValueType)os.getObject(); + SearchOptions.ValueType type = os.getData(); switch (type) { case Text: so.setValueText(tfValueStringInput.getText(), cbValueStringCase.isSelected(), cbValueStringRegex.isSelected()); @@ -662,8 +672,9 @@ private void exportOptions(SearchOptions so) case Bitfield: { FlagsPanel fp = (FlagsPanel)bpwValueBitfield.getContent(); - ObjectString osMode = cbValueBitfieldMode.getModel().getElementAt(cbValueBitfieldMode.getSelectedIndex()); - so.setValueBitfield(fp.getValue(), (SearchOptions.BitFieldMode)osMode.getObject()); + DataString osMode = + cbValueBitfieldMode.getModel().getElementAt(cbValueBitfieldMode.getSelectedIndex()); + so.setValueBitfield(fp.getValue(), osMode.getData()); break; } } @@ -674,9 +685,9 @@ private void exportOptions(SearchOptions so) // field input { - ObjectString os = cbFieldType.getModel().getElementAt(cbFieldType.getSelectedIndex()); + DataString os = cbFieldType.getModel().getElementAt(cbFieldType.getSelectedIndex()); if (os != null) { - SearchOptions.FieldMode mode = (SearchOptions.FieldMode)os.getObject(); + SearchOptions.FieldMode mode = os.getData(); switch (mode) { case ByName: so.setSearchName(tfFieldName.getText(), cbFieldNameCase.isSelected(), cbFieldNameRegex.isSelected()); @@ -886,8 +897,8 @@ public void actionPerformed(ActionEvent event) } else if (event.getSource() == cbFieldType) { // switch field type panel if (cbFieldType.getSelectedIndex() >= 0) { - ObjectString os = cbFieldType.getModel().getElementAt(cbFieldType.getSelectedIndex()); - SearchOptions.FieldMode mode = (SearchOptions.FieldMode)os.getObject(); + DataString os = cbFieldType.getModel().getElementAt(cbFieldType.getSelectedIndex()); + SearchOptions.FieldMode mode = os.getData(); switch (mode) { case ByName: clFieldInput.show(pFieldInput, FIELD_TYPE_TEXT); @@ -907,8 +918,8 @@ public void actionPerformed(ActionEvent event) } else if (event.getSource() == cbValueType) { // switch value type panel if (cbValueType.getSelectedIndex() >= 0) { - ObjectString os = cbValueType.getModel().getElementAt(cbValueType.getSelectedIndex()); - SearchOptions.ValueType mode = (SearchOptions.ValueType)os.getObject(); + DataString os = cbValueType.getModel().getElementAt(cbValueType.getSelectedIndex()); + SearchOptions.ValueType mode = os.getData(); switch (mode) { case Number: clValueInput.show(pValueInput, VALUE_TYPE_NUMBER); diff --git a/src/org/infinity/search/advanced/NumberFormatterEx.java b/src/org/infinity/search/advanced/NumberFormatterEx.java index b4d7b76f5..8280cf589 100644 --- a/src/org/infinity/search/advanced/NumberFormatterEx.java +++ b/src/org/infinity/search/advanced/NumberFormatterEx.java @@ -10,7 +10,7 @@ import javax.swing.JFormattedTextField.AbstractFormatter; -import org.infinity.util.ObjectString; +import org.infinity.util.DataString; /** * A simplified NumberFormatter that handles conversion from Object to String and back. @@ -85,9 +85,9 @@ public Object stringToValue(String text) throws ParseException currentValue = convertToNumber(text); switch (numberFormat) { case hexadecimal: - return new ObjectString(String.format(fmtHex, currentValue), currentValue, ""); + return DataString.with(String.format(fmtHex, currentValue), Long.valueOf(currentValue), ""); default: - return new ObjectString(String.format(fmtDec, currentValue), currentValue, ""); + return DataString.with(String.format(fmtDec, currentValue), Long.valueOf(currentValue), ""); } } @@ -96,9 +96,9 @@ public String valueToString(Object value) throws ParseException { long retVal = currentValue; if (value != null) { - if (value instanceof ObjectString && ((ObjectString) value).getObject() instanceof Number) { - ObjectString os = (ObjectString) value; - Number n = (Number) os.getObject(); + if (value instanceof DataString && ((DataString)value).getData() instanceof Number) { + DataString os = (DataString) value; + Number n = (Number)os.getData(); retVal = getBoundedValue(n.longValue()); } else { retVal = convertToNumber(value.toString()); @@ -168,13 +168,13 @@ public long getNumericValue() public long getNumericValue(Object value) throws ParseException { Number n = null; - if (value instanceof ObjectString && ((ObjectString) value).getObject() instanceof Number) { - n = (Number) ((ObjectString) value).getObject(); + if (value instanceof DataString && ((DataString)value).getData() instanceof Number) { + n = (Number)((DataString)value).getData(); } else if (value instanceof Number) { n = (Number) value; } else { - ObjectString os = (ObjectString) stringToValue(value.toString()); - n = (Number) os.getObject(); + DataString os = (DataString)stringToValue(value.toString()); + n = (Number)os.getData(); } return getBoundedValue(n.longValue()); diff --git a/src/org/infinity/util/DataString.java b/src/org/infinity/util/DataString.java new file mode 100644 index 000000000..d98854951 --- /dev/null +++ b/src/org/infinity/util/DataString.java @@ -0,0 +1,232 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.util; + +/** + * Associates a string with a typed object. Both string and object definitions are immutable. + * @param type of the data object associated with the {@code DataString}. + */ +public class DataString implements CharSequence, Comparable +{ + /** Print {@code String} together with associated data object in parentheses. */ + public static final String FMT_DATA_BRACKETS = "%s (%s)"; + + /** Print associated data object together with {@code String} in parentheses. */ + public static final String FMT_DATA_BRACKETS_REVERSED = "%2$s (%1$s)"; + + /** Print {@code String} together with associated data object after hyphen. */ + public static final String FMT_DATA_HYPHEN = "%s - %s"; + + /** Print associated data object together with {@code String} after hyphen. */ + public static final String FMT_DATA_HYPHEN_REVERSED = "%2$s - %1$s"; + + /** Print only the String part of the {@code DataString} instance. */ + public static final String FMT_STRING_ONLY = "%1$s"; + + /** Print only the data object part of the {@code DataString} instance. */ + public static final String FMT_DATA_ONLY = "%2$s"; + + + private final String text; + private final T data; + + private String fmt; + + /** + * Creates a new {@code DataString} instance with the specified parameters. + * @param the data object type. + * @param s the associated {@code String}. + * @param data the associated data of type {@code T}. + * @return a new {@code DataString} object. + */ + public static DataString with(String s, T data) + { + return new DataString(s, data); + } + + /** + * Creates a new {@code DataString} instance with the specified parameters. + * @param the data object type. + * @param s the associated {@code String}. + * @param data the associated data of type {@code T}. + * @param fmt a formatter string used by the {@link #toString()} method. Specifying {@code null} results in using + * the {@link #FMT_STRING_ONLY} formatter string. + * @return a new {@code DataString} object. + */ + public static DataString with(String s, T data, String fmt) + { + return new DataString(s, data, fmt); + } + + /** + * Constructs a new {@code DataString} object and initializes it with the specified arguments. + * @param s the associated {@code String}. + * @param data the associated data of type {@code T}. + */ + public DataString(String s, T data) + { + this(s, data, null); + } + + /** + * Constructs a new {@code DataString} object and initializes it with the specified arguments. + * @param s the associated {@code String}. + * @param data the associated data of type {@code T}. + * @param fmt a formatter string used by the {@link #toString()} method. Specifying {@code null} results in using + * the {@link #FMT_STRING_ONLY} formatter string. + */ + public DataString(String s, T data, String fmt) + { + super(); + this.text = (s != null) ? s : ""; + this.data = data; + setFormatter(fmt); + } + +//--------------------- Begin Interface CharSequence --------------------- + + /** Returns the length of the formatted string returned by {@link #toString()}. */ + @Override + public int length() + { + return toString().length(); + } + + /** + * Returns the {@code char} value at the specified index. An index ranges from {@code 0} to {@code length() - 1}. + * The first char value of the sequence is at index {@code 0}, the next at index {@code 1}, and so on, + * as for array indexing. + *

    The method is applied to the formatted string returned by {@link #toString()}. + *

    If the {@code char} value specified by the index is a surrogate, the surrogatevalue is returned. + */ + @Override + public char charAt(int index) + { + return toString().charAt(index); + } + + /** + * Returns a {@code CharSequence} that is a subsequence of this sequence. The subsequence starts with the + * {@code char} value at the specified index and ends with the char value at index {@code end - 1}. + * The length (in {@code char}s) of there turned sequence is {@code end - start}, so if {@code start == end} + * then an empty sequence is returned. + *

    The method is applied to the formatted string returned by {@link #toString()}. + * @param start Index the begin index, inclusive. + * @param end the end index, exclusive. + * @return the specified subsequence. + */ + @Override + public CharSequence subSequence(int start, int end) + { + return toString().subSequence(start, end); + } + + /** + * Returns a string with the content controlled by the associated formatter string. + */ + @Override + public String toString() + { + return String.format(getFormatter(), getString(), getData()); + } + +//--------------------- End Interface CharSequence --------------------- + +//--------------------- Begin Interface Comparable --------------------- + + /** + * Compares this object with the specified object for order. Returns anegative integer, zero, + * or a positive integer as this object is lessthan, equal to, or greater than the specified object. + *

    The method is applied to the formatted string returned by {@link #toString()}. + */ + @Override + public int compareTo(CharSequence o) + { + if (o == null) { + return 1; + } + return toString().compareTo(o.toString()); + } + +//--------------------- End Interface Comparable --------------------- + + @Override + public boolean equals(Object anObject) + { + if (this == anObject) { + return true; + } + if (!(anObject instanceof DataString)) { + return false; + } + DataString other = (DataString)anObject; + boolean retVal = text.equals(other.text); + retVal &= (data == null && other.data == null) || + (data != null && data.equals(other.data)); + return retVal; + } + + @Override + public int hashCode() + { + int hash = 7; + hash = 31 * hash + text.hashCode(); + hash = 31 * hash + ((data == null) ? 0 : data.hashCode()); + return hash; + } + + /** + * Returns the raw {@code String} associated with the {@code DataString}. + * @return {@code String} part of the {@code DataString} + */ + public String getString() + { + return text; + } + + /** + * Returns the data object associated with the {@code DataString}. + * @return data object of type {@link T}. + */ + public T getData() + { + return data; + } + + /** + * Returns the formatter string used by {@link #toString()} to generate the return value. + */ + public String getFormatter() + { + return fmt; + } + + /** + *

    Sets the formatter string that is used by {@link #toString()} to generate the return value. + *

    The formatter string may have up to two format specifiers. The first specifier is used for the string portion + * of the {@code DataString}. The second specifier is used for the data portion of the {@code DataString}. + *

    The position flag of the specifiers can be used to control order of output. + * @param fmt the formatter string. Specifying {@code null} results in using the {@link #FMT_STRING_ONLY} + * formatter string. + */ + public void setFormatter(String fmt) + { + if (fmt == null) { + fmt = FMT_STRING_ONLY; + } + if (!fmt.equals(this.fmt)) { + this.fmt = fmt; + } + } + + /** + * Returns {@code true} if, and only if, {@link #length()} is {@code 0}. + * @return {@code true} if {@link #length()} is {@code null}, otherwise {@code false}. + */ + public boolean isEmpty() + { + return toString().isEmpty(); + } +} diff --git a/src/org/infinity/util/IdsMap.java b/src/org/infinity/util/IdsMap.java index 7d54529ee..ab2b0ca32 100644 --- a/src/org/infinity/util/IdsMap.java +++ b/src/org/infinity/util/IdsMap.java @@ -221,7 +221,8 @@ private void extractIDS(String line) private long normalizedKey(long key) { - return key & 0xffffffffL; + // cap to 32 bit but preserve sign + return key % 0x100000000L; } private String normalizedString(String s) diff --git a/src/org/infinity/util/ObjectString.java b/src/org/infinity/util/ObjectString.java deleted file mode 100644 index eb2b6d4cf..000000000 --- a/src/org/infinity/util/ObjectString.java +++ /dev/null @@ -1,171 +0,0 @@ -// Near Infinity - An Infinity Engine Browser and Editor -// Copyright (C) 2001 - 2019 Jon Olav Hauglid -// See LICENSE.txt for license information - -package org.infinity.util; - -import java.util.Map; - -/** - * Associates a string with an object. Both string and object definitions are immutable. - */ -public class ObjectString implements CharSequence, Comparable -{ - /** Print String together with associated object in parentheses. */ - public static final String FMT_OBJECT_BRACKETS = "%1$s (%2$s)"; - /** Print String together with associated object after hyphen. */ - public static final String FMT_OBJECT_HYPHEN = "%1$s - %2$s"; - /** Print only Object part of the ObjectString instance. */ - public static final String FMT_STRING_ONLY = "%1$s"; - /** Print only String part of the ObjectString instance. */ - public static final String FMT_OBJECT_ONLY = "%2$s"; - - private final String string; - private final Object object; - - private final String displayFormat; - - /** - * Helper routine: Associate strings with objects. - */ - public static ObjectString[] createObjectStrings(String[] strings, Object[] objects, String fmt) - { - ObjectString[] retVal = null; - if (strings != null && objects != null) { - int size = Math.max(strings.length, objects.length); - retVal = new ObjectString[size]; - for (int i = 0; i < size; i++) { - String s = (i < strings.length) ? strings[i] : ""; - Object o = (i < objects.length) ? objects[i] : null; - retVal[i] = new ObjectString(s, o, fmt); - } - } else { - retVal = new ObjectString[0]; - } - return retVal; - } - - /** - * Helper routine: Automatically create string/index pairs from string array. - */ - public static ObjectString[] createIndexedStrings(String[] strings, int startIndex, int ofsIndex, - String fmt) - { - ObjectString[] retVal = null; - if (strings != null && startIndex < strings.length) { - retVal = new ObjectString[strings.length - startIndex]; - for (int i = startIndex; i < strings.length; i++) { - retVal[i - startIndex] = new ObjectString(strings[i], Integer.valueOf(i - startIndex + ofsIndex), fmt); - } - } else { - retVal = new ObjectString[0]; - } - return retVal; - } - - /** - * Helper routine: Automatically create string/index pairs from HashBitmap source. - */ - public static ObjectString[] createIndexedStrings(LongIntegerHashMap map, String fmt) - { - if (map == null) { - return new ObjectString[0]; - } - final ObjectString[] retVal = new ObjectString[map.size()]; - int i = 0; - for (Map.Entry e : map.entrySet()) { - retVal[i] = new ObjectString(e.getValue().toString(), Integer.valueOf(e.getKey().intValue()), fmt); - ++i; - } - return retVal; - } - - /** - * Constructs an ObjectString with the specified String and object fields. - */ - public ObjectString(String s, Object object) - { - this(s, object, null); - } - - /** - * Constructs an ObjectString with the specified String and object fields. - * @param fmt Indicates how {@code toString()} should display the formatted string. - */ - public ObjectString(String s, Object object, String fmt) - { - this.string = (s != null) ? s : ""; - this.object = object; - this.displayFormat = (fmt != null) ? fmt : FMT_OBJECT_BRACKETS; - } - - @Override - public boolean equals(Object o) - { - boolean eq = false; - if (o instanceof ObjectString) { - ObjectString os = (ObjectString)o; - eq = string.equals(os.getString()); - if (eq) { - eq = (object == null && os.getObject() == null) || - (object != null && object.equals(os.getObject())); - } - } - return eq; - } - -//--------------------- Begin Interface CharSequence --------------------- - - @Override - public int length() - { - return string.length(); - } - - @Override - public char charAt(int index) - { - return string.charAt(index); - } - - @Override - public CharSequence subSequence(int start, int end) - { - return string.subSequence(start, end); - } - - @Override - public String toString() - { - return String.format(displayFormat, string, (object != null) ? object.toString() : "null"); - } - -//--------------------- End Interface CharSequence --------------------- - -//--------------------- Begin Interface Comparable --------------------- - - @Override - public int compareTo(ObjectString o) - { - if (o != null) { - return getString().compareTo(o.getString()); - } else { - throw new NullPointerException(); - } - } - -//--------------------- End Interface Comparable --------------------- - - /** Returns associated string. */ - public String getString() - { - return string; - } - - /** Returns the associated object. */ - @SuppressWarnings("unchecked") - public T getObject() - { - return (T)object; - } -} diff --git a/src/org/infinity/util/tuples/Tuple.java b/src/org/infinity/util/tuples/Tuple.java index fca3a962f..1c12b66c9 100644 --- a/src/org/infinity/util/tuples/Tuple.java +++ b/src/org/infinity/util/tuples/Tuple.java @@ -10,12 +10,17 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.function.Function; public abstract class Tuple implements Iterable, Comparable { + public static final Function, String> FMT_DEFAULT = (list) -> list.toString(); + private final Object[] values; private final List valueList; + private Function, String> formatter; + protected Tuple(Object... values) { this.values = values; @@ -66,10 +71,33 @@ public Iterator iterator() return this.valueList.iterator(); } + /** + * Returns a functional interface used to generate the return value of the {@link #toString()} method. + *

    It takes a list of {@code Objects} which represent the values assigned to the tuple and returns a + * textual representation of the values. + * @return Function to generate the return value of the {@link #toString()} method. + */ + public Function, String> getFormatter() + { + return (formatter != null) ? formatter : FMT_DEFAULT; + } + + /** + * Assigns a new functional interface responsible for generating the return value of the {@link #toString()} method. + *

    It takes a list of {@code Objects} which represent the values assigned to the tuple and returns a + * textual representation of the values. + * @param formatter the {@code Function} to generate the return value of the {@link #toString()} method. + * Specify {@code null} to revert to the default function. + */ + public void setFormatter(Function, String> formatter) + { + this.formatter = formatter; + } + @Override public String toString() { - return valueList.toString(); + return getFormatter().apply(valueList); } /** From ec7721f86aa7a3e50d75b0ee6b7a8f6690f58cb2 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 19 Mar 2021 22:34:10 +0100 Subject: [PATCH 063/158] Fix color values from effects to ignore randomized color entries Only color fields in CRE resources support random colors. --- src/org/infinity/datatype/ColorValue.java | 12 +++---- src/org/infinity/resource/EffectFactory.java | 6 ++-- .../infinity/resource/cre/CreResource.java | 32 ++++++++--------- .../resource/cre/decoder/SpriteDecoder.java | 12 ++++--- .../cre/decoder/internal/CreatureInfo.java | 34 +++++++++++++------ .../infinity/resource/pro/ProAreaType.java | 2 +- .../infinity/resource/pro/ProSingleType.java | 4 +-- 7 files changed, 58 insertions(+), 44 deletions(-) diff --git a/src/org/infinity/datatype/ColorValue.java b/src/org/infinity/datatype/ColorValue.java index 1b24d1609..6c17a94fd 100644 --- a/src/org/infinity/datatype/ColorValue.java +++ b/src/org/infinity/datatype/ColorValue.java @@ -86,19 +86,19 @@ public class ColorValue extends Datatype implements Editable, IsNumeric private ResourceEntry colorEntry; // the source of color ranges private IdsMap colorMap; // provides an optional symbolic color name - public ColorValue(ByteBuffer buffer, int offset, int length, String name) + public ColorValue(ByteBuffer buffer, int offset, int length, String name, boolean allowRandom) { - this(buffer, offset, length, name, null); + this(buffer, offset, length, name, allowRandom, null); } - public ColorValue(ByteBuffer buffer, int offset, int length, String name, String bmpFile) + public ColorValue(ByteBuffer buffer, int offset, int length, String name, boolean allowRandom, String bmpFile) { super(offset, length, name); - init(bmpFile); + init(bmpFile, allowRandom); read(buffer, offset); } - private void init(String bmpFile) + private void init(String bmpFile, boolean allowRandom) { if (bmpFile != null && ResourceFactory.resourceExists(bmpFile)) { this.colorEntry = ResourceFactory.getResourceEntry(bmpFile); @@ -108,7 +108,7 @@ private void init(String bmpFile) } ResourceEntry randomEntry = null; - if (ResourceFactory.resourceExists("RANDCOLR.2DA")) { + if (allowRandom && ResourceFactory.resourceExists("RANDCOLR.2DA")) { randomEntry = ResourceFactory.getResourceEntry("RANDCOLR.2DA"); } diff --git a/src/org/infinity/resource/EffectFactory.java b/src/org/infinity/resource/EffectFactory.java index b75a2535d..acec9d063 100644 --- a/src/org/infinity/resource/EffectFactory.java +++ b/src/org/infinity/resource/EffectFactory.java @@ -1063,7 +1063,7 @@ private static boolean updateOpcode342(AbstractStruct struct) throws Exception newEntry = new Bitmap(getEntryData(struct, EffectEntry.IDX_PARAM1), 0, 4, "Enabled?", AbstractStruct.OPTION_NOYES); break; case 2: - newEntry = new ColorValue(getEntryData(struct, EffectEntry.IDX_PARAM1), 0, 4, "Color"); + newEntry = new ColorValue(getEntryData(struct, EffectEntry.IDX_PARAM1), 0, 4, "Color", false); break; default: newEntry = new DecNumber(getEntryData(struct, EffectEntry.IDX_PARAM1), 0, 4, "Value"); @@ -2157,7 +2157,7 @@ private String makeEffectParamsGeneric(Datatype parent, ByteBuffer buffer, int o break; case 7: // Set color - s.add(new ColorValue(buffer, offset, 4, "Color")); + s.add(new ColorValue(buffer, offset, 4, "Color", false)); s.add(new HashBitmap(buffer, offset + 4, 4, "Location", m_colorloc, false)); break; @@ -4288,7 +4288,7 @@ private String makeEffectParamsBG2(Datatype parent, ByteBuffer buffer, int offse "Personal space"}); switch (bmp.getValue()) { case 1: s.add(new Bitmap(buffer, offset, 4, "Enabled?", AbstractStruct.OPTION_NOYES)); break; - case 2: s.add(new ColorValue(buffer, offset, 4, "Color")); break; + case 2: s.add(new ColorValue(buffer, offset, 4, "Color", false)); break; default: s.add(new DecNumber(buffer, offset, 4, "Value")); } s.add(bmp); diff --git a/src/org/infinity/resource/cre/CreResource.java b/src/org/infinity/resource/cre/CreResource.java index fbe6990a2..4a1af755b 100644 --- a/src/org/infinity/resource/cre/CreResource.java +++ b/src/org/infinity/resource/cre/CreResource.java @@ -1021,13 +1021,13 @@ private int readIWD2(ByteBuffer buffer, int offset) throws Exception addField(new DecNumber(buffer, offset + 28, 2, CRE_HP_CURRENT)); addField(new DecNumber(buffer, offset + 30, 2, CRE_HP_MAX)); addField(new IdsBitmap(buffer, offset + 32, 4, CRE_ANIMATION, "ANIMATE.IDS")); - addField(new ColorValue(buffer, offset + 36, 1, CRE_COLOR_METAL)); - addField(new ColorValue(buffer, offset + 37, 1, CRE_COLOR_MINOR)); - addField(new ColorValue(buffer, offset + 38, 1, CRE_COLOR_MAJOR)); - addField(new ColorValue(buffer, offset + 39, 1, CRE_COLOR_SKIN)); - addField(new ColorValue(buffer, offset + 40, 1, CRE_COLOR_LEATHER)); - addField(new ColorValue(buffer, offset + 41, 1, CRE_COLOR_ARMOR)); - addField(new ColorValue(buffer, offset + 42, 1, CRE_COLOR_HAIR)); + addField(new ColorValue(buffer, offset + 36, 1, CRE_COLOR_METAL, true)); + addField(new ColorValue(buffer, offset + 37, 1, CRE_COLOR_MINOR, true)); + addField(new ColorValue(buffer, offset + 38, 1, CRE_COLOR_MAJOR, true)); + addField(new ColorValue(buffer, offset + 39, 1, CRE_COLOR_SKIN, true)); + addField(new ColorValue(buffer, offset + 40, 1, CRE_COLOR_LEATHER, true)); + addField(new ColorValue(buffer, offset + 41, 1, CRE_COLOR_ARMOR, true)); + addField(new ColorValue(buffer, offset + 42, 1, CRE_COLOR_HAIR, true)); Bitmap effect_version = addField(new Bitmap(buffer, offset + 43, 1, CRE_EFFECT_VERSION, s_effversion)); effect_version.addUpdateListener(this); addField(new ResourceRef(buffer, offset + 44, CRE_PORTRAIT_SMALL, "BMP")); @@ -1444,13 +1444,13 @@ private int readOther(String version, ByteBuffer buffer, int offset) throws Exce if (Profile.getGame() == Profile.Game.PSTEE && version.equals("V1.0")) { setColorFieldsPSTEE(animate.getValue(), buffer, offset + 36, false); } else { - addField(new ColorValue(buffer, offset + 36, 1, CRE_COLOR_METAL)); - addField(new ColorValue(buffer, offset + 37, 1, CRE_COLOR_MINOR)); - addField(new ColorValue(buffer, offset + 38, 1, CRE_COLOR_MAJOR)); - addField(new ColorValue(buffer, offset + 39, 1, CRE_COLOR_SKIN)); - addField(new ColorValue(buffer, offset + 40, 1, CRE_COLOR_LEATHER)); - addField(new ColorValue(buffer, offset + 41, 1, CRE_COLOR_ARMOR)); - addField(new ColorValue(buffer, offset + 42, 1, CRE_COLOR_HAIR)); + addField(new ColorValue(buffer, offset + 36, 1, CRE_COLOR_METAL, true)); + addField(new ColorValue(buffer, offset + 37, 1, CRE_COLOR_MINOR, true)); + addField(new ColorValue(buffer, offset + 38, 1, CRE_COLOR_MAJOR, true)); + addField(new ColorValue(buffer, offset + 39, 1, CRE_COLOR_SKIN, true)); + addField(new ColorValue(buffer, offset + 40, 1, CRE_COLOR_LEATHER, true)); + addField(new ColorValue(buffer, offset + 41, 1, CRE_COLOR_ARMOR, true)); + addField(new ColorValue(buffer, offset + 42, 1, CRE_COLOR_HAIR, true)); } Bitmap effect_version = addField(new Bitmap(buffer, offset + 43, 1, CRE_EFFECT_VERSION, s_effversion)); effect_version.addUpdateListener(this); @@ -1668,7 +1668,7 @@ else if (version.equalsIgnoreCase("V9.0") || version.equalsIgnoreCase("V9.1")) { addField(new DecNumber(buffer, offset + 727, 1, CRE_NUM_COLORS)); addField(new Flag(buffer, offset + 728, 4, CRE_ATTRIBUTES, s_attributes_pst)); for (int i = 0; i < 7; i++) { - addField(new ColorValue(buffer, offset + 732 + (i * 2), 2, String.format(CRE_COLOR_FMT, i+1), + addField(new ColorValue(buffer, offset + 732 + (i * 2), 2, String.format(CRE_COLOR_FMT, i+1), true, "PAL32.BMP")); // addField(new IdsBitmap(buffer, offset + 732 + (i * 2), 2, // String.format(CRE_COLOR_FMT, i+1), "CLOWNCLR.IDS")); @@ -2003,7 +2003,7 @@ private int setColorFieldsPSTEE(int animId, ByteBuffer buffer, int startOffset, } startOffset += field.getSize(); } else { - addField(new ColorValue(buffer, startOffset, 1, colorNames[i], "PAL32.BMP")); + addField(new ColorValue(buffer, startOffset, 1, colorNames[i], true, "PAL32.BMP")); startOffset++; } } diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index 76f4db7fd..052bae2e5 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -1925,11 +1925,11 @@ protected int getColorOffset(int locationIndex) * @param colorIndex the color entry. * @return palette data as int array. Returns {@code null} if palette data could not be determined. */ - protected int[] getColorData(int colorIndex) + protected int[] getColorData(int colorIndex, boolean allowRandom) { int[] retVal = null; try { - retVal = SpriteUtils.getColorGradient(colorIndex, true); + retVal = SpriteUtils.getColorGradient(colorIndex, allowRandom); } catch (Exception e) { e.printStackTrace(); } @@ -1950,9 +1950,11 @@ protected void applyFalseColors(BamV1Control control, SegmentDef sd) final Map colorRanges = new HashMap(); for (int loc = 0; loc < 7; loc++) { int ofs = getColorOffset(loc); - int col = getCreatureInfo().getEffectiveColorValue(sd.getSpriteType(), loc); - if (ofs > 0 && col >= 0) { - int[] range = getColorData(col); + Couple colorInfo = getCreatureInfo().getEffectiveColorValue(sd.getSpriteType(), loc); + int colIdx = colorInfo.getValue0().intValue(); + boolean allowRandom = colorInfo.getValue1().booleanValue(); + if (ofs > 0 && colIdx >= 0) { + int[] range = getColorData(colIdx, allowRandom); if (range != null) { colorRanges.put(ofs, range); } diff --git a/src/org/infinity/resource/cre/decoder/internal/CreatureInfo.java b/src/org/infinity/resource/cre/decoder/internal/CreatureInfo.java index 5bde83e07..8a8191b03 100644 --- a/src/org/infinity/resource/cre/decoder/internal/CreatureInfo.java +++ b/src/org/infinity/resource/cre/decoder/internal/CreatureInfo.java @@ -33,6 +33,7 @@ import org.infinity.util.Misc; import org.infinity.util.Table2da; import org.infinity.util.Table2daCache; +import org.infinity.util.tuples.Couple; /** * Provides useful information about a creature resource and their equipment. @@ -491,23 +492,34 @@ public int getColorValue(SegmentDef.SpriteType type, int locationIndex) /** * Returns the color entry of the specified location index for the avatar sprite - * after applying all equipment and effect colors. - * Returns -1 if color entry is not available. + * after applying all equipment and effect colors as well as the source of the color value. + * @param locationIndex the color location index. Available range: [0, 6] + * @return a tuple with the color entry as well as a {@code Boolean} value indicating whether random colors are allowed. + * Returns {@code -1} for the color entry if value could not be determined. */ - public int getEffectiveColorValue(int locationIndex) + public Couple getEffectiveColorValue(int locationIndex) { return getEffectiveColorValue(SegmentDef.SpriteType.AVATAR, locationIndex); } /** * Returns the color entry of the location index for the specified sprite overlay type - * after applying all equipment and effect colors. - * Returns -1 if color entry is not available. + * after applying all equipment and effect colors as well as the source of the color value. + * @param type the {@link SegmentDef.SpriteType SpriteType} target + * @param locationIndex the color location index. Available range: [0, 6] + * @return a tuple with the color entry as well as a {@code Boolean} value indicating whether random colors are allowed. + * Returns {@code -1} for the color entry if value could not be determined. */ - public int getEffectiveColorValue(SegmentDef.SpriteType type, int locationIndex) + public Couple getEffectiveColorValue(SegmentDef.SpriteType type, int locationIndex) { + Couple retVal = Couple.with(-1, true); + // using creature color by default - int retVal = getColorValue(SegmentDef.SpriteType.AVATAR, locationIndex); + int value = getColorValue(SegmentDef.SpriteType.AVATAR, locationIndex); + if (value >= 0) { + retVal.setValue0(value); + retVal.setValue1(Boolean.TRUE); + } if (type == null) { type = SegmentDef.SpriteType.AVATAR; @@ -518,14 +530,16 @@ public int getEffectiveColorValue(SegmentDef.SpriteType type, int locationIndex) for (final ItemInfo info : itemInfos) { int v = info.getColorInfo().getValue(type, locationIndex); if (v >= 0) { - retVal = v; + retVal.setValue0(v); + retVal.setValue1(Boolean.FALSE); } } // checking creature effects int v = getColorInfo().getValue(type, locationIndex); if (v >= 0) { - retVal = v; + retVal.setValue0(v); + retVal.setValue1(Boolean.FALSE); } return retVal; @@ -548,8 +562,6 @@ private void init() throws Exception initV10(); break; case "V1.1": // non-standard PST format - initV10(); - break; case "V1.2": initV12(); break; diff --git a/src/org/infinity/resource/pro/ProAreaType.java b/src/org/infinity/resource/pro/ProAreaType.java index 5a7d8435b..fcb037e74 100644 --- a/src/org/infinity/resource/pro/ProAreaType.java +++ b/src/org/infinity/resource/pro/ProAreaType.java @@ -161,7 +161,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new ProRef(buffer, offset + 20, PRO_AREA_SECONDARY_PROJECTILE, false)); addField(new DecNumber(buffer, offset + 22, 1, PRO_AREA_NUM_REPETITIONS)); addField(new HashBitmap(buffer, offset + 23, 1, PRO_AREA_EXPLOSION_EFFECT, m_proj)); - addField(new ColorValue(buffer, offset + 24, 1, PRO_AREA_EXPLOSION_COLOR)); + addField(new ColorValue(buffer, offset + 24, 1, PRO_AREA_EXPLOSION_COLOR, false)); addField(new Unknown(buffer, offset + 25, 1, COMMON_UNUSED)); addField(new ProRef(buffer, offset + 26, PRO_AREA_EXPLOSION_PROJECTILE)); addField(new ResourceRef(buffer, offset + 28, PRO_AREA_EXPLOSION_ANIMATION, "VEF", "VVC", "BAM")); diff --git a/src/org/infinity/resource/pro/ProSingleType.java b/src/org/infinity/resource/pro/ProSingleType.java index ab8bde41b..51909ad60 100644 --- a/src/org/infinity/resource/pro/ProSingleType.java +++ b/src/org/infinity/resource/pro/ProSingleType.java @@ -90,11 +90,11 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new DecNumber(buffer, offset + 26, 2, PRO_SINGLE_LIGHT_SPOT_HEIGHT)); addField(new ResourceRef(buffer, offset + 28, PRO_SINGLE_PALETTE, "BMP")); for (int i = 0; i < 7; i++) { - addField(new ColorValue(buffer, offset + 36 + i, 1, String.format(PRO_SINGLE_PRO_COLOR_FMT, i+1))); + addField(new ColorValue(buffer, offset + 36 + i, 1, String.format(PRO_SINGLE_PRO_COLOR_FMT, i+1), false)); } addField(new DecNumber(buffer, offset + 43, 1, PRO_SINGLE_SMOKE_PUFF_DELAY)); for (int i = 0; i < 7; i++) { - addField(new ColorValue(buffer, offset + 44 + i, 1, String.format(PRO_SINGLE_SMOKE_COLOR_FMT, i+1))); + addField(new ColorValue(buffer, offset + 44 + i, 1, String.format(PRO_SINGLE_SMOKE_COLOR_FMT, i+1), false)); } addField(new HashBitmap(buffer, offset + 51, 1, PRO_SINGLE_FACE_TARGET_GRANULARITY, m_facetarget)); addField(new IdsBitmap(buffer, offset + 52, 2, PRO_SINGLE_SMOKE_ANIMATION, "ANIMATE.IDS")); From 88a9f02ff9b606034b4d39c1baaefb21bbc7d3a7 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 19 Mar 2021 22:48:44 +0100 Subject: [PATCH 064/158] Fix signedness issue in IdsBitmap datatype --- src/org/infinity/datatype/AnimateBitmap.java | 2 +- src/org/infinity/datatype/IdsBitmap.java | 8 ++++---- src/org/infinity/resource/are/Song.java | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/org/infinity/datatype/AnimateBitmap.java b/src/org/infinity/datatype/AnimateBitmap.java index 35b37331c..91abf3ed0 100644 --- a/src/org/infinity/datatype/AnimateBitmap.java +++ b/src/org/infinity/datatype/AnimateBitmap.java @@ -46,7 +46,7 @@ public class AnimateBitmap extends IdsBitmap implements ActionListener public AnimateBitmap(ByteBuffer buffer, int offset, int length, String name) { - super(buffer, offset, length, name, "ANIMATE.IDS", true, true); + super(buffer, offset, length, name, "ANIMATE.IDS", true, true, false); setFormatter(formatterAnimateBitmap); if (Profile.isEnhancedEdition() || ResourceFactory.resourceExists("ANISND.IDS")) { diff --git a/src/org/infinity/datatype/IdsBitmap.java b/src/org/infinity/datatype/IdsBitmap.java index c80226b57..a4bf3abc5 100644 --- a/src/org/infinity/datatype/IdsBitmap.java +++ b/src/org/infinity/datatype/IdsBitmap.java @@ -33,18 +33,18 @@ public class IdsBitmap extends AbstractBitmap public IdsBitmap(ByteBuffer buffer, int offset, int length, String name, String resource) { - this(buffer, offset, length, name, resource, true, false); + this(buffer, offset, length, name, resource, true, false, false); } public IdsBitmap(ByteBuffer buffer, int offset, int length, String name, String resource, boolean sortByName) { - this(buffer, offset, length, name, resource, sortByName, false); + this(buffer, offset, length, name, resource, sortByName, false, false); } public IdsBitmap(ByteBuffer buffer, int offset, int length, String name, String resource, boolean sortByName, - boolean showAsHex) + boolean showAsHex, boolean signed) { - super(buffer, offset, length, name, createResourceList(resource), null, true); + super(buffer, offset, length, name, createResourceList(resource), null, signed); setSortByName(sortByName); setShowAsHex(showAsHex); setFormatter(formatterIdsBitmap); diff --git a/src/org/infinity/resource/are/Song.java b/src/org/infinity/resource/are/Song.java index c1d69bb34..c01d3e892 100644 --- a/src/org/infinity/resource/are/Song.java +++ b/src/org/infinity/resource/are/Song.java @@ -75,7 +75,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new DecNumber(buffer, offset + 76, 4, ARE_SONGS_AMBIENT_VOLUME_NIGHT)); } if (ResourceFactory.resourceExists("REVERB.IDS")) { - addField(new IdsBitmap(buffer, offset + 80, 4, ARE_SONGS_REVERB, "REVERB.IDS")); + addField(new IdsBitmap(buffer, offset + 80, 4, ARE_SONGS_REVERB, "REVERB.IDS", true, false, true)); addField(new Unknown(buffer, offset + 84, 60)); } else if (ResourceFactory.resourceExists("REVERB.2DA")) { addField(new TableBitmap(buffer, offset + 80, 4, ARE_SONGS_REVERB, "REVERB.2DA")); From c98143a8cb68ef99edbc82c0422e5f29c80546bd Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sat, 20 Mar 2021 11:05:20 +0100 Subject: [PATCH 065/158] Rename "Creature Animation Viewer" -> "Creature Animation Browser" --- src/org/infinity/gui/BrowserMenuBar.java | 9 +++++---- src/org/infinity/resource/cre/ViewerAnimation.java | 4 ++-- .../resource/cre/viewer/ColorSelectionModel.java | 2 +- .../resource/cre/viewer/CreatureAllegianceModel.java | 2 +- .../resource/cre/viewer/CreatureAnimationModel.java | 2 +- .../resource/cre/viewer/CreatureSelectionModel.java | 2 +- src/org/infinity/resource/cre/viewer/CreatureViewer.java | 2 +- .../infinity/resource/cre/viewer/ItemSelectionModel.java | 2 +- src/org/infinity/resource/cre/viewer/bg/Backgrounds.java | 2 +- 9 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/org/infinity/gui/BrowserMenuBar.java b/src/org/infinity/gui/BrowserMenuBar.java index 09ca2b485..5a3eb4535 100644 --- a/src/org/infinity/gui/BrowserMenuBar.java +++ b/src/org/infinity/gui/BrowserMenuBar.java @@ -1483,7 +1483,7 @@ private static final class ToolsMenu extends JMenu implements ActionListener { private static final String TOOLS_DEBUG_EXTRA_INFO = "DebugShowExtraInfo"; - private final JMenuItem toolInfinityAmp, toolCreatureViewer, toolCleanKeyfile, toolCheckAllDialog, toolCheckOverrideDialog; + private final JMenuItem toolInfinityAmp, toolCreatureBrowser, toolCleanKeyfile, toolCheckAllDialog, toolCheckOverrideDialog; private final JMenuItem toolCheckResRef, toolIDSBrowser, toolDropZone, toolCheckCREInv; private final JMenuItem toolCheckIDSRef, toolCheckIDSBCSRef, toolCheckScripts, toolCheckStructs; private final JMenuItem toolCheckStringUse, toolCheckStringIndex, toolCheckFileUse, toolMassExport; @@ -1498,8 +1498,9 @@ private ToolsMenu() super("Tools"); setMnemonic(KeyEvent.VK_T); - toolCreatureViewer = makeMenuItem("Creature Animation Viewer", KeyEvent.VK_A, Icons.getIcon(Icons.ICON_CRE_VIEWER_24), -1, this); - add(toolCreatureViewer); + toolCreatureBrowser = makeMenuItem("Creature Animation Browser", KeyEvent.VK_A, + Icons.getIcon(Icons.ICON_CRE_VIEWER_24), -1, this); + add(toolCreatureBrowser); toolInfinityAmp = makeMenuItem("InfinityAmp", KeyEvent.VK_I, Icons.getIcon(Icons.ICON_VOLUME_16), -1, this); add(toolInfinityAmp); @@ -1688,7 +1689,7 @@ private void storePreferences() @Override public void actionPerformed(ActionEvent event) { - if (event.getSource() == toolCreatureViewer) { + if (event.getSource() == toolCreatureBrowser) { ChildFrame.show(CreatureViewer.class, () -> new CreatureViewer()); } else if (event.getSource() == toolInfinityAmp) { diff --git a/src/org/infinity/resource/cre/ViewerAnimation.java b/src/org/infinity/resource/cre/ViewerAnimation.java index 6653197ba..7e8fe178c 100644 --- a/src/org/infinity/resource/cre/ViewerAnimation.java +++ b/src/org/infinity/resource/cre/ViewerAnimation.java @@ -445,8 +445,8 @@ private void init() throws Exception cbShowSpace.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0)); cbShowSpace.addActionListener(this); - JButton bOpenViewer = new JButton("Open in viewer", Icons.getIcon(Icons.ICON_CRE_VIEWER_24)); - bOpenViewer.setToolTipText("Open in Creature Animation Viewer"); + JButton bOpenViewer = new JButton("Open in browser", Icons.getIcon(Icons.ICON_CRE_VIEWER_24)); + bOpenViewer.setToolTipText("Open in Creature Animation Browser"); bOpenViewer.addActionListener(this); buttonControlPanel.addControl(lCycle, CtrlCycleLabel); diff --git a/src/org/infinity/resource/cre/viewer/ColorSelectionModel.java b/src/org/infinity/resource/cre/viewer/ColorSelectionModel.java index 58a7dffa7..6d603e3ca 100644 --- a/src/org/infinity/resource/cre/viewer/ColorSelectionModel.java +++ b/src/org/infinity/resource/cre/viewer/ColorSelectionModel.java @@ -41,7 +41,7 @@ import org.infinity.util.Table2daCache; /** - * {@code ComboBoxModel} for the color selection combo box used in the Creature Animation Viewer. + * {@code ComboBoxModel} for the color selection combo box used in the Creature Animation Browser. */ public class ColorSelectionModel extends AbstractListModel implements ComboBoxModel diff --git a/src/org/infinity/resource/cre/viewer/CreatureAllegianceModel.java b/src/org/infinity/resource/cre/viewer/CreatureAllegianceModel.java index 22df28569..9d72e8ddc 100644 --- a/src/org/infinity/resource/cre/viewer/CreatureAllegianceModel.java +++ b/src/org/infinity/resource/cre/viewer/CreatureAllegianceModel.java @@ -19,7 +19,7 @@ import org.infinity.util.IdsMapEntry; /** - * {@code ComboBoxModel} for the creature allegiance combo box used in the Creature Animation Viewer. + * {@code ComboBoxModel} for the creature allegiance combo box used in the Creature Animation Browser. */ public class CreatureAllegianceModel extends AbstractListModel implements ComboBoxModel diff --git a/src/org/infinity/resource/cre/viewer/CreatureAnimationModel.java b/src/org/infinity/resource/cre/viewer/CreatureAnimationModel.java index ff795f0a1..94b1f4aa2 100644 --- a/src/org/infinity/resource/cre/viewer/CreatureAnimationModel.java +++ b/src/org/infinity/resource/cre/viewer/CreatureAnimationModel.java @@ -20,7 +20,7 @@ import org.infinity.util.IdsMapEntry; /** - * {@code ComboBoxModel} for the creature animation combo box used in the Creature Animation Viewer. + * {@code ComboBoxModel} for the creature animation combo box used in the Creature Animation Browser. */ public class CreatureAnimationModel extends AbstractListModel implements MutableComboBoxModel diff --git a/src/org/infinity/resource/cre/viewer/CreatureSelectionModel.java b/src/org/infinity/resource/cre/viewer/CreatureSelectionModel.java index 417a231ee..2b2e08b96 100644 --- a/src/org/infinity/resource/cre/viewer/CreatureSelectionModel.java +++ b/src/org/infinity/resource/cre/viewer/CreatureSelectionModel.java @@ -27,7 +27,7 @@ import org.infinity.util.tuples.Couple; /** - * {@code ComboBoxModel} for the creature selection combo box used in the Creature Animation Viewer. + * {@code ComboBoxModel} for the creature selection combo box used in the Creature Animation Browser. */ public class CreatureSelectionModel extends AbstractListModel implements ComboBoxModel diff --git a/src/org/infinity/resource/cre/viewer/CreatureViewer.java b/src/org/infinity/resource/cre/viewer/CreatureViewer.java index 873feac76..e382a6769 100644 --- a/src/org/infinity/resource/cre/viewer/CreatureViewer.java +++ b/src/org/infinity/resource/cre/viewer/CreatureViewer.java @@ -153,7 +153,7 @@ private void init() add(mainCentralPanel, BorderLayout.CENTER); setIconImage(Icons.getImage(Icons.ICON_CRE_VIEWER_24)); - setTitle("Creature Animation Viewer"); + setTitle("Creature Animation Browser"); setSize(NearInfinity.getInstance().getPreferredSize()); Center.center(this, NearInfinity.getInstance().getBounds()); setExtendedState(NearInfinity.getInstance().getExtendedState() & ~ICONIFIED); diff --git a/src/org/infinity/resource/cre/viewer/ItemSelectionModel.java b/src/org/infinity/resource/cre/viewer/ItemSelectionModel.java index caa8ff636..578ec6d70 100644 --- a/src/org/infinity/resource/cre/viewer/ItemSelectionModel.java +++ b/src/org/infinity/resource/cre/viewer/ItemSelectionModel.java @@ -16,7 +16,7 @@ import org.infinity.resource.key.ResourceEntry; /** - * {@code ComboBoxModel} for item selection combo boxes used in the Creature Animation Viewer. + * {@code ComboBoxModel} for item selection combo boxes used in the Creature Animation Browser. * The model allows to filter items by custom criteria. */ public class ItemSelectionModel extends AbstractListModel implements ComboBoxModel diff --git a/src/org/infinity/resource/cre/viewer/bg/Backgrounds.java b/src/org/infinity/resource/cre/viewer/bg/Backgrounds.java index 36b9d85f4..30c464a6f 100644 --- a/src/org/infinity/resource/cre/viewer/bg/Backgrounds.java +++ b/src/org/infinity/resource/cre/viewer/bg/Backgrounds.java @@ -20,7 +20,7 @@ import org.infinity.resource.Profile; /** - * Backgrounds for the creature animation viewer. + * Backgrounds for the creature animation browser. */ public final class Backgrounds { From 175d81a8478d031e54858f441190a081498bfd51 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sat, 20 Mar 2021 15:06:00 +0100 Subject: [PATCH 066/158] SpriteDecoder: Fix false color replacement of offhand weapons --- .../resource/cre/decoder/SpriteDecoder.java | 20 +++ .../cre/decoder/internal/ItemInfo.java | 119 ++++++++++++++++++ 2 files changed, 139 insertions(+) diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index 052bae2e5..931eee5fa 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -37,11 +37,13 @@ import org.infinity.resource.Profile; import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.ColorInfo; import org.infinity.resource.cre.decoder.internal.CreatureInfo; import org.infinity.resource.cre.decoder.internal.CycleDef; import org.infinity.resource.cre.decoder.internal.DecoderAttribute; import org.infinity.resource.cre.decoder.internal.DirDef; import org.infinity.resource.cre.decoder.internal.FrameInfo; +import org.infinity.resource.cre.decoder.internal.ItemInfo; import org.infinity.resource.cre.decoder.internal.SegmentDef; import org.infinity.resource.cre.decoder.internal.SeqDef; import org.infinity.resource.cre.decoder.tables.SpriteTables; @@ -1961,6 +1963,24 @@ protected void applyFalseColors(BamV1Control control, SegmentDef sd) } } + // Special: Off-hand weapon uses weapon colors + if (sd.getSpriteType() == SegmentDef.SpriteType.SHIELD) { + ItemInfo itemInfo = getCreatureInfo().getEquippedShield(); + if (itemInfo != null && itemInfo.getSlotType() == ItemInfo.SlotType.WEAPON) { + ColorInfo colorInfo = itemInfo.getColorInfo(); + for (int loc = 0; loc < 7; loc++) { + int ofs = getColorOffset(loc); + int colIdx = colorInfo.getValue(SegmentDef.SpriteType.WEAPON, loc); + if (ofs > 0 && colIdx >= 0) { + int[] range = getColorData(colIdx, false); + if (range != null) { + colorRanges.put(ofs, range); + } + } + } + } + } + // applying colors int[] palette = control.getCurrentPalette(); for (final Integer ofs : colorRanges.keySet()) { diff --git a/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java b/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java index 03ab04c72..315334e8c 100644 --- a/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java +++ b/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java @@ -30,6 +30,40 @@ */ public class ItemInfo implements Comparable { + /** Generalized categories based on equipment slots. */ + public enum SlotType { + /** Helmet slot (PST: Right earring/lens/helmet) */ + HELMET, + /** Armor slot */ + ARMOR, + /** Shield slot (PST: left tattoo) */ + SHIELD, + /** Gloves slot (PST: hand) */ + GLOVES, + /** Left/right ring slot */ + RING, + /** Amulet slot (PST: Left earring/eyeball) */ + AMULET, + /** Belt slot (PST: Right lower tattoo) */ + BELT, + /** Boots slot */ + BOOTS, + /** Weapon slots */ + WEAPON, + /** Quiver slots */ + QUIVER, + /** Cloak slot (PST: Right upper tattoo) */ + CLOAK, + /** (PST only) Covers the following slots: {@code SHIELD}, {@code AMULET}, {@code CLOAK} */ + TATTOO, + /** (PST only) Covers the following slots: {@code HELMET}, {@code AMULET} */ + EARRING, + /** Quick item slot */ + QUICK_ITEM, + /** Generic inventory slot (default slot if none other are matching) */ + INVENTORY, + } + /** * This predicate simply returns {@code false} for all items. */ @@ -510,6 +544,91 @@ private ItemInfo(ResourceEntry itmEntry) throws Exception /** Returns a sequential {@link Stream} of the {@code EffectInfo} list. */ public Stream getEffectStream() { return effectsInfo.stream(); } + /** Returns the most suitable item slot type compatible with the current item. */ + public SlotType getSlotType() + { + return getSlotType(getCategory()); + } + + /** Returns the most suitable item slot type compatible with the specified item category. */ + public static SlotType getSlotType(int category) + { + switch (category) { + case 7: // headgear + case 40: // lenses + case 72: // hats + return SlotType.HELMET; + case 2: // armor + case 60: // leather armor + case 61: // studded leather + case 62: // chain mail + case 63: // splint mail + case 64: // plate mail + case 65: // full plate + case 66: // hide armor + case 67: // robes + case 68: // scale mail + return SlotType.ARMOR; + case 12: // shields + case 41: // bucklers + case 47: // large shields + case 49: // medium shields + case 53: // small shields + return SlotType.SHIELD; + case 6: // bracers/gauntlets + case 70: // scarves + case 73: // gloves + case 77: // bracelets + return SlotType.GLOVES; + case 10: // rings + return SlotType.RING; + case 1: // amulets + case 74: // eyeballs + return SlotType.AMULET; + case 3: // belts + return SlotType.BELT; + case 4: // boots + return SlotType.BOOTS; + case 15: // bows + case 16: // daggers + case 17: // maces + case 18: // slings + case 19: // small swords + case 20: // large swords + case 21: // hammers + case 22: // morning stars + case 23: // flails + case 24: // darts + case 25: // axes + case 26: // quarterstaves + case 27: // crossbows + case 28: // hand-to-hand weapons + case 29: // spears + case 30: // halberds + case 44: // clubs + case 57: // greatswords + case 69: // bastard swords + case 76: // teeth + return SlotType.WEAPON; + case 5: // arrows + case 14: // bullets + case 31: // bolts + return SlotType.QUIVER; + case 32: // cloaks and robes + return SlotType.CLOAK; + case 39: // tattoos + return SlotType.TATTOO; + case 9: // potions + case 11: // scrolls + case 35: // wands + return SlotType.QUICK_ITEM; + case 75: // earrings + return SlotType.EARRING; + default: + return SlotType.INVENTORY; + } + } + /** Invoked for {@code null} items. */ private void initDefault() { From 0287af68b8171fe022b214cc8b82b3c80445547a Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sat, 20 Mar 2021 17:21:51 +0100 Subject: [PATCH 067/158] SpriteDecoder: Shadows in PST are not semi-transparent --- .../cre/decoder/MonsterPlanescapeDecoder.java | 3 ++- .../resource/cre/decoder/SpriteDecoder.java | 19 ++++++++++++++++++- .../resource/cre/decoder/SpriteUtils.java | 5 +++-- .../resource/cre/viewer/MediaPanel.java | 2 +- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java index 04400b9d6..92e1ae2ad 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java @@ -67,7 +67,7 @@ public void accept(BamV1Control control, SegmentDef sd) // "Pillar of Skulls" uses separate shadow animation if (!resref.equalsIgnoreCase("POSMAIN")) { - SpriteUtils.fixShadowColor(control); + SpriteUtils.fixShadowColor(control, isTransparentShadow()); } if (isPaletteReplacementEnabled() && isFalseColor()) { @@ -279,6 +279,7 @@ public MonsterPlanescapeDecoder(int animationId, IniMap ini) throws Exception public MonsterPlanescapeDecoder(CreResource cre) throws Exception { super(ANIMATION_TYPE, cre); + setTransparentShadow(false); } /** Returns the bestiary entry index. */ diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index 931eee5fa..9a4af1a9a 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -639,7 +639,7 @@ public void accept(BamV1Control control, SegmentDef sd) } } - SpriteUtils.fixShadowColor(control); + SpriteUtils.fixShadowColor(control, isTransparentShadow()); if (isPaletteReplacementEnabled() && isFalseColor()) { applyFalseColors(control, sd); @@ -704,6 +704,7 @@ public void accept(DirDef dd, int frameIdx) private boolean showCircle; private boolean showPersonalSpace; private boolean showBoundingBox; + private boolean transparentShadow; private boolean translucencyEnabled; private boolean paletteReplacementEnabled; private boolean renderSpriteAvatar; @@ -819,6 +820,7 @@ protected SpriteDecoder(AnimationType type, CreResource cre) throws Exception this.showCircle = false; this.showPersonalSpace = false; this.showBoundingBox = false; + this.transparentShadow = true; this.translucencyEnabled = true; this.paletteReplacementEnabled = true; this.renderSpriteAvatar = true; @@ -1908,6 +1910,21 @@ protected void drawSelectionCircle(Graphics2D g, Point center, Color color, floa } } + /** Returns whether creature shadow is semi-transparent. */ + protected boolean isTransparentShadow() + { + return transparentShadow; + } + + /** Sets whether creature shadow is semi-transparent. */ + protected void setTransparentShadow(boolean b) + { + if (transparentShadow != b) { + transparentShadow = b; + spriteChanged(); + } + } + /** * Translates the specified color location index into a palette color offset. * @param locationIndex the location to translate. diff --git a/src/org/infinity/resource/cre/decoder/SpriteUtils.java b/src/org/infinity/resource/cre/decoder/SpriteUtils.java index 2834d1d8f..84ff7c109 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteUtils.java +++ b/src/org/infinity/resource/cre/decoder/SpriteUtils.java @@ -473,13 +473,14 @@ public static boolean bamHasFalseColors(ResourceEntry entry) /** * Ensures that "transparent" and "shadow" palette entries of the animation are properly set. * @param control the BAM controller associated with the animation. + * @param isTransparentShadow indicates whether shadow color is semi-transparent */ - public static void fixShadowColor(BamV1Control control) + public static void fixShadowColor(BamV1Control control, boolean isTransparentShadow) { if (control != null) { int[] palette = control.getCurrentPalette(); palette[0] = 0x0000FF00; - palette[1] = 0x80000000; + palette[1] = isTransparentShadow ? 0x80000000 : 0xFF000000; control.setExternalPalette(palette); } } diff --git a/src/org/infinity/resource/cre/viewer/MediaPanel.java b/src/org/infinity/resource/cre/viewer/MediaPanel.java index e119e0bbb..8c953df97 100644 --- a/src/org/infinity/resource/cre/viewer/MediaPanel.java +++ b/src/org/infinity/resource/cre/viewer/MediaPanel.java @@ -520,7 +520,7 @@ private void init() l1 = new JLabel("Sequence:"); modelSequences = new DefaultComboBoxModel<>(); cbSequences = new JComboBox<>(modelSequences); - cbSequences.setPrototypeDisplayValue(SpriteDecoder.Sequence.ATTACK_BACKSLASH_1H); + cbSequences.setPrototypeDisplayValue(SpriteDecoder.Sequence.ATTACK_2WEAPONS1); cbSequences.addItemListener(listeners); cbLoop = new JCheckBox("Loop", isLoop); From 77ff9eb7d484f934f92c06daebe58c22a4002979 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sat, 20 Mar 2021 20:55:35 +0100 Subject: [PATCH 068/158] SpriteDecoder: Add option to use ornate selection circle graphics from PST --- .../resource/cre/decoder/SpriteDecoder.java | 96 ++++++++++++++---- .../resource/cre/viewer/MediaPanel.java | 1 + .../resource/cre/viewer/SettingsPanel.java | 48 ++++++--- .../resource/cre/viewer/icon/Icons.java | 7 +- .../resource/cre/viewer/icon/circle_blue.png | Bin 0 -> 5120 bytes .../resource/cre/viewer/icon/circle_green.png | Bin 0 -> 5824 bytes .../resource/cre/viewer/icon/circle_red.png | Bin 0 -> 5193 bytes .../cre/viewer/icon/circle_yellow.png | Bin 0 -> 5289 bytes 8 files changed, 119 insertions(+), 33 deletions(-) create mode 100644 src/org/infinity/resource/cre/viewer/icon/circle_blue.png create mode 100644 src/org/infinity/resource/cre/viewer/icon/circle_green.png create mode 100644 src/org/infinity/resource/cre/viewer/icon/circle_red.png create mode 100644 src/org/infinity/resource/cre/viewer/icon/circle_yellow.png diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index 9a4af1a9a..cb6823326 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -10,6 +10,7 @@ import java.awt.Composite; import java.awt.Dimension; import java.awt.Graphics2D; +import java.awt.Image; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; @@ -47,6 +48,7 @@ import org.infinity.resource.cre.decoder.internal.SegmentDef; import org.infinity.resource.cre.decoder.internal.SeqDef; import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.cre.viewer.icon.Icons; import org.infinity.resource.graphics.ColorConvert; import org.infinity.resource.graphics.PseudoBamDecoder; import org.infinity.resource.graphics.BamV1Decoder.BamV1Control; @@ -702,6 +704,7 @@ public void accept(DirDef dd, int frameIdx) private Sequence currentSequence; private boolean showCircle; + private boolean selectionCircleBitmap; private boolean showPersonalSpace; private boolean showBoundingBox; private boolean transparentShadow; @@ -818,6 +821,7 @@ protected SpriteDecoder(AnimationType type, CreResource cre) throws Exception this.ini = Objects.requireNonNull(getAnimationInfo(getAnimationId()), "No INI data available for animation id: " + getAnimationId()); this.currentSequence = Sequence.NONE; this.showCircle = false; + this.selectionCircleBitmap = (Profile.getGame() == Profile.Game.PST) || (Profile.getGame() == Profile.Game.PSTEE); this.showPersonalSpace = false; this.showBoundingBox = false; this.transparentShadow = true; @@ -1180,6 +1184,27 @@ public void setPaletteReplacementEnabled(boolean b) } } + /** + * Returns {@code true} if a bitmap is used to render the selection circle. + * Returns {@code false} if a colored circle is drawn instead. + */ + public boolean isSelectionCircleBitmap() + { + return selectionCircleBitmap; + } + + /** + * Specify {@code true} if a bitmap should be used to render the selection circle. + * Specify {@code false} if a colored circle should be drawn instead. + */ + public void setSelectionCircleBitmap(boolean b) + { + if (selectionCircleBitmap != b) { + selectionCircleBitmap = b; + selectionCircleChanged(); + } + } + /** Returns the moving speed of the creature animation. */ public double getMoveScale() { @@ -1717,7 +1742,7 @@ protected int createFrame(FrameInfo[] sourceFrames, BeforeSourceFrame beforeSrcF if (isSelectionCircleEnabled()) { // Drawing selection circle - drawSelectionCircle(g, center, null, circleStrokeSize); + drawSelectionCircle(g, center, circleStrokeSize); } // drawing source frames to target image @@ -1860,6 +1885,9 @@ protected Dimension getSelectionCircleSize() { Dimension dim = new Dimension(); dim.width = Math.max(0, getEllipse()); + if (isSelectionCircleBitmap()) { + dim.width += 4; // compensation for missing stroke size + } dim.height = dim.width * 4 / 7; // ratio 1.75 if (dim.height % 7 > 3) { // rounding up @@ -1868,15 +1896,13 @@ protected Dimension getSelectionCircleSize() return dim; } - /** Determines a circle stroke size relative to the circle size. Empty circles have no stroke size. */ + /** Determines a circle stroke size relative to the circle size. Empty circles or bitmap circles have no stroke size. */ protected float getSelectionCircleStrokeSize() { - float circleStrokeSize; - if (getEllipse() > 0) { + float circleStrokeSize = 0.0f; + if (!isSelectionCircleBitmap() && getEllipse() > 0) { // thickness relative to circle size circleStrokeSize = Math.max(1.0f, (float)(Math.floor(Math.sqrt(getEllipse()) / 2.0))); - } else { - circleStrokeSize = 0.0f; } return circleStrokeSize; @@ -1889,24 +1915,28 @@ protected float getSelectionCircleStrokeSize() * @param color the circle color. Specify {@code null} to use global defaults. * @param strokeSize the thickness of the selection circle. */ - protected void drawSelectionCircle(Graphics2D g, Point center, Color color, float strokeSize) + protected void drawSelectionCircle(Graphics2D g, Point center, float strokeSize) { if (g != null) { Dimension dim = getSelectionCircleSize(); - if (color == null) { - if (getCreatureInfo().isStatusPanic()) { - // panic - color = getAllegianceColor(-1); - } else { - color = getAllegianceColor(getCreatureInfo().getAllegiance()); - } + + if (isSelectionCircleBitmap()) { + Image image = getCreatureInfo().isStatusPanic() ? getAllegianceImage(-1) + : getAllegianceImage(getCreatureInfo().getAllegiance()); + Object oldHints = g.getRenderingHint(RenderingHints.KEY_INTERPOLATION); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); + g.drawImage(image, center.x - dim.width, center.y - dim.height, 2 * dim.width, 2 * dim.height, null); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, (oldHints != null) ? oldHints : RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); + } else { + Color color = getCreatureInfo().isStatusPanic() ? getAllegianceColor(-1) + : getAllegianceColor(getCreatureInfo().getAllegiance()); + Object oldHints = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setColor(color); + g.setStroke(new BasicStroke(strokeSize)); + g.drawOval(center.x - dim.width, center.y - dim.height, 2 * dim.width, 2 * dim.height); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, (oldHints != null) ? oldHints : RenderingHints.VALUE_ANTIALIAS_DEFAULT); } - Object oldHints = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - g.setColor(color); - g.setStroke(new BasicStroke(strokeSize)); - g.drawOval(center.x - dim.width, center.y - dim.height, 2 * dim.width, 2 * dim.height); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, (oldHints != null) ? oldHints : RenderingHints.VALUE_ANTIALIAS_DEFAULT); } } @@ -2180,6 +2210,32 @@ protected static Color getAllegianceColor(int value) return c; } + /** + * Determines the right selection circle bitmap based on the specified allegiance value and returns it + * as {@code Image} object. A negative value will enable the "panic" bitmap. + * @param value numeric allegiance value. Specify a negative value to override allegiance by the "panic" status. + * @return + */ + protected static Image getAllegianceImage(int value) + { + Image retVal = null; + if (value < 0) { + // treat as panic + retVal = Icons.getImage(Icons.ICON_CIRCLE_YELLOW); + } else if (value >= 2 && value <= 4 || value == 201) { + // ally + retVal = Icons.getImage(Icons.ICON_CIRCLE_GREEN); + } else if (value == 255 || value == 254 || value == 28 || value == 6 || value == 5) { + // enemy + retVal = Icons.getImage(Icons.ICON_CIRCLE_RED); + } else { + // neutral + retVal = Icons.getImage(Icons.ICON_CIRCLE_BLUE); + } + + return retVal; + } + /** * A helper method that parses the specified data array and generates a list of INI lines * related to the "general" section. diff --git a/src/org/infinity/resource/cre/viewer/MediaPanel.java b/src/org/infinity/resource/cre/viewer/MediaPanel.java index 8c953df97..daf2f044b 100644 --- a/src/org/infinity/resource/cre/viewer/MediaPanel.java +++ b/src/org/infinity/resource/cre/viewer/MediaPanel.java @@ -121,6 +121,7 @@ public void reset(boolean preserveState) decoder.setPaletteReplacementEnabled(settings.isPaletteReplacementEnabled()); decoder.setTranslucencyEnabled(settings.isTranslucencyEnabled()); decoder.setSelectionCircleEnabled(settings.isSelectionCircleEnabled()); + decoder.setSelectionCircleBitmap(settings.isOrnateSelectionCircle()); decoder.setPersonalSpaceVisible(settings.isPersonalSpaceEnabled()); decoder.setRenderAvatar(settings.isAvatarVisible()); decoder.setRenderHelmet(settings.isHelmetVisible()); diff --git a/src/org/infinity/resource/cre/viewer/SettingsPanel.java b/src/org/infinity/resource/cre/viewer/SettingsPanel.java index a6bc7f079..22551a85c 100644 --- a/src/org/infinity/resource/cre/viewer/SettingsPanel.java +++ b/src/org/infinity/resource/cre/viewer/SettingsPanel.java @@ -55,7 +55,7 @@ public class SettingsPanel extends JPanel }}; private static int indexZoom, indexFrameRate, indexBackground; - private static boolean isFiltering, isBlending, isTranslucent, isSelectionCircle, isPersonalSpace, + private static boolean isFiltering, isBlending, isTranslucent, isSelectionCircle, isOrnateSelectionCircle, isPersonalSpace, isPaletteReplacementEnabled, isShowAvatar, isShowHelmet, isShowShield, isShowWeapon, isShowBorders; @@ -67,6 +67,7 @@ public class SettingsPanel extends JPanel isBlending = true; isTranslucent = true; isSelectionCircle = false; + isOrnateSelectionCircle = (Profile.getGame() == Profile.Game.PST) || (Profile.getGame() == Profile.Game.PSTEE); isPersonalSpace = false; isShowAvatar = true; isShowHelmet = true; @@ -83,7 +84,7 @@ public class SettingsPanel extends JPanel private JComboBox> cbFrameRate; private JComboBox cbBackground; private JButton bCenter; - private JCheckBox cbFiltering, cbBlending, cbTranslucent, cbSelectionCircle, cbPersonalSpace, + private JCheckBox cbFiltering, cbBlending, cbTranslucent, cbSelectionCircle, cbOrnateSelectionCircle, cbPersonalSpace, cbPaletteReplacementEnabled, cbShowAvatar, cbShowHelmet, cbShowShield, cbShowWeapon, cbShowBorders; private AttributesPanel panelAttributes; @@ -228,6 +229,18 @@ private void setSelectionCircleEnabled(boolean b) } } + /** Returns whether an ornate graphics is used to draw the selection circle. */ + public boolean isOrnateSelectionCircle() { return isOrnateSelectionCircle; } + + private void setOrnateSelectionCircle(boolean b) + { + if (isOrnateSelectionCircle != b) { + isOrnateSelectionCircle = b; + cbOrnateSelectionCircle.setSelected(isOrnateSelectionCircle); + getViewer().getMediaPanel().reset(true); + } + } + /** Returns whether personal space is visible. */ public boolean isPersonalSpaceEnabled() { return isPersonalSpace; } @@ -381,6 +394,7 @@ private void init() // checkbox controls cbFiltering = new JCheckBox("Enable filtering", isFiltering); + cbFiltering.setToolTipText("On: bilinear filtering, off: nearest neighbor filtering"); cbFiltering.addActionListener(listeners); cbBlending = new JCheckBox("Enable blending", isBlending); @@ -391,23 +405,30 @@ private void init() cbTranslucent.setToolTipText("Affects only creature animations with translucency effect (e.g. ghosts or air elementals)"); cbTranslucent.addActionListener(listeners); - cbPaletteReplacementEnabled = new JCheckBox("Palette replacement", isPaletteReplacementEnabled); + cbPaletteReplacementEnabled = new JCheckBox("Enable palette replacement", isPaletteReplacementEnabled); cbPaletteReplacementEnabled.setToolTipText("Enable full palette or false color palette replacement."); cbPaletteReplacementEnabled.addActionListener(listeners); cbSelectionCircle = new JCheckBox("Show selection circle", isSelectionCircle); cbSelectionCircle.addActionListener(listeners); + cbOrnateSelectionCircle = new JCheckBox("Use ornate selection circle", isOrnateSelectionCircle); + cbOrnateSelectionCircle.setToolTipText("Enable to use the ornate selection circle graphics from PST."); + cbOrnateSelectionCircle.addActionListener(listeners); + cbPersonalSpace = new JCheckBox("Show personal space", isPersonalSpace); + cbPersonalSpace.setToolTipText("Enable to visualize the search map cells blocked by the creature."); cbPersonalSpace.addActionListener(listeners); cbShowAvatar = new JCheckBox("Show avatar overlay", isShowAvatar); + cbShowAvatar.setToolTipText("Includes the avatar, separate shadows and ground layers of buried creatures."); cbShowAvatar.addActionListener(listeners); cbShowHelmet = new JCheckBox("Show helmet overlay", isShowHelmet); cbShowHelmet.addActionListener(listeners); cbShowShield = new JCheckBox("Show shield overlay", isShowShield); + cbShowShield.setToolTipText("Includes shields and offhand weapons."); cbShowShield.addActionListener(listeners); cbShowWeapon = new JCheckBox("Show weapon overlay", isShowWeapon); @@ -427,38 +448,38 @@ private void init() c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 0, 0, 0), 0, 0); - panel2.add(cbTranslucent, c); + panel2.add(cbSelectionCircle, c); c = ViewerUtil.setGBC(c, 1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 16, 0, 0), 0, 0); - panel2.add(cbPaletteReplacementEnabled, c); + panel2.add(cbShowAvatar, c); c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 0, 0, 0), 0, 0); - panel2.add(cbSelectionCircle, c); + panel2.add(cbOrnateSelectionCircle, c); c = ViewerUtil.setGBC(c, 1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 16, 0, 0), 0, 0); - panel2.add(cbPersonalSpace, c); + panel2.add(cbShowHelmet, c); c = ViewerUtil.setGBC(c, 0, 3, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 0, 0, 0), 0, 0); - panel2.add(cbShowAvatar, c); + panel2.add(cbPersonalSpace, c); c = ViewerUtil.setGBC(c, 1, 3, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 16, 0, 0), 0, 0); - panel2.add(cbShowHelmet, c); + panel2.add(cbShowWeapon, c); c = ViewerUtil.setGBC(c, 0, 4, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 0, 0, 0), 0, 0); - panel2.add(cbShowWeapon, c); + panel2.add(cbTranslucent, c); c = ViewerUtil.setGBC(c, 1, 4, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 16, 0, 0), 0, 0); panel2.add(cbShowShield, c); c = ViewerUtil.setGBC(c, 0, 5, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 0, 0, 0), 0, 0); - panel2.add(cbShowBorders, c); + panel2.add(cbPaletteReplacementEnabled, c); c = ViewerUtil.setGBC(c, 1, 5, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 16, 0, 0), 0, 0); - panel2.add(new JPanel(), c); + panel2.add(cbShowBorders, c); // attributes table panel @@ -526,6 +547,9 @@ else if (e.getSource() == cbPaletteReplacementEnabled) { else if (e.getSource() == cbSelectionCircle) { setSelectionCircleEnabled(cbSelectionCircle.isSelected()); } + else if (e.getSource() == cbOrnateSelectionCircle) { + setOrnateSelectionCircle(cbOrnateSelectionCircle.isSelected()); + } else if (e.getSource() == cbPersonalSpace) { setPersonalSpaceEnabled(cbPersonalSpace.isSelected()); } diff --git a/src/org/infinity/resource/cre/viewer/icon/Icons.java b/src/org/infinity/resource/cre/viewer/icon/Icons.java index 466c3c5c5..d4a7152c7 100644 --- a/src/org/infinity/resource/cre/viewer/icon/Icons.java +++ b/src/org/infinity/resource/cre/viewer/icon/Icons.java @@ -23,7 +23,12 @@ public class Icons public static final String ICON_STOP = "btn_stop.png"; public static final String ICON_CENTER = "btn_center.png"; - private static final Map ICONMAP = new HashMap(12); + public static final String ICON_CIRCLE_GREEN = "circle_green.png"; + public static final String ICON_CIRCLE_BLUE = "circle_blue.png"; + public static final String ICON_CIRCLE_RED = "circle_red.png"; + public static final String ICON_CIRCLE_YELLOW = "circle_yellow.png"; + + private static final Map ICONMAP = new HashMap(20); /** * Returns an ImageIcon object of the specified graphics filename. diff --git a/src/org/infinity/resource/cre/viewer/icon/circle_blue.png b/src/org/infinity/resource/cre/viewer/icon/circle_blue.png new file mode 100644 index 0000000000000000000000000000000000000000..607414cc0c0a34c2c79b9f6e67801694ae589e53 GIT binary patch literal 5120 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 literal 0 HcmV?d00001 diff --git a/src/org/infinity/resource/cre/viewer/icon/circle_red.png b/src/org/infinity/resource/cre/viewer/icon/circle_red.png new file mode 100644 index 0000000000000000000000000000000000000000..dd5c5314fa02a013d9d9622798d101756ef2ff32 GIT binary patch literal 5193 zcmW+)1yB^u7hd{k4v+>xx;q5q=vEqOj*#w)Z>Ags^)@W7MD0N}8KNGiY&fnfnLW*~|Wo}w5KL0Fi` zMMMA~K?8IUz$B`V1AsIhAfW>s_^!ABaN`3~DBxAXQE}?11pt>6*gzFv0E>kVFoSJ! z6exwb5D4`_Z4tc*10bL^*b4wgehExK@d)r?0wov#*m%-dKv4z&4p<@q5W+-}wBki^ z+vWypM9ee*sCxhkX22w3tP2Eq0Q3Kih2sM+XF!V%fVd&BfC6(;aSZ%RKLqj+s#p>;y9bV` ztE=`uVVFW98HZlKAw|yp{K0ySJk$>4w#Z<9`!V}mzg`e|E`R1ec%<|K$-I7fvhX+E z{fFYg>KYum#!-2}d1%o~M~U40_t~6y=HI}{rzUIi`&F~;Dr6c*9v>2Ui6ku(xVhK* zz8rmv-2b~U(ts44ANqE0xc+ITV`vBY@9gws5{ViYBMYO*rM)90as#=5L~ecS>F)V; zT$77j3$*U7L((r!QQEYl+MeXGnxdS(zeT4Bp+5P`tS+B25oY?D&1N#3^;CEnx{j0} z+uodQl=4$ggw^Y`IyJg^L(fv3*!k2v&3!#dW~OT4oB{e1TOR?k=X2+3vEC$QjkM4P zRmF&p2{0%o8BMfL!p964Z_rV-(?_?wm5BRfU4zdn)j@m9cZZ!vU6i07lQ^bf4ITtr%^?(tHlBN?sEh^sc=zoqgM;xTt`@ zEi^ILqU8Qtk*%=bd|0^DY{{_r1p5`^UHr8^T3!ht0(zT<2qwnZR49*sj_4L+tS+TV~E$H z-?RRED_Lxwg;%7gG@l~lU=VmM-|7*P@{x}w?*JW|jm--8<$61lEyz*%_B8zC(ftdE1wD;!v z7U@y~!`2;dwLP867N?Xdp6IHpm=oOa|LH~~a%6eF+l_|nl$^~IRoGG8`ApI5zK9}+_?Ec;GSK^WWcKfjf>nuR(+;vdJZ6+K}{k?P3L+630e z{jyfmQ(TOv5}zd%uRhrCTH+G=1cwkHLOnzUC=mkPWrG@}XXsvsFdH11@)o97cC4dP z7UGVt9R5a^u@f}&b>yeNROXqX8&(}RB(mPFjtsdoU)RvNE!>{){KZ&M~*OgW!mIM3-K>!KPQJrVEz8;GLfJ;B9iVi9BB}DR`jg+ zG;odir9z?RJ7g=9c=N5Ck%0te?v-2y7K49Qa4dynety1G;b@^n(t2lL05wyXfJR<` zB@FQCkPD8_W0mBUJqmI0D;a|$9s5%%p$6}O-OROKgZLa3$-b&F$Kf!s3o^;<$ z^qM4T&KK=2-xku$kgKJ6{$1&_m#>f#7YiVKoqTI_B7cYZ{z1H>WBze2NH%709=3P~Z;^*c%AmDQBOFY31$}*HkYhEj~ zmwWJQ>=(u)jMSX2dq+W|%b|{KFQatAd0n4%#$YUuz5hE{ffmc~`MS%=SELm)$o$A< zU$8IQa`I}6B*ezSX*l8`aI;}2I>l<1Pc@j`0jf&a$=kXV4Q>TT@UfJ%&KJyrtO(_I!a2}&mZQ~iCRjWt1?$4 zeRu?aq7RYXQ54)HWR$o&Ok*?5H&w%xt`2wujAzq6Xn327^|f7n*D2i-TvMEA-VgD` zX)2vwA&j+$tE$Ee-w|)!NARmVj+#lW(r@crN_b4LD2xwJ+WP!b8%uj!*P(J=SPu55 zy&mF~^Qu^`F{ZVe!zLQo)OKO|EG8yDX^5^^cH$no?sz2Vir*Gta`4-Vr{VFygiB_J zcY5YxwUQptGBst=QuisH+PVZGeslfWo*`2^q(@oN4{kwwY~Ql=rlX=x1z-YAbnorh zB3SwBzEq}H6l{*T#%D}L(S})UAVdVx3f{}`wtD=%$x`A#7ssW+HObZ%Rtujs2VVKK zZq{oj_m-cz&6{w{ku*568)_>Q<-&W*JKl}$YX((G0c^ec?{h7g)QaKkcZFlU?W4Ef zAgu_mgU9j8r=#e67hlQ42YM8WqA}xl%(RXsWA`=u6E%3Imytn5f&w=za|b*Gr7|x# zuoH3FrXMFJs*bxo?Wck){LJzk&==(i8fbs`$c*Kcl*8I^cVMs{*YNTYV$5zcdRQXF z`-_h9s$(t9Vo`L+%_TjT$5_^4G=&3iKTKqPQQVJ#^%&pRn`g(#H>ZFVVKZ(caC^Yg zu0I1fsXY;=pRbeYSO<)~oR8%l+w_kFV4~uT7QxT0fh|Mc5^HSAvUV~puaS>gAk~=f zde4t|s?BOLbob1F-!+O1>Gt;CG^76O&AH5w9*hX_;L_;?C&$dSh)$1C+1+|u?W*tw zK|($sW|iECQAKDv&uHolDSjH!ZV|*KRSNQje6Qe%#K$wWZv6Rfe4<~|K{O5B)qO0P z)kzhL8KUBtbn03afobJ;?Y->w204g?5bPB6^;^|ThL%Pf1vLjFg7-11E!y-3^wD@6 z%(rjd#S7X)Ypd;e7Eh!(zr1|yyHxLYe^Ys@16oGlo)B1W zKd)1Ki)H>vl6P)kSMW?$X82zs5>MnR{TG?{&pzWQ$CIpq79|~ChIBd&@}j^)EMJ~6 zjG0~&PIg7_i9rwTraAi^N$fwu<3>|6lkiOE?xMlUM+GLL5N zq9$e*r5l4+;_%S=`)_iH3!0)+2x(X}_k?I8I13$wfN)Zf)s!h>h5Ww(D&i zwo&{ej&at?4V9+oBH0NQzk*J`r_8q?a9RAmk*Q>rep43SQmrde?f~4(^0YhWWxt953@hrKcy z3DK#lnegG-cfI#!xk-#m#v8i2>THZP1bGRU&vI7Prj%*Bo&Kz`QrJRgjFD6jUPPq? z!6ymx$8P`D0^Qz4o;s`&>wkXkC+g@Z9Bsg2Si-&OAj&3iTkB!-`^LDq*sRn@B=r9~&E4)kw25Y>Kvp-SE0EVnw9i6Jw5Z&& zC1w3Zhl_+&4MT&y-odEjWl_xVJF{MDsrBXlFYkI?YVURn$dk3y^hhMkinJ1NOnOu3 z*Mg?|O{z*sFm8@gAp*Sr`LvKA(&g{qIc68r=IrssSrl7+CGb?Tv^RUItOF65BX6qz zX+M=$UJYEylAAEuMxc{4Fiky~-AP@p(7o0ECL8k!BYgL~N_2?y_IP~n=l&IU783e8 zeR=i1=$&GBM136Bwkk|Gt|qKJW3(y5d;*e46Ki8<=08pqu3yL}bj$1iVp4>BU^zKC z@yulPw2hj|(UCaz8N~}YWhG-n*;}WJ;=!iIVMjXWnugf1qIVKL$0+S#wTdBYVXs1Qj02L2TyZc%;}^UNrggM&aI4v4_;O4QSw>x zIa}vpoI8GrkfkB%N=q0nN}Jf)iJ(*$>^fxc zQKgo~kwC+N9m4^?$q!84u>GAf4t%?ca$zZ!nwI&YX*I$O<%FZqBki~AYl**{s=^}c z^Ez|r<0?nm7ZED;-X_qu0gUc8IFxiHqy|gHhLtUU5LJh_>(x-N0IQadn$)nPOr8QK zE`u!fM>Hkz!@~0=Zt59*&p7bQ5Ec9WIylb-bypY1=wc8Y!P2J$^-lG1M`#%xdXks_ z;e8x&W)}1>P6eBgn5ps8vlBZT93eue+{@xSrr7Nd=P;JzM}F3=%-oZwzNy*oRGzc- zv%}&MpZ3>{i!_FRNgIy1wQM^OVNL9QeOip$Go|H1OR`6{LF>gw@Y{Acv1N^3zk@n_ zF~KN1xvx_?#b>i0Sl2YDGnoda765^9V1o1$=&5xCG^-#c}B`+dgSg_ozAgmEoni zV3yUBSGsH$yEG=c$KC(DI5HM;so5d(uw@ zV!SdI^fVs5uk8ExCOFJiC9g!#rxQgY20Fa@|(O@^uedW=CrYAlHm&f}$7N^6T3 zA^+XGdA^`6-ffuq^R@ZlyT;Vzy8~OH&G-1XOM!>kXa)Z)v%b06Bk)eAa!V@BlPSWc z&b?Ai2AQqI=cbZGQ%KvNk)=>{X;4mhc7+N63+u=`wCSHGC`H*K3 zEZHH>$+==gHkK@-s={LXT@C*a*;&z9bucxC5ZKpdHW1o2bR(TRkq?j3kwRen5c(_^ zu5@7MQ@KXZ3}x>Xo~-%mYQ{!OTaiDZNmvm4`?E54N{Rp#T5? literal 0 HcmV?d00001 diff --git a/src/org/infinity/resource/cre/viewer/icon/circle_yellow.png b/src/org/infinity/resource/cre/viewer/icon/circle_yellow.png new file mode 100644 index 0000000000000000000000000000000000000000..e476c85b42a9ec1898d32a8fb1dd782b1333ce21 GIT binary patch literal 5289 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 Date: Sun, 21 Mar 2021 11:33:40 +0100 Subject: [PATCH 069/158] Loosen type cast restrictions --- src/org/infinity/check/CreInvChecker.java | 3 +- .../infinity/check/EffectsIndexChecker.java | 7 +-- src/org/infinity/check/StructChecker.java | 32 +++++----- src/org/infinity/gui/StringEditor.java | 9 ++- .../infinity/resource/AbstractAbility.java | 11 ++-- src/org/infinity/resource/EffectFactory.java | 22 +++---- .../infinity/resource/are/AreResource.java | 14 ++--- src/org/infinity/resource/are/Container.java | 22 +++---- src/org/infinity/resource/are/Door.java | 44 ++++++------- src/org/infinity/resource/are/ITEPoint.java | 12 ++-- .../resource/are/viewer/LayerObject.java | 5 +- .../are/viewer/LayerObjectAutomap.java | 8 +-- .../resource/are/viewer/TilesetRenderer.java | 14 ++--- src/org/infinity/resource/chu/Control.java | 14 ++--- src/org/infinity/resource/chu/Viewer.java | 18 +++--- .../infinity/resource/cre/CreResource.java | 14 ++--- .../resource/cre/SpellMemorization.java | 12 ++-- .../infinity/resource/cre/ViewerItems.java | 3 +- .../cre/decoder/internal/CreatureInfo.java | 2 +- src/org/infinity/resource/dlg/DlgItem.java | 5 +- .../infinity/resource/dlg/DlgResource.java | 18 +++--- src/org/infinity/resource/dlg/State.java | 7 ++- src/org/infinity/resource/dlg/Transition.java | 7 ++- .../infinity/resource/itm/ItmResource.java | 15 ++--- .../infinity/resource/pro/ProAreaType.java | 5 +- .../infinity/resource/spl/SplResource.java | 14 ++--- src/org/infinity/resource/vertex/Vertex.java | 5 +- src/org/infinity/resource/wed/Door.java | 6 +- src/org/infinity/resource/wed/Polygon.java | 12 ++-- src/org/infinity/resource/wed/Tilemap.java | 3 +- src/org/infinity/resource/wed/Wallgroup.java | 5 +- .../infinity/resource/wed/WedResource.java | 7 ++- src/org/infinity/resource/wmp/AreaEntry.java | 17 +++--- src/org/infinity/resource/wmp/ViewerArea.java | 4 +- src/org/infinity/resource/wmp/ViewerMap.java | 61 +++++++++---------- .../infinity/resource/wmp/WmpResource.java | 4 +- src/org/infinity/search/AbstractSearcher.java | 2 + .../infinity/search/AttributeSearcher.java | 5 +- src/org/infinity/search/FileTypeSelector.java | 2 + .../search/advanced/AdvancedSearchWorker.java | 3 +- 40 files changed, 237 insertions(+), 236 deletions(-) diff --git a/src/org/infinity/check/CreInvChecker.java b/src/org/infinity/check/CreInvChecker.java index fd98c76c9..a47bb8657 100644 --- a/src/org/infinity/check/CreInvChecker.java +++ b/src/org/infinity/check/CreInvChecker.java @@ -25,6 +25,7 @@ import org.infinity.NearInfinity; import org.infinity.datatype.DecNumber; +import org.infinity.datatype.IsNumeric; import org.infinity.gui.BrowserMenuBar; import org.infinity.gui.Center; import org.infinity.gui.ChildFrame; @@ -199,7 +200,7 @@ private void checkCreature(CreResource cre) { final List items = new ArrayList<>(); final List slots = new ArrayList<>(); - final DecNumber slots_offset = (DecNumber)cre.getAttribute(CreResource.CRE_OFFSET_ITEM_SLOTS); + final IsNumeric slots_offset = (IsNumeric)cre.getAttribute(CreResource.CRE_OFFSET_ITEM_SLOTS); for (final StructEntry entry : cre.getFields()) { if (entry instanceof Item) { items.add((Item)entry); diff --git a/src/org/infinity/check/EffectsIndexChecker.java b/src/org/infinity/check/EffectsIndexChecker.java index ed7e48b93..0420c2380 100644 --- a/src/org/infinity/check/EffectsIndexChecker.java +++ b/src/org/infinity/check/EffectsIndexChecker.java @@ -5,8 +5,7 @@ package org.infinity.check; import org.infinity.NearInfinity; -import org.infinity.datatype.DecNumber; -import org.infinity.datatype.SectionCount; +import org.infinity.datatype.IsNumeric; import org.infinity.resource.AbstractAbility; import org.infinity.resource.AbstractStruct; import org.infinity.resource.Resource; @@ -56,12 +55,12 @@ protected Runnable newWorker(ResourceEntry entry) private void search(ResourceEntry entry, AbstractStruct struct) { - final int numGlobalEffects = ((SectionCount) struct.getAttribute(SplResource.SPL_NUM_GLOBAL_EFFECTS)).getValue(); + final int numGlobalEffects = ((IsNumeric) struct.getAttribute(SplResource.SPL_NUM_GLOBAL_EFFECTS)).getValue(); int expectedEffectsIndex = numGlobalEffects; for (final StructEntry e : struct.getFields()) { if (e instanceof AbstractAbility) { final AbstractAbility abil = (AbstractAbility) e; - final int effectsIndex = ((DecNumber) abil.getAttribute(AbstractAbility.ABILITY_FIRST_EFFECT_INDEX)).getValue(); + final int effectsIndex = ((IsNumeric) abil.getAttribute(AbstractAbility.ABILITY_FIRST_EFFECT_INDEX)).getValue(); if (effectsIndex != expectedEffectsIndex) { synchronized (hitFrame) { hitFrame.addHit(entry, entry.getSearchString(), abil); diff --git a/src/org/infinity/check/StructChecker.java b/src/org/infinity/check/StructChecker.java index be11dc7a7..b114cd597 100644 --- a/src/org/infinity/check/StructChecker.java +++ b/src/org/infinity/check/StructChecker.java @@ -25,10 +25,8 @@ import org.infinity.NearInfinity; import org.infinity.datatype.DecNumber; -import org.infinity.datatype.Flag; -import org.infinity.datatype.ResourceRef; -import org.infinity.datatype.SectionCount; -import org.infinity.datatype.SectionOffset; +import org.infinity.datatype.IsNumeric; +import org.infinity.datatype.IsReference; import org.infinity.datatype.TextString; import org.infinity.gui.BrowserMenuBar; import org.infinity.gui.Center; @@ -290,8 +288,8 @@ private List getWedCorruption(ResourceEntry entry, AbstractStruct st final List list = new ArrayList<>(); if (entry.getExtension().equalsIgnoreCase("WED")) { final int ovlSize = 0x18; // size of an Overlay structure - int ovlCount = ((SectionCount)struct.getAttribute(8, false)).getValue(); // # overlays - int ovlStartOfs = ((SectionOffset)struct.getAttribute(16, false)).getValue(); // Overlays offset + int ovlCount = ((IsNumeric)struct.getAttribute(8, false)).getValue(); // # overlays + int ovlStartOfs = ((IsNumeric)struct.getAttribute(16, false)).getValue(); // Overlays offset for (int ovlIdx = 0; ovlIdx < ovlCount; ovlIdx++) { int ovlOfs = ovlStartOfs + ovlIdx*ovlSize; @@ -299,11 +297,11 @@ private List getWedCorruption(ResourceEntry entry, AbstractStruct st if (overlay == null) { continue; } - int width = ((DecNumber)overlay.getAttribute(ovlOfs + 0, false)).getValue(); - int height = ((DecNumber)overlay.getAttribute(ovlOfs + 2, false)).getValue(); - String tisName = ((ResourceRef)overlay.getAttribute(ovlOfs + 4, false)).getResourceName(); - int tileStartOfs = ((SectionOffset)overlay.getAttribute(ovlOfs + 16, false)).getValue(); - int indexStartOfs = ((SectionOffset)overlay.getAttribute(ovlOfs + 20, false)).getValue(); + int width = ((IsNumeric)overlay.getAttribute(ovlOfs + 0, false)).getValue(); + int height = ((IsNumeric)overlay.getAttribute(ovlOfs + 2, false)).getValue(); + String tisName = ((IsReference)overlay.getAttribute(ovlOfs + 4, false)).getResourceName(); + int tileStartOfs = ((IsNumeric)overlay.getAttribute(ovlOfs + 16, false)).getValue(); + int indexStartOfs = ((IsNumeric)overlay.getAttribute(ovlOfs + 20, false)).getValue(); if (tisName == null || tisName.isEmpty() || !ResourceFactory.resourceExists(tisName)) { continue; } @@ -359,7 +357,7 @@ private List getWedCorruption(ResourceEntry entry, AbstractStruct st mapTiles.put(Integer.valueOf(index), (Tilemap)item); } else if (item.getOffset() > indexStartOfs && curOfs < indexEndOfs && item instanceof DecNumber) { int index = (curOfs - indexStartOfs) / 2; - mapIndices.put(Integer.valueOf(index), Integer.valueOf(((DecNumber)item).getValue())); + mapIndices.put(Integer.valueOf(index), Integer.valueOf(((IsNumeric)item).getValue())); } } // checking indices @@ -368,11 +366,11 @@ private List getWedCorruption(ResourceEntry entry, AbstractStruct st if (tile != null) { int tileOfs = tile.getOffset(); int tileIdx = (tileOfs - tileStartOfs) / tileSize; - int tileIdxPri = ((DecNumber)tile.getAttribute(tileOfs + 0, false)).getValue(); - int tileCountPri = ((DecNumber)tile.getAttribute(tileOfs + 2, false)).getValue(); - int tileIdxSec = ((DecNumber)tile.getAttribute(tileOfs + 4, false)).getValue(); - Flag tileFlag = (Flag)tile.getAttribute(tileOfs + 6, false); - int tileFlagValue = (int)tileFlag.getValue(); + int tileIdxPri = ((IsNumeric)tile.getAttribute(tileOfs + 0, false)).getValue(); + int tileCountPri = ((IsNumeric)tile.getAttribute(tileOfs + 2, false)).getValue(); + int tileIdxSec = ((IsNumeric)tile.getAttribute(tileOfs + 4, false)).getValue(); + IsNumeric tileFlag = (IsNumeric)tile.getAttribute(tileOfs + 6, false); + int tileFlagValue = tileFlag.getValue(); for (int j = tileIdxPri, count = tileIdxPri + tileCountPri; j < count; j++) { Integer tileLookupIndex = mapIndices.get(Integer.valueOf(j)); if (tileLookupIndex != null) { diff --git a/src/org/infinity/gui/StringEditor.java b/src/org/infinity/gui/StringEditor.java index feade47bd..728b6f426 100644 --- a/src/org/infinity/gui/StringEditor.java +++ b/src/org/infinity/gui/StringEditor.java @@ -47,9 +47,8 @@ import javax.swing.filechooser.FileNameExtensionFilter; import org.infinity.NearInfinity; -import org.infinity.datatype.DecNumber; import org.infinity.datatype.Editable; -import org.infinity.datatype.Flag; +import org.infinity.datatype.IsNumeric; import org.infinity.datatype.ResourceRef; import org.infinity.icon.Icons; import org.infinity.resource.Profile; @@ -615,14 +614,14 @@ private void updateTableItem(int row) if (cellName != null && cellValue != null && entry != null) { String name = cellName.toString(); if (StringEditor.TLK_FLAGS.equals(name)) { - entry.setFlags((short)((Flag)cellValue).getValue()); + entry.setFlags((short)((IsNumeric)cellValue).getValue()); } else if (StringEditor.TLK_SOUND.equals(name)) { ResourceRef ref = (ResourceRef)cellValue; entry.setSoundRef(ref.isEmpty() ? "" : ref.getText()); } else if (StringEditor.TLK_VOLUME.equals(name)) { - entry.setVolume(((DecNumber)cellValue).getValue()); + entry.setVolume(((IsNumeric)cellValue).getValue()); } else if (StringEditor.TLK_PITCH.equals(name)) { - entry.setPitch(((DecNumber)cellValue).getValue()); + entry.setPitch(((IsNumeric)cellValue).getValue()); } updateModifiedUI(getSelectedDialogType()); } diff --git a/src/org/infinity/resource/AbstractAbility.java b/src/org/infinity/resource/AbstractAbility.java index 0b86431d0..80563aaf6 100644 --- a/src/org/infinity/resource/AbstractAbility.java +++ b/src/org/infinity/resource/AbstractAbility.java @@ -9,8 +9,7 @@ import java.nio.ByteBuffer; import org.infinity.datatype.DecNumber; -import org.infinity.datatype.SectionCount; -import org.infinity.datatype.SectionOffset; +import org.infinity.datatype.IsNumeric; import org.infinity.resource.spl.Ability; import org.infinity.resource.spl.SplResource; @@ -362,15 +361,15 @@ public void write(OutputStream os) throws IOException protected void setAddRemovableOffset(AddRemovable datatype) { if (datatype instanceof Effect && getEffectsCount() >= 1) { - final SectionOffset effectOffset = (SectionOffset)getParent().getAttribute(SplResource.SPL_OFFSET_EFFECTS); - int effectIndex = ((DecNumber)getAttribute(Ability.ABILITY_FIRST_EFFECT_INDEX)).getValue() + getEffectsCount() - 1; + final IsNumeric effectOffset = (IsNumeric)getParent().getAttribute(SplResource.SPL_OFFSET_EFFECTS); + int effectIndex = ((IsNumeric)getAttribute(Ability.ABILITY_FIRST_EFFECT_INDEX)).getValue() + getEffectsCount() - 1; datatype.setOffset(effectOffset.getValue() + effectIndex * 48); } } public int getEffectsCount() { - return ((SectionCount)getAttribute(Ability.ABILITY_NUM_EFFECTS)).getValue(); + return ((IsNumeric)getAttribute(Ability.ABILITY_NUM_EFFECTS)).getValue(); } public void incEffectsIndex(int value) @@ -380,7 +379,7 @@ public void incEffectsIndex(int value) public int readEffects(ByteBuffer buffer, int off) throws Exception { - int effect_count = ((SectionCount)getAttribute(Ability.ABILITY_NUM_EFFECTS)).getValue(); + int effect_count = ((IsNumeric)getAttribute(Ability.ABILITY_NUM_EFFECTS)).getValue(); for (int i = 0; i < effect_count; i++) { Effect eff = new Effect(this, buffer, off, i); off = eff.getEndOffset(); diff --git a/src/org/infinity/resource/EffectFactory.java b/src/org/infinity/resource/EffectFactory.java index acec9d063..0a0c47b4f 100644 --- a/src/org/infinity/resource/EffectFactory.java +++ b/src/org/infinity/resource/EffectFactory.java @@ -766,7 +766,7 @@ public static boolean updateOpcode(AbstractStruct struct) throws Exception if (struct != null) { EffectType effType = (EffectType)getEntry(struct, EffectEntry.IDX_OPCODE); if (effType != null) { - int opcode = ((EffectType)getEntry(struct, EffectEntry.IDX_OPCODE)).getValue(); + int opcode = ((IsNumeric)getEntry(struct, EffectEntry.IDX_OPCODE)).getValue(); switch (opcode) { case 1: // Modify attacks per round return updateOpcode1(struct); @@ -799,7 +799,7 @@ private static boolean updateOpcode1(AbstractStruct struct) throws Exception { if (struct != null) { if (Profile.isEnhancedEdition()) { - int opcode = ((EffectType)getEntry(struct, EffectEntry.IDX_OPCODE)).getValue(); + int opcode = ((IsNumeric)getEntry(struct, EffectEntry.IDX_OPCODE)).getValue(); if (opcode == 1) { int param2 = ((IsNumeric)getEntry(struct, EffectEntry.IDX_PARAM2)).getValue(); if (param2 == 2) { // Set % of @@ -824,7 +824,7 @@ private static boolean updateOpcode15(AbstractStruct struct) throws Exception if (Profile.getEngine() == Profile.Engine.IWD || Profile.getEngine() == Profile.Engine.IWD2 || Profile.isEnhancedEdition() || (boolean)Profile.getProperty(Profile.Key.IS_GAME_TOBEX)) { boolean isV1 = (getEntry(struct, EffectEntry.IDX_OPCODE).getSize() == 2); - int opcode = ((EffectType)getEntry(struct, EffectEntry.IDX_OPCODE)).getValue(); + int opcode = ((IsNumeric)getEntry(struct, EffectEntry.IDX_OPCODE)).getValue(); int param2 = ((IsNumeric)getEntry(struct, EffectEntry.IDX_PARAM2)).getValue(); if (opcode == 15) { if (isV1 && param2 == 3) { @@ -847,7 +847,7 @@ private static boolean updateOpcode23(AbstractStruct struct) throws Exception { if (struct != null) { if (Profile.isEnhancedEdition()) { - int opcode = ((EffectType)getEntry(struct, EffectEntry.IDX_OPCODE)).getValue(); + int opcode = ((IsNumeric)getEntry(struct, EffectEntry.IDX_OPCODE)).getValue(); if (opcode == 23) { int special = ((IsNumeric)getEntry(struct, EffectEntry.IDX_SPECIAL)).getValue(); if (special == 0 ) { @@ -878,7 +878,7 @@ private static boolean updateOpcode44(AbstractStruct struct) throws Exception if (Profile.getEngine() == Profile.Engine.IWD || Profile.getEngine() == Profile.Engine.IWD2 || Profile.isEnhancedEdition() || (boolean)Profile.getProperty(Profile.Key.IS_GAME_TOBEX)) { boolean isV1 = (getEntry(struct, EffectEntry.IDX_OPCODE).getSize() == 2); - int opcode = ((EffectType)getEntry(struct, EffectEntry.IDX_OPCODE)).getValue(); + int opcode = ((IsNumeric)getEntry(struct, EffectEntry.IDX_OPCODE)).getValue(); int param2 = ((IsNumeric)getEntry(struct, EffectEntry.IDX_PARAM2)).getValue(); if (opcode == 44) { if (isV1 && param2 == 3) { @@ -901,7 +901,7 @@ private static boolean updateOpcode78(AbstractStruct struct) throws Exception { if (struct != null) { if (Profile.isEnhancedEdition()) { - int opcode = ((EffectType)getEntry(struct, EffectEntry.IDX_OPCODE)).getValue(); + int opcode = ((IsNumeric)getEntry(struct, EffectEntry.IDX_OPCODE)).getValue(); if (opcode == 78) { int param2 = ((IsNumeric)getEntry(struct, EffectEntry.IDX_PARAM2)).getValue(); switch (param2) { @@ -929,7 +929,7 @@ private static boolean updateOpcode232(AbstractStruct struct) throws Exception { if (struct != null) { if (Profile.getEngine() == Profile.Engine.BG2 || Profile.isEnhancedEdition()) { - int opcode = ((EffectType)getEntry(struct, EffectEntry.IDX_OPCODE)).getValue(); + int opcode = ((IsNumeric)getEntry(struct, EffectEntry.IDX_OPCODE)).getValue(); if (opcode == 232) { int param2 = ((IsNumeric)getEntry(struct, EffectEntry.IDX_PARAM2)).getValue(); switch (param2) { @@ -964,7 +964,7 @@ private static boolean updateOpcode233(AbstractStruct struct) throws Exception { if (struct != null) { if (Profile.isEnhancedEdition()) { - int opcode = ((EffectType)getEntry(struct, EffectEntry.IDX_OPCODE)).getValue(); + int opcode = ((IsNumeric)getEntry(struct, EffectEntry.IDX_OPCODE)).getValue(); if (opcode == 233) { boolean signed = ((MultiNumber)getEntry(struct, EffectEntry.IDX_PARAM1)).isSigned(); int mode = ((IsNumeric)getEntry(struct, EffectEntry.IDX_PARAM2B)).getValue(); @@ -986,7 +986,7 @@ private static boolean updateOpcode319(AbstractStruct struct) throws Exception boolean retVal = false; if (struct != null) { if (Profile.getEngine() == Profile.Engine.BG2 || Profile.isEnhancedEdition()) { - int opcode = ((EffectType)getEntry(struct, EffectEntry.IDX_OPCODE)).getValue(); + int opcode = ((IsNumeric)getEntry(struct, EffectEntry.IDX_OPCODE)).getValue(); if (opcode == 319) { boolean isEEex = Profile.getProperty(Profile.Key.IS_GAME_EEEX); if (isEEex) { @@ -1030,7 +1030,7 @@ private static boolean updateOpcode328(AbstractStruct struct) throws Exception { if (struct != null) { if (Profile.isEnhancedEdition()) { - int opcode = ((EffectType)getEntry(struct, EffectEntry.IDX_OPCODE)).getValue(); + int opcode = ((IsNumeric)getEntry(struct, EffectEntry.IDX_OPCODE)).getValue(); if (opcode == 328) { int special = ((IsNumeric)getEntry(struct, EffectEntry.IDX_SPECIAL)).getValue(); if (special == 1 && ResourceFactory.resourceExists("SPLSTATE.IDS")) { @@ -1054,7 +1054,7 @@ private static boolean updateOpcode342(AbstractStruct struct) throws Exception { if (struct != null) { if (Profile.isEnhancedEdition()) { - int opcode = ((EffectType)getEntry(struct, EffectEntry.IDX_OPCODE)).getValue(); + int opcode = ((IsNumeric)getEntry(struct, EffectEntry.IDX_OPCODE)).getValue(); if (opcode == 342) { int param2 = ((IsNumeric)getEntry(struct, EffectEntry.IDX_PARAM2)).getValue(); StructEntry newEntry = null; diff --git a/src/org/infinity/resource/are/AreResource.java b/src/org/infinity/resource/are/AreResource.java index dac3b55e5..c30eec580 100644 --- a/src/org/infinity/resource/are/AreResource.java +++ b/src/org/infinity/resource/are/AreResource.java @@ -977,8 +977,8 @@ public static boolean matchSearchOptions(ResourceEntry entry, SearchOptions sear Object o; // preparing substructures - DecNumber ofs = (DecNumber)are.getAttribute(ARE_OFFSET_ACTORS, false); - DecNumber cnt = (DecNumber)are.getAttribute(ARE_NUM_ACTORS, false); + IsNumeric ofs = (IsNumeric)are.getAttribute(ARE_OFFSET_ACTORS, false); + IsNumeric cnt = (IsNumeric)are.getAttribute(ARE_NUM_ACTORS, false); if (ofs != null && ofs.getValue() > 0 && cnt != null && cnt.getValue() > 0) { actors = new Actor[cnt.getValue()]; for (int idx = 0; idx < actors.length; idx++) { @@ -988,8 +988,8 @@ public static boolean matchSearchOptions(ResourceEntry entry, SearchOptions sear actors = new Actor[0]; } - ofs = (DecNumber)are.getAttribute(ARE_OFFSET_ANIMATIONS, false); - cnt = (DecNumber)are.getAttribute(ARE_NUM_ANIMATIONS, false); + ofs = (IsNumeric)are.getAttribute(ARE_OFFSET_ANIMATIONS, false); + cnt = (IsNumeric)are.getAttribute(ARE_NUM_ANIMATIONS, false); if (ofs != null && ofs.getValue() > 0 && cnt != null && cnt.getValue() > 0) { animations = new Animation[cnt.getValue()]; for (int idx = 0; idx < animations.length; idx++) { @@ -999,15 +999,15 @@ public static boolean matchSearchOptions(ResourceEntry entry, SearchOptions sear animations = new Animation[0]; } - ofs = (DecNumber)are.getAttribute(ARE_OFFSET_CONTAINERS, false); - cnt = (DecNumber)are.getAttribute(ARE_NUM_CONTAINERS, false); + ofs = (IsNumeric)are.getAttribute(ARE_OFFSET_CONTAINERS, false); + cnt = (IsNumeric)are.getAttribute(ARE_NUM_CONTAINERS, false); if (ofs != null && ofs.getValue() > 0 && cnt != null && cnt.getValue() > 0) { items = new Item[cnt.getValue()][]; for (int i = 0; i < cnt.getValue(); i++) { String label = String.format(SearchOptions.getResourceName(SearchOptions.ARE_Container), i); Container container = (Container)are.getAttribute(label, false); if (container != null) { - DecNumber cnt2 = (DecNumber)container.getAttribute(ARE_NUM_ITEMS, false); + IsNumeric cnt2 = (IsNumeric)container.getAttribute(ARE_NUM_ITEMS, false); if (cnt2 != null && cnt2.getValue() > 0) { items[i] = new Item[cnt2.getValue()]; for (int j = 0; j < cnt2.getValue(); j++) { diff --git a/src/org/infinity/resource/are/Container.java b/src/org/infinity/resource/are/Container.java index 9d71ba83f..11ad7f0a7 100644 --- a/src/org/infinity/resource/are/Container.java +++ b/src/org/infinity/resource/are/Container.java @@ -11,7 +11,7 @@ import org.infinity.datatype.Bitmap; import org.infinity.datatype.DecNumber; import org.infinity.datatype.Flag; -import org.infinity.datatype.HexNumber; +import org.infinity.datatype.IsNumeric; import org.infinity.datatype.ResourceRef; import org.infinity.datatype.StringRef; import org.infinity.datatype.TextString; @@ -124,8 +124,8 @@ public boolean viewerTabAddedBefore(int index) @Override public void readVertices(ByteBuffer buffer, int offset) throws Exception { - int firstVertex = ((DecNumber)getAttribute(ARE_CONTAINER_FIRST_VERTEX_INDEX)).getValue(); - int numVertices = ((DecNumber)getAttribute(ARE_CONTAINER_NUM_VERTICES)).getValue(); + int firstVertex = ((IsNumeric)getAttribute(ARE_CONTAINER_FIRST_VERTEX_INDEX)).getValue(); + int numVertices = ((IsNumeric)getAttribute(ARE_CONTAINER_NUM_VERTICES)).getValue(); offset += firstVertex << 2; for (int i = 0; i < numVertices; i++) { addField(new Vertex(this, buffer, offset + 4 * i, i)); @@ -154,16 +154,16 @@ public int updateVertices(int offset, int number) protected void setAddRemovableOffset(AddRemovable datatype) { if (datatype instanceof Vertex) { - int index = ((DecNumber)getAttribute(ARE_CONTAINER_FIRST_VERTEX_INDEX)).getValue(); - index += ((DecNumber)getAttribute(ARE_CONTAINER_NUM_VERTICES)).getValue(); - final int offset = ((HexNumber)getParent().getAttribute(AreResource.ARE_OFFSET_VERTICES)).getValue(); + int index = ((IsNumeric)getAttribute(ARE_CONTAINER_FIRST_VERTEX_INDEX)).getValue(); + index += ((IsNumeric)getAttribute(ARE_CONTAINER_NUM_VERTICES)).getValue(); + final int offset = ((IsNumeric)getParent().getAttribute(AreResource.ARE_OFFSET_VERTICES)).getValue(); datatype.setOffset(offset + 4 * index); ((AbstractStruct)datatype).realignStructOffsets(); } else if (datatype instanceof Item) { - int index = ((DecNumber)getAttribute(ARE_CONTAINER_FIRST_ITEM_INDEX)).getValue(); - index += ((DecNumber)getAttribute(ARE_CONTAINER_NUM_ITEMS)).getValue(); - final int offset = ((HexNumber)getParent().getAttribute(AreResource.ARE_OFFSET_ITEMS)).getValue(); + int index = ((IsNumeric)getAttribute(ARE_CONTAINER_FIRST_ITEM_INDEX)).getValue(); + index += ((IsNumeric)getAttribute(ARE_CONTAINER_NUM_ITEMS)).getValue(); + final int offset = ((IsNumeric)getParent().getAttribute(AreResource.ARE_OFFSET_ITEMS)).getValue(); datatype.setOffset(offset + 20 * index); ((AbstractStruct)datatype).realignStructOffsets(); } @@ -171,8 +171,8 @@ else if (datatype instanceof Item) { public void readItems(ByteBuffer buffer, int offset) throws Exception { - int firstIndex = ((DecNumber)getAttribute(ARE_CONTAINER_FIRST_ITEM_INDEX)).getValue(); - int numItems = ((DecNumber)getAttribute(ARE_CONTAINER_NUM_ITEMS)).getValue(); + int firstIndex = ((IsNumeric)getAttribute(ARE_CONTAINER_FIRST_ITEM_INDEX)).getValue(); + int numItems = ((IsNumeric)getAttribute(ARE_CONTAINER_NUM_ITEMS)).getValue(); offset += firstIndex * 20; for (int i = 0; i < numItems; i++) { addField(new Item(this, buffer, offset + 20 * i, i)); diff --git a/src/org/infinity/resource/are/Door.java b/src/org/infinity/resource/are/Door.java index b046f44f3..2ecc01878 100644 --- a/src/org/infinity/resource/are/Door.java +++ b/src/org/infinity/resource/are/Door.java @@ -9,7 +9,7 @@ import org.infinity.datatype.Bitmap; import org.infinity.datatype.DecNumber; import org.infinity.datatype.Flag; -import org.infinity.datatype.HexNumber; +import org.infinity.datatype.IsNumeric; import org.infinity.datatype.ResourceRef; import org.infinity.datatype.SectionCount; import org.infinity.datatype.StringRef; @@ -121,26 +121,26 @@ public boolean canRemove() @Override public void readVertices(ByteBuffer buffer, int offset) throws Exception { - DecNumber firstVertex = (DecNumber)getAttribute(ARE_DOOR_FIRST_VERTEX_INDEX_OPEN); - DecNumber numVertices = (DecNumber)getAttribute(ARE_DOOR_NUM_VERTICES_OPEN); + IsNumeric firstVertex = (IsNumeric)getAttribute(ARE_DOOR_FIRST_VERTEX_INDEX_OPEN); + IsNumeric numVertices = (IsNumeric)getAttribute(ARE_DOOR_NUM_VERTICES_OPEN); for (int i = 0; i < numVertices.getValue(); i++) { addField(new OpenVertex(this, buffer, offset + 4 * (firstVertex.getValue() + i), i)); } - firstVertex = (DecNumber)getAttribute(ARE_DOOR_FIRST_VERTEX_INDEX_CLOSED); - numVertices = (DecNumber)getAttribute(ARE_DOOR_NUM_VERTICES_CLOSED); + firstVertex = (IsNumeric)getAttribute(ARE_DOOR_FIRST_VERTEX_INDEX_CLOSED); + numVertices = (IsNumeric)getAttribute(ARE_DOOR_NUM_VERTICES_CLOSED); for (int i = 0; i < numVertices.getValue(); i++) { addField(new ClosedVertex(this, buffer, offset + 4 * (firstVertex.getValue() + i), i)); } - firstVertex = (DecNumber)getAttribute(ARE_DOOR_FIRST_VERTEX_INDEX_IMPEDED_OPEN); - numVertices = (DecNumber)getAttribute(ARE_DOOR_NUM_VERTICES_IMPEDED_OPEN); + firstVertex = (IsNumeric)getAttribute(ARE_DOOR_FIRST_VERTEX_INDEX_IMPEDED_OPEN); + numVertices = (IsNumeric)getAttribute(ARE_DOOR_NUM_VERTICES_IMPEDED_OPEN); for (int i = 0; i < numVertices.getValue(); i++) { addField(new OpenVertexImpeded(this, buffer, offset + 4 * (firstVertex.getValue() + i), i)); } - firstVertex = (DecNumber)getAttribute(ARE_DOOR_FIRST_VERTEX_INDEX_IMPEDED_CLOSED); - numVertices = (DecNumber)getAttribute(ARE_DOOR_NUM_VERTICES_IMPEDED_CLOSED); + firstVertex = (IsNumeric)getAttribute(ARE_DOOR_FIRST_VERTEX_INDEX_IMPEDED_CLOSED); + numVertices = (IsNumeric)getAttribute(ARE_DOOR_NUM_VERTICES_IMPEDED_CLOSED); for (int i = 0; i < numVertices.getValue(); i++) { addField(new ClosedVertexImpeded(this, buffer, offset + 4 * (firstVertex.getValue() + i), i)); } @@ -151,13 +151,13 @@ public int updateVertices(int offset, int number) { // Must assume that the number is correct ((DecNumber)getAttribute(ARE_DOOR_FIRST_VERTEX_INDEX_OPEN)).setValue(number); - int count = ((DecNumber)getAttribute(ARE_DOOR_NUM_VERTICES_OPEN)).getValue(); + int count = ((IsNumeric)getAttribute(ARE_DOOR_NUM_VERTICES_OPEN)).getValue(); ((DecNumber)getAttribute(ARE_DOOR_FIRST_VERTEX_INDEX_CLOSED)).setValue(number + count); - count += ((DecNumber)getAttribute(ARE_DOOR_NUM_VERTICES_CLOSED)).getValue(); + count += ((IsNumeric)getAttribute(ARE_DOOR_NUM_VERTICES_CLOSED)).getValue(); ((DecNumber)getAttribute(ARE_DOOR_FIRST_VERTEX_INDEX_IMPEDED_OPEN)).setValue(number + count); - count += ((DecNumber)getAttribute(ARE_DOOR_NUM_VERTICES_IMPEDED_OPEN)).getValue(); + count += ((IsNumeric)getAttribute(ARE_DOOR_NUM_VERTICES_IMPEDED_OPEN)).getValue(); ((DecNumber)getAttribute(ARE_DOOR_FIRST_VERTEX_INDEX_IMPEDED_CLOSED)).setValue(number + count); - count += ((DecNumber)getAttribute(ARE_DOOR_NUM_VERTICES_IMPEDED_CLOSED)).getValue(); + count += ((IsNumeric)getAttribute(ARE_DOOR_NUM_VERTICES_IMPEDED_CLOSED)).getValue(); for (final StructEntry entry : getFields()) { if (entry instanceof Vertex) { @@ -174,25 +174,25 @@ public int updateVertices(int offset, int number) @Override protected void setAddRemovableOffset(AddRemovable datatype) { - final int offset = ((HexNumber)getParent().getAttribute(AreResource.ARE_OFFSET_VERTICES)).getValue(); + final int offset = ((IsNumeric)getParent().getAttribute(AreResource.ARE_OFFSET_VERTICES)).getValue(); if (datatype instanceof OpenVertex) { - int index = ((DecNumber)getAttribute(ARE_DOOR_FIRST_VERTEX_INDEX_OPEN)).getValue(); - index += ((DecNumber)getAttribute(ARE_DOOR_NUM_VERTICES_OPEN)).getValue(); + int index = ((IsNumeric)getAttribute(ARE_DOOR_FIRST_VERTEX_INDEX_OPEN)).getValue(); + index += ((IsNumeric)getAttribute(ARE_DOOR_NUM_VERTICES_OPEN)).getValue(); datatype.setOffset(offset + 4 * (index - 1)); } else if (datatype instanceof ClosedVertex) { - int index = ((DecNumber)getAttribute(ARE_DOOR_FIRST_VERTEX_INDEX_CLOSED)).getValue(); - index += ((DecNumber)getAttribute(ARE_DOOR_NUM_VERTICES_CLOSED)).getValue(); + int index = ((IsNumeric)getAttribute(ARE_DOOR_FIRST_VERTEX_INDEX_CLOSED)).getValue(); + index += ((IsNumeric)getAttribute(ARE_DOOR_NUM_VERTICES_CLOSED)).getValue(); datatype.setOffset(offset + 4 * (index - 1)); } else if (datatype instanceof OpenVertexImpeded) { - int index = ((DecNumber)getAttribute(ARE_DOOR_FIRST_VERTEX_INDEX_IMPEDED_OPEN)).getValue(); - index += ((DecNumber)getAttribute(ARE_DOOR_NUM_VERTICES_IMPEDED_OPEN)).getValue(); + int index = ((IsNumeric)getAttribute(ARE_DOOR_FIRST_VERTEX_INDEX_IMPEDED_OPEN)).getValue(); + index += ((IsNumeric)getAttribute(ARE_DOOR_NUM_VERTICES_IMPEDED_OPEN)).getValue(); datatype.setOffset(offset + 4 * (index - 1)); } else if (datatype instanceof ClosedVertexImpeded) { - int index = ((DecNumber)getAttribute(ARE_DOOR_FIRST_VERTEX_INDEX_IMPEDED_CLOSED)).getValue(); - index += ((DecNumber)getAttribute(ARE_DOOR_NUM_VERTICES_IMPEDED_CLOSED)).getValue(); + int index = ((IsNumeric)getAttribute(ARE_DOOR_FIRST_VERTEX_INDEX_IMPEDED_CLOSED)).getValue(); + index += ((IsNumeric)getAttribute(ARE_DOOR_NUM_VERTICES_IMPEDED_CLOSED)).getValue(); datatype.setOffset(offset + 4 * (index - 1)); } } diff --git a/src/org/infinity/resource/are/ITEPoint.java b/src/org/infinity/resource/are/ITEPoint.java index 7034f8c23..40bdded13 100644 --- a/src/org/infinity/resource/are/ITEPoint.java +++ b/src/org/infinity/resource/are/ITEPoint.java @@ -9,7 +9,7 @@ import org.infinity.datatype.Bitmap; import org.infinity.datatype.DecNumber; import org.infinity.datatype.Flag; -import org.infinity.datatype.HexNumber; +import org.infinity.datatype.IsNumeric; import org.infinity.datatype.ResourceRef; import org.infinity.datatype.StringRef; import org.infinity.datatype.TextString; @@ -103,8 +103,8 @@ public boolean canRemove() @Override public void readVertices(ByteBuffer buffer, int offset) throws Exception { - int firstVertex = ((DecNumber)getAttribute(ARE_TRIGGER_FIRST_VERTEX_INDEX)).getValue(); - int numVertices = ((DecNumber)getAttribute(ARE_TRIGGER_NUM_VERTICES)).getValue(); + int firstVertex = ((IsNumeric)getAttribute(ARE_TRIGGER_FIRST_VERTEX_INDEX)).getValue(); + int numVertices = ((IsNumeric)getAttribute(ARE_TRIGGER_NUM_VERTICES)).getValue(); offset += firstVertex << 2; for (int i = 0; i < numVertices; i++) { addField(new Vertex(this, buffer, offset + 4 * i, i)); @@ -134,9 +134,9 @@ public int updateVertices(int offset, int number) protected void setAddRemovableOffset(AddRemovable datatype) { if (datatype instanceof Vertex) { - int index = ((DecNumber)getAttribute(ARE_TRIGGER_FIRST_VERTEX_INDEX)).getValue(); - index += ((DecNumber)getAttribute(ARE_TRIGGER_NUM_VERTICES)).getValue(); - final int offset = ((HexNumber)getParent().getAttribute(AreResource.ARE_OFFSET_VERTICES)).getValue(); + int index = ((IsNumeric)getAttribute(ARE_TRIGGER_FIRST_VERTEX_INDEX)).getValue(); + index += ((IsNumeric)getAttribute(ARE_TRIGGER_NUM_VERTICES)).getValue(); + final int offset = ((IsNumeric)getParent().getAttribute(AreResource.ARE_OFFSET_VERTICES)).getValue(); datatype.setOffset(offset + 4 * index); ((AbstractStruct)datatype).realignStructOffsets(); } diff --git a/src/org/infinity/resource/are/viewer/LayerObject.java b/src/org/infinity/resource/are/viewer/LayerObject.java index 018ca0fb7..144b1a5dc 100644 --- a/src/org/infinity/resource/are/viewer/LayerObject.java +++ b/src/org/infinity/resource/are/viewer/LayerObject.java @@ -13,7 +13,6 @@ import java.awt.event.MouseMotionListener; import java.util.EventListener; -import org.infinity.datatype.DecNumber; import org.infinity.datatype.Flag; import org.infinity.datatype.IsNumeric; import org.infinity.datatype.ResourceRef; @@ -190,8 +189,8 @@ protected static Point[] loadVertices(AbstractStruct superStruct, int baseOfs, i if (e.getOffset() >= baseOfs && type.isAssignableFrom(e.getClass())) { if (idx >= index) { final Vertex vertex = (Vertex)e; - coords[cnt] = new Point(((DecNumber)vertex.getAttribute(Vertex.VERTEX_X)).getValue(), - ((DecNumber)vertex.getAttribute(Vertex.VERTEX_Y)).getValue()); + coords[cnt] = new Point(((IsNumeric)vertex.getAttribute(Vertex.VERTEX_X)).getValue(), + ((IsNumeric)vertex.getAttribute(Vertex.VERTEX_Y)).getValue()); cnt++; if (cnt >= count) { break; diff --git a/src/org/infinity/resource/are/viewer/LayerObjectAutomap.java b/src/org/infinity/resource/are/viewer/LayerObjectAutomap.java index 0ec147c1d..da914c5e0 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectAutomap.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectAutomap.java @@ -9,8 +9,6 @@ import java.nio.file.Path; import org.infinity.datatype.IsNumeric; -import org.infinity.datatype.SectionCount; -import org.infinity.datatype.SectionOffset; import org.infinity.gui.layeritem.AbstractLayerItem; import org.infinity.gui.layeritem.IconLayerItem; import org.infinity.icon.Icons; @@ -68,8 +66,8 @@ public LayerObjectAutomap(AreResource parent, AutomapNote note) if (FileEx.create(tohFile).exists()) { FileResourceEntry tohEntry = new FileResourceEntry(tohFile); TohResource toh = new TohResource(tohEntry); - SectionOffset so = (SectionOffset)toh.getAttribute(TohResource.TOH_OFFSET_ENTRIES); - SectionCount sc = (SectionCount)toh.getAttribute(TohResource.TOH_NUM_ENTRIES); + IsNumeric so = (IsNumeric)toh.getAttribute(TohResource.TOH_OFFSET_ENTRIES); + IsNumeric sc = (IsNumeric)toh.getAttribute(TohResource.TOH_NUM_ENTRIES); if (so != null && sc != null && sc.getValue() > 0) { for (int i = 0, count = sc.getValue(), curOfs = so.getValue(); i < count; i++) { StrRefEntry2 strref = (StrRefEntry2)toh.getAttribute(curOfs, false); @@ -97,7 +95,7 @@ public LayerObjectAutomap(AreResource parent, AutomapNote note) FileResourceEntry totEntry = new FileResourceEntry(totFile); TohResource toh = new TohResource(tohEntry); TotResource tot = new TotResource(totEntry); - SectionCount sc = (SectionCount)toh.getAttribute(TohResource.TOH_NUM_ENTRIES); + IsNumeric sc = (IsNumeric)toh.getAttribute(TohResource.TOH_NUM_ENTRIES); if (sc != null && sc.getValue() > 0) { for (int i = 0, count = sc.getValue(), curOfs = 0x14; i < count; i++) { StrRefEntry strref = (StrRefEntry)toh.getAttribute(curOfs, false); diff --git a/src/org/infinity/resource/are/viewer/TilesetRenderer.java b/src/org/infinity/resource/are/viewer/TilesetRenderer.java index 9df90d92e..fe36dfc80 100644 --- a/src/org/infinity/resource/are/viewer/TilesetRenderer.java +++ b/src/org/infinity/resource/are/viewer/TilesetRenderer.java @@ -19,8 +19,6 @@ import org.infinity.datatype.IsNumeric; import org.infinity.datatype.IsTextual; import org.infinity.datatype.ResourceRef; -import org.infinity.datatype.SectionCount; -import org.infinity.datatype.SectionOffset; import org.infinity.gui.RenderCanvas; import org.infinity.resource.Profile; import org.infinity.resource.ResourceFactory; @@ -661,8 +659,8 @@ private boolean initWed(WedResource wed) { if (wed != null) { // loading overlay structures - SectionOffset so = (SectionOffset)wed.getAttribute(WedResource.WED_OFFSET_OVERLAYS); - SectionCount sc = (SectionCount)wed.getAttribute(WedResource.WED_NUM_OVERLAYS); + IsNumeric so = (IsNumeric)wed.getAttribute(WedResource.WED_OFFSET_OVERLAYS); + IsNumeric sc = (IsNumeric)wed.getAttribute(WedResource.WED_NUM_OVERLAYS); if (so != null && sc != null) { for (int i = 0, count = sc.getValue(), curOfs = so.getValue(); i < count; i++) { Overlay ovl = (Overlay)wed.getAttribute(curOfs, false); @@ -680,8 +678,8 @@ private boolean initWed(WedResource wed) } // loading door structures - so = (SectionOffset)wed.getAttribute(WedResource.WED_OFFSET_DOORS); - sc = (SectionCount)wed.getAttribute(WedResource.WED_NUM_DOORS); + so = (IsNumeric)wed.getAttribute(WedResource.WED_OFFSET_DOORS); + sc = (IsNumeric)wed.getAttribute(WedResource.WED_NUM_DOORS); IsNumeric lookupOfs = (IsNumeric)wed.getAttribute(WedResource.WED_OFFSET_DOOR_TILEMAP_LOOKUP); if (so != null && sc != null && lookupOfs != null) { for (int i = 0, count = sc.getValue(), curOfs = so.getValue(); i < count; i++) { @@ -691,7 +689,7 @@ private boolean initWed(WedResource wed) boolean isClosed = ((IsNumeric)door.getAttribute(Door.WED_DOOR_IS_DOOR)).getValue() == 1; final int tileSize = 2; int tileIdx = ((IsNumeric)door.getAttribute(Door.WED_DOOR_TILEMAP_LOOKUP_INDEX)).getValue(); - int tileCount = ((SectionCount)door.getAttribute(Door.WED_DOOR_NUM_TILEMAP_INDICES)).getValue(); + int tileCount = ((IsNumeric)door.getAttribute(Door.WED_DOOR_NUM_TILEMAP_INDICES)).getValue(); if (tileCount < 0) tileCount = 0; int[] indices = new int[tileCount]; for (int j = 0; j < tileCount; j++) { @@ -1135,7 +1133,7 @@ private void init(WedResource wed, Overlay ovl) // storing tile information tilesX = ((IsNumeric)ovl.getAttribute(Overlay.WED_OVERLAY_WIDTH)).getValue(); tilesY = ((IsNumeric)ovl.getAttribute(Overlay.WED_OVERLAY_HEIGHT)).getValue(); - int mapOfs = ((SectionOffset)ovl.getAttribute(Overlay.WED_OVERLAY_OFFSET_TILEMAP)).getValue(); + int mapOfs = ((IsNumeric)ovl.getAttribute(Overlay.WED_OVERLAY_OFFSET_TILEMAP)).getValue(); int idxOfs = ((IsNumeric)ovl.getAttribute(Overlay.WED_OVERLAY_OFFSET_TILEMAP_LOOKUP)).getValue(); int tileCount = tilesX * tilesY; for (int i = 0, curOfs = mapOfs; i < tileCount; i++) { diff --git a/src/org/infinity/resource/chu/Control.java b/src/org/infinity/resource/chu/Control.java index 20bad1d84..e32b721b1 100644 --- a/src/org/infinity/resource/chu/Control.java +++ b/src/org/infinity/resource/chu/Control.java @@ -136,21 +136,21 @@ public int read(ByteBuffer buffer, int offset) /** Returns the control id. */ public int getControlId() { - return ((DecNumber)getAttribute(CHU_CONTROL_ID)).getValue(); + return ((IsNumeric)getAttribute(CHU_CONTROL_ID)).getValue(); } /** Returns the x and y position of the control. */ public Point getControlPosition() { - return new Point(((DecNumber)getAttribute(CHU_CONTROL_POSITION_X)).getValue(), - ((DecNumber)getAttribute(CHU_CONTROL_POSITION_Y)).getValue()); + return new Point(((IsNumeric)getAttribute(CHU_CONTROL_POSITION_X)).getValue(), + ((IsNumeric)getAttribute(CHU_CONTROL_POSITION_Y)).getValue()); } /** Returns the width and height of the control. */ public Dimension getControlDimensions() { - return new Dimension(((DecNumber)getAttribute(CHU_CONTROL_WIDTH)).getValue(), - ((DecNumber)getAttribute(CHU_CONTROL_HEIGHT)).getValue()); + return new Dimension(((IsNumeric)getAttribute(CHU_CONTROL_WIDTH)).getValue(), + ((IsNumeric)getAttribute(CHU_CONTROL_HEIGHT)).getValue()); } /** Returns the control type. */ @@ -162,7 +162,7 @@ public int getControlType() public int readControl(ByteBuffer buffer) { - int offset = ((HexNumber)getAttribute(CHU_CONTROL_OFFSET)).getValue(); + int offset = ((IsNumeric)getAttribute(CHU_CONTROL_OFFSET)).getValue(); final int endOffset = offset + size; addField(new DecNumber(buffer, offset, 2, CHU_CONTROL_ID)); addField(new DecNumber(buffer, offset + 2, 2, CHU_CONTROL_BUFFER_LENGTH)); @@ -265,7 +265,7 @@ public int readControl(ByteBuffer buffer) offset += 40; break; default: - HexNumber len = (HexNumber)getAttribute(CHU_CONTROL_LENGTH); + IsNumeric len = (IsNumeric)getAttribute(CHU_CONTROL_LENGTH); addField(new Unknown(buffer, offset + 14, len.getValue() - 14)); offset += len.getValue(); break; diff --git a/src/org/infinity/resource/chu/Viewer.java b/src/org/infinity/resource/chu/Viewer.java index 0328b19ac..b77bfae42 100644 --- a/src/org/infinity/resource/chu/Viewer.java +++ b/src/org/infinity/resource/chu/Viewer.java @@ -46,8 +46,8 @@ import org.infinity.datatype.Flag; import org.infinity.datatype.IsNumeric; +import org.infinity.datatype.IsReference; import org.infinity.datatype.IsTextual; -import org.infinity.datatype.ResourceRef; import org.infinity.gui.RenderCanvas; import org.infinity.gui.StructViewer; import org.infinity.gui.ViewerUtil; @@ -1428,7 +1428,7 @@ public void updateImage() // 2. drawing control if (isVisible()) { // loading BAM - String resName = ((ResourceRef)getResource().getAttribute(Control.CHU_CONTROL_BTN_RESREF)).getResourceName(); + String resName = ((IsReference)getResource().getAttribute(Control.CHU_CONTROL_BTN_RESREF)).getResourceName(); // getting specified cycle index int cycleIdx = ((IsNumeric)getResource().getAttribute(Control.CHU_CONTROL_BTN_ANIMATION_INDEX)).getValue(); int frameIdx = 0; @@ -1536,7 +1536,7 @@ public void updateImage() // 2. drawing control // 2.1. drawing background image - String resName = ((ResourceRef)getResource().getAttribute(Control.CHU_CONTROL_SLD_BACKGROUND)).getResourceName(); + String resName = ((IsReference)getResource().getAttribute(Control.CHU_CONTROL_SLD_BACKGROUND)).getResourceName(); MosDecoder mos = MosDecoder.loadMos(ResourceFactory.getResourceEntry(resName)); if (mos != null) { if (mos instanceof MosV1Decoder) { @@ -1547,7 +1547,7 @@ public void updateImage() // 2.2. drawing control elements if (isVisible()) { - resName = ((ResourceRef)getResource().getAttribute(Control.CHU_CONTROL_SLD_KNOB)).getResourceName(); + resName = ((IsReference)getResource().getAttribute(Control.CHU_CONTROL_SLD_KNOB)).getResourceName(); BamDecoder bam = BamDecoder.loadBam(ResourceFactory.getResourceEntry(resName)); if (bam != null) { BamControl bamCtrl = bam.createControl(); @@ -1645,7 +1645,7 @@ public void updateImage() // 2. drawing control // 2.1. drawing background image (is it actually used?) - String resName = ((ResourceRef)getResource().getAttribute(Control.CHU_CONTROL_TF_BACKGROUND_1)).getResourceName(); + String resName = ((IsReference)getResource().getAttribute(Control.CHU_CONTROL_TF_BACKGROUND_1)).getResourceName(); if (!isResourceIgnored(resName)) { MosDecoder mos = MosDecoder.loadMos(ResourceFactory.getResourceEntry(resName)); if (mos != null) { @@ -1663,7 +1663,7 @@ public void updateImage() String text = ((IsTextual)getResource().getAttribute(Control.CHU_CONTROL_TF_TEXT)).getText(); if (!text.isEmpty()) { - resName = ((ResourceRef)getResource().getAttribute(Control.CHU_CONTROL_TF_FONT)).getResourceName(); + resName = ((IsReference)getResource().getAttribute(Control.CHU_CONTROL_TF_FONT)).getResourceName(); resName = resName.toUpperCase(Locale.ENGLISH).replace(".FNT", ".BAM"); BamDecoder bam = BamDecoder.loadBam(ResourceFactory.getResourceEntry(resName)); if (bam != null) { @@ -1681,7 +1681,7 @@ public void updateImage() // 2.3. drawing caret if (isCaretEnabled()) { - resName = ((ResourceRef)getResource().getAttribute(Control.CHU_CONTROL_TF_CARET)).getResourceName(); + resName = ((IsReference)getResource().getAttribute(Control.CHU_CONTROL_TF_CARET)).getResourceName(); BamDecoder bam = BamDecoder.loadBam(ResourceFactory.getResourceEntry(resName)); if (bam != null) { BamControl bamCtrl = bam.createControl(); @@ -1815,7 +1815,7 @@ public void updateImage() if (isVisible()) { String text = StringTable.getStringRef(((IsNumeric)getResource().getAttribute(Control.CHU_CONTROL_LBL_TEXT)).getValue()); if (text != null) { - String resName = ((ResourceRef)getResource().getAttribute(Control.CHU_CONTROL_LBL_FONT)).getResourceName(); + String resName = ((IsReference)getResource().getAttribute(Control.CHU_CONTROL_LBL_FONT)).getResourceName(); resName = resName.toUpperCase(Locale.ENGLISH).replace(".FNT", ".BAM"); BamDecoder bam = BamDecoder.loadBam(ResourceFactory.getResourceEntry(resName)); if (bam != null) { @@ -1920,7 +1920,7 @@ public void updateImage() // 2. drawing control if (isVisible()) { - String resName = ((ResourceRef)getResource().getAttribute(Control.CHU_CONTROL_SB_GRAPHICS)).getResourceName(); + String resName = ((IsReference)getResource().getAttribute(Control.CHU_CONTROL_SB_GRAPHICS)).getResourceName(); BamDecoder bam = BamDecoder.loadBam(ResourceFactory.getResourceEntry(resName)); if (bam != null) { int cycleIdx = ((IsNumeric)getResource().getAttribute(Control.CHU_CONTROL_SB_ANIMATION_INDEX)).getValue(); diff --git a/src/org/infinity/resource/cre/CreResource.java b/src/org/infinity/resource/cre/CreResource.java index 4a1af755b..6eb8681b5 100644 --- a/src/org/infinity/resource/cre/CreResource.java +++ b/src/org/infinity/resource/cre/CreResource.java @@ -1923,7 +1923,7 @@ else if (version.equalsIgnoreCase("V9.0") || version.equalsIgnoreCase("V9.1")) { private void updateMemorizedSpells() { // Assumes memorized spells offset is correct - int offset = ((HexNumber)getAttribute(CRE_OFFSET_MEMORIZED_SPELLS)).getValue() + getExtraOffset(); + int offset = ((IsNumeric)getAttribute(CRE_OFFSET_MEMORIZED_SPELLS)).getValue() + getExtraOffset(); int count = 0; for (final StructEntry o : getFields()) { if (o instanceof SpellMemorization) { @@ -2191,8 +2191,8 @@ public static boolean matchSearchOptions(ResourceEntry entry, SearchOptions sear Object o; // preparing substructures - DecNumber ofs = (DecNumber)cre.getAttribute(CRE_OFFSET_EFFECTS, false); - DecNumber cnt = (DecNumber)cre.getAttribute(CRE_NUM_EFFECTS, false); + IsNumeric ofs = (IsNumeric)cre.getAttribute(CRE_OFFSET_EFFECTS, false); + IsNumeric cnt = (IsNumeric)cre.getAttribute(CRE_NUM_EFFECTS, false); if (ofs != null && ofs.getValue() > 0 && cnt != null && cnt.getValue() > 0) { effects = new AbstractStruct[cnt.getValue()]; for (int idx = 0; idx < cnt.getValue(); idx++) { @@ -2203,8 +2203,8 @@ public static boolean matchSearchOptions(ResourceEntry entry, SearchOptions sear effects = new AbstractStruct[0]; } - ofs = (DecNumber)cre.getAttribute(CRE_OFFSET_ITEMS, false); - cnt = (DecNumber)cre.getAttribute(CRE_NUM_ITEMS, false); + ofs = (IsNumeric)cre.getAttribute(CRE_OFFSET_ITEMS, false); + cnt = (IsNumeric)cre.getAttribute(CRE_NUM_ITEMS, false); if (ofs != null && ofs.getValue() > 0 && cnt != null && cnt.getValue() > 0) { items = new AbstractStruct[cnt.getValue()]; for (int idx = 0; idx < cnt.getValue(); idx++) { @@ -2248,8 +2248,8 @@ public static boolean matchSearchOptions(ResourceEntry entry, SearchOptions sear spells[i] = listSpells.get(i); } } else { - ofs = (DecNumber)cre.getAttribute(CRE_OFFSET_KNOWN_SPELLS, false); - cnt = (DecNumber)cre.getAttribute(CRE_NUM_KNOWN_SPELLS, false); + ofs = (IsNumeric)cre.getAttribute(CRE_OFFSET_KNOWN_SPELLS, false); + cnt = (IsNumeric)cre.getAttribute(CRE_NUM_KNOWN_SPELLS, false); if (ofs != null && ofs.getValue() > 0 && cnt != null && cnt.getValue() > 0) { spells = new Datatype[cnt.getValue()]; final String spellLabel = SearchOptions.getResourceName(SearchOptions.CRE_Spell_Spell1); diff --git a/src/org/infinity/resource/cre/SpellMemorization.java b/src/org/infinity/resource/cre/SpellMemorization.java index c8cff0fd6..bcc61f2d3 100644 --- a/src/org/infinity/resource/cre/SpellMemorization.java +++ b/src/org/infinity/resource/cre/SpellMemorization.java @@ -8,7 +8,7 @@ import org.infinity.datatype.Bitmap; import org.infinity.datatype.DecNumber; -import org.infinity.datatype.HexNumber; +import org.infinity.datatype.IsNumeric; import org.infinity.resource.AbstractStruct; import org.infinity.resource.AddRemovable; import org.infinity.resource.HasChildStructs; @@ -63,10 +63,10 @@ public AddRemovable confirmAddEntry(AddRemovable entry) throws Exception protected void setAddRemovableOffset(AddRemovable datatype) { if (datatype instanceof MemorizedSpells) { - int index = ((DecNumber)getAttribute(CRE_MEMORIZATION_SPELL_TABLE_INDEX)).getValue(); - index += ((DecNumber)getAttribute(CRE_MEMORIZATION_SPELL_COUNT)).getValue(); + int index = ((IsNumeric)getAttribute(CRE_MEMORIZATION_SPELL_TABLE_INDEX)).getValue(); + index += ((IsNumeric)getAttribute(CRE_MEMORIZATION_SPELL_COUNT)).getValue(); final CreResource cre = (CreResource)getParent(); - int offset = ((HexNumber)cre.getAttribute(CreResource.CRE_OFFSET_MEMORIZED_SPELLS)).getValue() + + int offset = ((IsNumeric)cre.getAttribute(CreResource.CRE_OFFSET_MEMORIZED_SPELLS)).getValue() + cre.getExtraOffset(); datatype.setOffset(offset + 12 * index); ((AbstractStruct)datatype).realignStructOffsets(); @@ -90,8 +90,8 @@ public int read(ByteBuffer buffer, int offset) public void readMemorizedSpells(ByteBuffer buffer, int offset) throws Exception { - DecNumber firstSpell = (DecNumber)getAttribute(CRE_MEMORIZATION_SPELL_TABLE_INDEX); - DecNumber numSpell = (DecNumber)getAttribute(CRE_MEMORIZATION_SPELL_COUNT); + IsNumeric firstSpell = (IsNumeric)getAttribute(CRE_MEMORIZATION_SPELL_TABLE_INDEX); + IsNumeric numSpell = (IsNumeric)getAttribute(CRE_MEMORIZATION_SPELL_COUNT); for (int i = 0; i < numSpell.getValue(); i++) { addField(new MemorizedSpells(this, buffer, offset + 12 * (firstSpell.getValue() + i))); } diff --git a/src/org/infinity/resource/cre/ViewerItems.java b/src/org/infinity/resource/cre/ViewerItems.java index ebe4efa72..b21147268 100644 --- a/src/org/infinity/resource/cre/ViewerItems.java +++ b/src/org/infinity/resource/cre/ViewerItems.java @@ -26,7 +26,6 @@ import javax.swing.table.DefaultTableCellRenderer; import org.infinity.datatype.DecNumber; -import org.infinity.datatype.HexNumber; import org.infinity.datatype.IsNumeric; import org.infinity.datatype.ResourceRef; import org.infinity.gui.ToolTipTableCellRenderer; @@ -48,7 +47,7 @@ final class ViewerItems extends JPanel implements ActionListener, ListSelectionL { super(new BorderLayout(0, 3)); final List items = new ArrayList<>(); - HexNumber slots_offset = (HexNumber)cre.getAttribute(CreResource.CRE_OFFSET_ITEM_SLOTS); + IsNumeric slots_offset = (IsNumeric)cre.getAttribute(CreResource.CRE_OFFSET_ITEM_SLOTS); for (final StructEntry entry : cre.getFields()) { if (entry instanceof Item) items.add((Item)entry); diff --git a/src/org/infinity/resource/cre/decoder/internal/CreatureInfo.java b/src/org/infinity/resource/cre/decoder/internal/CreatureInfo.java index 8a8191b03..7fe940ac9 100644 --- a/src/org/infinity/resource/cre/decoder/internal/CreatureInfo.java +++ b/src/org/infinity/resource/cre/decoder/internal/CreatureInfo.java @@ -697,7 +697,7 @@ private void initEffect(AbstractStruct as) int ofsParam2 = (fxType == 1) ? 0x18 : 0x08; // int ofsSpecial = (fxType == 1) ? 0x40 : 0x2c; - int opcode = ((EffectType)se).getValue(); + int opcode = ((IsNumeric)se).getValue(); switch (opcode) { case 7: // Set color { diff --git a/src/org/infinity/resource/dlg/DlgItem.java b/src/org/infinity/resource/dlg/DlgItem.java index 5ed963f88..99c0629f4 100644 --- a/src/org/infinity/resource/dlg/DlgItem.java +++ b/src/org/infinity/resource/dlg/DlgItem.java @@ -15,6 +15,7 @@ import javax.swing.tree.TreeNode; import org.infinity.datatype.Flag; +import org.infinity.datatype.IsNumeric; import org.infinity.datatype.SectionCount; import org.infinity.gui.BrowserMenuBar; import org.infinity.icon.Icons; @@ -124,8 +125,8 @@ public DlgItem(DlgTreeModel parent, DlgResource dlg) private int getAttribute(String attrName) { final StructEntry entry = getDialog().getAttribute(attrName, false); - if (entry instanceof SectionCount) { - return ((SectionCount)entry).getValue(); + if (entry instanceof IsNumeric) { + return ((IsNumeric)entry).getValue(); } return 0; } diff --git a/src/org/infinity/resource/dlg/DlgResource.java b/src/org/infinity/resource/dlg/DlgResource.java index 455cd1ca9..6307a127f 100644 --- a/src/org/infinity/resource/dlg/DlgResource.java +++ b/src/org/infinity/resource/dlg/DlgResource.java @@ -471,9 +471,9 @@ private void updateReferences(AddRemovable datatype, boolean added) { if (datatype instanceof StateTrigger) { StateTrigger trigger = (StateTrigger)datatype; - int ofsStates = ((SectionOffset)getAttribute(DLG_OFFSET_STATES)).getValue(); - int numStates = ((SectionCount)getAttribute(DLG_NUM_STATES)).getValue(); - int ofsTriggers = ((SectionOffset)getAttribute(DLG_OFFSET_STATE_TRIGGERS)).getValue(); + int ofsStates = ((IsNumeric)getAttribute(DLG_OFFSET_STATES)).getValue(); + int numStates = ((IsNumeric)getAttribute(DLG_NUM_STATES)).getValue(); + int ofsTriggers = ((IsNumeric)getAttribute(DLG_OFFSET_STATE_TRIGGERS)).getValue(); int idxTrigger = (trigger.getOffset() - ofsTriggers) / trigger.getSize(); // adjusting state trigger references @@ -500,9 +500,9 @@ private void updateReferences(AddRemovable datatype, boolean added) } } else if (datatype instanceof ResponseTrigger) { ResponseTrigger trigger = (ResponseTrigger)datatype; - int ofsTrans = ((SectionOffset)getAttribute(DLG_OFFSET_RESPONSES)).getValue(); - int numTrans = ((SectionCount)getAttribute(DLG_NUM_RESPONSES)).getValue(); - int ofsTriggers = ((SectionOffset)getAttribute(DLG_OFFSET_RESPONSE_TRIGGERS)).getValue(); + int ofsTrans = ((IsNumeric)getAttribute(DLG_OFFSET_RESPONSES)).getValue(); + int numTrans = ((IsNumeric)getAttribute(DLG_NUM_RESPONSES)).getValue(); + int ofsTriggers = ((IsNumeric)getAttribute(DLG_OFFSET_RESPONSE_TRIGGERS)).getValue(); int idxTrigger = (trigger.getOffset() - ofsTriggers) / trigger.getSize(); // adjusting response trigger references @@ -533,9 +533,9 @@ private void updateReferences(AddRemovable datatype, boolean added) } } else if (datatype instanceof Action) { Action action = (Action)datatype; - int ofsTrans = ((SectionOffset)getAttribute(DLG_OFFSET_RESPONSES)).getValue(); - int numTrans = ((SectionCount)getAttribute(DLG_NUM_RESPONSES)).getValue(); - int ofsActions = ((SectionOffset)getAttribute(DLG_OFFSET_ACTIONS)).getValue(); + int ofsTrans = ((IsNumeric)getAttribute(DLG_OFFSET_RESPONSES)).getValue(); + int numTrans = ((IsNumeric)getAttribute(DLG_NUM_RESPONSES)).getValue(); + int ofsActions = ((IsNumeric)getAttribute(DLG_OFFSET_ACTIONS)).getValue(); int idxAction = (action.getOffset() - ofsActions) / action.getSize(); // adjusting action references diff --git a/src/org/infinity/resource/dlg/State.java b/src/org/infinity/resource/dlg/State.java index c8022adba..0cff932eb 100644 --- a/src/org/infinity/resource/dlg/State.java +++ b/src/org/infinity/resource/dlg/State.java @@ -7,6 +7,7 @@ import java.nio.ByteBuffer; import org.infinity.datatype.DecNumber; +import org.infinity.datatype.IsNumeric; import org.infinity.datatype.StringRef; import org.infinity.resource.AbstractStruct; import org.infinity.resource.AddRemovable; @@ -56,7 +57,7 @@ public StringRef getAssociatedText() public int getFirstTrans() { - return ((DecNumber)getAttribute(DLG_STATE_FIRST_RESPONSE_INDEX, false)).getValue(); + return ((IsNumeric)getAttribute(DLG_STATE_FIRST_RESPONSE_INDEX, false)).getValue(); } public int getNumber() @@ -66,12 +67,12 @@ public int getNumber() public int getTransCount() { - return ((DecNumber)getAttribute(DLG_STATE_NUM_RESPONSES, false)).getValue(); + return ((IsNumeric)getAttribute(DLG_STATE_NUM_RESPONSES, false)).getValue(); } public int getTriggerIndex() { - return ((DecNumber)getAttribute(DLG_STATE_TRIGGER_INDEX, false)).getValue(); + return ((IsNumeric)getAttribute(DLG_STATE_TRIGGER_INDEX, false)).getValue(); } //--------------------- Begin Interface AddRemovable --------------------- diff --git a/src/org/infinity/resource/dlg/Transition.java b/src/org/infinity/resource/dlg/Transition.java index ecce51b3d..993e5eb7a 100644 --- a/src/org/infinity/resource/dlg/Transition.java +++ b/src/org/infinity/resource/dlg/Transition.java @@ -8,6 +8,7 @@ import org.infinity.datatype.DecNumber; import org.infinity.datatype.Flag; +import org.infinity.datatype.IsNumeric; import org.infinity.datatype.ResourceRef; import org.infinity.datatype.StringRef; import org.infinity.resource.AbstractStruct; @@ -64,7 +65,7 @@ public StringRef getAssociatedText() public int getActionIndex() { if (getFlag().isFlagSet(2)) { - return ((DecNumber)getAttribute(DLG_TRANS_ACTION_INDEX, false)).getValue(); + return ((IsNumeric)getAttribute(DLG_TRANS_ACTION_INDEX, false)).getValue(); } else { return -1; } @@ -87,7 +88,7 @@ public ResourceRef getNextDialog() public int getNextDialogState() { - return ((DecNumber)getAttribute(DLG_TRANS_NEXT_DIALOG_STATE, false)).getValue(); + return ((IsNumeric)getAttribute(DLG_TRANS_NEXT_DIALOG_STATE, false)).getValue(); } public int getNumber() @@ -98,7 +99,7 @@ public int getNumber() public int getTriggerIndex() { if (getFlag().isFlagSet(1)) { - return ((DecNumber)getAttribute(DLG_TRANS_TRIGGER_INDEX, false)).getValue(); + return ((IsNumeric)getAttribute(DLG_TRANS_TRIGGER_INDEX, false)).getValue(); } else { return -1; } diff --git a/src/org/infinity/resource/itm/ItmResource.java b/src/org/infinity/resource/itm/ItmResource.java index f0351bebd..ff3d15e13 100644 --- a/src/org/infinity/resource/itm/ItmResource.java +++ b/src/org/infinity/resource/itm/ItmResource.java @@ -19,6 +19,7 @@ import org.infinity.datatype.DecNumber; import org.infinity.datatype.Flag; import org.infinity.datatype.IdsBitmap; +import org.infinity.datatype.IsNumeric; import org.infinity.datatype.ResourceRef; import org.infinity.datatype.SectionCount; import org.infinity.datatype.SectionOffset; @@ -331,7 +332,7 @@ protected void datatypeAdded(AddRemovable datatype) } } else if (datatype instanceof Ability) { - int effect_count = ((SectionCount)getAttribute(ITM_NUM_GLOBAL_EFFECTS)).getValue(); + int effect_count = ((IsNumeric)getAttribute(ITM_NUM_GLOBAL_EFFECTS)).getValue(); for (final StructEntry o : getFields()) { if (o instanceof Ability) { Ability ability = (Ability)o; @@ -362,7 +363,7 @@ protected void datatypeRemoved(AddRemovable datatype) } } else if (datatype instanceof Ability) { - int effect_count = ((SectionCount)getAttribute(ITM_NUM_GLOBAL_EFFECTS)).getValue(); + int effect_count = ((IsNumeric)getAttribute(ITM_NUM_GLOBAL_EFFECTS)).getValue(); for (final StructEntry o : getFields()) { if (o instanceof Ability) { Ability ability = (Ability)o; @@ -593,8 +594,8 @@ public static boolean matchSearchOptions(ResourceEntry entry, SearchOptions sear Object o; // preparing substructures - DecNumber ofs = (DecNumber)itm.getAttribute(ITM_OFFSET_EFFECTS, false); - DecNumber cnt = (DecNumber)itm.getAttribute(ITM_NUM_GLOBAL_EFFECTS, false); + IsNumeric ofs = (IsNumeric)itm.getAttribute(ITM_OFFSET_EFFECTS, false); + IsNumeric cnt = (IsNumeric)itm.getAttribute(ITM_NUM_GLOBAL_EFFECTS, false); if (ofs != null && ofs.getValue() > 0 && cnt != null && cnt.getValue() > 0) { effects = new Effect[cnt.getValue()]; for (int idx = 0; idx < cnt.getValue(); idx++) { @@ -605,8 +606,8 @@ public static boolean matchSearchOptions(ResourceEntry entry, SearchOptions sear effects = new Effect[0]; } - ofs = (DecNumber)itm.getAttribute(ITM_OFFSET_ABILITIES, false); - cnt = (DecNumber)itm.getAttribute(ITM_NUM_ABILITIES, false); + ofs = (IsNumeric)itm.getAttribute(ITM_OFFSET_ABILITIES, false); + cnt = (IsNumeric)itm.getAttribute(ITM_NUM_ABILITIES, false); if (ofs != null && ofs.getValue() > 0 && cnt != null && cnt.getValue() > 0) { abilities = new Ability[cnt.getValue()]; for (int idx = 0; idx < cnt.getValue(); idx++) { @@ -620,7 +621,7 @@ public static boolean matchSearchOptions(ResourceEntry entry, SearchOptions sear abilityEffects = new Effect[abilities.length][]; for (int idx = 0; idx < abilities.length; idx++) { if (abilities[idx] != null) { - cnt = (DecNumber)abilities[idx].getAttribute(AbstractAbility.ABILITY_NUM_EFFECTS, false); + cnt = (IsNumeric)abilities[idx].getAttribute(AbstractAbility.ABILITY_NUM_EFFECTS, false); if (cnt != null && cnt.getValue() > 0) { abilityEffects[idx] = new Effect[cnt.getValue()]; for (int idx2 = 0; idx2 < cnt.getValue(); idx2++) { diff --git a/src/org/infinity/resource/pro/ProAreaType.java b/src/org/infinity/resource/pro/ProAreaType.java index fcb037e74..2b6f95285 100644 --- a/src/org/infinity/resource/pro/ProAreaType.java +++ b/src/org/infinity/resource/pro/ProAreaType.java @@ -11,6 +11,7 @@ import org.infinity.datatype.Flag; import org.infinity.datatype.HashBitmap; import org.infinity.datatype.IdsBitmap; +import org.infinity.datatype.IsNumeric; import org.infinity.datatype.ProRef; import org.infinity.datatype.ResourceRef; import org.infinity.datatype.Unknown; @@ -114,8 +115,8 @@ public boolean canRemove() public boolean valueUpdated(UpdateEvent event) { boolean retVal = false; - if (event.getSource() instanceof DecNumber && ((DecNumber)event.getSource()).getName().equals(PRO_AREA_RAY_COUNT)) { - int rayCount = ((DecNumber)event.getSource()).getValue(); + if (event.getSource() instanceof IsNumeric && ((StructEntry)event.getSource()).getName().equals(PRO_AREA_RAY_COUNT)) { + int rayCount = ((IsNumeric)event.getSource()).getValue(); AbstractStruct struct = event.getStructure(); if (rayCount > 0) { StructEntry field = struct.getField(null, 0x224); diff --git a/src/org/infinity/resource/spl/SplResource.java b/src/org/infinity/resource/spl/SplResource.java index 6a523c06e..4447a7f50 100644 --- a/src/org/infinity/resource/spl/SplResource.java +++ b/src/org/infinity/resource/spl/SplResource.java @@ -275,7 +275,7 @@ protected void datatypeAdded(AddRemovable datatype) } } else if (datatype instanceof Ability) { - int effect_count = ((SectionCount)getAttribute(SPL_NUM_GLOBAL_EFFECTS)).getValue(); + int effect_count = ((IsNumeric)getAttribute(SPL_NUM_GLOBAL_EFFECTS)).getValue(); for (final StructEntry o : getFields()) { if (o instanceof Ability) { Ability ability = (Ability)o; @@ -306,7 +306,7 @@ protected void datatypeRemoved(AddRemovable datatype) } } else if (datatype instanceof Ability) { - int effect_count = ((SectionCount)getAttribute(SPL_NUM_GLOBAL_EFFECTS)).getValue(); + int effect_count = ((IsNumeric)getAttribute(SPL_NUM_GLOBAL_EFFECTS)).getValue(); for (final StructEntry o : getFields()) { if (o instanceof Ability) { Ability ability = (Ability)o; @@ -444,8 +444,8 @@ public static boolean matchSearchOptions(ResourceEntry entry, SearchOptions sear Object o; // preparing substructures - DecNumber ofs = (DecNumber)spl.getAttribute(SPL_OFFSET_EFFECTS, false); - DecNumber cnt = (DecNumber)spl.getAttribute(SPL_NUM_GLOBAL_EFFECTS, false); + IsNumeric ofs = (IsNumeric)spl.getAttribute(SPL_OFFSET_EFFECTS, false); + IsNumeric cnt = (IsNumeric)spl.getAttribute(SPL_NUM_GLOBAL_EFFECTS, false); if (ofs != null && ofs.getValue() > 0 && cnt != null && cnt.getValue() > 0) { effects = new Effect[cnt.getValue()]; for (int idx = 0; idx < cnt.getValue(); idx++) { @@ -456,8 +456,8 @@ public static boolean matchSearchOptions(ResourceEntry entry, SearchOptions sear effects = new Effect[0]; } - ofs = (DecNumber)spl.getAttribute(SPL_OFFSET_ABILITIES, false); - cnt = (DecNumber)spl.getAttribute(SPL_NUM_ABILITIES, false); + ofs = (IsNumeric)spl.getAttribute(SPL_OFFSET_ABILITIES, false); + cnt = (IsNumeric)spl.getAttribute(SPL_NUM_ABILITIES, false); if (ofs != null && ofs.getValue() > 0 && cnt != null && cnt.getValue() > 0) { abilities = new Ability[cnt.getValue()]; for (int idx = 0; idx < cnt.getValue(); idx++) { @@ -471,7 +471,7 @@ public static boolean matchSearchOptions(ResourceEntry entry, SearchOptions sear abilityEffects = new Effect[abilities.length][]; for (int idx = 0; idx < abilities.length; idx++) { if (abilities[idx] != null) { - cnt = (DecNumber)abilities[idx].getAttribute(AbstractAbility.ABILITY_NUM_EFFECTS, false); + cnt = (IsNumeric)abilities[idx].getAttribute(AbstractAbility.ABILITY_NUM_EFFECTS, false); if (cnt != null && cnt.getValue() > 0) { abilityEffects[idx] = new Effect[cnt.getValue()]; for (int idx2 = 0; idx2 < cnt.getValue(); idx2++) { diff --git a/src/org/infinity/resource/vertex/Vertex.java b/src/org/infinity/resource/vertex/Vertex.java index 8ea445bea..1465f2196 100644 --- a/src/org/infinity/resource/vertex/Vertex.java +++ b/src/org/infinity/resource/vertex/Vertex.java @@ -8,6 +8,7 @@ import java.util.List; import org.infinity.datatype.DecNumber; +import org.infinity.datatype.IsNumeric; import org.infinity.resource.AbstractStruct; import org.infinity.resource.AddRemovable; import org.infinity.util.io.StreamUtils; @@ -56,7 +57,7 @@ public int read(ByteBuffer buffer, int offset) /** Returns the x coordinate of the vertex. */ public int getX() { - DecNumber dn = (DecNumber)getAttribute(getOffset(), false); + IsNumeric dn = (IsNumeric)getAttribute(getOffset(), false); if (dn != null) { return dn.getValue(); } else { @@ -76,7 +77,7 @@ public void setX(int value) /** Returns the y coordinate of the vertex. */ public int getY() { - DecNumber dn = (DecNumber)getAttribute(getOffset() + 2, false); + IsNumeric dn = (IsNumeric)getAttribute(getOffset() + 2, false); if (dn != null) { return dn.getValue(); } else { diff --git a/src/org/infinity/resource/wed/Door.java b/src/org/infinity/resource/wed/Door.java index 88573be7b..ede8c2ca9 100644 --- a/src/org/infinity/resource/wed/Door.java +++ b/src/org/infinity/resource/wed/Door.java @@ -8,7 +8,7 @@ import org.infinity.datatype.Bitmap; import org.infinity.datatype.DecNumber; -import org.infinity.datatype.HexNumber; +import org.infinity.datatype.IsNumeric; import org.infinity.datatype.RemovableDecNumber; import org.infinity.datatype.SectionCount; import org.infinity.datatype.SectionOffset; @@ -70,7 +70,7 @@ public boolean canRemove() protected void setAddRemovableOffset(AddRemovable datatype) { if (datatype instanceof RemovableDecNumber) { - final int offset = ((HexNumber)getParent().getAttribute(WedResource.WED_OFFSET_DOOR_TILEMAP_LOOKUP)).getValue(); + final int offset = ((IsNumeric)getParent().getAttribute(WedResource.WED_OFFSET_DOOR_TILEMAP_LOOKUP)).getValue(); int index = getTilemapIndex().getValue(); datatype.setOffset(offset + index * 2); } @@ -147,7 +147,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception } if (getParent() != null) { - final HexNumber offsetTileCell = (HexNumber)getParent().getAttribute(WedResource.WED_OFFSET_DOOR_TILEMAP_LOOKUP); + final IsNumeric offsetTileCell = (IsNumeric)getParent().getAttribute(WedResource.WED_OFFSET_DOOR_TILEMAP_LOOKUP); for (int i = 0; i < countTileCell.getValue(); i++) { addField(new RemovableDecNumber(buffer, offsetTileCell.getValue() + 2 * (indexTileCell.getValue() + i), 2, diff --git a/src/org/infinity/resource/wed/Polygon.java b/src/org/infinity/resource/wed/Polygon.java index 31bfc8f6d..0a06bd54e 100644 --- a/src/org/infinity/resource/wed/Polygon.java +++ b/src/org/infinity/resource/wed/Polygon.java @@ -8,7 +8,7 @@ import org.infinity.datatype.DecNumber; import org.infinity.datatype.Flag; -import org.infinity.datatype.HexNumber; +import org.infinity.datatype.IsNumeric; import org.infinity.datatype.SectionCount; import org.infinity.datatype.Unknown; import org.infinity.resource.AbstractStruct; @@ -64,12 +64,12 @@ public boolean canRemove() protected void setAddRemovableOffset(AddRemovable datatype) { if (datatype instanceof Vertex) { - int index = ((DecNumber)getAttribute(WED_POLY_VERTEX_INDEX)).getValue(); - index += ((DecNumber)getAttribute(WED_POLY_NUM_VERTICES)).getValue(); + int index = ((IsNumeric)getAttribute(WED_POLY_VERTEX_INDEX)).getValue(); + index += ((IsNumeric)getAttribute(WED_POLY_NUM_VERTICES)).getValue(); AbstractStruct superStruct = getParent(); while (superStruct.getParent() != null) superStruct = superStruct.getParent(); - int offset = ((HexNumber)superStruct.getAttribute(WedResource.WED_OFFSET_VERTICES)).getValue(); + int offset = ((IsNumeric)superStruct.getAttribute(WedResource.WED_OFFSET_VERTICES)).getValue(); datatype.setOffset(offset + 4 * index); ((AbstractStruct)datatype).realignStructOffsets(); } @@ -78,8 +78,8 @@ protected void setAddRemovableOffset(AddRemovable datatype) public void readVertices(ByteBuffer buffer, int offset) throws Exception { - DecNumber firstVertex = (DecNumber)getAttribute(WED_POLY_VERTEX_INDEX); - DecNumber numVertices = (DecNumber)getAttribute(WED_POLY_NUM_VERTICES); + IsNumeric firstVertex = (IsNumeric)getAttribute(WED_POLY_VERTEX_INDEX); + IsNumeric numVertices = (IsNumeric)getAttribute(WED_POLY_NUM_VERTICES); for (int i = 0; i < numVertices.getValue(); i++) { addField(new Vertex(this, buffer, offset + 4 * (firstVertex.getValue() + i), i)); } diff --git a/src/org/infinity/resource/wed/Tilemap.java b/src/org/infinity/resource/wed/Tilemap.java index efee936d3..48bf32e07 100644 --- a/src/org/infinity/resource/wed/Tilemap.java +++ b/src/org/infinity/resource/wed/Tilemap.java @@ -8,6 +8,7 @@ import org.infinity.datatype.DecNumber; import org.infinity.datatype.Flag; +import org.infinity.datatype.IsNumeric; import org.infinity.datatype.Unknown; import org.infinity.resource.AbstractStruct; @@ -32,7 +33,7 @@ public Tilemap(AbstractStruct superStruct, ByteBuffer buffer, int offset, int nu public int getTileCount() { - return ((DecNumber)getAttribute(WED_TILEMAP_TILE_COUNT_PRI)).getValue(); + return ((IsNumeric)getAttribute(WED_TILEMAP_TILE_COUNT_PRI)).getValue(); } @Override diff --git a/src/org/infinity/resource/wed/Wallgroup.java b/src/org/infinity/resource/wed/Wallgroup.java index 6d68239a8..0a29ad7d7 100644 --- a/src/org/infinity/resource/wed/Wallgroup.java +++ b/src/org/infinity/resource/wed/Wallgroup.java @@ -7,6 +7,7 @@ import java.nio.ByteBuffer; import org.infinity.datatype.DecNumber; +import org.infinity.datatype.IsNumeric; import org.infinity.resource.AbstractStruct; import org.infinity.resource.AddRemovable; import org.infinity.util.io.StreamUtils; @@ -30,8 +31,8 @@ public Wallgroup(AbstractStruct superStruct, ByteBuffer buffer, int offset, int public int getNextPolygonIndex() { - int count = ((DecNumber)getAttribute(WED_WALLGROUP_NUM_POLYGONS)).getValue(); - int index = ((DecNumber)getAttribute(WED_WALLGROUP_POLYGON_INDEX)).getValue(); + int count = ((IsNumeric)getAttribute(WED_WALLGROUP_NUM_POLYGONS)).getValue(); + int index = ((IsNumeric)getAttribute(WED_WALLGROUP_POLYGON_INDEX)).getValue(); return count + index; } diff --git a/src/org/infinity/resource/wed/WedResource.java b/src/org/infinity/resource/wed/WedResource.java index e0b0909aa..b7d3c42ad 100644 --- a/src/org/infinity/resource/wed/WedResource.java +++ b/src/org/infinity/resource/wed/WedResource.java @@ -15,6 +15,7 @@ import org.infinity.datatype.DecNumber; import org.infinity.datatype.HexNumber; +import org.infinity.datatype.IsNumeric; import org.infinity.datatype.RemovableDecNumber; import org.infinity.datatype.SectionCount; import org.infinity.datatype.SectionOffset; @@ -331,8 +332,8 @@ private void updateSectionOffsets(AddRemovable datatype, int size) } // Assumes polygon offset is correct - int offset = ((SectionOffset)getAttribute(WED_OFFSET_WALL_POLYGONS)).getValue(); - offset += ((SectionCount)getAttribute(WED_NUM_WALL_POLYGONS)).getValue() * 18; + int offset = ((IsNumeric)getAttribute(WED_OFFSET_WALL_POLYGONS)).getValue(); + offset += ((IsNumeric)getAttribute(WED_NUM_WALL_POLYGONS)).getValue() * 18; for (final StructEntry o : getFields()) { if (o instanceof Door) { ((Door)o).updatePolygonsOffset(offset); @@ -343,7 +344,7 @@ private void updateSectionOffsets(AddRemovable datatype, int size) private void updateVertices() { // Assumes vertices offset is correct - int offset = ((HexNumber)getAttribute(WED_OFFSET_VERTICES)).getValue(); + int offset = ((IsNumeric)getAttribute(WED_OFFSET_VERTICES)).getValue(); int count = 0; for (final StructEntry o : getFields()) { if (o instanceof Polygon) { diff --git a/src/org/infinity/resource/wmp/AreaEntry.java b/src/org/infinity/resource/wmp/AreaEntry.java index 5cdd2d189..3b6a03e77 100644 --- a/src/org/infinity/resource/wmp/AreaEntry.java +++ b/src/org/infinity/resource/wmp/AreaEntry.java @@ -10,6 +10,7 @@ import org.infinity.datatype.DecNumber; import org.infinity.datatype.Flag; +import org.infinity.datatype.IsNumeric; import org.infinity.datatype.ResourceRef; import org.infinity.datatype.SectionCount; import org.infinity.datatype.StringRef; @@ -105,29 +106,29 @@ public int read(ByteBuffer buffer, int offset) throws Exception void readLinks(ByteBuffer buffer, DecNumber linkOffset) throws Exception { - DecNumber northStart = (DecNumber)getAttribute(WMP_AREA_FIRST_LINK_NORTH); - DecNumber northCount = (DecNumber)getAttribute(WMP_AREA_NUM_LINKS_NORTH); + IsNumeric northStart = (IsNumeric)getAttribute(WMP_AREA_FIRST_LINK_NORTH); + IsNumeric northCount = (IsNumeric)getAttribute(WMP_AREA_NUM_LINKS_NORTH); int offset = linkOffset.getValue() + northStart.getValue() * 216; for (int i = 0; i < northCount.getValue(); i++) { addField(new AreaLinkNorth(this, buffer, offset + i * 216, i)); } - DecNumber westStart = (DecNumber)getAttribute(WMP_AREA_FIRST_LINK_WEST); - DecNumber westCount = (DecNumber)getAttribute(WMP_AREA_NUM_LINKS_WEST); + IsNumeric westStart = (IsNumeric)getAttribute(WMP_AREA_FIRST_LINK_WEST); + IsNumeric westCount = (IsNumeric)getAttribute(WMP_AREA_NUM_LINKS_WEST); offset = linkOffset.getValue() + westStart.getValue() * 216; for (int i = 0; i < westCount.getValue(); i++) { addField(new AreaLinkWest(this, buffer, offset + i * 216, i)); } - DecNumber southStart = (DecNumber)getAttribute(WMP_AREA_FIRST_LINK_SOUTH); - DecNumber southCount = (DecNumber)getAttribute(WMP_AREA_NUM_LINKS_SOUTH); + IsNumeric southStart = (IsNumeric)getAttribute(WMP_AREA_FIRST_LINK_SOUTH); + IsNumeric southCount = (IsNumeric)getAttribute(WMP_AREA_NUM_LINKS_SOUTH); offset = linkOffset.getValue() + southStart.getValue() * 216; for (int i = 0; i < southCount.getValue(); i++) { addField(new AreaLinkSouth(this, buffer, offset + i * 216, i)); } - DecNumber eastStart = (DecNumber)getAttribute(WMP_AREA_FIRST_LINK_EAST); - DecNumber eastCount = (DecNumber)getAttribute(WMP_AREA_NUM_LINKS_EAST); + IsNumeric eastStart = (IsNumeric)getAttribute(WMP_AREA_FIRST_LINK_EAST); + IsNumeric eastCount = (IsNumeric)getAttribute(WMP_AREA_NUM_LINKS_EAST); offset = linkOffset.getValue() + eastStart.getValue() * 216; for (int i = 0; i < eastCount.getValue(); i++) { addField(new AreaLinkEast(this, buffer, offset + i * 216, i)); diff --git a/src/org/infinity/resource/wmp/ViewerArea.java b/src/org/infinity/resource/wmp/ViewerArea.java index 4ae0d0d76..605de4bfc 100644 --- a/src/org/infinity/resource/wmp/ViewerArea.java +++ b/src/org/infinity/resource/wmp/ViewerArea.java @@ -17,8 +17,8 @@ import javax.swing.JList; import javax.swing.JPanel; -import org.infinity.datatype.DecNumber; import org.infinity.datatype.Flag; +import org.infinity.datatype.IsNumeric; import org.infinity.datatype.ResourceRef; import org.infinity.gui.ViewFrame; import org.infinity.gui.ViewerUtil; @@ -48,7 +48,7 @@ private static JPanel makeInfoPanel(AreaEntry areaEntry) JPanel infoPane = makeInfoPanel(areaEntry); JComponent icon = ViewerUtil.makeBamPanel( (ResourceRef)areaEntry.getParent().getAttribute(MapEntry.WMP_MAP_ICONS), - ((DecNumber)areaEntry.getAttribute(AreaEntry.WMP_AREA_ICON_INDEX)).getValue(), + ((IsNumeric)areaEntry.getAttribute(AreaEntry.WMP_AREA_ICON_INDEX)).getValue(), 0); JPanel linkPanelN = ViewerUtil.makeListPanel("North links", areaEntry, AreaLinkNorth.class, AreaLink.WMP_LINK_TARGET_ENTRANCE); JPanel linkPanelS = ViewerUtil.makeListPanel("South links", areaEntry, AreaLinkSouth.class, AreaLink.WMP_LINK_TARGET_ENTRANCE); diff --git a/src/org/infinity/resource/wmp/ViewerMap.java b/src/org/infinity/resource/wmp/ViewerMap.java index 836134c74..9221d6f20 100644 --- a/src/org/infinity/resource/wmp/ViewerMap.java +++ b/src/org/infinity/resource/wmp/ViewerMap.java @@ -44,12 +44,9 @@ import javax.swing.event.ListSelectionListener; import org.infinity.NearInfinity; -import org.infinity.datatype.DecNumber; import org.infinity.datatype.Flag; import org.infinity.datatype.IsNumeric; -import org.infinity.datatype.ResourceRef; -import org.infinity.datatype.SectionCount; -import org.infinity.datatype.SectionOffset; +import org.infinity.datatype.IsReference; import org.infinity.datatype.StringRef; import org.infinity.gui.BrowserMenuBar; import org.infinity.gui.RenderCanvas; @@ -136,7 +133,7 @@ private enum Direction { NORTH, WEST, SOUTH, EAST } try { mapIcons = null; - ResourceRef iconRef = (ResourceRef)wmpMap.getAttribute(MapEntry.WMP_MAP_ICONS); + IsReference iconRef = (IsReference)wmpMap.getAttribute(MapEntry.WMP_MAP_ICONS); if (iconRef != null) { ResourceEntry iconEntry = ResourceFactory.getResourceEntry(iconRef.getResourceName()); if (iconEntry != null) { @@ -204,7 +201,7 @@ private MapEntry getEntry() /** Load and return map graphics. */ private BufferedImage loadMap() { - String mapName = ((ResourceRef)getEntry().getAttribute(MapEntry.WMP_MAP_RESREF)).getResourceName(); + String mapName = ((IsReference)getEntry().getAttribute(MapEntry.WMP_MAP_RESREF)).getResourceName(); if (ResourceFactory.resourceExists(mapName)) { MosDecoder mos = MosDecoder.loadMos(ResourceFactory.getResourceEntry(mapName)); if (mos != null) { @@ -246,7 +243,7 @@ private void showMapIcons() for (int i = 0, count = listPanel.getList().getModel().getSize(); i < count; i++) { AreaEntry area = getAreaEntry(i, true); if (area != null) { - int iconIndex = ((DecNumber)area.getAttribute(AreaEntry.WMP_AREA_ICON_INDEX)).getValue(); + int iconIndex = ((IsNumeric)area.getAttribute(AreaEntry.WMP_AREA_ICON_INDEX)).getValue(); int frameIndex = mapIconsCtrl.cycleGetFrameIndexAbsolute(iconIndex, 0); if (frameIndex >= 0) { final Image mapIcon = mapIcons.frameGet(mapIconsCtrl, frameIndex); @@ -280,13 +277,13 @@ private void showMapIconLabels() for (int i = 0, count = listPanel.getList().getModel().getSize(); i < count; i++) { AreaEntry area = getAreaEntry(i, true); if (area != null) { - int iconIndex = ((DecNumber)area.getAttribute(AreaEntry.WMP_AREA_ICON_INDEX)).getValue(); + int iconIndex = ((IsNumeric)area.getAttribute(AreaEntry.WMP_AREA_ICON_INDEX)).getValue(); int frameIndex = mapIconsCtrl.cycleGetFrameIndexAbsolute(iconIndex, 0); if (frameIndex >= 0) { // getting area name - int strref = ((StringRef)area.getAttribute(AreaEntry.WMP_AREA_NAME)).getValue(); + int strref = ((IsNumeric)area.getAttribute(AreaEntry.WMP_AREA_NAME)).getValue(); if (strref < 0) { - strref = ((StringRef)area.getAttribute(AreaEntry.WMP_AREA_TOOLTIP)).getValue(); + strref = ((IsNumeric)area.getAttribute(AreaEntry.WMP_AREA_TOOLTIP)).getValue(); } String mapName = (strref >= 0) ? StringTable.getStringRef(strref) : null; if (mapName != null && mapName.trim().length() == 0) { @@ -294,7 +291,7 @@ private void showMapIconLabels() } // getting area code - String mapCode = ((ResourceRef)area.getAttribute(AreaEntry.WMP_AREA_CURRENT)).getResourceName(); + String mapCode = ((IsReference)area.getAttribute(AreaEntry.WMP_AREA_CURRENT)).getResourceName(); if (ResourceFactory.resourceExists(mapCode)) { mapCode = mapCode.replace(".ARE", ""); } else { @@ -357,16 +354,16 @@ private void showMapDistances(int areaIndex) final Color[] dirColor = { Color.GREEN, Color.RED, Color.CYAN, Color.YELLOW }; final int[] links = new int[8]; final int linkSize = 216; // size of a single area link structure - int ofsLinkBase = ((SectionOffset)getEntry().getAttribute(MapEntry.WMP_MAP_OFFSET_AREA_LINKS)).getValue(); - - links[0] = ((DecNumber)area.getAttribute(AreaEntry.WMP_AREA_FIRST_LINK_NORTH)).getValue(); - links[1] = ((SectionCount)area.getAttribute(AreaEntry.WMP_AREA_NUM_LINKS_NORTH)).getValue(); - links[2] = ((DecNumber)area.getAttribute(AreaEntry.WMP_AREA_FIRST_LINK_WEST)).getValue(); - links[3] = ((SectionCount)area.getAttribute(AreaEntry.WMP_AREA_NUM_LINKS_WEST)).getValue(); - links[4] = ((DecNumber)area.getAttribute(AreaEntry.WMP_AREA_FIRST_LINK_SOUTH)).getValue(); - links[5] = ((SectionCount)area.getAttribute(AreaEntry.WMP_AREA_NUM_LINKS_SOUTH)).getValue(); - links[6] = ((DecNumber)area.getAttribute(AreaEntry.WMP_AREA_FIRST_LINK_EAST)).getValue(); - links[7] = ((SectionCount)area.getAttribute(AreaEntry.WMP_AREA_NUM_LINKS_EAST)).getValue(); + int ofsLinkBase = ((IsNumeric)getEntry().getAttribute(MapEntry.WMP_MAP_OFFSET_AREA_LINKS)).getValue(); + + links[0] = ((IsNumeric)area.getAttribute(AreaEntry.WMP_AREA_FIRST_LINK_NORTH)).getValue(); + links[1] = ((IsNumeric)area.getAttribute(AreaEntry.WMP_AREA_NUM_LINKS_NORTH)).getValue(); + links[2] = ((IsNumeric)area.getAttribute(AreaEntry.WMP_AREA_FIRST_LINK_WEST)).getValue(); + links[3] = ((IsNumeric)area.getAttribute(AreaEntry.WMP_AREA_NUM_LINKS_WEST)).getValue(); + links[4] = ((IsNumeric)area.getAttribute(AreaEntry.WMP_AREA_FIRST_LINK_SOUTH)).getValue(); + links[5] = ((IsNumeric)area.getAttribute(AreaEntry.WMP_AREA_NUM_LINKS_SOUTH)).getValue(); + links[6] = ((IsNumeric)area.getAttribute(AreaEntry.WMP_AREA_FIRST_LINK_EAST)).getValue(); + links[7] = ((IsNumeric)area.getAttribute(AreaEntry.WMP_AREA_NUM_LINKS_EAST)).getValue(); for (int dir = 0; dir < srcDir.length; dir++) { Direction curDir = srcDir[dir]; Point ptOrigin = getMapIconCoordinate(areaIndex, curDir, true); @@ -375,7 +372,7 @@ private void showMapDistances(int areaIndex) AreaLink destLink = (AreaLink)area.getAttribute(ofsLink, false); if (destLink != null) { - int dstAreaIndex = ((DecNumber)destLink.getAttribute(AreaLink.WMP_LINK_TARGET_AREA)).getValue(); + int dstAreaIndex = ((IsNumeric)destLink.getAttribute(AreaLink.WMP_LINK_TARGET_AREA)).getValue(); Flag flag = (Flag)destLink.getAttribute(AreaLink.WMP_LINK_DEFAULT_ENTRANCE); Direction dstDir = Direction.NORTH; if (flag.isFlagSet(1)) { @@ -389,9 +386,9 @@ private void showMapDistances(int areaIndex) // checking for random encounters during travels boolean hasRandomEncounters = false; - if (((DecNumber)destLink.getAttribute(AreaLink.WMP_LINK_RANDOM_ENCOUNTER_PROBABILITY)).getValue() > 0) { + if (((IsNumeric)destLink.getAttribute(AreaLink.WMP_LINK_RANDOM_ENCOUNTER_PROBABILITY)).getValue() > 0) { for (int rnd = 1; rnd < 6; rnd++) { - String rndArea = ((ResourceRef)destLink + String rndArea = ((IsReference)destLink .getAttribute(String.format(AreaLink.WMP_LINK_RANDOM_ENCOUNTER_AREA_FMT, rnd))) .getResourceName(); if (ResourceFactory.resourceExists(rndArea)) { @@ -415,7 +412,7 @@ private void showMapDistances(int areaIndex) g.drawLine(ptOrigin.x, ptOrigin.y, ptTarget.x, ptTarget.y); // printing travel time (in hours) - String duration = String.format("%d h", ((DecNumber)destLink.getAttribute(AreaLink.WMP_LINK_DISTANCE_SCALE)).getValue() * 4); + String duration = String.format("%d h", ((IsNumeric)destLink.getAttribute(AreaLink.WMP_LINK_DISTANCE_SCALE)).getValue() * 4); LineMetrics lm = g.getFont().getLineMetrics(duration, g.getFontRenderContext()); Rectangle2D rectText = g.getFont().getStringBounds(duration, g.getFontRenderContext()); int textX = ptOrigin.x + ((ptTarget.x - ptOrigin.x) - rectText.getBounds().width) / 2; @@ -457,7 +454,7 @@ private Point getMapIconCoordinate(int areaIndex, Direction dir, boolean byPanel AreaEntry area = getAreaEntry(areaIndex, byPanel); if (area != null) { Point p = getAreaEntryPosition(area, isScaling()); - int iconIndex = ((DecNumber)area.getAttribute(AreaEntry.WMP_AREA_ICON_INDEX)).getValue(); + int iconIndex = ((IsNumeric)area.getAttribute(AreaEntry.WMP_AREA_ICON_INDEX)).getValue(); int frameIndex = mapIconsCtrl.cycleGetFrameIndexAbsolute(iconIndex, 0); int width, height; if (frameIndex >= 0) { @@ -519,9 +516,9 @@ private void showDot(AreaEntry entry, boolean restore) } if (entry != null) { storeDot(entry); - int x = ((DecNumber)entry.getAttribute(AreaEntry.WMP_AREA_COORDINATE_X)).getValue(); + int x = ((IsNumeric)entry.getAttribute(AreaEntry.WMP_AREA_COORDINATE_X)).getValue(); x = (int)(x * mapScaleX); - int y = ((DecNumber)entry.getAttribute(AreaEntry.WMP_AREA_COORDINATE_Y)).getValue(); + int y = ((IsNumeric)entry.getAttribute(AreaEntry.WMP_AREA_COORDINATE_Y)).getValue(); y = (int)(y * mapScaleY); int width = iconDot.getWidth(); int height = iconDot.getHeight(); @@ -542,9 +539,9 @@ private void showDot(AreaEntry entry, boolean restore) private void storeDot(AreaEntry entry) { if (entry != null) { - int x = ((DecNumber)entry.getAttribute(AreaEntry.WMP_AREA_COORDINATE_X)).getValue(); + int x = ((IsNumeric)entry.getAttribute(AreaEntry.WMP_AREA_COORDINATE_X)).getValue(); x = (int)(x * mapScaleX); - int y = ((DecNumber)entry.getAttribute(AreaEntry.WMP_AREA_COORDINATE_Y)).getValue(); + int y = ((IsNumeric)entry.getAttribute(AreaEntry.WMP_AREA_COORDINATE_Y)).getValue(); y = (int)(y * mapScaleY); int width = dotBackup.getWidth(); int height = dotBackup.getHeight(); @@ -814,7 +811,7 @@ public Component getListCellRendererComponent(JList list, Object value, int i label.setText(getListValue(value, true)); AbstractStruct struct = (AbstractStruct)value; - DecNumber animNr = (DecNumber)struct.getAttribute(AreaEntry.WMP_AREA_ICON_INDEX); + IsNumeric animNr = (IsNumeric)struct.getAttribute(AreaEntry.WMP_AREA_ICON_INDEX); setIcon(null); if (ctrl != null) { setIcon(new ImageIcon(bam.frameGet(ctrl, ctrl.cycleGetFrameIndexAbsolute(animNr.getValue(), 0)))); @@ -834,7 +831,7 @@ private String getListValue(Object value, boolean showFull) AbstractStruct struct = (AbstractStruct)value; StringRef areaName = (StringRef)struct.getAttribute(AreaEntry.WMP_AREA_NAME); - ResourceRef areaRef = (ResourceRef)struct.getAttribute(AreaEntry.WMP_AREA_CURRENT); + IsReference areaRef = (IsReference)struct.getAttribute(AreaEntry.WMP_AREA_CURRENT); String text1 = null, text2 = null; if (areaName.getValue() >= 0) { StringTable.Format fmt = BrowserMenuBar.getInstance().showStrrefs() ? StringTable.Format.STRREF_SUFFIX diff --git a/src/org/infinity/resource/wmp/WmpResource.java b/src/org/infinity/resource/wmp/WmpResource.java index 01c6bd020..81cb7a4a1 100644 --- a/src/org/infinity/resource/wmp/WmpResource.java +++ b/src/org/infinity/resource/wmp/WmpResource.java @@ -11,7 +11,7 @@ import javax.swing.JComponent; import javax.swing.JTabbedPane; -import org.infinity.datatype.DecNumber; +import org.infinity.datatype.IsNumeric; import org.infinity.datatype.SectionCount; import org.infinity.datatype.SectionOffset; import org.infinity.datatype.TextString; @@ -82,7 +82,7 @@ public JComponent getViewerTab(int index) case 0: { JTabbedPane tabbedPane = new JTabbedPane(); - int count = ((DecNumber)getAttribute(WMP_NUM_MAPS)).getValue(); + int count = ((IsNumeric)getAttribute(WMP_NUM_MAPS)).getValue(); for (int i = 0; i < count; i++) { MapEntry entry = (MapEntry)getAttribute(MapEntry.WMP_MAP + " " + i); tabbedPane.addTab(entry.getName(), entry.getViewerTab(0)); diff --git a/src/org/infinity/search/AbstractSearcher.java b/src/org/infinity/search/AbstractSearcher.java index ea57bf4e4..c81f8147d 100644 --- a/src/org/infinity/search/AbstractSearcher.java +++ b/src/org/infinity/search/AbstractSearcher.java @@ -7,8 +7,10 @@ import java.awt.Component; import java.util.List; import java.util.concurrent.ThreadPoolExecutor; + import javax.swing.JOptionPane; import javax.swing.ProgressMonitor; + import org.infinity.NearInfinity; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.Debugging; diff --git a/src/org/infinity/search/AttributeSearcher.java b/src/org/infinity/search/AttributeSearcher.java index 0f35a5180..9aeaa2a82 100644 --- a/src/org/infinity/search/AttributeSearcher.java +++ b/src/org/infinity/search/AttributeSearcher.java @@ -28,6 +28,7 @@ import javax.swing.JTextField; import org.infinity.datatype.DecNumber; +import org.infinity.datatype.IsNumeric; import org.infinity.gui.Center; import org.infinity.gui.ChildFrame; import org.infinity.icon.Icons; @@ -247,9 +248,9 @@ protected Runnable newWorker(ResourceEntry entry) if (rbexact.isSelected()) { hit = regPattern.matcher(searchEntry.toString()).matches(); } else if (rbless.isSelected()) { - hit = searchNumber > ((DecNumber)searchEntry).getValue(); + hit = searchNumber > ((IsNumeric)searchEntry).getValue(); } else if (rbgreater.isSelected()) { - hit = searchNumber < ((DecNumber)searchEntry).getValue(); + hit = searchNumber < ((IsNumeric)searchEntry).getValue(); } if (cbnot.isSelected()) { diff --git a/src/org/infinity/search/FileTypeSelector.java b/src/org/infinity/search/FileTypeSelector.java index ad4986dc0..aa379cf2b 100644 --- a/src/org/infinity/search/FileTypeSelector.java +++ b/src/org/infinity/search/FileTypeSelector.java @@ -15,10 +15,12 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; + import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JPanel; + import org.infinity.icon.Icons; import org.infinity.resource.ResourceFactory; import org.infinity.resource.key.ResourceEntry; diff --git a/src/org/infinity/search/advanced/AdvancedSearchWorker.java b/src/org/infinity/search/advanced/AdvancedSearchWorker.java index b51e104a7..e4a51c017 100644 --- a/src/org/infinity/search/advanced/AdvancedSearchWorker.java +++ b/src/org/infinity/search/advanced/AdvancedSearchWorker.java @@ -392,8 +392,7 @@ private boolean isMatchResource(StructEntry se, String resref, String ext) private boolean isMatchBitfield(StructEntry se, int value, SearchOptions.BitFieldMode mode) { if (se instanceof Flag) { - Flag flag = (Flag)se; - int bits = flag.getValue(); + int bits = ((IsNumeric)se).getValue(); switch (mode) { case Exact: return bits == value; From 2c28c8c1f00c927c3feffb9e054324aba562ca0d Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sun, 21 Mar 2021 18:48:49 +0100 Subject: [PATCH 070/158] SpriteDecoder: Refactor code --- .../are/viewer/ActorAnimationProvider.java | 5 +- .../resource/are/viewer/LayerObjectActor.java | 68 +- .../resource/cre/ViewerAnimation.java | 41 +- .../resource/cre/decoder/AmbientDecoder.java | 4 +- .../cre/decoder/AmbientStaticDecoder.java | 4 +- .../cre/decoder/ArmoredBaseDecoder.java | 7 +- .../cre/decoder/CharacterDecoder.java | 4 +- .../cre/decoder/CharacterOldDecoder.java | 5 +- .../resource/cre/decoder/EffectDecoder.java | 5 +- .../resource/cre/decoder/FlyingDecoder.java | 4 +- .../cre/decoder/MonsterAnkhegDecoder.java | 5 +- .../resource/cre/decoder/MonsterDecoder.java | 5 +- .../cre/decoder/MonsterIcewindDecoder.java | 4 +- .../cre/decoder/MonsterLarge16Decoder.java | 5 +- .../cre/decoder/MonsterLargeDecoder.java | 4 +- .../cre/decoder/MonsterLayeredDecoder.java | 4 +- .../decoder/MonsterLayeredSpellDecoder.java | 6 +- .../cre/decoder/MonsterMultiDecoder.java | 7 +- .../cre/decoder/MonsterMultiNewDecoder.java | 4 +- .../cre/decoder/MonsterOldDecoder.java | 4 +- .../cre/decoder/MonsterPlanescapeDecoder.java | 5 +- .../cre/decoder/MonsterQuadrantDecoder.java | 4 +- .../cre/decoder/QuadrantsBaseDecoder.java | 5 +- .../resource/cre/decoder/SpriteDecoder.java | 620 +----------------- .../cre/decoder/TownStaticDecoder.java | 4 +- .../cre/decoder/internal/AnimationInfo.java | 344 ++++++++++ .../resource/cre/decoder/internal/DirDef.java | 2 - .../cre/decoder/internal/Direction.java | 62 ++ .../cre/decoder/internal/NumberRange.java | 60 ++ .../resource/cre/decoder/internal/SeqDef.java | 2 - .../cre/decoder/internal/Sequence.java | 135 ++++ .../cre/decoder/tables/InfinityTables.java | 35 +- .../resource/cre/viewer/MediaPanel.java | 84 +-- 33 files changed, 830 insertions(+), 727 deletions(-) create mode 100644 src/org/infinity/resource/cre/decoder/internal/AnimationInfo.java create mode 100644 src/org/infinity/resource/cre/decoder/internal/Direction.java create mode 100644 src/org/infinity/resource/cre/decoder/internal/NumberRange.java create mode 100644 src/org/infinity/resource/cre/decoder/internal/Sequence.java diff --git a/src/org/infinity/resource/are/viewer/ActorAnimationProvider.java b/src/org/infinity/resource/are/viewer/ActorAnimationProvider.java index bd522ceaa..6be15ee4e 100644 --- a/src/org/infinity/resource/are/viewer/ActorAnimationProvider.java +++ b/src/org/infinity/resource/are/viewer/ActorAnimationProvider.java @@ -15,6 +15,7 @@ import java.util.Objects; import org.infinity.resource.cre.decoder.SpriteDecoder; +import org.infinity.resource.cre.decoder.internal.Direction; import org.infinity.resource.graphics.ColorConvert; import org.infinity.resource.graphics.PseudoBamDecoder.PseudoBamControl; @@ -125,10 +126,10 @@ public int getOrientation() */ public void setOrientation(int dir) { - dir = Math.abs(dir) % SpriteDecoder.Direction.values().length; + dir = Math.abs(dir) % Direction.values().length; if (dir != orientation) { orientation = dir; - SpriteDecoder.Direction direction = getDecoder().getExistingDirection(SpriteDecoder.Direction.from(orientation)); + Direction direction = getDecoder().getExistingDirection(Direction.from(orientation)); int idx = getDecoder().getDirectionMap().getOrDefault(direction, 0); setCycle(idx); } diff --git a/src/org/infinity/resource/are/viewer/LayerObjectActor.java b/src/org/infinity/resource/are/viewer/LayerObjectActor.java index a3ab7c66d..b1c99b48d 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectActor.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectActor.java @@ -15,6 +15,8 @@ import org.infinity.resource.AbstractStruct; import org.infinity.resource.cre.CreResource; import org.infinity.resource.cre.decoder.SpriteDecoder; +import org.infinity.resource.cre.decoder.internal.AnimationInfo; +import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.graphics.BamDecoder; /** @@ -33,35 +35,35 @@ protected enum Allegiance { protected static final Color COLOR_FRAME_HIGHLIGHTED = new Color(0xFF2020FF, false); // Default animation sequence to load if available; fall back to the first available sequence if no default is available - private static final SpriteDecoder.Sequence[] DEFAULT_SEQUENCE = { - SpriteDecoder.Sequence.STAND, - SpriteDecoder.Sequence.STAND2, - SpriteDecoder.Sequence.STAND3, - SpriteDecoder.Sequence.STAND_EMERGED, - SpriteDecoder.Sequence.PST_STAND, - SpriteDecoder.Sequence.STANCE, - SpriteDecoder.Sequence.STANCE2, - SpriteDecoder.Sequence.PST_STANCE, - SpriteDecoder.Sequence.WALK, - SpriteDecoder.Sequence.PST_WALK, + private static final Sequence[] DEFAULT_SEQUENCE = { + Sequence.STAND, + Sequence.STAND2, + Sequence.STAND3, + Sequence.STAND_EMERGED, + Sequence.PST_STAND, + Sequence.STANCE, + Sequence.STANCE2, + Sequence.PST_STANCE, + Sequence.WALK, + Sequence.PST_WALK, }; // Potential sequences for "death" state - private static final SpriteDecoder.Sequence[] DEATH_SEQUENCE = { - SpriteDecoder.Sequence.TWITCH, - SpriteDecoder.Sequence.DIE, - SpriteDecoder.Sequence.PST_DIE_FORWARD, - SpriteDecoder.Sequence.PST_DIE_BACKWARD, - SpriteDecoder.Sequence.PST_DIE_COLLAPSE, + private static final Sequence[] DEATH_SEQUENCE = { + Sequence.TWITCH, + Sequence.DIE, + Sequence.PST_DIE_FORWARD, + Sequence.PST_DIE_BACKWARD, + Sequence.PST_DIE_COLLAPSE, }; // Potential sequences for "unconscious" state - private static final SpriteDecoder.Sequence[] SLEEP_SEQUENCE = { - SpriteDecoder.Sequence.SLEEP, - SpriteDecoder.Sequence.SLEEP2, - SpriteDecoder.Sequence.TWITCH, - SpriteDecoder.Sequence.DIE, - SpriteDecoder.Sequence.PST_DIE_FORWARD, - SpriteDecoder.Sequence.PST_DIE_BACKWARD, - SpriteDecoder.Sequence.PST_DIE_COLLAPSE, + private static final Sequence[] SLEEP_SEQUENCE = { + Sequence.SLEEP, + Sequence.SLEEP2, + Sequence.TWITCH, + Sequence.DIE, + Sequence.PST_DIE_FORWARD, + Sequence.PST_DIE_BACKWARD, + Sequence.PST_DIE_COLLAPSE, }; protected final Point location = new Point(); @@ -186,7 +188,7 @@ protected static ActorAnimationProvider createAnimationProvider(CreResource cre) decoder.setSelectionCircleEnabled(Settings.ShowActorSelectionCircle); decoder.setPersonalSpaceVisible(Settings.ShowActorPersonalSpace); - SpriteDecoder.Sequence sequence = null; + Sequence sequence = null; // check for special animation sequence if (isDead) { @@ -197,9 +199,9 @@ protected static ActorAnimationProvider createAnimationProvider(CreResource cre) if (sequence == null) { // improve visualization of flying creatures - if (decoder.getAnimationType() == SpriteDecoder.AnimationType.FLYING && - decoder.isSequenceAvailable(SpriteDecoder.Sequence.WALK)) { - sequence = SpriteDecoder.Sequence.WALK; + if (decoder.getAnimationType() == AnimationInfo.Type.FLYING && + decoder.isSequenceAvailable(Sequence.WALK)) { + sequence = Sequence.WALK; } } @@ -277,18 +279,18 @@ protected static String createKey(CreResource cre) } /** Returns the first matching animation sequence listed in {@code sequences} that is available in the {@code SpriteDecoder} instance. */ - protected static SpriteDecoder.Sequence getMatchingSequence(SpriteDecoder decoder, SpriteDecoder.Sequence[] sequences) + protected static Sequence getMatchingSequence(SpriteDecoder decoder, Sequence[] sequences) { - SpriteDecoder.Sequence retVal = null; + Sequence retVal = null; if (sequences == null) { - sequences = SpriteDecoder.Sequence.values(); + sequences = Sequence.values(); } if (decoder == null || sequences.length == 0) { return retVal; } - for (final SpriteDecoder.Sequence seq : sequences) { + for (final Sequence seq : sequences) { if (decoder.isSequenceAvailable(seq)) { retVal = seq; break; diff --git a/src/org/infinity/resource/cre/ViewerAnimation.java b/src/org/infinity/resource/cre/ViewerAnimation.java index 7e8fe178c..482f85665 100644 --- a/src/org/infinity/resource/cre/ViewerAnimation.java +++ b/src/org/infinity/resource/cre/ViewerAnimation.java @@ -45,6 +45,7 @@ import org.infinity.icon.Icons; import org.infinity.resource.cre.decoder.SpriteDecoder; import org.infinity.resource.cre.decoder.SpriteUtils; +import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.viewer.CreatureViewer; import org.infinity.resource.gam.PartyNPC; import org.infinity.resource.graphics.PseudoBamDecoder.PseudoBamControl; @@ -73,17 +74,17 @@ public class ViewerAnimation extends ChildFrame implements ActionListener private static final ButtonPanel.Control CtrlOpenViewer = ButtonPanel.Control.CUSTOM_13; // List of potential sequences to display when loading a new creature - private static final List InitialSequences = new ArrayList() {{ - add(SpriteDecoder.Sequence.STAND); - add(SpriteDecoder.Sequence.STAND2); - add(SpriteDecoder.Sequence.STAND3); - add(SpriteDecoder.Sequence.STAND_EMERGED); - add(SpriteDecoder.Sequence.PST_STAND); - add(SpriteDecoder.Sequence.STANCE); - add(SpriteDecoder.Sequence.STANCE2); - add(SpriteDecoder.Sequence.PST_STANCE); - add(SpriteDecoder.Sequence.WALK); - add(SpriteDecoder.Sequence.PST_WALK); + private static final List InitialSequences = new ArrayList() {{ + add(Sequence.STAND); + add(Sequence.STAND2); + add(Sequence.STAND3); + add(Sequence.STAND_EMERGED); + add(Sequence.PST_STAND); + add(Sequence.STANCE); + add(Sequence.STANCE2); + add(Sequence.PST_STANCE); + add(Sequence.WALK); + add(Sequence.PST_WALK); }}; private static boolean zoom = false; @@ -98,7 +99,7 @@ public class ViewerAnimation extends ChildFrame implements ActionListener private RenderCanvas rcDisplay; private int curCycle, curFrame; private Timer timer; - private SpriteDecoder.Sequence sequence; + private Sequence sequence; public ViewerAnimation(CreResource cre) { @@ -142,13 +143,13 @@ private void setController(PseudoBamControl ctrl) } /** Returns the selected animation sequence. */ - public SpriteDecoder.Sequence getAnimationSequence() + public Sequence getAnimationSequence() { return sequence; } /** Loads a new animation sequence. */ - private void setAnimationSequence(SpriteDecoder.Sequence seq) throws Exception + private void setAnimationSequence(Sequence seq) throws Exception { if (seq != null && seq != getAnimationSequence()) { sequence = seq; @@ -286,7 +287,7 @@ public void actionPerformed(ActionEvent event) } else if (buttonControlPanel.getControlByType(CtrlSequenceList) == event.getSource()) { JComboBox cb = (JComboBox)buttonControlPanel.getControlByType(CtrlSequenceList); - SpriteDecoder.Sequence seq = (SpriteDecoder.Sequence)(cb).getSelectedItem(); + Sequence seq = (Sequence)(cb).getSelectedItem(); try { WindowBlocker.blockWindow(this, true); setAnimationSequence(seq); @@ -423,10 +424,10 @@ private void init() throws Exception JLabel lSequence = new JLabel("Sequence:"); lSequence.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0)); - DefaultComboBoxModel modelSequences = new DefaultComboBoxModel<>(); - JComboBox cbSequences = new JComboBox<>(modelSequences); + DefaultComboBoxModel modelSequences = new DefaultComboBoxModel<>(); + JComboBox cbSequences = new JComboBox<>(modelSequences); cbSequences.addActionListener(this); - for (final SpriteDecoder.Sequence seq : SpriteDecoder.Sequence.values()) { + for (final Sequence seq : Sequence.values()) { if (getDecoder().isSequenceAvailable(seq)) { modelSequences.addElement(seq); } @@ -508,14 +509,14 @@ private void init() throws Exception // loading animation sequence if (cbSequences.isEnabled()) { int seqIdx = 0; - for (final SpriteDecoder.Sequence sequence : InitialSequences) { + for (final Sequence sequence : InitialSequences) { int idx = ((DefaultComboBoxModel)cbSequences.getModel()).getIndexOf(sequence); if (idx >= 0) { seqIdx = idx; break; } } - SpriteDecoder.Sequence seq = cbSequences.getModel().getElementAt(seqIdx); + Sequence seq = cbSequences.getModel().getElementAt(seqIdx); cbSequences.setSelectedItem(seq); setAnimationSequence(seq); } diff --git a/src/org/infinity/resource/cre/decoder/AmbientDecoder.java b/src/org/infinity/resource/cre/decoder/AmbientDecoder.java index 762bc9027..1bab75440 100644 --- a/src/org/infinity/resource/cre/decoder/AmbientDecoder.java +++ b/src/org/infinity/resource/cre/decoder/AmbientDecoder.java @@ -10,10 +10,12 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.AnimationInfo; import org.infinity.resource.cre.decoder.internal.DecoderAttribute; import org.infinity.resource.cre.decoder.internal.DirDef; import org.infinity.resource.cre.decoder.internal.SegmentDef; import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; @@ -27,7 +29,7 @@ public class AmbientDecoder extends SpriteDecoder { /** The animation type associated with this class definition. */ - public static final AnimationType ANIMATION_TYPE = AnimationType.AMBIENT; + public static final AnimationInfo.Type ANIMATION_TYPE = AnimationInfo.Type.AMBIENT; public static final DecoderAttribute KEY_INVULNERABLE = DecoderAttribute.with("invulnerable", DecoderAttribute.DataType.BOOLEAN); public static final DecoderAttribute KEY_PATH_SMOOTH = DecoderAttribute.with("path_smooth", DecoderAttribute.DataType.BOOLEAN); diff --git a/src/org/infinity/resource/cre/decoder/AmbientStaticDecoder.java b/src/org/infinity/resource/cre/decoder/AmbientStaticDecoder.java index de51dd124..f802d36e0 100644 --- a/src/org/infinity/resource/cre/decoder/AmbientStaticDecoder.java +++ b/src/org/infinity/resource/cre/decoder/AmbientStaticDecoder.java @@ -10,10 +10,12 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.AnimationInfo; import org.infinity.resource.cre.decoder.internal.DecoderAttribute; import org.infinity.resource.cre.decoder.internal.DirDef; import org.infinity.resource.cre.decoder.internal.SegmentDef; import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; @@ -27,7 +29,7 @@ public class AmbientStaticDecoder extends SpriteDecoder { /** The animation type associated with this class definition. */ - public static final AnimationType ANIMATION_TYPE = AnimationType.AMBIENT_STATIC; + public static final AnimationInfo.Type ANIMATION_TYPE = AnimationInfo.Type.AMBIENT_STATIC; public static final DecoderAttribute KEY_INVULNERABLE = DecoderAttribute.with("invulnerable", DecoderAttribute.DataType.BOOLEAN); diff --git a/src/org/infinity/resource/cre/decoder/ArmoredBaseDecoder.java b/src/org/infinity/resource/cre/decoder/ArmoredBaseDecoder.java index 31f83322a..8165a064e 100644 --- a/src/org/infinity/resource/cre/decoder/ArmoredBaseDecoder.java +++ b/src/org/infinity/resource/cre/decoder/ArmoredBaseDecoder.java @@ -10,6 +10,7 @@ import org.infinity.datatype.IsNumeric; import org.infinity.resource.Profile; import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.AnimationInfo; import org.infinity.resource.cre.decoder.internal.DecoderAttribute; import org.infinity.resource.cre.decoder.internal.ItemInfo; import org.infinity.util.IniMap; @@ -34,12 +35,12 @@ public enum AttackType { BOW, CROSSBOW, SLING } - public ArmoredBaseDecoder(AnimationType type, int animationId, IniMap ini) throws Exception + public ArmoredBaseDecoder(AnimationInfo.Type type, int animationId, IniMap ini) throws Exception { super(type, animationId, ini); } - public ArmoredBaseDecoder(AnimationType type, CreResource cre) throws Exception + public ArmoredBaseDecoder(AnimationInfo.Type type, CreResource cre) throws Exception { super(type, cre); } @@ -156,7 +157,7 @@ public AttackType getAttackType(ItemInfo itm, int abilityIndex, boolean preferTw protected String guessHeightCode() { String retVal = ""; - boolean isCharacter = (getAnimationType() == AnimationType.CHARACTER); + boolean isCharacter = (getAnimationType() == AnimationInfo.Type.CHARACTER); String c2 = isCharacter ? "Q" : "P"; // try resref naming scheme diff --git a/src/org/infinity/resource/cre/decoder/CharacterDecoder.java b/src/org/infinity/resource/cre/decoder/CharacterDecoder.java index 61628445e..9787d8e2d 100644 --- a/src/org/infinity/resource/cre/decoder/CharacterDecoder.java +++ b/src/org/infinity/resource/cre/decoder/CharacterDecoder.java @@ -14,11 +14,13 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.AnimationInfo; import org.infinity.resource.cre.decoder.internal.DecoderAttribute; import org.infinity.resource.cre.decoder.internal.DirDef; import org.infinity.resource.cre.decoder.internal.ItemInfo; import org.infinity.resource.cre.decoder.internal.SegmentDef; import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; @@ -32,7 +34,7 @@ public class CharacterDecoder extends ArmoredBaseDecoder { /** The animation type associated with this class definition. */ - public static final AnimationType ANIMATION_TYPE = AnimationType.CHARACTER; + public static final AnimationInfo.Type ANIMATION_TYPE = AnimationInfo.Type.CHARACTER; public static final DecoderAttribute KEY_SPLIT_BAMS = DecoderAttribute.with("split_bams", DecoderAttribute.DataType.BOOLEAN); public static final DecoderAttribute KEY_HEIGHT_CODE_SHIELD = DecoderAttribute.with("height_code_shield", DecoderAttribute.DataType.STRING); diff --git a/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java b/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java index 79a5cf897..0fbf64b4f 100644 --- a/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java +++ b/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java @@ -14,12 +14,15 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.AnimationInfo; import org.infinity.resource.cre.decoder.internal.CycleDef; import org.infinity.resource.cre.decoder.internal.DecoderAttribute; import org.infinity.resource.cre.decoder.internal.DirDef; +import org.infinity.resource.cre.decoder.internal.Direction; import org.infinity.resource.cre.decoder.internal.ItemInfo; import org.infinity.resource.cre.decoder.internal.SegmentDef; import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; @@ -33,7 +36,7 @@ public class CharacterOldDecoder extends ArmoredBaseDecoder { /** The animation type associated with this class definition. */ - public static final AnimationType ANIMATION_TYPE = AnimationType.CHARACTER_OLD; + public static final AnimationInfo.Type ANIMATION_TYPE = AnimationInfo.Type.CHARACTER_OLD; public static final DecoderAttribute KEY_HIDE_WEAPONS = DecoderAttribute.with("hide_weapons", DecoderAttribute.DataType.BOOLEAN); public static final DecoderAttribute KEY_SHADOW = DecoderAttribute.with("shadow", DecoderAttribute.DataType.STRING); diff --git a/src/org/infinity/resource/cre/decoder/EffectDecoder.java b/src/org/infinity/resource/cre/decoder/EffectDecoder.java index 84aec65da..00017094e 100644 --- a/src/org/infinity/resource/cre/decoder/EffectDecoder.java +++ b/src/org/infinity/resource/cre/decoder/EffectDecoder.java @@ -10,10 +10,13 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.AnimationInfo; import org.infinity.resource.cre.decoder.internal.CycleDef; import org.infinity.resource.cre.decoder.internal.DecoderAttribute; import org.infinity.resource.cre.decoder.internal.DirDef; +import org.infinity.resource.cre.decoder.internal.Direction; import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; @@ -27,7 +30,7 @@ public class EffectDecoder extends SpriteDecoder { /** The animation type associated with this class definition. */ - public static final AnimationType ANIMATION_TYPE = AnimationType.EFFECT; + public static final AnimationInfo.Type ANIMATION_TYPE = AnimationInfo.Type.EFFECT; public static final DecoderAttribute KEY_SHADOW = DecoderAttribute.with("shadow", DecoderAttribute.DataType.STRING); public static final DecoderAttribute KEY_PALLETIZED = DecoderAttribute.with("palletized", DecoderAttribute.DataType.BOOLEAN); diff --git a/src/org/infinity/resource/cre/decoder/FlyingDecoder.java b/src/org/infinity/resource/cre/decoder/FlyingDecoder.java index d8d2b0a85..9d3238b2c 100644 --- a/src/org/infinity/resource/cre/decoder/FlyingDecoder.java +++ b/src/org/infinity/resource/cre/decoder/FlyingDecoder.java @@ -9,8 +9,10 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.AnimationInfo; import org.infinity.resource.cre.decoder.internal.DirDef; import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; @@ -23,7 +25,7 @@ public class FlyingDecoder extends SpriteDecoder { /** The animation type associated with this class definition. */ - public static final AnimationType ANIMATION_TYPE = AnimationType.FLYING; + public static final AnimationInfo.Type ANIMATION_TYPE = AnimationInfo.Type.FLYING; /** * A helper method that parses the specified data array and generates a {@link IniMap} instance out of it. diff --git a/src/org/infinity/resource/cre/decoder/MonsterAnkhegDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterAnkhegDecoder.java index a34728a03..e8f9a75a5 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterAnkhegDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterAnkhegDecoder.java @@ -10,10 +10,13 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.AnimationInfo; import org.infinity.resource.cre.decoder.internal.DecoderAttribute; import org.infinity.resource.cre.decoder.internal.DirDef; +import org.infinity.resource.cre.decoder.internal.Direction; import org.infinity.resource.cre.decoder.internal.SegmentDef; import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; @@ -27,7 +30,7 @@ public class MonsterAnkhegDecoder extends SpriteDecoder { /** The animation type associated with this class definition. */ - public static final AnimationType ANIMATION_TYPE = AnimationType.MONSTER_ANKHEG; + public static final AnimationInfo.Type ANIMATION_TYPE = AnimationInfo.Type.MONSTER_ANKHEG; public static final DecoderAttribute KEY_MIRROR = DecoderAttribute.with("mirror", DecoderAttribute.DataType.BOOLEAN); public static final DecoderAttribute KEY_EXTEND_DIRECTION = DecoderAttribute.with("extend_direction", DecoderAttribute.DataType.BOOLEAN); diff --git a/src/org/infinity/resource/cre/decoder/MonsterDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterDecoder.java index 5a2f5c286..6b0db9615 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterDecoder.java @@ -12,11 +12,14 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.AnimationInfo; import org.infinity.resource.cre.decoder.internal.DecoderAttribute; import org.infinity.resource.cre.decoder.internal.DirDef; +import org.infinity.resource.cre.decoder.internal.Direction; import org.infinity.resource.cre.decoder.internal.ItemInfo; import org.infinity.resource.cre.decoder.internal.SegmentDef; import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; @@ -41,7 +44,7 @@ public class MonsterDecoder extends SpriteDecoder { /** The animation type associated with this class definition. */ - public static final AnimationType ANIMATION_TYPE = AnimationType.MONSTER; + public static final AnimationInfo.Type ANIMATION_TYPE = AnimationInfo.Type.MONSTER; public static final DecoderAttribute KEY_CAN_LIE_DOWN = DecoderAttribute.with("can_lie_down", DecoderAttribute.DataType.BOOLEAN); public static final DecoderAttribute KEY_PATH_SMOOTH = DecoderAttribute.with("path_smooth", DecoderAttribute.DataType.BOOLEAN); diff --git a/src/org/infinity/resource/cre/decoder/MonsterIcewindDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterIcewindDecoder.java index 6b269486d..bbd52ff88 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterIcewindDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterIcewindDecoder.java @@ -10,11 +10,13 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.AnimationInfo; import org.infinity.resource.cre.decoder.internal.DecoderAttribute; import org.infinity.resource.cre.decoder.internal.DirDef; import org.infinity.resource.cre.decoder.internal.ItemInfo; import org.infinity.resource.cre.decoder.internal.SegmentDef; import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; @@ -27,7 +29,7 @@ public class MonsterIcewindDecoder extends SpriteDecoder { /** The animation type associated with this class definition. */ - public static final AnimationType ANIMATION_TYPE = AnimationType.MONSTER_ICEWIND; + public static final AnimationInfo.Type ANIMATION_TYPE = AnimationInfo.Type.MONSTER_ICEWIND; public static final DecoderAttribute KEY_WEAPON_LEFT_HAND = DecoderAttribute.with("weapon_left_hand", DecoderAttribute.DataType.BOOLEAN); diff --git a/src/org/infinity/resource/cre/decoder/MonsterLarge16Decoder.java b/src/org/infinity/resource/cre/decoder/MonsterLarge16Decoder.java index 5019f61be..7011876c9 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterLarge16Decoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterLarge16Decoder.java @@ -10,9 +10,12 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.AnimationInfo; import org.infinity.resource.cre.decoder.internal.DirDef; +import org.infinity.resource.cre.decoder.internal.Direction; import org.infinity.resource.cre.decoder.internal.SegmentDef; import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; @@ -26,7 +29,7 @@ public class MonsterLarge16Decoder extends SpriteDecoder { /** The animation type associated with this class definition. */ - public static final AnimationType ANIMATION_TYPE = AnimationType.MONSTER_LARGE_16; + public static final AnimationInfo.Type ANIMATION_TYPE = AnimationInfo.Type.MONSTER_LARGE_16; private static final HashMap> suffixMap = new HashMap>() {{ put(Sequence.WALK, Couple.with("G1", 0)); diff --git a/src/org/infinity/resource/cre/decoder/MonsterLargeDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterLargeDecoder.java index 407c706ab..592efff9f 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterLargeDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterLargeDecoder.java @@ -10,9 +10,11 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.AnimationInfo; import org.infinity.resource.cre.decoder.internal.DirDef; import org.infinity.resource.cre.decoder.internal.SegmentDef; import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; @@ -26,7 +28,7 @@ public class MonsterLargeDecoder extends SpriteDecoder { /** The animation type associated with this class definition. */ - public static final AnimationType ANIMATION_TYPE = AnimationType.MONSTER_LARGE; + public static final AnimationInfo.Type ANIMATION_TYPE = AnimationInfo.Type.MONSTER_LARGE; private static final HashMap> suffixMap = new HashMap>() {{ put(Sequence.STAND, Couple.with("G1", 0)); diff --git a/src/org/infinity/resource/cre/decoder/MonsterLayeredDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterLayeredDecoder.java index 4c92bc6d4..d08a0bc45 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterLayeredDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterLayeredDecoder.java @@ -10,11 +10,13 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.AnimationInfo; import org.infinity.resource.cre.decoder.internal.DecoderAttribute; import org.infinity.resource.cre.decoder.internal.DirDef; import org.infinity.resource.cre.decoder.internal.ItemInfo; import org.infinity.resource.cre.decoder.internal.SegmentDef; import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; @@ -28,7 +30,7 @@ public class MonsterLayeredDecoder extends SpriteDecoder { /** The animation type associated with this class definition. */ - public static final AnimationType ANIMATION_TYPE = AnimationType.MONSTER_LAYERED; + public static final AnimationInfo.Type ANIMATION_TYPE = AnimationInfo.Type.MONSTER_LAYERED; public static final DecoderAttribute KEY_RESREF_WEAPON1 = DecoderAttribute.with("resref_weapon1", DecoderAttribute.DataType.STRING); public static final DecoderAttribute KEY_RESREF_WEAPON2 = DecoderAttribute.with("resref_weapon2", DecoderAttribute.DataType.STRING); diff --git a/src/org/infinity/resource/cre/decoder/MonsterLayeredSpellDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterLayeredSpellDecoder.java index a216018ca..90a49a716 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterLayeredSpellDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterLayeredSpellDecoder.java @@ -10,11 +10,13 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.AnimationInfo; import org.infinity.resource.cre.decoder.internal.DecoderAttribute; import org.infinity.resource.cre.decoder.internal.DirDef; import org.infinity.resource.cre.decoder.internal.ItemInfo; import org.infinity.resource.cre.decoder.internal.SegmentDef; import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; @@ -29,7 +31,7 @@ public class MonsterLayeredSpellDecoder extends SpriteDecoder { /** The animation type associated with this class definition. */ - public static final AnimationType ANIMATION_TYPE = AnimationType.MONSTER_LAYERED_SPELL; + public static final AnimationInfo.Type ANIMATION_TYPE = AnimationInfo.Type.MONSTER_LAYERED_SPELL; public static final DecoderAttribute KEY_DUAL_ATTACK = DecoderAttribute.with("dual_attack", DecoderAttribute.DataType.BOOLEAN); public static final DecoderAttribute KEY_INVULNERABLE = DecoderAttribute.with("invulnerable", DecoderAttribute.DataType.BOOLEAN); @@ -171,7 +173,7 @@ protected void init() throws Exception IniMapSection section = getSpecificIniSection(); if (section.getEntryCount() == 0) { // EE: defined as "monster_layered" type - section = getAnimationInfo().getSection(AnimationType.MONSTER_LAYERED.getSectionName()); + section = getAnimationInfo().getSection(AnimationInfo.Type.MONSTER_LAYERED.getSectionName()); } setDetectedByInfravision(true); diff --git a/src/org/infinity/resource/cre/decoder/MonsterMultiDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterMultiDecoder.java index 5a9e2602a..0ef12b3ab 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterMultiDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterMultiDecoder.java @@ -11,10 +11,13 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.AnimationInfo; import org.infinity.resource.cre.decoder.internal.DecoderAttribute; import org.infinity.resource.cre.decoder.internal.DirDef; +import org.infinity.resource.cre.decoder.internal.Direction; import org.infinity.resource.cre.decoder.internal.SegmentDef; import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; @@ -30,7 +33,7 @@ public class MonsterMultiDecoder extends QuadrantsBaseDecoder { /** The animation type associated with this class definition. */ - public static final AnimationType ANIMATION_TYPE = AnimationType.MONSTER_MULTI; + public static final AnimationInfo.Type ANIMATION_TYPE = AnimationInfo.Type.MONSTER_MULTI; public static final DecoderAttribute KEY_SPLIT_BAMS = DecoderAttribute.with("split_bams", DecoderAttribute.DataType.BOOLEAN); public static final DecoderAttribute KEY_DOUBLE_BLIT = DecoderAttribute.with("double_blit", DecoderAttribute.DataType.BOOLEAN); @@ -229,7 +232,7 @@ protected void init() throws Exception IniMapSection section = getSpecificIniSection(); if (section.getEntryCount() == 0) { // EE: defined as "multi_new" type - section = getAnimationInfo().getSection(AnimationType.MONSTER_MULTI_NEW.getSectionName()); + section = getAnimationInfo().getSection(AnimationInfo.Type.MONSTER_MULTI_NEW.getSectionName()); } setSplittedBams(section.getAsInteger(KEY_SPLIT_BAMS.getName(), 0) != 0); setQuadrants(section.getAsInteger(KEY_QUADRANTS.getName(), 9)); diff --git a/src/org/infinity/resource/cre/decoder/MonsterMultiNewDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterMultiNewDecoder.java index f76522c0c..c52463ef9 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterMultiNewDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterMultiNewDecoder.java @@ -10,10 +10,12 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.AnimationInfo; import org.infinity.resource.cre.decoder.internal.DecoderAttribute; import org.infinity.resource.cre.decoder.internal.DirDef; import org.infinity.resource.cre.decoder.internal.SegmentDef; import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; @@ -28,7 +30,7 @@ public class MonsterMultiNewDecoder extends QuadrantsBaseDecoder { /** The animation type associated with this class definition. */ - public static final AnimationType ANIMATION_TYPE = AnimationType.MONSTER_MULTI_NEW; + public static final AnimationInfo.Type ANIMATION_TYPE = AnimationInfo.Type.MONSTER_MULTI_NEW; public static final DecoderAttribute KEY_CAN_LIE_DOWN = DecoderAttribute.with("can_lie_down", DecoderAttribute.DataType.BOOLEAN); public static final DecoderAttribute KEY_PATH_SMOOTH = DecoderAttribute.with("path_smooth", DecoderAttribute.DataType.BOOLEAN); diff --git a/src/org/infinity/resource/cre/decoder/MonsterOldDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterOldDecoder.java index 7efea5cbb..e1da940e0 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterOldDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterOldDecoder.java @@ -10,9 +10,11 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.AnimationInfo; import org.infinity.resource.cre.decoder.internal.DirDef; import org.infinity.resource.cre.decoder.internal.SegmentDef; import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; @@ -38,7 +40,7 @@ public class MonsterOldDecoder extends SpriteDecoder { /** The animation type associated with this class definition. */ - public static final AnimationType ANIMATION_TYPE = AnimationType.MONSTER_OLD; + public static final AnimationInfo.Type ANIMATION_TYPE = AnimationInfo.Type.MONSTER_OLD; private static final HashMap> suffixMap = new HashMap>() {{ put(Sequence.WALK, Couple.with("G1", 0)); diff --git a/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java index 92e1ae2ad..c63756935 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java @@ -17,8 +17,11 @@ import org.infinity.resource.Profile; import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.Sequence; +import org.infinity.resource.cre.decoder.internal.AnimationInfo; import org.infinity.resource.cre.decoder.internal.DecoderAttribute; import org.infinity.resource.cre.decoder.internal.DirDef; +import org.infinity.resource.cre.decoder.internal.Direction; import org.infinity.resource.cre.decoder.internal.SegmentDef; import org.infinity.resource.cre.decoder.internal.SeqDef; import org.infinity.resource.cre.decoder.tables.SpriteTables; @@ -37,7 +40,7 @@ public class MonsterPlanescapeDecoder extends SpriteDecoder { /** The animation type associated with this class definition. */ - public static final AnimationType ANIMATION_TYPE = AnimationType.MONSTER_PLANESCAPE; + public static final AnimationInfo.Type ANIMATION_TYPE = AnimationInfo.Type.MONSTER_PLANESCAPE; public static final DecoderAttribute KEY_BESTIARY = DecoderAttribute.with("bestiary", DecoderAttribute.DataType.INT); public static final DecoderAttribute KEY_CLOWN = DecoderAttribute.with("clown", DecoderAttribute.DataType.BOOLEAN); diff --git a/src/org/infinity/resource/cre/decoder/MonsterQuadrantDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterQuadrantDecoder.java index 9d9b8b1e2..10d16053d 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterQuadrantDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterQuadrantDecoder.java @@ -10,10 +10,12 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.AnimationInfo; import org.infinity.resource.cre.decoder.internal.DecoderAttribute; import org.infinity.resource.cre.decoder.internal.DirDef; import org.infinity.resource.cre.decoder.internal.SegmentDef; import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; @@ -28,7 +30,7 @@ public class MonsterQuadrantDecoder extends QuadrantsBaseDecoder { /** The animation type associated with this class definition. */ - public static final AnimationType ANIMATION_TYPE = AnimationType.MONSTER_QUADRANT; + public static final AnimationInfo.Type ANIMATION_TYPE = AnimationInfo.Type.MONSTER_QUADRANT; public static final DecoderAttribute KEY_CASTER = DecoderAttribute.with("caster", DecoderAttribute.DataType.BOOLEAN); public static final DecoderAttribute KEY_PATH_SMOOTH = DecoderAttribute.with("path_smooth", DecoderAttribute.DataType.BOOLEAN); diff --git a/src/org/infinity/resource/cre/decoder/QuadrantsBaseDecoder.java b/src/org/infinity/resource/cre/decoder/QuadrantsBaseDecoder.java index 40086c667..e2a08cbc5 100644 --- a/src/org/infinity/resource/cre/decoder/QuadrantsBaseDecoder.java +++ b/src/org/infinity/resource/cre/decoder/QuadrantsBaseDecoder.java @@ -5,6 +5,7 @@ package org.infinity.resource.cre.decoder; import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.AnimationInfo; import org.infinity.resource.cre.decoder.internal.DecoderAttribute; import org.infinity.util.IniMap; @@ -19,12 +20,12 @@ public abstract class QuadrantsBaseDecoder extends SpriteDecoder public int getQuadrants() { return getAttribute(KEY_QUADRANTS); } protected void setQuadrants(int v) { setAttribute(KEY_QUADRANTS, v); } - public QuadrantsBaseDecoder(AnimationType type, int animationId, IniMap ini) throws Exception + public QuadrantsBaseDecoder(AnimationInfo.Type type, int animationId, IniMap ini) throws Exception { super(type, animationId, ini); } - protected QuadrantsBaseDecoder(AnimationType type, CreResource cre) throws Exception + protected QuadrantsBaseDecoder(AnimationInfo.Type type, CreResource cre) throws Exception { super(type, cre); } diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index cb6823326..7968c0626 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -26,7 +26,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.EnumMap; -import java.util.EnumSet; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -38,11 +37,14 @@ import org.infinity.resource.Profile; import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.AnimationInfo; +import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.internal.ColorInfo; import org.infinity.resource.cre.decoder.internal.CreatureInfo; import org.infinity.resource.cre.decoder.internal.CycleDef; import org.infinity.resource.cre.decoder.internal.DecoderAttribute; import org.infinity.resource.cre.decoder.internal.DirDef; +import org.infinity.resource.cre.decoder.internal.Direction; import org.infinity.resource.cre.decoder.internal.FrameInfo; import org.infinity.resource.cre.decoder.internal.ItemInfo; import org.infinity.resource.cre.decoder.internal.SegmentDef; @@ -100,528 +102,28 @@ public abstract class SpriteDecoder extends PseudoBamDecoder public static final DecoderAttribute KEY_FALSE_COLOR = DecoderAttribute.with("false_color", DecoderAttribute.DataType.BOOLEAN); public static final DecoderAttribute KEY_TRANSLUCENT = DecoderAttribute.with("translucent", DecoderAttribute.DataType.BOOLEAN); - - // Predefined sets of games with common animation slot mappings - private static final EnumSet TYPE_GAME_BG1 = EnumSet.of(Profile.Game.BG1, Profile.Game.BG1TotSC); - private static final EnumSet TYPE_GAME_BG2_TOB = EnumSet.of(Profile.Game.BG2ToB, Profile.Game.BGT, Profile.Game.Tutu); - // all BG2 game variants - private static final EnumSet TYPE_GAME_BG2 = EnumSet.of(Profile.Game.BG2SoA, Profile.Game.BG2ToB, Profile.Game.BGT, - Profile.Game.Tutu); - private static final EnumSet TYPE_GAME_IWD = EnumSet.of(Profile.Game.IWD); - private static final EnumSet TYPE_GAME_IWD_HOW = EnumSet.of(Profile.Game.IWDHoW, Profile.Game.IWDHowTotLM); - private static final EnumSet TYPE_GAME_IWD2 = EnumSet.of(Profile.Game.IWD2); - private static final EnumSet TYPE_GAME_PST = EnumSet.of(Profile.Game.PST); - // all EE games - private static final EnumSet TYPE_GAME_EE = EnumSet.of(Profile.Game.BG1EE, Profile.Game.BG1SoD, Profile.Game.BG2EE, - Profile.Game.EET, Profile.Game.IWDEE, Profile.Game.PSTEE); - private static final EnumSet TYPE_GAME_PSTEE = EnumSet.of(Profile.Game.PSTEE); - // all games except PST - private static final EnumSet TYPE_GAME_ALL = EnumSet.complementOf(EnumSet.of(Profile.Game.Unknown, Profile.Game.PST)); - - // Predefined slot ranges for specific animation types - private static final List RANGE_EFFECT = Arrays.asList(new Range(0, 0xfff)); - private static final List RANGE_MONSTER_QUADRANT = Arrays.asList(new Range(0x1000, 0x1ff)); - private static final List RANGE_MONSTER_MULTI = Arrays.asList(new Range(0x1200, 0xff), - new Range(0x1400, 0xbff)); - private static final List RANGE_MONSTER_MULTI_NEW = Arrays.asList(new Range(0x1300, 0xff)); - private static final List RANGE_MONSTER_LAYERED = Arrays.asList(new Range(0x8000, 0xfff)); - private static final List RANGE_MONSTER_LAYERED_SPELL = Arrays.asList(new Range(0x2000, 0xfff)); - private static final List RANGE_MONSTER_ANKHEG = Arrays.asList(new Range(0x3000, 0xfff)); - private static final List RANGE_TOWN_STATIC = Arrays.asList(new Range(0x4000, 0xfff)); - private static final List RANGE_CHARACTER = Arrays.asList(new Range(0x5000, 0x3ff), - new Range(0x5500, 0xff), - new Range(0x6000, 0x3ff), - new Range(0x6500, 0xff)); - private static final List RANGE_CHARACTER_IA = Arrays.asList(new Range(0x6600, 0x4ff)); - private static final List RANGE_CHARACTER_OLD = Arrays.asList(new Range(0x5400, 0xff), - new Range(0x5600, 0x9ff), - new Range(0x6400, 0xff), - new Range(0x6600, 0x9ff)); - private static final List RANGE_MONSTER = Arrays.asList(new Range(0x7002, 0xd, 0x00, 0x1f, 4), - new Range(0x7004, 0xb, 0x20, 0x0f, 4), - new Range(0x7000, 0xf, 0x30, 0x0f, 4), - new Range(0x7003, 0xc, 0x40, 0x0f, 4), - new Range(0x7002, 0xd, 0x50, 0x0f, 4), - new Range(0x7003, 0xc, 0x70, 0x0f, 4), - new Range(0x7005, 0xa, 0x90, 0x1f, 4), - new Range(0x7007, 0x8, 0xb0, 0x0f, 4), - new Range(0x7002, 0xd, 0xc0, 0x0f, 4), - new Range(0x7002, 0xd, 0xe0, 0x0f, 4), - new Range(0x7000, 0xf, 0xf0, 0x0f, 4)); - private static final List RANGE_MONSTER_IA = Arrays.asList(new Range(0x5b00, 0x4ff)); - private static final List RANGE_MONSTER_OLD = Arrays.asList(new Range(0x7000, 0x1, 0x00, 0x1f, 4), - new Range(0x7000, 0x3, 0x20, 0x0f, 4), - new Range(0x7000, 0x2, 0x40, 0x0f, 4), - new Range(0x7000, 0x1, 0x50, 0x0f, 4), - new Range(0x7000, 0xf, 0x60, 0x0f, 4), - new Range(0x7000, 0x2, 0x70, 0x0f, 4), - new Range(0x7000, 0xf, 0x80, 0x0f, 4), - new Range(0x7000, 0x4, 0x90, 0x1f, 4), - new Range(0x7000, 0x6, 0xb0, 0x0f, 4), - new Range(0x7000, 0x1, 0xc0, 0x0f, 4), - new Range(0x7000, 0xf, 0xd0, 0x0f, 4), - new Range(0x7000, 0x1, 0xe0, 0x0f, 4)); - private static final List RANGE_MONSTER_OLD_IA = Arrays.asList(new Range(0x547a, 0x479)); - private static final List RANGE_MONSTER_LARGE = Arrays.asList(new Range(0x9000, 0xfff)); - private static final List RANGE_MONSTER_LARGE_16 = Arrays.asList(new Range(0xa000, 0xfff)); - private static final List RANGE_AMBIENT_STATIC = Arrays.asList(new Range(0xb000, 0xfff)); - private static final List RANGE_AMBIENT = Arrays.asList(new Range(0xc000, 0xfff)); - private static final List RANGE_FLYING = Arrays.asList(new Range(0xd000, 0xfff)); - private static final List RANGE_MONSTER_ICEWIND = Arrays.asList(new Range(0xe000, 0xfff)); - private static final List RANGE_MONSTER_ICEWIND_EX = Arrays.asList(new Range(0xe000, 0x1fff)); - private static final List RANGE_MONSTER_ICEWIND_IA = Arrays.asList(new Range(0x5000, 0x478)); - private static final List RANGE_MONSTER_PLANESCAPE = Arrays.asList(new Range(0xf000, 0xfff)); - private static final List RANGE_MONSTER_PLANESCAPE_EX = Arrays.asList(new Range(0x0000, 0xffff)); - - /** Available creature animation types. */ - public enum AnimationType { - /** Animation type: 0000 */ - EFFECT(0x0000, "effect", Arrays.asList( - Couple.with(TYPE_GAME_ALL, RANGE_EFFECT))), // type=0 - /** Animation type: 1000 (slots [1000..11ff]) */ - MONSTER_QUADRANT(0x1000, "monster_quadrant", Arrays.asList( - Couple.with(TYPE_GAME_ALL, RANGE_MONSTER_QUADRANT))), // type=1 - /** Animation type: 1000 (slots [1200..12ff], [1400..1fff] */ - MONSTER_MULTI(0x1000, "monster_multi", Arrays.asList( - Couple.with(TYPE_GAME_EE, RANGE_MONSTER_MULTI), - Couple.with(TYPE_GAME_BG2, RANGE_MONSTER_MULTI), - Couple.with(TYPE_GAME_IWD2, RANGE_MONSTER_MULTI))), // type=2 - /** Animation type: 1000 (slots [1300..13ff]) */ - MONSTER_MULTI_NEW(0x1000, "multi_new", Arrays.asList( - Couple.with(TYPE_GAME_EE, RANGE_MONSTER_MULTI_NEW), - Couple.with(TYPE_GAME_BG2_TOB, RANGE_MONSTER_MULTI_NEW))), // type=3 - /** Animation type: 8000 */ - MONSTER_LAYERED(0x8000, "monster_layered", Arrays.asList( - Couple.with(TYPE_GAME_ALL, RANGE_MONSTER_LAYERED))), // type=4 - /** Animation type: 2000 */ - MONSTER_LAYERED_SPELL(0x2000, "monster_layered_spell", Arrays.asList( - Couple.with(TYPE_GAME_ALL, RANGE_MONSTER_LAYERED_SPELL))), // type=5 - /** Animation type: 3000 */ - MONSTER_ANKHEG(0x3000, "monster_ankheg", Arrays.asList( - Couple.with(TYPE_GAME_ALL, RANGE_MONSTER_ANKHEG))), // type=6 - /** Animation type: 4000 */ - TOWN_STATIC(0x4000, "town_static", Arrays.asList( - Couple.with(TYPE_GAME_ALL, RANGE_TOWN_STATIC))), // type=7 - /** Animation types: 5000, 6000 (slots [5000..53ff], [5500..55ff], [6000..63ff], [6500..65ff]) */ - CHARACTER(new int[] {0x5000, 0x6000}, "character", Arrays.asList( - Couple.with(TYPE_GAME_EE, RANGE_CHARACTER), - Couple.with(TYPE_GAME_BG2, RANGE_CHARACTER), - Couple.with(TYPE_GAME_IWD_HOW, RANGE_CHARACTER), - Couple.with(TYPE_GAME_IWD2, RANGE_CHARACTER)), - RANGE_CHARACTER_IA), // type=8 - /** Animation types: 5000, 6000 (slots [5400..54ff], [5600..5fff], [6400..64ff], [6600..6fff]) */ - CHARACTER_OLD(new int[] {0x5000, 0x6000}, "character_old", Arrays.asList( - Couple.with(TYPE_GAME_ALL, RANGE_CHARACTER_OLD), - Couple.with(TYPE_GAME_BG1, RANGE_CHARACTER), - Couple.with(TYPE_GAME_IWD, RANGE_CHARACTER))), // type=9 - /** Animation type: 7000 (many subranges) */ - MONSTER(0x7000, "monster", Arrays.asList( - Couple.with(TYPE_GAME_EE, RANGE_MONSTER), - Couple.with(TYPE_GAME_BG2, RANGE_MONSTER), - Couple.with(TYPE_GAME_IWD, RANGE_MONSTER), - Couple.with(TYPE_GAME_IWD_HOW, RANGE_MONSTER), - Couple.with(TYPE_GAME_IWD2, RANGE_MONSTER)), - RANGE_MONSTER_IA), // type=10 - /** Animation type: 7000 (many subranges) */ - MONSTER_OLD(0x7000, "monster_old", Arrays.asList( - Couple.with(TYPE_GAME_ALL, RANGE_MONSTER_OLD)), - RANGE_MONSTER_OLD_IA), // type=11 - /** Animation type: 9000 */ - MONSTER_LARGE(0x9000, "monster_large", Arrays.asList( - Couple.with(TYPE_GAME_ALL, RANGE_MONSTER_LARGE))), // type=12 - /** Animation type: A000 */ - MONSTER_LARGE_16(0xa000, "monster_large16", Arrays.asList( - Couple.with(TYPE_GAME_ALL, RANGE_MONSTER_LARGE_16))), // type=13 - /** Animation type: B000 */ - AMBIENT_STATIC(0xb000, "ambient_static", Arrays.asList( - Couple.with(TYPE_GAME_ALL, RANGE_AMBIENT_STATIC))), // type=14 - /** Animation type: C000 */ - AMBIENT(0xc000, "ambient", Arrays.asList( - Couple.with(TYPE_GAME_ALL, RANGE_AMBIENT))), // type=15 - /** Animation type: D000 */ - FLYING(0xd000, "flying", Arrays.asList( - Couple.with(TYPE_GAME_ALL, RANGE_FLYING))), // type=16 - /** Animation type: E000 (for non-EE: also slots [f000..ffff]) */ - MONSTER_ICEWIND(0xe000, "monster_icewind", Arrays.asList( - Couple.with(TYPE_GAME_EE, RANGE_MONSTER_ICEWIND), - Couple.with(TYPE_GAME_BG2, RANGE_MONSTER_ICEWIND), - Couple.with(TYPE_GAME_IWD, RANGE_MONSTER_ICEWIND_EX), - Couple.with(TYPE_GAME_IWD_HOW, RANGE_MONSTER_ICEWIND_EX), - Couple.with(TYPE_GAME_IWD2, RANGE_MONSTER_ICEWIND_EX)), - RANGE_MONSTER_ICEWIND_IA), // type=17 - /** Animation type: F000 */ - MONSTER_PLANESCAPE(0xf000, "monster_planescape", Arrays.asList( - Couple.with(TYPE_GAME_PSTEE, RANGE_MONSTER_PLANESCAPE), - Couple.with(TYPE_GAME_PST, RANGE_MONSTER_PLANESCAPE_EX))); // type=18 - - private final EnumMap> rangeMap = new EnumMap<>(Profile.Game.class); - private final List iaRanges; - private final int[] animationTypes; - private final String sectionName; - - /** - * @param type slot base range - * @param sectionName INI section name - * @param entries list of games and their associated slot ranges. - */ - private AnimationType(int type, String sectionName, List, List>> entries) - throws IllegalArgumentException - { - this(new int[] {type}, sectionName, entries, null); - } - - /** - * @param type - * @param sectionName - * @param entries - * @param infinityAnimationRanges - * @throws IllegalArgumentException - */ - private AnimationType(int type, String sectionName, List, List>> entries, - List infinityAnimationRanges) - throws IllegalArgumentException - { - this(new int[] {type}, sectionName, entries, infinityAnimationRanges); - } - - /** - * @param types - * @param sectionName - * @param entries - * @throws IllegalArgumentException - */ - private AnimationType(int[] types, String sectionName, List, List>> entries) - throws IllegalArgumentException - { - this(types, sectionName, entries, null); - } - - /** - * @param type list of slot base ranges - * @param sectionName INI section name - * @param entries list of games and their associated slot ranges. - * @throws IllegalArgumentException - */ - private AnimationType(int[] types, String sectionName, List, List>> entries, - List infinityAnimationRanges) throws IllegalArgumentException - { - try { - Misc.requireCondition(types != null && types.length > 0, "Type cannot be empty", IllegalArgumentException.class); - Misc.requireCondition(sectionName != null && !sectionName.isEmpty(), "Section name cannot be empty", IllegalArgumentException.class); - } catch (IllegalArgumentException iae) { - throw iae; - } catch (Exception e) { - } - this.animationTypes = types; - this.sectionName = sectionName; - for (final Couple, List> entry : entries) { - EnumSet games = entry.getValue0(); - List ranges = entry.getValue1(); - for (final Profile.Game game : games) { - if (ranges.size() > 0) { - List list = this.rangeMap.get(game); - if (list != null) { - list.addAll(ranges); - } else { - this.rangeMap.put(game, new ArrayList<>(ranges)); - } - } - } - } - this.iaRanges = infinityAnimationRanges; - } - - /** Returns the name for the type-specific INI section. */ - public String getSectionName() { return sectionName; } - - /** Returns the first available base animation type associated with the enum instance. */ - public int getType() { return animationTypes[0]; } - - /** Returns the number of defined base animation types associated with the enum instance. */ - public int getTypeCount() { return animationTypes.length; } - - /** Returns the specified base animation type associated with the enum instance. */ - public int getType(int idx) { return animationTypes[idx]; } - - /** - * Returns whether the specified value is covered by the ranges associated with the enum instance - * for the current game. - */ - public boolean contains(int value) { return contains(Profile.getGame(), value); } - - /** - * Returns whether the specified value is covered by the ranges associated with the enum instance - * for the specified game. - */ - public boolean contains(Profile.Game game, int value) - { - if (game == null) { - game = Profile.getGame(); - } - boolean retVal = false; - - // Infinity Animations sprite takes precedence over regular sprite - AnimationType type = containsInfinityAnimations(value); - if (type != null && type != this) { - return retVal; - } - retVal = (type == this); - - if (!retVal) { - retVal = contains(value, rangeMap.get(Profile.getGame())); - } - return retVal; - } - - // Checks whether specified value is covered by the given ranges - private static boolean contains(int value, List ranges) - { - if (ranges != null) { - return ranges - .parallelStream() - .anyMatch(r -> r.contains(value)); - } - return false; - } - - /** - * Determines the {@code AnimationType} enum where a defined Infinity Animations (IA) range covers the specied value. - * @param value the value to check. - * @return {@code AnimationType} enum supporting the specified IA value. - * Returns {@code null} if value is not covered by any IA range. - */ - public static AnimationType containsInfinityAnimations(int value) - { - AnimationType retVal = null; - if (Profile.getProperty(Profile.Key.GET_INFINITY_ANIMATIONS) > 0) { - for (AnimationType type : AnimationType.values()) { - if (contains(value, type.iaRanges)) { - retVal = type; - break; - } - } - } - return retVal; - } - - /** - * Returns the {@code AnimationType} enum covering the specified animation id. - * @param animationId the animation id - * @return {@code AnimationType} enum that covers the specified animation id. Returns {@code null} otherwise. - */ - public static AnimationType typeOfId(int animationId) - { - for (final AnimationType type : values()) { - if (type.contains(animationId)) { - return type; - } - } - return null; - } - } - - /** Available animation sequences. Note: PST-specific animation sequences are prefixed by "PST_". */ - public enum Sequence { - /** Special value: Used when no animation sequence is loaded. */ - NONE, - STAND("Stand"), - STAND2("Stand 2"), - STAND3("Stand 3"), - /** For buried creatures only: stand sequence when emerged. */ - STAND_EMERGED("Stand (emerged)"), - /** For buried creatures only: stand sequence when hidden. */ - STAND_HIDDEN("Stand (hidden)"), - STANCE("Combat ready"), - STANCE2("Combat ready"), - GET_HIT("Get hit"), - /** Dying sequence; may also be used for sleep sequence. */ - DIE("Die"), - TWITCH("Twitch"), - /** The animation sequence used while chanting a spell. */ - SPELL("Conjure spell"), - /** The animation sequence used when releasing a spell. */ - CAST("Cast spell"), - SPELL1("Conjure spell 1"), - CAST1("Cast spell 1"), - SPELL2("Conjure spell 2"), - CAST2("Cast spell 2"), - SPELL3("Conjure spell 3"), - CAST3("Cast spell 3"), - SPELL4("Conjure spell 4"), - CAST4("Cast spell 4"), - SLEEP("Sleep"), - GET_UP("Get up"), - SLEEP2("Sleep 2"), - GET_UP2("Get up 2"), - WALK("Walk"), - ATTACK("Attack"), - ATTACK_2("Attack 2"), - ATTACK_3("Attack 3"), - ATTACK_4("Attack 4"), - ATTACK_5("Attack 5"), - ATTACK_2H("Attack (2-h)"), - /** 1-h slash attack; also used for throwing/sling in BG1/IWD */ - ATTACK_SLASH_1H("Attack (slash)"), - /** 1-h backslash attack */ - ATTACK_BACKSLASH_1H("Attack (backslash)"), - /** 1-h thrust/jab attack */ - ATTACK_JAB_1H("Attack (jab)"), - /** 2-h slash attack */ - ATTACK_SLASH_2H("Attack (slash)"), - /** 2-h backslash attack */ - ATTACK_BACKSLASH_2H("Attack (backslash)"), - /** 2-h thrust/jab attack */ - ATTACK_JAB_2H("Attack (jab)"), - /** two-weapon attack */ - ATTACK_2WEAPONS1("Attack (two-weapons) 1"), - /** two-weapon attack */ - ATTACK_2WEAPONS2("Attack (two-weapons) 2"), - /** Generic overhead attack; may be used for throwing weapons and weapons without equipped appearance. */ - ATTACK_OVERHEAD("Attack (overhead)"), - ATTACK_BOW("Attack (bow)"), - ATTACK_SLING("Attack (sling)"), - ATTACK_CROSSBOW("Attack (crossbow)"), - /** Generic ranged attack animation (e.g. for innate powers/breath weapons) */ - SHOOT("Attack (ranged)"), - /** For buried creatures only: monster emerges from underground. */ - EMERGE("Emerge"), - /** For buried creatures only: monster retreats to underground. */ - HIDE("Hide"), - - PST_ATTACK1("Attack"), - PST_ATTACK2("Attack 2"), - PST_ATTACK3("Attack 3"), - PST_GET_HIT("Get hit"), - PST_RUN("Run"), - PST_WALK("Walk"), - PST_SPELL1("Cast spell"), - PST_SPELL2("Cast spell 2"), - PST_SPELL3("Cast spell 3"), - PST_GET_UP("Get up"), - PST_DIE_FORWARD("Die (fall forward)"), - PST_DIE_BACKWARD("Die (fall backwards)"), - PST_DIE_COLLAPSE("Die (collapse)"), - PST_TALK1("Talk"), - PST_TALK2("Talk 2"), - PST_TALK3("Talk 3"), - PST_STAND_FIDGET1("Stand (fidget)"), - PST_STAND_FIDGET2("Stand (fidget 2)"), - PST_STANCE_FIDGET1("Combat ready (fidget)"), - PST_STANCE_FIDGET2("Combat ready (fidget 2)"), - PST_STAND("Stand"), - PST_STANCE("Combat ready"), - PST_STANCE_TO_STAND("Combat ready to stand"), - PST_STAND_TO_STANCE("Stand to combat ready"), - PST_MISC1("Custom sequence 1"), - PST_MISC2("Custom sequence 2"), - PST_MISC3("Custom sequence 3"), - PST_MISC4("Custom sequence 4"), - PST_MISC5("Custom sequence 5"), - PST_MISC6("Custom sequence 6"), - PST_MISC7("Custom sequence 7"), - PST_MISC8("Custom sequence 8"), - PST_MISC9("Custom sequence 9"), - PST_MISC10("Custom sequence 10"), - PST_MISC11("Custom sequence 11"), - PST_MISC12("Custom sequence 12"), - PST_MISC13("Custom sequence 13"), - PST_MISC14("Custom sequence 14"), - PST_MISC15("Custom sequence 15"), - PST_MISC16("Custom sequence 16"), - PST_MISC17("Custom sequence 17"), - PST_MISC18("Custom sequence 18"), - PST_MISC19("Custom sequence 19"), - PST_MISC20("Custom sequence 20"); - - private final String desc; - - private Sequence() { this(null); } - private Sequence(String desc) { this.desc = desc; } - - @Override - public String toString() - { - return (desc != null) ? desc : super.toString(); - } - } - - /** Available cardinal directions for action sequences. */ - public enum Direction { - /** South */ - S(0), - /** South-southwest */ - SSW(1), - /** Southwest */ - SW(2), - /** West-southwest */ - WSW(3), - /** West */ - W(4), - /** West-northwest */ - WNW(5), - /** Northwest */ - NW(6), - /** North-northwest */ - NNW(7), - /** North */ - N(8), - /** North-northeast */ - NNE(9), - /** Northeast */ - NE(10), - /** East-northeast */ - ENE(11), - /** East */ - E(12), - /** East-southeast */ - ESE(13), - /** Southeast */ - SE(14), - /** South-southeast */ - SSE(15); - - private final int dir; - private Direction(int dir) { this.dir = dir; } - - /** Returns the numeric direction value. */ - public int getValue() { return dir; } - - /** - * Determines the {@link Direction} instance associated with the specified numeric value and returns it. - * Return {@code null} if association could not be determined. - */ - public static Direction from(int value) { - for (final Direction d : Direction.values()) { - if (d.getValue() == value) { - return d; - } - } - return null; - } - } - /** Mappings between animation types and compatible sprite classes. */ - private static final EnumMap> typeAssociations = - new EnumMap>(AnimationType.class) {{ - put(AnimationType.EFFECT, EffectDecoder.class); - put(AnimationType.MONSTER_QUADRANT, MonsterQuadrantDecoder.class); - put(AnimationType.MONSTER_MULTI, MonsterMultiDecoder.class); - put(AnimationType.MONSTER_MULTI_NEW, MonsterMultiNewDecoder.class); - put(AnimationType.MONSTER_LAYERED_SPELL, MonsterLayeredSpellDecoder.class); - put(AnimationType.MONSTER_ANKHEG, MonsterAnkhegDecoder.class); - put(AnimationType.TOWN_STATIC, TownStaticDecoder.class); - put(AnimationType.CHARACTER, CharacterDecoder.class); - put(AnimationType.CHARACTER_OLD, CharacterOldDecoder.class); - put(AnimationType.MONSTER, MonsterDecoder.class); - put(AnimationType.MONSTER_OLD, MonsterOldDecoder.class); - put(AnimationType.MONSTER_LAYERED, MonsterLayeredDecoder.class); - put(AnimationType.MONSTER_LARGE, MonsterLargeDecoder.class); - put(AnimationType.MONSTER_LARGE_16, MonsterLarge16Decoder.class); - put(AnimationType.AMBIENT_STATIC, AmbientStaticDecoder.class); - put(AnimationType.AMBIENT, AmbientDecoder.class); - put(AnimationType.FLYING, FlyingDecoder.class); - put(AnimationType.MONSTER_ICEWIND, MonsterIcewindDecoder.class); - put(AnimationType.MONSTER_PLANESCAPE, MonsterPlanescapeDecoder.class); + private static final EnumMap> typeAssociations = + new EnumMap>(AnimationInfo.Type.class) {{ + put(AnimationInfo.Type.EFFECT, EffectDecoder.class); + put(AnimationInfo.Type.MONSTER_QUADRANT, MonsterQuadrantDecoder.class); + put(AnimationInfo.Type.MONSTER_MULTI, MonsterMultiDecoder.class); + put(AnimationInfo.Type.MONSTER_MULTI_NEW, MonsterMultiNewDecoder.class); + put(AnimationInfo.Type.MONSTER_LAYERED_SPELL, MonsterLayeredSpellDecoder.class); + put(AnimationInfo.Type.MONSTER_ANKHEG, MonsterAnkhegDecoder.class); + put(AnimationInfo.Type.TOWN_STATIC, TownStaticDecoder.class); + put(AnimationInfo.Type.CHARACTER, CharacterDecoder.class); + put(AnimationInfo.Type.CHARACTER_OLD, CharacterOldDecoder.class); + put(AnimationInfo.Type.MONSTER, MonsterDecoder.class); + put(AnimationInfo.Type.MONSTER_OLD, MonsterOldDecoder.class); + put(AnimationInfo.Type.MONSTER_LAYERED, MonsterLayeredDecoder.class); + put(AnimationInfo.Type.MONSTER_LARGE, MonsterLargeDecoder.class); + put(AnimationInfo.Type.MONSTER_LARGE_16, MonsterLarge16Decoder.class); + put(AnimationInfo.Type.AMBIENT_STATIC, AmbientStaticDecoder.class); + put(AnimationInfo.Type.AMBIENT, AmbientDecoder.class); + put(AnimationInfo.Type.FLYING, FlyingDecoder.class); + put(AnimationInfo.Type.MONSTER_ICEWIND, MonsterIcewindDecoder.class); + put(AnimationInfo.Type.MONSTER_PLANESCAPE, MonsterPlanescapeDecoder.class); }}; /** @@ -749,14 +251,14 @@ public static Class getSpriteDecoderClass(int animation Class retVal = null; // Testing Infinity Animation range first - AnimationType animType = AnimationType.containsInfinityAnimations(animationId); + AnimationInfo.Type animType = AnimationInfo.Type.containsInfinityAnimations(animationId); if (animType != null) { retVal = typeAssociations.get(animType); } // Testing regular ranges if (retVal == null) { - for (final AnimationType type : AnimationType.values()) { + for (final AnimationInfo.Type type : AnimationInfo.Type.values()) { if (type.contains(animationId)) { retVal = typeAssociations.get(type); if (retVal != null) { @@ -774,7 +276,7 @@ public static Class getSpriteDecoderClass(int animation * @param type the {@code AnimationType} * @return the associated {@code SpriteClass} class object. Returns {@code null} if class could not be determined. */ - public static Class getSpriteDecoderClass(AnimationType type) + public static Class getSpriteDecoderClass(AnimationInfo.Type type) { return typeAssociations.get(type); } @@ -787,7 +289,7 @@ public static Class getSpriteDecoderClass(AnimationType * @param ini the INI file with creature animation attributes * @throws Exception */ - protected SpriteDecoder(AnimationType type, int animationId, IniMap ini) throws Exception + protected SpriteDecoder(AnimationInfo.Type type, int animationId, IniMap ini) throws Exception { Objects.requireNonNull(type, "Animation type cannot be null"); Objects.requireNonNull(ini, "No INI data available for animation id: " + animationId); @@ -810,7 +312,7 @@ protected SpriteDecoder(AnimationType type, int animationId, IniMap ini) throws * @param cre the CRE resource instance. * @throws Exception */ - protected SpriteDecoder(AnimationType type, CreResource cre) throws Exception + protected SpriteDecoder(AnimationInfo.Type type, CreResource cre) throws Exception { Objects.requireNonNull(type, "Animation type cannot be null"); this.attributesMap = new TreeMap<>(); @@ -881,7 +383,7 @@ public Iterator getAttributeIterator() } /** Returns the type of the current creature animation. */ - public AnimationType getAnimationType() + public AnimationInfo.Type getAnimationType() { return getAttribute(KEY_ANIMATION_TYPE); } @@ -2036,7 +1538,7 @@ protected void applyFalseColors(BamV1Control control, SegmentDef sd) palette = SpriteUtils.replaceColors(palette, range, ofs.intValue(), range.length, false); } - if (getAnimationType() != AnimationType.MONSTER_PLANESCAPE) { + if (getAnimationType() != AnimationInfo.Type.MONSTER_PLANESCAPE) { // preparing offset array final int srcOfs = 4; final int dstOfs = 88; @@ -2243,7 +1745,7 @@ protected static Image getAllegianceImage(int value) * @param type the animation type. * @return the initialized "general" INI section as list of strings. An empty list otherwise. */ - protected static List processTableDataGeneral(String[] data, AnimationType type) + protected static List processTableDataGeneral(String[] data, AnimationInfo.Type type) { List retVal = new ArrayList<>(); if (data == null || type == null) { @@ -2260,7 +1762,7 @@ protected static List processTableDataGeneral(String[] data, AnimationTy String palette = SpriteTables.valueToString(data, SpriteTables.COLUMN_PALETTE, ""); int animIndex = SpriteTables.valueToInt(data, SpriteTables.COLUMN_TYPE, -1); - if (animIndex < 0 || animIndex >= AnimationType.values().length || AnimationType.values()[animIndex] != type) { + if (animIndex < 0 || animIndex >= AnimationInfo.Type.values().length || AnimationInfo.Type.values()[animIndex] != type) { return retVal; } @@ -2379,8 +1881,8 @@ protected static Class detectAnimationType(int animatio retVal = cls; } } else { - for (final AnimationType type : AnimationType.values()) { - if (type != AnimationType.MONSTER_PLANESCAPE) { + for (final AnimationInfo.Type type : AnimationInfo.Type.values()) { + if (type != AnimationInfo.Type.MONSTER_PLANESCAPE) { cls = typeAssociations.get(type); if (isSpriteDecoderAvailable(cls, animationId, iniList)) { retVal = cls; @@ -2541,7 +2043,7 @@ private static List guessIniMaps(int animationId) } List tableEntries = new ArrayList<>(); - AnimationType type = AnimationType.typeOfId(animationId); + AnimationInfo.Type type = AnimationInfo.Type.typeOfId(animationId); if (type == null) { return retVal; } @@ -2834,56 +2336,6 @@ public boolean equals(Object o) //-------------------------- INNER CLASSES -------------------------- - /** - * A helper class that allows you to define numeric ranges. - */ - public static class Range - { - private final List> ranges = new ArrayList<>(); - - /** - * Defines a range that starts at base and ends at base + range (inclusive). - * @param base start value of the range. - * @param range length of the range. - */ - public Range(int base, int range) - { - this(base, range, 0, 0, 0); - } - - /** - * Defines a set of common ranges. The resolved list of ranges can be defined as:

    - * base + ([subBase, subBase+subRange] << subPos) + [0, range]

    - * where [x, y] defines a range from x to y (inclusive). - * @param base start value of the range. - * @param range length of the range. - */ - public Range(int base, int range, int subBase, int subRange, int subPos) - { - init(base, range, subBase, subRange, subPos); - } - - /** Returns whether the range covers the specified value. */ - public boolean contains(int value) - { - return ranges - .parallelStream() - .anyMatch(c -> (value >= c.getValue0().intValue() && - value <= (c.getValue0().intValue() + c.getValue1().intValue()))); - } - - private void init(int base, int range, int subBase, int subRange, int subPos) - { - range = Math.abs(range); - subRange = Math.abs(subRange); - subPos = Math.max(0, Math.min(32, subPos)); - for (int i = 0; i <= subRange; i++) { - int curBase = base + ((subBase + i) << subPos); - ranges.add(Couple.with(curBase, range)); - } - } - } - /** * Represents an operation that is called once per source BAM resource when creating a creature animation. */ diff --git a/src/org/infinity/resource/cre/decoder/TownStaticDecoder.java b/src/org/infinity/resource/cre/decoder/TownStaticDecoder.java index 1b13cc98a..6e70d7d80 100644 --- a/src/org/infinity/resource/cre/decoder/TownStaticDecoder.java +++ b/src/org/infinity/resource/cre/decoder/TownStaticDecoder.java @@ -10,9 +10,11 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.internal.AnimationInfo; import org.infinity.resource.cre.decoder.internal.DecoderAttribute; import org.infinity.resource.cre.decoder.internal.SegmentDef; import org.infinity.resource.cre.decoder.internal.SeqDef; +import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; @@ -25,7 +27,7 @@ public class TownStaticDecoder extends SpriteDecoder { /** The animation type associated with this class definition. */ - public static final AnimationType ANIMATION_TYPE = AnimationType.TOWN_STATIC; + public static final AnimationInfo.Type ANIMATION_TYPE = AnimationInfo.Type.TOWN_STATIC; public static final DecoderAttribute KEY_CAN_LIE_DOWN = DecoderAttribute.with("can_lie_down", DecoderAttribute.DataType.BOOLEAN); diff --git a/src/org/infinity/resource/cre/decoder/internal/AnimationInfo.java b/src/org/infinity/resource/cre/decoder/internal/AnimationInfo.java new file mode 100644 index 000000000..54525bc99 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/internal/AnimationInfo.java @@ -0,0 +1,344 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder.internal; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.List; + +import org.infinity.resource.Profile; +import org.infinity.util.Misc; +import org.infinity.util.tuples.Couple; + +/** + * Static class containing animation slot and type information. + */ +public class AnimationInfo +{ + // Predefined sets of games with common animation slot mappings + private static final EnumSet TYPE_GAME_BG1 = EnumSet.of(Profile.Game.BG1, Profile.Game.BG1TotSC); + private static final EnumSet TYPE_GAME_BG2_TOB = EnumSet.of(Profile.Game.BG2ToB, Profile.Game.BGT, Profile.Game.Tutu); + // all BG2 game variants + private static final EnumSet TYPE_GAME_BG2 = EnumSet.of(Profile.Game.BG2SoA, Profile.Game.BG2ToB, Profile.Game.BGT, + Profile.Game.Tutu); + private static final EnumSet TYPE_GAME_IWD = EnumSet.of(Profile.Game.IWD); + private static final EnumSet TYPE_GAME_IWD_HOW = EnumSet.of(Profile.Game.IWDHoW, Profile.Game.IWDHowTotLM); + private static final EnumSet TYPE_GAME_IWD2 = EnumSet.of(Profile.Game.IWD2); + private static final EnumSet TYPE_GAME_PST = EnumSet.of(Profile.Game.PST); + // all EE games + private static final EnumSet TYPE_GAME_EE = EnumSet.of(Profile.Game.BG1EE, Profile.Game.BG1SoD, Profile.Game.BG2EE, + Profile.Game.EET, Profile.Game.IWDEE, Profile.Game.PSTEE); + private static final EnumSet TYPE_GAME_PSTEE = EnumSet.of(Profile.Game.PSTEE); + // all games except PST + private static final EnumSet TYPE_GAME_ALL = EnumSet.complementOf(EnumSet.of(Profile.Game.Unknown, Profile.Game.PST)); + + // Predefined slot ranges for specific animation types + private static final List RANGE_EFFECT = Arrays.asList(new NumberRange(0, 0xfff)); + private static final List RANGE_MONSTER_QUADRANT = Arrays.asList(new NumberRange(0x1000, 0x1ff)); + private static final List RANGE_MONSTER_MULTI = Arrays.asList(new NumberRange(0x1200, 0xff), + new NumberRange(0x1400, 0xbff)); + private static final List RANGE_MONSTER_MULTI_NEW = Arrays.asList(new NumberRange(0x1300, 0xff)); + private static final List RANGE_MONSTER_LAYERED = Arrays.asList(new NumberRange(0x8000, 0xfff)); + private static final List RANGE_MONSTER_LAYERED_SPELL = Arrays.asList(new NumberRange(0x2000, 0xfff)); + private static final List RANGE_MONSTER_ANKHEG = Arrays.asList(new NumberRange(0x3000, 0xfff)); + private static final List RANGE_TOWN_STATIC = Arrays.asList(new NumberRange(0x4000, 0xfff)); + private static final List RANGE_CHARACTER = Arrays.asList(new NumberRange(0x5000, 0x3ff), + new NumberRange(0x5500, 0xff), + new NumberRange(0x6000, 0x3ff), + new NumberRange(0x6500, 0xff)); + private static final List RANGE_CHARACTER_IA = Arrays.asList(new NumberRange(0x6600, 0x4ff)); + private static final List RANGE_CHARACTER_OLD = Arrays.asList(new NumberRange(0x5400, 0xff), + new NumberRange(0x5600, 0x9ff), + new NumberRange(0x6400, 0xff), + new NumberRange(0x6600, 0x9ff)); + private static final List RANGE_MONSTER = Arrays.asList(new NumberRange(0x7002, 0xd, 0x00, 0x1f, 4), + new NumberRange(0x7004, 0xb, 0x20, 0x0f, 4), + new NumberRange(0x7000, 0xf, 0x30, 0x0f, 4), + new NumberRange(0x7003, 0xc, 0x40, 0x0f, 4), + new NumberRange(0x7002, 0xd, 0x50, 0x0f, 4), + new NumberRange(0x7003, 0xc, 0x70, 0x0f, 4), + new NumberRange(0x7005, 0xa, 0x90, 0x1f, 4), + new NumberRange(0x7007, 0x8, 0xb0, 0x0f, 4), + new NumberRange(0x7002, 0xd, 0xc0, 0x0f, 4), + new NumberRange(0x7002, 0xd, 0xe0, 0x0f, 4), + new NumberRange(0x7000, 0xf, 0xf0, 0x0f, 4)); + private static final List RANGE_MONSTER_IA = Arrays.asList(new NumberRange(0x5b00, 0x4ff)); + private static final List RANGE_MONSTER_OLD = Arrays.asList(new NumberRange(0x7000, 0x1, 0x00, 0x1f, 4), + new NumberRange(0x7000, 0x3, 0x20, 0x0f, 4), + new NumberRange(0x7000, 0x2, 0x40, 0x0f, 4), + new NumberRange(0x7000, 0x1, 0x50, 0x0f, 4), + new NumberRange(0x7000, 0xf, 0x60, 0x0f, 4), + new NumberRange(0x7000, 0x2, 0x70, 0x0f, 4), + new NumberRange(0x7000, 0xf, 0x80, 0x0f, 4), + new NumberRange(0x7000, 0x4, 0x90, 0x1f, 4), + new NumberRange(0x7000, 0x6, 0xb0, 0x0f, 4), + new NumberRange(0x7000, 0x1, 0xc0, 0x0f, 4), + new NumberRange(0x7000, 0xf, 0xd0, 0x0f, 4), + new NumberRange(0x7000, 0x1, 0xe0, 0x0f, 4)); + private static final List RANGE_MONSTER_OLD_IA = Arrays.asList(new NumberRange(0x547a, 0x479)); + private static final List RANGE_MONSTER_LARGE = Arrays.asList(new NumberRange(0x9000, 0xfff)); + private static final List RANGE_MONSTER_LARGE_16 = Arrays.asList(new NumberRange(0xa000, 0xfff)); + private static final List RANGE_AMBIENT_STATIC = Arrays.asList(new NumberRange(0xb000, 0xfff)); + private static final List RANGE_AMBIENT = Arrays.asList(new NumberRange(0xc000, 0xfff)); + private static final List RANGE_FLYING = Arrays.asList(new NumberRange(0xd000, 0xfff)); + private static final List RANGE_MONSTER_ICEWIND = Arrays.asList(new NumberRange(0xe000, 0xfff)); + private static final List RANGE_MONSTER_ICEWIND_EX = Arrays.asList(new NumberRange(0xe000, 0x1fff)); + private static final List RANGE_MONSTER_ICEWIND_IA = Arrays.asList(new NumberRange(0x5000, 0x478)); + private static final List RANGE_MONSTER_PLANESCAPE = Arrays.asList(new NumberRange(0xf000, 0xfff)); + private static final List RANGE_MONSTER_PLANESCAPE_EX = Arrays.asList(new NumberRange(0x0000, 0xffff)); + + public enum Type { + /** Animation type: 0000 */ + EFFECT(0x0000, "effect", Arrays.asList( + Couple.with(TYPE_GAME_ALL, RANGE_EFFECT))), // type=0 + /** Animation type: 1000 (slots [1000..11ff]) */ + MONSTER_QUADRANT(0x1000, "monster_quadrant", Arrays.asList( + Couple.with(TYPE_GAME_ALL, RANGE_MONSTER_QUADRANT))), // type=1 + /** Animation type: 1000 (slots [1200..12ff], [1400..1fff] */ + MONSTER_MULTI(0x1000, "monster_multi", Arrays.asList( + Couple.with(TYPE_GAME_EE, RANGE_MONSTER_MULTI), + Couple.with(TYPE_GAME_BG2, RANGE_MONSTER_MULTI), + Couple.with(TYPE_GAME_IWD2, RANGE_MONSTER_MULTI))), // type=2 + /** Animation type: 1000 (slots [1300..13ff]) */ + MONSTER_MULTI_NEW(0x1000, "multi_new", Arrays.asList( + Couple.with(TYPE_GAME_EE, RANGE_MONSTER_MULTI_NEW), + Couple.with(TYPE_GAME_BG2_TOB, RANGE_MONSTER_MULTI_NEW))), // type=3 + /** Animation type: 8000 */ + MONSTER_LAYERED(0x8000, "monster_layered", Arrays.asList( + Couple.with(TYPE_GAME_ALL, RANGE_MONSTER_LAYERED))), // type=4 + /** Animation type: 2000 */ + MONSTER_LAYERED_SPELL(0x2000, "monster_layered_spell", Arrays.asList( + Couple.with(TYPE_GAME_ALL, RANGE_MONSTER_LAYERED_SPELL))), // type=5 + /** Animation type: 3000 */ + MONSTER_ANKHEG(0x3000, "monster_ankheg", Arrays.asList( + Couple.with(TYPE_GAME_ALL, RANGE_MONSTER_ANKHEG))), // type=6 + /** Animation type: 4000 */ + TOWN_STATIC(0x4000, "town_static", Arrays.asList( + Couple.with(TYPE_GAME_ALL, RANGE_TOWN_STATIC))), // type=7 + /** Animation types: 5000, 6000 (slots [5000..53ff], [5500..55ff], [6000..63ff], [6500..65ff]) */ + CHARACTER(new int[] {0x5000, 0x6000}, "character", Arrays.asList( + Couple.with(TYPE_GAME_EE, RANGE_CHARACTER), + Couple.with(TYPE_GAME_BG2, RANGE_CHARACTER), + Couple.with(TYPE_GAME_IWD_HOW, RANGE_CHARACTER), + Couple.with(TYPE_GAME_IWD2, RANGE_CHARACTER)), + RANGE_CHARACTER_IA), // type=8 + /** Animation types: 5000, 6000 (slots [5400..54ff], [5600..5fff], [6400..64ff], [6600..6fff]) */ + CHARACTER_OLD(new int[] {0x5000, 0x6000}, "character_old", Arrays.asList( + Couple.with(TYPE_GAME_ALL, RANGE_CHARACTER_OLD), + Couple.with(TYPE_GAME_BG1, RANGE_CHARACTER), + Couple.with(TYPE_GAME_IWD, RANGE_CHARACTER))), // type=9 + /** Animation type: 7000 (many subranges) */ + MONSTER(0x7000, "monster", Arrays.asList( + Couple.with(TYPE_GAME_EE, RANGE_MONSTER), + Couple.with(TYPE_GAME_BG2, RANGE_MONSTER), + Couple.with(TYPE_GAME_IWD, RANGE_MONSTER), + Couple.with(TYPE_GAME_IWD_HOW, RANGE_MONSTER), + Couple.with(TYPE_GAME_IWD2, RANGE_MONSTER)), + RANGE_MONSTER_IA), // type=10 + /** Animation type: 7000 (many subranges) */ + MONSTER_OLD(0x7000, "monster_old", Arrays.asList( + Couple.with(TYPE_GAME_ALL, RANGE_MONSTER_OLD)), + RANGE_MONSTER_OLD_IA), // type=11 + /** Animation type: 9000 */ + MONSTER_LARGE(0x9000, "monster_large", Arrays.asList( + Couple.with(TYPE_GAME_ALL, RANGE_MONSTER_LARGE))), // type=12 + /** Animation type: A000 */ + MONSTER_LARGE_16(0xa000, "monster_large16", Arrays.asList( + Couple.with(TYPE_GAME_ALL, RANGE_MONSTER_LARGE_16))), // type=13 + /** Animation type: B000 */ + AMBIENT_STATIC(0xb000, "ambient_static", Arrays.asList( + Couple.with(TYPE_GAME_ALL, RANGE_AMBIENT_STATIC))), // type=14 + /** Animation type: C000 */ + AMBIENT(0xc000, "ambient", Arrays.asList( + Couple.with(TYPE_GAME_ALL, RANGE_AMBIENT))), // type=15 + /** Animation type: D000 */ + FLYING(0xd000, "flying", Arrays.asList( + Couple.with(TYPE_GAME_ALL, RANGE_FLYING))), // type=16 + /** Animation type: E000 (for non-EE: also slots [f000..ffff]) */ + MONSTER_ICEWIND(0xe000, "monster_icewind", Arrays.asList( + Couple.with(TYPE_GAME_EE, RANGE_MONSTER_ICEWIND), + Couple.with(TYPE_GAME_BG2, RANGE_MONSTER_ICEWIND), + Couple.with(TYPE_GAME_IWD, RANGE_MONSTER_ICEWIND_EX), + Couple.with(TYPE_GAME_IWD_HOW, RANGE_MONSTER_ICEWIND_EX), + Couple.with(TYPE_GAME_IWD2, RANGE_MONSTER_ICEWIND_EX)), + RANGE_MONSTER_ICEWIND_IA), // type=17 + /** Animation type: F000 */ + MONSTER_PLANESCAPE(0xf000, "monster_planescape", Arrays.asList( + Couple.with(TYPE_GAME_PSTEE, RANGE_MONSTER_PLANESCAPE), + Couple.with(TYPE_GAME_PST, RANGE_MONSTER_PLANESCAPE_EX))); // type=18 + + private final EnumMap> rangeMap = new EnumMap<>(Profile.Game.class); + private final List iaRanges; + private final int[] animationTypes; + private final String sectionName; + + /** + * @param type slot base range + * @param sectionName INI section name + * @param entries list of games and their associated slot ranges. + */ + private Type(int type, String sectionName, List, List>> entries) + throws IllegalArgumentException + { + this(new int[] {type}, sectionName, entries, null); + } + + /** + * @param type + * @param sectionName + * @param entries + * @param infinityAnimationRanges + * @throws IllegalArgumentException + */ + private Type(int type, String sectionName, List, List>> entries, + List infinityAnimationRanges) + throws IllegalArgumentException + { + this(new int[] {type}, sectionName, entries, infinityAnimationRanges); + } + + /** + * @param types + * @param sectionName + * @param entries + * @throws IllegalArgumentException + */ + private Type(int[] types, String sectionName, List, List>> entries) + throws IllegalArgumentException + { + this(types, sectionName, entries, null); + } + + /** + * @param type list of slot base ranges + * @param sectionName INI section name + * @param entries list of games and their associated slot ranges. + * @throws IllegalArgumentException + */ + private Type(int[] types, String sectionName, List, List>> entries, + List infinityAnimationRanges) throws IllegalArgumentException + { + try { + Misc.requireCondition(types != null && types.length > 0, "Type cannot be empty", IllegalArgumentException.class); + Misc.requireCondition(sectionName != null && !sectionName.isEmpty(), "Section name cannot be empty", IllegalArgumentException.class); + } catch (IllegalArgumentException iae) { + throw iae; + } catch (Exception e) { + } + this.animationTypes = types; + this.sectionName = sectionName; + for (final Couple, List> entry : entries) { + EnumSet games = entry.getValue0(); + List ranges = entry.getValue1(); + for (final Profile.Game game : games) { + if (ranges.size() > 0) { + List list = this.rangeMap.get(game); + if (list != null) { + list.addAll(ranges); + } else { + this.rangeMap.put(game, new ArrayList<>(ranges)); + } + } + } + } + this.iaRanges = infinityAnimationRanges; + } + + /** Returns the name for the type-specific INI section. */ + public String getSectionName() { return sectionName; } + + /** Returns the first available base animation type associated with the enum instance. */ + public int getType() { return animationTypes[0]; } + + /** Returns the number of defined base animation types associated with the enum instance. */ + public int getTypeCount() { return animationTypes.length; } + + /** Returns the specified base animation type associated with the enum instance. */ + public int getType(int idx) { return animationTypes[idx]; } + + /** + * Returns whether the specified value is covered by the ranges associated with the enum instance + * for the current game. + */ + public boolean contains(int value) { return contains(Profile.getGame(), value); } + + /** + * Returns whether the specified value is covered by the ranges associated with the enum instance + * for the specified game. + */ + public boolean contains(Profile.Game game, int value) + { + if (game == null) { + game = Profile.getGame(); + } + boolean retVal = false; + + // Infinity Animations sprite takes precedence over regular sprite + Type type = containsInfinityAnimations(value); + if (type != null && type != this) { + return retVal; + } + retVal = (type == this); + + if (!retVal) { + retVal = contains(value, rangeMap.get(Profile.getGame())); + } + return retVal; + } + + // Checks whether specified value is covered by the given ranges + private static boolean contains(int value, List ranges) + { + if (ranges != null) { + return ranges + .parallelStream() + .anyMatch(r -> r.contains(value)); + } + return false; + } + + /** + * Determines the {@code AnimationType} enum where a defined Infinity Animations (IA) range covers the specied value. + * @param value the value to check. + * @return {@code AnimationType} enum supporting the specified IA value. + * Returns {@code null} if value is not covered by any IA range. + */ + public static Type containsInfinityAnimations(int value) + { + Type retVal = null; + if (Profile.getProperty(Profile.Key.GET_INFINITY_ANIMATIONS) > 0) { + for (Type type : Type.values()) { + if (contains(value, type.iaRanges)) { + retVal = type; + break; + } + } + } + return retVal; + } + + /** + * Returns the {@code AnimationType} enum covering the specified animation id. + * @param animationId the animation id + * @return {@code AnimationType} enum that covers the specified animation id. Returns {@code null} otherwise. + */ + public static Type typeOfId(int animationId) + { + for (final Type type : values()) { + if (type.contains(animationId)) { + return type; + } + } + return null; + } + } + + + private AnimationInfo() + { + } +} diff --git a/src/org/infinity/resource/cre/decoder/internal/DirDef.java b/src/org/infinity/resource/cre/decoder/internal/DirDef.java index 295179412..a9481b30a 100644 --- a/src/org/infinity/resource/cre/decoder/internal/DirDef.java +++ b/src/org/infinity/resource/cre/decoder/internal/DirDef.java @@ -6,8 +6,6 @@ import java.util.Objects; -import org.infinity.resource.cre.decoder.SpriteDecoder.Direction; - /** * Definition of a single direction in an animation sequence. */ diff --git a/src/org/infinity/resource/cre/decoder/internal/Direction.java b/src/org/infinity/resource/cre/decoder/internal/Direction.java new file mode 100644 index 000000000..60d1f8efa --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/internal/Direction.java @@ -0,0 +1,62 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder.internal; + +/** + * Available cardinal directions for animation sequences. + */ +public enum Direction { + /** South */ + S(0), + /** South-southwest */ + SSW(1), + /** Southwest */ + SW(2), + /** West-southwest */ + WSW(3), + /** West */ + W(4), + /** West-northwest */ + WNW(5), + /** Northwest */ + NW(6), + /** North-northwest */ + NNW(7), + /** North */ + N(8), + /** North-northeast */ + NNE(9), + /** Northeast */ + NE(10), + /** East-northeast */ + ENE(11), + /** East */ + E(12), + /** East-southeast */ + ESE(13), + /** Southeast */ + SE(14), + /** South-southeast */ + SSE(15); + + private final int dir; + private Direction(int dir) { this.dir = dir; } + + /** Returns the numeric direction value. */ + public int getValue() { return dir; } + + /** + * Determines the {@link Direction} instance associated with the specified numeric value and returns it. + * Return {@code null} if association could not be determined. + */ + public static Direction from(int value) { + for (final Direction d : Direction.values()) { + if (d.getValue() == value) { + return d; + } + } + return null; + } +} diff --git a/src/org/infinity/resource/cre/decoder/internal/NumberRange.java b/src/org/infinity/resource/cre/decoder/internal/NumberRange.java new file mode 100644 index 000000000..6d3babec8 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/internal/NumberRange.java @@ -0,0 +1,60 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder.internal; + +import java.util.ArrayList; +import java.util.List; + +import org.infinity.util.tuples.Couple; + +/** + * A class that allows you to define numeric ranges. + */ +public class NumberRange +{ + private final List> ranges = new ArrayList<>(); + + /** + * Defines a range that starts at base and ends at base + range (inclusive). + * @param base start value of the range. + * @param range length of the range. + */ + public NumberRange(int base, int range) + { + this(base, range, 0, 0, 0); + } + + /** + * Defines a set of common ranges. The resolved list of ranges can be defined as:

    + * base + ([subBase, subBase+subRange] << subPos) + [0, range]

    + * where [x, y] defines a range from x to y (inclusive). + * @param base start value of the range. + * @param range length of the range. + */ + public NumberRange(int base, int range, int subBase, int subRange, int subPos) + { + init(base, range, subBase, subRange, subPos); + } + + /** Returns whether the range covers the specified value. */ + public boolean contains(int value) + { + return ranges + .parallelStream() + .anyMatch(c -> (value >= c.getValue0().intValue() && + value <= (c.getValue0().intValue() + c.getValue1().intValue()))); + } + + private void init(int base, int range, int subBase, int subRange, int subPos) + { + range = Math.abs(range); + subRange = Math.abs(subRange); + subPos = Math.max(0, Math.min(32, subPos)); + for (int i = 0; i <= subRange; i++) { + int curBase = base + ((subBase + i) << subPos); + ranges.add(Couple.with(curBase, range)); + } + } +} diff --git a/src/org/infinity/resource/cre/decoder/internal/SeqDef.java b/src/org/infinity/resource/cre/decoder/internal/SeqDef.java index df4490807..9fb1a38b4 100644 --- a/src/org/infinity/resource/cre/decoder/internal/SeqDef.java +++ b/src/org/infinity/resource/cre/decoder/internal/SeqDef.java @@ -12,8 +12,6 @@ import java.util.Objects; import org.infinity.resource.cre.decoder.SpriteDecoder; -import org.infinity.resource.cre.decoder.SpriteDecoder.Direction; -import org.infinity.resource.cre.decoder.SpriteDecoder.Sequence; import org.infinity.resource.key.ResourceEntry; /** diff --git a/src/org/infinity/resource/cre/decoder/internal/Sequence.java b/src/org/infinity/resource/cre/decoder/internal/Sequence.java new file mode 100644 index 000000000..c98f4f875 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/internal/Sequence.java @@ -0,0 +1,135 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder.internal; + +/** + * Available animation sequences. Note: PST-specific animation sequences are prefixed by "PST_". + */ +public enum Sequence { + /** Special value: Used when no animation sequence is loaded. */ + NONE, + STAND("Stand"), + STAND2("Stand 2"), + STAND3("Stand 3"), + /** For buried creatures only: stand sequence when emerged. */ + STAND_EMERGED("Stand (emerged)"), + /** For buried creatures only: stand sequence when hidden. */ + STAND_HIDDEN("Stand (hidden)"), + STANCE("Combat ready"), + STANCE2("Combat ready"), + GET_HIT("Get hit"), + /** Dying sequence; may also be used for sleep sequence. */ + DIE("Die"), + TWITCH("Twitch"), + /** The animation sequence used while chanting a spell. */ + SPELL("Conjure spell"), + /** The animation sequence used when releasing a spell. */ + CAST("Cast spell"), + SPELL1("Conjure spell 1"), + CAST1("Cast spell 1"), + SPELL2("Conjure spell 2"), + CAST2("Cast spell 2"), + SPELL3("Conjure spell 3"), + CAST3("Cast spell 3"), + SPELL4("Conjure spell 4"), + CAST4("Cast spell 4"), + SLEEP("Sleep"), + GET_UP("Get up"), + SLEEP2("Sleep 2"), + GET_UP2("Get up 2"), + WALK("Walk"), + ATTACK("Attack"), + ATTACK_2("Attack 2"), + ATTACK_3("Attack 3"), + ATTACK_4("Attack 4"), + ATTACK_5("Attack 5"), + ATTACK_2H("Attack (2-h)"), + /** 1-h slash attack; also used for throwing/sling in BG1/IWD */ + ATTACK_SLASH_1H("Attack (slash)"), + /** 1-h backslash attack */ + ATTACK_BACKSLASH_1H("Attack (backslash)"), + /** 1-h thrust/jab attack */ + ATTACK_JAB_1H("Attack (jab)"), + /** 2-h slash attack */ + ATTACK_SLASH_2H("Attack (slash)"), + /** 2-h backslash attack */ + ATTACK_BACKSLASH_2H("Attack (backslash)"), + /** 2-h thrust/jab attack */ + ATTACK_JAB_2H("Attack (jab)"), + /** two-weapon attack */ + ATTACK_2WEAPONS1("Attack (two-weapons) 1"), + /** two-weapon attack */ + ATTACK_2WEAPONS2("Attack (two-weapons) 2"), + /** Generic overhead attack; may be used for throwing weapons and weapons without equipped appearance. */ + ATTACK_OVERHEAD("Attack (overhead)"), + ATTACK_BOW("Attack (bow)"), + ATTACK_SLING("Attack (sling)"), + ATTACK_CROSSBOW("Attack (crossbow)"), + /** Generic ranged attack animation (e.g. for innate powers/breath weapons) */ + SHOOT("Attack (ranged)"), + /** For buried creatures only: monster emerges from underground. */ + EMERGE("Emerge"), + /** For buried creatures only: monster retreats to underground. */ + HIDE("Hide"), + + PST_ATTACK1("Attack"), + PST_ATTACK2("Attack 2"), + PST_ATTACK3("Attack 3"), + PST_GET_HIT("Get hit"), + PST_RUN("Run"), + PST_WALK("Walk"), + PST_SPELL1("Cast spell"), + PST_SPELL2("Cast spell 2"), + PST_SPELL3("Cast spell 3"), + PST_GET_UP("Get up"), + PST_DIE_FORWARD("Die (fall forward)"), + PST_DIE_BACKWARD("Die (fall backwards)"), + PST_DIE_COLLAPSE("Die (collapse)"), + PST_TALK1("Talk"), + PST_TALK2("Talk 2"), + PST_TALK3("Talk 3"), + PST_STAND_FIDGET1("Stand (fidget)"), + PST_STAND_FIDGET2("Stand (fidget 2)"), + PST_STANCE_FIDGET1("Combat ready (fidget)"), + PST_STANCE_FIDGET2("Combat ready (fidget 2)"), + PST_STAND("Stand"), + PST_STANCE("Combat ready"), + PST_STANCE_TO_STAND("Combat ready to stand"), + PST_STAND_TO_STANCE("Stand to combat ready"), + PST_MISC1("Custom sequence 1"), + PST_MISC2("Custom sequence 2"), + PST_MISC3("Custom sequence 3"), + PST_MISC4("Custom sequence 4"), + PST_MISC5("Custom sequence 5"), + PST_MISC6("Custom sequence 6"), + PST_MISC7("Custom sequence 7"), + PST_MISC8("Custom sequence 8"), + PST_MISC9("Custom sequence 9"), + PST_MISC10("Custom sequence 10"), + PST_MISC11("Custom sequence 11"), + PST_MISC12("Custom sequence 12"), + PST_MISC13("Custom sequence 13"), + PST_MISC14("Custom sequence 14"), + PST_MISC15("Custom sequence 15"), + PST_MISC16("Custom sequence 16"), + PST_MISC17("Custom sequence 17"), + PST_MISC18("Custom sequence 18"), + PST_MISC19("Custom sequence 19"), + PST_MISC20("Custom sequence 20"); + + private final String desc; + + /** Creates a new {@code AnimationSequence} with an empty label. */ + private Sequence() { this(null); } + + /** Creates a new {@code AnimationSequence} with the specified label. */ + private Sequence(String desc) { this.desc = desc; } + + @Override + public String toString() + { + return (desc != null) ? desc : super.toString(); + } +} diff --git a/src/org/infinity/resource/cre/decoder/tables/InfinityTables.java b/src/org/infinity/resource/cre/decoder/tables/InfinityTables.java index 6c2124ff6..8be7f4716 100644 --- a/src/org/infinity/resource/cre/decoder/tables/InfinityTables.java +++ b/src/org/infinity/resource/cre/decoder/tables/InfinityTables.java @@ -14,6 +14,7 @@ import org.infinity.resource.Profile; import org.infinity.resource.cre.decoder.SpriteDecoder; +import org.infinity.resource.cre.decoder.internal.AnimationInfo; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IdsMap; import org.infinity.util.IdsMapEntry; @@ -78,7 +79,7 @@ private static List processTable(IdsMap idsMap, int animationId) // determining SpriteDecoder class instance int classType = SpriteTables.valueToInt(data, SpriteTables.COLUMN_TYPE, -1); - SpriteDecoder.AnimationType animType = SpriteDecoder.AnimationType.values()[classType]; + AnimationInfo.Type animType = AnimationInfo.Type.values()[classType]; Class cls = SpriteDecoder.getSpriteDecoderClass(animType); if (cls != null) { // calling method of signature: public static IniMap processTableData(String[] data); @@ -128,31 +129,31 @@ private static String[] parseTableEntry(int animationId, String entry) throws Ex // char type = SpriteTables.valueToString(items, 4, " ").charAt(0); - final HashMap animationTypes = new HashMap() {{ - put("BGI MONSTER LONG 4 PART", SpriteDecoder.AnimationType.MONSTER_QUADRANT); - put("DRAGONS", SpriteDecoder.AnimationType.MONSTER_MULTI); - put("BGII SPLIT 4 PART", SpriteDecoder.AnimationType.MONSTER_MULTI_NEW); - put("BGI SIMPLE CASTER", SpriteDecoder.AnimationType.MONSTER_LAYERED_SPELL); - put("BROKEN ANKHEG", SpriteDecoder.AnimationType.MONSTER_ANKHEG); - put("CHARACTER BGII", SpriteDecoder.AnimationType.CHARACTER); - put("CHARACTER BGI", SpriteDecoder.AnimationType.CHARACTER_OLD); - put("BGII UNSPLIT EXT.", SpriteDecoder.AnimationType.MONSTER); - put("BGII SPLIT", SpriteDecoder.AnimationType.MONSTER); - put("BGI SIMPLE MONSTER", SpriteDecoder.AnimationType.MONSTER_OLD); - put("BGI MONSTER LONG", SpriteDecoder.AnimationType.MONSTER_LARGE_16); - put("IWD", SpriteDecoder.AnimationType.MONSTER_ICEWIND); + final HashMap animationTypes = new HashMap() {{ + put("BGI MONSTER LONG 4 PART", AnimationInfo.Type.MONSTER_QUADRANT); + put("DRAGONS", AnimationInfo.Type.MONSTER_MULTI); + put("BGII SPLIT 4 PART", AnimationInfo.Type.MONSTER_MULTI_NEW); + put("BGI SIMPLE CASTER", AnimationInfo.Type.MONSTER_LAYERED_SPELL); + put("BROKEN ANKHEG", AnimationInfo.Type.MONSTER_ANKHEG); + put("CHARACTER BGII", AnimationInfo.Type.CHARACTER); + put("CHARACTER BGI", AnimationInfo.Type.CHARACTER_OLD); + put("BGII UNSPLIT EXT.", AnimationInfo.Type.MONSTER); + put("BGII SPLIT", AnimationInfo.Type.MONSTER); + put("BGI SIMPLE MONSTER", AnimationInfo.Type.MONSTER_OLD); + put("BGI MONSTER LONG", AnimationInfo.Type.MONSTER_LARGE_16); + put("IWD", AnimationInfo.Type.MONSTER_ICEWIND); }}; String typeString = null; - SpriteDecoder.AnimationType animType = null; + AnimationInfo.Type animType = null; for (int len = items.length - 5; len > 0 && animType == null; len--) { typeString = concatItems(items, 5, len); animType = animationTypes.get(typeString); } int type = -1; - for (int i = SpriteDecoder.AnimationType.values().length - 1; i >= 0; i--) { - if (SpriteDecoder.AnimationType.values()[i] == animType) { + for (int i = AnimationInfo.Type.values().length - 1; i >= 0; i--) { + if (AnimationInfo.Type.values()[i] == animType) { type = i; break; } diff --git a/src/org/infinity/resource/cre/viewer/MediaPanel.java b/src/org/infinity/resource/cre/viewer/MediaPanel.java index daf2f044b..cbb1a923d 100644 --- a/src/org/infinity/resource/cre/viewer/MediaPanel.java +++ b/src/org/infinity/resource/cre/viewer/MediaPanel.java @@ -36,6 +36,8 @@ import org.infinity.gui.ViewerUtil; import org.infinity.gui.WindowBlocker; import org.infinity.resource.cre.decoder.SpriteDecoder; +import org.infinity.resource.cre.decoder.internal.Direction; +import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.viewer.icon.Icons; import org.infinity.resource.graphics.PseudoBamDecoder.PseudoBamControl; @@ -45,17 +47,17 @@ public class MediaPanel extends JPanel { // List of potential sequences to display when loading a new creature - private static final List DEFAULT_SEQUENCES = new ArrayList() {{ - add(SpriteDecoder.Sequence.STAND); - add(SpriteDecoder.Sequence.STAND2); - add(SpriteDecoder.Sequence.STAND3); - add(SpriteDecoder.Sequence.STAND_EMERGED); - add(SpriteDecoder.Sequence.PST_STAND); - add(SpriteDecoder.Sequence.STANCE); - add(SpriteDecoder.Sequence.STANCE2); - add(SpriteDecoder.Sequence.PST_STANCE); - add(SpriteDecoder.Sequence.WALK); - add(SpriteDecoder.Sequence.PST_WALK); + private static final List DEFAULT_SEQUENCES = new ArrayList() {{ + add(Sequence.STAND); + add(Sequence.STAND2); + add(Sequence.STAND3); + add(Sequence.STAND_EMERGED); + add(Sequence.PST_STAND); + add(Sequence.STANCE); + add(Sequence.STANCE2); + add(Sequence.PST_STANCE); + add(Sequence.WALK); + add(Sequence.PST_WALK); }}; private static boolean isLoop; @@ -65,13 +67,13 @@ public class MediaPanel extends JPanel } // mapping of slider value to direction - private final HashMap directionMap = new HashMap<>(); + private final HashMap directionMap = new HashMap<>(); private final Listeners listeners = new Listeners(); private final CreatureViewer viewer; private JButton bHome, bEnd, bStepBack, bStepForward, bPlay, bStop; - private DefaultComboBoxModel modelSequences; - private JComboBox cbSequences; + private DefaultComboBoxModel modelSequences; + private JComboBox cbSequences; private JCheckBox cbLoop; private JSlider slDirection; private JLabel lDirection; @@ -96,8 +98,8 @@ public MediaPanel(CreatureViewer viewer) */ public void reset(boolean preserveState) { - SpriteDecoder.Sequence oldSequence = preserveState ? getSequence() : null; - SpriteDecoder.Direction oldDir = preserveState ? getDirection(getCurrentDirection()) : null; + Sequence oldSequence = preserveState ? getSequence() : null; + Direction oldDir = preserveState ? getDirection(getCurrentDirection()) : null; int oldFrameIdx = preserveState ? getCurrentFrame() : 0; boolean isRunning = preserveState ? isRunning() : false; @@ -262,7 +264,7 @@ public void setCurrentDirection(int pos) throws IndexOutOfBoundsException } /** Sets the specified direction if available. Throws an {@code IllegalArgumentException} otherwise. */ - public void setCurrentDirection(SpriteDecoder.Direction dir) throws IllegalArgumentException + public void setCurrentDirection(Direction dir) throws IllegalArgumentException { if (dir != null) { for (int i = 0; i < slDirection.getMaximum(); i++) { @@ -278,13 +280,13 @@ public void setCurrentDirection(SpriteDecoder.Direction dir) throws IllegalArgum } /** Returns the currently selected animation sequence. Returns {@code null} if no sequence is active. */ - public SpriteDecoder.Sequence getSequence() + public Sequence getSequence() { return modelSequences.getElementAt(cbSequences.getSelectedIndex()); } /** Sets the specified sequence and loads the associated animation. */ - public void setSequence(SpriteDecoder.Sequence seq) throws IllegalArgumentException + public void setSequence(Sequence seq) throws IllegalArgumentException { int oldIdx = cbSequences.getSelectedIndex(); int idx = modelSequences.getIndexOf(seq); @@ -298,11 +300,11 @@ public void setSequence(SpriteDecoder.Sequence seq) throws IllegalArgumentExcept } } - public void loadSequence(SpriteDecoder.Sequence seq) throws IllegalArgumentException + public void loadSequence(Sequence seq) throws IllegalArgumentException { SpriteDecoder decoder = getViewer().getDecoder(); RenderPanel renderer = getViewer().getRenderPanel(); - SpriteDecoder.Direction oldDir = getDirection(getCurrentDirection()); + Direction oldDir = getDirection(getCurrentDirection()); boolean isRunning = isRunning(); pause(); try { @@ -521,7 +523,7 @@ private void init() l1 = new JLabel("Sequence:"); modelSequences = new DefaultComboBoxModel<>(); cbSequences = new JComboBox<>(modelSequences); - cbSequences.setPrototypeDisplayValue(SpriteDecoder.Sequence.ATTACK_2WEAPONS1); + cbSequences.setPrototypeDisplayValue(Sequence.ATTACK_2WEAPONS1); cbSequences.addItemListener(listeners); cbLoop = new JCheckBox("Loop", isLoop); @@ -587,7 +589,7 @@ private void updateLabels() { lFrameCur.setText(Integer.toString(getCurrentFrame())); lFrameMax.setText(Integer.toString(getMaxFrame() - 1)); - SpriteDecoder.Direction dir = getDirection(getCurrentDirection()); + Direction dir = getDirection(getCurrentDirection()); lDirection.setText(dir.toString()); String text = ""; if (getController() != null) { @@ -598,11 +600,11 @@ private void updateLabels() } /** Initializes the sequence list with available animation sequences and preselects a suitable default sequence. */ - private void initSequences(SpriteDecoder decoder, SpriteDecoder.Sequence defSeq) + private void initSequences(SpriteDecoder decoder, Sequence defSeq) { modelSequences.removeAllElements(); if (decoder != null) { - for (final SpriteDecoder.Sequence seq : SpriteDecoder.Sequence.values()) { + for (final Sequence seq : Sequence.values()) { if (decoder.isSequenceAvailable(seq)) { modelSequences.addElement(seq); } @@ -618,7 +620,7 @@ private void initSequences(SpriteDecoder decoder, SpriteDecoder.Sequence defSeq) } if (idx < 0) { // try default sequence from list - for (final SpriteDecoder.Sequence seq : DEFAULT_SEQUENCES) { + for (final Sequence seq : DEFAULT_SEQUENCES) { idx = modelSequences.getIndexOf(seq); if (idx >= 0) { break; @@ -634,7 +636,7 @@ private void initSequences(SpriteDecoder decoder, SpriteDecoder.Sequence defSeq) } /** Initializes the direction slider and label with available directions. */ - private void initDirection(SpriteDecoder decoder, SpriteDecoder.Direction defDir) + private void initDirection(SpriteDecoder decoder, Direction defDir) { // discarding old data slDirection.setMinimum(0); @@ -643,8 +645,8 @@ private void initDirection(SpriteDecoder decoder, SpriteDecoder.Direction defDir if (decoder != null) { // collecting directions - List directions = new ArrayList<>(SpriteDecoder.Direction.values().length * 2 + 1); - for (final SpriteDecoder.Direction dir : decoder.getDirectionMap().keySet()) { + List directions = new ArrayList<>(Direction.values().length * 2 + 1); + for (final Direction dir : decoder.getDirectionMap().keySet()) { directions.add(Integer.valueOf(dir.getValue())); } @@ -656,19 +658,19 @@ private void initDirection(SpriteDecoder decoder, SpriteDecoder.Direction defDir // duplicate list entries directions.addAll(new ArrayList(directions)); // remove excess entries from left (negative) side - while (directions.size() > 1 && directions.get(0) > SpriteDecoder.Direction.N.getValue()) { + while (directions.size() > 1 && directions.get(0) > Direction.N.getValue()) { directions.remove(0); min++; } // remove excess entries from right (positive) side - while (directions.size() > 1 && directions.get(directions.size() - 1) < SpriteDecoder.Direction.N.getValue()) { + while (directions.size() > 1 && directions.get(directions.size() - 1) < Direction.N.getValue()) { directions.remove(directions.size() - 1); max--; } for (int i = min; i <= max; i++) { int dirVal = directions.get(i - min); - SpriteDecoder.Direction dir = SpriteDecoder.Direction.from(dirVal); + Direction dir = Direction.from(dirVal); directionMap.put(Integer.valueOf(i), dir); } @@ -676,13 +678,13 @@ private void initDirection(SpriteDecoder decoder, SpriteDecoder.Direction defDir slDirection.setMinimum(min); slDirection.setMaximum(max); // defining major ticks distance - Integer d1 = decoder.getDirectionMap().get(SpriteDecoder.Direction.S); + Integer d1 = decoder.getDirectionMap().get(Direction.S); if (d1 != null) { - Integer d2 = decoder.getDirectionMap().get(SpriteDecoder.Direction.W); + Integer d2 = decoder.getDirectionMap().get(Direction.W); if (d2 != null) { slDirection.setMajorTickSpacing(d2 - d1); } else { - d2 = decoder.getDirectionMap().get(SpriteDecoder.Direction.N); + d2 = decoder.getDirectionMap().get(Direction.N); if (d2 != null) { slDirection.setMajorTickSpacing((d2 - d1) / 2); } @@ -693,7 +695,7 @@ private void initDirection(SpriteDecoder decoder, SpriteDecoder.Direction defDir // defining direction labels Hashtable labels = new Hashtable<>(); for (int i = min; i <= max; i++) { - SpriteDecoder.Direction dir = getDirection(i); + Direction dir = getDirection(i); if (dir != null && (dir.getValue() % 4) == 0) { labels.put(Integer.valueOf(i), new JLabel(dir.toString())); } @@ -716,9 +718,9 @@ private void initDirection(SpriteDecoder decoder, SpriteDecoder.Direction defDir } /** Returns the {@code Direction} of the specified direction slider position. Defaults to {@code Direction.S}. */ - private SpriteDecoder.Direction getDirection(int index) + private Direction getDirection(int index) { - return directionMap.getOrDefault(Integer.valueOf(index), SpriteDecoder.Direction.S); + return directionMap.getOrDefault(Integer.valueOf(index), Direction.S); } //-------------------------- INNER CLASSES -------------------------- @@ -775,7 +777,7 @@ else if (e.getSource() == bStop) { public void stateChanged(ChangeEvent e) { if (e.getSource() == slDirection) { - SpriteDecoder.Direction dir = getDirection(slDirection.getValue()); + Direction dir = getDirection(slDirection.getValue()); lDirection.setText(dir.toString()); int cycle = getViewer().getDecoder().getDirectionMap().getOrDefault(dir, -1).intValue(); if (cycle >= 0) { @@ -793,8 +795,8 @@ public void itemStateChanged(ItemEvent e) { if (e.getSource() == cbSequences) { if (e.getStateChange() == ItemEvent.SELECTED && - e.getItem() instanceof SpriteDecoder.Sequence) { - final SpriteDecoder.Sequence seq = (SpriteDecoder.Sequence)e.getItem(); + e.getItem() instanceof Sequence) { + final Sequence seq = (Sequence)e.getItem(); try { WindowBlocker.blockWindow(getViewer(), true); loadSequence(seq); From 8c299c8f130a5a97dd54de2cea955da6c2b60746 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sun, 21 Mar 2021 19:05:06 +0100 Subject: [PATCH 071/158] SpriteDecoder: Update equals() and hashCode() methods --- .../resource/cre/decoder/SpriteDecoder.java | 12 ++++++++++ .../cre/decoder/internal/ItemInfo.java | 19 ++++++++++++++-- .../cre/decoder/internal/NumberRange.java | 22 +++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index 7968c0626..4e4fc0123 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -2295,7 +2295,13 @@ public int hashCode() hash = 31 * hash + ((attributesMap == null) ? 0 : attributesMap.hashCode()); hash = 31 * hash + ((currentSequence == null) ? 0 : currentSequence.hashCode()); hash = 31 * hash + Boolean.valueOf(showCircle).hashCode(); + hash = 31 * hash + Boolean.valueOf(selectionCircleBitmap).hashCode(); hash = 31 * hash + Boolean.valueOf(showPersonalSpace).hashCode(); + hash = 31 * hash + Boolean.valueOf(showBoundingBox).hashCode(); + hash = 31 * hash + Boolean.valueOf(transparentShadow).hashCode(); + hash = 31 * hash + Boolean.valueOf(translucencyEnabled).hashCode(); + hash = 31 * hash + Boolean.valueOf(paletteReplacementEnabled).hashCode(); + hash = 31 * hash + Boolean.valueOf(renderSpriteAvatar).hashCode(); hash = 31 * hash + Boolean.valueOf(renderSpriteWeapon).hashCode(); hash = 31 * hash + Boolean.valueOf(renderSpriteHelmet).hashCode(); hash = 31 * hash + Boolean.valueOf(renderSpriteShield).hashCode(); @@ -2324,7 +2330,13 @@ public boolean equals(Object o) retVal &= (this.currentSequence == null && other.currentSequence == null) || (this.currentSequence != null && this.currentSequence.equals(other.currentSequence)); retVal &= (this.showCircle == other.showCircle); + retVal &= (this.selectionCircleBitmap == other.selectionCircleBitmap); retVal &= (this.showPersonalSpace == other.showPersonalSpace); + retVal &= (this.showBoundingBox == other.showBoundingBox); + retVal &= (this.transparentShadow == other.transparentShadow); + retVal &= (this.translucencyEnabled == other.translucencyEnabled); + retVal &= (this.paletteReplacementEnabled == other.paletteReplacementEnabled); + retVal &= (this.renderSpriteAvatar == other.renderSpriteAvatar); retVal &= (this.renderSpriteWeapon == other.renderSpriteWeapon); retVal &= (this.renderSpriteHelmet == other.renderSpriteHelmet); retVal &= (this.renderSpriteShield == other.renderSpriteShield); diff --git a/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java b/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java index 315334e8c..f2ac71fe3 100644 --- a/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java +++ b/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java @@ -838,12 +838,18 @@ public int hashCode() int hash = 7; hash = 31 * hash + colorInfo.hashCode(); hash = 31 * hash + abilityInfo.hashCode(); + hash = 31 * hash + effectsInfo.hashCode(); hash = 31 * hash + ((itmEntry == null) ? 0 : itmEntry.hashCode()); + hash = 31 * hash + ((name == null) ? 0 : name.hashCode()); + hash = 31 * hash + ((nameIdentified == null) ? 0 : nameIdentified.hashCode()); hash = 31 * hash + flags; hash = 31 * hash + category; hash = 31 * hash + unusable; hash = 31 * hash + unusableKits; hash = 31 * hash + ((appearance == null) ? 0 : appearance.hashCode()); + hash = 31 * hash + proficiency; + hash = 31 * hash + stack; + hash = 31 * hash + enchantment; return hash; } @@ -859,16 +865,25 @@ public boolean equals(Object o) ItemInfo other = (ItemInfo)o; boolean retVal = (this.colorInfo == null && other.colorInfo == null) || (this.colorInfo != null && this.colorInfo.equals(other.colorInfo)); - retVal &= (this.abilityInfo == null && other.abilityInfo == null || - this.abilityInfo != null && this.abilityInfo.equals(other.abilityInfo)); + retVal &= (this.abilityInfo == null && other.abilityInfo == null) || + (this.abilityInfo != null && this.abilityInfo.equals(other.abilityInfo)); + retVal &= (this.effectsInfo == null && other.effectsInfo == null) || + (this.effectsInfo != null && this.effectsInfo.equals(other.effectsInfo)); retVal &= (this.itmEntry == null && other.itmEntry == null) || (this.itmEntry != null && this.itmEntry.equals(other.itmEntry)); + retVal &= (this.name == null && other.name == null) || + (this.name != null && this.name.equals(other.name)); + retVal &= (this.nameIdentified == null && other.nameIdentified == null) || + (this.nameIdentified != null && this.nameIdentified.equals(other.nameIdentified)); retVal &= this.flags == other.flags; retVal &= this.category == other.category; retVal &= this.unusable == other.unusable; retVal &= this.unusableKits == other.unusableKits; retVal &= (this.appearance == null && other.appearance == null) || (this.appearance != null && this.appearance.equals(other.appearance)); + retVal &= this.proficiency == other.proficiency; + retVal &= this.stack == other.stack; + retVal &= this.enchantment == other.enchantment; return retVal; } diff --git a/src/org/infinity/resource/cre/decoder/internal/NumberRange.java b/src/org/infinity/resource/cre/decoder/internal/NumberRange.java index 6d3babec8..37e2204d9 100644 --- a/src/org/infinity/resource/cre/decoder/internal/NumberRange.java +++ b/src/org/infinity/resource/cre/decoder/internal/NumberRange.java @@ -57,4 +57,26 @@ private void init(int base, int range, int subBase, int subRange, int subPos) ranges.add(Couple.with(curBase, range)); } } + + @Override + public int hashCode() + { + int hash = 7; + hash = 31 * hash + ((ranges == null) ? 0 : ranges.hashCode()); + return hash; + } + + @Override + public boolean equals(Object o) + { + if (o == this) { + return true; + } + if (!(o instanceof NumberRange)) { + return false; + } + NumberRange other = (NumberRange)o; + boolean retVal = ranges.equals(other.ranges); + return retVal; + } } From b7653d66134e9d12ec3ebefe5edb66f3a62743c5 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sun, 21 Mar 2021 19:10:40 +0100 Subject: [PATCH 072/158] SpriteDecoder: More refactoring --- src/org/infinity/NearInfinity.java | 2 +- .../are/viewer/ActorAnimationProvider.java | 2 +- .../resource/are/viewer/LayerObjectActor.java | 4 ++-- .../resource/cre/ViewerAnimation.java | 2 +- .../resource/cre/decoder/AmbientDecoder.java | 12 +++++----- .../cre/decoder/AmbientStaticDecoder.java | 12 +++++----- .../cre/decoder/ArmoredBaseDecoder.java | 6 ++--- .../cre/decoder/CharacterDecoder.java | 14 +++++------ .../cre/decoder/CharacterOldDecoder.java | 18 +++++++------- .../resource/cre/decoder/EffectDecoder.java | 14 +++++------ .../resource/cre/decoder/FlyingDecoder.java | 8 +++---- .../cre/decoder/MonsterAnkhegDecoder.java | 14 +++++------ .../resource/cre/decoder/MonsterDecoder.java | 16 ++++++------- .../cre/decoder/MonsterIcewindDecoder.java | 14 +++++------ .../cre/decoder/MonsterLarge16Decoder.java | 12 +++++----- .../cre/decoder/MonsterLargeDecoder.java | 10 ++++---- .../cre/decoder/MonsterLayeredDecoder.java | 14 +++++------ .../decoder/MonsterLayeredSpellDecoder.java | 14 +++++------ .../cre/decoder/MonsterMultiDecoder.java | 14 +++++------ .../cre/decoder/MonsterMultiNewDecoder.java | 12 +++++----- .../cre/decoder/MonsterOldDecoder.java | 10 ++++---- .../cre/decoder/MonsterPlanescapeDecoder.java | 14 +++++------ .../cre/decoder/MonsterQuadrantDecoder.java | 12 +++++----- .../cre/decoder/QuadrantsBaseDecoder.java | 4 ++-- .../resource/cre/decoder/SpriteDecoder.java | 24 +++++++++---------- .../cre/decoder/TownStaticDecoder.java | 10 ++++---- .../cre/decoder/tables/InfinityTables.java | 2 +- .../{internal => util}/AnimationInfo.java | 2 +- .../decoder/{internal => util}/ColorInfo.java | 2 +- .../{internal => util}/CreatureInfo.java | 4 ++-- .../decoder/{internal => util}/CycleDef.java | 6 ++--- .../{internal => util}/DecoderAttribute.java | 2 +- .../decoder/{internal => util}/DirDef.java | 2 +- .../decoder/{internal => util}/Direction.java | 2 +- .../decoder/{internal => util}/FrameInfo.java | 2 +- .../decoder/{internal => util}/ItemInfo.java | 2 +- .../{internal => util}/NumberRange.java | 2 +- .../{internal => util}/SegmentDef.java | 2 +- .../decoder/{internal => util}/SeqDef.java | 2 +- .../decoder/{internal => util}/Sequence.java | 2 +- .../resource/cre/viewer/CreUtils.java | 2 +- .../cre/viewer/CreatureControlModel.java | 6 ++--- .../cre/viewer/CreatureControlPanel.java | 2 +- .../viewer/DecoderAttributesTableModel.java | 2 +- .../cre/viewer/ItemSelectionModel.java | 4 ++-- .../resource/cre/viewer/MediaPanel.java | 4 ++-- 46 files changed, 171 insertions(+), 171 deletions(-) rename src/org/infinity/resource/cre/decoder/{internal => util}/AnimationInfo.java (99%) rename src/org/infinity/resource/cre/decoder/{internal => util}/ColorInfo.java (98%) rename src/org/infinity/resource/cre/decoder/{internal => util}/CreatureInfo.java (99%) rename src/org/infinity/resource/cre/decoder/{internal => util}/CycleDef.java (96%) rename src/org/infinity/resource/cre/decoder/{internal => util}/DecoderAttribute.java (98%) rename src/org/infinity/resource/cre/decoder/{internal => util}/DirDef.java (98%) rename src/org/infinity/resource/cre/decoder/{internal => util}/Direction.java (96%) rename src/org/infinity/resource/cre/decoder/{internal => util}/FrameInfo.java (98%) rename src/org/infinity/resource/cre/decoder/{internal => util}/ItemInfo.java (99%) rename src/org/infinity/resource/cre/decoder/{internal => util}/NumberRange.java (97%) rename src/org/infinity/resource/cre/decoder/{internal => util}/SegmentDef.java (99%) rename src/org/infinity/resource/cre/decoder/{internal => util}/SeqDef.java (99%) rename src/org/infinity/resource/cre/decoder/{internal => util}/Sequence.java (98%) diff --git a/src/org/infinity/NearInfinity.java b/src/org/infinity/NearInfinity.java index 1491c6357..abc9b776e 100644 --- a/src/org/infinity/NearInfinity.java +++ b/src/org/infinity/NearInfinity.java @@ -92,7 +92,7 @@ import org.infinity.resource.ViewableContainer; import org.infinity.resource.bcs.Signatures; import org.infinity.resource.cre.decoder.SpriteUtils; -import org.infinity.resource.cre.decoder.internal.ItemInfo; +import org.infinity.resource.cre.decoder.util.ItemInfo; import org.infinity.resource.key.FileResourceEntry; import org.infinity.resource.key.ResourceEntry; import org.infinity.resource.key.ResourceTreeModel; diff --git a/src/org/infinity/resource/are/viewer/ActorAnimationProvider.java b/src/org/infinity/resource/are/viewer/ActorAnimationProvider.java index 6be15ee4e..56cdebda2 100644 --- a/src/org/infinity/resource/are/viewer/ActorAnimationProvider.java +++ b/src/org/infinity/resource/are/viewer/ActorAnimationProvider.java @@ -15,7 +15,7 @@ import java.util.Objects; import org.infinity.resource.cre.decoder.SpriteDecoder; -import org.infinity.resource.cre.decoder.internal.Direction; +import org.infinity.resource.cre.decoder.util.Direction; import org.infinity.resource.graphics.ColorConvert; import org.infinity.resource.graphics.PseudoBamDecoder.PseudoBamControl; diff --git a/src/org/infinity/resource/are/viewer/LayerObjectActor.java b/src/org/infinity/resource/are/viewer/LayerObjectActor.java index b1c99b48d..c825f7d66 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectActor.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectActor.java @@ -15,8 +15,8 @@ import org.infinity.resource.AbstractStruct; import org.infinity.resource.cre.CreResource; import org.infinity.resource.cre.decoder.SpriteDecoder; -import org.infinity.resource.cre.decoder.internal.AnimationInfo; -import org.infinity.resource.cre.decoder.internal.Sequence; +import org.infinity.resource.cre.decoder.util.AnimationInfo; +import org.infinity.resource.cre.decoder.util.Sequence; import org.infinity.resource.graphics.BamDecoder; /** diff --git a/src/org/infinity/resource/cre/ViewerAnimation.java b/src/org/infinity/resource/cre/ViewerAnimation.java index 482f85665..97fc5b49c 100644 --- a/src/org/infinity/resource/cre/ViewerAnimation.java +++ b/src/org/infinity/resource/cre/ViewerAnimation.java @@ -45,7 +45,7 @@ import org.infinity.icon.Icons; import org.infinity.resource.cre.decoder.SpriteDecoder; import org.infinity.resource.cre.decoder.SpriteUtils; -import org.infinity.resource.cre.decoder.internal.Sequence; +import org.infinity.resource.cre.decoder.util.Sequence; import org.infinity.resource.cre.viewer.CreatureViewer; import org.infinity.resource.gam.PartyNPC; import org.infinity.resource.graphics.PseudoBamDecoder.PseudoBamControl; diff --git a/src/org/infinity/resource/cre/decoder/AmbientDecoder.java b/src/org/infinity/resource/cre/decoder/AmbientDecoder.java index 1bab75440..9c6a1abf8 100644 --- a/src/org/infinity/resource/cre/decoder/AmbientDecoder.java +++ b/src/org/infinity/resource/cre/decoder/AmbientDecoder.java @@ -10,13 +10,13 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; -import org.infinity.resource.cre.decoder.internal.AnimationInfo; -import org.infinity.resource.cre.decoder.internal.DecoderAttribute; -import org.infinity.resource.cre.decoder.internal.DirDef; -import org.infinity.resource.cre.decoder.internal.SegmentDef; -import org.infinity.resource.cre.decoder.internal.SeqDef; -import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.cre.decoder.util.AnimationInfo; +import org.infinity.resource.cre.decoder.util.DecoderAttribute; +import org.infinity.resource.cre.decoder.util.DirDef; +import org.infinity.resource.cre.decoder.util.SegmentDef; +import org.infinity.resource.cre.decoder.util.SeqDef; +import org.infinity.resource.cre.decoder.util.Sequence; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; diff --git a/src/org/infinity/resource/cre/decoder/AmbientStaticDecoder.java b/src/org/infinity/resource/cre/decoder/AmbientStaticDecoder.java index f802d36e0..4afd3d1c9 100644 --- a/src/org/infinity/resource/cre/decoder/AmbientStaticDecoder.java +++ b/src/org/infinity/resource/cre/decoder/AmbientStaticDecoder.java @@ -10,13 +10,13 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; -import org.infinity.resource.cre.decoder.internal.AnimationInfo; -import org.infinity.resource.cre.decoder.internal.DecoderAttribute; -import org.infinity.resource.cre.decoder.internal.DirDef; -import org.infinity.resource.cre.decoder.internal.SegmentDef; -import org.infinity.resource.cre.decoder.internal.SeqDef; -import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.cre.decoder.util.AnimationInfo; +import org.infinity.resource.cre.decoder.util.DecoderAttribute; +import org.infinity.resource.cre.decoder.util.DirDef; +import org.infinity.resource.cre.decoder.util.SegmentDef; +import org.infinity.resource.cre.decoder.util.SeqDef; +import org.infinity.resource.cre.decoder.util.Sequence; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; diff --git a/src/org/infinity/resource/cre/decoder/ArmoredBaseDecoder.java b/src/org/infinity/resource/cre/decoder/ArmoredBaseDecoder.java index 8165a064e..76b17aa10 100644 --- a/src/org/infinity/resource/cre/decoder/ArmoredBaseDecoder.java +++ b/src/org/infinity/resource/cre/decoder/ArmoredBaseDecoder.java @@ -10,9 +10,9 @@ import org.infinity.datatype.IsNumeric; import org.infinity.resource.Profile; import org.infinity.resource.cre.CreResource; -import org.infinity.resource.cre.decoder.internal.AnimationInfo; -import org.infinity.resource.cre.decoder.internal.DecoderAttribute; -import org.infinity.resource.cre.decoder.internal.ItemInfo; +import org.infinity.resource.cre.decoder.util.AnimationInfo; +import org.infinity.resource.cre.decoder.util.DecoderAttribute; +import org.infinity.resource.cre.decoder.util.ItemInfo; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; import org.infinity.util.Misc; diff --git a/src/org/infinity/resource/cre/decoder/CharacterDecoder.java b/src/org/infinity/resource/cre/decoder/CharacterDecoder.java index 9787d8e2d..0fac390ea 100644 --- a/src/org/infinity/resource/cre/decoder/CharacterDecoder.java +++ b/src/org/infinity/resource/cre/decoder/CharacterDecoder.java @@ -14,14 +14,14 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; -import org.infinity.resource.cre.decoder.internal.AnimationInfo; -import org.infinity.resource.cre.decoder.internal.DecoderAttribute; -import org.infinity.resource.cre.decoder.internal.DirDef; -import org.infinity.resource.cre.decoder.internal.ItemInfo; -import org.infinity.resource.cre.decoder.internal.SegmentDef; -import org.infinity.resource.cre.decoder.internal.SeqDef; -import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.cre.decoder.util.AnimationInfo; +import org.infinity.resource.cre.decoder.util.DecoderAttribute; +import org.infinity.resource.cre.decoder.util.DirDef; +import org.infinity.resource.cre.decoder.util.ItemInfo; +import org.infinity.resource.cre.decoder.util.SegmentDef; +import org.infinity.resource.cre.decoder.util.SeqDef; +import org.infinity.resource.cre.decoder.util.Sequence; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; diff --git a/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java b/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java index 0fbf64b4f..837acf98a 100644 --- a/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java +++ b/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java @@ -14,16 +14,16 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; -import org.infinity.resource.cre.decoder.internal.AnimationInfo; -import org.infinity.resource.cre.decoder.internal.CycleDef; -import org.infinity.resource.cre.decoder.internal.DecoderAttribute; -import org.infinity.resource.cre.decoder.internal.DirDef; -import org.infinity.resource.cre.decoder.internal.Direction; -import org.infinity.resource.cre.decoder.internal.ItemInfo; -import org.infinity.resource.cre.decoder.internal.SegmentDef; -import org.infinity.resource.cre.decoder.internal.SeqDef; -import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.cre.decoder.util.AnimationInfo; +import org.infinity.resource.cre.decoder.util.CycleDef; +import org.infinity.resource.cre.decoder.util.DecoderAttribute; +import org.infinity.resource.cre.decoder.util.DirDef; +import org.infinity.resource.cre.decoder.util.Direction; +import org.infinity.resource.cre.decoder.util.ItemInfo; +import org.infinity.resource.cre.decoder.util.SegmentDef; +import org.infinity.resource.cre.decoder.util.SeqDef; +import org.infinity.resource.cre.decoder.util.Sequence; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; diff --git a/src/org/infinity/resource/cre/decoder/EffectDecoder.java b/src/org/infinity/resource/cre/decoder/EffectDecoder.java index 00017094e..922511ea7 100644 --- a/src/org/infinity/resource/cre/decoder/EffectDecoder.java +++ b/src/org/infinity/resource/cre/decoder/EffectDecoder.java @@ -10,14 +10,14 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; -import org.infinity.resource.cre.decoder.internal.AnimationInfo; -import org.infinity.resource.cre.decoder.internal.CycleDef; -import org.infinity.resource.cre.decoder.internal.DecoderAttribute; -import org.infinity.resource.cre.decoder.internal.DirDef; -import org.infinity.resource.cre.decoder.internal.Direction; -import org.infinity.resource.cre.decoder.internal.SeqDef; -import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.cre.decoder.util.AnimationInfo; +import org.infinity.resource.cre.decoder.util.CycleDef; +import org.infinity.resource.cre.decoder.util.DecoderAttribute; +import org.infinity.resource.cre.decoder.util.DirDef; +import org.infinity.resource.cre.decoder.util.Direction; +import org.infinity.resource.cre.decoder.util.SeqDef; +import org.infinity.resource.cre.decoder.util.Sequence; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; diff --git a/src/org/infinity/resource/cre/decoder/FlyingDecoder.java b/src/org/infinity/resource/cre/decoder/FlyingDecoder.java index 9d3238b2c..d19897984 100644 --- a/src/org/infinity/resource/cre/decoder/FlyingDecoder.java +++ b/src/org/infinity/resource/cre/decoder/FlyingDecoder.java @@ -9,11 +9,11 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; -import org.infinity.resource.cre.decoder.internal.AnimationInfo; -import org.infinity.resource.cre.decoder.internal.DirDef; -import org.infinity.resource.cre.decoder.internal.SeqDef; -import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.cre.decoder.util.AnimationInfo; +import org.infinity.resource.cre.decoder.util.DirDef; +import org.infinity.resource.cre.decoder.util.SeqDef; +import org.infinity.resource.cre.decoder.util.Sequence; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; diff --git a/src/org/infinity/resource/cre/decoder/MonsterAnkhegDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterAnkhegDecoder.java index e8f9a75a5..59564d128 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterAnkhegDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterAnkhegDecoder.java @@ -10,14 +10,14 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; -import org.infinity.resource.cre.decoder.internal.AnimationInfo; -import org.infinity.resource.cre.decoder.internal.DecoderAttribute; -import org.infinity.resource.cre.decoder.internal.DirDef; -import org.infinity.resource.cre.decoder.internal.Direction; -import org.infinity.resource.cre.decoder.internal.SegmentDef; -import org.infinity.resource.cre.decoder.internal.SeqDef; -import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.cre.decoder.util.AnimationInfo; +import org.infinity.resource.cre.decoder.util.DecoderAttribute; +import org.infinity.resource.cre.decoder.util.DirDef; +import org.infinity.resource.cre.decoder.util.Direction; +import org.infinity.resource.cre.decoder.util.SegmentDef; +import org.infinity.resource.cre.decoder.util.SeqDef; +import org.infinity.resource.cre.decoder.util.Sequence; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; diff --git a/src/org/infinity/resource/cre/decoder/MonsterDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterDecoder.java index 6b0db9615..513aed4f4 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterDecoder.java @@ -12,15 +12,15 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; -import org.infinity.resource.cre.decoder.internal.AnimationInfo; -import org.infinity.resource.cre.decoder.internal.DecoderAttribute; -import org.infinity.resource.cre.decoder.internal.DirDef; -import org.infinity.resource.cre.decoder.internal.Direction; -import org.infinity.resource.cre.decoder.internal.ItemInfo; -import org.infinity.resource.cre.decoder.internal.SegmentDef; -import org.infinity.resource.cre.decoder.internal.SeqDef; -import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.cre.decoder.util.AnimationInfo; +import org.infinity.resource.cre.decoder.util.DecoderAttribute; +import org.infinity.resource.cre.decoder.util.DirDef; +import org.infinity.resource.cre.decoder.util.Direction; +import org.infinity.resource.cre.decoder.util.ItemInfo; +import org.infinity.resource.cre.decoder.util.SegmentDef; +import org.infinity.resource.cre.decoder.util.SeqDef; +import org.infinity.resource.cre.decoder.util.Sequence; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; diff --git a/src/org/infinity/resource/cre/decoder/MonsterIcewindDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterIcewindDecoder.java index bbd52ff88..29c309e6c 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterIcewindDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterIcewindDecoder.java @@ -10,14 +10,14 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; -import org.infinity.resource.cre.decoder.internal.AnimationInfo; -import org.infinity.resource.cre.decoder.internal.DecoderAttribute; -import org.infinity.resource.cre.decoder.internal.DirDef; -import org.infinity.resource.cre.decoder.internal.ItemInfo; -import org.infinity.resource.cre.decoder.internal.SegmentDef; -import org.infinity.resource.cre.decoder.internal.SeqDef; -import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.cre.decoder.util.AnimationInfo; +import org.infinity.resource.cre.decoder.util.DecoderAttribute; +import org.infinity.resource.cre.decoder.util.DirDef; +import org.infinity.resource.cre.decoder.util.ItemInfo; +import org.infinity.resource.cre.decoder.util.SegmentDef; +import org.infinity.resource.cre.decoder.util.SeqDef; +import org.infinity.resource.cre.decoder.util.Sequence; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; diff --git a/src/org/infinity/resource/cre/decoder/MonsterLarge16Decoder.java b/src/org/infinity/resource/cre/decoder/MonsterLarge16Decoder.java index 7011876c9..703e23162 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterLarge16Decoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterLarge16Decoder.java @@ -10,13 +10,13 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; -import org.infinity.resource.cre.decoder.internal.AnimationInfo; -import org.infinity.resource.cre.decoder.internal.DirDef; -import org.infinity.resource.cre.decoder.internal.Direction; -import org.infinity.resource.cre.decoder.internal.SegmentDef; -import org.infinity.resource.cre.decoder.internal.SeqDef; -import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.cre.decoder.util.AnimationInfo; +import org.infinity.resource.cre.decoder.util.DirDef; +import org.infinity.resource.cre.decoder.util.Direction; +import org.infinity.resource.cre.decoder.util.SegmentDef; +import org.infinity.resource.cre.decoder.util.SeqDef; +import org.infinity.resource.cre.decoder.util.Sequence; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; diff --git a/src/org/infinity/resource/cre/decoder/MonsterLargeDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterLargeDecoder.java index 592efff9f..a96ee7f46 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterLargeDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterLargeDecoder.java @@ -10,12 +10,12 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; -import org.infinity.resource.cre.decoder.internal.AnimationInfo; -import org.infinity.resource.cre.decoder.internal.DirDef; -import org.infinity.resource.cre.decoder.internal.SegmentDef; -import org.infinity.resource.cre.decoder.internal.SeqDef; -import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.cre.decoder.util.AnimationInfo; +import org.infinity.resource.cre.decoder.util.DirDef; +import org.infinity.resource.cre.decoder.util.SegmentDef; +import org.infinity.resource.cre.decoder.util.SeqDef; +import org.infinity.resource.cre.decoder.util.Sequence; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; diff --git a/src/org/infinity/resource/cre/decoder/MonsterLayeredDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterLayeredDecoder.java index d08a0bc45..5cfb3769b 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterLayeredDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterLayeredDecoder.java @@ -10,14 +10,14 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; -import org.infinity.resource.cre.decoder.internal.AnimationInfo; -import org.infinity.resource.cre.decoder.internal.DecoderAttribute; -import org.infinity.resource.cre.decoder.internal.DirDef; -import org.infinity.resource.cre.decoder.internal.ItemInfo; -import org.infinity.resource.cre.decoder.internal.SegmentDef; -import org.infinity.resource.cre.decoder.internal.SeqDef; -import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.cre.decoder.util.AnimationInfo; +import org.infinity.resource.cre.decoder.util.DecoderAttribute; +import org.infinity.resource.cre.decoder.util.DirDef; +import org.infinity.resource.cre.decoder.util.ItemInfo; +import org.infinity.resource.cre.decoder.util.SegmentDef; +import org.infinity.resource.cre.decoder.util.SeqDef; +import org.infinity.resource.cre.decoder.util.Sequence; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; diff --git a/src/org/infinity/resource/cre/decoder/MonsterLayeredSpellDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterLayeredSpellDecoder.java index 90a49a716..d78006d5e 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterLayeredSpellDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterLayeredSpellDecoder.java @@ -10,14 +10,14 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; -import org.infinity.resource.cre.decoder.internal.AnimationInfo; -import org.infinity.resource.cre.decoder.internal.DecoderAttribute; -import org.infinity.resource.cre.decoder.internal.DirDef; -import org.infinity.resource.cre.decoder.internal.ItemInfo; -import org.infinity.resource.cre.decoder.internal.SegmentDef; -import org.infinity.resource.cre.decoder.internal.SeqDef; -import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.cre.decoder.util.AnimationInfo; +import org.infinity.resource.cre.decoder.util.DecoderAttribute; +import org.infinity.resource.cre.decoder.util.DirDef; +import org.infinity.resource.cre.decoder.util.ItemInfo; +import org.infinity.resource.cre.decoder.util.SegmentDef; +import org.infinity.resource.cre.decoder.util.SeqDef; +import org.infinity.resource.cre.decoder.util.Sequence; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; diff --git a/src/org/infinity/resource/cre/decoder/MonsterMultiDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterMultiDecoder.java index 0ef12b3ab..852c247e2 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterMultiDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterMultiDecoder.java @@ -11,14 +11,14 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; -import org.infinity.resource.cre.decoder.internal.AnimationInfo; -import org.infinity.resource.cre.decoder.internal.DecoderAttribute; -import org.infinity.resource.cre.decoder.internal.DirDef; -import org.infinity.resource.cre.decoder.internal.Direction; -import org.infinity.resource.cre.decoder.internal.SegmentDef; -import org.infinity.resource.cre.decoder.internal.SeqDef; -import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.cre.decoder.util.AnimationInfo; +import org.infinity.resource.cre.decoder.util.DecoderAttribute; +import org.infinity.resource.cre.decoder.util.DirDef; +import org.infinity.resource.cre.decoder.util.Direction; +import org.infinity.resource.cre.decoder.util.SegmentDef; +import org.infinity.resource.cre.decoder.util.SeqDef; +import org.infinity.resource.cre.decoder.util.Sequence; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; diff --git a/src/org/infinity/resource/cre/decoder/MonsterMultiNewDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterMultiNewDecoder.java index c52463ef9..0c3ad00a3 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterMultiNewDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterMultiNewDecoder.java @@ -10,13 +10,13 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; -import org.infinity.resource.cre.decoder.internal.AnimationInfo; -import org.infinity.resource.cre.decoder.internal.DecoderAttribute; -import org.infinity.resource.cre.decoder.internal.DirDef; -import org.infinity.resource.cre.decoder.internal.SegmentDef; -import org.infinity.resource.cre.decoder.internal.SeqDef; -import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.cre.decoder.util.AnimationInfo; +import org.infinity.resource.cre.decoder.util.DecoderAttribute; +import org.infinity.resource.cre.decoder.util.DirDef; +import org.infinity.resource.cre.decoder.util.SegmentDef; +import org.infinity.resource.cre.decoder.util.SeqDef; +import org.infinity.resource.cre.decoder.util.Sequence; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; diff --git a/src/org/infinity/resource/cre/decoder/MonsterOldDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterOldDecoder.java index e1da940e0..f89bf8f76 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterOldDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterOldDecoder.java @@ -10,12 +10,12 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; -import org.infinity.resource.cre.decoder.internal.AnimationInfo; -import org.infinity.resource.cre.decoder.internal.DirDef; -import org.infinity.resource.cre.decoder.internal.SegmentDef; -import org.infinity.resource.cre.decoder.internal.SeqDef; -import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.cre.decoder.util.AnimationInfo; +import org.infinity.resource.cre.decoder.util.DirDef; +import org.infinity.resource.cre.decoder.util.SegmentDef; +import org.infinity.resource.cre.decoder.util.SeqDef; +import org.infinity.resource.cre.decoder.util.Sequence; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; diff --git a/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java index c63756935..e052d0b4e 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java @@ -17,14 +17,14 @@ import org.infinity.resource.Profile; import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; -import org.infinity.resource.cre.decoder.internal.Sequence; -import org.infinity.resource.cre.decoder.internal.AnimationInfo; -import org.infinity.resource.cre.decoder.internal.DecoderAttribute; -import org.infinity.resource.cre.decoder.internal.DirDef; -import org.infinity.resource.cre.decoder.internal.Direction; -import org.infinity.resource.cre.decoder.internal.SegmentDef; -import org.infinity.resource.cre.decoder.internal.SeqDef; import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.cre.decoder.util.AnimationInfo; +import org.infinity.resource.cre.decoder.util.DecoderAttribute; +import org.infinity.resource.cre.decoder.util.DirDef; +import org.infinity.resource.cre.decoder.util.Direction; +import org.infinity.resource.cre.decoder.util.SegmentDef; +import org.infinity.resource.cre.decoder.util.SeqDef; +import org.infinity.resource.cre.decoder.util.Sequence; import org.infinity.resource.graphics.BamV1Decoder; import org.infinity.resource.graphics.BamV1Decoder.BamV1Control; import org.infinity.resource.graphics.BamV1Decoder.BamV1FrameEntry; diff --git a/src/org/infinity/resource/cre/decoder/MonsterQuadrantDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterQuadrantDecoder.java index 10d16053d..b7905076e 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterQuadrantDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterQuadrantDecoder.java @@ -10,13 +10,13 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; -import org.infinity.resource.cre.decoder.internal.AnimationInfo; -import org.infinity.resource.cre.decoder.internal.DecoderAttribute; -import org.infinity.resource.cre.decoder.internal.DirDef; -import org.infinity.resource.cre.decoder.internal.SegmentDef; -import org.infinity.resource.cre.decoder.internal.SeqDef; -import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.cre.decoder.util.AnimationInfo; +import org.infinity.resource.cre.decoder.util.DecoderAttribute; +import org.infinity.resource.cre.decoder.util.DirDef; +import org.infinity.resource.cre.decoder.util.SegmentDef; +import org.infinity.resource.cre.decoder.util.SeqDef; +import org.infinity.resource.cre.decoder.util.Sequence; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; diff --git a/src/org/infinity/resource/cre/decoder/QuadrantsBaseDecoder.java b/src/org/infinity/resource/cre/decoder/QuadrantsBaseDecoder.java index e2a08cbc5..160258aa3 100644 --- a/src/org/infinity/resource/cre/decoder/QuadrantsBaseDecoder.java +++ b/src/org/infinity/resource/cre/decoder/QuadrantsBaseDecoder.java @@ -5,8 +5,8 @@ package org.infinity.resource.cre.decoder; import org.infinity.resource.cre.CreResource; -import org.infinity.resource.cre.decoder.internal.AnimationInfo; -import org.infinity.resource.cre.decoder.internal.DecoderAttribute; +import org.infinity.resource.cre.decoder.util.AnimationInfo; +import org.infinity.resource.cre.decoder.util.DecoderAttribute; import org.infinity.util.IniMap; /** diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index 4e4fc0123..e2fbd3a30 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -37,19 +37,19 @@ import org.infinity.resource.Profile; import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; -import org.infinity.resource.cre.decoder.internal.AnimationInfo; -import org.infinity.resource.cre.decoder.internal.Sequence; -import org.infinity.resource.cre.decoder.internal.ColorInfo; -import org.infinity.resource.cre.decoder.internal.CreatureInfo; -import org.infinity.resource.cre.decoder.internal.CycleDef; -import org.infinity.resource.cre.decoder.internal.DecoderAttribute; -import org.infinity.resource.cre.decoder.internal.DirDef; -import org.infinity.resource.cre.decoder.internal.Direction; -import org.infinity.resource.cre.decoder.internal.FrameInfo; -import org.infinity.resource.cre.decoder.internal.ItemInfo; -import org.infinity.resource.cre.decoder.internal.SegmentDef; -import org.infinity.resource.cre.decoder.internal.SeqDef; import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.cre.decoder.util.AnimationInfo; +import org.infinity.resource.cre.decoder.util.ColorInfo; +import org.infinity.resource.cre.decoder.util.CreatureInfo; +import org.infinity.resource.cre.decoder.util.CycleDef; +import org.infinity.resource.cre.decoder.util.DecoderAttribute; +import org.infinity.resource.cre.decoder.util.DirDef; +import org.infinity.resource.cre.decoder.util.Direction; +import org.infinity.resource.cre.decoder.util.FrameInfo; +import org.infinity.resource.cre.decoder.util.ItemInfo; +import org.infinity.resource.cre.decoder.util.SegmentDef; +import org.infinity.resource.cre.decoder.util.SeqDef; +import org.infinity.resource.cre.decoder.util.Sequence; import org.infinity.resource.cre.viewer.icon.Icons; import org.infinity.resource.graphics.ColorConvert; import org.infinity.resource.graphics.PseudoBamDecoder; diff --git a/src/org/infinity/resource/cre/decoder/TownStaticDecoder.java b/src/org/infinity/resource/cre/decoder/TownStaticDecoder.java index 6e70d7d80..b86e68c1f 100644 --- a/src/org/infinity/resource/cre/decoder/TownStaticDecoder.java +++ b/src/org/infinity/resource/cre/decoder/TownStaticDecoder.java @@ -10,12 +10,12 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; -import org.infinity.resource.cre.decoder.internal.AnimationInfo; -import org.infinity.resource.cre.decoder.internal.DecoderAttribute; -import org.infinity.resource.cre.decoder.internal.SegmentDef; -import org.infinity.resource.cre.decoder.internal.SeqDef; -import org.infinity.resource.cre.decoder.internal.Sequence; import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.cre.decoder.util.AnimationInfo; +import org.infinity.resource.cre.decoder.util.DecoderAttribute; +import org.infinity.resource.cre.decoder.util.SegmentDef; +import org.infinity.resource.cre.decoder.util.SeqDef; +import org.infinity.resource.cre.decoder.util.Sequence; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; diff --git a/src/org/infinity/resource/cre/decoder/tables/InfinityTables.java b/src/org/infinity/resource/cre/decoder/tables/InfinityTables.java index 8be7f4716..7c775619d 100644 --- a/src/org/infinity/resource/cre/decoder/tables/InfinityTables.java +++ b/src/org/infinity/resource/cre/decoder/tables/InfinityTables.java @@ -14,7 +14,7 @@ import org.infinity.resource.Profile; import org.infinity.resource.cre.decoder.SpriteDecoder; -import org.infinity.resource.cre.decoder.internal.AnimationInfo; +import org.infinity.resource.cre.decoder.util.AnimationInfo; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IdsMap; import org.infinity.util.IdsMapEntry; diff --git a/src/org/infinity/resource/cre/decoder/internal/AnimationInfo.java b/src/org/infinity/resource/cre/decoder/util/AnimationInfo.java similarity index 99% rename from src/org/infinity/resource/cre/decoder/internal/AnimationInfo.java rename to src/org/infinity/resource/cre/decoder/util/AnimationInfo.java index 54525bc99..94a35b52b 100644 --- a/src/org/infinity/resource/cre/decoder/internal/AnimationInfo.java +++ b/src/org/infinity/resource/cre/decoder/util/AnimationInfo.java @@ -2,7 +2,7 @@ // Copyright (C) 2001 - 2021 Jon Olav Hauglid // See LICENSE.txt for license information -package org.infinity.resource.cre.decoder.internal; +package org.infinity.resource.cre.decoder.util; import java.util.ArrayList; import java.util.Arrays; diff --git a/src/org/infinity/resource/cre/decoder/internal/ColorInfo.java b/src/org/infinity/resource/cre/decoder/util/ColorInfo.java similarity index 98% rename from src/org/infinity/resource/cre/decoder/internal/ColorInfo.java rename to src/org/infinity/resource/cre/decoder/util/ColorInfo.java index 82856955c..ddeab11d6 100644 --- a/src/org/infinity/resource/cre/decoder/internal/ColorInfo.java +++ b/src/org/infinity/resource/cre/decoder/util/ColorInfo.java @@ -2,7 +2,7 @@ // Copyright (C) 2001 - 2021 Jon Olav Hauglid // See LICENSE.txt for license information -package org.infinity.resource.cre.decoder.internal; +package org.infinity.resource.cre.decoder.util; import java.util.Collections; import java.util.EnumMap; diff --git a/src/org/infinity/resource/cre/decoder/internal/CreatureInfo.java b/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java similarity index 99% rename from src/org/infinity/resource/cre/decoder/internal/CreatureInfo.java rename to src/org/infinity/resource/cre/decoder/util/CreatureInfo.java index 7fe940ac9..76f73c7bd 100644 --- a/src/org/infinity/resource/cre/decoder/internal/CreatureInfo.java +++ b/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java @@ -2,7 +2,7 @@ // Copyright (C) 2001 - 2021 Jon Olav Hauglid // See LICENSE.txt for license information -package org.infinity.resource.cre.decoder.internal; +package org.infinity.resource.cre.decoder.util; import java.io.IOException; import java.io.InputStream; @@ -28,7 +28,7 @@ import org.infinity.resource.cre.Item; import org.infinity.resource.cre.decoder.MonsterPlanescapeDecoder; import org.infinity.resource.cre.decoder.SpriteDecoder; -import org.infinity.resource.cre.decoder.internal.ItemInfo.EffectInfo; +import org.infinity.resource.cre.decoder.util.ItemInfo.EffectInfo; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.Misc; import org.infinity.util.Table2da; diff --git a/src/org/infinity/resource/cre/decoder/internal/CycleDef.java b/src/org/infinity/resource/cre/decoder/util/CycleDef.java similarity index 96% rename from src/org/infinity/resource/cre/decoder/internal/CycleDef.java rename to src/org/infinity/resource/cre/decoder/util/CycleDef.java index dee700b52..42b910cdc 100644 --- a/src/org/infinity/resource/cre/decoder/internal/CycleDef.java +++ b/src/org/infinity/resource/cre/decoder/util/CycleDef.java @@ -2,7 +2,7 @@ // Copyright (C) 2001 - 2021 Jon Olav Hauglid // See LICENSE.txt for license information -package org.infinity.resource.cre.decoder.internal; +package org.infinity.resource.cre.decoder.util; import java.util.ArrayList; import java.util.Collection; @@ -10,8 +10,8 @@ import java.util.List; import java.util.Objects; -import org.infinity.resource.cre.decoder.internal.SegmentDef.Behavior; -import org.infinity.resource.cre.decoder.internal.SegmentDef.SpriteType; +import org.infinity.resource.cre.decoder.util.SegmentDef.Behavior; +import org.infinity.resource.cre.decoder.util.SegmentDef.SpriteType; import org.infinity.resource.key.ResourceEntry; /** diff --git a/src/org/infinity/resource/cre/decoder/internal/DecoderAttribute.java b/src/org/infinity/resource/cre/decoder/util/DecoderAttribute.java similarity index 98% rename from src/org/infinity/resource/cre/decoder/internal/DecoderAttribute.java rename to src/org/infinity/resource/cre/decoder/util/DecoderAttribute.java index 1a930ad6c..485f4f09c 100644 --- a/src/org/infinity/resource/cre/decoder/internal/DecoderAttribute.java +++ b/src/org/infinity/resource/cre/decoder/util/DecoderAttribute.java @@ -2,7 +2,7 @@ // Copyright (C) 2001 - 2021 Jon Olav Hauglid // See LICENSE.txt for license information -package org.infinity.resource.cre.decoder.internal; +package org.infinity.resource.cre.decoder.util; import java.util.Objects; diff --git a/src/org/infinity/resource/cre/decoder/internal/DirDef.java b/src/org/infinity/resource/cre/decoder/util/DirDef.java similarity index 98% rename from src/org/infinity/resource/cre/decoder/internal/DirDef.java rename to src/org/infinity/resource/cre/decoder/util/DirDef.java index a9481b30a..9db499fba 100644 --- a/src/org/infinity/resource/cre/decoder/internal/DirDef.java +++ b/src/org/infinity/resource/cre/decoder/util/DirDef.java @@ -2,7 +2,7 @@ // Copyright (C) 2001 - 2021 Jon Olav Hauglid // See LICENSE.txt for license information -package org.infinity.resource.cre.decoder.internal; +package org.infinity.resource.cre.decoder.util; import java.util.Objects; diff --git a/src/org/infinity/resource/cre/decoder/internal/Direction.java b/src/org/infinity/resource/cre/decoder/util/Direction.java similarity index 96% rename from src/org/infinity/resource/cre/decoder/internal/Direction.java rename to src/org/infinity/resource/cre/decoder/util/Direction.java index 60d1f8efa..ad782bcf0 100644 --- a/src/org/infinity/resource/cre/decoder/internal/Direction.java +++ b/src/org/infinity/resource/cre/decoder/util/Direction.java @@ -2,7 +2,7 @@ // Copyright (C) 2001 - 2021 Jon Olav Hauglid // See LICENSE.txt for license information -package org.infinity.resource.cre.decoder.internal; +package org.infinity.resource.cre.decoder.util; /** * Available cardinal directions for animation sequences. diff --git a/src/org/infinity/resource/cre/decoder/internal/FrameInfo.java b/src/org/infinity/resource/cre/decoder/util/FrameInfo.java similarity index 98% rename from src/org/infinity/resource/cre/decoder/internal/FrameInfo.java rename to src/org/infinity/resource/cre/decoder/util/FrameInfo.java index d76f82ef1..1d2f663d0 100644 --- a/src/org/infinity/resource/cre/decoder/internal/FrameInfo.java +++ b/src/org/infinity/resource/cre/decoder/util/FrameInfo.java @@ -2,7 +2,7 @@ // Copyright (C) 2001 - 2021 Jon Olav Hauglid // See LICENSE.txt for license information -package org.infinity.resource.cre.decoder.internal; +package org.infinity.resource.cre.decoder.util; import java.awt.BasicStroke; import java.awt.Color; diff --git a/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java b/src/org/infinity/resource/cre/decoder/util/ItemInfo.java similarity index 99% rename from src/org/infinity/resource/cre/decoder/internal/ItemInfo.java rename to src/org/infinity/resource/cre/decoder/util/ItemInfo.java index f2ac71fe3..948e524fc 100644 --- a/src/org/infinity/resource/cre/decoder/internal/ItemInfo.java +++ b/src/org/infinity/resource/cre/decoder/util/ItemInfo.java @@ -2,7 +2,7 @@ // Copyright (C) 2001 - 2021 Jon Olav Hauglid // See LICENSE.txt for license information -package org.infinity.resource.cre.decoder.internal; +package org.infinity.resource.cre.decoder.util; import java.io.IOException; import java.io.InputStream; diff --git a/src/org/infinity/resource/cre/decoder/internal/NumberRange.java b/src/org/infinity/resource/cre/decoder/util/NumberRange.java similarity index 97% rename from src/org/infinity/resource/cre/decoder/internal/NumberRange.java rename to src/org/infinity/resource/cre/decoder/util/NumberRange.java index 37e2204d9..34b14c7f6 100644 --- a/src/org/infinity/resource/cre/decoder/internal/NumberRange.java +++ b/src/org/infinity/resource/cre/decoder/util/NumberRange.java @@ -2,7 +2,7 @@ // Copyright (C) 2001 - 2021 Jon Olav Hauglid // See LICENSE.txt for license information -package org.infinity.resource.cre.decoder.internal; +package org.infinity.resource.cre.decoder.util; import java.util.ArrayList; import java.util.List; diff --git a/src/org/infinity/resource/cre/decoder/internal/SegmentDef.java b/src/org/infinity/resource/cre/decoder/util/SegmentDef.java similarity index 99% rename from src/org/infinity/resource/cre/decoder/internal/SegmentDef.java rename to src/org/infinity/resource/cre/decoder/util/SegmentDef.java index eb80dbec7..7e00c9bbc 100644 --- a/src/org/infinity/resource/cre/decoder/internal/SegmentDef.java +++ b/src/org/infinity/resource/cre/decoder/util/SegmentDef.java @@ -2,7 +2,7 @@ // Copyright (C) 2001 - 2021 Jon Olav Hauglid // See LICENSE.txt for license information -package org.infinity.resource.cre.decoder.internal; +package org.infinity.resource.cre.decoder.util; import java.util.EnumMap; import java.util.Objects; diff --git a/src/org/infinity/resource/cre/decoder/internal/SeqDef.java b/src/org/infinity/resource/cre/decoder/util/SeqDef.java similarity index 99% rename from src/org/infinity/resource/cre/decoder/internal/SeqDef.java rename to src/org/infinity/resource/cre/decoder/util/SeqDef.java index 9fb1a38b4..8850aea42 100644 --- a/src/org/infinity/resource/cre/decoder/internal/SeqDef.java +++ b/src/org/infinity/resource/cre/decoder/util/SeqDef.java @@ -2,7 +2,7 @@ // Copyright (C) 2001 - 2021 Jon Olav Hauglid // See LICENSE.txt for license information -package org.infinity.resource.cre.decoder.internal; +package org.infinity.resource.cre.decoder.util; import java.util.ArrayList; import java.util.Collection; diff --git a/src/org/infinity/resource/cre/decoder/internal/Sequence.java b/src/org/infinity/resource/cre/decoder/util/Sequence.java similarity index 98% rename from src/org/infinity/resource/cre/decoder/internal/Sequence.java rename to src/org/infinity/resource/cre/decoder/util/Sequence.java index c98f4f875..b2afd0120 100644 --- a/src/org/infinity/resource/cre/decoder/internal/Sequence.java +++ b/src/org/infinity/resource/cre/decoder/util/Sequence.java @@ -2,7 +2,7 @@ // Copyright (C) 2001 - 2021 Jon Olav Hauglid // See LICENSE.txt for license information -package org.infinity.resource.cre.decoder.internal; +package org.infinity.resource.cre.decoder.util; /** * Available animation sequences. Note: PST-specific animation sequences are prefixed by "PST_". diff --git a/src/org/infinity/resource/cre/viewer/CreUtils.java b/src/org/infinity/resource/cre/viewer/CreUtils.java index d75f0c2ef..03baa835e 100644 --- a/src/org/infinity/resource/cre/viewer/CreUtils.java +++ b/src/org/infinity/resource/cre/viewer/CreUtils.java @@ -20,7 +20,7 @@ import org.infinity.resource.StructEntry; import org.infinity.resource.cre.CreResource; import org.infinity.resource.cre.Item; -import org.infinity.resource.cre.decoder.internal.ItemInfo; +import org.infinity.resource.cre.decoder.util.ItemInfo; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.Misc; diff --git a/src/org/infinity/resource/cre/viewer/CreatureControlModel.java b/src/org/infinity/resource/cre/viewer/CreatureControlModel.java index 4fdb0984b..a85faaabb 100644 --- a/src/org/infinity/resource/cre/viewer/CreatureControlModel.java +++ b/src/org/infinity/resource/cre/viewer/CreatureControlModel.java @@ -14,9 +14,9 @@ import org.infinity.resource.Profile; import org.infinity.resource.cre.CreResource; import org.infinity.resource.cre.decoder.SpriteDecoder; -import org.infinity.resource.cre.decoder.internal.CreatureInfo; -import org.infinity.resource.cre.decoder.internal.ItemInfo; -import org.infinity.resource.cre.decoder.internal.ItemInfo.ItemPredicate; +import org.infinity.resource.cre.decoder.util.CreatureInfo; +import org.infinity.resource.cre.decoder.util.ItemInfo; +import org.infinity.resource.cre.decoder.util.ItemInfo.ItemPredicate; import org.infinity.resource.cre.viewer.ColorSelectionModel.ColorEntry; import org.infinity.resource.cre.viewer.CreatureAllegianceModel.AllegianceEntry; import org.infinity.resource.cre.viewer.CreatureAnimationModel.AnimateEntry; diff --git a/src/org/infinity/resource/cre/viewer/CreatureControlPanel.java b/src/org/infinity/resource/cre/viewer/CreatureControlPanel.java index 80498ee1b..62cd2f7e0 100644 --- a/src/org/infinity/resource/cre/viewer/CreatureControlPanel.java +++ b/src/org/infinity/resource/cre/viewer/CreatureControlPanel.java @@ -33,7 +33,7 @@ import org.infinity.resource.cre.CreResource; import org.infinity.resource.cre.decoder.MonsterPlanescapeDecoder; import org.infinity.resource.cre.decoder.SpriteDecoder; -import org.infinity.resource.cre.decoder.internal.ItemInfo; +import org.infinity.resource.cre.decoder.util.ItemInfo; import org.infinity.resource.cre.viewer.ColorSelectionModel.ColorEntry; import org.infinity.resource.cre.viewer.CreatureSelectionModel.CreatureItem; import org.infinity.util.IdsMap; diff --git a/src/org/infinity/resource/cre/viewer/DecoderAttributesTableModel.java b/src/org/infinity/resource/cre/viewer/DecoderAttributesTableModel.java index 3fd4e1904..4929de8cf 100644 --- a/src/org/infinity/resource/cre/viewer/DecoderAttributesTableModel.java +++ b/src/org/infinity/resource/cre/viewer/DecoderAttributesTableModel.java @@ -19,7 +19,7 @@ import javax.swing.table.TableColumn; import org.infinity.resource.cre.decoder.SpriteDecoder; -import org.infinity.resource.cre.decoder.internal.DecoderAttribute; +import org.infinity.resource.cre.decoder.util.DecoderAttribute; import org.infinity.util.Misc; /** diff --git a/src/org/infinity/resource/cre/viewer/ItemSelectionModel.java b/src/org/infinity/resource/cre/viewer/ItemSelectionModel.java index 578ec6d70..b1206ceea 100644 --- a/src/org/infinity/resource/cre/viewer/ItemSelectionModel.java +++ b/src/org/infinity/resource/cre/viewer/ItemSelectionModel.java @@ -11,8 +11,8 @@ import javax.swing.AbstractListModel; import javax.swing.ComboBoxModel; -import org.infinity.resource.cre.decoder.internal.ItemInfo; -import org.infinity.resource.cre.decoder.internal.ItemInfo.ItemPredicate; +import org.infinity.resource.cre.decoder.util.ItemInfo; +import org.infinity.resource.cre.decoder.util.ItemInfo.ItemPredicate; import org.infinity.resource.key.ResourceEntry; /** diff --git a/src/org/infinity/resource/cre/viewer/MediaPanel.java b/src/org/infinity/resource/cre/viewer/MediaPanel.java index cbb1a923d..ccee75c63 100644 --- a/src/org/infinity/resource/cre/viewer/MediaPanel.java +++ b/src/org/infinity/resource/cre/viewer/MediaPanel.java @@ -36,8 +36,8 @@ import org.infinity.gui.ViewerUtil; import org.infinity.gui.WindowBlocker; import org.infinity.resource.cre.decoder.SpriteDecoder; -import org.infinity.resource.cre.decoder.internal.Direction; -import org.infinity.resource.cre.decoder.internal.Sequence; +import org.infinity.resource.cre.decoder.util.Direction; +import org.infinity.resource.cre.decoder.util.Sequence; import org.infinity.resource.cre.viewer.icon.Icons; import org.infinity.resource.graphics.PseudoBamDecoder.PseudoBamControl; From ad197456df9d9e46940e515828022b69f347408e Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Mon, 22 Mar 2021 19:12:54 +0100 Subject: [PATCH 073/158] SpriteDecoder: More refactoring --- src/org/infinity/NearInfinity.java | 2 +- src/org/infinity/resource/cre/ViewerAnimation.java | 2 +- src/org/infinity/resource/cre/decoder/AmbientDecoder.java | 1 + .../infinity/resource/cre/decoder/AmbientStaticDecoder.java | 1 + .../{ArmoredBaseDecoder.java => CharacterBaseDecoder.java} | 6 +++--- src/org/infinity/resource/cre/decoder/CharacterDecoder.java | 3 ++- .../infinity/resource/cre/decoder/CharacterOldDecoder.java | 3 ++- src/org/infinity/resource/cre/decoder/EffectDecoder.java | 1 + src/org/infinity/resource/cre/decoder/FlyingDecoder.java | 1 + .../infinity/resource/cre/decoder/MonsterAnkhegDecoder.java | 1 + src/org/infinity/resource/cre/decoder/MonsterDecoder.java | 1 + .../resource/cre/decoder/MonsterIcewindDecoder.java | 1 + .../resource/cre/decoder/MonsterLarge16Decoder.java | 1 + .../infinity/resource/cre/decoder/MonsterLargeDecoder.java | 1 + .../resource/cre/decoder/MonsterLayeredDecoder.java | 1 + .../resource/cre/decoder/MonsterLayeredSpellDecoder.java | 1 + .../infinity/resource/cre/decoder/MonsterMultiDecoder.java | 1 + .../resource/cre/decoder/MonsterMultiNewDecoder.java | 1 + .../infinity/resource/cre/decoder/MonsterOldDecoder.java | 1 + .../resource/cre/decoder/MonsterPlanescapeDecoder.java | 1 + .../resource/cre/decoder/MonsterQuadrantDecoder.java | 1 + src/org/infinity/resource/cre/decoder/SpriteDecoder.java | 1 + .../infinity/resource/cre/decoder/TownStaticDecoder.java | 1 + src/org/infinity/resource/cre/decoder/util/SegmentDef.java | 1 - .../resource/cre/decoder/{ => util}/SpriteUtils.java | 2 +- src/org/infinity/resource/cre/viewer/CreatureViewer.java | 2 +- 26 files changed, 29 insertions(+), 10 deletions(-) rename src/org/infinity/resource/cre/decoder/{ArmoredBaseDecoder.java => CharacterBaseDecoder.java} (97%) rename src/org/infinity/resource/cre/decoder/{ => util}/SpriteUtils.java (99%) diff --git a/src/org/infinity/NearInfinity.java b/src/org/infinity/NearInfinity.java index abc9b776e..a4157b397 100644 --- a/src/org/infinity/NearInfinity.java +++ b/src/org/infinity/NearInfinity.java @@ -91,8 +91,8 @@ import org.infinity.resource.Viewable; import org.infinity.resource.ViewableContainer; import org.infinity.resource.bcs.Signatures; -import org.infinity.resource.cre.decoder.SpriteUtils; import org.infinity.resource.cre.decoder.util.ItemInfo; +import org.infinity.resource.cre.decoder.util.SpriteUtils; import org.infinity.resource.key.FileResourceEntry; import org.infinity.resource.key.ResourceEntry; import org.infinity.resource.key.ResourceTreeModel; diff --git a/src/org/infinity/resource/cre/ViewerAnimation.java b/src/org/infinity/resource/cre/ViewerAnimation.java index 97fc5b49c..2ce8f8441 100644 --- a/src/org/infinity/resource/cre/ViewerAnimation.java +++ b/src/org/infinity/resource/cre/ViewerAnimation.java @@ -44,8 +44,8 @@ import org.infinity.gui.WindowBlocker; import org.infinity.icon.Icons; import org.infinity.resource.cre.decoder.SpriteDecoder; -import org.infinity.resource.cre.decoder.SpriteUtils; import org.infinity.resource.cre.decoder.util.Sequence; +import org.infinity.resource.cre.decoder.util.SpriteUtils; import org.infinity.resource.cre.viewer.CreatureViewer; import org.infinity.resource.gam.PartyNPC; import org.infinity.resource.graphics.PseudoBamDecoder.PseudoBamControl; diff --git a/src/org/infinity/resource/cre/decoder/AmbientDecoder.java b/src/org/infinity/resource/cre/decoder/AmbientDecoder.java index 9c6a1abf8..cfa7f3b1d 100644 --- a/src/org/infinity/resource/cre/decoder/AmbientDecoder.java +++ b/src/org/infinity/resource/cre/decoder/AmbientDecoder.java @@ -17,6 +17,7 @@ import org.infinity.resource.cre.decoder.util.SegmentDef; import org.infinity.resource.cre.decoder.util.SeqDef; import org.infinity.resource.cre.decoder.util.Sequence; +import org.infinity.resource.cre.decoder.util.SpriteUtils; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; diff --git a/src/org/infinity/resource/cre/decoder/AmbientStaticDecoder.java b/src/org/infinity/resource/cre/decoder/AmbientStaticDecoder.java index 4afd3d1c9..76752afc1 100644 --- a/src/org/infinity/resource/cre/decoder/AmbientStaticDecoder.java +++ b/src/org/infinity/resource/cre/decoder/AmbientStaticDecoder.java @@ -17,6 +17,7 @@ import org.infinity.resource.cre.decoder.util.SegmentDef; import org.infinity.resource.cre.decoder.util.SeqDef; import org.infinity.resource.cre.decoder.util.Sequence; +import org.infinity.resource.cre.decoder.util.SpriteUtils; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; diff --git a/src/org/infinity/resource/cre/decoder/ArmoredBaseDecoder.java b/src/org/infinity/resource/cre/decoder/CharacterBaseDecoder.java similarity index 97% rename from src/org/infinity/resource/cre/decoder/ArmoredBaseDecoder.java rename to src/org/infinity/resource/cre/decoder/CharacterBaseDecoder.java index 76b17aa10..a99e9569c 100644 --- a/src/org/infinity/resource/cre/decoder/ArmoredBaseDecoder.java +++ b/src/org/infinity/resource/cre/decoder/CharacterBaseDecoder.java @@ -20,7 +20,7 @@ /** * Common base for processing creature animations with different armor levels. */ -public abstract class ArmoredBaseDecoder extends SpriteDecoder +public abstract class CharacterBaseDecoder extends SpriteDecoder { public static final DecoderAttribute KEY_CAN_LIE_DOWN = DecoderAttribute.with("can_lie_down", DecoderAttribute.DataType.BOOLEAN); public static final DecoderAttribute KEY_DOUBLE_BLIT = DecoderAttribute.with("double_blit", DecoderAttribute.DataType.BOOLEAN); @@ -35,12 +35,12 @@ public enum AttackType { BOW, CROSSBOW, SLING } - public ArmoredBaseDecoder(AnimationInfo.Type type, int animationId, IniMap ini) throws Exception + public CharacterBaseDecoder(AnimationInfo.Type type, int animationId, IniMap ini) throws Exception { super(type, animationId, ini); } - public ArmoredBaseDecoder(AnimationInfo.Type type, CreResource cre) throws Exception + public CharacterBaseDecoder(AnimationInfo.Type type, CreResource cre) throws Exception { super(type, cre); } diff --git a/src/org/infinity/resource/cre/decoder/CharacterDecoder.java b/src/org/infinity/resource/cre/decoder/CharacterDecoder.java index 0fac390ea..3c678bfb9 100644 --- a/src/org/infinity/resource/cre/decoder/CharacterDecoder.java +++ b/src/org/infinity/resource/cre/decoder/CharacterDecoder.java @@ -22,6 +22,7 @@ import org.infinity.resource.cre.decoder.util.SegmentDef; import org.infinity.resource.cre.decoder.util.SeqDef; import org.infinity.resource.cre.decoder.util.Sequence; +import org.infinity.resource.cre.decoder.util.SpriteUtils; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; @@ -31,7 +32,7 @@ * Creature animation decoder for processing type 5000/6000 (character) animations. * Available ranges: [5000,53ff], [5500,55ff], [6000,63ff], [6500,65ff] */ -public class CharacterDecoder extends ArmoredBaseDecoder +public class CharacterDecoder extends CharacterBaseDecoder { /** The animation type associated with this class definition. */ public static final AnimationInfo.Type ANIMATION_TYPE = AnimationInfo.Type.CHARACTER; diff --git a/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java b/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java index 837acf98a..b8cb4b431 100644 --- a/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java +++ b/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java @@ -24,6 +24,7 @@ import org.infinity.resource.cre.decoder.util.SegmentDef; import org.infinity.resource.cre.decoder.util.SeqDef; import org.infinity.resource.cre.decoder.util.Sequence; +import org.infinity.resource.cre.decoder.util.SpriteUtils; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; @@ -33,7 +34,7 @@ * Creature animation decoder for processing type 5000/6000 (character_old) animations. * Available ranges: [5400,54ff], [5600,5fff], [6400,64ff], [6600,6fff] */ -public class CharacterOldDecoder extends ArmoredBaseDecoder +public class CharacterOldDecoder extends CharacterBaseDecoder { /** The animation type associated with this class definition. */ public static final AnimationInfo.Type ANIMATION_TYPE = AnimationInfo.Type.CHARACTER_OLD; diff --git a/src/org/infinity/resource/cre/decoder/EffectDecoder.java b/src/org/infinity/resource/cre/decoder/EffectDecoder.java index 922511ea7..01912ebc5 100644 --- a/src/org/infinity/resource/cre/decoder/EffectDecoder.java +++ b/src/org/infinity/resource/cre/decoder/EffectDecoder.java @@ -18,6 +18,7 @@ import org.infinity.resource.cre.decoder.util.Direction; import org.infinity.resource.cre.decoder.util.SeqDef; import org.infinity.resource.cre.decoder.util.Sequence; +import org.infinity.resource.cre.decoder.util.SpriteUtils; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; diff --git a/src/org/infinity/resource/cre/decoder/FlyingDecoder.java b/src/org/infinity/resource/cre/decoder/FlyingDecoder.java index d19897984..e41bf87ef 100644 --- a/src/org/infinity/resource/cre/decoder/FlyingDecoder.java +++ b/src/org/infinity/resource/cre/decoder/FlyingDecoder.java @@ -14,6 +14,7 @@ import org.infinity.resource.cre.decoder.util.DirDef; import org.infinity.resource.cre.decoder.util.SeqDef; import org.infinity.resource.cre.decoder.util.Sequence; +import org.infinity.resource.cre.decoder.util.SpriteUtils; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; diff --git a/src/org/infinity/resource/cre/decoder/MonsterAnkhegDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterAnkhegDecoder.java index 59564d128..6254ec4fa 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterAnkhegDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterAnkhegDecoder.java @@ -18,6 +18,7 @@ import org.infinity.resource.cre.decoder.util.SegmentDef; import org.infinity.resource.cre.decoder.util.SeqDef; import org.infinity.resource.cre.decoder.util.Sequence; +import org.infinity.resource.cre.decoder.util.SpriteUtils; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; diff --git a/src/org/infinity/resource/cre/decoder/MonsterDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterDecoder.java index 513aed4f4..8847e6e0b 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterDecoder.java @@ -21,6 +21,7 @@ import org.infinity.resource.cre.decoder.util.SegmentDef; import org.infinity.resource.cre.decoder.util.SeqDef; import org.infinity.resource.cre.decoder.util.Sequence; +import org.infinity.resource.cre.decoder.util.SpriteUtils; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; diff --git a/src/org/infinity/resource/cre/decoder/MonsterIcewindDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterIcewindDecoder.java index 29c309e6c..39d7d7100 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterIcewindDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterIcewindDecoder.java @@ -18,6 +18,7 @@ import org.infinity.resource.cre.decoder.util.SegmentDef; import org.infinity.resource.cre.decoder.util.SeqDef; import org.infinity.resource.cre.decoder.util.Sequence; +import org.infinity.resource.cre.decoder.util.SpriteUtils; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; diff --git a/src/org/infinity/resource/cre/decoder/MonsterLarge16Decoder.java b/src/org/infinity/resource/cre/decoder/MonsterLarge16Decoder.java index 703e23162..dd1e974e0 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterLarge16Decoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterLarge16Decoder.java @@ -17,6 +17,7 @@ import org.infinity.resource.cre.decoder.util.SegmentDef; import org.infinity.resource.cre.decoder.util.SeqDef; import org.infinity.resource.cre.decoder.util.Sequence; +import org.infinity.resource.cre.decoder.util.SpriteUtils; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; diff --git a/src/org/infinity/resource/cre/decoder/MonsterLargeDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterLargeDecoder.java index a96ee7f46..4aaa7cf66 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterLargeDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterLargeDecoder.java @@ -16,6 +16,7 @@ import org.infinity.resource.cre.decoder.util.SegmentDef; import org.infinity.resource.cre.decoder.util.SeqDef; import org.infinity.resource.cre.decoder.util.Sequence; +import org.infinity.resource.cre.decoder.util.SpriteUtils; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; diff --git a/src/org/infinity/resource/cre/decoder/MonsterLayeredDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterLayeredDecoder.java index 5cfb3769b..16b7851a8 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterLayeredDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterLayeredDecoder.java @@ -18,6 +18,7 @@ import org.infinity.resource.cre.decoder.util.SegmentDef; import org.infinity.resource.cre.decoder.util.SeqDef; import org.infinity.resource.cre.decoder.util.Sequence; +import org.infinity.resource.cre.decoder.util.SpriteUtils; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; diff --git a/src/org/infinity/resource/cre/decoder/MonsterLayeredSpellDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterLayeredSpellDecoder.java index d78006d5e..5c42a3e7f 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterLayeredSpellDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterLayeredSpellDecoder.java @@ -18,6 +18,7 @@ import org.infinity.resource.cre.decoder.util.SegmentDef; import org.infinity.resource.cre.decoder.util.SeqDef; import org.infinity.resource.cre.decoder.util.Sequence; +import org.infinity.resource.cre.decoder.util.SpriteUtils; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; diff --git a/src/org/infinity/resource/cre/decoder/MonsterMultiDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterMultiDecoder.java index 852c247e2..5f809cc3d 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterMultiDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterMultiDecoder.java @@ -19,6 +19,7 @@ import org.infinity.resource.cre.decoder.util.SegmentDef; import org.infinity.resource.cre.decoder.util.SeqDef; import org.infinity.resource.cre.decoder.util.Sequence; +import org.infinity.resource.cre.decoder.util.SpriteUtils; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; diff --git a/src/org/infinity/resource/cre/decoder/MonsterMultiNewDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterMultiNewDecoder.java index 0c3ad00a3..73babf147 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterMultiNewDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterMultiNewDecoder.java @@ -17,6 +17,7 @@ import org.infinity.resource.cre.decoder.util.SegmentDef; import org.infinity.resource.cre.decoder.util.SeqDef; import org.infinity.resource.cre.decoder.util.Sequence; +import org.infinity.resource.cre.decoder.util.SpriteUtils; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; diff --git a/src/org/infinity/resource/cre/decoder/MonsterOldDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterOldDecoder.java index f89bf8f76..2f20c61ab 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterOldDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterOldDecoder.java @@ -16,6 +16,7 @@ import org.infinity.resource.cre.decoder.util.SegmentDef; import org.infinity.resource.cre.decoder.util.SeqDef; import org.infinity.resource.cre.decoder.util.Sequence; +import org.infinity.resource.cre.decoder.util.SpriteUtils; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; diff --git a/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java index e052d0b4e..3a5bbed90 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java @@ -25,6 +25,7 @@ import org.infinity.resource.cre.decoder.util.SegmentDef; import org.infinity.resource.cre.decoder.util.SeqDef; import org.infinity.resource.cre.decoder.util.Sequence; +import org.infinity.resource.cre.decoder.util.SpriteUtils; import org.infinity.resource.graphics.BamV1Decoder; import org.infinity.resource.graphics.BamV1Decoder.BamV1Control; import org.infinity.resource.graphics.BamV1Decoder.BamV1FrameEntry; diff --git a/src/org/infinity/resource/cre/decoder/MonsterQuadrantDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterQuadrantDecoder.java index b7905076e..d6dcb1d27 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterQuadrantDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterQuadrantDecoder.java @@ -17,6 +17,7 @@ import org.infinity.resource.cre.decoder.util.SegmentDef; import org.infinity.resource.cre.decoder.util.SeqDef; import org.infinity.resource.cre.decoder.util.Sequence; +import org.infinity.resource.cre.decoder.util.SpriteUtils; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index e2fbd3a30..6ae1a5950 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -50,6 +50,7 @@ import org.infinity.resource.cre.decoder.util.SegmentDef; import org.infinity.resource.cre.decoder.util.SeqDef; import org.infinity.resource.cre.decoder.util.Sequence; +import org.infinity.resource.cre.decoder.util.SpriteUtils; import org.infinity.resource.cre.viewer.icon.Icons; import org.infinity.resource.graphics.ColorConvert; import org.infinity.resource.graphics.PseudoBamDecoder; diff --git a/src/org/infinity/resource/cre/decoder/TownStaticDecoder.java b/src/org/infinity/resource/cre/decoder/TownStaticDecoder.java index b86e68c1f..ec8aec5b5 100644 --- a/src/org/infinity/resource/cre/decoder/TownStaticDecoder.java +++ b/src/org/infinity/resource/cre/decoder/TownStaticDecoder.java @@ -16,6 +16,7 @@ import org.infinity.resource.cre.decoder.util.SegmentDef; import org.infinity.resource.cre.decoder.util.SeqDef; import org.infinity.resource.cre.decoder.util.Sequence; +import org.infinity.resource.cre.decoder.util.SpriteUtils; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; diff --git a/src/org/infinity/resource/cre/decoder/util/SegmentDef.java b/src/org/infinity/resource/cre/decoder/util/SegmentDef.java index 7e00c9bbc..a52889ecb 100644 --- a/src/org/infinity/resource/cre/decoder/util/SegmentDef.java +++ b/src/org/infinity/resource/cre/decoder/util/SegmentDef.java @@ -7,7 +7,6 @@ import java.util.EnumMap; import java.util.Objects; -import org.infinity.resource.cre.decoder.SpriteUtils; import org.infinity.resource.key.ResourceEntry; /** diff --git a/src/org/infinity/resource/cre/decoder/SpriteUtils.java b/src/org/infinity/resource/cre/decoder/util/SpriteUtils.java similarity index 99% rename from src/org/infinity/resource/cre/decoder/SpriteUtils.java rename to src/org/infinity/resource/cre/decoder/util/SpriteUtils.java index 84ff7c109..0bb33c734 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteUtils.java +++ b/src/org/infinity/resource/cre/decoder/util/SpriteUtils.java @@ -2,7 +2,7 @@ // Copyright (C) 2001 - 2021 Jon Olav Hauglid // See LICENSE.txt for license information -package org.infinity.resource.cre.decoder; +package org.infinity.resource.cre.decoder.util; import java.awt.image.BufferedImage; import java.nio.ByteBuffer; diff --git a/src/org/infinity/resource/cre/viewer/CreatureViewer.java b/src/org/infinity/resource/cre/viewer/CreatureViewer.java index e382a6769..60d094c8a 100644 --- a/src/org/infinity/resource/cre/viewer/CreatureViewer.java +++ b/src/org/infinity/resource/cre/viewer/CreatureViewer.java @@ -24,7 +24,7 @@ import org.infinity.icon.Icons; import org.infinity.resource.cre.CreResource; import org.infinity.resource.cre.decoder.SpriteDecoder; -import org.infinity.resource.cre.decoder.SpriteUtils; +import org.infinity.resource.cre.decoder.util.SpriteUtils; /** * The Creature Viewer implements a highly customizable viewer for creature animations. From 0948ef43b813356fa3fb826c91503cb6c2b21e4f Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Mon, 22 Mar 2021 19:14:16 +0100 Subject: [PATCH 074/158] BamDecoder: Add composite support --- .../resource/graphics/BamDecoder.java | 22 +++++++++++++++++++ .../resource/graphics/BamV1Decoder.java | 7 ++++-- .../resource/graphics/BamV2Decoder.java | 12 +++++++--- .../resource/graphics/PseudoBamDecoder.java | 6 +++-- 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/org/infinity/resource/graphics/BamDecoder.java b/src/org/infinity/resource/graphics/BamDecoder.java index 95a8ea98e..54d4630df 100644 --- a/src/org/infinity/resource/graphics/BamDecoder.java +++ b/src/org/infinity/resource/graphics/BamDecoder.java @@ -4,6 +4,8 @@ package org.infinity.resource.graphics; +import java.awt.AlphaComposite; +import java.awt.Composite; import java.awt.Dimension; import java.awt.Image; import java.awt.Point; @@ -26,6 +28,7 @@ public enum Type { INVALID, BAMC, BAMV1, BAMV2, CUSTOM } private final ResourceEntry bamEntry; private Type type; + private Composite composite; /** * Returns whether the specified resource entry points to a valid BAM resource. @@ -154,11 +157,30 @@ public Type getType() /** Draws the specified frame onto the canvas. */ public abstract void frameGet(BamControl control, int frameIdx, Image canvas); + /** + * Returns the {@link Composite} instance that is used to draw the BAM frame onto a canvas. + *

    By default the {@link AlphaComposite#SrcOver} composite object is used. + */ + public Composite getComposite() + { + return (composite != null) ? composite : AlphaComposite.SrcOver; + } + + /** + * Sets a {@link Composite} instance that is used to draw the BAM frame onto a canvas. + * Specify {@code null} to use the default {@code Composite}. + */ + public void setComposite(Composite comp) + { + this.composite = comp; + } + protected BamDecoder(ResourceEntry bamEntry) { this.bamEntry = bamEntry; this.type = Type.INVALID; + this.composite = null; // use default } diff --git a/src/org/infinity/resource/graphics/BamV1Decoder.java b/src/org/infinity/resource/graphics/BamV1Decoder.java index 71d339502..7809ac891 100644 --- a/src/org/infinity/resource/graphics/BamV1Decoder.java +++ b/src/org/infinity/resource/graphics/BamV1Decoder.java @@ -5,7 +5,7 @@ package org.infinity.resource.graphics; import java.awt.Dimension; -import java.awt.Graphics; +import java.awt.Graphics2D; import java.awt.Image; import java.awt.image.BufferedImage; import java.awt.image.DataBufferByte; @@ -325,8 +325,11 @@ private void decodeFrame(BamControl control, int frameIdx, Image canvas) // rendering resulting image onto the canvas if needed if (image != canvas) { - Graphics g = canvas.getGraphics(); + Graphics2D g = (Graphics2D)canvas.getGraphics(); try { + if (getComposite() != null) { + g.setComposite(getComposite()); + } g.drawImage(image, 0, 0, null); } finally { g.dispose(); diff --git a/src/org/infinity/resource/graphics/BamV2Decoder.java b/src/org/infinity/resource/graphics/BamV2Decoder.java index 4cad8aec8..a4fbece8e 100644 --- a/src/org/infinity/resource/graphics/BamV2Decoder.java +++ b/src/org/infinity/resource/graphics/BamV2Decoder.java @@ -5,7 +5,7 @@ package org.infinity.resource.graphics; import java.awt.Dimension; -import java.awt.Graphics; +import java.awt.Graphics2D; import java.awt.Image; import java.awt.Transparency; import java.awt.image.BufferedImage; @@ -310,8 +310,11 @@ private void renderFrame(BamControl control, int frameIdx, Image canvas) // rendering resulting image onto the canvas if needed if (image != canvas) { - Graphics g = canvas.getGraphics(); + Graphics2D g = (Graphics2D)canvas.getGraphics(); try { + if (getComposite() != null) { + g.setComposite(getComposite()); + } g.drawImage(image, 0, 0, null); } finally { g.dispose(); @@ -426,7 +429,10 @@ private void decodeImage(ByteBuffer buffer, int ofsBlocks, int start, int count) if (decoder != null) { try { BufferedImage srcImage = decoder.decode(srcX, srcY, w, h); - Graphics g = frame.getGraphics(); + Graphics2D g = (Graphics2D)frame.getGraphics(); + if (getComposite() != null) { + g.setComposite(getComposite()); + } g.drawImage(srcImage, dstX, dstY, null); g.dispose(); g = null; diff --git a/src/org/infinity/resource/graphics/PseudoBamDecoder.java b/src/org/infinity/resource/graphics/PseudoBamDecoder.java index 45d23cb60..833602611 100644 --- a/src/org/infinity/resource/graphics/PseudoBamDecoder.java +++ b/src/org/infinity/resource/graphics/PseudoBamDecoder.java @@ -7,7 +7,6 @@ import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Dimension; -import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Point; @@ -589,8 +588,11 @@ private void renderFrame(BamControl control, int frameIdx, Image canvas) // rendering resulting image onto the canvas if needed if (dstImage != canvas) { - Graphics g = canvas.getGraphics(); + Graphics2D g = (Graphics2D)canvas.getGraphics(); try { + if (getComposite() != null) { + g.setComposite(getComposite()); + } g.drawImage(dstImage, 0, 0, null); } finally { g.dispose(); From 46def3a6b5ce3b7973cc777f530350e87feb3e24 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Tue, 23 Mar 2021 20:42:25 +0100 Subject: [PATCH 075/158] Improve usability of MultiNumber datatype Field value is automatically updated when a table value is entered --- src/org/infinity/datatype/MultiNumber.java | 37 ++++++++++++++-------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/src/org/infinity/datatype/MultiNumber.java b/src/org/infinity/datatype/MultiNumber.java index aef86748b..d8cd83792 100644 --- a/src/org/infinity/datatype/MultiNumber.java +++ b/src/org/infinity/datatype/MultiNumber.java @@ -6,27 +6,29 @@ import java.awt.Color; import java.awt.Dimension; +import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; +import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; import javax.swing.BorderFactory; -import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.ListSelectionModel; +import javax.swing.event.TableModelEvent; +import javax.swing.event.TableModelListener; import javax.swing.table.AbstractTableModel; import org.infinity.gui.BrowserMenuBar; import org.infinity.gui.StructViewer; import org.infinity.gui.ViewerUtil; -import org.infinity.icon.Icons; import org.infinity.resource.AbstractStruct; import org.infinity.util.Misc; @@ -104,19 +106,31 @@ public JComponent edit(ActionListener container) tValues = new JTable(mValues); tValues.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); tValues.setFont(Misc.getScaledFont(BrowserMenuBar.getInstance().getScriptFont())); + tValues.getTableHeader().setFont(tValues.getTableHeader().getFont().deriveFont(Font.BOLD)); tValues.setRowHeight(tValues.getFontMetrics(tValues.getFont()).getHeight() + 1); tValues.setBorder(BorderFactory.createLineBorder(Color.GRAY)); tValues.getTableHeader().setBorder(BorderFactory.createLineBorder(Color.GRAY)); tValues.getTableHeader().setReorderingAllowed(false); tValues.getTableHeader().setResizingAllowed(true); + tValues.setRowHeight(tValues.getRowHeight() * 5 / 4); + tValues.setIntercellSpacing(new Dimension(4, 0)); tValues.setPreferredScrollableViewportSize(tValues.getPreferredSize()); + tValues.setCellSelectionEnabled(true); + tValues.getModel().addTableModelListener(new TableModelListener() { + @Override + public void tableChanged(TableModelEvent e) + { + // update field value automatically + if (e.getType() == TableModelEvent.UPDATE) { + ActionEvent ae = new ActionEvent(tValues, 0, StructViewer.UPDATE_VALUE); + container.actionPerformed(ae); + } + } + }); + JScrollPane scroll = new JScrollPane(tValues); scroll.setBorder(BorderFactory.createEmptyBorder()); - JButton bUpdate = new JButton("Update value", Icons.getIcon(Icons.ICON_REFRESH_16)); - bUpdate.addActionListener(container); - bUpdate.setActionCommand(StructViewer.UPDATE_VALUE); - JPanel panel = new JPanel(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); @@ -124,17 +138,14 @@ public JComponent edit(ActionListener container) GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); panel.add(scroll, gbc); - gbc = ViewerUtil.setGBC(gbc, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, - GridBagConstraints.NONE, new Insets(0, 8, 0, 0), 0, 0); - panel.add(bUpdate, gbc); - Dimension dim = Misc.getScaledDimension(DIM_MEDIUM); panel.setPreferredSize(dim); // making "Attribute" column wider - int tableWidth = dim.width - bUpdate.getPreferredSize().width - 8; - tValues.getColumnModel().getColumn(0).setPreferredWidth(tableWidth * 3 / 4); - tValues.getColumnModel().getColumn(1).setPreferredWidth(tableWidth * 1 / 4); + tValues.getColumnModel().getColumn(0).setPreferredWidth(dim.width * 3 / 4); + tValues.getColumnModel().getColumn(1).setPreferredWidth(dim.width * 1 / 4); + + tValues.changeSelection(0, 1, false, false); return panel; } From 515f58dcf983ad7c1cc838974082bda124493cd7 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Tue, 23 Mar 2021 22:52:03 +0100 Subject: [PATCH 076/158] Cosmetic --- .../infinity/resource/cre/viewer/CreatureControlPanel.java | 3 +-- src/org/infinity/resource/cre/viewer/CreatureViewer.java | 5 ++++- src/org/infinity/resource/cre/viewer/MediaPanel.java | 3 +-- src/org/infinity/resource/cre/viewer/SettingsPanel.java | 3 +-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/org/infinity/resource/cre/viewer/CreatureControlPanel.java b/src/org/infinity/resource/cre/viewer/CreatureControlPanel.java index 62cd2f7e0..1b689c3f8 100644 --- a/src/org/infinity/resource/cre/viewer/CreatureControlPanel.java +++ b/src/org/infinity/resource/cre/viewer/CreatureControlPanel.java @@ -24,7 +24,6 @@ import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; -import javax.swing.border.EtchedBorder; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; @@ -459,7 +458,7 @@ private void init() panelMain.add(pColumn4, c); JScrollPane scroll = new JScrollPane(panelMain); - scroll.setBorder(new EtchedBorder()); + scroll.setBorder(panelMain.getBorder()); setLayout(new BorderLayout()); add(scroll, BorderLayout.CENTER); diff --git a/src/org/infinity/resource/cre/viewer/CreatureViewer.java b/src/org/infinity/resource/cre/viewer/CreatureViewer.java index 60d094c8a..7ba2df4fe 100644 --- a/src/org/infinity/resource/cre/viewer/CreatureViewer.java +++ b/src/org/infinity/resource/cre/viewer/CreatureViewer.java @@ -123,20 +123,23 @@ private void init() // *** CRE customization panel *** panelCreature = new CreatureControlPanel(this); + panelCreature.setBorder(new EtchedBorder()); // *** Settings panel *** panelSettings = new SettingsPanel(this); + panelSettings.setBorder(new EtchedBorder()); // *** Animation viewer panel *** panelCanvas = new RenderPanel(this); JPanel viewerPanel = new JPanel(new BorderLayout()); - viewerPanel.add(panelCanvas, BorderLayout.CENTER); viewerPanel.setBorder(new EtchedBorder()); + viewerPanel.add(panelCanvas, BorderLayout.CENTER); // *** Controls panel *** // sub panel for playback controls panelMedia = new MediaPanel(this); + panelMedia.setBorder(new EtchedBorder()); // *** Top-level viewer panel *** JPanel viewerMainPanel = new JPanel(new BorderLayout()); diff --git a/src/org/infinity/resource/cre/viewer/MediaPanel.java b/src/org/infinity/resource/cre/viewer/MediaPanel.java index ccee75c63..ba697fe87 100644 --- a/src/org/infinity/resource/cre/viewer/MediaPanel.java +++ b/src/org/infinity/resource/cre/viewer/MediaPanel.java @@ -29,7 +29,6 @@ import javax.swing.JSlider; import javax.swing.SwingConstants; import javax.swing.Timer; -import javax.swing.border.EtchedBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; @@ -556,7 +555,7 @@ private void init() panelMain.add(new JPanel(), c); JScrollPane scrollMedia = new JScrollPane(panelMain); - scrollMedia.setBorder(new EtchedBorder()); + scrollMedia.setBorder(panelMain.getBorder()); setLayout(new BorderLayout()); add(scrollMedia, BorderLayout.CENTER); diff --git a/src/org/infinity/resource/cre/viewer/SettingsPanel.java b/src/org/infinity/resource/cre/viewer/SettingsPanel.java index 22551a85c..c318cab70 100644 --- a/src/org/infinity/resource/cre/viewer/SettingsPanel.java +++ b/src/org/infinity/resource/cre/viewer/SettingsPanel.java @@ -22,7 +22,6 @@ import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; -import javax.swing.border.EtchedBorder; import org.infinity.gui.ViewerUtil; import org.infinity.resource.Profile; @@ -501,7 +500,7 @@ private void init() panelMain.add(panelAttributes, c); JScrollPane scroll = new JScrollPane(panelMain); - scroll.setBorder(new EtchedBorder()); + scroll.setBorder(panelMain.getBorder()); setLayout(new BorderLayout()); add(scroll, BorderLayout.CENTER); From a9ae6ffcf7df7dd218dab0f45c8b98b82bc9de0a Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Wed, 24 Mar 2021 22:21:39 +0100 Subject: [PATCH 077/158] Improve color reduction algorithm used in BAM, MOS and TIS conversions Added option to MOS and TIS converters to revert to original (faster) algorithm. --- src/org/infinity/NearInfinity.java | 2 + .../infinity/gui/converter/ConvertToBam.java | 6 +- .../infinity/gui/converter/ConvertToMos.java | 22 +- .../infinity/gui/converter/ConvertToTis.java | 23 +- .../resource/graphics/BamResource.java | 6 +- .../resource/graphics/ColorConvert.java | 552 +++++++++++++----- .../resource/graphics/MosResource.java | 2 +- .../resource/graphics/TisResource.java | 2 +- 8 files changed, 453 insertions(+), 162 deletions(-) diff --git a/src/org/infinity/NearInfinity.java b/src/org/infinity/NearInfinity.java index a4157b397..0fa032659 100644 --- a/src/org/infinity/NearInfinity.java +++ b/src/org/infinity/NearInfinity.java @@ -93,6 +93,7 @@ import org.infinity.resource.bcs.Signatures; import org.infinity.resource.cre.decoder.util.ItemInfo; import org.infinity.resource.cre.decoder.util.SpriteUtils; +import org.infinity.resource.graphics.ColorConvert; import org.infinity.resource.key.FileResourceEntry; import org.infinity.resource.key.ResourceEntry; import org.infinity.resource.key.ResourceTreeModel; @@ -1045,6 +1046,7 @@ private static void clearCache(boolean refreshOnly) StringTable.resetAll(); ProRef.clearCache(); Signatures.clearCache(); + ColorConvert.clearCache(); SpriteUtils.clearCache(); ItemInfo.clearCache(); } diff --git a/src/org/infinity/gui/converter/ConvertToBam.java b/src/org/infinity/gui/converter/ConvertToBam.java index 918788c81..4012bd749 100644 --- a/src/org/infinity/gui/converter/ConvertToBam.java +++ b/src/org/infinity/gui/converter/ConvertToBam.java @@ -4129,7 +4129,8 @@ private void updateFinalBamDecoder(int bamVersion) throws Exception if (ci >= transIndex) ci++; dstBuf[ofs] = colIdx.byteValue();//(byte)ci; } else { - byte color = (byte)ColorConvert.nearestColorRGB(srcBuf[ofs], palette, !getUseAlpha()); + double weight = getUseAlpha() ? 1.0 : 0.0; + byte color = (byte)ColorConvert.getNearestColor(srcBuf[ofs], palette, weight, ColorConvert.COLOR_DISTANCE_CIE94); dstBuf[ofs] = color;//(byte)ci; colorCache.put(Integer.valueOf(c), Byte.valueOf(color)); } @@ -4221,7 +4222,8 @@ private void updateFinalBamFrame(int bamVersion, int frameIdx) if (ci >= transIndex) ci++; dstBuf[ofs] = colIdx.byteValue(); } else { - byte color = (byte)ColorConvert.nearestColorRGB(srcBuf[ofs], palette, !getUseAlpha()); + double weight = getUseAlpha() ? 1.0 : 0.0; + byte color = (byte)ColorConvert.getNearestColor(srcBuf[ofs], palette, weight, ColorConvert.COLOR_DISTANCE_CIE94); dstBuf[ofs] = color;//(byte)ci; colorCache.put(Integer.valueOf(c), Byte.valueOf(color)); } diff --git a/src/org/infinity/gui/converter/ConvertToMos.java b/src/org/infinity/gui/converter/ConvertToMos.java index f074296db..9c6d0471b 100644 --- a/src/org/infinity/gui/converter/ConvertToMos.java +++ b/src/org/infinity/gui/converter/ConvertToMos.java @@ -75,7 +75,7 @@ public class ConvertToMos extends ChildFrame private JSpinner sPvrzIndex; private JLabel lPvrzInfo; private JComboBox cbCompression; - private JCheckBox cbCompress, cbCloseOnExit; + private JCheckBox cbCompress, cbColorConvert, cbCloseOnExit; private SwingWorker, Void> workerConvert; private WindowBlocker blocker; @@ -85,13 +85,15 @@ public class ConvertToMos extends ChildFrame * @param img The source image to convert into a MOS resource. * @param mosFileName The name of the resulting MOS file. * @param compressed If {@code true}, converts into a compressed BAMC file. + * @param fastConvert If {@code true}, uses a fast but less accurate color reduction algorith. * @param result Returns more specific information about the conversion process. Data placed in the * first item indicates success, data in the second item indicates failure. * @param showProgress Specify whether to show a progress monitor (needs a valid 'parent' parameter). * @return {@code true} if the conversion finished successfully, {@code false} otherwise. */ public static boolean convertV1(Component parent, BufferedImage img, String mosFileName, - boolean compressed, List result, boolean showProgress) + boolean compressed, boolean fastConvert, List result, + boolean showProgress) { // checking parameters if (result == null) { @@ -194,7 +196,8 @@ public static boolean convertV1(Component parent, BufferedImage img, String mosF if (palIndex != null) { tileData[i] = (byte)(palIndex + 1); } else { - byte color = (byte)ColorConvert.nearestColorRGB(pixels[i], palette, true); + ColorConvert.ColorDistanceFunc colorFunc = fastConvert ? ColorConvert.COLOR_DISTANCE_ARGB : ColorConvert.COLOR_DISTANCE_CIE94; + byte color = (byte)ColorConvert.getNearestColor(pixels[i], palette, 0.0, colorFunc); tileData[i] = (byte)(color + 1); colorCache.put(pixels[i], color); } @@ -768,13 +771,21 @@ private void init() JPanel pOptionsV1 = new JPanel(new GridBagLayout()); pOptionsV1.setBorder(BorderFactory.createTitledBorder("Options ")); cbCompress = new JCheckBox("Compressed (MOSC)", false); + cbColorConvert = new JCheckBox("Fast conversion", false); + cbColorConvert.setToolTipText("Enable to use a faster but less accurate color reduction algorithm."); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(4, 0, 0, 0), 0, 0); pOptionsV1.add(cbCompress, c); c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 0, 0); pOptionsV1.add(new JPanel(), c); - c = ViewerUtil.setGBC(c, 0, 1, 2, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, + c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.NONE, new Insets(4, 0, 0, 0), 0, 0); + pOptionsV1.add(cbColorConvert, c); + c = ViewerUtil.setGBC(c, 1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 0, 0); + pOptionsV1.add(new JPanel(), c); + c = ViewerUtil.setGBC(c, 0, 2, 2, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0); pOptionsV1.add(new JPanel(), c); @@ -1013,10 +1024,11 @@ private List convert() // fetching remaining settings int pvrzIndex = getPvrzIndex(sPvrzIndex.getValue()); boolean isMOSC = cbCompress.isSelected(); + boolean isFastConversion = cbColorConvert.isSelected(); // converting if (tabPane.getSelectedIndex() == 0) { - convertV1(this, srcImage, tfOutputV1.getText(), isMOSC, result, true); + convertV1(this, srcImage, tfOutputV1.getText(), isMOSC, isFastConversion, result, true); } else if (tabPane.getSelectedIndex() == 1) { convertV2(this, srcImage, tfOutputV2.getText(), dxtType, pvrzIndex, result, true); } else { diff --git a/src/org/infinity/gui/converter/ConvertToTis.java b/src/org/infinity/gui/converter/ConvertToTis.java index 57cb757c7..f3183d382 100644 --- a/src/org/infinity/gui/converter/ConvertToTis.java +++ b/src/org/infinity/gui/converter/ConvertToTis.java @@ -75,7 +75,7 @@ public class ConvertToTis extends ChildFrame private JButton bConvert, bCancel; private JButton bInput, bOutput, bVersionHelp; private JComboBox cbVersion; - private JCheckBox cbCloseOnExit; + private JCheckBox cbColorConvert, cbCloseOnExit; private SwingWorker, Void> workerConvert; private WindowBlocker blocker; @@ -92,7 +92,7 @@ public class ConvertToTis extends ChildFrame * @return {@code true} if the conversion finished successfully, {@code false} otherwise. */ public static boolean convertV1(Component parent, BufferedImage img, String tisFileName, int tileCount, - List result, boolean showProgress) + boolean fastConvert, List result, boolean showProgress) { // checking parameters if (result == null) { @@ -199,7 +199,8 @@ public static boolean convertV1(Component parent, BufferedImage img, String tisF if (palIndex != null) { tileData[i] = (byte)(palIndex + 1); } else { - byte color = (byte)ColorConvert.nearestColorRGB(srcBlock[i], palette, true); + ColorConvert.ColorDistanceFunc colorFunc = fastConvert ? ColorConvert.COLOR_DISTANCE_ARGB : ColorConvert.COLOR_DISTANCE_CIE94; + byte color = (byte)ColorConvert.getNearestColor(srcBlock[i], palette, 0.0, colorFunc); tileData[i] = (byte)(color + 1); colorCache.put(srcBlock[i], color); } @@ -860,11 +861,20 @@ private void init() JLabel lVersion = new JLabel("TIS version:"); cbVersion = new JComboBox<>(new String[]{"Legacy", "PVRZ-based"}); cbVersion.setSelectedIndex(0); + cbVersion.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) + { + cbColorConvert.setEnabled(cbVersion.getSelectedIndex() == 0); + } + }); bVersionHelp = new JButton("?"); bVersionHelp.setToolTipText("About TIS versions"); bVersionHelp.addActionListener(this); bVersionHelp.setMargin(new Insets(bVersionHelp.getInsets().top, 4, bVersionHelp.getInsets().bottom, 4)); + cbColorConvert = new JCheckBox("Fast conversion"); + cbColorConvert.setToolTipText("(Legacy only) Enable to use a faster but less accurate color reduction algorithm."); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); @@ -875,7 +885,10 @@ private void init() c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); pSubOptions.add(bVersionHelp, c); - c = ViewerUtil.setGBC(c, 3, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, + c = ViewerUtil.setGBC(c, 3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.NONE, new Insets(0, 16, 0, 0), 0, 0); + pSubOptions.add(cbColorConvert, c); + c = ViewerUtil.setGBC(c, 4, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); pSubOptions.add(new JPanel(), c); @@ -1101,7 +1114,7 @@ private List convert() convertV2(this, srcImage, outFileName, tileCount, ret, true); } else { // TIS V1 conversion - convertV1(this, srcImage, outFileName, tileCount, ret, true); + convertV1(this, srcImage, outFileName, tileCount, cbColorConvert.isSelected(), ret, true); } return ret; diff --git a/src/org/infinity/resource/graphics/BamResource.java b/src/org/infinity/resource/graphics/BamResource.java index e88449eff..57d68cf31 100644 --- a/src/org/infinity/resource/graphics/BamResource.java +++ b/src/org/infinity/resource/graphics/BamResource.java @@ -1120,7 +1120,8 @@ private byte[] convertToBamV1(boolean compressed) throws Exception if (colIdx != null) { dstData[dstIdx++] = (byte)(colIdx + colorShift); } else { - int color = ColorConvert.nearestColorRGB(srcData[srcIdx], palette, ignoreAlpha); + double weight = ignoreAlpha ? 0.0 : 1.0; + int color = ColorConvert.getNearestColor(srcData[srcIdx], palette, weight, ColorConvert.COLOR_DISTANCE_CIE94); dstData[dstIdx++] = (byte)(color); if (color > 0) { colorCache.put(srcData[srcIdx], (byte)(color - colorShift)); @@ -1144,7 +1145,8 @@ private byte[] convertToBamV1(boolean compressed) throws Exception if (colIdx != null) { dstData[idx] = (byte)(colIdx + colorShift); } else { - int color = ColorConvert.nearestColorRGB(srcData[idx], palette, ignoreAlpha); + double weight = ignoreAlpha ? 0.0 : 1.0; + int color = ColorConvert.getNearestColor(srcData[idx], palette, weight, ColorConvert.COLOR_DISTANCE_CIE94); dstData[idx] = (byte)(color); if (color > 0) { colorCache.put(srcData[idx], (byte)(color - colorShift)); diff --git a/src/org/infinity/resource/graphics/ColorConvert.java b/src/org/infinity/resource/graphics/ColorConvert.java index 1bec5e26c..67b1bdc4a 100644 --- a/src/org/infinity/resource/graphics/ColorConvert.java +++ b/src/org/infinity/resource/graphics/ColorConvert.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; +import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; import java.util.List; @@ -38,12 +39,53 @@ import org.infinity.util.DynamicArray; import org.infinity.util.io.FileEx; import org.infinity.util.io.StreamUtils; +import org.infinity.util.tuples.Triple; /** * Contains a set of color-related static methods (little endian order only). */ public class ColorConvert { + /** + * A fast but somewhat inaccurate algorithm for calculating a distance between two ARGB values. + * It uses predefined weight values for each color component to calculate the distance. + */ + public static final ColorDistanceFunc COLOR_DISTANCE_ARGB = (argb1, argb2, weight) -> { + int a1 = (argb1 >> 24) & 0xff; + int r1 = (argb1 >> 16) & 0xff; + int g1 = (argb1 >> 8) & 0xff; + int b1 = argb1 & 0xff; + + int a2 = (argb2 >> 24) & 0xff; + int r2 = (argb2 >> 16) & 0xff; + int g2 = (argb2 >> 8) & 0xff; + int b2 = argb2 & 0xff; + + weight = Math.max(0.0, Math.min(2.0, weight)); + double da = (double)(a1 - a2) * 48.0 * weight; + double dr = (double)(r1 - r2) * 14.0; + double dg = (double)(g1 - g2) * 28.0; + double db = (double)(b1 - b2) * 6.0; + return Math.sqrt(da*da + dr*dr + dg*dg + db*db); + }; + + /** + * Returns the distance between the two ARGB values using CIELAB colorspace and CIE94 formula. + * This algorithm is slower than the default ARGB distance calculation but more accurate. + */ + public static final ColorDistanceFunc COLOR_DISTANCE_CIE94 = (argb1, argb2, weight) -> { + Triple lab1 = convertRGBtoLab(argb1); + Triple lab2 = convertRGBtoLab(argb2); + weight = Math.max(0.0, Math.min(2.0, weight)); + double alpha1 = (double)((argb1 >> 24) & 0xff) * weight; + double alpha2 = (double)((argb2 >> 24) & 0xff) * weight; + return getColorDistanceLabCIE94(lab1.getValue0().doubleValue(), lab1.getValue1().doubleValue(), lab1.getValue2().doubleValue(), alpha1, + lab2.getValue0().doubleValue(), lab2.getValue1().doubleValue(), lab2.getValue2().doubleValue(), alpha2); + }; + + // Cache for ARGB key -> CIELAB color space values + private static final HashMap> ARGB_LAB_CACHE = new HashMap<>(); + // max. number of colors for color reduction algorithms private static final int MAX_COLORS = 256; @@ -55,7 +97,13 @@ public enum SortType { Red, // Sort by red color component. Green, // Sort by green color component. Blue, // Sort by blue color component. - Alpha // Sort by alpha component. + Alpha, // Sort by alpha component. + Lab, // Sort by CIELAB L component. + } + + public static void clearCache() + { + ARGB_LAB_CACHE.clear(); } /** @@ -230,50 +278,86 @@ public static Dimension getImageDimension(Path fileName) return d; } +// /** +// * Calculates the nearest color available in the given RGBA palette for the specified color. +// * @param rgbColor The source color in ARGB format. +// * @param rgbPalette A palette containing ARGB color entries. +// * @param ignoreAlpha Whether to exclude alpha component from the calculation. +// * @return The palette index pointing to the nearest color, or -1 on error. +// */ +// public static int nearestColorRGB(int rgbColor, int[] rgbPalette, boolean ignoreAlpha) +// { +// // TODO: Improve match quality for grayscaled colors +// int index = -1; +// if (rgbPalette != null && rgbPalette.length > 0) { +// int mask = ignoreAlpha ? 0 : 0xff000000; +// int minDist = Integer.MAX_VALUE; +// int a = ((rgbColor & mask) >> 24) & 0xff; +// int r = (rgbColor >> 16) & 0xff; +// int g = (rgbColor >> 8) & 0xff; +// int b = rgbColor & 0xff; +// +// int da, dr, dg, db; +// for (int i = 0; i < rgbPalette.length; i++) { +// int col = rgbPalette[i]; +// // Extra check for full transparency +// if (a == 0) { +// if ((col & 0xff000000) == 0) { return i; } +// if (col == 0xff00ff00) { return i; } +// } +// // Computing weighted distance to compensate for perceived color differences +// int a2 = ((rgbPalette[i] & mask) >> 24) & 0xff; +// int r2 = (rgbPalette[i] >> 16) & 0xff; +// int g2 = (rgbPalette[i] >> 8) & 0xff; +// int b2 = rgbPalette[i] & 0xff; +// da = (a - a2) * 48; +// dr = (r - r2) * 14; +// dg = (g - g2) * 28; +// db = (b - b2) * 6; +// int dist = da*da + dr*dr + dg*dg + db*db; +// if (dist < minDist) { +// minDist = dist; +// index = i; +// } +// } +// } +// return index; +// } + /** - * Calculates the nearest color available in the given RGBA palette for the specified color. - * @param rgbColor The source color in ARGB format. - * @param rgbPalette A palette containing ARGB color entries. - * @param ignoreAlpha Whether to exclude alpha component from the calculation. - * @return The palette index pointing to the nearest color, or -1 on error. + * Calculates the nearest color available in the given palette using the specified color distance function. + * @param argb the reference ARGB color. + * @param palette palette with ARGB colors to search. + * @param alphaWeight Weight factor of the alpha component. Supported range: [0.0, 2.0]. + * A value < 1.0 makes alpha less important for the distance calculation. + * A value > 1.0 makes alpha more important for the distance calculation. + * Specify 1.0 to use the unmodified alpha compomponent for the calculation. + * Specify 0.0 to ignore the alpha part in the calculation. + * @param calculator the function for distance calculation. Choose one of the predefined functions or specify a custom + * instance. Specify {@code null} to use the fastest (but slightly inaccurate) distance calculation. + * @return Palette index pointing to the nearest color value. Returns -1 if color entry could not be determined. */ - public static int nearestColorRGB(int rgbColor, int[] rgbPalette, boolean ignoreAlpha) + public static int getNearestColor(int argb, int[] palette, double alphaWeight, ColorDistanceFunc calculator) { - // TODO: Improve match quality for grayscaled colors - int index = -1; - if (rgbPalette != null && rgbPalette.length > 0) { - int mask = ignoreAlpha ? 0 : 0xff000000; - int minDist = Integer.MAX_VALUE; - int a = ((rgbColor & mask) >> 24) & 0xff; - int r = (rgbColor >> 16) & 0xff; - int g = (rgbColor >> 8) & 0xff; - int b = rgbColor & 0xff; - - int da, dr, dg, db; - for (int i = 0; i < rgbPalette.length; i++) { - int col = rgbPalette[i]; - // Extra check for full transparency - if (a == 0) { - if ((col & 0xff000000) == 0) { return i; } - if (col == 0xff00ff00) { return i; } - } - // Computing weighted distance to compensate for perceived color differences - int a2 = ((rgbPalette[i] & mask) >> 24) & 0xff; - int r2 = (rgbPalette[i] >> 16) & 0xff; - int g2 = (rgbPalette[i] >> 8) & 0xff; - int b2 = rgbPalette[i] & 0xff; - da = (a - a2) * 48; - dr = (r - r2) * 14; - dg = (g - g2) * 28; - db = (b - b2) * 6; - int dist = da*da + dr*dr + dg*dg + db*db; - if (dist < minDist) { - minDist = dist; - index = i; - } + int retVal = -1; + if (palette == null) { + return retVal; + } + + if (calculator == null) { + calculator = COLOR_DISTANCE_ARGB; + } + alphaWeight = Math.max(0.0, Math.min(2.0, alphaWeight)); + double minDist = Double.MAX_VALUE; + for (int i = 0; i < palette.length; i++) { + double dist = calculator.calculate(argb, palette[i], alphaWeight); + if (dist < minDist) { + minDist = dist; + retVal = i; } } - return index; + + return retVal; } /** @@ -293,25 +377,28 @@ public static void sortPalette(int[] palette, int startIndex, SortType type, boo switch (type) { case Lightness: - Arrays.sort(tmpColors, startIndex, tmpColors.length, new CompareByLightness()); + Arrays.sort(tmpColors, startIndex, tmpColors.length, CompareByLightness); break; case Saturation: - Arrays.sort(tmpColors, startIndex, tmpColors.length, new CompareBySaturation()); + Arrays.sort(tmpColors, startIndex, tmpColors.length, CompareBySaturation); break; case Hue: - Arrays.sort(tmpColors, startIndex, tmpColors.length, new CompareByHue()); + Arrays.sort(tmpColors, startIndex, tmpColors.length, CompareByHue); break; case Red: - Arrays.sort(tmpColors, startIndex, tmpColors.length, new CompareByRed()); + Arrays.sort(tmpColors, startIndex, tmpColors.length, CompareByRed); break; case Green: - Arrays.sort(tmpColors, startIndex, tmpColors.length, new CompareByGreen()); + Arrays.sort(tmpColors, startIndex, tmpColors.length, CompareByGreen); break; case Blue: - Arrays.sort(tmpColors, startIndex, tmpColors.length, new CompareByBlue()); + Arrays.sort(tmpColors, startIndex, tmpColors.length, CompareByBlue); break; case Alpha: - Arrays.sort(tmpColors, startIndex, tmpColors.length, new CompareByAlpha()); + Arrays.sort(tmpColors, startIndex, tmpColors.length, CompareByAlpha); + break; + case Lab: + Arrays.sort(tmpColors, startIndex, tmpColors.length, CompareByLabL); break; default: break; @@ -331,6 +418,208 @@ public static void sortPalette(int[] palette, int startIndex, SortType type, boo } } + /** + * Converts a single RGB value into the CIELAB colorspace. + * @param rgb The RGB value to convert. + * @return the converted color value in CIELAB colorspace. Order: L, a, b, alpha + * where L range is [0.0, 100.0], a and b are open-ended (usually between -150 and 150). + */ + public static Triple convertRGBtoLab(int rgb) + { + Integer key = Integer.valueOf(rgb & 0xffffff); + Triple retVal = ARGB_LAB_CACHE.get(key); + + if (retVal == null) { + int red = (rgb >> 16) & 0xff; + int green = (rgb >> 8) & 0xff; + int blue = rgb & 0xff; + + // 1. Linearize RGB + double r = (double)red / 255.0; + double g = (double)green / 255.0; + double b = (double)blue / 255.0; + + // 2. Convert to CIEXYZ + r = (r > 0.04045) ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92; + g = (g > 0.04045) ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92; + b = (b > 0.04045) ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92; + r *= 100.0; + g *= 100.0; + b *= 100.0; + double x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805); + double y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722); + double z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505); + + // 3. Convert to Lab + x /= 95.047; + y /= 100.0; + z /= 108.883; + x = (x > 0.008856) ? Math.pow(x, 1.0 / 3.0) : (7.787 * x) + (16.0 / 116.0); + y = (y > 0.008856) ? Math.pow(y, 1.0 / 3.0) : (7.787 * y) + (16.0 / 116.0); + z = (z > 0.008856) ? Math.pow(z, 1.0 / 3.0) : (7.787 * z) + (16.0 / 116.0); + Double dstL = Double.valueOf((116.0 * y) - 16.0); + Double dstA = Double.valueOf(500.0 * (x - y)); + Double dstB = Double.valueOf(200.0 * (y - z)); + retVal = Triple.with(dstL, dstA, dstB); + + ARGB_LAB_CACHE.put(key, retVal); + } + + return retVal; + } + + /** + * Converts a color entry in CIELAB colorspace into an RGB value. Alpha part is set to 0. + * @param L the L (lightness) value in range [0.0, 100.0]. + * @param a the a axis (green-red) value. + * @param b the b axis (blue-yellow) value. + * @return RGB value where blue is located in the lowest byte, followed by green and red. Highest byte is set to 0. + */ + public static int convertLabToRGB(double L, double a, double b) + { + // 1. Convert to CIEXYZ + double y = (L + 16.0) / 116.0; + double x = (a / 500.0) + y; + double z = y - (b / 200.0); + + double d = Math.pow(y, 3.0); + y = (d > 0.008856) ? d : (y - (16.0 / 116.0)) / 7.787; + d = Math.pow(x, 3.0); + x = (d > 0.008856) ? d : (x - (16.0 / 116.0)) / 7.787; + d = Math.pow(z, 3.0); + z = (d > 0.008856) ? d : (z - (16.0 / 116.0)) / 7.787; + + x *= 95.047; + y *= 100.0; + z *= 108.883; + + // 2. Convert to linear RGB + x /= 100.0; + y /= 100.0; + z /= 100.0; + double red = (x * 3.2406) + (y * -1.5372) + (z * -0.4986); + double green = (x * -0.9689) + (y * 1.8758) + (z * 0.0415); + double blue = (x * 0.0557) + (y * -0.2040) + (z * 1.0570); + + red = (red > 0.0031308) ? 1.055 * Math.pow(red, 1.0 / 2.4) - 0.055 : 12.92 * red; + green = (green > 0.0031308) ? 1.055 * Math.pow(green, 1.0 / 2.4) - 0.055 : 12.92 * green; + blue = (blue > 0.0031308) ? 1.055 * Math.pow(blue, 1.0 / 2.4) - 0.055 : 12.92 * blue; + + // convert to non-linear RGB + int retVal = 0; + retVal |= Math.max(0, Math.min(255, (int)(red * 255.0))); + retVal <<= 8; + retVal |= Math.max(0, Math.min(255, (int)(green * 255.0))); + retVal <<= 8; + retVal |= Math.max(0, Math.min(255, (int)(blue * 255.0))); + + return retVal; + } + + /** + * Converts a ARGB palette to a palette in CIELAB colorspace. The returned array contains four components per + * color entry: L, a, b and alpha. L range is [0.0, 100.0], a and b are open-ended (usually between -150 and 150), + * alpha range is [0.0, 255.0]. + * @param palette Palette with ARGB color entries + * @return array with CIELAB color entries plus alpha component. + */ + public static double[] convertRGBtoLabPalette(int[] palette) + { + double[] retVal = null; + if (palette == null) { + return retVal; + } + + retVal = new double[palette.length * 4]; + for (int i = 0; i < palette.length; i++) { + int a = (palette[i] >> 24) & 0xff; + Triple entry = convertRGBtoLab(palette[i]); + retVal[i * 4] = entry.getValue0().doubleValue(); + retVal[i * 4 + 1] = entry.getValue1().doubleValue(); + retVal[i * 4 + 2] = entry.getValue2().doubleValue(); + retVal[i * 4 + 3] = (double)a; + } + + return retVal; + } + + /** + * Converts a palette in CIELAB colorspace plus alpha into a ARGB palette. + * @param palette Palette with L, a, b and alpha component per color entry. + * @return array with ARGB color entries packed into single integer per entry. + */ + public static int[] convertLabToRGBPalette(double[] palette) + { + int[] retVal = null; + if (palette == null) { + return retVal; + } + + retVal = new int[palette.length / 4]; + for (int i = 0; i < retVal.length; i++) { + double L = palette[i * 4]; + double a = palette[i * 4 + 1]; + double b = palette[i * 4 + 2]; + + int rgba = convertLabToRGB(L, a, b); + int alpha = Math.max(0, Math.min(255, (int)palette[i * 4 + 3])); + rgba |= (alpha << 24); + retVal[i] = rgba; + } + + return retVal; + } + + /** Calculates the distance between two CIELAB colors based on CIE94 formula. */ + public static double getColorDistanceLabCIE94(double L1, double a1, double b1, double alpha1, + double L2, double a2, double b2, double alpha2) + { + final double kl = 1.0; + final double k1 = 0.045; + final double k2 = 0.015; + + double deltaL = L1 - L2; + double deltaA = a1 - a2; + double deltaB = b1 - b2; + + double c1 = Math.sqrt(a1*a1 + b1*b1); + double c2 = Math.sqrt(a2*a2 + b2*b2); + double deltaC = c1 - c2; + + double deltaH = deltaA*deltaA + deltaB*deltaB - deltaC*deltaC; + deltaH = (deltaH < 0.0) ? 0.0 : Math.sqrt(deltaH); + + double sc = 1.0 + k1*c1; + double sh = 1.0 + k2*c1; + + double i = Math.pow(deltaL / kl, 2.0) + + Math.pow(deltaC / sc, 2.0) + + Math.pow(deltaH / sh, 2.0) + + (alpha1 - alpha2)*(alpha1 - alpha2); + + return (i < 0.0) ? 0.0 : Math.sqrt(i); + } + + /** + * Returns the distance between the two ARGB values using CIELAB colorspace and CIE94 formula. + * @param argb1 the first ARGB color entry. + * @param argb2 the second ARGB color entry. + * @param alphaWeight Weight factor of the alpha component. Supported range: [0.0, 2.0]. + * A value < 1.0 makes alpha less important for the distance calculation. + * A value > 1.0 makes alpha more important for the distance calculation. + * Specify 0.0 to ignore the alpha part in the calculation. + */ + public static double getRGBColorDistanceLabCIE94(int argb1, int argb2, double alphaWeight) + { + Triple lab1 = convertRGBtoLab(argb1); + Triple lab2 = convertRGBtoLab(argb2); + alphaWeight = Math.max(0.0, Math.min(2.0, alphaWeight)); + double alpha1 = (double)((argb1 >> 24) & 0xff) * alphaWeight; + double alpha2 = (double)((argb2 >> 24) & 0xff) * alphaWeight; + return getColorDistanceLabCIE94(lab1.getValue0().doubleValue(), lab1.getValue1().doubleValue(), lab1.getValue2().doubleValue(), alpha1, + lab2.getValue0().doubleValue(), lab2.getValue1().doubleValue(), lab2.getValue2().doubleValue(), alpha2); + } + // Returns each color component as float array {b, g, r, a} in range [0.0, 1.0]. private static double[] getNormalizedColor(int color) { @@ -378,18 +667,16 @@ public static boolean medianCut(int[] pixels, int desiredColors, int[] palette, throw new NullPointerException(); if (desiredColors > 0 && desiredColors <= MAX_COLORS && palette.length >= desiredColors) { - PriorityQueue blockQueue = + final PriorityQueue blockQueue = new PriorityQueue(desiredColors, PixelBlock.PixelBlockComparator); - Pixel[] p = null; - p = new Pixel[pixels.length]; + final Pixel[] p = new Pixel[pixels.length]; int mask = ignoreAlpha ? 0xff000000: 0; - p = new Pixel[pixels.length]; for (int i = 0; i < p.length; i++) { p[i] = new Pixel(pixels[i] | mask); } - PixelBlock initialBlock = new PixelBlock(p); + final PixelBlock initialBlock = new PixelBlock(p); initialBlock.shrink(); blockQueue.add(initialBlock); while (blockQueue.size() < desiredColors) { @@ -400,8 +687,8 @@ public static boolean medianCut(int[] pixels, int desiredColors, int[] palette, Arrays.sort(longestBlock.getPixels(), longestBlock.offset(), longestBlock.offset() + longestBlock.size(), Pixel.PixelComparator.get(longestBlock.getLongestSideIndex())); - PixelBlock block1 = new PixelBlock(longestBlock.getPixels(), ofsBegin, ofsMedian - ofsBegin); - PixelBlock block2 = new PixelBlock(longestBlock.getPixels(), ofsMedian, ofsEnd - ofsMedian); + final PixelBlock block1 = new PixelBlock(longestBlock.getPixels(), ofsBegin, ofsMedian - ofsBegin); + final PixelBlock block2 = new PixelBlock(longestBlock.getPixels(), ofsMedian, ofsEnd - ofsMedian); block1.shrink(); block2.shrink(); blockQueue.add(block1); @@ -410,14 +697,14 @@ public static boolean medianCut(int[] pixels, int desiredColors, int[] palette, int palIndex = 0; while (!blockQueue.isEmpty() && palIndex < desiredColors) { - PixelBlock block = blockQueue.poll(); - int[] sum = {0, 0, 0, 0}; + final PixelBlock block = blockQueue.poll(); + final int[] sum = {0, 0, 0, 0}; for (int i = 0; i < block.size(); i++) { for (int j = 0; j < Pixel.MAX_SIZE; j++) { sum[j] += block.getPixel(i).getElement(j); } } - Pixel avgPixel = new Pixel(); + final Pixel avgPixel = new Pixel(); if (block.size() > 0) { for (int i = 0; i < Pixel.MAX_SIZE; i++) { avgPixel.color[i] = (byte)(sum[i] / block.size()); @@ -736,6 +1023,25 @@ private static BufferedImage convertToIndexedImage(BufferedImage image) //-------------------------- INNER CLASSES -------------------------- + /** + * Represents a function to calculate the distance between two ARGB color values. + */ + public interface ColorDistanceFunc + { + /** + * Performs a calculation to determine the distance between the specified ARGB color values. The third argument + * indicates how much influence the alpha component should have. + * @param argb1 the first ARGB color value. + * @param argb2 the second ARGB color value + * @param alphaWeight Weight factor of the alpha component. Supported range: [0.0, 2.0]. + * A value < 1.0 makes alpha less important for the distance calculation. + * A value > 1.0 makes alpha more important for the distance calculation. + * Specify 0.0 to completely ignore the alpha part in the calculation. + * @return the relative distance between the two color values. + */ + double calculate(int argb1, int argb2, double alphaWeight); + } + private static class PixelBlock { private final Pixel minCorner, maxCorner; @@ -755,8 +1061,8 @@ public PixelBlock(Pixel[] pixels, int ofs, int len) this.pixels = pixels; this.ofs = ofs; this.len = len; - minCorner = new Pixel(Byte.MIN_VALUE, Byte.MIN_VALUE, Byte.MIN_VALUE, Byte.MIN_VALUE); - maxCorner = new Pixel(Byte.MAX_VALUE, Byte.MAX_VALUE, Byte.MAX_VALUE, Byte.MAX_VALUE); + minCorner = new Pixel(0); + maxCorner = new Pixel(0xffffffff); } public Pixel[] getPixels() @@ -825,13 +1131,9 @@ public void shrink() } } - public static Comparator PixelBlockComparator = new Comparator() { - @Override - public int compare(PixelBlock pb1, PixelBlock pb2) - { - // inverting natural order by switching sides - return pb2.getLongestSideLength() - pb1.getLongestSideLength(); - } + public static final Comparator PixelBlockComparator = (pb1, pb2) -> { + // inverting natural order by switching sides + return pb2.getLongestSideLength() - pb1.getLongestSideLength(); }; } @@ -853,11 +1155,6 @@ public Pixel(int color) (byte)(color & 0xff)}; } - public Pixel(byte r, byte g, byte b, byte a) - { - this.color = new byte[]{a, r, g, b}; - } - public int toColor() { return ((color[0] & 0xff) << 24) | ((color[1] & 0xff) << 16) | ((color[2] & 0xff) << 8) | (color[3] & 0xff); @@ -872,45 +1169,17 @@ public int getElement(int index) } } - public static List> PixelComparator = new ArrayList>(MAX_SIZE); - static { - PixelComparator.add(new Comparator() { - @Override - public int compare(Pixel p1, Pixel p2) - { - return p1.getElement(0) - p2.getElement(0); - } - }); - PixelComparator.add(new Comparator() { - @Override - public int compare(Pixel p1, Pixel p2) - { - return p1.getElement(1) - p2.getElement(1); - } - }); - PixelComparator.add(new Comparator() { - @Override - public int compare(Pixel p1, Pixel p2) - { - return p1.getElement(2) - p2.getElement(2); - } - }); - PixelComparator.add(new Comparator() { - @Override - public int compare(Pixel p1, Pixel p2) - { - return p1.getElement(3) - p2.getElement(3); - } - }); - } + public static final List> PixelComparator = new ArrayList>(MAX_SIZE) {{ + add((p1, p2) -> p1.getElement(0) - p2.getElement(0)); + add((p1, p2) -> p1.getElement(1) - p2.getElement(1)); + add((p1, p2) -> p1.getElement(2) - p2.getElement(2)); + add((p1, p2) -> p1.getElement(3) - p2.getElement(3)); + }}; } // Compare colors by perceived lightness. - private static class CompareByLightness implements Comparator { - @Override - public int compare(Integer c1, Integer c2) - { + private static final Comparator CompareByLightness = (c1, c2) -> { Integer[] colors = new Integer[] {c1, c2}; double[] dist = new double[colors.length]; for (int i = 0; i < colors.length; i++) { @@ -923,14 +1192,10 @@ public int compare(Integer c1, Integer c2) dist[i] = Math.sqrt(b + g + r + a); } return (dist[0] < dist[1]) ? -1 : ((dist[0] > dist[1]) ? 1 : 0); - } - } + }; // Compare colors by saturation. - private static class CompareBySaturation implements Comparator { - @Override - public int compare(Integer c1, Integer c2) - { + private static final Comparator CompareBySaturation = (c1, c2) -> { Integer[] colors = new Integer[] {c1, c2}; double[] dist = new double[colors.length]; for (int i = 0; i < colors.length; i++) { @@ -950,14 +1215,10 @@ public int compare(Integer c1, Integer c2) dist[i] = s; } return (dist[0] < dist[1]) ? -1 : ((dist[0] > dist[1]) ? 1 : 0); - } - } + }; // Compare colors by hue. - private static class CompareByHue implements Comparator { - @Override - public int compare(Integer c1, Integer c2) - { + private static final Comparator CompareByHue = (c1, c2) -> { Integer[] colors = new Integer[] {c1, c2}; double[] dist = new double[colors.length]; for (int i = 0; i < colors.length; i++) { @@ -988,50 +1249,49 @@ public int compare(Integer c1, Integer c2) dist[i] = h; } return (dist[0] < dist[1]) ? -1 : ((dist[0] > dist[1]) ? 1 : 0); - } - } + }; // Compare colors by red amount. - private static class CompareByRed implements Comparator { - @Override - public int compare(Integer c1, Integer c2) - { + private static final Comparator CompareByRed = (c1, c2) -> { int dist1 = (c1 >>> 16) & 0xff; int dist2 = (c2 >>> 16) & 0xff; return dist1 - dist2; - } - } + }; // Compare colors by green amount. - private static class CompareByGreen implements Comparator { - @Override - public int compare(Integer c1, Integer c2) - { + private static final Comparator CompareByGreen = (c1, c2) -> { int dist1 = (c1 >>> 8) & 0xff; int dist2 = (c2 >>> 8) & 0xff; return dist1 - dist2; - } - } + }; // Compare colors by blue amount. - private static class CompareByBlue implements Comparator { - @Override - public int compare(Integer c1, Integer c2) - { + private static final Comparator CompareByBlue = (c1, c2) -> { int dist1 = c1 & 0xff; int dist2 = c2 & 0xff; return dist1 - dist2; - } - } + }; // Compare colors by alpha. - private static class CompareByAlpha implements Comparator { - @Override - public int compare(Integer c1, Integer c2) - { - int dist1 = (c1 >>> 24) & 0xff; - int dist2 = (c2 >>> 24) & 0xff; - return dist1 - dist2; + private static final Comparator CompareByAlpha = (c1, c2) -> { + int dist1 = (c1 >>> 24) & 0xff; + int dist2 = (c2 >>> 24) & 0xff; + return dist1 - dist2; + }; + + // Compare colors by CIELAB L component. + private static final Comparator CompareByLabL = (c1, c2) -> { + Triple dist1 = convertRGBtoLab(c1); + Triple dist2 = convertRGBtoLab(c2); + if (dist1.getValue0() < dist2.getValue0()) { + return -1; + } else if (dist1.getValue0() > dist2.getValue0()) { + return 1; + } else { + return 0; } - } +// int dist1 = (c1 >>> 24) & 0xff; +// int dist2 = (c2 >>> 24) & 0xff; +// return dist1 - dist2; + }; } diff --git a/src/org/infinity/resource/graphics/MosResource.java b/src/org/infinity/resource/graphics/MosResource.java index e49d9477b..fc284a6dd 100644 --- a/src/org/infinity/resource/graphics/MosResource.java +++ b/src/org/infinity/resource/graphics/MosResource.java @@ -531,7 +531,7 @@ private byte[] convertToMosV1(boolean compressed) throws Exception if (palIndex != null) { tileData[i] = (byte)(palIndex + 1); } else { - byte color = (byte)ColorConvert.nearestColorRGB(pixels[i], palette, true); + byte color = (byte)ColorConvert.getNearestColor(pixels[i], palette, 0.0, ColorConvert.COLOR_DISTANCE_CIE94); tileData[i] = (byte)(color + 1); colorCache.put(pixels[i], color); } diff --git a/src/org/infinity/resource/graphics/TisResource.java b/src/org/infinity/resource/graphics/TisResource.java index 0830352da..0c41953e2 100644 --- a/src/org/infinity/resource/graphics/TisResource.java +++ b/src/org/infinity/resource/graphics/TisResource.java @@ -721,7 +721,7 @@ public Status convertToPaletteTis(Path output, boolean showProgress) if (palIndex != null) { tileData[i] = (byte)(palIndex + 1); } else { - byte color = (byte)ColorConvert.nearestColorRGB(pixels[i], palette, true); + byte color = (byte)ColorConvert.getNearestColor(pixels[i], palette, 0.0, ColorConvert.COLOR_DISTANCE_CIE94); tileData[i] = (byte)(color + 1); colorCache.put(pixels[i], color); } From 8f5f47a02580f8de728ce34aa34c0a1f9528a83a Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Wed, 24 Mar 2021 22:23:11 +0100 Subject: [PATCH 078/158] BAM Converter: Add new color filter "CIELAB (L/a/b)" --- .../gui/converter/BamFilterColorLab.java | 462 ++++++++++++++++++ .../gui/converter/BamFilterFactory.java | 3 + 2 files changed, 465 insertions(+) create mode 100644 src/org/infinity/gui/converter/BamFilterColorLab.java diff --git a/src/org/infinity/gui/converter/BamFilterColorLab.java b/src/org/infinity/gui/converter/BamFilterColorLab.java new file mode 100644 index 000000000..6e35b2cd4 --- /dev/null +++ b/src/org/infinity/gui/converter/BamFilterColorLab.java @@ -0,0 +1,462 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2005 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.gui.converter; + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.image.BufferedImage; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.DataBufferInt; +import java.awt.image.IndexColorModel; + +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSlider; +import javax.swing.JSpinner; +import javax.swing.SpinnerNumberModel; +import javax.swing.SwingConstants; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.infinity.gui.ButtonPopupWindow; +import org.infinity.gui.ViewerUtil; +import org.infinity.icon.Icons; +import org.infinity.resource.graphics.ColorConvert; +import org.infinity.resource.graphics.PseudoBamDecoder.PseudoBamFrameEntry; +import org.infinity.util.tuples.Triple; + +/** + * ColorFilter: adjust CIELAB values L, a and b. + */ +public class BamFilterColorLab extends BamFilterBaseColor + implements ChangeListener, ActionListener +{ + private static final String FilterName = "CIELAB (L/a/b)"; + private static final String FilterDesc = "This filter provides controls for adjusting values in the CIELAB color space"; + + private static final int LAB_L_MIN = -127; + private static final int LAB_L_MAX = 127; + private static final int LAB_A_MIN = -255; + private static final int LAB_A_MAX = 255; + private static final int LAB_B_MIN = -255; + private static final int LAB_B_MAX = 255; + + private JSlider sliderL, sliderA, sliderB; + private JSpinner spinnerL, spinnerA, spinnerB; + private ButtonPopupWindow bpwExclude; + private BamFilterBaseColor.ExcludeColorsPanel pExcludeColors; + + public static String getFilterName() { return FilterName; } + public static String getFilterDesc() { return FilterDesc; } + + public BamFilterColorLab(ConvertToBam parent) + { + super(parent, FilterName, FilterDesc); + } + + @Override + public BufferedImage process(BufferedImage frame) throws Exception + { + return applyEffect(frame); + } + + @Override + public PseudoBamFrameEntry updatePreview(PseudoBamFrameEntry entry) + { + if (entry != null) { + entry.setFrame(applyEffect(entry.getFrame())); + } + return entry; + } + + @Override + public void updateControls() + { + bpwExclude.setEnabled(getConverter().isBamV1Selected()); + } + + @Override + public String getConfiguration() + { + StringBuilder sb = new StringBuilder(); + sb.append(sliderL.getValue()).append(';'); + sb.append(sliderA.getValue()).append(';'); + sb.append(sliderB.getValue()).append(';'); + sb.append(encodeColorList(pExcludeColors.getSelectedIndices())); + return sb.toString(); + } + + @Override + public boolean setConfiguration(String config) + { + if (config != null) { + config = config.trim(); + if (!config.isEmpty()) { + String[] params = config.trim().split(";"); + Integer lValue = Integer.MIN_VALUE; + Integer aValue = Integer.MIN_VALUE; + Integer bValue = Integer.MIN_VALUE; + int[] indices = null; + + // parsing configuration data + if (params.length > 0) { // set L value + lValue = decodeNumber(params[0], sliderL.getMinimum(), sliderL.getMaximum(), Integer.MIN_VALUE); + if (lValue == Integer.MIN_VALUE) { + return false; + } + } + if (params.length > 1) { // set A value + aValue = decodeNumber(params[1], sliderA.getMinimum(), sliderA.getMaximum(), Integer.MIN_VALUE); + if (aValue == Integer.MIN_VALUE) { + return false; + } + } + if (params.length > 2) { // set B value + bValue = decodeNumber(params[2], sliderB.getMinimum(), sliderB.getMaximum(), Integer.MIN_VALUE); + if (bValue == Integer.MIN_VALUE) { + return false; + } + } + if (params.length > 3) { + indices = decodeColorList(params[3]); + if (indices == null) { + return false; + } + } + + // applying configuration data + if (lValue != Integer.MIN_VALUE) { + sliderL.setValue(lValue); + } + if (aValue != Integer.MIN_VALUE) { + sliderA.setValue(aValue); + } + if (bValue != Integer.MIN_VALUE) { + sliderB.setValue(bValue); + } + if (indices != null) { + pExcludeColors.setSelectedIndices(indices); + } + } + return true; + } + return false; + } + + @Override + protected JPanel loadControls() + { + GridBagConstraints c = new GridBagConstraints(); + + JLabel l1 = new JLabel("Exclude colors:"); + pExcludeColors = new BamFilterBaseColor.ExcludeColorsPanel( + getConverter().getPaletteDialog().getPalette(getConverter().getPaletteDialog().getPaletteType())); + pExcludeColors.addChangeListener(this); + bpwExclude = new ButtonPopupWindow("Palette", Icons.getIcon(Icons.ICON_ARROW_DOWN_15), pExcludeColors); + bpwExclude.setIconTextGap(8); + bpwExclude.addActionListener(this); + bpwExclude.setEnabled(getConverter().isBamV1Selected()); + JPanel pExclude = new JPanel(new GridBagLayout()); + ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); + pExclude.add(l1, c); + ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.NONE, new Insets(0, 8, 0, 0), 0, 0); + pExclude.add(bpwExclude, c); + ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); + pExclude.add(new JPanel(), c); + + JLabel lh = new JLabel("L:"); + JLabel ls = new JLabel("a:"); + JLabel ll = new JLabel("b:"); + sliderL = new JSlider(SwingConstants.HORIZONTAL, LAB_L_MIN, LAB_L_MAX, 0); + sliderL.addChangeListener(this); + sliderA = new JSlider(SwingConstants.HORIZONTAL, LAB_A_MIN, LAB_A_MAX, 0); + sliderA.addChangeListener(this); + sliderB = new JSlider(SwingConstants.HORIZONTAL, LAB_B_MIN, LAB_B_MAX, 0); + sliderB.addChangeListener(this); + spinnerL = new JSpinner(new SpinnerNumberModel(sliderL.getValue(), + sliderL.getMinimum(), + sliderL.getMaximum(), 1)); + spinnerL.addChangeListener(this); + spinnerA = new JSpinner(new SpinnerNumberModel(sliderA.getValue(), + sliderA.getMinimum(), + sliderA.getMaximum(), 1)); + spinnerA.addChangeListener(this); + spinnerB = new JSpinner(new SpinnerNumberModel(sliderB.getValue(), + sliderB.getMinimum() , + sliderB.getMaximum(), 1)); + spinnerB.addChangeListener(this); + + JPanel p = new JPanel(new GridBagLayout()); + ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); + p.add(lh, c); + ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(0, 4, 0, 0), 0, 0); + p.add(sliderL, c); + ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(0, 4, 0, 0), 0, 0); + p.add(spinnerL, c); + + ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.NONE, new Insets(4, 0, 0, 0), 0, 0); + p.add(ls, c); + ViewerUtil.setGBC(c, 1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(4, 4, 0, 0), 0, 0); + p.add(sliderA, c); + ViewerUtil.setGBC(c, 2, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(4, 4, 0, 0), 0, 0); + p.add(spinnerA, c); + + ViewerUtil.setGBC(c, 0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.NONE, new Insets(4, 0, 0, 0), 0, 0); + p.add(ll, c); + ViewerUtil.setGBC(c, 1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(4, 4, 0, 0), 0, 0); + p.add(sliderB, c); + ViewerUtil.setGBC(c, 2, 2, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(4, 4, 0, 0), 0, 0); + p.add(spinnerB, c); + ViewerUtil.setGBC(c, 0, 3, 3, 1, 0.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); + p.add(pExclude, c); + + JPanel panel = new JPanel(new GridBagLayout()); + ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, + GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); + panel.add(p, c); + + return panel; + } + +//--------------------- Begin Interface ChangeListener --------------------- + + @Override + public void stateChanged(ChangeEvent event) + { + if (event.getSource() == pExcludeColors) { + fireChangeListener(); + } else if (event.getSource() == sliderL) { + spinnerL.setValue(Integer.valueOf(sliderL.getValue())); + if (sliderL.getModel().getValueIsAdjusting() == false) { + fireChangeListener(); + } + } else if (event.getSource() == sliderA) { + spinnerA.setValue(Integer.valueOf(sliderA.getValue())); + if (sliderA.getModel().getValueIsAdjusting() == false) { + fireChangeListener(); + } + } else if (event.getSource() == sliderB) { + spinnerB.setValue(Integer.valueOf(sliderB.getValue())); + if (sliderB.getModel().getValueIsAdjusting() == false) { + fireChangeListener(); + } + } else if (event.getSource() == spinnerL) { + sliderL.setValue(((Integer)spinnerL.getValue()).intValue()); + } else if (event.getSource() == spinnerA) { + sliderA.setValue(((Integer)spinnerA.getValue()).intValue()); + } else if (event.getSource() == spinnerB) { + sliderB.setValue(((Integer)spinnerB.getValue()).intValue()); + } + } + +//--------------------- End Interface ChangeListener --------------------- + +//--------------------- Begin Interface ActionListener --------------------- + + @Override + public void actionPerformed(ActionEvent event) + { + if (event.getSource() == bpwExclude) { + pExcludeColors.updatePalette(getConverter().getPaletteDialog().getPalette( + getConverter().getPaletteDialog().getPaletteType())); + } + } + +//--------------------- End Interface ActionListener --------------------- + + private BufferedImage applyEffect(BufferedImage srcImage) + { + if (srcImage != null) { + int[] buffer; + IndexColorModel cm = null; + boolean isPremultiplied = false; + if (srcImage.getType() == BufferedImage.TYPE_BYTE_INDEXED) { + // paletted image + cm = (IndexColorModel)srcImage.getColorModel(); + buffer = new int[1 << cm.getPixelSize()]; + cm.getRGBs(buffer); + isPremultiplied = cm.isAlphaPremultiplied(); + // applying proper alpha + if (!cm.hasAlpha()) { + final int Green = 0x0000ff00; + boolean greenFound = false; + for (int i = 0; i < buffer.length; i++) { + if (!greenFound && buffer[i] == Green) { + greenFound = true; + buffer[i] &= 0x00ffffff; + } else { + buffer[i] |= 0xff000000; + } + } + } + } else if (srcImage.getRaster().getDataBuffer().getDataType() == DataBuffer.TYPE_INT) { + // truecolor image + buffer = ((DataBufferInt)srcImage.getRaster().getDataBuffer()).getData(); + isPremultiplied = srcImage.isAlphaPremultiplied(); + } else { + buffer = new int[0]; + } + + // L hue in range [0, 100] + double labL = ((Integer)spinnerL.getValue()).doubleValue(); + // a in range [-160, 160] + double labA = ((Integer)spinnerA.getValue()).doubleValue(); + // b in range [-160, 160] + double labB = ((Integer)spinnerB.getValue()).doubleValue(); + + for (int i = 0; i < buffer.length; i++) { + if ((cm == null || (cm != null && !pExcludeColors.isSelectedIndex(i))) && + (buffer[i] & 0xff000000) != 0) { + // convert RGB -> Lab + int fa = isPremultiplied ? (buffer[i] >>> 24) & 0xff : 255; + int fr = ((buffer[i] >>> 16) & 0xff) * fa / 255; + int fg = ((buffer[i] >>> 8) & 0xff) * fa / 255; + int fb = (buffer[i] & 0xff) * fa / 255; + int rgb = (fr << 16) | (fg << 8) | fb; + Triple lab = ColorConvert.convertRGBtoLab(rgb); + double l = lab.getValue0().doubleValue(); + double a = lab.getValue1().doubleValue(); + double b = lab.getValue2().doubleValue(); + +// float fa = isPremultiplied ? (float)((buffer[i] >>> 24) & 0xff) : 255.0f; +// float fr = (float)((buffer[i] >>> 16) & 0xff) / fa; +// float fg = (float)((buffer[i] >>> 8) & 0xff) / fa; +// float fb = (float)(buffer[i] & 0xff) / fa; +// float cmin = fr; if (fg < cmin) cmin = fg; if (fb < cmin) cmin = fb; +// float cmax = fr; if (fg > cmax) cmax = fg; if (fb > cmax) cmax = fb; +// float cdelta = cmax - cmin; +// float cdelta2 = cdelta / 2.0f; +// float h, s, l; +// +// l = (cmax + cmin) / 2.0f; +// +// if (cdelta == 0.0f) { +// h = 0.0f; +// s = 0.0f; +// } else { +// if (l < 0.5f) { +// s = cdelta / (cmax + cmin); +// } else { +// s = cdelta / (2.0f - cmax - cmin); +// } +// +// float dr = (((cmax - fr) / 6.0f) + cdelta2) / cdelta; +// float dg = (((cmax - fg) / 6.0f) + cdelta2) / cdelta; +// float db = (((cmax - fb) / 6.0f) + cdelta2) / cdelta; +// +// if (fr == cmax) { +// h = db - dg; +// } else if (fg == cmax) { +// h = (1.0f / 3.0f) + dr - db; +// } else { +// h = (2.0f / 3.0f) + dg - dr; +// } +// +// if (h < 0.0f) h += 1.0f; else if (h > 1.0f) h -= 1.0f; +// } + + // applying adjustments + + l = Math.max((double)LAB_L_MIN, Math.min((double)LAB_L_MAX, l + labL)); + a = Math.max((double)LAB_A_MIN, Math.min((double)LAB_A_MAX, a + labA)); + b = Math.max((double)LAB_B_MIN, Math.min((double)LAB_B_MAX, b + labB)); + + // converting Lab -> RGB + rgb = ColorConvert.convertLabToRGB(l, a, b); + buffer[i] = (buffer[i] & 0xff000000) | rgb; + +// if (s == 0.0f) { +// // achromatic +// int v = (int)(l * 255.0f); +// buffer[i] = (buffer[i] & 0xff000000) | (v << 16) | (v << 8) | v; +// } else { +// float f2 = (l < 0.5f) ? l * (1.0f + s) : (l + s) - (s * l); +// float f1 = 2.0f * l - f2; +// float res; +// +// // red +// float t = h + (1.0f / 3.0f); +// if (t < 0.0f) t += 1.0f; else if (t > 1.0f) t -= 1.0f; +// if ((6.0f * t) < 1.0f) { +// res = f1 + (f2 - f1) * 6.0f * t; +// } else if ((2.0f * t) < 1.0f) { +// res = f2; +// } else if ((3.0f * t) < 2.0f) { +// res = f1 + (f2 - f1) * ((2.0f / 3.0f) - t) * 6.0f; +// } else { +// res = f1; +// } +// int r = (int)(res * fa); +// +// // green +// t = h; +// if ((6.0f * t) < 1.0f) { +// res = f1 + (f2 - f1) * 6.0f * t; +// } else if ((2.0f * t) < 1.0f) { +// res = f2; +// } else if ((3.0f * t) < 2.0f) { +// res = f1 + (f2 - f1) * ((2.0f / 3.0f) - t) * 6.0f; +// } else { +// res = f1; +// } +// int g = (int)(res * fa); +// +// // blue +// t = h - (1.0f / 3.0f); +// if (t < 0.0f) t += 1.0f; else if (t > 1.0f) t -= 1.0f; +// if ((6.0f * t) < 1.0f) { +// res = f1 + (f2 - f1) * 6.0f * t; +// } else if ((2.0f * t) < 1.0f) { +// res = f2; +// } else if ((3.0f * t) < 2.0f) { +// res = f1 + (f2 - f1) * ((2.0f / 3.0f) - t) * 6.0f; +// } else { +// res = f1; +// } +// int b = (int)(res * fa); +// +// if (r < 0) r = 0; else if (r > 255) r = 255; +// if (g < 0) g = 0; else if (g > 255) g = 255; +// if (b < 0) b = 0; else if (b > 255) b = 255; +// buffer[i] = (buffer[i] & 0xff000000) | (r << 16) | (g << 8) | b; +// } + } + } + + if (cm != null) { + // recreating paletted image + IndexColorModel cm2 = new IndexColorModel(cm.getPixelSize(), buffer.length, buffer, 0, + cm.hasAlpha(), cm.getTransparentPixel(), DataBuffer.TYPE_BYTE); + int width = srcImage.getWidth(); + int height = srcImage.getHeight(); + BufferedImage dstImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_INDEXED, cm2); + byte[] srcPixels = ((DataBufferByte)srcImage.getRaster().getDataBuffer()).getData(); + byte[] dstPixels = ((DataBufferByte)dstImage.getRaster().getDataBuffer()).getData(); + System.arraycopy(srcPixels, 0, dstPixels, 0, srcPixels.length); + srcImage = dstImage; + srcPixels = null; dstPixels = null; + } + } + + return srcImage; + } +} diff --git a/src/org/infinity/gui/converter/BamFilterFactory.java b/src/org/infinity/gui/converter/BamFilterFactory.java index a46ce83f8..077d0f983 100644 --- a/src/org/infinity/gui/converter/BamFilterFactory.java +++ b/src/org/infinity/gui/converter/BamFilterFactory.java @@ -21,6 +21,9 @@ public class BamFilterFactory FilterInfoList.add(new FilterInfo(BamFilterColorHSL.getFilterName(), BamFilterColorHSL.getFilterDesc(), BamFilterColorHSL.class)); + FilterInfoList.add(new FilterInfo(BamFilterColorLab.getFilterName(), + BamFilterColorLab.getFilterDesc(), + BamFilterColorLab.class)); FilterInfoList.add(new FilterInfo(BamFilterColorBalance.getFilterName(), BamFilterColorBalance.getFilterDesc(), BamFilterColorBalance.class)); From 54c040d32d52275adfc0c4a1e1ed29c2816bb5ef Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Thu, 25 Mar 2021 11:19:07 +0100 Subject: [PATCH 079/158] Disable CIELAB-based color reduction because of quality issues --- .../infinity/gui/converter/ConvertToBam.java | 4 +-- .../infinity/gui/converter/ConvertToMos.java | 21 ++++---------- .../infinity/gui/converter/ConvertToTis.java | 23 ++++----------- .../resource/graphics/BamResource.java | 4 +-- .../resource/graphics/ColorConvert.java | 28 +++++++++++++++---- .../resource/graphics/MosResource.java | 2 +- .../resource/graphics/TisResource.java | 2 +- 7 files changed, 38 insertions(+), 46 deletions(-) diff --git a/src/org/infinity/gui/converter/ConvertToBam.java b/src/org/infinity/gui/converter/ConvertToBam.java index 4012bd749..f9317c08a 100644 --- a/src/org/infinity/gui/converter/ConvertToBam.java +++ b/src/org/infinity/gui/converter/ConvertToBam.java @@ -4130,7 +4130,7 @@ private void updateFinalBamDecoder(int bamVersion) throws Exception dstBuf[ofs] = colIdx.byteValue();//(byte)ci; } else { double weight = getUseAlpha() ? 1.0 : 0.0; - byte color = (byte)ColorConvert.getNearestColor(srcBuf[ofs], palette, weight, ColorConvert.COLOR_DISTANCE_CIE94); + byte color = (byte)ColorConvert.getNearestColor(srcBuf[ofs], palette, weight, null); dstBuf[ofs] = color;//(byte)ci; colorCache.put(Integer.valueOf(c), Byte.valueOf(color)); } @@ -4223,7 +4223,7 @@ private void updateFinalBamFrame(int bamVersion, int frameIdx) dstBuf[ofs] = colIdx.byteValue(); } else { double weight = getUseAlpha() ? 1.0 : 0.0; - byte color = (byte)ColorConvert.getNearestColor(srcBuf[ofs], palette, weight, ColorConvert.COLOR_DISTANCE_CIE94); + byte color = (byte)ColorConvert.getNearestColor(srcBuf[ofs], palette, weight, null); dstBuf[ofs] = color;//(byte)ci; colorCache.put(Integer.valueOf(c), Byte.valueOf(color)); } diff --git a/src/org/infinity/gui/converter/ConvertToMos.java b/src/org/infinity/gui/converter/ConvertToMos.java index 9c6d0471b..98959f23e 100644 --- a/src/org/infinity/gui/converter/ConvertToMos.java +++ b/src/org/infinity/gui/converter/ConvertToMos.java @@ -75,7 +75,7 @@ public class ConvertToMos extends ChildFrame private JSpinner sPvrzIndex; private JLabel lPvrzInfo; private JComboBox cbCompression; - private JCheckBox cbCompress, cbColorConvert, cbCloseOnExit; + private JCheckBox cbCompress, cbCloseOnExit; private SwingWorker, Void> workerConvert; private WindowBlocker blocker; @@ -92,8 +92,7 @@ public class ConvertToMos extends ChildFrame * @return {@code true} if the conversion finished successfully, {@code false} otherwise. */ public static boolean convertV1(Component parent, BufferedImage img, String mosFileName, - boolean compressed, boolean fastConvert, List result, - boolean showProgress) + boolean compressed, List result, boolean showProgress) { // checking parameters if (result == null) { @@ -196,8 +195,7 @@ public static boolean convertV1(Component parent, BufferedImage img, String mosF if (palIndex != null) { tileData[i] = (byte)(palIndex + 1); } else { - ColorConvert.ColorDistanceFunc colorFunc = fastConvert ? ColorConvert.COLOR_DISTANCE_ARGB : ColorConvert.COLOR_DISTANCE_CIE94; - byte color = (byte)ColorConvert.getNearestColor(pixels[i], palette, 0.0, colorFunc); + byte color = (byte)ColorConvert.getNearestColor(pixels[i], palette, 0.0, null); tileData[i] = (byte)(color + 1); colorCache.put(pixels[i], color); } @@ -771,21 +769,13 @@ private void init() JPanel pOptionsV1 = new JPanel(new GridBagLayout()); pOptionsV1.setBorder(BorderFactory.createTitledBorder("Options ")); cbCompress = new JCheckBox("Compressed (MOSC)", false); - cbColorConvert = new JCheckBox("Fast conversion", false); - cbColorConvert.setToolTipText("Enable to use a faster but less accurate color reduction algorithm."); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(4, 0, 0, 0), 0, 0); pOptionsV1.add(cbCompress, c); c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 0, 0); pOptionsV1.add(new JPanel(), c); - c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, - GridBagConstraints.NONE, new Insets(4, 0, 0, 0), 0, 0); - pOptionsV1.add(cbColorConvert, c); - c = ViewerUtil.setGBC(c, 1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 0, 0); - pOptionsV1.add(new JPanel(), c); - c = ViewerUtil.setGBC(c, 0, 2, 2, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, + c = ViewerUtil.setGBC(c, 0, 1, 2, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0); pOptionsV1.add(new JPanel(), c); @@ -1024,11 +1014,10 @@ private List convert() // fetching remaining settings int pvrzIndex = getPvrzIndex(sPvrzIndex.getValue()); boolean isMOSC = cbCompress.isSelected(); - boolean isFastConversion = cbColorConvert.isSelected(); // converting if (tabPane.getSelectedIndex() == 0) { - convertV1(this, srcImage, tfOutputV1.getText(), isMOSC, isFastConversion, result, true); + convertV1(this, srcImage, tfOutputV1.getText(), isMOSC, result, true); } else if (tabPane.getSelectedIndex() == 1) { convertV2(this, srcImage, tfOutputV2.getText(), dxtType, pvrzIndex, result, true); } else { diff --git a/src/org/infinity/gui/converter/ConvertToTis.java b/src/org/infinity/gui/converter/ConvertToTis.java index f3183d382..4577f7d80 100644 --- a/src/org/infinity/gui/converter/ConvertToTis.java +++ b/src/org/infinity/gui/converter/ConvertToTis.java @@ -75,7 +75,7 @@ public class ConvertToTis extends ChildFrame private JButton bConvert, bCancel; private JButton bInput, bOutput, bVersionHelp; private JComboBox cbVersion; - private JCheckBox cbColorConvert, cbCloseOnExit; + private JCheckBox cbCloseOnExit; private SwingWorker, Void> workerConvert; private WindowBlocker blocker; @@ -92,7 +92,7 @@ public class ConvertToTis extends ChildFrame * @return {@code true} if the conversion finished successfully, {@code false} otherwise. */ public static boolean convertV1(Component parent, BufferedImage img, String tisFileName, int tileCount, - boolean fastConvert, List result, boolean showProgress) + List result, boolean showProgress) { // checking parameters if (result == null) { @@ -199,8 +199,7 @@ public static boolean convertV1(Component parent, BufferedImage img, String tisF if (palIndex != null) { tileData[i] = (byte)(palIndex + 1); } else { - ColorConvert.ColorDistanceFunc colorFunc = fastConvert ? ColorConvert.COLOR_DISTANCE_ARGB : ColorConvert.COLOR_DISTANCE_CIE94; - byte color = (byte)ColorConvert.getNearestColor(srcBlock[i], palette, 0.0, colorFunc); + byte color = (byte)ColorConvert.getNearestColor(srcBlock[i], palette, 0.0, null); tileData[i] = (byte)(color + 1); colorCache.put(srcBlock[i], color); } @@ -861,20 +860,11 @@ private void init() JLabel lVersion = new JLabel("TIS version:"); cbVersion = new JComboBox<>(new String[]{"Legacy", "PVRZ-based"}); cbVersion.setSelectedIndex(0); - cbVersion.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) - { - cbColorConvert.setEnabled(cbVersion.getSelectedIndex() == 0); - } - }); bVersionHelp = new JButton("?"); bVersionHelp.setToolTipText("About TIS versions"); bVersionHelp.addActionListener(this); bVersionHelp.setMargin(new Insets(bVersionHelp.getInsets().top, 4, bVersionHelp.getInsets().bottom, 4)); - cbColorConvert = new JCheckBox("Fast conversion"); - cbColorConvert.setToolTipText("(Legacy only) Enable to use a faster but less accurate color reduction algorithm."); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); @@ -885,10 +875,7 @@ public void actionPerformed(ActionEvent e) c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); pSubOptions.add(bVersionHelp, c); - c = ViewerUtil.setGBC(c, 3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, - GridBagConstraints.NONE, new Insets(0, 16, 0, 0), 0, 0); - pSubOptions.add(cbColorConvert, c); - c = ViewerUtil.setGBC(c, 4, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, + c = ViewerUtil.setGBC(c, 3, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); pSubOptions.add(new JPanel(), c); @@ -1114,7 +1101,7 @@ private List convert() convertV2(this, srcImage, outFileName, tileCount, ret, true); } else { // TIS V1 conversion - convertV1(this, srcImage, outFileName, tileCount, cbColorConvert.isSelected(), ret, true); + convertV1(this, srcImage, outFileName, tileCount, ret, true); } return ret; diff --git a/src/org/infinity/resource/graphics/BamResource.java b/src/org/infinity/resource/graphics/BamResource.java index 57d68cf31..3a77160b7 100644 --- a/src/org/infinity/resource/graphics/BamResource.java +++ b/src/org/infinity/resource/graphics/BamResource.java @@ -1121,7 +1121,7 @@ private byte[] convertToBamV1(boolean compressed) throws Exception dstData[dstIdx++] = (byte)(colIdx + colorShift); } else { double weight = ignoreAlpha ? 0.0 : 1.0; - int color = ColorConvert.getNearestColor(srcData[srcIdx], palette, weight, ColorConvert.COLOR_DISTANCE_CIE94); + int color = ColorConvert.getNearestColor(srcData[srcIdx], palette, weight, null); dstData[dstIdx++] = (byte)(color); if (color > 0) { colorCache.put(srcData[srcIdx], (byte)(color - colorShift)); @@ -1146,7 +1146,7 @@ private byte[] convertToBamV1(boolean compressed) throws Exception dstData[idx] = (byte)(colIdx + colorShift); } else { double weight = ignoreAlpha ? 0.0 : 1.0; - int color = ColorConvert.getNearestColor(srcData[idx], palette, weight, ColorConvert.COLOR_DISTANCE_CIE94); + int color = ColorConvert.getNearestColor(srcData[idx], palette, weight, null); dstData[idx] = (byte)(color); if (color > 0) { colorCache.put(srcData[idx], (byte)(color - colorShift)); diff --git a/src/org/infinity/resource/graphics/ColorConvert.java b/src/org/infinity/resource/graphics/ColorConvert.java index 67b1bdc4a..1d8c91961 100644 --- a/src/org/infinity/resource/graphics/ColorConvert.java +++ b/src/org/infinity/resource/graphics/ColorConvert.java @@ -55,11 +55,21 @@ public class ColorConvert int r1 = (argb1 >> 16) & 0xff; int g1 = (argb1 >> 8) & 0xff; int b1 = argb1 & 0xff; + if (a1 != 0xff) { + r1 = r1 * a1 / 255; + g1 = g1 * a1 / 255; + b1 = b1 * a1 / 255; + } int a2 = (argb2 >> 24) & 0xff; int r2 = (argb2 >> 16) & 0xff; int g2 = (argb2 >> 8) & 0xff; int b2 = argb2 & 0xff; + if (a2 != 0xff) { + r2 = r2 * a2 / 255; + g2 = g2 * a2 / 255; + b2 = b2 * a2 / 255; + } weight = Math.max(0.0, Math.min(2.0, weight)); double da = (double)(a1 - a2) * 48.0 * weight; @@ -420,19 +430,25 @@ public static void sortPalette(int[] palette, int startIndex, SortType type, boo /** * Converts a single RGB value into the CIELAB colorspace. - * @param rgb The RGB value to convert. + * @param argb The ARGB value to convert. * @return the converted color value in CIELAB colorspace. Order: L, a, b, alpha * where L range is [0.0, 100.0], a and b are open-ended (usually between -150 and 150). */ - public static Triple convertRGBtoLab(int rgb) + public static Triple convertRGBtoLab(int argb) { - Integer key = Integer.valueOf(rgb & 0xffffff); + Integer key = Integer.valueOf(argb & 0xffffff); Triple retVal = ARGB_LAB_CACHE.get(key); if (retVal == null) { - int red = (rgb >> 16) & 0xff; - int green = (rgb >> 8) & 0xff; - int blue = rgb & 0xff; + int alpha = (argb >> 24) & 0xff; + int red = (argb >> 16) & 0xff; + int green = (argb >> 8) & 0xff; + int blue = argb & 0xff; + if (alpha != 255) { + red = red * alpha / 255; + green = green * alpha / 255; + blue = blue * alpha / 255; + } // 1. Linearize RGB double r = (double)red / 255.0; diff --git a/src/org/infinity/resource/graphics/MosResource.java b/src/org/infinity/resource/graphics/MosResource.java index fc284a6dd..b308ca545 100644 --- a/src/org/infinity/resource/graphics/MosResource.java +++ b/src/org/infinity/resource/graphics/MosResource.java @@ -531,7 +531,7 @@ private byte[] convertToMosV1(boolean compressed) throws Exception if (palIndex != null) { tileData[i] = (byte)(palIndex + 1); } else { - byte color = (byte)ColorConvert.getNearestColor(pixels[i], palette, 0.0, ColorConvert.COLOR_DISTANCE_CIE94); + byte color = (byte)ColorConvert.getNearestColor(pixels[i], palette, 0.0, null); tileData[i] = (byte)(color + 1); colorCache.put(pixels[i], color); } diff --git a/src/org/infinity/resource/graphics/TisResource.java b/src/org/infinity/resource/graphics/TisResource.java index 0c41953e2..e7ee96707 100644 --- a/src/org/infinity/resource/graphics/TisResource.java +++ b/src/org/infinity/resource/graphics/TisResource.java @@ -721,7 +721,7 @@ public Status convertToPaletteTis(Path output, boolean showProgress) if (palIndex != null) { tileData[i] = (byte)(palIndex + 1); } else { - byte color = (byte)ColorConvert.getNearestColor(pixels[i], palette, 0.0, ColorConvert.COLOR_DISTANCE_CIE94); + byte color = (byte)ColorConvert.getNearestColor(pixels[i], palette, 0.0, null); tileData[i] = (byte)(color + 1); colorCache.put(pixels[i], color); } From add1d0dcf224724d4cf8ee47c5e4a85fc7913508 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Thu, 25 Mar 2021 20:57:41 +0100 Subject: [PATCH 080/158] Do not automatically maximize window of creature animation quick view --- src/org/infinity/resource/cre/ViewerAnimation.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/org/infinity/resource/cre/ViewerAnimation.java b/src/org/infinity/resource/cre/ViewerAnimation.java index 2ce8f8441..a16643e69 100644 --- a/src/org/infinity/resource/cre/ViewerAnimation.java +++ b/src/org/infinity/resource/cre/ViewerAnimation.java @@ -503,7 +503,6 @@ private void init() throws Exception setSize(NearInfinity.getInstance().getPreferredSize()); Center.center(this, NearInfinity.getInstance().getBounds()); - setExtendedState(NearInfinity.getInstance().getExtendedState() & ~ICONIFIED); setVisible(true); // loading animation sequence From 38710b6ea5ab66ace1d890b16e50498397f872d2 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Thu, 25 Mar 2021 23:30:57 +0100 Subject: [PATCH 081/158] Creature Animation Browser: Layout changes in creature selection panel - Renamed "allegiance" combobox to "status" combobox - Simplified allegiance entries to friendly, neutral, hostile - Moved "panicked" option to "status" combobox - Reduced minimum horizontal size of controls panel - Added option to toggle visibility of creature selection panel --- .../cre/viewer/CreatureControlModel.java | 27 +- .../cre/viewer/CreatureControlPanel.java | 348 +++++++----------- ...nceModel.java => CreatureStatusModel.java} | 101 ++--- 3 files changed, 199 insertions(+), 277 deletions(-) rename src/org/infinity/resource/cre/viewer/{CreatureAllegianceModel.java => CreatureStatusModel.java} (53%) diff --git a/src/org/infinity/resource/cre/viewer/CreatureControlModel.java b/src/org/infinity/resource/cre/viewer/CreatureControlModel.java index a85faaabb..d82266b62 100644 --- a/src/org/infinity/resource/cre/viewer/CreatureControlModel.java +++ b/src/org/infinity/resource/cre/viewer/CreatureControlModel.java @@ -9,8 +9,6 @@ import java.util.List; import java.util.Objects; -import javax.swing.JToggleButton.ToggleButtonModel; - import org.infinity.resource.Profile; import org.infinity.resource.cre.CreResource; import org.infinity.resource.cre.decoder.SpriteDecoder; @@ -18,7 +16,7 @@ import org.infinity.resource.cre.decoder.util.ItemInfo; import org.infinity.resource.cre.decoder.util.ItemInfo.ItemPredicate; import org.infinity.resource.cre.viewer.ColorSelectionModel.ColorEntry; -import org.infinity.resource.cre.viewer.CreatureAllegianceModel.AllegianceEntry; +import org.infinity.resource.cre.viewer.CreatureStatusModel.StatusEntry; import org.infinity.resource.cre.viewer.CreatureAnimationModel.AnimateEntry; import org.infinity.resource.cre.viewer.CreatureSelectionModel.CreatureItem; import org.infinity.resource.key.ResourceEntry; @@ -34,12 +32,11 @@ public class CreatureControlModel private CreatureSelectionModel modelCreSelection; private CreatureAnimationModel modelCreAnimation; - private CreatureAllegianceModel modelCreAllegiance; + private CreatureStatusModel modelCreAllegiance; private ItemSelectionModel modelItemHelmet; private ItemSelectionModel modelItemArmor; private ItemSelectionModel modelItemShield; private ItemSelectionModel modelItemWeapon; - private ToggleButtonModel modelPanic; private int hashCreature; private boolean canApply, canReset; @@ -231,17 +228,11 @@ public CreatureAnimationModel getModelAnimation() } /** Returns the model of the creature allegiance combobox. */ - public CreatureAllegianceModel getModelAllegiance() + public CreatureStatusModel getModelAllegiance() { return modelCreAllegiance; } - /** Returns the model of the panic checkbox. */ - public ToggleButtonModel getModelPanic() - { - return modelPanic; - } - /** Returns the model of the helmet combobox. */ public ItemSelectionModel getModelHelmet() { @@ -319,10 +310,10 @@ public AnimateEntry getSelectedAnimation() * Returns the {@code AnimateEntry} instance of the currently selected creature allegiance. * Returns {@code null} if entry is not available. */ - public AllegianceEntry getSelectedAllegiance() + public StatusEntry getSelectedAllegiance() { - if (modelCreAllegiance != null && modelCreAllegiance.getSelectedItem() instanceof AllegianceEntry) { - return (AllegianceEntry)modelCreAllegiance.getSelectedItem(); + if (modelCreAllegiance != null && modelCreAllegiance.getSelectedItem() instanceof StatusEntry) { + return (StatusEntry)modelCreAllegiance.getSelectedItem(); } else { return null; } @@ -409,9 +400,6 @@ public void creSelectionChanged() throws Exception int ea = creInfo.getAllegiance(); setSelectedAllegiance(ea); - // resetting panic option - getModelPanic().setSelected(false); - // setting equipped helmet ItemInfo helmet = creInfo.getEquippedHelmet(); setSelectedHelmet(helmet); @@ -599,7 +587,7 @@ private void init() // perform lazy initialization: time-consuming initializations are performed on demand modelCreSelection = new CreatureSelectionModel(false); modelCreAnimation = new CreatureAnimationModel(); - modelCreAllegiance = new CreatureAllegianceModel(); + modelCreAllegiance = new CreatureStatusModel(); modelItemHelmet = new ItemSelectionModel(ItemInfo.FILTER_HELMET, false); modelItemArmor = new ItemSelectionModel(ItemInfo.FILTER_ARMOR, false); modelItemShield = new ItemSelectionModel(ItemInfo.FILTER_SHIELD.or(ItemInfo.FILTER_WEAPON_MELEE_LEFT_HANDED), false); @@ -607,6 +595,5 @@ private void init() for (int i = 0; i < 7; i++) { colorModels.add(new ColorSelectionModel()); } - modelPanic = new ToggleButtonModel(); } } diff --git a/src/org/infinity/resource/cre/viewer/CreatureControlPanel.java b/src/org/infinity/resource/cre/viewer/CreatureControlPanel.java index 1b689c3f8..caa5b6fb6 100644 --- a/src/org/infinity/resource/cre/viewer/CreatureControlPanel.java +++ b/src/org/infinity/resource/cre/viewer/CreatureControlPanel.java @@ -5,6 +5,7 @@ package org.infinity.resource.cre.viewer; import java.awt.BorderLayout; +import java.awt.CardLayout; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; @@ -17,7 +18,6 @@ import java.util.List; import javax.swing.JButton; -import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JLabel; @@ -59,14 +59,15 @@ public class CreatureControlPanel extends JPanel private CreatureControlModel model; private JComboBox cbCreSelection; private JComboBox cbCreAnimation; - private JComboBox cbCreAllegiance; + private JComboBox cbCreAllegiance; private JComboBox cbItemHelmet; private JComboBox cbItemArmor; private JComboBox cbItemShield; private JComboBox cbItemWeapon; - private JCheckBox cbPanic; - private JButton bReset; - private JButton bApply; + private JButton bReset, bApply, bHidePanel, bShowPanel; + private JPanel panelMain, panelHidden; + private JScrollPane scrollShown; + private CardLayout layoutMain; public CreatureControlPanel(CreatureViewer viewer) { @@ -113,7 +114,7 @@ public void applySettings() CreResource cre = getControlModel().getDecoder().getCreResource(); CreUtils.setAnimation(cre, getControlModel().getSelectedAnimation().getValue()); CreUtils.setAllegiance(cre, getControlModel().getSelectedAllegiance().getValue()); - CreUtils.setStatusPanic(cre, getControlModel().getModelPanic().isSelected()); + CreUtils.setStatusPanic(cre, getControlModel().getSelectedAllegiance().getStatus() == CreatureStatusModel.Status.PANICKED); CreUtils.setEquipmentHelmet(cre, getControlModel().getSelectedHelmet(getControlModel().getModelHelmet())); CreUtils.setEquipmentArmor(cre, getControlModel().getSelectedArmor(getControlModel().getModelArmor())); CreUtils.setEquipmentWeapon(cre, getControlModel().getSelectedWeapon(getControlModel().getModelWeapon())); @@ -160,7 +161,6 @@ private void init() int defWidth = cbCreSelection.getPreferredSize().width * 5 / 4; setPreferredWidth(cbCreSelection, defWidth); cbCreSelection.addActionListener(listeners); -// model.getModelCreature().addListDataListener(listeners); updateToolTip(cbCreSelection); JLabel l2 = new JLabel("Creature animation:"); @@ -171,102 +171,89 @@ private void init() cbCreAnimation.addActionListener(listeners); updateToolTip(cbCreAnimation); - JLabel l3 = new JLabel("Allegiance:"); + JLabel l3 = new JLabel("Status:"); cbCreAllegiance = new JComboBox<>(model.getModelAllegiance()); setPreferredWidth(cbCreAllegiance, defWidth); cbCreAllegiance.addActionListener(listeners); updateToolTip(cbCreAllegiance); - cbPanic = new JCheckBox("Panicked"); - cbPanic.setModel(model.getModelPanic()); - cbPanic.addActionListener(listeners); - - JPanel pColumn1 = new JPanel(new GridBagLayout()); - c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, - GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); - pColumn1.add(l1, c); - c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(0, 4, 0, 0), 0, 0); - pColumn1.add(cbCreSelection, c); - - c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, - GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); - pColumn1.add(l2, c); - c = ViewerUtil.setGBC(c, 1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(8, 4, 0, 0), 0, 0); - pColumn1.add(cbCreAnimation, c); - - c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, - GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); - pColumn1.add(l3, c); - c = ViewerUtil.setGBC(c, 1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(8, 4, 0, 0), 0, 0); - pColumn1.add(cbCreAllegiance, c); - - c = ViewerUtil.setGBC(c, 0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, - GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); - pColumn1.add(new JPanel(), c); - c = ViewerUtil.setGBC(c, 1, 3, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(8, 4, 0, 0), 0, 0); - pColumn1.add(cbPanic, c); - - - // second column - l1 = new JLabel("Helmet:"); + JLabel l4 = new JLabel("Helmet:"); cbItemHelmet = new JComboBox<>(model.getModelHelmet()); setPreferredWidth(cbItemHelmet, defWidth); cbItemHelmet.addActionListener(listeners); updateToolTip(cbItemHelmet); - l2 = new JLabel("Armor:"); + JLabel l5 = new JLabel("Armor:"); cbItemArmor = new JComboBox<>(model.getModelArmor()); setPreferredWidth(cbItemArmor, defWidth); cbItemArmor.addActionListener(listeners); updateToolTip(cbItemArmor); - l3 = new JLabel("Shield:"); + JLabel l6 = new JLabel("Shield:"); cbItemShield = new JComboBox<>(model.getModelShield()); setPreferredWidth(cbItemShield, defWidth); cbItemShield.addActionListener(listeners); updateToolTip(cbItemShield); - JLabel l4 = new JLabel("Weapon:"); + JLabel l7 = new JLabel("Weapon:"); cbItemWeapon = new JComboBox<>(model.getModelWeapon()); setPreferredWidth(cbItemWeapon, defWidth); cbItemWeapon.addActionListener(listeners); updateToolTip(cbItemWeapon); - JPanel pColumn2 = new JPanel(new GridBagLayout()); + JPanel pColumn1 = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); - pColumn2.add(l1, c); + pColumn1.add(l1, c); c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 4, 0, 0), 0, 0); - pColumn2.add(cbItemHelmet, c); + pColumn1.add(cbCreSelection, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); - pColumn2.add(l2, c); + pColumn1.add(l2, c); c = ViewerUtil.setGBC(c, 1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 4, 0, 0), 0, 0); - pColumn2.add(cbItemArmor, c); + pColumn1.add(cbCreAnimation, c); c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); - pColumn2.add(l3, c); + pColumn1.add(l3, c); c = ViewerUtil.setGBC(c, 1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 4, 0, 0), 0, 0); - pColumn2.add(cbItemShield, c); + pColumn1.add(cbCreAllegiance, c); + c = ViewerUtil.setGBC(c, 0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, - GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); - pColumn2.add(l4, c); + GridBagConstraints.NONE, new Insets(32, 0, 0, 0), 0, 0); + pColumn1.add(l4, c); c = ViewerUtil.setGBC(c, 1, 3, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(32, 4, 0, 0), 0, 0); + pColumn1.add(cbItemHelmet, c); + + c = ViewerUtil.setGBC(c, 0, 4, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, + GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); + pColumn1.add(l5, c); + c = ViewerUtil.setGBC(c, 1, 4, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 4, 0, 0), 0, 0); + pColumn1.add(cbItemArmor, c); + + c = ViewerUtil.setGBC(c, 0, 5, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, + GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); + pColumn1.add(l6, c); + c = ViewerUtil.setGBC(c, 1, 5, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 4, 0, 0), 0, 0); + pColumn1.add(cbItemShield, c); + + c = ViewerUtil.setGBC(c, 0, 6, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, + GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); + pColumn1.add(l7, c); + c = ViewerUtil.setGBC(c, 1, 6, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 4, 0, 0), 0, 0); - pColumn2.add(cbItemWeapon, c); + pColumn1.add(cbItemWeapon, c); - // third column + // second column JComboBox cb; ColorSelectionModel cm = model.getModelColor(0); @@ -279,124 +266,40 @@ private void init() } } - l1 = new JLabel("Metal color:"); - colorLabels.add(l1); - cm = model.getModelColor(0); - cb = new JComboBox<>(cm); - cb.setRenderer(cm.getRenderer()); - if (ce != null) { - cb.setPrototypeDisplayValue(ce); - defWidth = cb.getPreferredSize().width; + final String[] labels = {"Metal color:", "Minor color:", "Major color:", "Skin color:", + "Leather color:", "Armor color:", "Hair color:"}; + for (int i = 0; i < labels.length; i++) { + l1 = new JLabel(labels[i]); + colorLabels.add(l1); + cm = model.getModelColor(i); + cb = new JComboBox<>(cm); + cb.setRenderer(cm.getRenderer()); + if (i == 0 && ce != null) { + cb.setPrototypeDisplayValue(ce); + defWidth = cb.getPreferredSize().width; + } else { + cb.setPrototypeDisplayValue((ColorSelectionModel.ColorEntry)cm.getSelectedItem()); + } + setPreferredWidth(cb, defWidth); + cb.addActionListener(listeners); + cm.addListDataListener(listeners); + updateToolTip(cb); + colorControls.add(cb); } - setPreferredWidth(cb, defWidth); - cb.addActionListener(listeners); - cm.addListDataListener(listeners); - updateToolTip(cb); - colorControls.add(cb); - - l1 = new JLabel("Minor color:"); - colorLabels.add(l1); - cm = model.getModelColor(1); - cb = new JComboBox<>(cm); - cb.setRenderer(cm.getRenderer()); - cb.setPrototypeDisplayValue((ColorSelectionModel.ColorEntry)cm.getSelectedItem()); - setPreferredWidth(cb, defWidth); - cb.addActionListener(listeners); - cm.addListDataListener(listeners); - updateToolTip(cb); - colorControls.add(cb); - - l1 = new JLabel("Major color:"); - colorLabels.add(l1); - cm = model.getModelColor(2); - cb = new JComboBox<>(cm); - cb.setRenderer(cm.getRenderer()); - cb.setPrototypeDisplayValue((ColorSelectionModel.ColorEntry)cm.getSelectedItem()); - setPreferredWidth(cb, defWidth); - cb.addActionListener(listeners); - cm.addListDataListener(listeners); - updateToolTip(cb); - colorControls.add(cb); - - l1 = new JLabel("Skin color:"); - colorLabels.add(l1); - cm = model.getModelColor(3); - cb = new JComboBox<>(cm); - cb.setRenderer(cm.getRenderer()); - cb.setPrototypeDisplayValue((ColorSelectionModel.ColorEntry)cm.getSelectedItem()); - setPreferredWidth(cb, defWidth); - cb.addActionListener(listeners); - cm.addListDataListener(listeners); - updateToolTip(cb); - colorControls.add(cb); - - JPanel pColumn3 = new JPanel(new GridBagLayout()); - c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, - GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); - pColumn3.add(colorLabels.get(0), c); - c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(0, 4, 0, 0), 0, 0); - pColumn3.add(colorControls.get(0), c); - - c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, - GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); - pColumn3.add(colorLabels.get(1), c); - c = ViewerUtil.setGBC(c, 1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(8, 4, 0, 0), 0, 0); - pColumn3.add(colorControls.get(1), c); - - c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, - GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); - pColumn3.add(colorLabels.get(2), c); - c = ViewerUtil.setGBC(c, 1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(8, 4, 0, 0), 0, 0); - pColumn3.add(colorControls.get(2), c); - - c = ViewerUtil.setGBC(c, 0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, - GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); - pColumn3.add(colorLabels.get(3), c); - c = ViewerUtil.setGBC(c, 1, 3, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(8, 4, 0, 0), 0, 0); - pColumn3.add(colorControls.get(3), c); + JPanel pColumn2 = new JPanel(new GridBagLayout()); + for (int i = 0; i < colorControls.size(); i++) { + int top = (i == 0) ? 0 : 8; + c = ViewerUtil.setGBC(c, 0, i, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, + GridBagConstraints.NONE, new Insets(top, 0, 0, 0), 0, 0); + pColumn2.add(colorLabels.get(i), c); + c = ViewerUtil.setGBC(c, 1, i, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(top, 4, 0, 0), 0, 0); + pColumn2.add(colorControls.get(i), c); + } - // fourth column - l1 = new JLabel("Leather color:"); - colorLabels.add(l1); - cm = model.getModelColor(4); - cb = new JComboBox<>(cm); - cb.setRenderer(cm.getRenderer()); - cb.setPrototypeDisplayValue((ColorSelectionModel.ColorEntry)cm.getSelectedItem()); - setPreferredWidth(cb, defWidth); - cb.addActionListener(listeners); - cm.addListDataListener(listeners); - updateToolTip(cb); - colorControls.add(cb); - - l1 = new JLabel("Armor color:"); - colorLabels.add(l1); - cm = model.getModelColor(5); - cb = new JComboBox<>(cm); - cb.setRenderer(cm.getRenderer()); - cb.setPrototypeDisplayValue((ColorSelectionModel.ColorEntry)cm.getSelectedItem()); - setPreferredWidth(cb, defWidth); - cb.addActionListener(listeners); - cm.addListDataListener(listeners); - updateToolTip(cb); - colorControls.add(cb); - - l1 = new JLabel("Hair color:"); - colorLabels.add(l1); - cm = model.getModelColor(6); - cb = new JComboBox<>(cm); - cb.setRenderer(cm.getRenderer()); - cb.setPrototypeDisplayValue((ColorSelectionModel.ColorEntry)cm.getSelectedItem()); - setPreferredWidth(cb, defWidth); - cb.addActionListener(listeners); - cm.addListDataListener(listeners); - updateToolTip(cb); - colorControls.add(cb); + // third column Insets margin; bReset = new JButton("Reset", Icons.getIcon(Icons.ICON_UNDO_16)); margin = bReset.getMargin(); @@ -410,58 +313,76 @@ private void init() bApply.setMargin(margin); bApply.addActionListener(listeners); - JPanel pColumn4 = new JPanel(new GridBagLayout()); - c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, - GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); - pColumn4.add(colorLabels.get(4), c); - c = ViewerUtil.setGBC(c, 1, 0, 2, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(0, 4, 0, 0), 0, 0); - pColumn4.add(colorControls.get(4), c); + JPanel pColumn3 = new JPanel(new GridBagLayout()); + c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); + pColumn3.add(bApply, c); + c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 0, 0, 0), 0, 0); + pColumn3.add(bReset, c); + c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 0.0, 1.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.VERTICAL, new Insets(8, 0, 0, 0), 0, 0); + pColumn3.add(new JPanel(), c); - c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, - GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); - pColumn4.add(colorLabels.get(5), c); - c = ViewerUtil.setGBC(c, 1, 1, 2, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(8, 4, 0, 0), 0, 0); - pColumn4.add(colorControls.get(5), c); - c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, - GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); - pColumn4.add(colorLabels.get(6), c); - c = ViewerUtil.setGBC(c, 1, 2, 2, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(8, 4, 0, 0), 0, 0); - pColumn4.add(colorControls.get(6), c); + // fourth column + bHidePanel = new JButton("Hide panel"); + margin = bHidePanel.getMargin(); + margin.top += 4; margin.bottom += 4; + bHidePanel.setMargin(margin); + defWidth = bHidePanel.getPreferredSize().width; + bHidePanel.addActionListener(listeners); - c = ViewerUtil.setGBC(c, 0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, - GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); + JPanel pColumn4 = new JPanel(new GridBagLayout()); + c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_END, + GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); + pColumn4.add(bHidePanel, c); + c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_END, + GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0); pColumn4.add(new JPanel(), c); - c = ViewerUtil.setGBC(c, 1, 3, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, - GridBagConstraints.NONE, new Insets(8, 4, 0, 0), 0, 0); - pColumn4.add(bReset, c); - c = ViewerUtil.setGBC(c, 2, 3, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(8, 8, 0, 0), 0, 0); - pColumn4.add(bApply, c); // combining columns - JPanel panelMain = new JPanel(new GridBagLayout()); + JPanel panelShown = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE, new Insets(8, 8, 8, 0), 0, 0); - panelMain.add(pColumn1, c); + panelShown.add(pColumn1, c); c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE, new Insets(8, 16, 8, 0), 0, 0); - panelMain.add(pColumn2, c); + panelShown.add(pColumn2, c); c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE, new Insets(8, 16, 8, 0), 0, 0); - panelMain.add(pColumn3, c); - c = ViewerUtil.setGBC(c, 3, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, - GridBagConstraints.NONE, new Insets(8, 16, 8, 8), 0, 0); - panelMain.add(pColumn4, c); + panelShown.add(pColumn3, c); + c = ViewerUtil.setGBC(c, 3, 0, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.BOTH, new Insets(8, 16, 8, 8), 0, 0); + panelShown.add(pColumn4, c); - JScrollPane scroll = new JScrollPane(panelMain); - scroll.setBorder(panelMain.getBorder()); + scrollShown = new JScrollPane(panelShown); + scrollShown.setBorder(panelShown.getBorder()); + + // hidden panel + bShowPanel = new JButton("Show panel"); + margin = bShowPanel.getMargin(); + margin.top += 4; margin.bottom += 4; + bShowPanel.setMargin(margin); + defWidth = Math.max(defWidth, bShowPanel.getPreferredSize().width); + bShowPanel.setPreferredSize(new Dimension(defWidth, bShowPanel.getPreferredSize().height)); + bShowPanel.addActionListener(listeners); + + bHidePanel.setPreferredSize(new Dimension(defWidth, bHidePanel.getPreferredSize().height)); + + panelHidden = new JPanel(new GridBagLayout()); + c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_END, + GridBagConstraints.NONE, new Insets(8, 8, 8, 8), 0, 0); + panelHidden.add(bShowPanel, c); + + layoutMain = new CardLayout(); + panelMain = new JPanel(layoutMain); + panelMain.add(scrollShown); + panelMain.add(panelHidden); + layoutMain.first(panelMain); setLayout(new BorderLayout()); - add(scroll, BorderLayout.CENTER); + add(panelMain, BorderLayout.CENTER); fireSettingsChanged(); } @@ -601,8 +522,13 @@ else if (e.getSource() == cbItemWeapon) { getControlModel().itemWeaponChanged(); updateToolTip(cbItemWeapon); } - else if (e.getSource() == cbPanic) { - getControlModel().crePanicChanged(); + else if (e.getSource() == bHidePanel) { + layoutMain.last(panelMain); + panelMain.setPreferredSize(panelHidden.getPreferredSize()); + } + else if (e.getSource() == bShowPanel) { + layoutMain.first(panelMain); + panelMain.setPreferredSize(scrollShown.getPreferredSize()); } else { // color selection diff --git a/src/org/infinity/resource/cre/viewer/CreatureAllegianceModel.java b/src/org/infinity/resource/cre/viewer/CreatureStatusModel.java similarity index 53% rename from src/org/infinity/resource/cre/viewer/CreatureAllegianceModel.java rename to src/org/infinity/resource/cre/viewer/CreatureStatusModel.java index 9d72e8ddc..f9240b8d4 100644 --- a/src/org/infinity/resource/cre/viewer/CreatureAllegianceModel.java +++ b/src/org/infinity/resource/cre/viewer/CreatureStatusModel.java @@ -5,8 +5,6 @@ package org.infinity.resource.cre.viewer; import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.stream.IntStream; @@ -14,21 +12,37 @@ import javax.swing.AbstractListModel; import javax.swing.ComboBoxModel; -import org.infinity.util.IdsMap; -import org.infinity.util.IdsMapCache; -import org.infinity.util.IdsMapEntry; +import org.infinity.util.Misc; /** * {@code ComboBoxModel} for the creature allegiance combo box used in the Creature Animation Browser. */ -public class CreatureAllegianceModel extends AbstractListModel - implements ComboBoxModel +public class CreatureStatusModel extends AbstractListModel + implements ComboBoxModel { - private final List eaList = new ArrayList<>(); + /** Available creature status entries. */ + public enum Status { + /** Indicates green selection circle. */ + FRIENDLY(2), + /** Indicates cyan selection circle. */ + NEUTRAL(128), + /** Indicates red selection circle. */ + HOSTILE(255), + /** Indicates yellow selection circle. */ + PANICKED(-1); + + private final int id; + private Status(int id) { this.id = id; } + + /** Returns the numeric id associated with the enum. */ + public int getValue() { return id; } + } + + private final List statusList = new ArrayList<>(); private Object selectedItem; - public CreatureAllegianceModel() + public CreatureStatusModel() { super(); init(); @@ -41,26 +55,26 @@ public void reload() /** * Returns the index-position of the specified object in the list. - * @param anItem a {@code AllegianceEntry} object, {@code Number} object or {@code String} specifying an allegiance name. + * @param anItem a {@code StatusEntry} object, {@code Number} object or {@code String} specifying an allegiance name. * @return an int representing the index position, where 0 is the first position. Returns -1 * if the item could not be found in the list. */ public int getIndexOf(Object anItem) { - if (anItem instanceof AllegianceEntry) { - return eaList.indexOf(anItem); + if (anItem instanceof StatusEntry) { + return statusList.indexOf(anItem); } else if (anItem instanceof Number) { final int eaValue = ((Number)anItem).intValue(); return IntStream - .range(0, eaList.size()) - .filter(i -> eaList.get(i).value == eaValue) + .range(0, statusList.size()) + .filter(i -> statusList.get(i).getStatus().getValue() == eaValue) .findAny() .orElse(-1); } else if (anItem != null) { final String eaName = anItem.toString().trim(); return IntStream - .range(0, eaList.size()) - .filter(i -> eaName.equalsIgnoreCase(eaList.get(i).name)) + .range(0, statusList.size()) + .filter(i -> eaName.equalsIgnoreCase(statusList.get(i).getName())) .findAny() .orElse(-1); } @@ -70,9 +84,9 @@ public int getIndexOf(Object anItem) /** Empties the list. */ public void removeAllElements() { - if (!eaList.isEmpty()) { - int oldSize = eaList.size(); - eaList.clear(); + if (!statusList.isEmpty()) { + int oldSize = statusList.size(); + statusList.clear(); selectedItem = null; if (oldSize > 0) { fireIntervalRemoved(this, 0, oldSize - 1); @@ -87,14 +101,14 @@ public void removeAllElements() @Override public int getSize() { - return eaList.size(); + return statusList.size(); } @Override - public AllegianceEntry getElementAt(int index) + public StatusEntry getElementAt(int index) { - if (index >= 0 && index < eaList.size()) { - return eaList.get(index); + if (index >= 0 && index < statusList.size()) { + return statusList.get(index); } else { return null; } @@ -126,48 +140,43 @@ private void init() { removeAllElements(); - IdsMap map = IdsMapCache.get("EA.IDS"); - if (map != null) { - for (Iterator iter = map.getAllValues().iterator(); iter.hasNext(); ) { - IdsMapEntry entry = iter.next(); - eaList.add(new AllegianceEntry(entry)); - } - Collections.sort(eaList); - } - if (!eaList.isEmpty()) { - fireIntervalAdded(this, 0, eaList.size() - 1); + for (final Status status : Status.values()) { + statusList.add(new StatusEntry(status)); } + fireIntervalAdded(this, 0, statusList.size() - 1); setSelectedItem(getElementAt(0)); } //-------------------------- INNER CLASSES -------------------------- - public static class AllegianceEntry implements Comparable + public static class StatusEntry { - private final int value; + private final Status status; private final String name; - public AllegianceEntry(IdsMapEntry eaEntry) + public StatusEntry(Status status) { - this.value = (int)Objects.requireNonNull(eaEntry).getID(); - this.name = eaEntry.getSymbol(); + this.status = Objects.requireNonNull(status); + this.name = Misc.prettifySymbol(this.status.toString()); } - public int getValue() { return value; } + /** Returns the status enum. */ + public Status getStatus() { return status; } + + /** + * Returns a typical EA.IDS value representing the status. + * Returns negative values for non-allegiance status types. + */ + public int getValue() { return status.getValue(); } + /** Returns the descriptive name of the status. */ public String getName() { return name; } @Override public String toString() { - return value + " - " + name; - } - - @Override - public int compareTo(AllegianceEntry o) - { - return value - o.value; + return getName(); } } } From 3ac571fb5796c9b32a67da0cab11342dabb46882 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 26 Mar 2021 00:24:15 +0100 Subject: [PATCH 082/158] Creature Animation Browser: Update background list in settings panel - Added more background images - Removed redundant colored backgrounds - Slightly modified selection colors --- .../resource/cre/decoder/SpriteDecoder.java | 4 +- .../resource/cre/viewer/SettingsPanel.java | 26 +++++++++- .../resource/cre/viewer/bg/Backgrounds.java | 46 +++++++----------- .../resource/cre/viewer/bg/iwd_wilderness.jpg | Bin 0 -> 288355 bytes .../resource/cre/viewer/bg/pst_dungeon.jpg | Bin 0 -> 193704 bytes .../resource/cre/viewer/bg/sod_city_night.jpg | Bin 0 -> 267596 bytes 6 files changed, 44 insertions(+), 32 deletions(-) create mode 100644 src/org/infinity/resource/cre/viewer/bg/iwd_wilderness.jpg create mode 100644 src/org/infinity/resource/cre/viewer/bg/pst_dungeon.jpg create mode 100644 src/org/infinity/resource/cre/viewer/bg/sod_city_night.jpg diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index 6ae1a5950..091fa2ee4 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -1698,7 +1698,7 @@ protected static Color getAllegianceColor(int value) Color c = null; if (value < 0) { // treat as panic - c = new Color(0xffff40, false); + c = new Color(0xffff20, false); } else if (value >= 2 && value <= 4 || value == 201) { // ally c = new Color(0x20ff20, false); @@ -1707,7 +1707,7 @@ protected static Color getAllegianceColor(int value) c = new Color(0xff2020, false); } else { // neutral - c = new Color(0x40ffff, false); + c = new Color(0x20ffff, false); } return c; diff --git a/src/org/infinity/resource/cre/viewer/SettingsPanel.java b/src/org/infinity/resource/cre/viewer/SettingsPanel.java index c318cab70..72a89e2a1 100644 --- a/src/org/infinity/resource/cre/viewer/SettingsPanel.java +++ b/src/org/infinity/resource/cre/viewer/SettingsPanel.java @@ -12,9 +12,11 @@ import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Vector; +import java.util.stream.Collectors; import javax.swing.JButton; import javax.swing.JCheckBox; @@ -26,6 +28,7 @@ import org.infinity.gui.ViewerUtil; import org.infinity.resource.Profile; import org.infinity.resource.cre.viewer.bg.Backgrounds; +import org.infinity.resource.cre.viewer.bg.Backgrounds.BackgroundInfo; import org.infinity.resource.cre.viewer.icon.Icons; /** @@ -33,6 +36,16 @@ */ public class SettingsPanel extends JPanel { + // Available render canvas backgrounds + public static final List backgroundList = new ArrayList() {{ + add(Backgrounds.BG_COLOR_NONE); + add(Backgrounds.BG_WILDERNESS_BG); add(Backgrounds.BG_CAVE_BG); + add(Backgrounds.BG_CITY_NIGHT_SOD); add(Backgrounds.BG_WILDERNESS_IWD); + add(Backgrounds.BG_CITY_PST); add(Backgrounds.BG_DUNGEON_PST); + add(Backgrounds.BG_COLOR_WHITE); add(Backgrounds.BG_COLOR_BLACK); + add(Backgrounds.BG_COLOR_LIGHT_GRAY); add(Backgrounds.BG_COLOR_GRAY); + }}; + // Available items for zoom selection list private static final Vector> zoomList = new Vector>() {{ add(ItemString.with("50 %", 50)); @@ -87,6 +100,15 @@ public class SettingsPanel extends JPanel cbPaletteReplacementEnabled, cbShowAvatar, cbShowHelmet, cbShowShield, cbShowWeapon, cbShowBorders; private AttributesPanel panelAttributes; + /** Returns a list of background info instances available for the specified game. */ + public static List getBackgrounds(Profile.Game game) + { + return backgroundList + .stream() + .filter(bi -> bi.getGames().contains((game != null) ? game : Profile.getGame())) + .collect(Collectors.toList()); + } + public SettingsPanel(CreatureViewer viewer) { super(); @@ -359,9 +381,9 @@ private void init() cbFrameRate.addActionListener(listeners); JLabel l3 = new JLabel("Background:"); - List bgList = Backgrounds.getBackgrounds(Profile.getGame()); + List bgList = getBackgrounds(Profile.getGame()); cbBackground = new JComboBox(bgList.toArray(new Backgrounds.BackgroundInfo[bgList.size()])); - cbBackground.setPrototypeDisplayValue(Backgrounds.BackgroundList.get(1)); + cbBackground.setPrototypeDisplayValue(backgroundList.get(1)); cbBackground.setSelectedIndex(indexBackground); cbBackground.addActionListener(listeners); diff --git a/src/org/infinity/resource/cre/viewer/bg/Backgrounds.java b/src/org/infinity/resource/cre/viewer/bg/Backgrounds.java index 30c464a6f..1bbba5803 100644 --- a/src/org/infinity/resource/cre/viewer/bg/Backgrounds.java +++ b/src/org/infinity/resource/cre/viewer/bg/Backgrounds.java @@ -8,11 +8,8 @@ import java.awt.Image; import java.awt.Point; import java.net.URL; -import java.util.ArrayList; import java.util.EnumSet; -import java.util.List; import java.util.Objects; -import java.util.stream.Collectors; import javax.imageio.ImageIO; import javax.swing.UIManager; @@ -24,15 +21,24 @@ */ public final class Backgrounds { - public static final BackgroundInfo BG_WILDERNESS = new BackgroundInfo("Sword Coast wilderness tileset", new Color(0x282f12), - "bg_wilderness.jpg", new Point(640, 632), - EnumSet.complementOf(EnumSet.of(Profile.Game.PST, Profile.Game.PSTEE))); - public static final BackgroundInfo BG_CAVE = new BackgroundInfo("Cave tileset", new Color(0x010101), - "bg_cave.jpg", new Point(640, 554), - EnumSet.complementOf(EnumSet.of(Profile.Game.PST, Profile.Game.PSTEE))); - public static final BackgroundInfo BG_CITY_PST = new BackgroundInfo("Sigil city tileset", new Color(0x494131), - "pst_city.jpg", new Point(640, 580), - EnumSet.of(Profile.Game.PST, Profile.Game.PSTEE)); + public static final BackgroundInfo BG_WILDERNESS_BG = new BackgroundInfo("Wilderness (BG2)", new Color(0x282f12), + "bg_wilderness.jpg", new Point(640, 632), + EnumSet.complementOf(EnumSet.of(Profile.Game.PST, Profile.Game.PSTEE))); + public static final BackgroundInfo BG_CAVE_BG = new BackgroundInfo("Cave (BG2)", new Color(0x010101), + "bg_cave.jpg", new Point(640, 554), + EnumSet.complementOf(EnumSet.of(Profile.Game.PST, Profile.Game.PSTEE))); + public static final BackgroundInfo BG_CITY_NIGHT_SOD = new BackgroundInfo("City at night (SoD)", new Color(0x12151c), + "sod_city_night.jpg", new Point(620, 530), + EnumSet.complementOf(EnumSet.of(Profile.Game.PST, Profile.Game.PSTEE))); + public static final BackgroundInfo BG_WILDERNESS_IWD = new BackgroundInfo("Wilderness (IWD)", new Color(0xd3deda), + "iwd_wilderness.jpg", new Point(640, 554), + EnumSet.complementOf(EnumSet.of(Profile.Game.PST, Profile.Game.PSTEE))); + public static final BackgroundInfo BG_CITY_PST = new BackgroundInfo("Sigil city (PST)", new Color(0x494131), + "pst_city.jpg", new Point(640, 580), + EnumSet.of(Profile.Game.PST, Profile.Game.PSTEE)); + public static final BackgroundInfo BG_DUNGEON_PST = new BackgroundInfo("Sigil dungeon (PST)", new Color(0x494131), + "pst_dungeon.jpg", new Point(640, 480), + EnumSet.of(Profile.Game.PST, Profile.Game.PSTEE)); public static final BackgroundInfo BG_COLOR_NONE = new BackgroundInfo("System color", null); public static final BackgroundInfo BG_COLOR_WHITE = new BackgroundInfo("White color", Color.WHITE); public static final BackgroundInfo BG_COLOR_BLACK = new BackgroundInfo("Black color", Color.BLACK); @@ -45,22 +51,6 @@ public final class Backgrounds public static final BackgroundInfo BG_COLOR_MAGENTA = new BackgroundInfo("Magenta color", Color.MAGENTA); public static final BackgroundInfo BG_COLOR_CYAN = new BackgroundInfo("Cyan color", Color.CYAN); - public static final List BackgroundList = new ArrayList() {{ - add(BG_COLOR_NONE); add(BG_WILDERNESS); add(BG_CAVE); add(BG_CITY_PST); - add(BG_COLOR_WHITE); add(BG_COLOR_BLACK); add(BG_COLOR_LIGHT_GRAY); add(BG_COLOR_GRAY); - add(BG_COLOR_RED); add(BG_COLOR_GREEN); add(BG_COLOR_BLUE); add(BG_COLOR_YELLOW); - add(BG_COLOR_MAGENTA); add(BG_COLOR_CYAN); - }}; - - /** Returns a list of background info instances available for the specified game. */ - public static List getBackgrounds(Profile.Game game) - { - return BackgroundList - .stream() - .filter(bi -> bi.games.contains((game != null) ? game : Profile.getGame())) - .collect(Collectors.toList()); - } - /** * Returns an {@link Image} object of the specified graphics filename. * @param fileName The graphics filename. diff --git a/src/org/infinity/resource/cre/viewer/bg/iwd_wilderness.jpg b/src/org/infinity/resource/cre/viewer/bg/iwd_wilderness.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bec5c5c435a850f7b4ad6225519443e45c05e73b GIT binary patch literal 288355 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⪼YEEbK1&QlDa+GVS%b#P34pUbUW@*cMcyMuzGR6Xg>y%ISAwE6-;8FSlU+?84OcK2w^j&M0FA_hcd%3LiQRU54RD(G0qaMOZc?wLnXq-rGv~@ zTSH6RsI72o$*9b7SG1sEOgcaj8mA|jbW{ZgPng@cWyCpxeYy=sHSi)*_Mjzv5FDdW z1ykKzWo#d5W&;!d07Ix>v8iMa%w7cS3#KbXKM_k6cJ3RqvVyv(wighM3s3i!0SBxx z7bo4Bm9MabybEy62ol5QnAle)DpLTpE66r6R|~{1ndvInXBb(3SO8}OToTn3pjKt~QBsZ*5_rtO2MMFlGU7e_@5~Eo z=IXOkI!0g>{YtxEm}i#dWjBsk+685tx51Ms&)6-oVJB06iu#K%rnVV;v;5YrZd9eLxZj*esc<$`Ck zc%-_8%lQK48&?pfo(y35wO+3xErUc{Mv*#sMbCtS_d3!sR^1~%{1s%#YrzAm?^>Y^v*4zXt zAWtxct3}?W_;xkiLK$WD#YmZy7E``qAPdXPeSTD!V+Np|MF+&p

    G!uNs4M(5Y#R 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�IzSgsd2 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%0DI{gW;A83JQS*$BarqEy6SB);BFlaz=(UxDjaa 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

    xp(+na!MjPorE zFL8pFZ!k^qTJGLR6EUpjf9z#`BC*Z?0N4xeF`_vcXjuhoJ^e!F0ZDUoMy2zi}Z&^}`ILCj+t|Q(MOo%wY2lnCXqZOk^os^oCx2kxk10 zZI!8CCT#1>&~;pVtbkAn2Lw64a4Sm1Vyof{zr?YF+tetQ6e%kr%WGSEj^qy5x6P8* z3649)BP>u19nCr@OVqW+u2cj!Prn_+*0o7%G4?(|f;PeO_kY*qUC3@Q-=GBQGb|0yIce*;~^SI_5Di{mbrnkRjhFp zak^N;MMqX9`z~fGK5b5l7@f@lT0uP_&Jw$Sb(^cqhqpbEF_V+gJU>E)F zB33M}rSXe@F$Eo*=$RO!sBWlkn?5>}8m&A&<=c!JZ`P_GS(~aiVRsi2WC63XtK^P^ zUAHeLF#;=C%~1KMZ2o|v9h;e!*J5f~2^yhLZJ*8Vz9PMk1ClMot+kn^mRP86{7a_< zO98z;<>fZNMl-Kes>=<1$F7M2*s0@~kTvj2 zlGmin3^$o}U^p*`RfQ*fMeYD8h}0GHMCKks1!Lwv!Yv5}C|8kyVBd#`n#Jb;?&h>U zV6jSZ0cd{%{{UebRr0_ve2Xh+$>8vo3r1^dqX3T1`@Da%E}#LYiA)MG19^D+jG$Xx z_7QyJ_u>*3Hi!!!t5~RYS8Axb@prSBK>}Z>^wi;~F>kQSM-+Rwh60{qGN9qh1;}dc z!xjzYH3JPxFP3~m-hS_3f)(o7sCJ3sutIReSsW!LzgyO4-27s#+-z;Mg1*UDx5wZ$EX7`GPYo&zb z^Y%(0Qeho+yhdU!6O@2RCB;|7TR=Uqh$jn_6`*^KqjTB{7hdHdWW4S;;X`Dw=w5k& zd^5#IK#A88S)F}CCO1`wcH&s@IGq6Kc$j}Ph=F-bv|{}V;(fcEY+B>MgBu_G(MH*vU5tXJZBA(n#U?^ixiy+wqSfFHU zUFnsc*6j#8`Oy^92lFbR+PHv>815m56J`WfK?aQ#0wgl${q$c;*Pz2JO>bMyF{EcE@ngVy!jdB5_|u2*r~Q zR?so?D4@;LdViQ!SdyG)Q43GDJ{3ad=?*%ImXtnOlS_(lq{XeAHFHXF7AO>C`ytFz zX`A-2lFhvcUg{Jo`Q4T~SUv($})UfOMxE?9-0nO%WT{K}VSCvlT2nr4e*l8R{+X%zi zy&8ZevCR`8c~=CfT3BITkW!#(S7RpLZT-t@Sg6%=r&x;Q0Yi2{qQDvyb5R$9r^;Qj zpoUZ21VN~{9z4LBHf3J7N0<~=qHdO>tX#xdzG^99Z#M1%F>dM2Lgtgj?pbZVS&cPJ za~lkP_baomFEX+fd>F|&6W-z_T75+pZK{@*fM;>;smaqYl%-~aQrus-rA$v(D9fjb zLro6bh|N=yrIuFnOHah4thP@O7-(qVhb*h>f@jS^B@kG{^#qM-2ErPy3b9zpe_wKm zL2101ynXt6kA;ksKv<{ZIFK%mtKaSp1A0v)$QU{06fUQPw$R8Sgqg5k47Dp=ypJ$! ztEK|rOH{?zHs&&FmW^d{8i*;4+)SU$0m8L#Ar>{%L?5Ap$qwooUo2YJaT7vY^Zx*_ zDlD?5DmvzO1#gZEfr`heunR?VTl~iX$CHRqD5#E$Rcjwe?Cvy?vncT>R}20ni)+`J z%3n25hebPOXcvO$Y4Hds2UsH59^+RqJmHj^mrJvRO^v#$KA{w&d?e(z*GZCT_bkHg z<{Cp$BTbx;ln7uJ%-0Z{n~r9f01A(AP~^Rmi^?mGAgXguJfn()3k3Lym@lc$tE?@WiYKLCkq8o_c{me33NBbjn~g{KQ}jA;zp+Rn(xA9$IF2S{n>O z3rEsq51^EY4a+zB{he};!2>oEA+wy4leKJQ?8ubHU9u2 zI6|smbB`I0>`6>BpL>Y-ryOz^vmf`U%wlffONAoM$*4I+qgPSry zfc3;^3LBIZv-KJ{F2(}hddx5uRc?uJ-!WCqUxE(Si8S# zJ>MMVnLZrAR3NkhARyB8l1EPi@RWC#}f;n_bk$=TN}qXnU%Q7;Bc_Fx70FA^BQKzq&V(MRr zDS0}CR5dQOE%mCmA+@fcBLzZ&@_6D|6@{|4p7O=W5XrNKh$h7uqm})POQBO>a}XFT zu4lOHN~zfFVCm}Dnmt<=6j6vTVOottE6fTgWlQAh6l36PQi*L{5Txp9?f?jP5Zc~u zU{?GQiz@Ss=HVT8Iq@GsXE3P%Z+V*LSE#Ci>j89ujTtrE$Ua`PF}AYlv$&K|y}e8w z_qfU{=-d*hSEyZwtV}|SGh|fDO;jotsmlQk(JUBHy=j?@19_KQ(xM0Vh6Uzx-g$}{ zs+X8stK7x0*5X7CCc`<@EKrtsieNu7hnJ2eDso?O1w#J-JC*1BlM#s3%G5;Ln3kv$ zBn{RwFv$bO!ka>{tWI6tR$&+pRs)NTurc>@AxdyrTdjU z^QCOJULhlp(i0ok%3R3pbrgNgc8nY)z_56(Qou&_hTzR&SF;@zs1Oz0 zup703F%=cqS9QSs!2yd<6_z7y)%n0xr|XMOq93B?j0S4*KWh{`ie;%gMznR$|O6$MKsnbqA43!hq!!qoOc>v z5MP}e_Yek>s8W?z-Jda525Pi2CF-y0T{o6%+zeAI6xI{fN94MPY=M_n%Mcg6C4?(_ zF)9ibvCI+;gFrCpY7mpt$>sid(vX3c>Ch293f^EOek62+Zfe5&?_y zwk4@UQM&EL^$vnpj$nvWTdl`h#mI3nw*jVkCCveUBg46EVFe0?*1X)jz!{?}&xrQq z)sMI_(176wqN}YZH0!^)cdvMj(s9GMLp~WvY;Y> z!C>-N5KygeOU<714gs93x1Z|#!x3R-)&&d;q ziOyn(X^}}cT>Qk)fK7*pv7%r=Fk1x;ljxOTk06LUanip46F<-+z=kQiU8Q+;0HT-NF;_1tB93NuclZm9~yua zQ3D4bk`9_b4&V_5$c<}tf~ocZwU#JHib8utT)=%@ie={9uCO1GNC6#;cg51rX9 zf5=t?XTnAkD!VJg9Ln0sLsVVXe2!zKg;6kXq~f}iVN2asCHR2V@#owU@D-SZ$d#`y zZW2IWr0g!fujv zb#Ron0cnlYeLyWs$Bx~2hpB^0YOeiUBq;*ebpGK6@LHoUhs8^>iD_AWPxfINyvfax z)^4lUV=%9*h%*PD*L#<+Yj?|N7c->0X%5(G!m6egCB0RBw*5c=)*Od9Wg(AIC`*3j zBy0>Rn1F-DreR>5xj?hY3!6jY09BCgE%96!J7G0MMoV3FDvoXzRLxn$NGM;pHWx2U z16Dlo7Nd{ZFBorX7&hexKI4d95W)iXYEYIya>q{uHKZp+BW0buCZ5fj0kG3&Jk)yMjjZnNWlFD}HnR>pXtrjg6 z)LJQNlmhcnO1@~~CdEW5!X#t^d)XV@Vq*F8{Yqw?8mf&|sE4Ots8?v0S1`1eZlQ%0 zlet%su&eL5Ax)t|)iQuQMH-TfVLiw7SeiB8aL=jIqnuN~brlhL%POK*n1e$Gu2PFZ z!PK!W#ap;Y04914F&1L!;Eno>^cmV`O}4seAW;YjH#Y;YA)uH)~%fS~aZ;VvfgYF4TLsfClOI&6PIlEZ6YOc75{54j>f6hW`MGeyXBkTmaJst0I0ZtNPL=rmN4+Q<4o^M& zK?UT%>^HIEIjn?dk|VoeL>^R0Sn-l6RV_KI^e`<_#OcrV3os}p%J7udj7ufGwj;w0 z2q?oTvutKIs4vvVC0OufO+%$_Or7p3<iz)`AF z8Vkp6;=%4ID)NLz#&*i1pQ%fLIqeYw)Y40m2nMp;2ND*FQ>kDW-y-Y0(a$G_RSDixvir9Z-%vM~Cu~paD+#m{e1`Jh= z)&0i+q$z}m2X-+l73Juy%P~V$1~fpgJ;Y?^%VRB@ZTLLg2Qy4#a|PJ~HZ39DLXB3@Fk)oy!m}Kt`<&z{}7C;!=oRfH@*V zHU1&+@o>;wDK-!FEjd_@OLY|Gdfb}XetrWMYLrL0q z<_JGYQZFkx5Dhh-@ys;bk!A5`TYxsnV|*<-hUB(L~O}Y40O8^!&H1UJb@-jGdYXiZkKh>JXqUk0oS?8J&ZA`X(Gy z0IJsOiiZnW_n!~f=2-v*p-Y`t!!q;$?3FWsyBjec9qf3EgI3|h0u^_;_XH1U*+qfo zCL@j_v>s67e77klkQbGf1%FDEF9pWs2;H}NfY=oA1M~;+8iXqlIS-gYAbHFNUR?G| zAf~<`B9ZC`E)d;EAXi_+%FATkC5G)R#W-UJE}dmEdWh&n-g8y&QF_{`SmNFMR3*T; zmrh5*@iT%Y6^=F2!{$?uN)Pl6M_Og5GPcK^{Ys(}mXR=@6W;`K@^u)CYfAG5j&QW; zma0+DOa#y|2rQ3pcGSDb7^Vnd_*rJ$vV^W3 zlHo9rw-NUS#l!UxgEjbxb#pO;SA^U-MEF>bFHl-0SEq3)lI*J`H8$L|q$O@pZK}d* zQ58o-l%k}Tr!@82Q8PBzYHR)Kqz6n z`GCg?@dXf&8x~MpD|X{te=tU{9QKqEym>nyky59c!!W4wexlH|M_YpF55%Ngkh+yn z-P$vgFlyW@CB{5PCLI3d&0sTotNMR)S*S$=%6tA}Zxdh1_CL91qE(13-M_7p<-{~8Mx7U>#4fRb%G)V+Yf;&f3@A}Y zgr+cJ@N17TRd*U&A8?ABcETJ!vnmpJZ`7+H{7b;RskniVb#R(4S+>#U;h_fGVqH;Z zF4>A-Vf@E-!AkIiVP$i;iC0q1FvSFbm7ka)Kn25YW?RX5cVYE3l$5UD<4~47VqMyJ zztk;al~i-67sa@;h6{-8s2Cl!xbxbH2GaV0A0{}^&(v|fi(vUZk}LQ&&(ZF zTuWR{^%b&&th0QYxqw&Ih#k^4KqaQlaVuYLFT(c}^C^8?ZEhMb^BS;aMUc=GRMWrr z(;6g@6Ls0#yYrPI%t&>J*)=_Oy+dV4?< zD>-3T3`JtsMj(fPSRe>fbi#leIn~O&3MDPd!Qf>JC7lbXnSWD6c@WxrC$uyD^a4An`0{waJbTFw+$?X*_$F z$-bpHRo4*%1s%ZYw`K`PYZSY=L28dIN~IoL$I5$#45>D83ABhi8;H9)#It0?%V8IG z#vSUaiGuwri1Q`9_b}MRdMTQ3V09uFKvp%GrL+qC=6#9ul z_E|s?zF>vTXksnKws#B+ThlFhFM>9j7*JijM;N?8!JNQrHhfEBQT$3YMcgDPNEt=Z zqllKare@m4GXMhbnOb4uCNY8PCASq=v*|i6?c51K-$;V|9;G^OC|+4x5ue>9u)-14n zz{d@0rL7GwrVC-rrM1=gnEnrPxWDFBUBA(7*U2uud#%T(7=dmsCe)yQ*}1f`nSw7F@h(bmdyK-fE^JbtAGFC0jTqJ;>tejGey{Ti`p{*&KpSlm zstf+FxFH^x?7`M5W5?NG2G3O~%-LDXSYN~q?GSC>ID!Vo;$680B*%z_(*}a2fTZtG zr89buDp#H$VyGfwzv?8*uHXw?L@L>=EGa8hylMf2e{pIHJGf{T@+l6`}E;%oZ9iR~^pWcGS*pVo9LfOOvb)Do35S5sFk+d;XHb*{UHnRoTKh~*s;kXH z^2fMf-8T@~@|SM-(m_J52+ODzzQ_{nSyg*d#T;1~0*5qz5m^?zOVpq{xCF3M7Bb7G z*_8``WrDmt1hmr(YbBnUK_8A|lTdM0E)E_g_9<+v%0X1)m_?|XY5*;lP!_YsHvuvM z(J5#{v~H%sTiMiFYePiEMYBWw#HfHS>CC1sdz1=w!vQiWR1hh_3c}THoa5910{3lY z$GGy1evR%vhlZp4bx$=!)ByWYl0*oRKY? z`a$MhMM`nP+J?v$l%o8~j2pmrF}|T;Rw~a#y_!}QdOC^;Tn+p`s1sO%?Ylp5Ql$kQ zvjHAFS_Lh7`-4U!7TUMOBCrGsT&GtAj3Uhxw!hn%*jpbQK^zN%U1zE6Uo!AOS#acC zFPNh@_Z>s>fRORb1v3xKpgL}1#vJ;tqAkIVt`jg8zGIch^_h4&@|)?j5r%&Fi7uI@ewDY3+8U0W95wQ1zkM5rKB ziA1++iWSgn z?qjA4di4-;Q`S)1Ysu6MqT_I|G1b7}xWE^sml^oV(pAA7;3*KSa z4ZTaWK+sqW73s_agJ;R&G_oR`f8!3zb~9M%riGl)76p;PVhy!P}@&2TeT2KGBPPAc3G~+;kwW zo;*vaK&IA~VU$Z%bWPE(<{FBCH?rnp$c>o)0LfTD^e!w-pK*%`qk1&?_Xd&ZAOup3 zmqQhALjX7CR+%rIz@-575lUV3Tskqk)kO@8?o{D=fJza0$|~Nx<|t++Y(EYo;fqRK zv9^J(Emr7;Zo=g!it`ItP`Ew+0JRl{!Nd+v)EeY0Hk6 zYg4nveZ&z}y3eF%2cb=gUy?o?3-xAxu02+zXSh2yK5;VFi;BQQ*&F6!9bL5<`rM;5 zdX{Zh=3I1(Er-sc5{zn%%!ZFJ$SwRKuu*$#=1L=f!wQu%8r}{4x9S_fmt{2-9t$TAmuJVcIn1D+1n#DsxVaO7TF$MxvUs^LmHbCg2+ii<(!mMYa@fg(!*M7A^7mSpqS)bzB|&vK zH2(m8C11G0!0x5QvD1{dGj!qdyV73vn=4e9D!1^DC9UU?CNgIi~Y51p9^FJ#J8zJXA12*pQ%X zJ|ay9Y7>K7ShW!GU?A2AILPq;w9 zIKEU%Djw@8LC*u@xoKUN8ltYCa#6snO$O1pv@l8xU199*alS7-K@%X?DOfk_UM1QF zCw^g-at~LCA^}e|{{YECw6ln+Ib&Oasse*R`H6tP;nZuhUSnvtk{M}!V47A=m<9^0 z4Or^`@F#-U`Dw8w;qoz z8ari~XG`c?42&F9*v*(#k0JCcU0GqFvs_Sa-xM;atO{$AofOA~SnYmxg0F_fK zR@&;P2(F5Uo0$i7DH7i-Se*PsC`S#Z8LVC=V7JV14&a$ko*)}qJ-}l|x+SFnZyeO& zuAX5PQ-vnca>5XRqh*r|lgbPj25#aU8tI8GBYwyJT%wHEP6c88!7Mip&R|8oO7igc z{K{fHNFPhzHhGE4t}6}Wg!g9PCc$G~EnRGrdvu*i-#!Ti^ z2GjKeF!w4i2V~h|*2Yp_B;N`q!rbZ>C~|m?VTa72<>FGOCHJU?f&Tz=o&1n1`w>aD zIEZ%0?z+S=8XJNdITR%UHFi1m5EYi#jWJbKEzYdPuZm0c{{UtL+6CeMY7w^T?53^@ zC_~_0d{toc9o#1U%a%b$h2PYtD_U0+Tfa3JS2}eYtgGE(1zpHweAMWyo{?03%y!;L~_h);^+IorbAeetVR5EK{=c`#%sztLhn{tr_Y+85b=R z7u|lK`az|d{PU*}sI6LLvYaCq4Jok38FLHIMfjA6v|Ust(`)8qHWFjGG0PP>s4Gjp z3~uYxD+so!#_^a*;)rEa*NT^nhc$Tr0FsixA1+~3if0FMjb59L)?k?{c~k(z29=fy zhq70&buC)gYQg|v;y6f$)Au+Br6xhZ0h~Nbj zIXj%%Yr)1G=iERWz@oi~Ub+{uui^TS3IZ*l9pC0JS-R9>iP=yEELn0Rf&o=9dxLTs zJUpTUViDx}P}>+P&~#L`B%>*7 zhwXkKx}do!#fRgWl7IouzV$5pU9iRP?NOVw)X`=BPl%z}YAGKbkkOM1_$B5+_X{9T z5cgBfKrxH51#Hmzd_Euxj@N5-_lZxL31aCBI)E{j4$2Yw)!B?4J#gV@5ZSi9{>OV literal 0 HcmV?d00001 diff --git a/src/org/infinity/resource/cre/viewer/bg/pst_dungeon.jpg b/src/org/infinity/resource/cre/viewer/bg/pst_dungeon.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1c8a60df09a21e8ca297ccdd7cb76c64baf68c9a GIT binary patch literal 193704 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�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 Q)XLtDbGS-!zx&_+*&fW43IG5A literal 0 HcmV?d00001 diff --git a/src/org/infinity/resource/cre/viewer/bg/sod_city_night.jpg b/src/org/infinity/resource/cre/viewer/bg/sod_city_night.jpg new file mode 100644 index 0000000000000000000000000000000000000000..23006fc18c54babe5b305994381e5d8dc8735f71 GIT binary patch literal 267596 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⋙q{K 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≺kz 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

    j{Jtj=lCpL7zxJvUV3coS!1=|sBuBA5I zP$zCj@dPg}>E;E}w|VEOY6qUar#h~(Ry0hF8wZ}{(oLbLkX?aR2ax!UX$KW~j;fFY zUQaLeHWpFAWJ920r4YE;z{p=F>!^7ShJ!M&4xHjA6mH}LYVqQ-%aG)OtFyCAJXP;9 z5K{X6M+_TxO8|ANd1Bi|G%i^AT5>X9R|3NG@hYpU@|pNlvYWpJyVNeYish6MbkX*C ztgRnf12_N{j*OU$=xVqGU8WReb5c`uU42GyE!U<`$!D-MX_Ftszk{?T4Dh@6T)%9oHzkzb^j(5nHI*s4r*s zl~ATDRb2l7UM81|dkiyn z6cWK>F0l!8wU%d$gnBNn^uyyLC_&o{p{NfAr8khv3RUE@ibwDW(6}hl4)8VR_j9nP z4TC9`lIufD92@{O;?W(|3Jk6i1Yf5kN*xWs?7b1VparneZa!rRLY=%}3C-oZ%&9&S zxyT>f7VZ%>jcYsRWI(Yztgna?>0{^WDM!qTsoUj&9?qEu0q$B^-fA z#K~(5wupeg_Hif{#`z{_#lBG9Sz5!x3DB_%rhxdHm9D=&MSBY609{Xw*>F zIE%^oipr-l+~>y=Cfq(IvX}Ul*^E3&B^mHT2<5mwA$zFXtxTOl*GH*s;%Qv2C)qLF zF2=Q~wnE{s0u`1K3X^wra-P@RS8c^tldG29=KMfz{0DOo!0%B!50el+pl$ueX;O6_ z#mg49K^`9)fN;^%2HHMl=}tR{tcd}|o?_pL2)n&OH1v}TL&6po`&krf+GrQ^1Oii* zGeo%ZcX=jIJ7YI53)@$JQqVASdq!ARfL+0XYB_CaIhawSP%qH1`-n&!$#On_TY!*7 zp@&Vk*)cY_Aa#*Yzfs`2Tu__r&Ay3cy!5 z)U}xo1b7!q)JJ;lxXS+k5U?>^0?FV}K&P%qjU0h->K+znIb|J?P<8<5`HhYm3_b=( zc=u(~?BW{Fb-&Y?R15SpdS#m*@oGJ@8MFA5ko#jvLRkh1BfGn6LtiysBLz}~M$T>> zqamrv@K=QfRLn(ay;lq02(38pn1Co~J2)=Y&k#m2gG9%Ud#LJx$ZYKQ>TThIiX##1J=<@XCB$&Is|Gd2ze=MTh6F`ny>9^(WJ<1dt5^Tf!-Udu#^OX9UW&dRHX03vUK!i%D6CJa>7z7Pn%zC@D~o z2u6!h#=}vz!9uYGX+nfGRpBwd^hm&=c|flt6op(|oP!3Q#W!8tKm}E%S~wS8i`Nju x-uc{c62g~2G2P0qpQv)0IR53tiDKz&sZ4EXLJOdjtuWaK8pZ0RbhJK6|Je}sr;7jp literal 0 HcmV?d00001 From c7cc195b9d1593575a8153c86bf536886974ab39 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 26 Mar 2021 10:33:00 +0100 Subject: [PATCH 083/158] PseudoBamDecoder: Improve transparent color detection --- src/org/infinity/resource/graphics/PseudoBamDecoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/infinity/resource/graphics/PseudoBamDecoder.java b/src/org/infinity/resource/graphics/PseudoBamDecoder.java index 833602611..2f845da64 100644 --- a/src/org/infinity/resource/graphics/PseudoBamDecoder.java +++ b/src/org/infinity/resource/graphics/PseudoBamDecoder.java @@ -629,7 +629,7 @@ public static boolean isRleColor(int color, int rleColor, int threshold) */ public static boolean isTransparentColor(int color, int threshold) { - if (threshold < 0) return false; + if (threshold < 0) return (color & 0xff000000) == 0; final int Green = 0x0000ff00; if (threshold < 0) threshold = 0; else if (threshold > 255) threshold = 255; boolean isAlpha = (((color >>> 24) & 0xff) < (255 - threshold)); From 72a573e6681c6bbd19555f7c0bbaf8326742cce7 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 26 Mar 2021 10:34:29 +0100 Subject: [PATCH 084/158] CRE quick animation viewer: Make initial window size more consistent --- src/org/infinity/resource/cre/ViewerAnimation.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/org/infinity/resource/cre/ViewerAnimation.java b/src/org/infinity/resource/cre/ViewerAnimation.java index a16643e69..e6e03eff7 100644 --- a/src/org/infinity/resource/cre/ViewerAnimation.java +++ b/src/org/infinity/resource/cre/ViewerAnimation.java @@ -269,6 +269,9 @@ public void rewind() @Override protected boolean windowClosing(boolean forced) throws Exception { + if (getDecoder() != null) { + getDecoder().close(); + } SpriteUtils.clearCache(); return true; } @@ -393,8 +396,7 @@ else if (buttonControlPanel.getControlByType(CtrlOpenViewer) == event.getSource( private void init() throws Exception { - Dimension dim = new Dimension(1, 1); - rcDisplay = new RenderCanvas(new BufferedImage(dim.width, dim.height, BufferedImage.TYPE_INT_ARGB)); + rcDisplay = new RenderCanvas(new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB)); rcDisplay.setHorizontalAlignment(SwingConstants.CENTER); rcDisplay.setVerticalAlignment(SwingConstants.CENTER); rcDisplay.setInterpolationType(RenderCanvas.TYPE_NEAREST_NEIGHBOR); @@ -501,7 +503,8 @@ private void init() throws Exception setTitle(resName); } - setSize(NearInfinity.getInstance().getPreferredSize()); + Dimension dim = NearInfinity.getInstance().getSize(); + setSize(dim.width - 200, dim.height - 45); Center.center(this, NearInfinity.getInstance().getBounds()); setVisible(true); From 651d8c664a02b535a25e837090bd3b8fc6e8d252 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 26 Mar 2021 10:35:00 +0100 Subject: [PATCH 085/158] SpriteDecoder: Ensure that shield slot is empty for two-handed weapons --- .../resource/cre/decoder/util/CreatureInfo.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java b/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java index 76f73c7bd..d87a31223 100644 --- a/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java +++ b/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java @@ -763,6 +763,13 @@ private void initEquipment(HashMap slotMap) initEquipmentItem(slot, itemIdx, itemList); } + + // Make sure that shield slot is empty when two-handed weapon is equipped + if (getEquippedWeapon() != null) { + if (ItemInfo.FILTER_WEAPON_2H.test(getEquippedWeapon())) { + unsetItemSlot(ItemSlots.SHIELD); + } + } } private void initEquipmentItem(ItemSlots slot, int itemIndex, List itemList) @@ -788,6 +795,16 @@ private void initEquipmentItem(ItemSlots slot, int itemIndex, List } } + // Removes any equipment set in the specified item slot. Returns the removed equipment. + private ItemInfo unsetItemSlot(ItemSlots slot) + { + ItemInfo retVal = null; + if (slot != null) { + retVal = equipment.remove(slot); + } + return retVal; + } + private ResourceEntry getFistWeapon() { ResourceEntry retVal = null; From 2bf178f7ec50c1d0425465944fa106a00def67db Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 26 Mar 2021 11:12:46 +0100 Subject: [PATCH 086/158] Creature Animation Browser: Add option to view/edit associated INI file --- .../resource/cre/viewer/AttributesPanel.java | 74 +++++++++++++++++-- 1 file changed, 69 insertions(+), 5 deletions(-) diff --git a/src/org/infinity/resource/cre/viewer/AttributesPanel.java b/src/org/infinity/resource/cre/viewer/AttributesPanel.java index 3400a5c17..f2e0d3f2a 100644 --- a/src/org/infinity/resource/cre/viewer/AttributesPanel.java +++ b/src/org/infinity/resource/cre/viewer/AttributesPanel.java @@ -4,28 +4,40 @@ package org.infinity.resource.cre.viewer; -import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.util.Objects; +import javax.swing.JButton; import javax.swing.JLabel; +import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.border.EtchedBorder; +import org.infinity.gui.ViewFrame; +import org.infinity.gui.ViewerUtil; +import org.infinity.icon.Icons; +import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.decoder.SpriteDecoder; +import org.infinity.resource.key.ResourceEntry; /** * This panel provides a table view of creature animation attributes. */ -public class AttributesPanel extends JPanel +public class AttributesPanel extends JPanel implements ActionListener { private final CreatureViewer viewer; private JTable tableAnimation; private DecoderAttributesTableModel tableModel; + private JButton bIni; public AttributesPanel(CreatureViewer viewer) { @@ -42,11 +54,41 @@ public void reset() { SpriteDecoder decoder = getViewer().getDecoder(); tableModel.setDecoder(decoder); + bIni.setEnabled(getIniResource() != null); + } + + /** + * Returns the INI file associated with the currently selected creature animation if available, + * returns {@code null} otherwise. + */ + private ResourceEntry getIniResource() + { + ResourceEntry retVal = null; + if (getViewer().getDecoder() != null) { + String iniResref = String.format("%04X", getViewer().getDecoder().getAnimationId()); + if (iniResref.length() == 4) { + retVal = ResourceFactory.getResourceEntry(iniResref + ".INI"); + } + } + return retVal; } private void init() { JLabel label = new JLabel("Animation attributes:"); + + bIni = new JButton("View/Edit INI", Icons.getIcon(Icons.ICON_ZOOM_16)); + bIni.addActionListener(this); + + GridBagConstraints c = new GridBagConstraints(); + JPanel panel = new JPanel(new GridBagLayout()); + c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); + panel.add(label, c); + c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, + GridBagConstraints.NONE, new Insets(0, 8, 0, 0), 0, 0); + panel.add(bIni); + tableModel = new DecoderAttributesTableModel(); tableAnimation = new JTable(tableModel, new DecoderAttributesTableModel.AttributesColumnModel(), @@ -64,8 +106,30 @@ private void init() JScrollPane scrollTable = new JScrollPane(tableAnimation); scrollTable.setBorder(new EtchedBorder()); - setLayout(new BorderLayout()); - add(label, BorderLayout.PAGE_START); - add(scrollTable, BorderLayout.CENTER); + setLayout(new GridBagLayout()); + c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); + add(panel, c); + c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.BOTH, new Insets(4, 0, 0, 0), 0, 0); + add(scrollTable, c); } + + //--------------------- Begin Interface ActionListener --------------------- + + @Override + public void actionPerformed(ActionEvent e) + { + if (e.getSource() == bIni) { + ResourceEntry entry = getIniResource(); + if (entry != null) { + new ViewFrame(getViewer(), ResourceFactory.getResource(entry)); + } else { + JOptionPane.showMessageDialog(getViewer(), "Unable to open INI resource.", "View/Edit INI", + JOptionPane.ERROR_MESSAGE); + } + } + } + + //--------------------- End Interface ActionListener --------------------- } From 7e2b4a1cb83865fe662514292097f57a44a925dc Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 26 Mar 2021 11:42:41 +0100 Subject: [PATCH 087/158] Improve controls of Flag datatype --- src/org/infinity/datatype/Flag.java | 33 ++++++++++++++++------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/org/infinity/datatype/Flag.java b/src/org/infinity/datatype/Flag.java index 6add3c075..60eed848e 100644 --- a/src/org/infinity/datatype/Flag.java +++ b/src/org/infinity/datatype/Flag.java @@ -7,7 +7,8 @@ import java.awt.BorderLayout; import java.awt.Color; import java.awt.FlowLayout; -import java.awt.GridLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -23,6 +24,7 @@ import javax.swing.UIManager; import org.infinity.gui.StructViewer; +import org.infinity.gui.ViewerUtil; import org.infinity.resource.AbstractStruct; import org.infinity.util.Misc; @@ -122,21 +124,22 @@ public JComponent edit(ActionListener container) bPanel.add(bNone); bPanel.add(new JLabel("None = " + nodesc)); - JPanel boxPanel = new JPanel(new GridLayout(0, 4)); - int rows = checkBoxes.length >> 2; - if (rows << 2 != checkBoxes.length) { - for (int i = 0; i < checkBoxes.length; i++) { - boxPanel.add(checkBoxes[i]); - checkBoxes[i].setSelected(isFlagSet(i)); + // spreading flags over columns with 8 rows each + JPanel boxPanel = new JPanel(new GridBagLayout()); + int cols = checkBoxes.length / 8; + GridBagConstraints c = new GridBagConstraints(); + for (int col = 0; col < cols; col++) { + JPanel colPanel = new JPanel(new GridBagLayout()); + for (int row = 0; row < 8; row++) { + int idx = (col * 8) + row; + c = ViewerUtil.setGBC(c, 0, row, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); + colPanel.add(checkBoxes[idx], c); + checkBoxes[idx].setSelected(isFlagSet(idx)); } - } - else { - for (int i = 0; i < rows; i++) - for (int j = 0; j < 4; j++) { - int index = i + j * rows; - boxPanel.add(checkBoxes[index]); - checkBoxes[index].setSelected(isFlagSet(index)); - } + c = ViewerUtil.setGBC(c, col, 0, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.BOTH, new Insets(0, 8, 0, 8), 0, 0); + boxPanel.add(colPanel, c); } JPanel panel = new JPanel(new BorderLayout()); From 437e385b92ec666a59c5f456f23c093f339e7cbf Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 26 Mar 2021 15:26:09 +0100 Subject: [PATCH 088/158] SpriteDecoder: Fix endless loop when loading non-existing IWD animation --- src/org/infinity/resource/cre/decoder/SpriteDecoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index 091fa2ee4..690ab0b23 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -2177,7 +2177,7 @@ private static List guessIniMaps(int animationId) case MONSTER_ICEWIND: { boolean found = false; - while (resref.length() >= 4 && !found) { + if (resref.length() >= 4 && !found) { for (final String suffix : new String[] { "A1", "A2", "A3", "A4", "CA", "DE", "GH", "GU", "SC", "SD", "SL", "SP", "TW", "WK" }) { if (ResourceFactory.resourceExists(resref + suffix + ".BAM")) { found = true; From 0113eafd0d1a7ec999d631774425fb424e4563c3 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 26 Mar 2021 15:27:02 +0100 Subject: [PATCH 089/158] Creature Animation Browser: Improve behavior of animation combobox --- .../resource/cre/viewer/CreatureControlPanel.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/org/infinity/resource/cre/viewer/CreatureControlPanel.java b/src/org/infinity/resource/cre/viewer/CreatureControlPanel.java index caa5b6fb6..6396d256a 100644 --- a/src/org/infinity/resource/cre/viewer/CreatureControlPanel.java +++ b/src/org/infinity/resource/cre/viewer/CreatureControlPanel.java @@ -12,6 +12,8 @@ import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; @@ -26,6 +28,7 @@ import javax.swing.JScrollPane; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; +import javax.swing.text.JTextComponent; import org.infinity.gui.ViewerUtil; import org.infinity.icon.Icons; @@ -166,6 +169,15 @@ private void init() JLabel l2 = new JLabel("Creature animation:"); l2.setToolTipText("Supports manually entered numbers. Add \"0x\" prefix or \"h\" suffix to specify a hexadecimal number."); cbCreAnimation = new JComboBox<>(model.getModelAnimation()); + cbCreAnimation.getEditor().getEditorComponent().addFocusListener(new FocusAdapter() { + @Override + public void focusGained(FocusEvent e) + { + if (e.getSource() instanceof JTextComponent) { + ((JTextComponent)e.getSource()).selectAll(); + } + } + }); setPreferredWidth(cbCreAnimation, defWidth); cbCreAnimation.setEditable(true); cbCreAnimation.addActionListener(listeners); From 6cbb8c27258d82bd4aa37345bfa5dbc5bcc32974 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 26 Mar 2021 15:33:50 +0100 Subject: [PATCH 090/158] Fix flags bit 9 description in ITM Ability and SPL structure --- src/org/infinity/resource/itm/Ability.java | 2 +- src/org/infinity/resource/spl/SplResource.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/infinity/resource/itm/Ability.java b/src/org/infinity/resource/itm/Ability.java index 314137d2f..1e9a34d35 100644 --- a/src/org/infinity/resource/itm/Ability.java +++ b/src/org/infinity/resource/itm/Ability.java @@ -51,7 +51,7 @@ public final class Ability extends AbstractAbility implements AddRemovable, HasC public static final String[] s_abilityuse = {"", "Weapon", "Spell", "Item", "Ability", "reserved"}; public static final String[] s_recharge = { "No flags set", "Add strength bonus", "Breakable", "EE: Damage strength bonus", - "EE: THAC0 strength bonus", null, null, null, null, null, "EE: Break Sanctuary;Ignored for Target: Caster", + "EE: THAC0 strength bonus", null, null, null, null, null, "EE: Break Sanctuary/Invisibility;Ignored for Target: Caster", "Hostile", "Recharge after resting", null, null, null, null, "Bypass armor", "Keen edge", null, null, null, null, null, null, null, "Ex: Toggle backstab", "EE/Ex: Cannot target invisible"}; diff --git a/src/org/infinity/resource/spl/SplResource.java b/src/org/infinity/resource/spl/SplResource.java index 4447a7f50..0140a5340 100644 --- a/src/org/infinity/resource/spl/SplResource.java +++ b/src/org/infinity/resource/spl/SplResource.java @@ -110,7 +110,7 @@ public final class SplResource extends AbstractStruct implements Resource, HasCh "Divination", "Illusion", "Invocation", "Necromancy", "Innate"}; public static final String[] s_spellflag = {"No flags set", "", "", "", "", "", "", "", "", - "", "EE: Break Sanctuary", "Hostile", "No LOS required", + "", "EE: Break Sanctuary/Invisibility", "Hostile", "No LOS required", "Allow spotting", "Outdoors only", "Ignore dead/wild magic", "Ignore wild surge", "Non-combat ability", "", "", "", "", "", "", "", "EE/Ex: Can target invisible", "EE/Ex: Castable when silenced"}; From 64a8385a7b5cecc64eb0fa316a974dc0ac25a18c Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 26 Mar 2021 15:36:01 +0100 Subject: [PATCH 091/158] Improve PRO field name --- src/org/infinity/resource/pro/ProAreaType.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/infinity/resource/pro/ProAreaType.java b/src/org/infinity/resource/pro/ProAreaType.java index 2b6f95285..6ce55a1a3 100644 --- a/src/org/infinity/resource/pro/ProAreaType.java +++ b/src/org/infinity/resource/pro/ProAreaType.java @@ -44,7 +44,7 @@ public final class ProAreaType extends AbstractStruct implements AddRemovable, U public static final String PRO_AREA_EXPLOSION_ANIMATION = "Explosion animation"; public static final String PRO_AREA_CONE_WIDTH = "Cone width"; public static final String PRO_AREA_ANGLE_BETWEEN_RAYS = "Angle between rays"; - public static final String PRO_AREA_ROTATE_RAYS = "Rotate rays"; + public static final String PRO_AREA_ROTATE_RAYS = "Rotate rays clockwise"; public static final String PRO_AREA_SPREAD_ANIMATION = "Spread animation"; public static final String PRO_AREA_RING_ANIMATION = "Ring animation"; public static final String PRO_AREA_SOUND = "Area sound"; From bb0602a7e59164c80a56785ab12451e48c3831d6 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 26 Mar 2021 15:55:05 +0100 Subject: [PATCH 092/158] Creature Animation Browser: Improve order of available backgrounds --- .../resource/cre/viewer/SettingsPanel.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/org/infinity/resource/cre/viewer/SettingsPanel.java b/src/org/infinity/resource/cre/viewer/SettingsPanel.java index 72a89e2a1..861323aad 100644 --- a/src/org/infinity/resource/cre/viewer/SettingsPanel.java +++ b/src/org/infinity/resource/cre/viewer/SettingsPanel.java @@ -39,11 +39,16 @@ public class SettingsPanel extends JPanel // Available render canvas backgrounds public static final List backgroundList = new ArrayList() {{ add(Backgrounds.BG_COLOR_NONE); - add(Backgrounds.BG_WILDERNESS_BG); add(Backgrounds.BG_CAVE_BG); - add(Backgrounds.BG_CITY_NIGHT_SOD); add(Backgrounds.BG_WILDERNESS_IWD); - add(Backgrounds.BG_CITY_PST); add(Backgrounds.BG_DUNGEON_PST); - add(Backgrounds.BG_COLOR_WHITE); add(Backgrounds.BG_COLOR_BLACK); - add(Backgrounds.BG_COLOR_LIGHT_GRAY); add(Backgrounds.BG_COLOR_GRAY); + add(Backgrounds.BG_CAVE_BG); + add(Backgrounds.BG_CITY_NIGHT_SOD); + add(Backgrounds.BG_WILDERNESS_BG); + add(Backgrounds.BG_WILDERNESS_IWD); + add(Backgrounds.BG_CITY_PST); + add(Backgrounds.BG_DUNGEON_PST); + add(Backgrounds.BG_COLOR_WHITE); + add(Backgrounds.BG_COLOR_BLACK); + add(Backgrounds.BG_COLOR_LIGHT_GRAY); + add(Backgrounds.BG_COLOR_GRAY); }}; // Available items for zoom selection list From a7792efec23d5cccfbb2620086dd48bb68585b88 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 26 Mar 2021 17:18:19 +0100 Subject: [PATCH 093/158] Improve controls of CRE animation quick viewer --- .../resource/cre/ViewerAnimation.java | 242 ++++++++++-------- .../resource/cre/decoder/SpriteDecoder.java | 14 + .../resource/cre/viewer/MediaPanel.java | 16 +- 3 files changed, 147 insertions(+), 125 deletions(-) diff --git a/src/org/infinity/resource/cre/ViewerAnimation.java b/src/org/infinity/resource/cre/ViewerAnimation.java index e6e03eff7..fba5b46fa 100644 --- a/src/org/infinity/resource/cre/ViewerAnimation.java +++ b/src/org/infinity/resource/cre/ViewerAnimation.java @@ -15,8 +15,6 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.image.BufferedImage; -import java.util.ArrayList; -import java.util.List; import java.util.Objects; import javax.swing.BorderFactory; @@ -26,6 +24,7 @@ import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JOptionPane; +import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JToggleButton; import javax.swing.ScrollPaneConstants; @@ -36,7 +35,6 @@ import org.infinity.NearInfinity; import org.infinity.datatype.IsNumeric; import org.infinity.datatype.IsTextual; -import org.infinity.gui.ButtonPanel; import org.infinity.gui.Center; import org.infinity.gui.ChildFrame; import org.infinity.gui.RenderCanvas; @@ -56,42 +54,13 @@ */ public class ViewerAnimation extends ChildFrame implements ActionListener { - private static final Color TransparentColor = new Color(0, true); + private static final Color COLOR_TRANSPARENT = new Color(0, true); private static final int ANIM_DELAY = 1000 / 15; // 15 fps in milliseconds - private static final ButtonPanel.Control CtrlNextCycle = ButtonPanel.Control.CUSTOM_1; - private static final ButtonPanel.Control CtrlPrevCycle = ButtonPanel.Control.CUSTOM_2; - private static final ButtonPanel.Control CtrlNextFrame = ButtonPanel.Control.CUSTOM_3; - private static final ButtonPanel.Control CtrlPrevFrame = ButtonPanel.Control.CUSTOM_4; - private static final ButtonPanel.Control CtrlPlay = ButtonPanel.Control.CUSTOM_5; - private static final ButtonPanel.Control CtrlCycleLabel = ButtonPanel.Control.CUSTOM_6; - private static final ButtonPanel.Control CtrlFrameLabel = ButtonPanel.Control.CUSTOM_7; - private static final ButtonPanel.Control CtrlSequenceLabel = ButtonPanel.Control.CUSTOM_8; - private static final ButtonPanel.Control CtrlSequenceList = ButtonPanel.Control.CUSTOM_9; - private static final ButtonPanel.Control CtrlShowCircle = ButtonPanel.Control.CUSTOM_10; - private static final ButtonPanel.Control CtrlShowSpace = ButtonPanel.Control.CUSTOM_11; - private static final ButtonPanel.Control CtrlZoom = ButtonPanel.Control.CUSTOM_12; - private static final ButtonPanel.Control CtrlOpenViewer = ButtonPanel.Control.CUSTOM_13; - - // List of potential sequences to display when loading a new creature - private static final List InitialSequences = new ArrayList() {{ - add(Sequence.STAND); - add(Sequence.STAND2); - add(Sequence.STAND3); - add(Sequence.STAND_EMERGED); - add(Sequence.PST_STAND); - add(Sequence.STANCE); - add(Sequence.STANCE2); - add(Sequence.PST_STANCE); - add(Sequence.WALK); - add(Sequence.PST_WALK); - }}; - private static boolean zoom = false; private static boolean showSelectionCircle = false; private static boolean showPersonalSpace = false; - private final ButtonPanel buttonControlPanel = new ButtonPanel(); private final CreResource cre; private SpriteDecoder decoder; @@ -100,6 +69,11 @@ public class ViewerAnimation extends ChildFrame implements ActionListener private int curCycle, curFrame; private Timer timer; private Sequence sequence; + private JButton bNextCycle, bPrevCycle, bNextFrame, bPrevFrame, bOpenBrowser; + private JToggleButton bPlay; + private JLabel lCurCycle, lCurFrame; + private JComboBox cbSequences; + private JCheckBox cbShowCircle, cbShowSpace, cbZoom; public ViewerAnimation(CreResource cre) { @@ -176,6 +150,7 @@ private void resetAnimationSequence() throws Exception updateCanvasSize(); } + /** Ensures that the canvas is big enough to contain the current creature animation sequence. */ public void updateCanvasSize() { int zoom = isZoomed() ? 2 : 1; @@ -198,13 +173,14 @@ public void updateCanvasSize() updateCanvas(); } + /** Updates display with content of the current animation frame. */ public void updateCanvas() { BufferedImage image = (BufferedImage)rcDisplay.getImage(); Graphics2D g = image.createGraphics(); try { g.setComposite(AlphaComposite.Src); - g.setColor(TransparentColor); + g.setColor(COLOR_TRANSPARENT); g.fillRect(0, 0, image.getWidth(), image.getHeight()); } finally { g.dispose(); @@ -219,7 +195,7 @@ public void updateCanvas() /** Returns whether animation is zoomed. */ public boolean isZoomed() { - return ((JCheckBox)buttonControlPanel.getControlByType(CtrlZoom)).isSelected(); + return cbZoom.isSelected(); } /** Returns whether the animation is played back. */ @@ -284,13 +260,14 @@ protected boolean windowClosing(boolean forced) throws Exception public void actionPerformed(ActionEvent event) { if (timer == event.getSource()) { - curFrame += 1; - curFrame %= getController().cycleFrameCount(); + if (getController().cycleFrameCount() > 0) { + curFrame += 1; + curFrame %= getController().cycleFrameCount(); + } showFrame(); } - else if (buttonControlPanel.getControlByType(CtrlSequenceList) == event.getSource()) { - JComboBox cb = (JComboBox)buttonControlPanel.getControlByType(CtrlSequenceList); - Sequence seq = (Sequence)(cb).getSelectedItem(); + else if (cbSequences == event.getSource()) { + Sequence seq = cbSequences.getModel().getElementAt(cbSequences.getSelectedIndex()); try { WindowBlocker.blockWindow(this, true); setAnimationSequence(seq); @@ -298,24 +275,24 @@ else if (buttonControlPanel.getControlByType(CtrlSequenceList) == event.getSourc } catch (Exception e) { e.printStackTrace(); JOptionPane.showMessageDialog(this, e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); - cb.setSelectedItem(getAnimationSequence()); + cbSequences.setSelectedItem(getAnimationSequence()); } finally { WindowBlocker.blockWindow(this, false); } } - else if (buttonControlPanel.getControlByType(CtrlZoom) == event.getSource()) { + else if (cbZoom == event.getSource()) { try { WindowBlocker.blockWindow(this, true); - zoom = ((JCheckBox)buttonControlPanel.getControlByType(CtrlZoom)).isSelected(); + zoom = cbZoom.isSelected(); updateCanvasSize(); } finally { WindowBlocker.blockWindow(this, false); } } - else if (buttonControlPanel.getControlByType(CtrlShowCircle) == event.getSource()) { + else if (cbShowCircle == event.getSource()) { try { WindowBlocker.blockWindow(this, true); - showSelectionCircle = ((JCheckBox)buttonControlPanel.getControlByType(CtrlShowCircle)).isSelected(); + showSelectionCircle = cbShowCircle.isSelected(); getDecoder().setSelectionCircleEnabled(showSelectionCircle); resetAnimationSequence(); } catch (Exception e) { @@ -324,10 +301,10 @@ else if (buttonControlPanel.getControlByType(CtrlShowCircle) == event.getSource( WindowBlocker.blockWindow(this, false); } } - else if (buttonControlPanel.getControlByType(CtrlShowSpace) == event.getSource()) { + else if (cbShowSpace == event.getSource()) { try { WindowBlocker.blockWindow(this, true); - showPersonalSpace = ((JCheckBox)buttonControlPanel.getControlByType(CtrlShowSpace)).isSelected(); + showPersonalSpace = cbShowSpace.isSelected(); getDecoder().setPersonalSpaceVisible(showPersonalSpace); resetAnimationSequence(); } catch (Exception e) { @@ -336,51 +313,51 @@ else if (buttonControlPanel.getControlByType(CtrlShowSpace) == event.getSource() WindowBlocker.blockWindow(this, false); } } - else if (buttonControlPanel.getControlByType(CtrlPrevCycle) == event.getSource()) { + else if (bPrevCycle == event.getSource()) { if (curCycle > 0) { curCycle--; getController().cycleSet(curCycle); if (isPlaying() && getController().cycleFrameCount() == 0) { pause(); - ((JToggleButton)buttonControlPanel.getControlByType(CtrlPlay)).setSelected(false); + bPlay.setSelected(false); } rewind(); showFrame(); } } - else if (buttonControlPanel.getControlByType(CtrlNextCycle) == event.getSource()) { + else if (bNextCycle == event.getSource()) { if (curCycle < getController().cycleCount() - 1) { curCycle++; getController().cycleSet(curCycle); if (isPlaying() && getController().cycleFrameCount() == 0) { pause(); - ((JToggleButton)buttonControlPanel.getControlByType(CtrlPlay)).setSelected(false); + bPlay.setSelected(false); } rewind(); showFrame(); } } - else if (buttonControlPanel.getControlByType(CtrlPrevFrame) == event.getSource()) { + else if (bPrevFrame == event.getSource()) { if (curFrame > 0) { curFrame--; showFrame(); } } - else if (buttonControlPanel.getControlByType(CtrlNextFrame) == event.getSource()) { + else if (bNextFrame == event.getSource()) { if (curFrame < getController().cycleFrameCount() - 1) { curFrame++; showFrame(); } } - else if (buttonControlPanel.getControlByType(CtrlPlay) == event.getSource()) { - if (((JToggleButton)buttonControlPanel.getControlByType(CtrlPlay)).isSelected()) { + else if (bPlay == event.getSource()) { + if (bPlay.isSelected()) { play(); } else { pause(); } updateControls(); } - else if (buttonControlPanel.getControlByType(CtrlOpenViewer) == event.getSource()) { + else if (bOpenBrowser == event.getSource()) { CreatureViewer cv = ChildFrame.show(CreatureViewer.class, () -> new CreatureViewer(getCre())); if (cv != null) { if (getCre() != cv.getCreResource()) { @@ -404,76 +381,123 @@ private void init() throws Exception JScrollPane scrollDisplay = new JScrollPane(rcDisplay, ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); scrollDisplay.setBorder(BorderFactory.createEmptyBorder()); - JToggleButton bPlay = new JToggleButton("Play", Icons.getIcon(Icons.ICON_PLAY_16)); + bPlay = new JToggleButton("Play", Icons.getIcon(Icons.ICON_PLAY_16)); bPlay.addActionListener(this); - JLabel lCycle = new JLabel("", JLabel.CENTER); - JButton bPrevCycle = new JButton(Icons.getIcon(Icons.ICON_BACK_16)); + lCurCycle = new JLabel("", JLabel.CENTER); + bPrevCycle = new JButton(Icons.getIcon(Icons.ICON_BACK_16)); bPrevCycle.setMargin(new Insets(bPrevCycle.getMargin().top, 2, bPrevCycle.getMargin().bottom, 2)); bPrevCycle.addActionListener(this); - JButton bNextCycle = new JButton(Icons.getIcon(Icons.ICON_FORWARD_16)); + bNextCycle = new JButton(Icons.getIcon(Icons.ICON_FORWARD_16)); bNextCycle.setMargin(bPrevCycle.getMargin()); bNextCycle.addActionListener(this); - JLabel lFrame = new JLabel("", JLabel.CENTER); - lFrame.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0)); - JButton bPrevFrame = new JButton(Icons.getIcon(Icons.ICON_BACK_16)); + lCurFrame = new JLabel("", JLabel.CENTER); + bPrevFrame = new JButton(Icons.getIcon(Icons.ICON_BACK_16)); bPrevFrame.setMargin(new Insets(bPrevFrame.getMargin().top, 2, bPrevFrame.getMargin().bottom, 2)); bPrevFrame.addActionListener(this); - JButton bNextFrame = new JButton(Icons.getIcon(Icons.ICON_FORWARD_16)); + bNextFrame = new JButton(Icons.getIcon(Icons.ICON_FORWARD_16)); bNextFrame.setMargin(bPrevFrame.getMargin()); bNextFrame.addActionListener(this); JLabel lSequence = new JLabel("Sequence:"); - lSequence.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0)); DefaultComboBoxModel modelSequences = new DefaultComboBoxModel<>(); - JComboBox cbSequences = new JComboBox<>(modelSequences); - cbSequences.addActionListener(this); + cbSequences = new JComboBox<>(modelSequences); for (final Sequence seq : Sequence.values()) { if (getDecoder().isSequenceAvailable(seq)) { modelSequences.addElement(seq); } } cbSequences.setEnabled(cbSequences.getItemCount() > 0); + cbSequences.addActionListener(this); - JCheckBox cbZoom = new JCheckBox("Zoom", zoom); - cbZoom.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0)); + cbZoom = new JCheckBox("Zoom", zoom); cbZoom.addActionListener(this); getDecoder().setSelectionCircleEnabled(showSelectionCircle); - JCheckBox cbShowCircle = new JCheckBox("Show selection circle", getDecoder().isSelectionCircleEnabled()); - cbShowCircle.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0)); + cbShowCircle = new JCheckBox("Show selection circle", getDecoder().isSelectionCircleEnabled()); cbShowCircle.addActionListener(this); getDecoder().setPersonalSpaceVisible(showPersonalSpace); - JCheckBox cbShowSpace = new JCheckBox("Show personal space", getDecoder().isPersonalSpaceVisible()); - cbShowSpace.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0)); + cbShowSpace = new JCheckBox("Show personal space", getDecoder().isPersonalSpaceVisible()); cbShowSpace.addActionListener(this); - JButton bOpenViewer = new JButton("Open in browser", Icons.getIcon(Icons.ICON_CRE_VIEWER_24)); - bOpenViewer.setToolTipText("Open in Creature Animation Browser"); - bOpenViewer.addActionListener(this); - - buttonControlPanel.addControl(lCycle, CtrlCycleLabel); - buttonControlPanel.addControl(bPrevCycle, CtrlPrevCycle); - buttonControlPanel.addControl(bNextCycle, CtrlNextCycle); - buttonControlPanel.addControl(lFrame, CtrlFrameLabel); - buttonControlPanel.addControl(bPrevFrame, CtrlPrevFrame); - buttonControlPanel.addControl(bNextFrame, CtrlNextFrame); - buttonControlPanel.addControl(bPlay, CtrlPlay); - buttonControlPanel.addControl(lSequence, CtrlSequenceLabel); - buttonControlPanel.addControl(cbSequences, CtrlSequenceList); - buttonControlPanel.addControl(cbZoom, CtrlZoom); - buttonControlPanel.addControl(cbShowCircle, CtrlShowCircle); - buttonControlPanel.addControl(cbShowSpace, CtrlShowSpace); - buttonControlPanel.addControl(bOpenViewer, CtrlOpenViewer); + bOpenBrowser = new JButton("Open in browser", Icons.getIcon(Icons.ICON_CRE_VIEWER_24)); + bOpenBrowser.setToolTipText("Open in Creature Animation Browser"); + bOpenBrowser.addActionListener(this); + + GridBagConstraints c = new GridBagConstraints(); + // first row of controls: animation controls, sequence selection and browser button + JPanel pRow1 = new JPanel(new GridBagLayout()); + c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); + pRow1.add(new JPanel(), c); + c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); + pRow1.add(lCurCycle, c); + c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.NONE, new Insets(0, 8, 0, 0), 0, 0); + pRow1.add(bPrevCycle, c); + c = ViewerUtil.setGBC(c, 3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); + pRow1.add(bNextCycle, c); + + c = ViewerUtil.setGBC(c, 4, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.NONE, new Insets(0, 16, 0, 0), 0, 0); + pRow1.add(lCurFrame, c); + c = ViewerUtil.setGBC(c, 5, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.NONE, new Insets(0, 8, 0, 0), 0, 0); + pRow1.add(bPrevFrame, c); + c = ViewerUtil.setGBC(c, 6, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); + pRow1.add(bNextFrame, c); + + c = ViewerUtil.setGBC(c, 7, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.NONE, new Insets(0, 16, 0, 0), 0, 0); + pRow1.add(bPlay, c); + + c = ViewerUtil.setGBC(c, 8, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.NONE, new Insets(0, 16, 0, 0), 0, 0); + pRow1.add(lSequence, c); + c = ViewerUtil.setGBC(c, 9, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); + pRow1.add(cbSequences, c); + + c = ViewerUtil.setGBC(c, 10, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.NONE, new Insets(0, 16, 0, 0), 0, 0); + pRow1.add(bOpenBrowser, c); + + c = ViewerUtil.setGBC(c, 11, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); + pRow1.add(new JPanel(), c); + + // second row of controls: various checkboxes + JPanel pRow2 = new JPanel(new GridBagLayout()); + c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); + pRow2.add(new JPanel(), c); + c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); + pRow2.add(cbZoom, c); + c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.NONE, new Insets(0, 8, 0, 0), 0, 0); + pRow2.add(cbShowCircle, c); + c = ViewerUtil.setGBC(c, 3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.NONE, new Insets(0, 8, 0, 0), 0, 0); + pRow2.add(cbShowSpace, c); + c = ViewerUtil.setGBC(c, 4, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); + pRow2.add(new JPanel(), c); + setLayout(new GridBagLayout()); - GridBagConstraints c; - c = ViewerUtil.setGBC(null, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, + c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); add(scrollDisplay, c); - c = ViewerUtil.setGBC(null, 0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(8, 0, 8, 0), 0, 0); - add(buttonControlPanel, c); + c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, + GridBagConstraints.HORIZONTAL, new Insets(8, 0, 0, 0), 0, 0); + add(pRow1, c); + c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, + GridBagConstraints.HORIZONTAL, new Insets(8, 0, 8, 0), 0, 0); + add(pRow2, c); // determining creature resource and name String resName, name; @@ -511,7 +535,7 @@ private void init() throws Exception // loading animation sequence if (cbSequences.isEnabled()) { int seqIdx = 0; - for (final Sequence sequence : InitialSequences) { + for (final Sequence sequence : SpriteDecoder.DEFAULT_SEQUENCES) { int idx = ((DefaultComboBoxModel)cbSequences.getModel()).getIndexOf(sequence); if (idx >= 0) { seqIdx = idx; @@ -537,27 +561,25 @@ private void showFrame() updateCanvas(); - ((JLabel)buttonControlPanel.getControlByType(CtrlCycleLabel)) - .setText("Cycle: " + curCycle + "/" + (getController().cycleCount() - 1)); - ((JLabel)buttonControlPanel.getControlByType(CtrlFrameLabel)) - .setText("Frame: " + curFrame + "/" + (getController().cycleFrameCount() - 1)); + lCurCycle.setText("Cycle: " + curCycle + "/" + (getController().cycleCount() - 1)); + lCurFrame.setText("Frame: " + curFrame + "/" + (getController().cycleFrameCount() - 1)); updateControls(); } private void updateControls() { if (getController() != null) { - buttonControlPanel.getControlByType(CtrlPrevFrame).setEnabled(curFrame > 0); - buttonControlPanel.getControlByType(CtrlPrevCycle).setEnabled(curCycle > 0); - buttonControlPanel.getControlByType(CtrlNextFrame).setEnabled(curFrame < getController().cycleFrameCount() - 1); - buttonControlPanel.getControlByType(CtrlNextCycle).setEnabled(curCycle < getController().cycleCount() - 1); - buttonControlPanel.getControlByType(CtrlPlay).setEnabled(getController().cycleFrameCount() > 0); + bPrevFrame.setEnabled(curFrame > 0); + bPrevCycle.setEnabled(curCycle > 0); + bNextFrame.setEnabled(curFrame < getController().cycleFrameCount() - 1); + bNextCycle.setEnabled(curCycle < getController().cycleCount() - 1); + bPlay.setEnabled(getController().cycleFrameCount() > 0); } else { - buttonControlPanel.getControlByType(CtrlPrevFrame).setEnabled(false); - buttonControlPanel.getControlByType(CtrlPrevCycle).setEnabled(false); - buttonControlPanel.getControlByType(CtrlNextFrame).setEnabled(false); - buttonControlPanel.getControlByType(CtrlNextCycle).setEnabled(false); - buttonControlPanel.getControlByType(CtrlPlay).setEnabled(false); + bPrevFrame.setEnabled(false); + bPrevCycle.setEnabled(false); + bNextFrame.setEnabled(false); + bNextCycle.setEnabled(false); + bPlay.setEnabled(false); } } } diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index 690ab0b23..79583fadf 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -103,6 +103,20 @@ public abstract class SpriteDecoder extends PseudoBamDecoder public static final DecoderAttribute KEY_FALSE_COLOR = DecoderAttribute.with("false_color", DecoderAttribute.DataType.BOOLEAN); public static final DecoderAttribute KEY_TRANSLUCENT = DecoderAttribute.with("translucent", DecoderAttribute.DataType.BOOLEAN); + /** List of default animation sequences that are safe for initializing a new creature animation in order of preference. */ + public static final List DEFAULT_SEQUENCES = new ArrayList() {{ + add(Sequence.STAND); + add(Sequence.STAND2); + add(Sequence.STAND3); + add(Sequence.STAND_EMERGED); + add(Sequence.PST_STAND); + add(Sequence.STANCE); + add(Sequence.STANCE2); + add(Sequence.PST_STANCE); + add(Sequence.WALK); + add(Sequence.PST_WALK); + }}; + /** Mappings between animation types and compatible sprite classes. */ private static final EnumMap> typeAssociations = new EnumMap>(AnimationInfo.Type.class) {{ diff --git a/src/org/infinity/resource/cre/viewer/MediaPanel.java b/src/org/infinity/resource/cre/viewer/MediaPanel.java index ba697fe87..db09eedb1 100644 --- a/src/org/infinity/resource/cre/viewer/MediaPanel.java +++ b/src/org/infinity/resource/cre/viewer/MediaPanel.java @@ -45,20 +45,6 @@ */ public class MediaPanel extends JPanel { - // List of potential sequences to display when loading a new creature - private static final List DEFAULT_SEQUENCES = new ArrayList() {{ - add(Sequence.STAND); - add(Sequence.STAND2); - add(Sequence.STAND3); - add(Sequence.STAND_EMERGED); - add(Sequence.PST_STAND); - add(Sequence.STANCE); - add(Sequence.STANCE2); - add(Sequence.PST_STANCE); - add(Sequence.WALK); - add(Sequence.PST_WALK); - }}; - private static boolean isLoop; static { @@ -619,7 +605,7 @@ private void initSequences(SpriteDecoder decoder, Sequence defSeq) } if (idx < 0) { // try default sequence from list - for (final Sequence seq : DEFAULT_SEQUENCES) { + for (final Sequence seq : SpriteDecoder.DEFAULT_SEQUENCES) { idx = modelSequences.getIndexOf(seq); if (idx >= 0) { break; From c5541cb25db48c58baeebec549a5c912e762509a Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 26 Mar 2021 17:32:26 +0100 Subject: [PATCH 094/158] SpriteDecoder: Fix loading issue with CHR resources --- .../infinity/resource/cre/decoder/util/CreatureInfo.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java b/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java index d87a31223..43f664f84 100644 --- a/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java +++ b/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java @@ -549,7 +549,9 @@ public Couple getEffectiveColorValue(SegmentDef.SpriteType typ /** Returns the creature resource version. */ private String getCreatureVersion() { - return ((IsTextual)cre.getAttribute(CreResource.COMMON_VERSION)).getText().toUpperCase(Locale.ENGLISH); + String sig = ((IsTextual)cre.getAttribute(CreResource.COMMON_SIGNATURE)).getText().toUpperCase(Locale.ENGLISH); + String versionLabel = sig.equals("CHR ") ? CreResource.CHR_VERSION_2 : CreResource.COMMON_VERSION; + return ((IsTextual)cre.getAttribute(versionLabel)).getText().toUpperCase(Locale.ENGLISH); } private void init() throws Exception @@ -887,7 +889,7 @@ private int getEffectiveWeaponIndex(int slotIndex) if (retVal < 0) { // launcher required: find a weapon in weapon slots 1-4 with a matching item category - String creVer = ((IsTextual)Objects.requireNonNull(cre).getAttribute(CreResource.COMMON_VERSION)).getText().toUpperCase(Locale.ENGLISH); + String creVer = getCreatureVersion(); int idxWeaponSlots = 9; int slotGroupSize = creVer.equals("V2.2") ? 2 : 1; // IWD2 uses weapon/shield pairs for (int i = 0; i < 4; i++) { @@ -936,7 +938,7 @@ private int getWeaponSlotIndex(int slotId) return slotId; } - String creVer = ((IsTextual)Objects.requireNonNull(cre).getAttribute(CreResource.COMMON_VERSION)).getText().toUpperCase(Locale.ENGLISH); + String creVer = getCreatureVersion(); slotId += 35; // determine SLOTS.IDS value switch (slotId) { case 3: // IWD2: ammo1 From 3a6d567b951b1082b40987f8dea0eef7f0f8bc11 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sat, 27 Mar 2021 12:29:17 +0100 Subject: [PATCH 095/158] Creature Animation Browser: Improve UI controls - Creature panel: centered; moved buttons closer to CRE selection - Color selection: Color gradient image height depends on global font size - Improved alignments in various panels --- .../cre/viewer/ColorSelectionModel.java | 4 +- .../cre/viewer/CreatureControlPanel.java | 94 ++++++++++--------- .../resource/cre/viewer/MediaPanel.java | 2 +- .../resource/cre/viewer/SettingsPanel.java | 14 +-- .../resource/cre/viewer/bg/Backgrounds.java | 2 +- 5 files changed, 62 insertions(+), 54 deletions(-) diff --git a/src/org/infinity/resource/cre/viewer/ColorSelectionModel.java b/src/org/infinity/resource/cre/viewer/ColorSelectionModel.java index 6d603e3ca..9e5971cc4 100644 --- a/src/org/infinity/resource/cre/viewer/ColorSelectionModel.java +++ b/src/org/infinity/resource/cre/viewer/ColorSelectionModel.java @@ -29,6 +29,7 @@ import javax.swing.border.Border; import javax.swing.border.EmptyBorder; +import org.infinity.gui.BrowserMenuBar; import org.infinity.resource.Profile; import org.infinity.resource.ResourceFactory; import org.infinity.resource.graphics.GraphicsResource; @@ -308,7 +309,8 @@ public enum State { } private static final int DEFAULT_IMAGE_WIDTH = 128; // total width of color range - private static final int DEFAULT_IMAGE_HEIGHT = 24; + // take global font scaling into account + private static final int DEFAULT_IMAGE_HEIGHT = 20 * BrowserMenuBar.getInstance().getGlobalFontSize() / 100; private static final Color COLOR_INVALID = new Color(0xe0e0e0); private static final Color COLOR_RANDOM = Color.LIGHT_GRAY; diff --git a/src/org/infinity/resource/cre/viewer/CreatureControlPanel.java b/src/org/infinity/resource/cre/viewer/CreatureControlPanel.java index 6396d256a..2d090f113 100644 --- a/src/org/infinity/resource/cre/viewer/CreatureControlPanel.java +++ b/src/org/infinity/resource/cre/viewer/CreatureControlPanel.java @@ -7,6 +7,7 @@ import java.awt.BorderLayout; import java.awt.CardLayout; import java.awt.Dimension; +import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; @@ -156,7 +157,7 @@ private void init() GridBagConstraints c = new GridBagConstraints(); - // first column + // first column: creature and item selection JLabel l1 = new JLabel("Select CRE resource:"); cbCreSelection = new JComboBox<>(model.getModelCreature()); // this is a good default width for all selection controls in this panel @@ -189,25 +190,25 @@ public void focusGained(FocusEvent e) cbCreAllegiance.addActionListener(listeners); updateToolTip(cbCreAllegiance); - JLabel l4 = new JLabel("Helmet:"); + JLabel l4 = new JLabel("Helmet slot:"); cbItemHelmet = new JComboBox<>(model.getModelHelmet()); setPreferredWidth(cbItemHelmet, defWidth); cbItemHelmet.addActionListener(listeners); updateToolTip(cbItemHelmet); - JLabel l5 = new JLabel("Armor:"); + JLabel l5 = new JLabel("Armor slot:"); cbItemArmor = new JComboBox<>(model.getModelArmor()); setPreferredWidth(cbItemArmor, defWidth); cbItemArmor.addActionListener(listeners); updateToolTip(cbItemArmor); - JLabel l6 = new JLabel("Shield:"); + JLabel l6 = new JLabel("Shield slot:"); cbItemShield = new JComboBox<>(model.getModelShield()); setPreferredWidth(cbItemShield, defWidth); cbItemShield.addActionListener(listeners); updateToolTip(cbItemShield); - JLabel l7 = new JLabel("Weapon:"); + JLabel l7 = new JLabel("Weapon slot:"); cbItemWeapon = new JComboBox<>(model.getModelWeapon()); setPreferredWidth(cbItemWeapon, defWidth); cbItemWeapon.addActionListener(listeners); @@ -217,55 +218,54 @@ public void focusGained(FocusEvent e) c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); pColumn1.add(l1, c); - c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(0, 4, 0, 0), 0, 0); + c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(0, 8, 0, 0), 0, 0); pColumn1.add(cbCreSelection, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); pColumn1.add(l2, c); - c = ViewerUtil.setGBC(c, 1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(8, 4, 0, 0), 0, 0); + c = ViewerUtil.setGBC(c, 1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 8, 0, 0), 0, 0); pColumn1.add(cbCreAnimation, c); c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); pColumn1.add(l3, c); - c = ViewerUtil.setGBC(c, 1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(8, 4, 0, 0), 0, 0); + c = ViewerUtil.setGBC(c, 1, 2, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 8, 0, 0), 0, 0); pColumn1.add(cbCreAllegiance, c); - c = ViewerUtil.setGBC(c, 0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, - GridBagConstraints.NONE, new Insets(32, 0, 0, 0), 0, 0); + GridBagConstraints.NONE, new Insets(24, 0, 0, 0), 0, 0); pColumn1.add(l4, c); - c = ViewerUtil.setGBC(c, 1, 3, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(32, 4, 0, 0), 0, 0); + c = ViewerUtil.setGBC(c, 1, 3, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(24, 8, 0, 0), 0, 0); pColumn1.add(cbItemHelmet, c); c = ViewerUtil.setGBC(c, 0, 4, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); pColumn1.add(l5, c); - c = ViewerUtil.setGBC(c, 1, 4, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(8, 4, 0, 0), 0, 0); + c = ViewerUtil.setGBC(c, 1, 4, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 8, 0, 0), 0, 0); pColumn1.add(cbItemArmor, c); c = ViewerUtil.setGBC(c, 0, 5, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); pColumn1.add(l6, c); - c = ViewerUtil.setGBC(c, 1, 5, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(8, 4, 0, 0), 0, 0); + c = ViewerUtil.setGBC(c, 1, 5, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 8, 0, 0), 0, 0); pColumn1.add(cbItemShield, c); c = ViewerUtil.setGBC(c, 0, 6, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); pColumn1.add(l7, c); - c = ViewerUtil.setGBC(c, 1, 6, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(8, 4, 0, 0), 0, 0); + c = ViewerUtil.setGBC(c, 1, 6, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 8, 0, 0), 0, 0); pColumn1.add(cbItemWeapon, c); - // second column + // second column: color selection JComboBox cb; ColorSelectionModel cm = model.getModelColor(0); @@ -305,39 +305,41 @@ public void focusGained(FocusEvent e) c = ViewerUtil.setGBC(c, 0, i, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, GridBagConstraints.NONE, new Insets(top, 0, 0, 0), 0, 0); pColumn2.add(colorLabels.get(i), c); - c = ViewerUtil.setGBC(c, 1, i, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(top, 4, 0, 0), 0, 0); + c = ViewerUtil.setGBC(c, 1, i, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(top, 8, 0, 0), 0, 0); pColumn2.add(colorControls.get(i), c); } - // third column + // third column: buttons Insets margin; + bApply = new JButton("Apply", Icons.getIcon(Icons.ICON_REFRESH_16)); + Font fnt = bApply.getFont().deriveFont(bApply.getFont().getSize2D() * 1.25f); + bApply.setFont(fnt); + margin = bApply.getMargin(); + margin.top += 4; margin.bottom += 4; + bApply.setMargin(margin); + bApply.addActionListener(listeners); bReset = new JButton("Reset", Icons.getIcon(Icons.ICON_UNDO_16)); margin = bReset.getMargin(); margin.top += 4; margin.bottom += 4; bReset.setMargin(margin); bReset.setToolTipText("Revert to creature defaults"); bReset.addActionListener(listeners); - bApply = new JButton("Apply", Icons.getIcon(Icons.ICON_REFRESH_16)); - margin = bApply.getMargin(); - margin.top += 4; margin.bottom += 4; - bApply.setMargin(margin); - bApply.addActionListener(listeners); - JPanel pColumn3 = new JPanel(new GridBagLayout()); + JPanel pButtons = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); - pColumn3.add(bApply, c); + pButtons.add(bApply, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 0, 0, 0), 0, 0); - pColumn3.add(bReset, c); + pButtons.add(bReset, c); c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 0.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.VERTICAL, new Insets(8, 0, 0, 0), 0, 0); - pColumn3.add(new JPanel(), c); + pButtons.add(new JPanel(), c); - // fourth column + // fourth column: show/hide panel button bHidePanel = new JButton("Hide panel"); margin = bHidePanel.getMargin(); margin.top += 4; margin.bottom += 4; @@ -355,20 +357,24 @@ public void focusGained(FocusEvent e) // combining columns JPanel panelShown = new JPanel(new GridBagLayout()); - c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, + c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0); + panelShown.add(new JPanel(), c); + c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE, new Insets(8, 8, 8, 0), 0, 0); panelShown.add(pColumn1, c); - c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, - GridBagConstraints.NONE, new Insets(8, 16, 8, 0), 0, 0); - panelShown.add(pColumn2, c); c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, - GridBagConstraints.NONE, new Insets(8, 16, 8, 0), 0, 0); - panelShown.add(pColumn3, c); - c = ViewerUtil.setGBC(c, 3, 0, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.NONE, new Insets(8, 8, 8, 0), 0, 0); + panelShown.add(pButtons, c); + c = ViewerUtil.setGBC(c, 3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.NONE, new Insets(8, 32, 8, 0), 0, 0); + panelShown.add(pColumn2, c); + c = ViewerUtil.setGBC(c, 4, 0, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(8, 16, 8, 8), 0, 0); panelShown.add(pColumn4, c); - scrollShown = new JScrollPane(panelShown); + scrollShown = new JScrollPane(panelShown, JScrollPane.VERTICAL_SCROLLBAR_NEVER, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); + scrollShown.getHorizontalScrollBar().setUnitIncrement(32); scrollShown.setBorder(panelShown.getBorder()); // hidden panel @@ -383,7 +389,7 @@ public void focusGained(FocusEvent e) bHidePanel.setPreferredSize(new Dimension(defWidth, bHidePanel.getPreferredSize().height)); panelHidden = new JPanel(new GridBagLayout()); - c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_END, + c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_END, GridBagConstraints.NONE, new Insets(8, 8, 8, 8), 0, 0); panelHidden.add(bShowPanel, c); diff --git a/src/org/infinity/resource/cre/viewer/MediaPanel.java b/src/org/infinity/resource/cre/viewer/MediaPanel.java index db09eedb1..1389f3173 100644 --- a/src/org/infinity/resource/cre/viewer/MediaPanel.java +++ b/src/org/infinity/resource/cre/viewer/MediaPanel.java @@ -500,7 +500,7 @@ private void init() GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); pColumn1.add(pRow2, c); c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(16, 0, 0, 0), 0, 0); + GridBagConstraints.HORIZONTAL, new Insets(8, 0, 0, 0), 0, 0); pColumn1.add(pRow3, c); diff --git a/src/org/infinity/resource/cre/viewer/SettingsPanel.java b/src/org/infinity/resource/cre/viewer/SettingsPanel.java index 861323aad..32fb58dbb 100644 --- a/src/org/infinity/resource/cre/viewer/SettingsPanel.java +++ b/src/org/infinity/resource/cre/viewer/SettingsPanel.java @@ -393,27 +393,27 @@ private void init() cbBackground.addActionListener(listeners); JPanel panel1 = new JPanel(new GridBagLayout()); - c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_END, + c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, GridBagConstraints.VERTICAL, new Insets(0, 0, 0, 0), 0, 0); panel1.add(l1, c); - c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 4, 0, 0), 0, 0); panel1.add(cbZoom, c); - c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 1.0, GridBagConstraints.FIRST_LINE_START, + c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.VERTICAL, new Insets(0, 8, 0, 0), 0, 0); panel1.add(bCenter, c); - c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_END, + c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, GridBagConstraints.VERTICAL, new Insets(8, 0, 0, 0), 0, 0); panel1.add(l2, c); - c = ViewerUtil.setGBC(c, 1, 1, 2, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + c = ViewerUtil.setGBC(c, 1, 1, 2, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 4, 0, 0), 0, 0); panel1.add(cbFrameRate, c); - c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_END, + c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, GridBagConstraints.VERTICAL, new Insets(8, 0, 0, 0), 0, 0); panel1.add(l3, c); - c = ViewerUtil.setGBC(c, 1, 2, 2, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + c = ViewerUtil.setGBC(c, 1, 2, 2, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 4, 0, 0), 0, 0); panel1.add(cbBackground, c); diff --git a/src/org/infinity/resource/cre/viewer/bg/Backgrounds.java b/src/org/infinity/resource/cre/viewer/bg/Backgrounds.java index 1bbba5803..75207a563 100644 --- a/src/org/infinity/resource/cre/viewer/bg/Backgrounds.java +++ b/src/org/infinity/resource/cre/viewer/bg/Backgrounds.java @@ -36,7 +36,7 @@ public final class Backgrounds public static final BackgroundInfo BG_CITY_PST = new BackgroundInfo("Sigil city (PST)", new Color(0x494131), "pst_city.jpg", new Point(640, 580), EnumSet.of(Profile.Game.PST, Profile.Game.PSTEE)); - public static final BackgroundInfo BG_DUNGEON_PST = new BackgroundInfo("Sigil dungeon (PST)", new Color(0x494131), + public static final BackgroundInfo BG_DUNGEON_PST = new BackgroundInfo("Sigil dungeon (PST)", new Color(0x010101), "pst_dungeon.jpg", new Point(640, 480), EnumSet.of(Profile.Game.PST, Profile.Game.PSTEE)); public static final BackgroundInfo BG_COLOR_NONE = new BackgroundInfo("System color", null); From da29c30656d0f816e509df04f66000476e0ae999 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sat, 27 Mar 2021 15:08:13 +0100 Subject: [PATCH 096/158] Area Viewer: Fix lighting of actor animations --- .../resource/are/viewer/AreaViewer.java | 19 ++++++++++++------- .../resource/are/viewer/LayerActor.java | 2 ++ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/org/infinity/resource/are/viewer/AreaViewer.java b/src/org/infinity/resource/are/viewer/AreaViewer.java index bf6331d81..26b76671a 100644 --- a/src/org/infinity/resource/are/viewer/AreaViewer.java +++ b/src/org/infinity/resource/are/viewer/AreaViewer.java @@ -775,7 +775,7 @@ private void initGuiSettings() cbLayerRealActor[0].setSelected(Settings.ShowActorSprites == ViewerConstants.ANIM_SHOW_STILL); cbLayerRealActor[1].setSelected(false); updateRealActors(); - updateRealActorsLighting(getDayTime()); + updateRealActorsLighting(getVisualState()); // Setting up ambient sound ranges LayerAmbient layerAmbient = (LayerAmbient)layerManager.getLayer(ViewerConstants.LayerType.AMBIENT); @@ -794,7 +794,7 @@ private void initGuiSettings() cbLayerRealAnimation[0].setSelected(Settings.ShowRealAnimations == ViewerConstants.ANIM_SHOW_STILL); cbLayerRealAnimation[1].setSelected(false); updateRealAnimation(); - updateRealAnimationsLighting(getDayTime()); + updateRealAnimationsLighting(getVisualState()); updateWindowTitle(); applySettings(); @@ -867,17 +867,21 @@ private int getCurrentWedIndex() } - /** Returns the currently selected visual state (day/twilight/night). */ - private int getVisualState() + /** Returns the currently selected visual state (day/twilight/night) depending on whether the map supports day/night cycles. */ + public int getVisualState() { - return getDayTime(); + if (map.hasDayNight()) { + return getDayTime(); + } else { + return ViewerConstants.LIGHTING_DAY; + } } /** Set the lighting condition of the current map (day/twilight/night) and real background animations. */ private synchronized void setVisualState(int hour) { - while (hour < 0) { hour += 24; } hour %= 24; + while (hour < 0) { hour += 24; } int index = ViewerConstants.getDayTime(hour); if (!map.hasDayNight()) { index = ViewerConstants.LIGHTING_DAY; @@ -925,7 +929,8 @@ private synchronized void setVisualState(int hour) updateMiniMap(); updateToolBarButtons(); - updateRealAnimationsLighting(getDayTime()); + updateRealActorsLighting(getVisualState()); + updateRealAnimationsLighting(getVisualState()); updateScheduledItems(); updateWindowTitle(); } diff --git a/src/org/infinity/resource/are/viewer/LayerActor.java b/src/org/infinity/resource/are/viewer/LayerActor.java index 69cecbe89..e2581ff7d 100644 --- a/src/org/infinity/resource/are/viewer/LayerActor.java +++ b/src/org/infinity/resource/are/viewer/LayerActor.java @@ -108,6 +108,7 @@ public Void doInBackground() progress.setMillisToPopup(1000); try { + int visualState = getViewer().getVisualState(); for (int i = 0, size = list.size(); i < size; i++) { boolean state = isLayerVisible() && (!isScheduleEnabled() || isScheduled(i)); LayerObjectActor loa = list.get(i); @@ -126,6 +127,7 @@ public Void doInBackground() blocker.setBlocked(true); } loa.loadAnimation(); + loa.setLighting(visualState); } animItem.setVisible(state && realEnabled); From ca1e291c8716bfa895ede124d206e4867c3ffb05 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sat, 27 Mar 2021 21:04:11 +0100 Subject: [PATCH 097/158] SpriteDecoder: Improve Monster-type sequence detection; refactor default sequence list --- .../resource/cre/ViewerAnimation.java | 2 +- .../resource/cre/decoder/MonsterDecoder.java | 19 +++++++++++----- .../resource/cre/decoder/SpriteDecoder.java | 14 ------------ .../resource/cre/decoder/util/Sequence.java | 22 +++++++++++++++++++ .../resource/cre/viewer/MediaPanel.java | 2 +- 5 files changed, 37 insertions(+), 22 deletions(-) diff --git a/src/org/infinity/resource/cre/ViewerAnimation.java b/src/org/infinity/resource/cre/ViewerAnimation.java index fba5b46fa..4167f5835 100644 --- a/src/org/infinity/resource/cre/ViewerAnimation.java +++ b/src/org/infinity/resource/cre/ViewerAnimation.java @@ -535,7 +535,7 @@ private void init() throws Exception // loading animation sequence if (cbSequences.isEnabled()) { int seqIdx = 0; - for (final Sequence sequence : SpriteDecoder.DEFAULT_SEQUENCES) { + for (final Sequence sequence : Sequence.getDefaultSequences()) { int idx = ((DefaultComboBoxModel)cbSequences.getModel()).getIndexOf(sequence); if (idx >= 0) { seqIdx = idx; diff --git a/src/org/infinity/resource/cre/decoder/MonsterDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterDecoder.java index 8847e6e0b..4fdbcd5c1 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterDecoder.java @@ -271,20 +271,21 @@ protected SeqDef getSequenceDefinition(Sequence seq) // processing creature sprite String resref = getAnimationResref(); String suffix = data.getValue0(); - if (!SpriteUtils.bamCyclesExist(ResourceFactory.getResourceEntry(resref + SegmentDef.fixBehaviorSuffix(suffix) + ".BAM"), - data.getValue1().intValue(), 9)) { + int ofs = data.getValue1().intValue(); + ResourceEntry bamEntry = ResourceFactory.getResourceEntry(resref + SegmentDef.fixBehaviorSuffix(suffix) + ".BAM"); + if (!SpriteUtils.bamCyclesExist(bamEntry, ofs, 1)) { data = (isSplittedBams() ? replacementMapSplit: replacementMapUnsplit).get(seq); if (data == null) { return retVal; } suffix = data.getValue0(); - if (!ResourceFactory.resourceExists(resref + SegmentDef.fixBehaviorSuffix(suffix) + ".BAM")) { + bamEntry = ResourceFactory.getResourceEntry(resref + SegmentDef.fixBehaviorSuffix(suffix) + ".BAM"); + if (!SpriteUtils.bamCyclesExist(bamEntry, ofs, 1)) { return retVal; } } SegmentDef.Behavior behavior = SegmentDef.getBehaviorOf(suffix); suffix = SegmentDef.fixBehaviorSuffix(suffix); - int ofs = data.getValue1().intValue(); creResList.add(Couple.with(resref + suffix + ".BAM", SegmentDef.SpriteType.AVATAR)); // processing weapon overlay @@ -310,8 +311,14 @@ protected SeqDef getSequenceDefinition(Sequence seq) retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); } else if (entry != null && SpriteUtils.getBamCycles(entry) == 1) { // fallback solution: just use first bam cycle (required by a few animations) - SeqDef tmp = SeqDef.createSequence(seq, new Direction[] {Direction.S}, false, entry, 0, type, behavior); - retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); + for (final Direction dir : SeqDef.DIR_FULL_W) { + SeqDef tmp = SeqDef.createSequence(seq, new Direction[] {dir}, false, entry, 0, type, behavior); + retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); + } + for (final Direction dir : SeqDef.DIR_FULL_E) { + SeqDef tmp = SeqDef.createSequence(seq, new Direction[] {dir}, true, entry, 0, type, behavior); + retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); + } } } diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index 79583fadf..690ab0b23 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -103,20 +103,6 @@ public abstract class SpriteDecoder extends PseudoBamDecoder public static final DecoderAttribute KEY_FALSE_COLOR = DecoderAttribute.with("false_color", DecoderAttribute.DataType.BOOLEAN); public static final DecoderAttribute KEY_TRANSLUCENT = DecoderAttribute.with("translucent", DecoderAttribute.DataType.BOOLEAN); - /** List of default animation sequences that are safe for initializing a new creature animation in order of preference. */ - public static final List DEFAULT_SEQUENCES = new ArrayList() {{ - add(Sequence.STAND); - add(Sequence.STAND2); - add(Sequence.STAND3); - add(Sequence.STAND_EMERGED); - add(Sequence.PST_STAND); - add(Sequence.STANCE); - add(Sequence.STANCE2); - add(Sequence.PST_STANCE); - add(Sequence.WALK); - add(Sequence.PST_WALK); - }}; - /** Mappings between animation types and compatible sprite classes. */ private static final EnumMap> typeAssociations = new EnumMap>(AnimationInfo.Type.class) {{ diff --git a/src/org/infinity/resource/cre/decoder/util/Sequence.java b/src/org/infinity/resource/cre/decoder/util/Sequence.java index b2afd0120..dde47d3ef 100644 --- a/src/org/infinity/resource/cre/decoder/util/Sequence.java +++ b/src/org/infinity/resource/cre/decoder/util/Sequence.java @@ -4,6 +4,9 @@ package org.infinity.resource.cre.decoder.util; +import java.util.ArrayList; +import java.util.List; + /** * Available animation sequences. Note: PST-specific animation sequences are prefixed by "PST_". */ @@ -119,6 +122,19 @@ public enum Sequence { PST_MISC19("Custom sequence 19"), PST_MISC20("Custom sequence 20"); + private static final List DEFAULT_SEQUENCES = new ArrayList() {{ + add(Sequence.STAND); + add(Sequence.STAND2); + add(Sequence.STAND3); + add(Sequence.STAND_EMERGED); + add(Sequence.PST_STAND); + add(Sequence.STANCE); + add(Sequence.STANCE2); + add(Sequence.PST_STANCE); + add(Sequence.WALK); + add(Sequence.PST_WALK); + }}; + private final String desc; /** Creates a new {@code AnimationSequence} with an empty label. */ @@ -132,4 +148,10 @@ public String toString() { return (desc != null) ? desc : super.toString(); } + + /** + * Returns a list of default animation sequences that are safe for initializing a new + * creature animation in order of preference. + */ + public static List getDefaultSequences() { return DEFAULT_SEQUENCES; } } diff --git a/src/org/infinity/resource/cre/viewer/MediaPanel.java b/src/org/infinity/resource/cre/viewer/MediaPanel.java index 1389f3173..a98dcda2a 100644 --- a/src/org/infinity/resource/cre/viewer/MediaPanel.java +++ b/src/org/infinity/resource/cre/viewer/MediaPanel.java @@ -605,7 +605,7 @@ private void initSequences(SpriteDecoder decoder, Sequence defSeq) } if (idx < 0) { // try default sequence from list - for (final Sequence seq : SpriteDecoder.DEFAULT_SEQUENCES) { + for (final Sequence seq : Sequence.getDefaultSequences()) { idx = modelSequences.getIndexOf(seq); if (idx >= 0) { break; From f97965ea8770e2f8d29a9ede95f7cd38f2236e24 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sat, 27 Mar 2021 21:23:40 +0100 Subject: [PATCH 098/158] SpriteDecoder: More refactoring Moved static functionality from SpriteDecoder to SpriteUtils class. --- .../resource/cre/decoder/AmbientDecoder.java | 2 +- .../cre/decoder/AmbientStaticDecoder.java | 2 +- .../cre/decoder/CharacterDecoder.java | 2 +- .../cre/decoder/CharacterOldDecoder.java | 2 +- .../resource/cre/decoder/EffectDecoder.java | 2 +- .../resource/cre/decoder/FlyingDecoder.java | 2 +- .../cre/decoder/MonsterAnkhegDecoder.java | 2 +- .../resource/cre/decoder/MonsterDecoder.java | 2 +- .../cre/decoder/MonsterIcewindDecoder.java | 2 +- .../cre/decoder/MonsterLarge16Decoder.java | 2 +- .../cre/decoder/MonsterLargeDecoder.java | 2 +- .../cre/decoder/MonsterLayeredDecoder.java | 2 +- .../decoder/MonsterLayeredSpellDecoder.java | 2 +- .../cre/decoder/MonsterMultiDecoder.java | 2 +- .../cre/decoder/MonsterMultiNewDecoder.java | 2 +- .../cre/decoder/MonsterOldDecoder.java | 2 +- .../cre/decoder/MonsterPlanescapeDecoder.java | 2 +- .../cre/decoder/MonsterQuadrantDecoder.java | 2 +- .../resource/cre/decoder/SpriteDecoder.java | 799 +----------------- .../cre/decoder/TownStaticDecoder.java | 2 +- .../cre/decoder/tables/InfinityTables.java | 3 +- .../cre/decoder/tables/SpriteTables.java | 3 +- .../cre/decoder/util/SpriteUtils.java | 764 +++++++++++++++++ 23 files changed, 822 insertions(+), 785 deletions(-) diff --git a/src/org/infinity/resource/cre/decoder/AmbientDecoder.java b/src/org/infinity/resource/cre/decoder/AmbientDecoder.java index cfa7f3b1d..f6303a56b 100644 --- a/src/org/infinity/resource/cre/decoder/AmbientDecoder.java +++ b/src/org/infinity/resource/cre/decoder/AmbientDecoder.java @@ -75,7 +75,7 @@ public static IniMap processTableData(String[] data) } int falseColor = SpriteTables.valueToInt(data, SpriteTables.COLUMN_CLOWN, 0); - List lines = processTableDataGeneral(data, ANIMATION_TYPE); + List lines = SpriteUtils.processTableDataGeneral(data, ANIMATION_TYPE); lines.add("[ambient]"); lines.add("false_color=" + falseColor); lines.add("resref=" + resref); diff --git a/src/org/infinity/resource/cre/decoder/AmbientStaticDecoder.java b/src/org/infinity/resource/cre/decoder/AmbientStaticDecoder.java index 76752afc1..7547327a1 100644 --- a/src/org/infinity/resource/cre/decoder/AmbientStaticDecoder.java +++ b/src/org/infinity/resource/cre/decoder/AmbientStaticDecoder.java @@ -63,7 +63,7 @@ public static IniMap processTableData(String[] data) } int falseColor = SpriteTables.valueToInt(data, SpriteTables.COLUMN_CLOWN, 0); - List lines = processTableDataGeneral(data, ANIMATION_TYPE); + List lines = SpriteUtils.processTableDataGeneral(data, ANIMATION_TYPE); lines.add("[ambient_static]"); lines.add("false_color=" + falseColor); lines.add("resref=" + resref); diff --git a/src/org/infinity/resource/cre/decoder/CharacterDecoder.java b/src/org/infinity/resource/cre/decoder/CharacterDecoder.java index 3c678bfb9..32501df2e 100644 --- a/src/org/infinity/resource/cre/decoder/CharacterDecoder.java +++ b/src/org/infinity/resource/cre/decoder/CharacterDecoder.java @@ -179,7 +179,7 @@ public static IniMap processTableData(String[] data) String heightCodeShield = SpriteTables.valueToString(data, SpriteTables.COLUMN_HEIGHT_SHIELD, ""); String resrefSpecific = SpriteTables.valueToString(data, SpriteTables.COLUMN_RESREF2, ""); - List lines = processTableDataGeneral(data, ANIMATION_TYPE); + List lines = SpriteUtils.processTableDataGeneral(data, ANIMATION_TYPE); lines.add("[character]"); lines.add("equip_helmet=" + equipHelmet); lines.add("split_bams=" + splitBams); diff --git a/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java b/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java index b8cb4b431..2b2204d98 100644 --- a/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java +++ b/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java @@ -127,7 +127,7 @@ public static IniMap processTableData(String[] data) String heightCodeHelmet = heightCode; String shadow = SpriteTables.valueToString(data, SpriteTables.COLUMN_RESREF2, ""); - List lines = processTableDataGeneral(data, ANIMATION_TYPE); + List lines = SpriteUtils.processTableDataGeneral(data, ANIMATION_TYPE); lines.add("[character_old]"); lines.add("equip_helmet=" + equipHelmet); lines.add("false_color=" + falseColor); diff --git a/src/org/infinity/resource/cre/decoder/EffectDecoder.java b/src/org/infinity/resource/cre/decoder/EffectDecoder.java index 01912ebc5..c9963396c 100644 --- a/src/org/infinity/resource/cre/decoder/EffectDecoder.java +++ b/src/org/infinity/resource/cre/decoder/EffectDecoder.java @@ -75,7 +75,7 @@ public static IniMap processTableData(String[] data) String altPalette = SpriteTables.valueToString(data, SpriteTables.COLUMN_PALETTE2, ""); String resref2 = SpriteTables.valueToString(data, SpriteTables.COLUMN_HEIGHT, ""); - List lines = processTableDataGeneral(data, ANIMATION_TYPE); + List lines = SpriteUtils.processTableDataGeneral(data, ANIMATION_TYPE); lines.add("[effect]"); lines.add("resref=" + resref); if (!shadow.isEmpty()) { diff --git a/src/org/infinity/resource/cre/decoder/FlyingDecoder.java b/src/org/infinity/resource/cre/decoder/FlyingDecoder.java index e41bf87ef..6ff9dd1f9 100644 --- a/src/org/infinity/resource/cre/decoder/FlyingDecoder.java +++ b/src/org/infinity/resource/cre/decoder/FlyingDecoder.java @@ -47,7 +47,7 @@ public static IniMap processTableData(String[] data) } int falseColor = SpriteTables.valueToInt(data, SpriteTables.COLUMN_CLOWN, 0); - List lines = processTableDataGeneral(data, ANIMATION_TYPE); + List lines = SpriteUtils.processTableDataGeneral(data, ANIMATION_TYPE); lines.add("[flying]"); lines.add("false_color=" + falseColor); lines.add("resref=" + resref); diff --git a/src/org/infinity/resource/cre/decoder/MonsterAnkhegDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterAnkhegDecoder.java index 6254ec4fa..32d61dbdb 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterAnkhegDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterAnkhegDecoder.java @@ -69,7 +69,7 @@ public static IniMap processTableData(String[] data) } int falseColor = SpriteTables.valueToInt(data, SpriteTables.COLUMN_CLOWN, 0); - List lines = processTableDataGeneral(data, ANIMATION_TYPE); + List lines = SpriteUtils.processTableDataGeneral(data, ANIMATION_TYPE); lines.add("[monster_ankheg]"); lines.add("false_color=" + falseColor); lines.add("resref=" + resref); diff --git a/src/org/infinity/resource/cre/decoder/MonsterDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterDecoder.java index 4fdbcd5c1..d646cf1db 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterDecoder.java @@ -128,7 +128,7 @@ public static IniMap processTableData(String[] data) String palette1 = SpriteTables.valueToString(data, SpriteTables.COLUMN_PALETTE, ""); String palette2 = SpriteTables.valueToString(data, SpriteTables.COLUMN_PALETTE2, ""); - List lines = processTableDataGeneral(data, ANIMATION_TYPE); + List lines = SpriteUtils.processTableDataGeneral(data, ANIMATION_TYPE); lines.add("[monster]"); lines.add("false_color=" + falseColor); lines.add("split_bams=" + splitBams); diff --git a/src/org/infinity/resource/cre/decoder/MonsterIcewindDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterIcewindDecoder.java index 39d7d7100..5b5464e6e 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterIcewindDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterIcewindDecoder.java @@ -77,7 +77,7 @@ public static IniMap processTableData(String[] data) int translucent = SpriteTables.valueToInt(data, SpriteTables.COLUMN_TRANSLUCENT, 0); int leftHanded = SpriteTables.valueToInt(data, SpriteTables.COLUMN_WEAPON, 0); - List lines = processTableDataGeneral(data, ANIMATION_TYPE); + List lines = SpriteUtils.processTableDataGeneral(data, ANIMATION_TYPE); lines.add("[monster_icewind]"); lines.add("weapon_left_hand=" + leftHanded); lines.add("translucent=" + translucent); diff --git a/src/org/infinity/resource/cre/decoder/MonsterLarge16Decoder.java b/src/org/infinity/resource/cre/decoder/MonsterLarge16Decoder.java index dd1e974e0..f86c694c5 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterLarge16Decoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterLarge16Decoder.java @@ -65,7 +65,7 @@ public static IniMap processTableData(String[] data) } int falseColor = SpriteTables.valueToInt(data, SpriteTables.COLUMN_CLOWN, 0); - List lines = processTableDataGeneral(data, ANIMATION_TYPE); + List lines = SpriteUtils.processTableDataGeneral(data, ANIMATION_TYPE); lines.add("[monster_large16]"); lines.add("false_color=" + falseColor); lines.add("resref=" + resref); diff --git a/src/org/infinity/resource/cre/decoder/MonsterLargeDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterLargeDecoder.java index 4aaa7cf66..27f6ba399 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterLargeDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterLargeDecoder.java @@ -64,7 +64,7 @@ public static IniMap processTableData(String[] data) } int falseColor = SpriteTables.valueToInt(data, SpriteTables.COLUMN_CLOWN, 0); - List lines = processTableDataGeneral(data, ANIMATION_TYPE); + List lines = SpriteUtils.processTableDataGeneral(data, ANIMATION_TYPE); lines.add("[monster_large]"); lines.add("false_color=" + falseColor); lines.add("resref=" + resref); diff --git a/src/org/infinity/resource/cre/decoder/MonsterLayeredDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterLayeredDecoder.java index 16b7851a8..421d668fd 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterLayeredDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterLayeredDecoder.java @@ -70,7 +70,7 @@ public static IniMap processTableData(String[] data) String resrefWeapon1 = SpriteTables.valueToString(data, SpriteTables.COLUMN_HEIGHT, ""); String resrefWeapon2 = SpriteTables.valueToString(data, SpriteTables.COLUMN_HEIGHT_SHIELD, ""); - List lines = processTableDataGeneral(data, ANIMATION_TYPE); + List lines = SpriteUtils.processTableDataGeneral(data, ANIMATION_TYPE); lines.add("[monster_layered]"); lines.add("resref=" + resref); lines.add("resref_weapon1=" + resrefWeapon1); diff --git a/src/org/infinity/resource/cre/decoder/MonsterLayeredSpellDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterLayeredSpellDecoder.java index 5c42a3e7f..38dd83d0a 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterLayeredSpellDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterLayeredSpellDecoder.java @@ -75,7 +75,7 @@ public static IniMap processTableData(String[] data) String resrefWeapon1 = SpriteTables.valueToString(data, SpriteTables.COLUMN_HEIGHT, ""); String resrefWeapon2 = SpriteTables.valueToString(data, SpriteTables.COLUMN_HEIGHT_SHIELD, ""); - List lines = processTableDataGeneral(data, ANIMATION_TYPE); + List lines = SpriteUtils.processTableDataGeneral(data, ANIMATION_TYPE); lines.add("[monster_layered_spell]"); lines.add("dual_attack=" + dualAttack); lines.add("false_color=" + falseColor); diff --git a/src/org/infinity/resource/cre/decoder/MonsterMultiDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterMultiDecoder.java index 5f809cc3d..c5ebfd597 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterMultiDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterMultiDecoder.java @@ -105,7 +105,7 @@ public static IniMap processTableData(String[] data) int splitBams = SpriteTables.valueToInt(data, SpriteTables.COLUMN_SPLIT, 0); String paletteBase = SpriteTables.valueToString(data, SpriteTables.COLUMN_PALETTE, ""); - List lines = processTableDataGeneral(data, ANIMATION_TYPE); + List lines = SpriteUtils.processTableDataGeneral(data, ANIMATION_TYPE); lines.add("[monster_multi]"); lines.add("quadrants=9"); lines.add("split_bams=" + splitBams); diff --git a/src/org/infinity/resource/cre/decoder/MonsterMultiNewDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterMultiNewDecoder.java index 73babf147..a13607d59 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterMultiNewDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterMultiNewDecoder.java @@ -95,7 +95,7 @@ public static IniMap processTableData(String[] data) int splitBams = SpriteTables.valueToInt(data, SpriteTables.COLUMN_SPLIT, 0); int translucent = SpriteTables.valueToInt(data, SpriteTables.COLUMN_TRANSLUCENT, 0); - List lines = processTableDataGeneral(data, ANIMATION_TYPE); + List lines = SpriteUtils.processTableDataGeneral(data, ANIMATION_TYPE); lines.add("[multi_new]"); lines.add("false_color=" + falseColor); lines.add("quadrants=4"); diff --git a/src/org/infinity/resource/cre/decoder/MonsterOldDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterOldDecoder.java index 2f20c61ab..0a02d3733 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterOldDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterOldDecoder.java @@ -77,7 +77,7 @@ public static IniMap processTableData(String[] data) int falseColor = SpriteTables.valueToInt(data, SpriteTables.COLUMN_CLOWN, 0); int translucent = SpriteTables.valueToInt(data, SpriteTables.COLUMN_TRANSLUCENT, 0); - List lines = processTableDataGeneral(data, ANIMATION_TYPE); + List lines = SpriteUtils.processTableDataGeneral(data, ANIMATION_TYPE); lines.add("[monster_old]"); lines.add("false_color=" + falseColor); lines.add("translucent=" + translucent); diff --git a/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java index 3a5bbed90..b46e5d6a3 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java @@ -252,7 +252,7 @@ public static IniMap processTableData(String[] data) int bestiary = SpriteTables.valueToInt(data, SpriteTables.COLUMN_PST_BESTIARY, 0); int armor = SpriteTables.valueToInt(data, SpriteTables.COLUMN_PST_ARMOR, 0); - List lines = processTableDataGeneralPst(data); + List lines = SpriteUtils.processTableDataGeneralPst(data); lines.add("[monster_planescape]"); for (final String action : actions.keySet()) { lines.add(action + "=" + actions.get(action)); diff --git a/src/org/infinity/resource/cre/decoder/MonsterQuadrantDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterQuadrantDecoder.java index d6dcb1d27..893c9dc87 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterQuadrantDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterQuadrantDecoder.java @@ -80,7 +80,7 @@ public static IniMap processTableData(String[] data) extendDirectionTest = 5; // TBC } - List lines = processTableDataGeneral(data, ANIMATION_TYPE); + List lines = SpriteUtils.processTableDataGeneral(data, ANIMATION_TYPE); lines.add("[monster_quadrant]"); lines.add("false_color=" + falseColor); lines.add("caster=" + caster); diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index 690ab0b23..787534e35 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -22,7 +22,6 @@ import java.awt.image.DataBufferInt; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; -import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.EnumMap; @@ -37,7 +36,6 @@ import org.infinity.resource.Profile; import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; -import org.infinity.resource.cre.decoder.tables.SpriteTables; import org.infinity.resource.cre.decoder.util.AnimationInfo; import org.infinity.resource.cre.decoder.util.ColorInfo; import org.infinity.resource.cre.decoder.util.CreatureInfo; @@ -51,22 +49,14 @@ import org.infinity.resource.cre.decoder.util.SeqDef; import org.infinity.resource.cre.decoder.util.Sequence; import org.infinity.resource.cre.decoder.util.SpriteUtils; -import org.infinity.resource.cre.viewer.icon.Icons; import org.infinity.resource.graphics.ColorConvert; import org.infinity.resource.graphics.PseudoBamDecoder; import org.infinity.resource.graphics.BamV1Decoder.BamV1Control; import org.infinity.resource.graphics.BlendingComposite; -import org.infinity.resource.key.BufferedResourceEntry; import org.infinity.resource.key.ResourceEntry; -import org.infinity.util.IdsMap; -import org.infinity.util.IdsMapCache; -import org.infinity.util.IdsMapEntry; import org.infinity.util.IniMap; -import org.infinity.util.IniMapCache; -import org.infinity.util.IniMapEntry; import org.infinity.util.IniMapSection; import org.infinity.util.Misc; -import org.infinity.util.Table2da; import org.infinity.util.tuples.Couple; /** @@ -103,30 +93,6 @@ public abstract class SpriteDecoder extends PseudoBamDecoder public static final DecoderAttribute KEY_FALSE_COLOR = DecoderAttribute.with("false_color", DecoderAttribute.DataType.BOOLEAN); public static final DecoderAttribute KEY_TRANSLUCENT = DecoderAttribute.with("translucent", DecoderAttribute.DataType.BOOLEAN); - /** Mappings between animation types and compatible sprite classes. */ - private static final EnumMap> typeAssociations = - new EnumMap>(AnimationInfo.Type.class) {{ - put(AnimationInfo.Type.EFFECT, EffectDecoder.class); - put(AnimationInfo.Type.MONSTER_QUADRANT, MonsterQuadrantDecoder.class); - put(AnimationInfo.Type.MONSTER_MULTI, MonsterMultiDecoder.class); - put(AnimationInfo.Type.MONSTER_MULTI_NEW, MonsterMultiNewDecoder.class); - put(AnimationInfo.Type.MONSTER_LAYERED_SPELL, MonsterLayeredSpellDecoder.class); - put(AnimationInfo.Type.MONSTER_ANKHEG, MonsterAnkhegDecoder.class); - put(AnimationInfo.Type.TOWN_STATIC, TownStaticDecoder.class); - put(AnimationInfo.Type.CHARACTER, CharacterDecoder.class); - put(AnimationInfo.Type.CHARACTER_OLD, CharacterOldDecoder.class); - put(AnimationInfo.Type.MONSTER, MonsterDecoder.class); - put(AnimationInfo.Type.MONSTER_OLD, MonsterOldDecoder.class); - put(AnimationInfo.Type.MONSTER_LAYERED, MonsterLayeredDecoder.class); - put(AnimationInfo.Type.MONSTER_LARGE, MonsterLargeDecoder.class); - put(AnimationInfo.Type.MONSTER_LARGE_16, MonsterLarge16Decoder.class); - put(AnimationInfo.Type.AMBIENT_STATIC, AmbientStaticDecoder.class); - put(AnimationInfo.Type.AMBIENT, AmbientDecoder.class); - put(AnimationInfo.Type.FLYING, FlyingDecoder.class); - put(AnimationInfo.Type.MONSTER_ICEWIND, MonsterIcewindDecoder.class); - put(AnimationInfo.Type.MONSTER_PLANESCAPE, MonsterPlanescapeDecoder.class); - }}; - /** * A default operation that can be passed to the * {@link #createAnimation(SeqDef, List, BeforeSourceBam, BeforeSourceFrame, AfterSourceFrame, AfterDestFrame)} @@ -231,7 +197,7 @@ public static SpriteDecoder importSprite(CreResource cre) throws Exception Objects.requireNonNull(cre, "CRE resource cannot be null"); int animationId = ((IsNumeric)cre.getAttribute(CreResource.CRE_ANIMATION)).getValue(); Class spriteClass = - Objects.requireNonNull(detectAnimationType(animationId), "Could not determine animation type"); + Objects.requireNonNull(SpriteUtils.detectAnimationType(animationId), "Could not determine animation type"); try { Constructor ctor = Objects.requireNonNull(spriteClass.getConstructor(CreResource.class), "No matching constructor found"); @@ -241,47 +207,6 @@ public static SpriteDecoder importSprite(CreResource cre) throws Exception } } - /** - * Returns the {@code SpriteClass} class associated with the specified animation id. - * @param animationId the animation id - * @return a class type compatible with the specified animation id. - * Returns {@code null} if no class could be determined. - */ - public static Class getSpriteDecoderClass(int animationId) - { - Class retVal = null; - - // Testing Infinity Animation range first - AnimationInfo.Type animType = AnimationInfo.Type.containsInfinityAnimations(animationId); - if (animType != null) { - retVal = typeAssociations.get(animType); - } - - // Testing regular ranges - if (retVal == null) { - for (final AnimationInfo.Type type : AnimationInfo.Type.values()) { - if (type.contains(animationId)) { - retVal = typeAssociations.get(type); - if (retVal != null) { - break; - } - } - } - } - - return retVal; - } - - /** - * Returns the {@code SpriteClass} class associated with the specified {@code AnimationType} enum. - * @param type the {@code AnimationType} - * @return the associated {@code SpriteClass} class object. Returns {@code null} if class could not be determined. - */ - public static Class getSpriteDecoderClass(AnimationInfo.Type type) - { - return typeAssociations.get(type); - } - /** * Instances creates with this constructor are only suited for identification purposes. * @param type the animation type @@ -321,7 +246,8 @@ protected SpriteDecoder(AnimationInfo.Type type, CreResource cre) throws Excepti setAttribute(KEY_ANIMATION_TYPE, type); setAttribute(KEY_ANIMATION_SECTION, type.getSectionName()); this.creInfo = new CreatureInfo(this, cre); - this.ini = Objects.requireNonNull(getAnimationInfo(getAnimationId()), "No INI data available for animation id: " + getAnimationId()); + this.ini = Objects.requireNonNull(SpriteUtils.getAnimationInfo(getAnimationId()), + "No INI data available for animation id: " + getAnimationId()); this.currentSequence = Sequence.NONE; this.showCircle = false; this.selectionCircleBitmap = (Profile.getGame() == Profile.Game.PST) || (Profile.getGame() == Profile.Game.PSTEE); @@ -977,6 +903,29 @@ public Composite getComposite() } } + /** + * Returns the BAM cycle associated with the specified direction. + * Returns -1 if entry not found. + */ + public int getCycleIndex(Direction dir) + { + int retVal = -1; + Integer value = directionMap.get(dir); + if (value != null) { + retVal = value.intValue(); + } + + return retVal; + } + + /** + * Returns a copy of the map containing associations between animation directions and bam sequence numbers. + */ + public EnumMap getDirectionMap() + { + return directionMap.clone(); + } + /** Creates the BAM structure for the creature animation. */ protected abstract void init() throws Exception; @@ -1052,29 +1001,6 @@ protected IniMapSection getSpecificIniSection() return retVal; } - /** - * Returns the BAM cycle associated with the specified direction. - * Returns -1 if entry not found. - */ - public int getCycleIndex(Direction dir) - { - int retVal = -1; - Integer value = directionMap.get(dir); - if (value != null) { - retVal = value.intValue(); - } - - return retVal; - } - - /** - * Returns a copy of the map containing associations between animation directions and bam sequence numbers. - */ - public EnumMap getDirectionMap() - { - return directionMap.clone(); - } - /** * Assigns a cycle index to the specified BAM sequence and direction. * @param seq the sequence type for identification purposes. @@ -1208,22 +1134,22 @@ protected int createFrame(FrameInfo[] sourceFrames, BeforeSourceFrame beforeSrcF { Rectangle rect; if (Objects.requireNonNull(sourceFrames, "Source frame info objects required").length > 0) { - rect = getTotalFrameDimension(sourceFrames); + rect = SpriteUtils.getTotalFrameDimension(sourceFrames); } else { rect = new Rectangle(0, 0, 1, 1); } // include personal space region in image size if (isPersonalSpaceVisible()) { - rect = updateFrameDimension(rect, getPersonalSpaceSize(true)); + rect = SpriteUtils.updateFrameDimension(rect, getPersonalSpaceSize(true)); } // include selection circle in image size float circleStrokeSize = getSelectionCircleStrokeSize(); if (isSelectionCircleEnabled()) { Dimension dim = getSelectionCircleSize(); - rect = updateFrameDimension(rect, new Dimension(2 * (dim.width + (int)circleStrokeSize), - 2 * (dim.height + (int)circleStrokeSize))); + rect = SpriteUtils.updateFrameDimension(rect, new Dimension(2 * (dim.width + (int)circleStrokeSize), + 2 * (dim.height + (int)circleStrokeSize))); } // creating target image @@ -1336,7 +1262,7 @@ protected void drawPersonalSpace(Graphics2D g, Point center, Color color, float } /** Creates a bitmap with the personal space tiles. */ - private BufferedImage createPersonalSpace(Color color, float alpha) + protected BufferedImage createPersonalSpace(Color color, float alpha) { // preparations if (color == null) { @@ -1424,15 +1350,15 @@ protected void drawSelectionCircle(Graphics2D g, Point center, float strokeSize) Dimension dim = getSelectionCircleSize(); if (isSelectionCircleBitmap()) { - Image image = getCreatureInfo().isStatusPanic() ? getAllegianceImage(-1) - : getAllegianceImage(getCreatureInfo().getAllegiance()); + Image image = getCreatureInfo().isStatusPanic() ? SpriteUtils.getAllegianceImage(-1) + : SpriteUtils.getAllegianceImage(getCreatureInfo().getAllegiance()); Object oldHints = g.getRenderingHint(RenderingHints.KEY_INTERPOLATION); g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); g.drawImage(image, center.x - dim.width, center.y - dim.height, 2 * dim.width, 2 * dim.height, null); g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, (oldHints != null) ? oldHints : RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); } else { - Color color = getCreatureInfo().isStatusPanic() ? getAllegianceColor(-1) - : getAllegianceColor(getCreatureInfo().getAllegiance()); + Color color = getCreatureInfo().isStatusPanic() ? SpriteUtils.getAllegianceColor(-1) + : SpriteUtils.getAllegianceColor(getCreatureInfo().getAllegiance()); Object oldHints = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING); g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.setColor(color); @@ -1631,661 +1557,6 @@ protected boolean isMatchingAnimationType() return retVal; } - /** - * A helper method for calculating a dimension that can contain all specified source frames. - * @param frames one or more source frames. - * @return A rectangle object where x and y indicate the top-left corner relative to the center point. - * Width and height specify frame dimension. - */ - protected static Rectangle getTotalFrameDimension(FrameInfo... frames) - { - Rectangle retVal = new Rectangle(); - - if (frames.length > 0) { - int left = Integer.MAX_VALUE, top = Integer.MAX_VALUE, right = Integer.MIN_VALUE, bottom = Integer.MIN_VALUE; - for (final FrameInfo fi : frames) { - BamV1Control ctrl = fi.getController(); - int frameIdx = fi.getFrame(); - frameIdx = ctrl.cycleGetFrameIndexAbsolute(fi.getCycle(), frameIdx); - FrameEntry entry = fi.getController().getDecoder().getFrameInfo(frameIdx); - left = Math.min(left, -entry.getCenterX()); - top = Math.min(top, -entry.getCenterY()); - right = Math.max(right, entry.getWidth() - entry.getCenterX()); - bottom = Math.max(bottom, entry.getHeight() - entry.getCenterY()); - } - - retVal.x = left; - retVal.y = top; - retVal.width = right - left; - retVal.height = bottom - top; - } - - return retVal; - } - - /** A helper method that expands the rectangle to fit the specified dimension. */ - protected static Rectangle updateFrameDimension(Rectangle rect, Dimension dim) - { - Rectangle retVal = new Rectangle(Objects.requireNonNull(rect, "Bounding box cannot be null")); - if (dim != null) { - int w2 = dim.width / 2; - int h2 = dim.height / 2; - int left = retVal.x; - int top = retVal.y; - int right = left + retVal.width; - int bottom = top + retVal.height; - left = Math.min(left, -w2); - top = Math.min(top, -h2); - right = Math.max(right, w2); - bottom = Math.max(bottom, h2); - retVal.x = left; - retVal.y = top; - retVal.width = right - left; - retVal.height = bottom - top; - } - return retVal; - } - - - /** - * Determines the right allegiance color for selection circles and returns it as {@code Color} object. - * A negative value will enable the "panic" color. - * @param value numeric allegiance value. Specify a negative value to override allegiance by the "panic" status. - * @return a {@code Color} object with the associated allegiance or status color. - */ - protected static Color getAllegianceColor(int value) - { - Color c = null; - if (value < 0) { - // treat as panic - c = new Color(0xffff20, false); - } else if (value >= 2 && value <= 4 || value == 201) { - // ally - c = new Color(0x20ff20, false); - } else if (value == 255 || value == 254 || value == 28 || value == 6 || value == 5) { - // enemy - c = new Color(0xff2020, false); - } else { - // neutral - c = new Color(0x20ffff, false); - } - - return c; - } - - /** - * Determines the right selection circle bitmap based on the specified allegiance value and returns it - * as {@code Image} object. A negative value will enable the "panic" bitmap. - * @param value numeric allegiance value. Specify a negative value to override allegiance by the "panic" status. - * @return - */ - protected static Image getAllegianceImage(int value) - { - Image retVal = null; - if (value < 0) { - // treat as panic - retVal = Icons.getImage(Icons.ICON_CIRCLE_YELLOW); - } else if (value >= 2 && value <= 4 || value == 201) { - // ally - retVal = Icons.getImage(Icons.ICON_CIRCLE_GREEN); - } else if (value == 255 || value == 254 || value == 28 || value == 6 || value == 5) { - // enemy - retVal = Icons.getImage(Icons.ICON_CIRCLE_RED); - } else { - // neutral - retVal = Icons.getImage(Icons.ICON_CIRCLE_BLUE); - } - - return retVal; - } - - /** - * A helper method that parses the specified data array and generates a list of INI lines - * related to the "general" section. - * @param data the String array containing data for a specific table entry. - * @param type the animation type. - * @return the initialized "general" INI section as list of strings. An empty list otherwise. - */ - protected static List processTableDataGeneral(String[] data, AnimationInfo.Type type) - { - List retVal = new ArrayList<>(); - if (data == null || type == null) { - return retVal; - } - - int id = SpriteTables.valueToAnimationId(data, SpriteTables.COLUMN_ID, -1); - if (id < 0) { - return retVal; - } - int ellipse = SpriteTables.valueToInt(data, SpriteTables.COLUMN_ELLIPSE, 16); - int space = SpriteTables.valueToInt(data, SpriteTables.COLUMN_SPACE, 3); - int blending = SpriteTables.valueToInt(data, SpriteTables.COLUMN_BLENDING, 0); - String palette = SpriteTables.valueToString(data, SpriteTables.COLUMN_PALETTE, ""); - - int animIndex = SpriteTables.valueToInt(data, SpriteTables.COLUMN_TYPE, -1); - if (animIndex < 0 || animIndex >= AnimationInfo.Type.values().length || AnimationInfo.Type.values()[animIndex] != type) { - return retVal; - } - - int animType = -1; - for (int i = 0; i < type.getTypeCount(); i++) { - if (animType < 0 || (id & 0xf000) == type.getType(i)) { - animType = type.getType(i); - } - } - - retVal.add("[general]"); - retVal.add(String.format("animation_type=%04X", animType)); - retVal.add("ellipse=" + ellipse); - retVal.add("personal_space=" + space); - if ((blending & 1) == 1) { - retVal.add("brightest=1"); - } - if ((blending & 2) == 2) { - retVal.add("multiply_blend=1"); - } - if ((blending & 4) == 4) { - retVal.add("light_source=1"); - } - if (!palette.isEmpty()) { - retVal.add("new_palette=" + palette); - } - - return retVal; - } - - /** - * A helper method for PST animations that parses the specified data array and generates a list of INI lines - * related to the "general" section. - * @param data the String array containing data for a specific table entry. - * @return the initialized "general" INI section as list of strings. An empty list otherwise. - */ - protected static List processTableDataGeneralPst(String[] data) - { - List retVal = new ArrayList<>(); - if (data == null) { - return retVal; - } - - int id = SpriteTables.valueToInt(data, SpriteTables.COLUMN_ID, -1); - if (id < 0) { - return retVal; - } - int ellipse = SpriteTables.valueToInt(data, SpriteTables.COLUMN_PST_ELLIPSE, 16); - int space = SpriteTables.valueToInt(data, SpriteTables.COLUMN_PST_SPACE, 3); - - retVal.add("[general]"); - retVal.add("animation_type=F000"); - retVal.add("ellipse=" + ellipse); - retVal.add("personal_space=" + space); - - return retVal; - } - - /** - * Returns whether the specified {@code SpriteDecoder} class is compatible with the given animation id - * and any of the IniMap definitions. - */ - private static boolean isSpriteDecoderAvailable(Class spriteClass, int animationId, List iniList) - { - boolean retVal = false; - if (spriteClass == null || iniList == null) { - return retVal; - } - - try { - Constructor ctor = spriteClass.getConstructor(int.class, IniMap.class); - if (ctor != null) { - for (final IniMap ini : iniList) { - try { - retVal = (ctor.newInstance(animationId, ini).getClass() != null); - } catch (IllegalAccessException | InvocationTargetException | InstantiationException e) { - } - } - } - } catch (NoSuchMethodException e) { - } - - return retVal; - } - - /** - * Attempts to determine the animation type assigned to the specified creature. - * @return Class instance responsible for handling the detected animation type. {@code null} if type could not be determined. - */ - protected static Class detectAnimationType(int animationId) - { - Class retVal = null; - - List iniList = new ArrayList<>(); - iniList.addAll(getAnimationInfoByIni(animationId)); - - if (iniList.isEmpty()) { - iniList.addAll(getAnimationInfoByTable(animationId)); - } - - if (iniList.isEmpty()) { - iniList.addAll(getAnimationInfoByGuess(animationId)); - } - - if (!iniList.isEmpty()) { - // trying recommended sprite decoder class first - Class cls = getSpriteDecoderClass(animationId); - if (isSpriteDecoderAvailable(cls, animationId, iniList)) { - retVal = cls; - } - - if (retVal == null) { - // trying out all available sprite decoder classes otherwise - if (Profile.getGame() == Profile.Game.PST || Profile.getGame() == Profile.Game.PSTEE) { - if (isSpriteDecoderAvailable(MonsterPlanescapeDecoder.class, animationId, iniList)) { - retVal = cls; - } - } else { - for (final AnimationInfo.Type type : AnimationInfo.Type.values()) { - if (type != AnimationInfo.Type.MONSTER_PLANESCAPE) { - cls = typeAssociations.get(type); - if (isSpriteDecoderAvailable(cls, animationId, iniList)) { - retVal = cls; - break; - } - } - } - } - } - } - - return retVal; - } - - /** - * Returns creature animation info from an existing INI file. - * @param animationId the creature animation id - * @return an list of {@link IniMap} instances with potential creature animation data. - * Returns {@code null} if no matching INI was found. - */ - protected static List getAnimationInfoByIni(int animationId) - { - List retVal = new ArrayList<>(); - - animationId &= 0xffff; - String iniFile = String.format("%04X.INI", animationId); - if (ResourceFactory.resourceExists(iniFile)) { - retVal.add(new IniMap(ResourceFactory.getResourceEntry(iniFile), true)); - } - - return retVal; - } - - /** - * Returns creature animation info from hardcoded creature data. - * @param animationId the creature animation id - * @return an list of {@link IniMap} instance with potential creature animation data. - * Returns empty list if no creature data was found. - */ - protected static List getAnimationInfoByTable(int animationId) - { - return SpriteTables.createIniMaps(animationId & 0xffff); - } - - /** - * Returns creature animation info based on ANISND.2DA data and analyzing potential slot ranges. - * May return false positives. - * @param animationId the creature animation id - * @return a list of {@link IniMap} instances with potential creature animation data. - * Returns {@code null} if no potential match was found. - */ - protected static List getAnimationInfoByGuess(int animationId) - { - if (Profile.getGame() == Profile.Game.PST || Profile.getGame() == Profile.Game.PSTEE) { - return guessIniMapsPst(animationId); - } else { - return guessIniMaps(animationId); - } - } - - /** - * Returns creature animation info in INI format. Section and field format is based on the EE v2.0 INI format. - * The method will first look for existing INI data in the game resources. Failing that it will look up data in - * hardcoded tables and fill in missing data from associated 2DA file if available. Failing that it will guess - * the correct format based on animation type and available resources. - * @param animationId the 16-bit animation id. - * @return An IniMap structure containing necessary data for rendering creature animation. Returns {@code null} if no - * animation info could be assembled. - */ - protected static IniMap getAnimationInfo(int animationId) - { - List retVal = new ArrayList<>(); - - // 1. look up existing INI resource - retVal.addAll(getAnimationInfoByIni(animationId)); - - if (retVal.isEmpty()) { - // 2. look up hardcoded tables - retVal.addAll(getAnimationInfoByTable(animationId)); - } - - if (retVal.isEmpty()) { - // 3. guess animation info based on anisnd.2da entry and available sprite classes - retVal.addAll(getAnimationInfoByGuess(animationId)); - } - - if (!retVal.isEmpty()) { - return retVal.get(0); - } else { - return null; - } - } - - // Attempts to find potential non-PST-specific IniMap instances - private static List guessIniMaps(int animationId) - { - List retVal = new ArrayList<>(); - String resref = null; - String palette = null; - - // evaluate ANIMATE.SRC if available - ResourceEntry resEntry = ResourceFactory.getResourceEntry("ANIMATE.SRC"); - if (resEntry != null) { - IniMap anisrc = IniMapCache.get(resEntry); - if (anisrc != null) { - IniMapSection iniSection = anisrc.getUnnamedSection(); - if (iniSection != null) { - for (final Iterator iter = iniSection.iterator(); iter.hasNext(); ) { - IniMapEntry entry = iter.next(); - try { - String key = entry.getKey(); - int id = (key.startsWith("0x") || key.startsWith("0X")) ? Misc.toNumber(key.substring(2, key.length()), 16, -1) - : Misc.toNumber(key, -1); - if (id == animationId) { - String value = entry.getValue(); - if (id > 0x1000 && value.length() > 4) { - value = value.substring(0, 4); - } - resref = value; - break; - } - } catch (Exception e) { - e.printStackTrace(); - } - } - } - } - } - - if (resref == null) { - // evaluate ANISND.IDS if available - IdsMap anisnd = IdsMapCache.get("ANISND.IDS"); - if (anisnd != null) { - IdsMapEntry anisndEntry = anisnd.get(animationId); - if (anisndEntry != null) { - String[] elements = anisndEntry.getSymbol().split("\\s+"); - if (elements.length > 0 && elements[0].length() <= 8) { - resref = elements[0]; - int pos = resref.indexOf('_'); - if (pos > 0) { - // assuming underscore indicates a palette resref - palette = resref; - resref = resref.substring(0, pos); - } else if (animationId >= 0x1000 && resref.length() > 4) { - resref = resref.substring(0, 4); - } - } - } - } - } - - if (resref == null) { - return retVal; - } - - if (palette == null) { - palette = "*"; - } - - List tableEntries = new ArrayList<>(); - AnimationInfo.Type type = AnimationInfo.Type.typeOfId(animationId); - if (type == null) { - return retVal; - } - - ResourceEntry bamEntry; - switch (type) { - case EFFECT: - tableEntries.add(String.format("0x%04x %s 0 0 0 * %s * * * * * * * * *", animationId, resref, palette)); - break; - case MONSTER_QUADRANT: - if (ResourceFactory.resourceExists(resref + "G14.BAM")) { - tableEntries.add(String.format("0x%04x %s 1 32 5 * %s * * * * * * * * *", animationId, resref, palette)); - } - break; - case MONSTER_MULTI: - if (ResourceFactory.resourceExists(resref + "1908.BAM")) { - tableEntries.add(String.format("0x%04x %s 2 72 13 * %s * * * * 1 * * * *", animationId, resref, palette)); - } - break; - case MONSTER_MULTI_NEW: - if (ResourceFactory.resourceExists(resref + "G145.BAM")) { - tableEntries.add(String.format("0x%04x %s 2 32 5 * %s * * * * 1 * * * *", animationId, resref, palette)); - } else if (ResourceFactory.resourceExists(resref + "G1.BAM")) { - tableEntries.add(String.format("0x%04x %s 2 32 5 * %s * * * * 0 * * * *", animationId, resref, palette)); - } - break; - case MONSTER_LAYERED_SPELL: - resref = guessResourceRef(resref, "G1"); - bamEntry = ResourceFactory.getResourceEntry(resref + "G1.BAM"); - if (bamEntry != null) { - int falseColor = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; - tableEntries.add(String.format("0x%04x %s 4 16 3 * %s * * * %d * * * * *", animationId, resref, palette, falseColor)); - } - break; - case MONSTER_ANKHEG: - resref = guessResourceRef(resref, "DG1"); - bamEntry = ResourceFactory.getResourceEntry(resref + "DG1.BAM"); - if (bamEntry != null) { - tableEntries.add(String.format("0x%04x %s 6 24 5 * %s * * * * * * * * *", animationId, resref, palette)); - } - break; - case TOWN_STATIC: - resref = guessResourceRef(resref, ""); - bamEntry = ResourceFactory.getResourceEntry(resref + ".BAM"); - if (bamEntry != null) { - int falseColor = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; - tableEntries.add(String.format("0x%04x %s 7 16 3 * %s * * * %d * * * * *", animationId, resref, palette, falseColor)); - } - break; - case CHARACTER: - bamEntry = ResourceFactory.getResourceEntry(resref + "1G1.BAM"); - if (bamEntry != null) { - int falseColor = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; - int split = ResourceFactory.resourceExists(resref + "1G15.BAM") ? 1 : 0; - tableEntries.add(String.format("0x%04x %s 8 16 3 * * * * * %d %d 1 * * *", animationId, resref, falseColor, split)); - } - break; - case CHARACTER_OLD: - bamEntry = ResourceFactory.getResourceEntry(resref + "1G1.BAM"); - if (bamEntry != null) { - int falseColor = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; - tableEntries.add(String.format("0x%04x %s 9 16 3 * * * * * %d * * * * *", animationId, resref, falseColor)); - } - break; - case MONSTER: - resref = guessResourceRef(resref, "G1"); - bamEntry = ResourceFactory.getResourceEntry(resref + "G1.BAM"); - if (bamEntry != null) { - int split = ResourceFactory.resourceExists(resref + "G15.BAM") ? 1 : 0; - int falseColor = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; - tableEntries.add(String.format("0x%04x %s 10 16 3 * %s * * * %d %d * * * *", animationId, resref, palette, falseColor, split)); - tableEntries.add(String.format("0x%04x %s 11 16 3 * %s * * * %d * * * * *", animationId, resref, palette, falseColor)); - } - break; - case MONSTER_OLD: - resref = guessResourceRef(resref, "G1"); - bamEntry = ResourceFactory.getResourceEntry(resref + "G1.BAM"); - if (bamEntry != null) { - int falseColor = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; - tableEntries.add(String.format("0x%04x %s 11 16 3 * %s * * * %d * * * * *", animationId, resref, palette, falseColor)); - } - break; - case MONSTER_LAYERED: - resref = guessResourceRef(resref, "G1"); - bamEntry = ResourceFactory.getResourceEntry(resref + "G1.BAM"); - if (bamEntry != null) { - tableEntries.add(String.format("0x%04x %s 4 16 3 * * * * * * * * * * *", animationId, resref)); - } - break; - case MONSTER_LARGE: - resref = guessResourceRef(resref, "G1"); - bamEntry = ResourceFactory.getResourceEntry(resref + "G1.BAM"); - if (bamEntry != null) { - int falseColor = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; - tableEntries.add(String.format("0x%04x %s 12 16 3 * %s * * * %d * * * * *", animationId, resref, palette, falseColor)); - } - break; - case MONSTER_LARGE_16: - resref = guessResourceRef(resref, "G1"); - bamEntry = ResourceFactory.getResourceEntry(resref + "G1.BAM"); - if (bamEntry != null) { - int falseColor = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; - tableEntries.add(String.format("0x%04x %s 13 16 3 * %s * * * %d * * * * *", animationId, resref, palette, falseColor)); - } - break; - case AMBIENT_STATIC: - resref = guessResourceRef(resref, "G1"); - bamEntry = ResourceFactory.getResourceEntry(resref + "G1.BAM"); - if (bamEntry != null) { - int falseColor = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; - tableEntries.add(String.format("0x%04x %s 14 16 3 * %s * * * %d * * * * *", animationId, resref, palette, falseColor)); - } - break; - case AMBIENT: - resref = guessResourceRef(resref, "G1"); - bamEntry = ResourceFactory.getResourceEntry(resref + "G1.BAM"); - if (bamEntry != null) { - int falseColor = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; - tableEntries.add(String.format("0x%04x %s 15 16 3 * %s * * * %d * * * * *", animationId, resref, palette, falseColor)); - } - break; - case FLYING: - resref = guessResourceRef(resref, ""); - bamEntry = ResourceFactory.getResourceEntry(resref + ".BAM"); - if (bamEntry != null) { - int falseColor = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; - tableEntries.add(String.format("0x%04x %s 16 16 3 * %s * * * %d * * * * *", animationId, resref, palette, falseColor)); - } - break; - case MONSTER_ICEWIND: - { - boolean found = false; - if (resref.length() >= 4 && !found) { - for (final String suffix : new String[] { "A1", "A2", "A3", "A4", "CA", "DE", "GH", "GU", "SC", "SD", "SL", "SP", "TW", "WK" }) { - if (ResourceFactory.resourceExists(resref + suffix + ".BAM")) { - found = true; - break; - } - } - } - if (found) { - tableEntries.add(String.format("0x%04x %s 17 24 3 * %s * * * * * * * * *", animationId, resref, palette)); - } - break; - } - default: - } - - if (!tableEntries.isEmpty()) { - for (final String line : tableEntries) { - StringBuilder sb = new StringBuilder(); - sb.append("2DA V1.0").append('\n'); - sb.append("*").append('\n'); - sb.append(" RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD").append('\n'); - sb.append(line).append('\n'); - ResourceEntry entry = new BufferedResourceEntry(ByteBuffer.wrap(sb.toString().getBytes()), Integer.toString(animationId, 16) + ".2DA"); - Table2da table = new Table2da(entry); - retVal.addAll(SpriteTables.processTable(Profile.getGame(), table, animationId)); - } - } - - return retVal; - } - - // Helper method: attempts to find an existing resource with the specified name parts. - // Returns the resref of the matching resource. Returns the original resref otherwise. - private static String guessResourceRef(String resref, String suffix) - { - String retVal = resref; - if (retVal == null) { - return retVal; - } - - if (suffix == null) { - suffix = ""; - } - - while (retVal.length() >= 4) { - if (ResourceFactory.resourceExists(retVal + suffix + ".BAM")) { - return retVal; - } - retVal = retVal.substring(0, resref.length() - 1); - } - - return resref; - } - - // Attempts to find potential PST-specific IniMap instances - private static List guessIniMapsPst(int animationId) - { - List retVal = new ArrayList<>(); - String resref = null; - - IniMap resIni = IniMapCache.get("RESDATA.INI", true); - if (resIni == null) { - return retVal; - } - - // only regular animations are considered - int id = animationId & 0x0fff; - IniMapSection iniSection = resIni.getSection(Integer.toString(id)); - if (iniSection == null) { - iniSection = resIni.getSection("0x" + Integer.toString(id, 16)); - } - if (iniSection == null) { - return retVal; - } - - int clown = 0; - for (final Sequence seq : Sequence.values()) { - String cmd = MonsterPlanescapeDecoder.getActionCommand(seq); - if (cmd != null) { - String key = iniSection.getAsString(cmd); - if (key != null && key.length() >= 7) { - ResourceEntry bamEntry = ResourceFactory.getResourceEntry(key + "b.bam"); - if (bamEntry != null) { - clown = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; - resref = key.substring(0, 1) + key.substring(4, key.length()) + "b"; - break; - } - } - } - } - - if (resref != null) { - int armor = iniSection.getAsInteger("armor", 0); - int bestiary = iniSection.getAsInteger("bestiary", 0); - - StringBuilder sb = new StringBuilder(); - sb.append("2DA V1.0").append('\n'); - sb.append("*").append('\n'); - sb.append(" RESREF RESREF2 TYPE ELLIPSE SPACE CLOWN ARMOR BESTIARY").append('\n'); - sb.append(String.format("0x%04x %s * 18 16 3 %d %d %d", id, resref, clown, armor, bestiary)).append('\n'); - ResourceEntry entry = new BufferedResourceEntry(ByteBuffer.wrap(sb.toString().getBytes()), Integer.toString(animationId, 16) + ".2DA"); - Table2da table = new Table2da(entry); - retVal = SpriteTables.processTable(Profile.getGame(), table, animationId); - } - - return retVal; - } - @Override public int hashCode() { diff --git a/src/org/infinity/resource/cre/decoder/TownStaticDecoder.java b/src/org/infinity/resource/cre/decoder/TownStaticDecoder.java index ec8aec5b5..3cba8d64b 100644 --- a/src/org/infinity/resource/cre/decoder/TownStaticDecoder.java +++ b/src/org/infinity/resource/cre/decoder/TownStaticDecoder.java @@ -62,7 +62,7 @@ public static IniMap processTableData(String[] data) } int falseColor = SpriteTables.valueToInt(data, SpriteTables.COLUMN_CLOWN, 0); - List lines = processTableDataGeneral(data, ANIMATION_TYPE); + List lines = SpriteUtils.processTableDataGeneral(data, ANIMATION_TYPE); lines.add("[town_static]"); lines.add("false_color=" + falseColor); lines.add("resref=" + resref); diff --git a/src/org/infinity/resource/cre/decoder/tables/InfinityTables.java b/src/org/infinity/resource/cre/decoder/tables/InfinityTables.java index 7c775619d..085986050 100644 --- a/src/org/infinity/resource/cre/decoder/tables/InfinityTables.java +++ b/src/org/infinity/resource/cre/decoder/tables/InfinityTables.java @@ -15,6 +15,7 @@ import org.infinity.resource.Profile; import org.infinity.resource.cre.decoder.SpriteDecoder; import org.infinity.resource.cre.decoder.util.AnimationInfo; +import org.infinity.resource.cre.decoder.util.SpriteUtils; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IdsMap; import org.infinity.util.IdsMapEntry; @@ -80,7 +81,7 @@ private static List processTable(IdsMap idsMap, int animationId) // determining SpriteDecoder class instance int classType = SpriteTables.valueToInt(data, SpriteTables.COLUMN_TYPE, -1); AnimationInfo.Type animType = AnimationInfo.Type.values()[classType]; - Class cls = SpriteDecoder.getSpriteDecoderClass(animType); + Class cls = SpriteUtils.getSpriteDecoderClass(animType); if (cls != null) { // calling method of signature: public static IniMap processTableData(String[] data); Method method = cls.getMethod("processTableData", String[].class); diff --git a/src/org/infinity/resource/cre/decoder/tables/SpriteTables.java b/src/org/infinity/resource/cre/decoder/tables/SpriteTables.java index 1493d0290..9444105b2 100644 --- a/src/org/infinity/resource/cre/decoder/tables/SpriteTables.java +++ b/src/org/infinity/resource/cre/decoder/tables/SpriteTables.java @@ -17,6 +17,7 @@ import org.infinity.resource.Profile; import org.infinity.resource.cre.decoder.MonsterPlanescapeDecoder; import org.infinity.resource.cre.decoder.SpriteDecoder; +import org.infinity.resource.cre.decoder.util.SpriteUtils; import org.infinity.resource.key.BufferedResourceEntry; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; @@ -192,7 +193,7 @@ private static List processTable(Table2da table, int animationId) } // determining SpriteDecoder class instance - Class cls = SpriteDecoder.getSpriteDecoderClass(animationId); + Class cls = SpriteUtils.getSpriteDecoderClass(animationId); if (cls != null) { try { // calling method of signature: public static IniMap processTableData(String[] data); diff --git a/src/org/infinity/resource/cre/decoder/util/SpriteUtils.java b/src/org/infinity/resource/cre/decoder/util/SpriteUtils.java index 0bb33c734..325ec7032 100644 --- a/src/org/infinity/resource/cre/decoder/util/SpriteUtils.java +++ b/src/org/infinity/resource/cre/decoder/util/SpriteUtils.java @@ -4,20 +4,62 @@ package org.infinity.resource.cre.decoder.util; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Image; +import java.awt.Rectangle; import java.awt.image.BufferedImage; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.nio.ByteBuffer; +import java.util.ArrayList; import java.util.Arrays; +import java.util.EnumMap; import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; import java.util.Random; import org.infinity.resource.Profile; import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.AmbientDecoder; +import org.infinity.resource.cre.decoder.AmbientStaticDecoder; +import org.infinity.resource.cre.decoder.CharacterDecoder; +import org.infinity.resource.cre.decoder.CharacterOldDecoder; +import org.infinity.resource.cre.decoder.EffectDecoder; +import org.infinity.resource.cre.decoder.FlyingDecoder; +import org.infinity.resource.cre.decoder.MonsterAnkhegDecoder; +import org.infinity.resource.cre.decoder.MonsterDecoder; +import org.infinity.resource.cre.decoder.MonsterIcewindDecoder; +import org.infinity.resource.cre.decoder.MonsterLarge16Decoder; +import org.infinity.resource.cre.decoder.MonsterLargeDecoder; +import org.infinity.resource.cre.decoder.MonsterLayeredDecoder; +import org.infinity.resource.cre.decoder.MonsterLayeredSpellDecoder; +import org.infinity.resource.cre.decoder.MonsterMultiDecoder; +import org.infinity.resource.cre.decoder.MonsterMultiNewDecoder; +import org.infinity.resource.cre.decoder.MonsterOldDecoder; +import org.infinity.resource.cre.decoder.MonsterPlanescapeDecoder; +import org.infinity.resource.cre.decoder.MonsterQuadrantDecoder; +import org.infinity.resource.cre.decoder.SpriteDecoder; +import org.infinity.resource.cre.decoder.TownStaticDecoder; +import org.infinity.resource.cre.decoder.tables.SpriteTables; +import org.infinity.resource.cre.viewer.icon.Icons; import org.infinity.resource.graphics.BamV1Decoder; import org.infinity.resource.graphics.ColorConvert; import org.infinity.resource.graphics.GraphicsResource; +import org.infinity.resource.graphics.BamDecoder.FrameEntry; import org.infinity.resource.graphics.BamV1Decoder.BamV1Control; +import org.infinity.resource.key.BufferedResourceEntry; import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.IdsMap; +import org.infinity.util.IdsMapCache; +import org.infinity.util.IdsMapEntry; +import org.infinity.util.IniMap; +import org.infinity.util.IniMapCache; +import org.infinity.util.IniMapEntry; +import org.infinity.util.IniMapSection; import org.infinity.util.Misc; import org.infinity.util.Table2da; import org.infinity.util.Table2daCache; @@ -40,6 +82,30 @@ public class SpriteUtils /** A random number generator for general use */ private static final Random random = new Random(); + /** Mappings between animation types and compatible sprite classes. */ + private static final EnumMap> typeAssociations = + new EnumMap>(AnimationInfo.Type.class) {{ + put(AnimationInfo.Type.EFFECT, EffectDecoder.class); + put(AnimationInfo.Type.MONSTER_QUADRANT, MonsterQuadrantDecoder.class); + put(AnimationInfo.Type.MONSTER_MULTI, MonsterMultiDecoder.class); + put(AnimationInfo.Type.MONSTER_MULTI_NEW, MonsterMultiNewDecoder.class); + put(AnimationInfo.Type.MONSTER_LAYERED_SPELL, MonsterLayeredSpellDecoder.class); + put(AnimationInfo.Type.MONSTER_ANKHEG, MonsterAnkhegDecoder.class); + put(AnimationInfo.Type.TOWN_STATIC, TownStaticDecoder.class); + put(AnimationInfo.Type.CHARACTER, CharacterDecoder.class); + put(AnimationInfo.Type.CHARACTER_OLD, CharacterOldDecoder.class); + put(AnimationInfo.Type.MONSTER, MonsterDecoder.class); + put(AnimationInfo.Type.MONSTER_OLD, MonsterOldDecoder.class); + put(AnimationInfo.Type.MONSTER_LAYERED, MonsterLayeredDecoder.class); + put(AnimationInfo.Type.MONSTER_LARGE, MonsterLargeDecoder.class); + put(AnimationInfo.Type.MONSTER_LARGE_16, MonsterLarge16Decoder.class); + put(AnimationInfo.Type.AMBIENT_STATIC, AmbientStaticDecoder.class); + put(AnimationInfo.Type.AMBIENT, AmbientDecoder.class); + put(AnimationInfo.Type.FLYING, FlyingDecoder.class); + put(AnimationInfo.Type.MONSTER_ICEWIND, MonsterIcewindDecoder.class); + put(AnimationInfo.Type.MONSTER_PLANESCAPE, MonsterPlanescapeDecoder.class); + }}; + /** A stable pool of random numbers. */ private static int[] randomPool; @@ -739,6 +805,704 @@ public static void updateRandomPool() } } + /** + * Calculates a dimension that can contain all the specified source frames. + * @param frames one or more source frames. + * @return A rectangle object where x and y indicate the top-left corner relative to the center point. + * Width and height specify frame dimension. + */ + public static Rectangle getTotalFrameDimension(FrameInfo... frames) + { + Rectangle retVal = new Rectangle(); + + if (frames.length > 0) { + int left = Integer.MAX_VALUE, top = Integer.MAX_VALUE, right = Integer.MIN_VALUE, bottom = Integer.MIN_VALUE; + for (final FrameInfo fi : frames) { + BamV1Control ctrl = fi.getController(); + int frameIdx = fi.getFrame(); + frameIdx = ctrl.cycleGetFrameIndexAbsolute(fi.getCycle(), frameIdx); + FrameEntry entry = fi.getController().getDecoder().getFrameInfo(frameIdx); + left = Math.min(left, -entry.getCenterX()); + top = Math.min(top, -entry.getCenterY()); + right = Math.max(right, entry.getWidth() - entry.getCenterX()); + bottom = Math.max(bottom, entry.getHeight() - entry.getCenterY()); + } + + retVal.x = left; + retVal.y = top; + retVal.width = right - left; + retVal.height = bottom - top; + } + + return retVal; + } + + /** Expands the rectangle to fit the specified dimension. */ + public static Rectangle updateFrameDimension(Rectangle rect, Dimension dim) + { + Rectangle retVal = new Rectangle(Objects.requireNonNull(rect, "Bounding box cannot be null")); + if (dim != null) { + int w2 = dim.width / 2; + int h2 = dim.height / 2; + int left = retVal.x; + int top = retVal.y; + int right = left + retVal.width; + int bottom = top + retVal.height; + left = Math.min(left, -w2); + top = Math.min(top, -h2); + right = Math.max(right, w2); + bottom = Math.max(bottom, h2); + retVal.x = left; + retVal.y = top; + retVal.width = right - left; + retVal.height = bottom - top; + } + return retVal; + } + + + /** + * Determines the right allegiance color for selection circles and returns it as {@code Color} object. + * A negative value will enable the "panic" color. + * @param value numeric allegiance value. Specify a negative value to override allegiance by the "panic" status. + * @return a {@code Color} object with the associated allegiance or status color. + */ + public static Color getAllegianceColor(int value) + { + Color c = null; + if (value < 0) { + // treat as panic + c = new Color(0xffff20, false); + } else if (value >= 2 && value <= 4 || value == 201) { + // ally + c = new Color(0x20ff20, false); + } else if (value == 255 || value == 254 || value == 28 || value == 6 || value == 5) { + // enemy + c = new Color(0xff2020, false); + } else { + // neutral + c = new Color(0x20ffff, false); + } + + return c; + } + + /** + * Determines the right selection circle bitmap based on the specified allegiance value and returns it + * as {@code Image} object. A negative value will enable the "panic" bitmap. + * @param value numeric allegiance value. Specify a negative value to override allegiance by the "panic" status. + * @return + */ + public static Image getAllegianceImage(int value) + { + Image retVal = null; + if (value < 0) { + // treat as panic + retVal = Icons.getImage(Icons.ICON_CIRCLE_YELLOW); + } else if (value >= 2 && value <= 4 || value == 201) { + // ally + retVal = Icons.getImage(Icons.ICON_CIRCLE_GREEN); + } else if (value == 255 || value == 254 || value == 28 || value == 6 || value == 5) { + // enemy + retVal = Icons.getImage(Icons.ICON_CIRCLE_RED); + } else { + // neutral + retVal = Icons.getImage(Icons.ICON_CIRCLE_BLUE); + } + + return retVal; + } + + + /** + * Returns the {@code SpriteClass} class associated with the specified {@code AnimationType} enum. + * @param type the {@code AnimationType} + * @return the associated {@code SpriteClass} class object. Returns {@code null} if class could not be determined. + */ + public static Class getSpriteDecoderClass(AnimationInfo.Type type) + { + return typeAssociations.get(type); + } + + /** + * Returns the {@code SpriteClass} class associated with the specified animation id. + * @param animationId the animation id + * @return a class type compatible with the specified animation id. + * Returns {@code null} if no class could be determined. + */ + public static Class getSpriteDecoderClass(int animationId) + { + Class retVal = null; + + // Testing Infinity Animation range first + AnimationInfo.Type animType = AnimationInfo.Type.containsInfinityAnimations(animationId); + if (animType != null) { + retVal = typeAssociations.get(animType); + } + + // Testing regular ranges + if (retVal == null) { + for (final AnimationInfo.Type type : AnimationInfo.Type.values()) { + if (type.contains(animationId)) { + retVal = typeAssociations.get(type); + if (retVal != null) { + break; + } + } + } + } + + return retVal; + } + + /** + * Returns creature animation info in INI format. Section and field format is based on the EE v2.0 INI format. + * The method will first look for existing INI data in the game resources. Failing that it will look up data in + * hardcoded tables and fill in missing data from associated 2DA file if available. Failing that it will guess + * the correct format based on animation type and available resources. + * @param animationId the 16-bit animation id. + * @return An IniMap structure containing necessary data for rendering creature animation. Returns {@code null} if no + * animation info could be assembled. + */ + public static IniMap getAnimationInfo(int animationId) + { + List retVal = new ArrayList<>(); + + // 1. look up existing INI resource + retVal.addAll(getAnimationInfoByIni(animationId)); + + if (retVal.isEmpty()) { + // 2. look up hardcoded tables + retVal.addAll(getAnimationInfoByTable(animationId)); + } + + if (retVal.isEmpty()) { + // 3. guess animation info based on anisnd.2da entry and available sprite classes + retVal.addAll(getAnimationInfoByGuess(animationId)); + } + + if (!retVal.isEmpty()) { + return retVal.get(0); + } else { + return null; + } + } + + /** + * Attempts to determine the animation type assigned to the specified creature. + * @return Class instance responsible for handling the detected animation type. {@code null} if type could not be determined. + */ + public static Class detectAnimationType(int animationId) + { + Class retVal = null; + + List iniList = new ArrayList<>(); + iniList.addAll(getAnimationInfoByIni(animationId)); + + if (iniList.isEmpty()) { + iniList.addAll(getAnimationInfoByTable(animationId)); + } + + if (iniList.isEmpty()) { + iniList.addAll(getAnimationInfoByGuess(animationId)); + } + + if (!iniList.isEmpty()) { + // trying recommended sprite decoder class first + Class cls = getSpriteDecoderClass(animationId); + if (isSpriteDecoderAvailable(cls, animationId, iniList)) { + retVal = cls; + } + + if (retVal == null) { + // trying out all available sprite decoder classes otherwise + if (Profile.getGame() == Profile.Game.PST || Profile.getGame() == Profile.Game.PSTEE) { + if (isSpriteDecoderAvailable(MonsterPlanescapeDecoder.class, animationId, iniList)) { + retVal = cls; + } + } else { + for (final AnimationInfo.Type type : AnimationInfo.Type.values()) { + if (type != AnimationInfo.Type.MONSTER_PLANESCAPE) { + cls = typeAssociations.get(type); + if (isSpriteDecoderAvailable(cls, animationId, iniList)) { + retVal = cls; + break; + } + } + } + } + } + } + + return retVal; + } + + /** + * A helper method that parses the specified data array and generates a list of INI lines + * related to the "general" section. + * @param data the String array containing data for a specific table entry. + * @param type the animation type. + * @return the initialized "general" INI section as list of strings. An empty list otherwise. + */ + public static List processTableDataGeneral(String[] data, AnimationInfo.Type type) + { + List retVal = new ArrayList<>(); + if (data == null || type == null) { + return retVal; + } + + int id = SpriteTables.valueToAnimationId(data, SpriteTables.COLUMN_ID, -1); + if (id < 0) { + return retVal; + } + int ellipse = SpriteTables.valueToInt(data, SpriteTables.COLUMN_ELLIPSE, 16); + int space = SpriteTables.valueToInt(data, SpriteTables.COLUMN_SPACE, 3); + int blending = SpriteTables.valueToInt(data, SpriteTables.COLUMN_BLENDING, 0); + String palette = SpriteTables.valueToString(data, SpriteTables.COLUMN_PALETTE, ""); + + int animIndex = SpriteTables.valueToInt(data, SpriteTables.COLUMN_TYPE, -1); + if (animIndex < 0 || animIndex >= AnimationInfo.Type.values().length || AnimationInfo.Type.values()[animIndex] != type) { + return retVal; + } + + int animType = -1; + for (int i = 0; i < type.getTypeCount(); i++) { + if (animType < 0 || (id & 0xf000) == type.getType(i)) { + animType = type.getType(i); + } + } + + retVal.add("[general]"); + retVal.add(String.format("animation_type=%04X", animType)); + retVal.add("ellipse=" + ellipse); + retVal.add("personal_space=" + space); + if ((blending & 1) == 1) { + retVal.add("brightest=1"); + } + if ((blending & 2) == 2) { + retVal.add("multiply_blend=1"); + } + if ((blending & 4) == 4) { + retVal.add("light_source=1"); + } + if (!palette.isEmpty()) { + retVal.add("new_palette=" + palette); + } + + return retVal; + } + + /** + * A helper method for PST animations that parses the specified data array and generates a list of INI lines + * related to the "general" section. + * @param data the String array containing data for a specific table entry. + * @return the initialized "general" INI section as list of strings. An empty list otherwise. + */ + public static List processTableDataGeneralPst(String[] data) + { + List retVal = new ArrayList<>(); + if (data == null) { + return retVal; + } + + int id = SpriteTables.valueToInt(data, SpriteTables.COLUMN_ID, -1); + if (id < 0) { + return retVal; + } + int ellipse = SpriteTables.valueToInt(data, SpriteTables.COLUMN_PST_ELLIPSE, 16); + int space = SpriteTables.valueToInt(data, SpriteTables.COLUMN_PST_SPACE, 3); + + retVal.add("[general]"); + retVal.add("animation_type=F000"); + retVal.add("ellipse=" + ellipse); + retVal.add("personal_space=" + space); + + return retVal; + } + + /** + * Returns whether the specified {@code SpriteDecoder} class is compatible with the given animation id + * and any of the IniMap definitions. + */ + private static boolean isSpriteDecoderAvailable(Class spriteClass, int animationId, List iniList) + { + boolean retVal = false; + if (spriteClass == null || iniList == null) { + return retVal; + } + + try { + Constructor ctor = spriteClass.getConstructor(int.class, IniMap.class); + if (ctor != null) { + for (final IniMap ini : iniList) { + try { + retVal = (ctor.newInstance(animationId, ini).getClass() != null); + } catch (IllegalAccessException | InvocationTargetException | InstantiationException e) { + } + } + } + } catch (NoSuchMethodException e) { + } + + return retVal; + } + + /** + * Returns creature animation info from an existing INI file. + * @param animationId the creature animation id + * @return an list of {@link IniMap} instances with potential creature animation data. + * Returns {@code null} if no matching INI was found. + */ + private static List getAnimationInfoByIni(int animationId) + { + List retVal = new ArrayList<>(); + + animationId &= 0xffff; + String iniFile = String.format("%04X.INI", animationId); + if (ResourceFactory.resourceExists(iniFile)) { + retVal.add(new IniMap(ResourceFactory.getResourceEntry(iniFile), true)); + } + + return retVal; + } + + /** + * Returns creature animation info from hardcoded creature data. + * @param animationId the creature animation id + * @return an list of {@link IniMap} instance with potential creature animation data. + * Returns empty list if no creature data was found. + */ + private static List getAnimationInfoByTable(int animationId) + { + return SpriteTables.createIniMaps(animationId & 0xffff); + } + + /** + * Returns creature animation info based on ANISND.2DA data and analyzing potential slot ranges. + * May return false positives. + * @param animationId the creature animation id + * @return a list of {@link IniMap} instances with potential creature animation data. + * Returns {@code null} if no potential match was found. + */ + private static List getAnimationInfoByGuess(int animationId) + { + if (Profile.getGame() == Profile.Game.PST || Profile.getGame() == Profile.Game.PSTEE) { + return guessIniMapsPst(animationId); + } else { + return guessIniMaps(animationId); + } + } + + // Attempts to find potential non-PST-specific IniMap instances + private static List guessIniMaps(int animationId) + { + List retVal = new ArrayList<>(); + String resref = null; + String palette = null; + + // evaluate ANIMATE.SRC if available + ResourceEntry resEntry = ResourceFactory.getResourceEntry("ANIMATE.SRC"); + if (resEntry != null) { + IniMap anisrc = IniMapCache.get(resEntry); + if (anisrc != null) { + IniMapSection iniSection = anisrc.getUnnamedSection(); + if (iniSection != null) { + for (final Iterator iter = iniSection.iterator(); iter.hasNext(); ) { + IniMapEntry entry = iter.next(); + try { + String key = entry.getKey(); + int id = (key.startsWith("0x") || key.startsWith("0X")) ? Misc.toNumber(key.substring(2, key.length()), 16, -1) + : Misc.toNumber(key, -1); + if (id == animationId) { + String value = entry.getValue(); + if (id > 0x1000 && value.length() > 4) { + value = value.substring(0, 4); + } + resref = value; + break; + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + } + + if (resref == null) { + // evaluate ANISND.IDS if available + IdsMap anisnd = IdsMapCache.get("ANISND.IDS"); + if (anisnd != null) { + IdsMapEntry anisndEntry = anisnd.get(animationId); + if (anisndEntry != null) { + String[] elements = anisndEntry.getSymbol().split("\\s+"); + if (elements.length > 0 && elements[0].length() <= 8) { + resref = elements[0]; + int pos = resref.indexOf('_'); + if (pos > 0) { + // assuming underscore indicates a palette resref + palette = resref; + resref = resref.substring(0, pos); + } else if (animationId >= 0x1000 && resref.length() > 4) { + resref = resref.substring(0, 4); + } + } + } + } + } + + if (resref == null) { + return retVal; + } + + if (palette == null) { + palette = "*"; + } + + List tableEntries = new ArrayList<>(); + AnimationInfo.Type type = AnimationInfo.Type.typeOfId(animationId); + if (type == null) { + return retVal; + } + + ResourceEntry bamEntry; + switch (type) { + case EFFECT: + tableEntries.add(String.format("0x%04x %s 0 0 0 * %s * * * * * * * * *", animationId, resref, palette)); + break; + case MONSTER_QUADRANT: + if (ResourceFactory.resourceExists(resref + "G14.BAM")) { + tableEntries.add(String.format("0x%04x %s 1 32 5 * %s * * * * * * * * *", animationId, resref, palette)); + } + break; + case MONSTER_MULTI: + if (ResourceFactory.resourceExists(resref + "1908.BAM")) { + tableEntries.add(String.format("0x%04x %s 2 72 13 * %s * * * * 1 * * * *", animationId, resref, palette)); + } + break; + case MONSTER_MULTI_NEW: + if (ResourceFactory.resourceExists(resref + "G145.BAM")) { + tableEntries.add(String.format("0x%04x %s 2 32 5 * %s * * * * 1 * * * *", animationId, resref, palette)); + } else if (ResourceFactory.resourceExists(resref + "G1.BAM")) { + tableEntries.add(String.format("0x%04x %s 2 32 5 * %s * * * * 0 * * * *", animationId, resref, palette)); + } + break; + case MONSTER_LAYERED_SPELL: + resref = guessResourceRef(resref, "G1"); + bamEntry = ResourceFactory.getResourceEntry(resref + "G1.BAM"); + if (bamEntry != null) { + int falseColor = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; + tableEntries.add(String.format("0x%04x %s 4 16 3 * %s * * * %d * * * * *", animationId, resref, palette, falseColor)); + } + break; + case MONSTER_ANKHEG: + resref = guessResourceRef(resref, "DG1"); + bamEntry = ResourceFactory.getResourceEntry(resref + "DG1.BAM"); + if (bamEntry != null) { + tableEntries.add(String.format("0x%04x %s 6 24 5 * %s * * * * * * * * *", animationId, resref, palette)); + } + break; + case TOWN_STATIC: + resref = guessResourceRef(resref, ""); + bamEntry = ResourceFactory.getResourceEntry(resref + ".BAM"); + if (bamEntry != null) { + int falseColor = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; + tableEntries.add(String.format("0x%04x %s 7 16 3 * %s * * * %d * * * * *", animationId, resref, palette, falseColor)); + } + break; + case CHARACTER: + bamEntry = ResourceFactory.getResourceEntry(resref + "1G1.BAM"); + if (bamEntry != null) { + int falseColor = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; + int split = ResourceFactory.resourceExists(resref + "1G15.BAM") ? 1 : 0; + tableEntries.add(String.format("0x%04x %s 8 16 3 * * * * * %d %d 1 * * *", animationId, resref, falseColor, split)); + } + break; + case CHARACTER_OLD: + bamEntry = ResourceFactory.getResourceEntry(resref + "1G1.BAM"); + if (bamEntry != null) { + int falseColor = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; + tableEntries.add(String.format("0x%04x %s 9 16 3 * * * * * %d * * * * *", animationId, resref, falseColor)); + } + break; + case MONSTER: + resref = guessResourceRef(resref, "G1"); + bamEntry = ResourceFactory.getResourceEntry(resref + "G1.BAM"); + if (bamEntry != null) { + int split = ResourceFactory.resourceExists(resref + "G15.BAM") ? 1 : 0; + int falseColor = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; + tableEntries.add(String.format("0x%04x %s 10 16 3 * %s * * * %d %d * * * *", animationId, resref, palette, falseColor, split)); + tableEntries.add(String.format("0x%04x %s 11 16 3 * %s * * * %d * * * * *", animationId, resref, palette, falseColor)); + } + break; + case MONSTER_OLD: + resref = guessResourceRef(resref, "G1"); + bamEntry = ResourceFactory.getResourceEntry(resref + "G1.BAM"); + if (bamEntry != null) { + int falseColor = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; + tableEntries.add(String.format("0x%04x %s 11 16 3 * %s * * * %d * * * * *", animationId, resref, palette, falseColor)); + } + break; + case MONSTER_LAYERED: + resref = guessResourceRef(resref, "G1"); + bamEntry = ResourceFactory.getResourceEntry(resref + "G1.BAM"); + if (bamEntry != null) { + tableEntries.add(String.format("0x%04x %s 4 16 3 * * * * * * * * * * *", animationId, resref)); + } + break; + case MONSTER_LARGE: + resref = guessResourceRef(resref, "G1"); + bamEntry = ResourceFactory.getResourceEntry(resref + "G1.BAM"); + if (bamEntry != null) { + int falseColor = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; + tableEntries.add(String.format("0x%04x %s 12 16 3 * %s * * * %d * * * * *", animationId, resref, palette, falseColor)); + } + break; + case MONSTER_LARGE_16: + resref = guessResourceRef(resref, "G1"); + bamEntry = ResourceFactory.getResourceEntry(resref + "G1.BAM"); + if (bamEntry != null) { + int falseColor = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; + tableEntries.add(String.format("0x%04x %s 13 16 3 * %s * * * %d * * * * *", animationId, resref, palette, falseColor)); + } + break; + case AMBIENT_STATIC: + resref = guessResourceRef(resref, "G1"); + bamEntry = ResourceFactory.getResourceEntry(resref + "G1.BAM"); + if (bamEntry != null) { + int falseColor = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; + tableEntries.add(String.format("0x%04x %s 14 16 3 * %s * * * %d * * * * *", animationId, resref, palette, falseColor)); + } + break; + case AMBIENT: + resref = guessResourceRef(resref, "G1"); + bamEntry = ResourceFactory.getResourceEntry(resref + "G1.BAM"); + if (bamEntry != null) { + int falseColor = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; + tableEntries.add(String.format("0x%04x %s 15 16 3 * %s * * * %d * * * * *", animationId, resref, palette, falseColor)); + } + break; + case FLYING: + resref = guessResourceRef(resref, ""); + bamEntry = ResourceFactory.getResourceEntry(resref + ".BAM"); + if (bamEntry != null) { + int falseColor = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; + tableEntries.add(String.format("0x%04x %s 16 16 3 * %s * * * %d * * * * *", animationId, resref, palette, falseColor)); + } + break; + case MONSTER_ICEWIND: + { + boolean found = false; + if (resref.length() >= 4 && !found) { + for (final String suffix : new String[] { "A1", "A2", "A3", "A4", "CA", "DE", "GH", "GU", "SC", "SD", "SL", "SP", "TW", "WK" }) { + if (ResourceFactory.resourceExists(resref + suffix + ".BAM")) { + found = true; + break; + } + } + } + if (found) { + tableEntries.add(String.format("0x%04x %s 17 24 3 * %s * * * * * * * * *", animationId, resref, palette)); + } + break; + } + default: + } + + if (!tableEntries.isEmpty()) { + for (final String line : tableEntries) { + StringBuilder sb = new StringBuilder(); + sb.append("2DA V1.0").append('\n'); + sb.append("*").append('\n'); + sb.append(" RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD").append('\n'); + sb.append(line).append('\n'); + ResourceEntry entry = new BufferedResourceEntry(ByteBuffer.wrap(sb.toString().getBytes()), Integer.toString(animationId, 16) + ".2DA"); + Table2da table = new Table2da(entry); + retVal.addAll(SpriteTables.processTable(Profile.getGame(), table, animationId)); + } + } + + return retVal; + } + + // Helper method: attempts to find an existing resource with the specified name parts. + // Returns the resref of the matching resource. Returns the original resref otherwise. + private static String guessResourceRef(String resref, String suffix) + { + String retVal = resref; + if (retVal == null) { + return retVal; + } + + if (suffix == null) { + suffix = ""; + } + + while (retVal.length() >= 4) { + if (ResourceFactory.resourceExists(retVal + suffix + ".BAM")) { + return retVal; + } + retVal = retVal.substring(0, resref.length() - 1); + } + + return resref; + } + + // Attempts to find potential PST-specific IniMap instances + private static List guessIniMapsPst(int animationId) + { + List retVal = new ArrayList<>(); + String resref = null; + + IniMap resIni = IniMapCache.get("RESDATA.INI", true); + if (resIni == null) { + return retVal; + } + + // only regular animations are considered + int id = animationId & 0x0fff; + IniMapSection iniSection = resIni.getSection(Integer.toString(id)); + if (iniSection == null) { + iniSection = resIni.getSection("0x" + Integer.toString(id, 16)); + } + if (iniSection == null) { + return retVal; + } + + int clown = 0; + for (final Sequence seq : Sequence.values()) { + String cmd = MonsterPlanescapeDecoder.getActionCommand(seq); + if (cmd != null) { + String key = iniSection.getAsString(cmd); + if (key != null && key.length() >= 7) { + ResourceEntry bamEntry = ResourceFactory.getResourceEntry(key + "b.bam"); + if (bamEntry != null) { + clown = SpriteUtils.bamHasFalseColors(bamEntry) ? 1 : 0; + resref = key.substring(0, 1) + key.substring(4, key.length()) + "b"; + break; + } + } + } + } + + if (resref != null) { + int armor = iniSection.getAsInteger("armor", 0); + int bestiary = iniSection.getAsInteger("bestiary", 0); + + StringBuilder sb = new StringBuilder(); + sb.append("2DA V1.0").append('\n'); + sb.append("*").append('\n'); + sb.append(" RESREF RESREF2 TYPE ELLIPSE SPACE CLOWN ARMOR BESTIARY").append('\n'); + sb.append(String.format("0x%04x %s * 18 16 3 %d %d %d", id, resref, clown, armor, bestiary)).append('\n'); + ResourceEntry entry = new BufferedResourceEntry(ByteBuffer.wrap(sb.toString().getBytes()), Integer.toString(animationId, 16) + ".2DA"); + Table2da table = new Table2da(entry); + retVal = SpriteTables.processTable(Profile.getGame(), table, animationId); + } + + return retVal; + } + + private SpriteUtils() { } From a1ff76b05c272983d119c9d2477be06cbf7cf78a Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sat, 27 Mar 2021 22:04:44 +0100 Subject: [PATCH 099/158] SpriteDecoder: Improve method descriptions --- .../resource/cre/decoder/SpriteDecoder.java | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index 787534e35..5f57a48d3 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -711,37 +711,49 @@ protected int[] getNewPaletteData(ResourceEntry bamRes) return SpriteUtils.loadReplacementPalette(getNewPalette()); } - /** ??? */ + /** + * Returns whether blending mode "brightest" is enabled. + *

    Blending modes and their effects: + *

  • Brightest only: Use {@code GL_ONE_MINUS_DST_COLOR} + *
  • MultiplyBlend only: Use {@code GL_DST_COLOR} + *
  • Brightest and MultiplyBlend: Use {@code GL_SRC_COLOR} + */ public boolean isBrightest() { return getAttribute(KEY_BRIGHTEST); } - /** ??? */ + /** Sets blending mode "brightest". */ protected void setBrightest(boolean b) { setAttribute(KEY_BRIGHTEST, b); } - /** Returns whether blending mode is enabled. */ + /** + * Returns whether blending mode "multiply_blend" is enabled. + *

    Blending modes and their effects: + *

  • Brightest only: Use {@code GL_ONE_MINUS_DST_COLOR} + *
  • MultiplyBlend only: Use {@code GL_DST_COLOR} + *
  • Brightest and MultiplyBlend: Use {@code GL_SRC_COLOR} + */ public boolean isMultiplyBlend() { return getAttribute(KEY_MULTIPLY_BLEND); } - /** Sets blending mode. */ + /** Sets blending mode "multiply_blend". */ protected void setMultiplyBlend(boolean b) { setAttribute(KEY_MULTIPLY_BLEND, b); } - /** ??? */ + /** Returns whether sprite is affected by environmental lighting. */ public boolean isLightSource() { return getAttribute(KEY_LIGHT_SOURCE); } - /** ??? */ + /** Sets whether sprite is affected by environmental lighting. */ protected void setLightSource(boolean b) { setAttribute(KEY_LIGHT_SOURCE, b); From 63f25e413af5f6b2ea56764b9a423647a7406239 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sun, 28 Mar 2021 18:17:58 +0200 Subject: [PATCH 100/158] SpriteDecoder: Improve error messages --- .../infinity/resource/cre/decoder/SpriteDecoder.java | 2 +- .../resource/cre/viewer/CreatureControlPanel.java | 10 +++++----- .../infinity/resource/cre/viewer/CreatureViewer.java | 7 +++++-- src/org/infinity/resource/cre/viewer/MediaPanel.java | 2 +- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index 5f57a48d3..8ba2d5b2a 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -197,7 +197,7 @@ public static SpriteDecoder importSprite(CreResource cre) throws Exception Objects.requireNonNull(cre, "CRE resource cannot be null"); int animationId = ((IsNumeric)cre.getAttribute(CreResource.CRE_ANIMATION)).getValue(); Class spriteClass = - Objects.requireNonNull(SpriteUtils.detectAnimationType(animationId), "Could not determine animation type"); + Objects.requireNonNull(SpriteUtils.detectAnimationType(animationId), String.format("Creature animation is not available: 0x%04x", animationId)); try { Constructor ctor = Objects.requireNonNull(spriteClass.getConstructor(CreResource.class), "No matching constructor found"); diff --git a/src/org/infinity/resource/cre/viewer/CreatureControlPanel.java b/src/org/infinity/resource/cre/viewer/CreatureControlPanel.java index 2d090f113..40d7ac2ed 100644 --- a/src/org/infinity/resource/cre/viewer/CreatureControlPanel.java +++ b/src/org/infinity/resource/cre/viewer/CreatureControlPanel.java @@ -136,7 +136,7 @@ public void applySettings() getControlModel().resetDecoder(cre); } catch (Exception e) { e.printStackTrace(); - getViewer().showErrorMessage("Could not load creature animation.\nError: " + e.getMessage()); + getViewer().showErrorMessage(e.getMessage(), "Loading creature"); } getViewer().getSettingsPanel().reset(); @@ -163,7 +163,7 @@ private void init() // this is a good default width for all selection controls in this panel cbCreSelection.setPrototypeDisplayValue(CreatureItem.getDefault()); int defWidth = cbCreSelection.getPreferredSize().width * 5 / 4; - setPreferredWidth(cbCreSelection, defWidth); +// setPreferredWidth(cbCreSelection, defWidth); cbCreSelection.addActionListener(listeners); updateToolTip(cbCreSelection); @@ -179,14 +179,14 @@ public void focusGained(FocusEvent e) } } }); - setPreferredWidth(cbCreAnimation, defWidth); +// setPreferredWidth(cbCreAnimation, defWidth); cbCreAnimation.setEditable(true); cbCreAnimation.addActionListener(listeners); updateToolTip(cbCreAnimation); JLabel l3 = new JLabel("Status:"); cbCreAllegiance = new JComboBox<>(model.getModelAllegiance()); - setPreferredWidth(cbCreAllegiance, defWidth); +// setPreferredWidth(cbCreAllegiance, defWidth); cbCreAllegiance.addActionListener(listeners); updateToolTip(cbCreAllegiance); @@ -506,7 +506,7 @@ else if (e.getSource() == cbCreSelection) { updateToolTip(cbCreSelection); } catch (Exception ex) { ex.printStackTrace(); - getViewer().showErrorMessage("Could not load the creature resource.\nError: " + ex.getMessage()); + getViewer().showErrorMessage(ex.getMessage(), "Creature selection"); } } else if (e.getSource() == cbCreAnimation) { diff --git a/src/org/infinity/resource/cre/viewer/CreatureViewer.java b/src/org/infinity/resource/cre/viewer/CreatureViewer.java index 7ba2df4fe..ed2f66a2e 100644 --- a/src/org/infinity/resource/cre/viewer/CreatureViewer.java +++ b/src/org/infinity/resource/cre/viewer/CreatureViewer.java @@ -98,12 +98,15 @@ public void setCreResource(CreResource cre) public RenderPanel getRenderPanel() { return panelCanvas; } /** Shows an error dialog with the specified message. */ - public void showErrorMessage(String msg) + public void showErrorMessage(String msg, String title) { if (msg == null || msg.isEmpty()) { msg = "An error has occurred."; } - JOptionPane.showMessageDialog(this, msg, "Error", JOptionPane.ERROR_MESSAGE); + if (title == null || title.isEmpty()) { + title = "Error"; + } + JOptionPane.showMessageDialog(this, msg, title, JOptionPane.ERROR_MESSAGE); } //--------------------- Begin Class ChildFrame --------------------- diff --git a/src/org/infinity/resource/cre/viewer/MediaPanel.java b/src/org/infinity/resource/cre/viewer/MediaPanel.java index a98dcda2a..bbd0ed048 100644 --- a/src/org/infinity/resource/cre/viewer/MediaPanel.java +++ b/src/org/infinity/resource/cre/viewer/MediaPanel.java @@ -787,7 +787,7 @@ public void itemStateChanged(ItemEvent e) loadSequence(seq); } catch (Exception ex) { ex.printStackTrace(); - getViewer().showErrorMessage("Could not load animation sequence.\nError: " + ex.getMessage()); + getViewer().showErrorMessage(ex.getMessage(), "Sequence selection"); } finally { WindowBlocker.blockWindow(getViewer(), false); } From 4959d6a52ee805714de4199e20cbb21b1ee23a27 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sun, 28 Mar 2021 18:18:47 +0200 Subject: [PATCH 101/158] Creature Animation Browser: Sort animation list by name --- .../infinity/resource/cre/viewer/CreatureAnimationModel.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/org/infinity/resource/cre/viewer/CreatureAnimationModel.java b/src/org/infinity/resource/cre/viewer/CreatureAnimationModel.java index 94b1f4aa2..75dc60222 100644 --- a/src/org/infinity/resource/cre/viewer/CreatureAnimationModel.java +++ b/src/org/infinity/resource/cre/viewer/CreatureAnimationModel.java @@ -195,7 +195,7 @@ public void removeElementAt(int index) //--------------------- End Interface MutableComboBoxModel --------------------- /** - * Helper method: Attempts to convert the specified argument into a meaning numeric value. + * Helper method: Attempts to convert the specified argument into a meaningful numeric value. * An {@code AnimateEntry} object returns the associted animation id. * A string is parsed and returns as decimal or hexadecimal number. * Returns -1 if argument could not be converted. @@ -296,7 +296,8 @@ public String getSymbol() @Override public int compareTo(AnimateEntry o) { - return id - o.id; + // sort by symbolic name + return toString().compareTo(o.toString()); } @Override From f245f12d55abd1da327dc9eac2522f3c763dbf5a Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 2 Apr 2021 10:29:58 +0200 Subject: [PATCH 102/158] SpriteDecoder: Internal changes - Made sequence definition classes cloneable - Added Composite attribute to SegmentDef class - Converted a BamDecoder method description to JavaDoc format --- .../resource/cre/decoder/FlyingDecoder.java | 4 +- .../cre/decoder/MonsterPlanescapeDecoder.java | 4 +- .../resource/cre/decoder/SpriteDecoder.java | 6 +- .../resource/cre/decoder/util/CycleDef.java | 33 ++++++++- .../resource/cre/decoder/util/DirDef.java | 23 +++++- .../resource/cre/decoder/util/SegmentDef.java | 71 ++++++++++++++----- .../resource/cre/decoder/util/SeqDef.java | 44 +++++++++--- .../resource/graphics/BamDecoder.java | 24 ++++--- 8 files changed, 161 insertions(+), 48 deletions(-) diff --git a/src/org/infinity/resource/cre/decoder/FlyingDecoder.java b/src/org/infinity/resource/cre/decoder/FlyingDecoder.java index 6ff9dd1f9..78178c0ea 100644 --- a/src/org/infinity/resource/cre/decoder/FlyingDecoder.java +++ b/src/org/infinity/resource/cre/decoder/FlyingDecoder.java @@ -109,8 +109,8 @@ protected SeqDef getSequenceDefinition(Sequence seq) ResourceEntry entry = ResourceFactory.getResourceEntry(getAnimationResref() + ".BAM"); if (SpriteUtils.bamCyclesExist(entry, cycleIndex, SeqDef.DIR_FULL_W.length)) { - retVal = SeqDef.createSequence(seq, SeqDef.DIR_FULL_W, false, entry, cycleIndex); - SeqDef tmp = SeqDef.createSequence(seq, SeqDef.DIR_FULL_E, true, entry, cycleIndex + 1); + retVal = SeqDef.createSequence(seq, SeqDef.DIR_FULL_W, false, entry, cycleIndex, null); + SeqDef tmp = SeqDef.createSequence(seq, SeqDef.DIR_FULL_E, true, entry, cycleIndex + 1, null); retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); } diff --git a/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java index b46e5d6a3..4237e80bd 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java @@ -538,10 +538,10 @@ protected SeqDef getSequenceDefinition(Sequence seq) } if (directions != null && SpriteUtils.bamCyclesExist(entry, 0, directions.length)) { - SeqDef tmp = SeqDef.createSequence(seq, directions, false, entry, 0); + SeqDef tmp = SeqDef.createSequence(seq, directions, false, entry, 0, null); retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); if (mirror) { - tmp = SeqDef.createSequence(seq, directionsE, true, entry, 1); + tmp = SeqDef.createSequence(seq, directionsE, true, entry, 1, null); retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); } } diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index 8ba2d5b2a..71eb26fd5 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -875,14 +875,14 @@ public void setAutoApplyChanges(boolean b) /** * Returns whether a creature animation reload has been requested. - * Call {@link #applyPendingChanges()} to apply the changes. + * Call {@link #applyAnimationChanges()} to apply the changes. */ public boolean hasAnimationChanged() { return animationChanged; } - /** Call to request a creature animation reload by the method {@link #applyPendingChanges()}. */ + /** Call to request a creature animation reload by the method {@link #applyAnimationChanges()}. */ public void setAnimationChanged() { animationChanged = true; @@ -891,7 +891,7 @@ public void setAnimationChanged() } } - /** Call to cancel the request of a creature animation reload by the method {@link #applyPendingChanges()}. */ + /** Call to cancel the request of a creature animation reload by the method {@link #applyAnimationChanges()}. */ public void resetAnimationChanged() { animationChanged = false; diff --git a/src/org/infinity/resource/cre/decoder/util/CycleDef.java b/src/org/infinity/resource/cre/decoder/util/CycleDef.java index 42b910cdc..2043ea9b8 100644 --- a/src/org/infinity/resource/cre/decoder/util/CycleDef.java +++ b/src/org/infinity/resource/cre/decoder/util/CycleDef.java @@ -17,7 +17,7 @@ /** * Definition of a single cycle for a specific direction in an animation sequence. */ -public class CycleDef +public class CycleDef implements Cloneable { private final ArrayList cycles = new ArrayList<>(); @@ -84,12 +84,33 @@ public CycleDef(DirDef parent, ResourceEntry bamResource, int cycle, SegmentDef. addCycle(bamResource, cycle, type, behavior); } + /** + * Creates a new cycle definition with the specified parameters linked to the specified {@link DirDef} instance. + * @param parent the parent {@code DirDef} instance + * @param cycleInfo collection of fully defined {@code SegmentDef} instances that are added to this cycle definition. + */ public CycleDef(DirDef parent, Collection cycleInfo) { this.parent = parent; addCycles(cycleInfo); } + /** + * Creates a new cycle definition with the attributes defined in the specified {@code CycleDef} argument. + * Parent attribute is set to {@code null}. + * @param sd the {@code CycleDef} object to clone. + */ + public CycleDef(CycleDef cd) + { + Objects.requireNonNull(cd, "CycleDef instance cannot be null"); + this.parent = null; + List list = new ArrayList<>(); + for (final SegmentDef sd : cd.cycles) { + list.add(new SegmentDef(sd)); + } + addCycles(list); + } + /** Returns the parent {@link DirDef} instance linked to this object. */ public DirDef getParent() { return parent; } @@ -132,13 +153,13 @@ public void addCycles(Collection cycleInfo) /** Advances the animation by one frame for all segment definitions according to their respective behavior. */ public void advance() { - this.cycles.forEach(sd -> sd.advance()); + this.cycles.forEach(SegmentDef::advance); } /** Resets BAM cycles in all segment definitions back to the first frame. */ public void reset() { - this.cycles.forEach(sd -> sd.reset()); + this.cycles.forEach(SegmentDef::reset); } /** Determines the minimum number of frames in the whole list of segment definitions. */ @@ -153,6 +174,12 @@ public int getMaximumFrames() return cycles.stream().mapToInt(v -> v.getFrameCount()).max().orElse(0); } + @Override + public CycleDef clone() + { + return new CycleDef(this); + } + @Override public String toString() { diff --git a/src/org/infinity/resource/cre/decoder/util/DirDef.java b/src/org/infinity/resource/cre/decoder/util/DirDef.java index 9db499fba..fdf085164 100644 --- a/src/org/infinity/resource/cre/decoder/util/DirDef.java +++ b/src/org/infinity/resource/cre/decoder/util/DirDef.java @@ -9,7 +9,7 @@ /** * Definition of a single direction in an animation sequence. */ -public class DirDef +public class DirDef implements Cloneable { private final Direction direction; private final CycleDef cycle; @@ -44,6 +44,21 @@ public DirDef(SeqDef parent, Direction dir, boolean mirrored, CycleDef cycle) this.mirrored = mirrored; } + /** + * Creates a new direction definition with the attributes defined in the specified {@code DirDef} argument. + * Parent attribute is set to {@code null}. + * @param dd the {@code DirDef} object to clone. + */ + public DirDef(DirDef dd) + { + Objects.requireNonNull(dd, "DirDef instance cannot be null"); + this.parent = null; + this.direction = dd.direction; + this.cycle = new CycleDef(dd.cycle); + this.cycle.setParent(this); + this.mirrored = dd.mirrored; + } + /** Returns the parent {@link SeqDef} instance linked to this object. */ public SeqDef getParent() { return parent; } @@ -68,6 +83,12 @@ public DirDef(SeqDef parent, Direction dir, boolean mirrored, CycleDef cycle) /** Resets BAM cycles in all segment definitions in the associated cycle back to the first frame. */ public void reset() { cycle.reset(); } + @Override + public DirDef clone() + { + return new DirDef(this); + } + @Override public String toString() { diff --git a/src/org/infinity/resource/cre/decoder/util/SegmentDef.java b/src/org/infinity/resource/cre/decoder/util/SegmentDef.java index a52889ecb..08e120400 100644 --- a/src/org/infinity/resource/cre/decoder/util/SegmentDef.java +++ b/src/org/infinity/resource/cre/decoder/util/SegmentDef.java @@ -4,6 +4,8 @@ package org.infinity.resource.cre.decoder.util; +import java.awt.AlphaComposite; +import java.awt.Composite; import java.util.EnumMap; import java.util.Objects; @@ -12,7 +14,7 @@ /** * Definition of a single segment within a cycle definition. */ -public class SegmentDef +public class SegmentDef implements Cloneable { /** Indicates the sprite type for this particular BAM segment (such as avatar, weapon, shield or helmet). */ public enum SpriteType { @@ -80,6 +82,7 @@ public static Behavior getOpposite(Behavior b) private final SpriteType type; private final Behavior behavior; private final int numFrames; + private final Composite composite; private CycleDef parent; private int curFrame; @@ -120,25 +123,27 @@ public static String fixBehaviorSuffix(String suffix) /** * Creates a new independent segment definition with the specified parameters. - * Sprite type is assumed to be {@link SpriteType#AVATAR}. Behavior is assumed to be {@link Behavior#REPEAT}. + * Behavior is assumed to be {@link Behavior#REPEAT}. Composite object is initialized with {@link AlphaComposite#SrcOver}. * @param entry the BAM resource * @param cycleIdx the BAM cycle index + * @param type the sprite type of this segment. */ - public SegmentDef(ResourceEntry entry, int cycleIdx) + public SegmentDef(ResourceEntry entry, int cycleIdx, SpriteType type) { - this(null, entry, cycleIdx, SpriteType.AVATAR, null); + this(null, entry, cycleIdx, type, null, null); } /** * Creates a new independent segment definition with the specified parameters. - * Behavior is assumed to be {@link Behavior#REPEAT}. + * Composite object is initialized with {@link AlphaComposite#SrcOver}. * @param entry the BAM resource * @param cycleIdx the BAM cycle index * @param type the sprite type of this segment. + * @param behavior the playback behavior of this segment. */ - public SegmentDef(ResourceEntry entry, int cycleIdx, SpriteType type) + public SegmentDef(ResourceEntry entry, int cycleIdx, SpriteType type, Behavior behavior) { - this(null, entry, cycleIdx, type, null); + this(null, entry, cycleIdx, type, behavior, null); } /** @@ -147,35 +152,38 @@ public SegmentDef(ResourceEntry entry, int cycleIdx, SpriteType type) * @param cycleIdx the BAM cycle index * @param type the sprite type of this segment. * @param behavior the playback behavior of this segment. + * @param composite the {@link Composite} object used for rendering sprite frames onto the canvas. */ - public SegmentDef(ResourceEntry entry, int cycleIdx, SpriteType type, Behavior behavior) + public SegmentDef(ResourceEntry entry, int cycleIdx, SpriteType type, Behavior behavior, Composite composite) { - this(null, entry, cycleIdx, type, behavior); + this(null, entry, cycleIdx, type, behavior, composite); } /** * Creates a new segment definition with the specified parameters linked to the specified {@link CycleDef} instance. - * Sprite type is assumed to be {@link SpriteType#AVATAR}. Behavior is assumed to be {@link Behavior#REPEAT}. + * Behavior is assumed to be {@link Behavior#REPEAT}. Composite object is initialized with {@link AlphaComposite#SrcOver}. * @param parent the parent {@code CycleDef} instance. * @param entry the BAM resource * @param cycleIdx the BAM cycle index + * @param type the sprite type of this segment. */ - public SegmentDef(CycleDef parent, ResourceEntry entry, int cycleIdx) + public SegmentDef(CycleDef parent, ResourceEntry entry, int cycleIdx, SpriteType type) { - this(parent, entry, cycleIdx, SpriteType.AVATAR, null); + this(parent, entry, cycleIdx, type, null, null); } /** * Creates a new segment definition with the specified parameters linked to the specified {@link CycleDef} instance. - * Behavior is assumed to be {@link Behavior#REPEAT}. + * Composite object is initialized with {@link AlphaComposite#SrcOver}. * @param parent the parent {@code CycleDef} instance. * @param entry the BAM resource * @param cycleIdx the BAM cycle index - * @param type the sprite type of this segment. + * @param type the spriter type of this segment. + * @param behavior the playback behavior of this segment. */ - public SegmentDef(CycleDef parent, ResourceEntry entry, int cycleIdx, SpriteType type) + public SegmentDef(CycleDef parent, ResourceEntry entry, int cycleIdx, SpriteType type, Behavior behavior) { - this(parent, entry, cycleIdx, type, null); + this(parent, entry, cycleIdx, type, behavior, null); } /** @@ -185,8 +193,9 @@ public SegmentDef(CycleDef parent, ResourceEntry entry, int cycleIdx, SpriteType * @param cycleIdx the BAM cycle index * @param type the spriter type of this segment. * @param behavior the playback behavior of this segment. + * @param composite the {@link Composite} object used for rendering sprite frames onto the canvas. */ - public SegmentDef(CycleDef parent, ResourceEntry entry, int cycleIdx, SpriteType type, Behavior behavior) + public SegmentDef(CycleDef parent, ResourceEntry entry, int cycleIdx, SpriteType type, Behavior behavior, Composite composite) { this.parent = parent; this.bamEntry = Objects.requireNonNull(entry, "BAM resource entry cannot be null"); @@ -194,9 +203,28 @@ public SegmentDef(CycleDef parent, ResourceEntry entry, int cycleIdx, SpriteType this.type = (type != null) ? type : SpriteType.AVATAR; this.behavior = (behavior != null) ? behavior : Behavior.REPEAT; this.numFrames = SpriteUtils.getBamCycleFrames(this.bamEntry, this.cycleIndex); + this.composite = (composite != null) ? composite : AlphaComposite.SrcOver; reset(); } + /** + * Creates a new segment definition with the attributes defined in the specified {@code SegmentDef} argument. + * Parent attribute is set to {@code null}. + * @param sd the {@code SegmentDef} object to clone. + */ + public SegmentDef(SegmentDef sd) + { + Objects.requireNonNull(sd, "SegmentDef instance cannot be null"); + this.parent = null; + this.bamEntry = sd.bamEntry; + this.cycleIndex = sd.cycleIndex; + this.type = sd.type; + this.behavior = sd.behavior; + this.numFrames = sd.numFrames; + this.curFrame = sd.curFrame; + this.composite = sd.composite; + } + /** Returns the parent {@link CycleDef} instance linked with this object. */ public CycleDef getParent() { return parent; } @@ -206,6 +234,9 @@ public SegmentDef(CycleDef parent, ResourceEntry entry, int cycleIdx, SpriteType /** Returns the BAM resource entry associated with the segment. */ public ResourceEntry getEntry() { return bamEntry; } + /** Returns the {@link Composite} object used to render the sprite frames onto a canvas. */ + public Composite getComposite() { return composite; } + /** Returns the cycle index associated with the segment */ public int getCycleIndex() { return cycleIndex; } @@ -300,6 +331,12 @@ public void reset() } } + @Override + public SegmentDef clone() + { + return new SegmentDef(this); + } + @Override public String toString() { diff --git a/src/org/infinity/resource/cre/decoder/util/SeqDef.java b/src/org/infinity/resource/cre/decoder/util/SeqDef.java index 8850aea42..bb464f7ca 100644 --- a/src/org/infinity/resource/cre/decoder/util/SeqDef.java +++ b/src/org/infinity/resource/cre/decoder/util/SeqDef.java @@ -4,6 +4,8 @@ package org.infinity.resource.cre.decoder.util; +import java.awt.AlphaComposite; +import java.awt.Composite; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -17,8 +19,11 @@ /** * Definition of an animation sequence. */ -public class SeqDef +public class SeqDef implements Cloneable { + /** Definition of an empty {@code SeqDef} object. */ + public static final SeqDef DEFAULT = new SeqDef(Sequence.NONE, new DirDef[0]); + /** Full set of 16 directions (east & west). */ public static final Direction[] DIR_FULL = Direction.values(); /** Reduced set of 8 directions (east & west) */ @@ -51,6 +56,16 @@ public SeqDef(Sequence seq, DirDef... dirs) addDirections(dirs); } + public SeqDef(SeqDef sd) + { + Objects.requireNonNull(sd, "SeqDef instance cannot be null"); + this.sequence = sd.sequence; + this.directions = new ArrayList<>(); + for (final DirDef dd : sd.directions) { + addDirections(new DirDef(dd)); + } + } + /** Returns the sequence type this definition is assigned to. */ public Sequence getSequence() { return sequence; } @@ -89,6 +104,12 @@ public boolean isEmpty() return retVal; } + @Override + public SeqDef clone() + { + return new SeqDef(this); + } + @Override public String toString() { @@ -148,25 +169,25 @@ public static ResourceEntry[] getBamResourceList(SegmentDef.SpriteType type, Seq /** * Convenience method: Creates a fully defined sequence if specified directions are found within a single BAM resource. - * Sprite type is assumed to be {@link SegmentDef.SpriteType#AVATAR}. - * Behavior is assumed to be {@link SegmentDef.Behavior#REPEAT}. + * Behavior is assumed to be {@link SegmentDef.Behavior#REPEAT}. Composite object is initialized with {@link AlphaComposite#SrcOver}. * @param seq the animation {@link SpriteDecoder.Sequence}. * @param directions List of directions to add. Cycle indices are advanced accordingly for each direction. * @param mirrored indicates whether cycle indices are calculated in reversed direction * @param bamResource the BAM resource used for all cycle definitions. * @param cycleOfs the first BAM cycle index. Advanced by one for each direction. * Cycle indices are processed in reversed direction if {@code mirrored} is {@code true}. + * @param type the {@link SegmentDef.SpriteType} assigned to all cycle definitions. */ public static SeqDef createSequence(Sequence seq, Direction[] directions, boolean mirrored, - ResourceEntry bamResource, int cycleOfs) + ResourceEntry bamResource, int cycleOfs, SegmentDef.SpriteType type) { return createSequence(seq, directions, mirrored, - new ArrayList() {{ add(new SegmentDef(null, bamResource, cycleOfs)); }}); + new ArrayList() {{ add(new SegmentDef(null, bamResource, cycleOfs, type)); }}); } /** * Convenience method: Creates a fully defined sequence if specified directions are found within a single BAM resource. - * Behavior is assumed to be {@link SegmentDef.Behavior#REPEAT}. + * Composite object is initialized with {@link AlphaComposite#SrcOver}. * @param seq the animation {@link SpriteDecoder.Sequence}. * @param directions List of directions to add. Cycle indices are advanced accordingly for each direction. * @param mirrored indicates whether cycle indices are calculated in reversed direction @@ -174,12 +195,14 @@ public static SeqDef createSequence(Sequence seq, Direction[] directions, boolea * @param cycleOfs the first BAM cycle index. Advanced by one for each direction. * Cycle indices are processed in reversed direction if {@code mirrored} is {@code true}. * @param type the {@link SegmentDef.SpriteType} assigned to all cycle definitions. + * @param behavior the {@link SegmentDef.Behavior} assigned to all cycle definitions. */ public static SeqDef createSequence(Sequence seq, Direction[] directions, boolean mirrored, - ResourceEntry bamResource, int cycleOfs, SegmentDef.SpriteType type) + ResourceEntry bamResource, int cycleOfs, SegmentDef.SpriteType type, + SegmentDef.Behavior behavior) { return createSequence(seq, directions, mirrored, - new ArrayList() {{ add(new SegmentDef(null, bamResource, cycleOfs, type)); }}); + new ArrayList() {{ add(new SegmentDef(null, bamResource, cycleOfs, type, behavior)); }}); } /** @@ -192,13 +215,14 @@ public static SeqDef createSequence(Sequence seq, Direction[] directions, boolea * Cycle indices are processed in reversed direction if {@code mirrored} is {@code true}. * @param type the {@link SegmentDef.SpriteType} assigned to all cycle definitions. * @param behavior the {@link SegmentDef.Behavior} assigned to all cycle definitions. + * @param composite the {@link Composite} object used for rendering sprite frames onto the canvas. */ public static SeqDef createSequence(Sequence seq, Direction[] directions, boolean mirrored, ResourceEntry bamResource, int cycleOfs, SegmentDef.SpriteType type, - SegmentDef.Behavior behavior) + SegmentDef.Behavior behavior, Composite composite) { return createSequence(seq, directions, mirrored, - new ArrayList() {{ add(new SegmentDef(null, bamResource, cycleOfs, type, behavior)); }}); + new ArrayList() {{ add(new SegmentDef(null, bamResource, cycleOfs, type, behavior, composite)); }}); } /** diff --git a/src/org/infinity/resource/graphics/BamDecoder.java b/src/org/infinity/resource/graphics/BamDecoder.java index 54d4630df..48fda7a6f 100644 --- a/src/org/infinity/resource/graphics/BamDecoder.java +++ b/src/org/infinity/resource/graphics/BamDecoder.java @@ -416,16 +416,20 @@ protected void updateSharedBamSize() sharedBamSize = calculateSharedBamSize(sharedBamSize, isSharedPerCycle(), false); } - // Calculates a shared canvas size for the current BAM. - // cycleBased: true=for current cycle only, false=for all available frames - // isMirrored: true=mirror along the x axis, false=no mirroring - // To get the top-left corner of the selected frame: - // For unmirrored frames: - // left = -sharedBamSize.x - frameCenterX() - // top = -sharedBamSize.y - frameCenterY() - // For mirrored frames: - // left = -sharedBamSize.x - (frameWidth() - frameCenterX() - 1) - // top = -sharedBamSize.y - frameCenterY() + /** + * Calculates a shared canvas size for the current BAM. + * To get the top-left corner of the selected frame: + *

    For unmirrored frames: + *

    left = -sharedBamSize.x - frameCenterX()
    + *
    top  = -sharedBamSize.y - frameCenterY()
    + *

    For mirrored frames: + *

    left = -sharedBamSize.x - (frameWidth() - frameCenterX() - 1)
    + *
    top  = -sharedBamSize.y - frameCenterY()
    + * @param rect Reuse this {@code Rectangle} instance if available. + * @param cycleBased {@code true} for current cycle only, {@code false} for all available frames + * @param isMirrored {@code true}: mirror along the x axis, {@code false}: no mirroring + * @return the update rectangle + */ protected Rectangle calculateSharedBamSize(Rectangle rect, boolean cycleBased, boolean isMirrored) { if (rect == null) { From ad3da52fbf0f4a2cc8a16f81bb8a114684914d76 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Tue, 6 Apr 2021 11:13:11 +0200 Subject: [PATCH 103/158] Cosmetic Improved JavaDoc comments and toString() implementations. --- .../infinity/resource/cre/ViewerAnimation.java | 4 ++-- .../infinity/resource/graphics/BamDecoder.java | 6 +++--- .../resource/graphics/BamV1Decoder.java | 18 ++++++++++++++---- .../resource/graphics/BamV2Decoder.java | 7 +++++++ 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/org/infinity/resource/cre/ViewerAnimation.java b/src/org/infinity/resource/cre/ViewerAnimation.java index 4167f5835..488fba2fa 100644 --- a/src/org/infinity/resource/cre/ViewerAnimation.java +++ b/src/org/infinity/resource/cre/ViewerAnimation.java @@ -245,6 +245,7 @@ public void rewind() @Override protected boolean windowClosing(boolean forced) throws Exception { + pause(); if (getDecoder() != null) { getDecoder().close(); } @@ -261,8 +262,7 @@ public void actionPerformed(ActionEvent event) { if (timer == event.getSource()) { if (getController().cycleFrameCount() > 0) { - curFrame += 1; - curFrame %= getController().cycleFrameCount(); + curFrame = (curFrame + 1) % getController().cycleFrameCount(); } showFrame(); } diff --git a/src/org/infinity/resource/graphics/BamDecoder.java b/src/org/infinity/resource/graphics/BamDecoder.java index 48fda7a6f..ca45111e0 100644 --- a/src/org/infinity/resource/graphics/BamDecoder.java +++ b/src/org/infinity/resource/graphics/BamDecoder.java @@ -221,7 +221,7 @@ public String toString() { String retVal = "entry=" + ((bamEntry != null) ? bamEntry.toString() : "(null)"); retVal += ", Type=" + ((type != null) ? type.toString() : "(null)"); - return retVal; + return "[" + retVal + "]"; } //-------------------------- INNER CLASSES -------------------------- @@ -378,7 +378,7 @@ public boolean isEmpty() /** Returns the index of the active cycle. */ public abstract int cycleGet(); - /** Sets the active cycle. (Default: first available cycle) */ + /** Sets the active cycle. Returns whether the specified cycle could be set. */ public abstract boolean cycleSet(int cycleIdx); /** Returns whether the active cycle can be advanced by at least one more frame. */ @@ -399,7 +399,7 @@ public boolean isEmpty() /** Returns the index of the currently selected frame in the active cycle. */ public abstract int cycleGetFrameIndex(); - /** Selects the specified frame in the active cycle. */ + /** Selects the specified frame in the active cycle. Returns whether the specified frame index could be set. */ public abstract boolean cycleSetFrameIndex(int frameIdx); /** Translates the active cycle's frame index into an absolute frame index. Returns -1 if cycle doesn't contain frames. */ diff --git a/src/org/infinity/resource/graphics/BamV1Decoder.java b/src/org/infinity/resource/graphics/BamV1Decoder.java index 7809ac891..0f06babc8 100644 --- a/src/org/infinity/resource/graphics/BamV1Decoder.java +++ b/src/org/infinity/resource/graphics/BamV1Decoder.java @@ -416,6 +416,14 @@ private BamV1FrameEntry(ByteBuffer buffer, int ofs) public void resetCenter() { overrideCenterX = centerX; overrideCenterY = centerY; } public boolean isCompressed() { return compressed; } + + @Override + public String toString() + { + return "[width=" + getWidth() + ", height=" + getHeight() + + ", centerX=" + getCenterX() + ", centerY=" + getCenterY() + + ", compressed=" + Boolean.toString(isCompressed()) + "]" ; + } } @@ -559,10 +567,12 @@ public int cycleGet() @Override public boolean cycleSet(int cycleIdx) { - if (cycleIdx >= 0 && cycleIdx < getDecoder().listCycles.size() && currentCycle != cycleIdx) { - currentCycle = cycleIdx; - if (isSharedPerCycle()) { - updateSharedBamSize(); + if (cycleIdx >= 0 && cycleIdx < getDecoder().listCycles.size()) { + if (currentCycle != cycleIdx) { + currentCycle = cycleIdx; + if (isSharedPerCycle()) { + updateSharedBamSize(); + } } return true; } else { diff --git a/src/org/infinity/resource/graphics/BamV2Decoder.java b/src/org/infinity/resource/graphics/BamV2Decoder.java index a4fbece8e..57c6fd6cb 100644 --- a/src/org/infinity/resource/graphics/BamV2Decoder.java +++ b/src/org/infinity/resource/graphics/BamV2Decoder.java @@ -408,6 +408,13 @@ private BamV2FrameEntry(ByteBuffer buffer, int ofsFrame, int ofsBlocks) public Image getImage() { return frame; } + @Override + public String toString() + { + return "[width=" + getWidth() + ", height=" + getHeight() + + ", centerX=" + getCenterX() + ", centerY=" + getCenterY() + "]" ; + } + private void decodeImage(ByteBuffer buffer, int ofsBlocks, int start, int count) { frame = null; From 9a48809daf098fb97035b2a8e7c04c4dfa2cec6e Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Tue, 6 Apr 2021 11:23:29 +0200 Subject: [PATCH 104/158] Creature Animation Viewer: Fix replacement palette not updating when changing animation type --- src/org/infinity/resource/cre/viewer/MediaPanel.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/org/infinity/resource/cre/viewer/MediaPanel.java b/src/org/infinity/resource/cre/viewer/MediaPanel.java index bbd0ed048..13f316998 100644 --- a/src/org/infinity/resource/cre/viewer/MediaPanel.java +++ b/src/org/infinity/resource/cre/viewer/MediaPanel.java @@ -37,6 +37,7 @@ import org.infinity.resource.cre.decoder.SpriteDecoder; import org.infinity.resource.cre.decoder.util.Direction; import org.infinity.resource.cre.decoder.util.Sequence; +import org.infinity.resource.cre.decoder.util.SpriteUtils; import org.infinity.resource.cre.viewer.icon.Icons; import org.infinity.resource.graphics.PseudoBamDecoder.PseudoBamControl; @@ -103,6 +104,7 @@ public void reset(boolean preserveState) return; } + SpriteUtils.clearBamCache(); SettingsPanel settings = getViewer().getSettingsPanel(); decoder.setAutoApplyChanges(false); decoder.setPaletteReplacementEnabled(settings.isPaletteReplacementEnabled()); From 5a046ca13fa7c6225ae20feb4c3efafdb4f62bcd Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Tue, 6 Apr 2021 13:19:31 +0200 Subject: [PATCH 105/158] SpriteDecoder: Fix incorrectly displayed colors in type 0000 animations - Improved palette retrieval from BMP files: preserve alpha if available --- .../resource/cre/decoder/EffectDecoder.java | 38 +++++++++++++++++++ .../cre/decoder/MonsterPlanescapeDecoder.java | 7 ---- .../resource/cre/decoder/SpriteDecoder.java | 3 +- .../resource/graphics/ColorConvert.java | 7 +++- 4 files changed, 44 insertions(+), 11 deletions(-) diff --git a/src/org/infinity/resource/cre/decoder/EffectDecoder.java b/src/org/infinity/resource/cre/decoder/EffectDecoder.java index c9963396c..29890c144 100644 --- a/src/org/infinity/resource/cre/decoder/EffectDecoder.java +++ b/src/org/infinity/resource/cre/decoder/EffectDecoder.java @@ -5,7 +5,9 @@ package org.infinity.resource.cre.decoder; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.Random; import org.infinity.resource.ResourceFactory; @@ -16,9 +18,11 @@ import org.infinity.resource.cre.decoder.util.DecoderAttribute; import org.infinity.resource.cre.decoder.util.DirDef; import org.infinity.resource.cre.decoder.util.Direction; +import org.infinity.resource.cre.decoder.util.SegmentDef; import org.infinity.resource.cre.decoder.util.SeqDef; import org.infinity.resource.cre.decoder.util.Sequence; import org.infinity.resource.cre.decoder.util.SpriteUtils; +import org.infinity.resource.graphics.BamV1Decoder.BamV1Control; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; @@ -50,6 +54,30 @@ public class EffectDecoder extends SpriteDecoder // A secondary resref to consider if random_render == 1 public static final DecoderAttribute KEY_RESREF2 = DecoderAttribute.with("resref2", DecoderAttribute.DataType.STRING); + protected final BeforeSourceBam FN_BEFORE_SRC_BAM = new BeforeSourceBam() { + @Override + public void accept(BamV1Control control, SegmentDef sd) + { + if (isPaletteReplacementEnabled() && sd.getSpriteType() == SegmentDef.SpriteType.AVATAR) { + int[] palette = getNewPaletteData(sd.getEntry()); + if (palette != null) { + SpriteUtils.applyNewPalette(control, palette); + } + } + + if (isFalseColor()) { + SpriteUtils.fixShadowColor(control, isTransparentShadow()); + if (isPaletteReplacementEnabled()) { + applyFalseColors(control, sd); + } + } + + if (isTranslucencyEnabled() && isTranslucent()) { + applyTranslucency(control); + } + } + }; + /** * A helper method that parses the specified data array and generates a {@link IniMap} instance out of it. * @param data a String array containing table values for a specific table entry. @@ -207,6 +235,16 @@ protected void init() throws Exception setArmorSpecificResref(section.getAsString(KEY_RESREF_ARMOR_SPECIFIC.getName(), "")); } + @Override + protected void createSequence(Sequence seq, Direction[] directions) throws Exception + { + SeqDef sd = Objects.requireNonNull(getSequenceDefinition(seq), "Sequence not available: " + (seq != null ? seq : "(null)")); + if (directions == null) { + directions = Direction.values(); + } + createAnimation(sd, Arrays.asList(directions), FN_BEFORE_SRC_BAM, FN_BEFORE_SRC_FRAME, FN_AFTER_SRC_FRAME, FN_AFTER_DST_FRAME); + } + @Override protected SeqDef getSequenceDefinition(Sequence seq) { diff --git a/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java index 4237e80bd..293285cac 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java @@ -447,13 +447,6 @@ protected void init() throws Exception } } - @Override - protected void createSequence(Sequence seq) throws Exception - { - SeqDef sd = Objects.requireNonNull(getSequenceDefinition(seq), "Sequence not available: " + (seq != null ? seq : "(null)")); - createAnimation(sd, null, FN_BEFORE_SRC_BAM, FN_BEFORE_SRC_FRAME, FN_AFTER_SRC_FRAME, FN_AFTER_DST_FRAME); - } - @Override protected void createSequence(Sequence seq, Direction[] directions) throws Exception { diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index 71eb26fd5..c6ae0f381 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -1047,8 +1047,7 @@ protected int addDirection(Direction dir, int cycleIndex) */ protected void createSequence(Sequence seq) throws Exception { - SeqDef sd = Objects.requireNonNull(getSequenceDefinition(seq), "Sequence not available: " + (seq != null ? seq : "(null)")); - createAnimation(sd, null, FN_BEFORE_SRC_BAM, FN_BEFORE_SRC_FRAME, FN_AFTER_SRC_FRAME, FN_AFTER_DST_FRAME); + createSequence(seq, null); } /** diff --git a/src/org/infinity/resource/graphics/ColorConvert.java b/src/org/infinity/resource/graphics/ColorConvert.java index 1d8c91961..cb2a12d31 100644 --- a/src/org/infinity/resource/graphics/ColorConvert.java +++ b/src/org/infinity/resource/graphics/ColorConvert.java @@ -774,8 +774,11 @@ public static int[] loadPaletteBMP(ResourceEntry entry) throws Exception byte[] palette = new byte[colorCount*4]; is.read(palette); int[] retVal = new int[colorCount]; - for (int i =0; i < colorCount; i++) { - retVal[i] = 0xff000000 | (DynamicArray.getInt(palette, i << 2) & 0x00ffffff); + for (int i = 0; i < colorCount; i++) { + retVal[i] = DynamicArray.getInt(palette, i << 2); + if ((retVal[i] & 0xff000000) == 0) { + retVal[i] |= 0xff000000; + } } return retVal; } else { From d5820fa7e0eb1aade729a9e93436f93fcd6631c4 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Tue, 6 Apr 2021 22:33:04 +0200 Subject: [PATCH 106/158] SpriteDecoder: Add support for more color effect opcodes (8, 51, 52) - Creature Animation Viewer: Added option to turn on/off tint effects - Fixed bug in ItemInfo parser --- .../resource/cre/decoder/EffectDecoder.java | 4 + .../resource/cre/decoder/SpriteDecoder.java | 106 +++++++++++++++++- .../resource/cre/decoder/util/ColorInfo.java | 76 +++++++++---- .../cre/decoder/util/CreatureInfo.java | 101 +++++++++++++++-- .../resource/cre/decoder/util/ItemInfo.java | 58 +++++----- .../cre/decoder/util/SpriteUtils.java | 59 ++++++++++ .../resource/cre/viewer/MediaPanel.java | 3 +- .../resource/cre/viewer/SettingsPanel.java | 38 ++++++- 8 files changed, 377 insertions(+), 68 deletions(-) diff --git a/src/org/infinity/resource/cre/decoder/EffectDecoder.java b/src/org/infinity/resource/cre/decoder/EffectDecoder.java index 29890c144..bf914fc39 100644 --- a/src/org/infinity/resource/cre/decoder/EffectDecoder.java +++ b/src/org/infinity/resource/cre/decoder/EffectDecoder.java @@ -72,6 +72,10 @@ public void accept(BamV1Control control, SegmentDef sd) } } + if (isTintEnabled()) { + applyColorTint(control, sd); + } + if (isTranslucencyEnabled() && isTranslucent()) { applyTranslucency(control); } diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index c6ae0f381..898d049b3 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -26,6 +26,7 @@ import java.util.Arrays; import java.util.EnumMap; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -116,6 +117,10 @@ public void accept(BamV1Control control, SegmentDef sd) applyFalseColors(control, sd); } + if (isTintEnabled()) { + applyColorTint(control, sd); + } + if (isTranslucencyEnabled() && isTranslucent()) { applyTranslucency(control); } @@ -178,6 +183,7 @@ public void accept(DirDef dd, int frameIdx) private boolean showBoundingBox; private boolean transparentShadow; private boolean translucencyEnabled; + private boolean tintEnabled; private boolean paletteReplacementEnabled; private boolean renderSpriteAvatar; private boolean renderSpriteWeapon; @@ -255,6 +261,7 @@ protected SpriteDecoder(AnimationInfo.Type type, CreResource cre) throws Excepti this.showBoundingBox = false; this.transparentShadow = true; this.translucencyEnabled = true; + this.tintEnabled = true; this.paletteReplacementEnabled = true; this.renderSpriteAvatar = true; this.renderSpriteWeapon = true; @@ -348,6 +355,7 @@ protected void discard() { frameClear(); directionMap.clear(); + SpriteUtils.clearBamCache(); } /** @@ -597,6 +605,22 @@ public void setTranslucencyEnabled(boolean b) } } + /** Returns whether tint effects are applied to the creature animation. */ + public boolean isTintEnabled() + { + return tintEnabled; + } + + /** Sets whether tint effects are applied to the creature animation. */ + public void setTintEnabled(boolean b) + { + if (tintEnabled != b) { + tintEnabled = b; + SpriteUtils.clearBamCache(); + spriteChanged(); + } + } + /** Returns whether any kind of palette replacement (full palette or false colors) is enabled. */ public boolean isPaletteReplacementEnabled() { @@ -1085,6 +1109,9 @@ protected void createAnimation(SeqDef definition, List directions, return; } + // Ensure that BeforeSourceBam function is applied only once per source BAM + HashSet bamControlSet = new HashSet<>(); + for (final DirDef dd : definition.getDirections()) { if (!directions.contains(dd.getDirection())) { continue; @@ -1113,7 +1140,8 @@ protected void createAnimation(SeqDef definition, List directions, srcCtrl.cycleSet(sd.getCycleIndex()); if (sd.getCurrentFrame() >= 0) { - if (beforeSrcBam != null) { + if (beforeSrcBam != null && !bamControlSet.contains(srcCtrl)) { + bamControlSet.add(srcCtrl); beforeSrcBam.accept(srcCtrl, sd); } frameInfo.add(new FrameInfo(srcCtrl, sd)); @@ -1439,7 +1467,8 @@ protected void applyFalseColors(BamV1Control control, SegmentDef sd) final Map colorRanges = new HashMap(); for (int loc = 0; loc < 7; loc++) { int ofs = getColorOffset(loc); - Couple colorInfo = getCreatureInfo().getEffectiveColorValue(sd.getSpriteType(), loc); + Couple colorInfo = + getCreatureInfo().getEffectiveColorValue(sd.getSpriteType(), loc); int colIdx = colorInfo.getValue0().intValue(); boolean allowRandom = colorInfo.getValue1().booleanValue(); if (ofs > 0 && colIdx >= 0) { @@ -1457,7 +1486,7 @@ protected void applyFalseColors(BamV1Control control, SegmentDef sd) ColorInfo colorInfo = itemInfo.getColorInfo(); for (int loc = 0; loc < 7; loc++) { int ofs = getColorOffset(loc); - int colIdx = colorInfo.getValue(SegmentDef.SpriteType.WEAPON, loc); + int colIdx = colorInfo.getValue(SegmentDef.SpriteType.WEAPON, ColorInfo.OPCODE_SET_COLOR, loc); if (ofs > 0 && colIdx >= 0) { int[] range = getColorData(colIdx, false); if (range != null) { @@ -1506,6 +1535,75 @@ protected void applyFalseColors(BamV1Control control, SegmentDef sd) control.setExternalPalette(palette); } + /** + * Modifies BAM palette with tint colors from selected effect opcodes. + * @param control the BAM controller. + */ + protected void applyColorTint(BamV1Control control, SegmentDef sd) + { + if (control == null || sd == null || + getAnimationType() == AnimationInfo.Type.MONSTER_PLANESCAPE) { + return; + } + + int[] palette = control.getCurrentPalette(); + Couple fullTint = Couple.with(-1, -1); // stores info for later + // color locations >= 0: affects only false color BAMs directly; data is stored for full palette tint though + for (int loc = 0; loc < 7; loc++) { + int ofs = getColorOffset(loc); + Couple colorInfo = getCreatureInfo().getEffectiveTintValue(sd.getSpriteType(), loc); + int opcode = colorInfo.getValue0().intValue(); + int color = colorInfo.getValue1().intValue(); + if (ofs > 0 && opcode >= 0 && color >= 0) { + // applying tint to color range + if (isFalseColor()) { + palette = SpriteUtils.tintColors(palette, ofs, 12, opcode, color); + } else { + fullTint.setValue0(colorInfo.getValue0()); + fullTint.setValue1(colorInfo.getValue1()); + } + } + } + + if (isFalseColor()) { + // preparing offset array + final int srcOfs = 4; + final int dstOfs = 88; + final int srcLen = 12; + final int dstLen = 8; + final int[] offsets = new int[7]; + for (int i = 0; i < offsets.length; i++) { + offsets[i] = srcOfs + i * srcLen; + } + + // calculating mixed ranges + int k = 0; + for (int i = 0; i < offsets.length - 1; i++) { + int ofs1 = offsets[i]; + for (int j = i + 1; j < offsets.length; j++, k++) { + int ofs2 = offsets[j]; + int ofs3 = dstOfs + k * dstLen; + palette = SpriteUtils.interpolateColors(palette, ofs1, ofs2, srcLen, ofs3, dstLen, false); + } + } + } + + // color location -1: affects whole palette (except transparency and shadow color) + Couple colorInfo = getCreatureInfo().getEffectiveTintValue(sd.getSpriteType(), -1); + if (colorInfo.getValue0() >= 0 && colorInfo.getValue1() >= 0) { + fullTint.setValue0(colorInfo.getValue0()); + fullTint.setValue1(colorInfo.getValue1()); + } + int opcode = fullTint.getValue0().intValue(); + int color = fullTint.getValue1().intValue(); + if (opcode >= 0 && color >= 0) { + // applying tint to whole palette + palette = SpriteUtils.tintColors(palette, 2, 254, opcode, color); + } + + control.setExternalPalette(palette); + } + /** * The specified frame is mirrored horizontally. Both pixel data and center point are adjusted. * @param frameIndex absolute frame index in the BAM frame list. @@ -1583,6 +1681,7 @@ public int hashCode() hash = 31 * hash + Boolean.valueOf(showBoundingBox).hashCode(); hash = 31 * hash + Boolean.valueOf(transparentShadow).hashCode(); hash = 31 * hash + Boolean.valueOf(translucencyEnabled).hashCode(); + hash = 31 * hash + Boolean.valueOf(tintEnabled).hashCode(); hash = 31 * hash + Boolean.valueOf(paletteReplacementEnabled).hashCode(); hash = 31 * hash + Boolean.valueOf(renderSpriteAvatar).hashCode(); hash = 31 * hash + Boolean.valueOf(renderSpriteWeapon).hashCode(); @@ -1618,6 +1717,7 @@ public boolean equals(Object o) retVal &= (this.showBoundingBox == other.showBoundingBox); retVal &= (this.transparentShadow == other.transparentShadow); retVal &= (this.translucencyEnabled == other.translucencyEnabled); + retVal &= (this.tintEnabled == other.tintEnabled); retVal &= (this.paletteReplacementEnabled == other.paletteReplacementEnabled); retVal &= (this.renderSpriteAvatar == other.renderSpriteAvatar); retVal &= (this.renderSpriteWeapon == other.renderSpriteWeapon); diff --git a/src/org/infinity/resource/cre/decoder/util/ColorInfo.java b/src/org/infinity/resource/cre/decoder/util/ColorInfo.java index ddeab11d6..df1fd5061 100644 --- a/src/org/infinity/resource/cre/decoder/util/ColorInfo.java +++ b/src/org/infinity/resource/cre/decoder/util/ColorInfo.java @@ -4,21 +4,33 @@ package org.infinity.resource.cre.decoder.util; +import java.util.Arrays; import java.util.Collections; import java.util.EnumMap; import java.util.HashMap; import java.util.Iterator; import java.util.Map; +import org.infinity.util.tuples.Couple; + /** * Provides information about color definitions for sprites and sprite overlays. */ public class ColorInfo { + /** Effect opcode 7: Set color */ + public static final int OPCODE_SET_COLOR = 7; + /** Effect opcode 8: Set color glow solid */ + public static final int OPCODE_SET_COLOR_GLOW = 8; + /** Effect opcode 51: Character tint solid */ + public static final int OPCODE_TINT_SOLID = 51; + /** Effect opcode 52: Character tint bright */ + public static final int OPCODE_TINT_BRIGHT = 52; + private static final int[] EMPTY_INT_ARRAY = new int[0]; - // Maps value (Map) to an individual sprite overlay types (avatar, weapon, shield, ...) - private final EnumMap> colorMap = new EnumMap<>(SegmentDef.SpriteType.class); + // Maps value (Map, color value>) to an individual sprite overlay types (avatar, weapon, shield, ...) + private final EnumMap, Integer>> colorMap = new EnumMap<>(SegmentDef.SpriteType.class); public ColorInfo() { @@ -31,26 +43,31 @@ public ColorInfo() public SegmentDef.SpriteType[] getTypes() { return colorMap.keySet().toArray(new SegmentDef.SpriteType[colorMap.size()]); } /** Returns an iterator over the color locations for the specified sprite overlay type. */ - public Iterator getLocationIterator(SegmentDef.SpriteType type) + public Iterator> getEffectIterator(SegmentDef.SpriteType type) { - Map map = colorMap.get(type); + Map, Integer> map = colorMap.get(type); if (map != null) { return map.keySet().iterator(); } - return Collections.emptyList().iterator(); // empty iterator + return Collections.>emptyList().iterator(); // empty iterator } /** Returns an array of color location indices for the specified sprite overlay type. */ - public int[] getLocations(SegmentDef.SpriteType type) + public int[] getLocations(SegmentDef.SpriteType type, int opcode) { int[] retVal = EMPTY_INT_ARRAY; - Map map = colorMap.get(type); + Map, Integer> map = colorMap.get(type); if (map != null) { retVal = new int[map.size()]; int i = 0; - for (final Integer v : map.keySet()) { - retVal[i] = v.intValue(); - i++; + for (final Couple pair : map.keySet()) { + if (pair.getValue0().intValue() == opcode) { + retVal[i] = pair.getValue1().intValue(); + i++; + } + } + if (i < retVal.length) { + retVal = Arrays.copyOf(retVal, i); } } return retVal; @@ -60,11 +77,11 @@ public int[] getLocations(SegmentDef.SpriteType type) * Returns the color value for the specified sprite overlay type and location index. * Returns -1 if value is not available. */ - public int getValue(SegmentDef.SpriteType type, int index) + public int getValue(SegmentDef.SpriteType type, int opcode, int index) { - Map map = colorMap.get(type); + Map, Integer> map = colorMap.get(type); if (map != null) { - Integer v = map.get(index); + Integer v = map.get(Couple.with(opcode, index)); if (v != null) { return v.intValue(); } @@ -76,20 +93,39 @@ public int getValue(SegmentDef.SpriteType type, int index) * Adds a color entry and associates it with a sprite overlay type and color location. * Existing color entries will be updated. * @param type the sprite overlay type. - * @param locationIndex the color location. - * @param colorIndex the color index. + * @param opcode the effect opcode. + * @param location the color location. + * @param value the unprocessed color value. */ - public void add(SegmentDef.SpriteType type, int locationIndex, int colorIndex) + public void add(SegmentDef.SpriteType type, int opcode, int location, int value) { if (type == null) { return; } - if (locationIndex >= -1 && locationIndex < 7) { - Map map = colorMap.get(type); + if (location >= -1 && location < 7) { + Map, Integer> map = colorMap.get(type); if (map == null) { - map = new HashMap(); + map = new HashMap<>(); } - map.put(locationIndex, colorIndex); + + // swapping byte order of color value + switch (opcode) { + case OPCODE_SET_COLOR_GLOW: + case OPCODE_TINT_SOLID: + case OPCODE_TINT_BRIGHT: + { + int tmp = 0; + for (int i = 0; i < 4; i++) { + tmp <<= 8; + tmp |= value & 0xff; + value >>= 8; + } + value = tmp; + break; + } + } + + map.put(Couple.with(opcode, location), value); colorMap.put(type, map); } } diff --git a/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java b/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java index 43f664f84..6dc344854 100644 --- a/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java +++ b/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java @@ -422,8 +422,11 @@ public int getColorCount(SegmentDef.SpriteType type) if (itemInfo != null) { HashSet set = new HashSet<>(); - for (Iterator iter = itemInfo.getColorInfo().getLocationIterator(type); iter.hasNext(); ) { - set.add(iter.next()); + for (Iterator> iter = itemInfo.getColorInfo().getEffectIterator(type); iter.hasNext(); ) { + Couple pair = iter.next(); + if (pair.getValue0().intValue() == ColorInfo.OPCODE_SET_COLOR) { + set.add(pair.getValue1()); + } } retVal = set.size(); } @@ -484,7 +487,7 @@ public int getColorValue(SegmentDef.SpriteType type, int locationIndex) } if (itemInfo != null) { - retVal = itemInfo.getColorInfo().getValue(type, locationIndex); + retVal = itemInfo.getColorInfo().getValue(type, ColorInfo.OPCODE_SET_COLOR, locationIndex); } return retVal; @@ -493,7 +496,8 @@ public int getColorValue(SegmentDef.SpriteType type, int locationIndex) /** * Returns the color entry of the specified location index for the avatar sprite * after applying all equipment and effect colors as well as the source of the color value. - * @param locationIndex the color location index. Available range: [0, 6] + * @param opcode filter by this opcode. + * @param locationIndex the color location index. Available range: [-1, 6] * @return a tuple with the color entry as well as a {@code Boolean} value indicating whether random colors are allowed. * Returns {@code -1} for the color entry if value could not be determined. */ @@ -506,7 +510,7 @@ public Couple getEffectiveColorValue(int locationIndex) * Returns the color entry of the location index for the specified sprite overlay type * after applying all equipment and effect colors as well as the source of the color value. * @param type the {@link SegmentDef.SpriteType SpriteType} target - * @param locationIndex the color location index. Available range: [0, 6] + * @param locationIndex the color location index. Available range: [-1, 6] * @return a tuple with the color entry as well as a {@code Boolean} value indicating whether random colors are allowed. * Returns {@code -1} for the color entry if value could not be determined. */ @@ -528,7 +532,7 @@ public Couple getEffectiveColorValue(SegmentDef.SpriteType typ // checking equipped items ItemInfo[] itemInfos = getEffectiveItemInfo(); for (final ItemInfo info : itemInfos) { - int v = info.getColorInfo().getValue(type, locationIndex); + int v = info.getColorInfo().getValue(type, ColorInfo.OPCODE_SET_COLOR, locationIndex); if (v >= 0) { retVal.setValue0(v); retVal.setValue1(Boolean.FALSE); @@ -536,7 +540,7 @@ public Couple getEffectiveColorValue(SegmentDef.SpriteType typ } // checking creature effects - int v = getColorInfo().getValue(type, locationIndex); + int v = getColorInfo().getValue(type, ColorInfo.OPCODE_SET_COLOR, locationIndex); if (v >= 0) { retVal.setValue0(v); retVal.setValue1(Boolean.FALSE); @@ -545,6 +549,77 @@ public Couple getEffectiveColorValue(SegmentDef.SpriteType typ return retVal; } + /** + * Returns the color value of the specified location index for the specified sprite overlay type. + * @param type the sprite overlay type. + * @param opcode the opcode to filter. + * @param locationIndex the color location index. + * @return color entry index. Returns -1 if color value is not available. + */ + public int getTintValue(SegmentDef.SpriteType type, int opcode, int locationIndex) + { + if (type == null) { + type = SegmentDef.SpriteType.AVATAR; + } + + return getColorInfo().getValue(type, opcode, locationIndex); + } + + /** + * Returns the color tint value of the location index for the specified sprite overlay type + * after applying all equipment and effect colors as well as the source of the color value. + * @param type the {@link SegmentDef.SpriteType SpriteType} target + * @param locationIndex the color location index. Available range: [-1, 6] + * @return a tuple with the effect opcode as well as the RGB color value. + * Returns {@code -1} for each of the tuple elements if value could not be determined. + */ + public Couple getEffectiveTintValue(SegmentDef.SpriteType type, int locationIndex) + { + Couple retVal = Couple.with(-1, -1); + + if (type == null) { + type = SegmentDef.SpriteType.AVATAR; + } + + int opcode, value; + + // checking equipped items + ItemInfo[] itemInfos = getEffectiveItemInfo(); + for (final ItemInfo info : itemInfos) { + opcode = ColorInfo.OPCODE_TINT_BRIGHT; + value = info.getColorInfo().getValue(type, opcode, locationIndex); + if (value == -1) { + opcode = ColorInfo.OPCODE_TINT_SOLID; + value = info.getColorInfo().getValue(type, opcode, locationIndex); + if (value == -1) { + opcode = ColorInfo.OPCODE_SET_COLOR_GLOW; + value = info.getColorInfo().getValue(type, opcode, locationIndex); + } + } + if (value != -1) { + retVal.setValue0(opcode); + retVal.setValue1(value); + } + } + + // checking creature effects + opcode = ColorInfo.OPCODE_TINT_BRIGHT; + value = getColorInfo().getValue(type, opcode, locationIndex); + if (value == -1) { + opcode = ColorInfo.OPCODE_TINT_SOLID; + value = getColorInfo().getValue(type, opcode, locationIndex); + if (value == -1) { + opcode = ColorInfo.OPCODE_SET_COLOR_GLOW; + value = getColorInfo().getValue(type, opcode, locationIndex); + } + } + if (value != -1) { + retVal.setValue0(opcode); + retVal.setValue1(value); + } + + return retVal; + } /** Returns the creature resource version. */ private String getCreatureVersion() @@ -701,11 +776,14 @@ private void initEffect(AbstractStruct as) int opcode = ((IsNumeric)se).getValue(); switch (opcode) { - case 7: // Set color + case ColorInfo.OPCODE_SET_COLOR: + case ColorInfo.OPCODE_SET_COLOR_GLOW: + case ColorInfo.OPCODE_TINT_SOLID: + case ColorInfo.OPCODE_TINT_BRIGHT: { - se = as.getAttribute(ofsParam1); + se = as.getAttribute(as.getOffset() + ofsParam1); int param1 = (se instanceof IsNumeric) ? ((IsNumeric)se).getValue() : -1; - se = as.getAttribute(ofsParam2); + se = as.getAttribute(as.getOffset() + ofsParam2); int param2 = (se instanceof IsNumeric) ? ((IsNumeric)se).getValue() : -1; if (param1 != -1 && param2 != -1) { @@ -731,7 +809,8 @@ private void initEffect(AbstractStruct as) location = -1; } } - getColorInfo().add(type, location, param1); + + getColorInfo().add(type, opcode, location, param1); } break; } diff --git a/src/org/infinity/resource/cre/decoder/util/ItemInfo.java b/src/org/infinity/resource/cre/decoder/util/ItemInfo.java index 948e524fc..f4b5cd5d1 100644 --- a/src/org/infinity/resource/cre/decoder/util/ItemInfo.java +++ b/src/org/infinity/resource/cre/decoder/util/ItemInfo.java @@ -788,33 +788,37 @@ private void init() throws IOException, Exception // Processes a global effect: only "set color" effect is considered private void parseEffect(EffectInfo info) { - if (info.getOpcode() == 7) { - // set color - if (info.getTarget() == 1 && info.getTiming() == 2) { - // self target; on equip - SegmentDef.SpriteType type = null; - int location = info.getParameter2() & 0xf; - switch ((info.getParameter2() >> 4) & 0xf) { - case 0: - type = SegmentDef.SpriteType.AVATAR; - break; - case 1: - type = SegmentDef.SpriteType.WEAPON; - break; - case 2: - type = SegmentDef.SpriteType.SHIELD; - break; - case 3: - type = SegmentDef.SpriteType.HELMET; - break; - default: - if ((info.getParameter2() & 0xff) == 0xff) { + switch (info.getOpcode()) { + case ColorInfo.OPCODE_SET_COLOR: + case ColorInfo.OPCODE_SET_COLOR_GLOW: + case ColorInfo.OPCODE_TINT_SOLID: + case ColorInfo.OPCODE_TINT_BRIGHT: + if (info.getTarget() == 1 && info.getTiming() == 2) { + // self target; when equipped + SegmentDef.SpriteType type = null; + int location = info.getParameter2() & 0xf; + switch ((info.getParameter2() >> 4) & 0xf) { + case 0: type = SegmentDef.SpriteType.AVATAR; - location = -1; - } + break; + case 1: + type = SegmentDef.SpriteType.WEAPON; + break; + case 2: + type = SegmentDef.SpriteType.SHIELD; + break; + case 3: + type = SegmentDef.SpriteType.HELMET; + break; + default: + if ((info.getParameter2() & 0xff) == 0xff) { + type = SegmentDef.SpriteType.AVATAR; + location = -1; + } + } + getColorInfo().add(type, info.getOpcode(), location, info.getParameter1()); } - getColorInfo().add(type, location, info.getParameter1()); - } + break; } } @@ -1009,8 +1013,8 @@ private EffectInfo(byte[] effect) this.opcode = buf.getShort(0x0); this.target = buf.getByte(0x2); this.power = buf.getByte(0x3); - this.parameter1 = buf.getShort(0x4); - this.parameter2 = buf.getShort(0x8); + this.parameter1 = buf.getInt(0x4); + this.parameter2 = buf.getInt(0x8); this.timing = buf.getByte(0xc); this.dispelResist = buf.getByte(0xd); this.duration = buf.getByte(0xe); diff --git a/src/org/infinity/resource/cre/decoder/util/SpriteUtils.java b/src/org/infinity/resource/cre/decoder/util/SpriteUtils.java index 325ec7032..388b29680 100644 --- a/src/org/infinity/resource/cre/decoder/util/SpriteUtils.java +++ b/src/org/infinity/resource/cre/decoder/util/SpriteUtils.java @@ -805,6 +805,65 @@ public static void updateRandomPool() } } + /** + * Modifies the specified range of palette entries according to the opcode and color specifications. + * @param palette ARGB palette to modify. + * @param startOfs index of first color entry to modify. + * @param length number of color entries to modify. + * @param opcode effect opcode to apply. Supported opcodes: 8, 51, 52 + * @param color color value associated with the opcode. + * @return the modified palette. + */ + public static int[] tintColors(int[] palette, int startOfs, int length, int opcode, int color) + { + int[] retVal = palette; + + switch (opcode) { + case ColorInfo.OPCODE_SET_COLOR_GLOW: + case ColorInfo.OPCODE_TINT_SOLID: + case ColorInfo.OPCODE_TINT_BRIGHT: + break; + default: + return retVal; + } + + if (palette != null) { + retVal = Arrays.copyOf(palette, palette.length); + startOfs = Math.max(0, Math.min(retVal.length - 1, startOfs)); + length = Math.max(0, Math.min(retVal.length - startOfs, length)); + + int dr = (color >> 16) & 0xff; + int dg = (color >> 8) & 0xff; + int db = color & 0xff; + for (int i = 0; i < length; i++) { + int sr = (retVal[startOfs + i] >> 16) & 0xff; + int sg = (retVal[startOfs + i] >> 8) & 0xff; + int sb = retVal[startOfs + i] & 0xff; + switch (opcode) { + case ColorInfo.OPCODE_SET_COLOR_GLOW: + sr = Math.min(255, sr + dr - (sr >>> 2)); + sg = Math.min(255, sg + dg - (sg >>> 2)); + sb = Math.min(255, sb + db - (sb >>> 2)); + break; + case ColorInfo.OPCODE_TINT_SOLID: + sr = Math.min(255, dr * sr / 255); + sg = Math.min(255, dg * sg / 255); + sb = Math.min(255, db * sb / 255); + break; + case ColorInfo.OPCODE_TINT_BRIGHT: + sr = Math.min(255, sr + (dr * (sr >>> 3))); + sg = Math.min(255, sg + (dg * (sg >>> 3))); + sb = Math.min(255, sb + (db * (sb >>> 3))); + break; + } + retVal[startOfs + i] = (retVal[startOfs + i] & 0xff000000) | (sr << 16) | (sg << 8) | sb; + } + } + + return retVal; + } + + /** * Calculates a dimension that can contain all the specified source frames. * @param frames one or more source frames. diff --git a/src/org/infinity/resource/cre/viewer/MediaPanel.java b/src/org/infinity/resource/cre/viewer/MediaPanel.java index 13f316998..b5a0ed08c 100644 --- a/src/org/infinity/resource/cre/viewer/MediaPanel.java +++ b/src/org/infinity/resource/cre/viewer/MediaPanel.java @@ -37,7 +37,6 @@ import org.infinity.resource.cre.decoder.SpriteDecoder; import org.infinity.resource.cre.decoder.util.Direction; import org.infinity.resource.cre.decoder.util.Sequence; -import org.infinity.resource.cre.decoder.util.SpriteUtils; import org.infinity.resource.cre.viewer.icon.Icons; import org.infinity.resource.graphics.PseudoBamDecoder.PseudoBamControl; @@ -104,10 +103,10 @@ public void reset(boolean preserveState) return; } - SpriteUtils.clearBamCache(); SettingsPanel settings = getViewer().getSettingsPanel(); decoder.setAutoApplyChanges(false); decoder.setPaletteReplacementEnabled(settings.isPaletteReplacementEnabled()); + decoder.setTintEnabled(settings.isTintEnabled()); decoder.setTranslucencyEnabled(settings.isTranslucencyEnabled()); decoder.setSelectionCircleEnabled(settings.isSelectionCircleEnabled()); decoder.setSelectionCircleBitmap(settings.isOrnateSelectionCircle()); diff --git a/src/org/infinity/resource/cre/viewer/SettingsPanel.java b/src/org/infinity/resource/cre/viewer/SettingsPanel.java index 32fb58dbb..eff1f1294 100644 --- a/src/org/infinity/resource/cre/viewer/SettingsPanel.java +++ b/src/org/infinity/resource/cre/viewer/SettingsPanel.java @@ -73,7 +73,7 @@ public class SettingsPanel extends JPanel private static int indexZoom, indexFrameRate, indexBackground; private static boolean isFiltering, isBlending, isTranslucent, isSelectionCircle, isOrnateSelectionCircle, isPersonalSpace, - isPaletteReplacementEnabled, isShowAvatar, isShowHelmet, isShowShield, isShowWeapon, + isTintEnabled, isPaletteReplacementEnabled, isShowAvatar, isShowHelmet, isShowShield, isShowWeapon, isShowBorders; static { @@ -91,6 +91,7 @@ public class SettingsPanel extends JPanel isShowShield = true; isShowWeapon = true; isShowBorders = false; + isTintEnabled = true; isPaletteReplacementEnabled = true; } @@ -102,7 +103,8 @@ public class SettingsPanel extends JPanel private JComboBox cbBackground; private JButton bCenter; private JCheckBox cbFiltering, cbBlending, cbTranslucent, cbSelectionCircle, cbOrnateSelectionCircle, cbPersonalSpace, - cbPaletteReplacementEnabled, cbShowAvatar, cbShowHelmet, cbShowShield, cbShowWeapon, cbShowBorders; + cbTintEnabled, cbPaletteReplacementEnabled, cbShowAvatar, cbShowHelmet, cbShowShield, cbShowWeapon, + cbShowBorders; private AttributesPanel panelAttributes; /** Returns a list of background info instances available for the specified game. */ @@ -279,6 +281,18 @@ private void setPersonalSpaceEnabled(boolean b) } } + /** Returns whether tint effects (e.g. from opcodes 51/52) are enabled. */ + public boolean isTintEnabled() { return isTintEnabled; } + + private void setTintEnabled(boolean b) + { + if (isTintEnabled != b) { + isTintEnabled = b; + cbTintEnabled.setSelected(isTintEnabled); + getViewer().getMediaPanel().reset(true); + } + } + /** Returns whether palette replacement (full palette or false colors) is enabled. */ public boolean isPaletteReplacementEnabled() { return isPaletteReplacementEnabled; } @@ -424,13 +438,17 @@ private void init() cbFiltering.addActionListener(listeners); cbBlending = new JCheckBox("Enable blending", isBlending); - cbBlending.setToolTipText("Affects only creature animations with special blending attributes (e.g. movanic devas or wisps)"); + cbBlending.setToolTipText("Affects only creature animations with special blending attributes (e.g. movanic devas or wisps)."); cbBlending.addActionListener(listeners); cbTranslucent = new JCheckBox("Enable translucency", isTranslucent); - cbTranslucent.setToolTipText("Affects only creature animations with translucency effect (e.g. ghosts or air elementals)"); + cbTranslucent.setToolTipText("Affects only creature animations with translucency effect (e.g. ghosts or air elementals)."); cbTranslucent.addActionListener(listeners); + cbTintEnabled = new JCheckBox("Enable tint", isTintEnabled); + cbTintEnabled.setToolTipText("Includes color modifications defined by effect opcodes 8, 51 and 52."); + cbTintEnabled.addActionListener(listeners); + cbPaletteReplacementEnabled = new JCheckBox("Enable palette replacement", isPaletteReplacementEnabled); cbPaletteReplacementEnabled.setToolTipText("Enable full palette or false color palette replacement."); cbPaletteReplacementEnabled.addActionListener(listeners); @@ -502,11 +520,18 @@ private void init() c = ViewerUtil.setGBC(c, 0, 5, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 0, 0, 0), 0, 0); - panel2.add(cbPaletteReplacementEnabled, c); + panel2.add(cbTintEnabled, c); c = ViewerUtil.setGBC(c, 1, 5, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 16, 0, 0), 0, 0); panel2.add(cbShowBorders, c); + c = ViewerUtil.setGBC(c, 0, 6, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 0, 0, 0), 0, 0); + panel2.add(cbPaletteReplacementEnabled, c); + c = ViewerUtil.setGBC(c, 1, 6, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(8, 16, 0, 0), 0, 0); + panel2.add(new JPanel(), c); + // attributes table panel panelAttributes = new AttributesPanel(getViewer()); @@ -567,6 +592,9 @@ else if (e.getSource() == cbBlending) { else if (e.getSource() == cbTranslucent) { setTranslucencyEnabled(cbTranslucent.isSelected()); } + else if (e.getSource() == cbTintEnabled) { + setTintEnabled(cbTintEnabled.isSelected()); + } else if (e.getSource() == cbPaletteReplacementEnabled) { setPaletteReplacementEnabled(cbPaletteReplacementEnabled.isSelected()); } From 8b10b386241091c3e78b3de94a0f8aac8e25667d Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Wed, 7 Apr 2021 21:33:02 +0200 Subject: [PATCH 107/158] SpriteDecoder: Add support for special coloring (stoneskin, frozen, ...) - Supported states: frozen death, stone death, flame death - Supported effects: stoneskin, petrification - Added support to area viewer --- .../resource/are/viewer/LayerObjectActor.java | 28 ++++++++- .../resource/cre/decoder/SpriteDecoder.java | 56 ++++++++++++++++++ .../resource/cre/decoder/util/ColorInfo.java | 4 ++ .../cre/decoder/util/CreatureInfo.java | 58 +++++++++++++++++++ .../resource/cre/decoder/util/ItemInfo.java | 4 ++ 5 files changed, 147 insertions(+), 3 deletions(-) diff --git a/src/org/infinity/resource/are/viewer/LayerObjectActor.java b/src/org/infinity/resource/are/viewer/LayerObjectActor.java index c825f7d66..8321dacf6 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectActor.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectActor.java @@ -55,6 +55,21 @@ protected enum Allegiance { Sequence.PST_DIE_BACKWARD, Sequence.PST_DIE_COLLAPSE, }; + // Potential sequences for "frozen death" state + private static final Sequence[] FROZEN_DEATH_SEQUENCE = { + Sequence.STANCE, + Sequence.STANCE2, + Sequence.PST_STANCE, + Sequence.STAND, + Sequence.STAND2, + Sequence.STAND3, + Sequence.STAND_EMERGED, + Sequence.TWITCH, + Sequence.DIE, + Sequence.PST_DIE_FORWARD, + Sequence.PST_DIE_BACKWARD, + Sequence.PST_DIE_COLLAPSE, + }; // Potential sequences for "unconscious" state private static final Sequence[] SLEEP_SEQUENCE = { Sequence.SLEEP, @@ -174,11 +189,16 @@ protected static ActorAnimationProvider createAnimationProvider(CreResource cre) ActorAnimationProvider retVal = null; SpriteDecoder decoder = null; - final int maskDeath = 0xfc0; // actor is dead? - final int maskSleep = 0x1; // actor is unconscious? + final int maskDeath = 0xf00; // actor uses regular death sequence? + final int maskFrozenDeath = 0x40; // actor status is "frozen death"? + final int maskStoneDeath = 0x80; // actor status is "stone death"? + final int maskSleep = 0x01; // actor uses sleep sequence? int status = ((IsNumeric)cre.getAttribute(CreResource.CRE_STATUS)).getValue(); boolean isDead = (status & maskDeath) != 0; + boolean isFrozenDeath = (status & maskFrozenDeath) != 0; + boolean isStoneDeath = (status & maskStoneDeath) != 0; boolean isUnconscious = (status & maskSleep) != 0; + boolean isStillFrame = isDead || isFrozenDeath || isStoneDeath || isUnconscious; // loading SpriteDecoder instance from cache if available String key = createKey(cre); @@ -193,6 +213,8 @@ protected static ActorAnimationProvider createAnimationProvider(CreResource cre) // check for special animation sequence if (isDead) { sequence = getMatchingSequence(decoder, DEATH_SEQUENCE); + } else if (isFrozenDeath) { + sequence = getMatchingSequence(decoder, FROZEN_DEATH_SEQUENCE); } else if (isUnconscious) { sequence = getMatchingSequence(decoder, SLEEP_SEQUENCE); } @@ -243,7 +265,7 @@ protected static ActorAnimationProvider createAnimationProvider(CreResource cre) retVal = new ActorAnimationProvider(decoder); retVal.setActive(true); - if (isDead || isUnconscious) { + if (isStillFrame) { // using second last frame to avoid glitches for selected creature animations retVal.setStartFrame(-2); retVal.setFrameCap(-2); diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index 898d049b3..1ab075873 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -121,6 +121,10 @@ public void accept(BamV1Control control, SegmentDef sd) applyColorTint(control, sd); } + if (isPaletteReplacementEnabled() && isFalseColor()) { + applyColorEffects(control, sd); + } + if (isTranslucencyEnabled() && isTranslucent()) { applyTranslucency(control); } @@ -1535,6 +1539,58 @@ protected void applyFalseColors(BamV1Control control, SegmentDef sd) control.setExternalPalette(palette); } + /** + * Replaces false colors with special color effects applied to the CRE resource. + * Currently supported: stoneskin/petrification, frozen state, burned state. + * @param control the BAM controller. + */ + protected void applyColorEffects(BamV1Control control, SegmentDef sd) + { + if (control == null || sd == null || + getAnimationType() == AnimationInfo.Type.MONSTER_PLANESCAPE) { + return; + } + + boolean isStoneEffect = getCreatureInfo().isStoneEffect(); + boolean isFrozenEffect = getCreatureInfo().isFrozenEffect(); + boolean isBurnedEffect = getCreatureInfo().isBurnedEffect(); + + if (isStoneEffect || isFrozenEffect) { + // isStoneEffect: includes stoneskin effect, petrification/stone death status + // isFrozenEffect: includes frozen death status + int colorIdx = isStoneEffect ? 72 : 71; + int[] range = getColorData(colorIdx, false); + int[] palette = control.getCurrentPalette(); + + // replacing base ranges + for (int i = 0; i < 7; i++) { + int ofs = 4 + (i * range.length); + palette = SpriteUtils.replaceColors(palette, range, ofs, range.length, false); + } + + // calculating mixed ranges + int k = 0; + for (int i = 0; i < 6; i++) { + int ofs1 = 4 + (i * 12); + for (int j = i + 1; j < 7; j++, k++) { + int ofs2 = 4 + (j * 12); + int ofs3 = 88 + (k * 8); + palette = SpriteUtils.interpolateColors(palette, ofs1, ofs2, 12, ofs3, 8, false); + } + } + + control.setExternalPalette(palette); + } else if (isBurnedEffect) { + // isBurnedEffect: includes flame death status + int opcode = 51; + int color = 0x4b4b4b; + int[] palette = control.getCurrentPalette(); + palette = SpriteUtils.tintColors(palette, 2, 254, opcode, color); + control.setExternalPalette(palette); + } + + } + /** * Modifies BAM palette with tint colors from selected effect opcodes. * @param control the BAM controller. diff --git a/src/org/infinity/resource/cre/decoder/util/ColorInfo.java b/src/org/infinity/resource/cre/decoder/util/ColorInfo.java index df1fd5061..0fa32d188 100644 --- a/src/org/infinity/resource/cre/decoder/util/ColorInfo.java +++ b/src/org/infinity/resource/cre/decoder/util/ColorInfo.java @@ -26,6 +26,10 @@ public class ColorInfo public static final int OPCODE_TINT_SOLID = 51; /** Effect opcode 52: Character tint bright */ public static final int OPCODE_TINT_BRIGHT = 52; + /** Effect opcode 134: Petrification */ + public static final int OPCODE_PETRIFICATION = 134; + /** Effect opcode 218: Stoneskin */ + public static final int OPCODE_STONESKIN = 218; private static final int[] EMPTY_INT_ARRAY = new int[0]; diff --git a/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java b/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java index 6dc344854..706adb2d2 100644 --- a/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java +++ b/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java @@ -102,6 +102,28 @@ public boolean isStatusPanic() return ((getStatus() & (1 << 2)) != 0); } + /** + * Returns {@code true} if the creature is under the stoneskin effect or status is set to petrification/stone death. + */ + public boolean isStoneEffect() + { + return ((getStatus() & (1 << 7)) != 0) || + isEffectActive(SegmentDef.SpriteType.AVATAR, ColorInfo.OPCODE_PETRIFICATION, -1) || + isEffectActive(SegmentDef.SpriteType.AVATAR, ColorInfo.OPCODE_STONESKIN, -1); + } + + /** Returns {@code true} if the creature status is set to "frozen death". */ + public boolean isFrozenEffect() + { + return ((getStatus() & (1 << 6)) != 0); + } + + /** Returns {@code true} if the creature status is set to "flame death". */ + public boolean isBurnedEffect() + { + return ((getStatus() & (1 << 9)) != 0); + } + /** Returns the creature animation id. */ public int getAnimationId() { return ((IsNumeric)cre.getAttribute(CreResource.CRE_ANIMATION)).getValue(); } @@ -621,6 +643,38 @@ public Couple getEffectiveTintValue(SegmentDef.SpriteType type return retVal; } + /** + * Returns whether the specified effect is active with the given parameters. + * @param type the {@link SegmentDef.SpriteType SpriteType} target + * @param opcode the effect opcode to filter. + * @param location the color location index. Available range: [-1, 6] + * @return {@code true} if the effect with matching parameters exists. Returns {@code false} otherwise. + */ + public boolean isEffectActive(SegmentDef.SpriteType type, int opcode, int location) + { + boolean retVal = false; + + if (type == null) { + type = SegmentDef.SpriteType.AVATAR; + } + + // checking creature effects + retVal = (getColorInfo().getValue(type, opcode, location) >= 0); + + if (!retVal) { + // checking equipped items + ItemInfo[] itemInfos = getEffectiveItemInfo(); + for (final ItemInfo info : itemInfos) { + retVal = (info.getColorInfo().getValue(type, opcode, location) >= 0); + if (retVal) { + break; + } + } + } + + return retVal; + } + /** Returns the creature resource version. */ private String getCreatureVersion() { @@ -814,6 +868,10 @@ private void initEffect(AbstractStruct as) } break; } + case ColorInfo.OPCODE_PETRIFICATION: + case ColorInfo.OPCODE_STONESKIN: + getColorInfo().add(SegmentDef.SpriteType.AVATAR, opcode, -1, 0); + break; } } diff --git a/src/org/infinity/resource/cre/decoder/util/ItemInfo.java b/src/org/infinity/resource/cre/decoder/util/ItemInfo.java index f4b5cd5d1..903912609 100644 --- a/src/org/infinity/resource/cre/decoder/util/ItemInfo.java +++ b/src/org/infinity/resource/cre/decoder/util/ItemInfo.java @@ -819,6 +819,10 @@ private void parseEffect(EffectInfo info) getColorInfo().add(type, info.getOpcode(), location, info.getParameter1()); } break; + case ColorInfo.OPCODE_PETRIFICATION: + case ColorInfo.OPCODE_STONESKIN: + getColorInfo().add(SegmentDef.SpriteType.AVATAR, info.getOpcode(), -1, 0); + break; } } From 937ac89dca1a8fa8c665cad55af02862b7b109bb Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 9 Apr 2021 11:41:58 +0200 Subject: [PATCH 108/158] Area Viewer: Show global actors on the map (e.g. party members, NPCs) - Add Profile property: name of GAM file used in saved games - GAM file is included when unpacking SAV file content to temp. folder - Add new functionality to ChildFrame class - Reuse top-level Viewables when opening substructures via mouse click in Area Viewer --- src/org/infinity/gui/ChildFrame.java | 30 ++++ src/org/infinity/resource/Profile.java | 17 ++ .../resource/are/viewer/AreaViewer.java | 37 +++- .../resource/are/viewer/LayerActor.java | 54 +++++- .../are/viewer/LayerObjectAreActor.java | 2 +- .../are/viewer/LayerObjectGlobalActor.java | 160 ++++++++++++++++++ .../are/viewer/LayerObjectIniActor.java | 3 +- .../resource/are/viewer/icon/ViewerIcons.java | 6 + .../are/viewer/icon/itm_GamActorB1.png | Bin 0 -> 3011 bytes .../are/viewer/icon/itm_GamActorB2.png | Bin 0 -> 3024 bytes .../are/viewer/icon/itm_GamActorG1.png | Bin 0 -> 2975 bytes .../are/viewer/icon/itm_GamActorG2.png | Bin 0 -> 2959 bytes .../are/viewer/icon/itm_GamActorR1.png | Bin 0 -> 2974 bytes .../are/viewer/icon/itm_GamActorR2.png | Bin 0 -> 2972 bytes src/org/infinity/resource/sav/IOHandler.java | 10 ++ 15 files changed, 313 insertions(+), 6 deletions(-) create mode 100644 src/org/infinity/resource/are/viewer/LayerObjectGlobalActor.java create mode 100644 src/org/infinity/resource/are/viewer/icon/itm_GamActorB1.png create mode 100644 src/org/infinity/resource/are/viewer/icon/itm_GamActorB2.png create mode 100644 src/org/infinity/resource/are/viewer/icon/itm_GamActorG1.png create mode 100644 src/org/infinity/resource/are/viewer/icon/itm_GamActorG2.png create mode 100644 src/org/infinity/resource/are/viewer/icon/itm_GamActorR1.png create mode 100644 src/org/infinity/resource/are/viewer/icon/itm_GamActorR2.png diff --git a/src/org/infinity/gui/ChildFrame.java b/src/org/infinity/gui/ChildFrame.java index 52819f3da..5af37dcd6 100644 --- a/src/org/infinity/gui/ChildFrame.java +++ b/src/org/infinity/gui/ChildFrame.java @@ -12,6 +12,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.function.Predicate; import java.util.function.Supplier; import javax.swing.AbstractAction; @@ -96,6 +97,35 @@ public static T getFirstFrame(Class frameClass) return null; } + /** + * Returns an iterator over the registered {@code ChildFrame} instances that are compatible with the + * specified class type. + * @param Class of the filtered instances + * @param frameClass Runtime class of the windows to filter + * @return An iterator over matching instances. Returns {@code null} if the parameter is {@code null}. + */ + public static Iterator getFrameIterator(Class frameClass) + { + if (frameClass != null) { + return windows.stream().filter(frameClass::isInstance).map(frameClass::cast).iterator(); + } + return null; + } + + /** + * Returns an iterator over the registered {@code ChildFrame} instances that are matching the given predicate. + * @param pred Predicate used to filter registered windows + * @return An iterator over matching {@code ChildFrame} instances + */ + public static Iterator getFrameIterator(Predicate pred) + { + if (pred != null) { + return windows.stream().filter(pred).iterator(); + } else { + return windows.iterator(); + } + } + /** * Shows first window of specified class. If window do not yet exists, create * it with {@code init} function and then shows. diff --git a/src/org/infinity/resource/Profile.java b/src/org/infinity/resource/Profile.java index b3d76abbb..acfe9053c 100644 --- a/src/org/infinity/resource/Profile.java +++ b/src/org/infinity/resource/Profile.java @@ -424,6 +424,8 @@ public enum Key { IS_SUPPORTED_KITS, /** Property: ({@code String}) The name of the ALIGNMENT IDS resource. */ GET_IDS_ALIGNMENT, + /** Property: ({@code String}) The name of the .GAM file that is stored in saved games. */ + GET_GAM_NAME, /** Property: ({@code Boolean}) Indices whether overlays in tilesets are stenciled. */ IS_TILESET_STENCILED, } @@ -2283,6 +2285,21 @@ private void initFeatures() // the actual name of the "Alignment" IDS resource addEntry(Key.GET_IDS_ALIGNMENT, Type.STRING, (engine == Engine.IWD2) ? "ALIGNMNT.IDS" : "ALIGNMEN.IDS"); + // the GAM filename used in saved games + switch (engine) { + case IWD: + addEntry(Key.GET_GAM_NAME, Type.STRING, "ICEWIND.GAM"); + break; + case IWD2: + addEntry(Key.GET_GAM_NAME, Type.STRING, "ICEWIND2.GAM"); + break; + case PST: + addEntry(Key.GET_GAM_NAME, Type.STRING, "TORMENT.GAM"); + break; + default: + addEntry(Key.GET_GAM_NAME, Type.STRING, "BALDUR.GAM"); + } + // display mode of overlays in tilesets addEntry(Key.IS_TILESET_STENCILED, Type.BOOLEAN, (engine == Engine.BG2 || game == Game.BG2EE)); diff --git a/src/org/infinity/resource/are/viewer/AreaViewer.java b/src/org/infinity/resource/are/viewer/AreaViewer.java index 26b76671a..96e51d6f2 100644 --- a/src/org/infinity/resource/are/viewer/AreaViewer.java +++ b/src/org/infinity/resource/are/viewer/AreaViewer.java @@ -36,6 +36,7 @@ import java.util.ArrayList; import java.util.EventObject; import java.util.Hashtable; +import java.util.Iterator; import java.util.List; import java.util.Locale; @@ -1519,7 +1520,41 @@ private Window getViewerWindow(AbstractStruct as) } } // Window not found, creating and returning a new one - return new ViewFrame(NearInfinity.getInstance(), as); + List structChain = new ArrayList<>(); + structChain.add(as); + AbstractStruct as2 = as; + while (as2.getParent() != null) { + structChain.add(0, as2.getParent()); + as2 = as2.getParent(); + } + + if (map.getAre() == structChain.get(0)) { + // no need to create the whole Viewable chain + return new ViewFrame(NearInfinity.getInstance(), as); + } + + // recycling existing Viewables if possible + Window wnd = NearInfinity.getInstance(); + for (final Iterator iter = ChildFrame.getFrameIterator(cf -> cf instanceof ViewFrame && + ((ViewFrame)cf).getViewable() instanceof AbstractStruct); + iter.hasNext(); ) { + ViewFrame vf = (ViewFrame)iter.next(); + AbstractStruct struct = (AbstractStruct)vf.getViewable(); + if (struct.getResourceEntry() != null && + struct.getResourceEntry().equals(structChain.get(0).getResourceEntry())) { + structChain.remove(0); + wnd = vf; + break; + } + } + + // creating Viewable chain + for (int i = 0; i < structChain.size(); i++) { + wnd = new ViewFrame(wnd, structChain.get(i)); + } + if (wnd != null && wnd != NearInfinity.getInstance()) { + return wnd; + } } // Last resort: returning NearInfinity instance return NearInfinity.getInstance(); diff --git a/src/org/infinity/resource/are/viewer/LayerActor.java b/src/org/infinity/resource/are/viewer/LayerActor.java index e2581ff7d..133584f6b 100644 --- a/src/org/infinity/resource/are/viewer/LayerActor.java +++ b/src/org/infinity/resource/are/viewer/LayerActor.java @@ -6,19 +6,30 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.List; import java.util.Locale; import javax.swing.ProgressMonitor; import javax.swing.SwingWorker; +import org.infinity.datatype.IsTextual; import org.infinity.gui.WindowBlocker; import org.infinity.gui.layeritem.AbstractLayerItem; import org.infinity.gui.layeritem.AnimatedLayerItem; import org.infinity.gui.layeritem.IconLayerItem; +import org.infinity.resource.Profile; +import org.infinity.resource.Resource; import org.infinity.resource.ResourceFactory; +import org.infinity.resource.StructEntry; import org.infinity.resource.are.Actor; import org.infinity.resource.are.AreResource; +import org.infinity.resource.gam.GamResource; +import org.infinity.resource.gam.PartyNPC; +import org.infinity.resource.key.FileResourceEntry; +import org.infinity.resource.key.ResourceEntry; + import static org.infinity.resource.are.AreResource.ARE_NUM_ACTORS; import static org.infinity.resource.are.AreResource.ARE_OFFSET_ACTORS; import org.infinity.resource.text.PlainTextResource; @@ -54,7 +65,7 @@ protected void loadLayer() loadLayerItems(ARE_OFFSET_ACTORS, ARE_NUM_ACTORS, Actor.class, a -> new LayerObjectAreActor(parent, a)); - final List list = getLayerObjects(); + final List objectList = getLayerObjects(); // loading actors from associated INI final String iniFile = parent.getResourceEntry().getResourceName().toUpperCase(Locale.ENGLISH).replace(".ARE", ".INI"); IniMap ini = ResourceFactory.resourceExists(iniFile) ? IniMapCache.get(iniFile) : null; @@ -69,7 +80,7 @@ protected void loadLayer() PlainTextResource iniRes = new PlainTextResource(ResourceFactory.getResourceEntry(iniFile)); LayerObjectActor obj = new LayerObjectIniActor(iniRes, section, j); setListeners(obj); - list.add(obj); + objectList.add(obj); } catch (Exception e) { e.printStackTrace(); } @@ -78,6 +89,45 @@ protected void loadLayer() } } + // loading global actors from save's baldur.gam or default .gam + ResourceEntry areEntry = parent.getResourceEntry(); + if (areEntry != null) { + // loading associated GAM resource + ResourceEntry gamEntry = null; + Path arePath = areEntry.getActualPath(); + if (arePath != null) { + Path gamPath = arePath.getParent().resolve((String)Profile.getProperty(Profile.Key.GET_GAM_NAME)); + if (Files.isRegularFile(gamPath)) { + gamEntry = new FileResourceEntry(gamPath, false); + } + } + if (gamEntry == null) { + gamEntry = ResourceFactory.getResourceEntry(Profile.getProperty(Profile.Key.GET_GAM_NAME)); + } + + // scanning global NPCs + if (gamEntry != null) { + Resource res = ResourceFactory.getResource(gamEntry); + if (res instanceof GamResource) { + GamResource gamRes = (GamResource)res; + List npcList = gamRes.getFields(PartyNPC.class); + for (int i = 0, cnt = npcList.size(); i < cnt; i++) { + PartyNPC npc = (PartyNPC)npcList.get(i); + String area = ((IsTextual)npc.getAttribute(PartyNPC.GAM_NPC_CURRENT_AREA)).getText(); + if (areEntry.getResourceRef().equalsIgnoreCase(area)) { + try { + LayerObjectActor loa = new LayerObjectGlobalActor(gamRes, npc); + setListeners(loa); + objectList.add(loa); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + } + } + // sorting entries by vertical position to fix overlapping issues getLayerObjects().sort((c1, c2) -> c2.location.y - c1.location.y); } diff --git a/src/org/infinity/resource/are/viewer/LayerObjectAreActor.java b/src/org/infinity/resource/are/viewer/LayerObjectAreActor.java index 9fee5cf2a..173904e57 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectAreActor.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectAreActor.java @@ -30,7 +30,7 @@ */ public class LayerObjectAreActor extends LayerObjectActor { - private static EnumMap ICONS = new EnumMap(Allegiance.class) {{ + private static final EnumMap ICONS = new EnumMap(Allegiance.class) {{ put(Allegiance.GOOD, new Image[] {Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_ARE_ACTOR_G_1), Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_ARE_ACTOR_G_2)}); put(Allegiance.NEUTRAL, new Image[] {Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_ARE_ACTOR_B_1), diff --git a/src/org/infinity/resource/are/viewer/LayerObjectGlobalActor.java b/src/org/infinity/resource/are/viewer/LayerObjectGlobalActor.java new file mode 100644 index 000000000..082a0d869 --- /dev/null +++ b/src/org/infinity/resource/are/viewer/LayerObjectGlobalActor.java @@ -0,0 +1,160 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.are.viewer; + +import java.awt.Image; +import java.awt.Point; +import java.util.EnumMap; +import java.util.Objects; + +import org.infinity.datatype.IsNumeric; +import org.infinity.datatype.IsTextual; +import org.infinity.gui.layeritem.AbstractLayerItem; +import org.infinity.gui.layeritem.AnimatedLayerItem; +import org.infinity.gui.layeritem.IconLayerItem; +import org.infinity.icon.Icons; +import org.infinity.resource.Resource; +import org.infinity.resource.ResourceFactory; +import org.infinity.resource.StructEntry; +import org.infinity.resource.Viewable; +import org.infinity.resource.are.viewer.icon.ViewerIcons; +import org.infinity.resource.cre.CreResource; +import org.infinity.resource.gam.GamResource; +import org.infinity.resource.gam.PartyNPC; +import org.infinity.resource.key.ResourceEntry; + +/** + * Handles specific layer type: global GAM/Actor + */ +public class LayerObjectGlobalActor extends LayerObjectActor +{ + private static final EnumMap ICONS = new EnumMap(Allegiance.class) {{ + put(Allegiance.GOOD, new Image[] {Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_GAM_ACTOR_G_1), + Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_GAM_ACTOR_G_2)}); + put(Allegiance.NEUTRAL, new Image[] {Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_GAM_ACTOR_B_1), + Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_GAM_ACTOR_B_2)}); + put(Allegiance.ENEMY, new Image[] {Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_GAM_ACTOR_R_1), + Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_GAM_ACTOR_R_2)}); + }}; + private static final Point CENTER = new Point(12, 40); + + private final PartyNPC npc; + + private CreResource cre; + + public LayerObjectGlobalActor(GamResource parent, PartyNPC npc) + { + super(PartyNPC.class, parent); + this.npc = Objects.requireNonNull(npc); + + int creOfs = ((IsNumeric)this.npc.getAttribute(PartyNPC.GAM_NPC_OFFSET_CRE)).getValue(); + int creSize = ((IsNumeric)this.npc.getAttribute(PartyNPC.GAM_NPC_CRE_SIZE)).getValue(); + if (creOfs > 0 && creSize > 0) { + // attached resource? + StructEntry se = this.npc.getAttribute(PartyNPC.GAM_NPC_CRE_RESOURCE); + if (se instanceof CreResource) { + this.cre = (CreResource)se; + } + } else { + // external resource? + String creRes = ((IsTextual)this.npc.getAttribute(PartyNPC.GAM_NPC_NAME)).getText(); + ResourceEntry entry = ResourceFactory.getResourceEntry(creRes + ".CRE"); + if (entry != null) { + Resource res = ResourceFactory.getResource(entry); + if (res instanceof CreResource) { + this.cre = (CreResource)res; + } + } + } + + if (this.cre == null) { + throw new NullPointerException("Could not determine CRE resource: " + this.npc.getName()); + } + + location.x = ((IsNumeric)this.npc.getAttribute(PartyNPC.GAM_NPC_LOCATION_X)).getValue(); + location.y = ((IsNumeric)this.npc.getAttribute(PartyNPC.GAM_NPC_LOCATION_Y)).getValue(); + + int ea = ((IsNumeric)this.cre.getAttribute(CreResource.CRE_ALLEGIANCE)).getValue(); + Image[] icons = ICONS.get(getAllegiance(ea)); + icons = getIcons(icons); + + String tooltip = getTooltip(); + IconLayerItem item1 = new IconLayerItem(npc, tooltip, icons[0], CENTER); + item1.setLabelEnabled(Settings.ShowLabelActorsAre); + item1.setName(getCategory()); + item1.setToolTipText(tooltip); + item1.setImage(AbstractLayerItem.ItemState.HIGHLIGHTED, icons[1]); + item1.setVisible(isVisible()); + items[0] = item1; + + // payload is initialized on demand + AnimatedLayerItem item2 = new AnimatedLayerItem(npc, tooltip, AbstractAnimationProvider.DEFAULT_ANIMATION_PROVIDER); + item2.setName(getCategory()); + item2.setToolTipText(tooltip); + item2.setVisible(false); + item2.setFrameRate(Settings.getDefaultFrameRateAnimations()); + item2.setAutoPlay(false); + item2.setFrameColor(AbstractLayerItem.ItemState.NORMAL, COLOR_FRAME_NORMAL); + item2.setFrameWidth(AbstractLayerItem.ItemState.NORMAL, 2); + item2.setFrameEnabled(AbstractLayerItem.ItemState.NORMAL, false); + item2.setFrameColor(AbstractLayerItem.ItemState.HIGHLIGHTED, COLOR_FRAME_HIGHLIGHTED); + item2.setFrameWidth(AbstractLayerItem.ItemState.HIGHLIGHTED, 2); + item2.setFrameEnabled(AbstractLayerItem.ItemState.HIGHLIGHTED, true); + items[1] = item2; + } + + @Override + public void loadAnimation() + { + if (items[1] instanceof AnimatedLayerItem) { + AnimatedLayerItem item = (AnimatedLayerItem)items[1]; + if (item.getAnimation() == AbstractAnimationProvider.DEFAULT_ANIMATION_PROVIDER) { + if (cre != null) { + try { + int orientation = ((IsNumeric)npc.getAttribute(PartyNPC.GAM_NPC_ORIENTATION)).getValue(); + ActorAnimationProvider sprite = createAnimationProvider(cre); + sprite.setOrientation(orientation); + + item.setAnimation(sprite); + item.setComposite(Settings.UseActorAccurateBlending ? sprite.getDecoder().getComposite() : null); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + } + + @Override + public Viewable getViewable() + { + return npc; + } + + @Override + public boolean isScheduled(int schedule) + { + return true; // always active + } + + /** Tooltip for actor object. */ + private String getTooltip() + { + String retVal = null; + + retVal = ((IsTextual)npc.getAttribute(PartyNPC.GAM_NPC_NAME)).getText().trim(); + if (retVal.isEmpty()) { + retVal = ((IsTextual)cre.getAttribute(CreResource.CRE_NAME)).getText().trim(); + } + if (retVal.isEmpty()) { + retVal = ((IsTextual)cre.getAttribute(CreResource.CRE_TOOLTIP)).getText().trim(); + } + if (retVal.isEmpty()) { + retVal = "(no name)"; + } + + return retVal; + } +} diff --git a/src/org/infinity/resource/are/viewer/LayerObjectIniActor.java b/src/org/infinity/resource/are/viewer/LayerObjectIniActor.java index 1267cdc70..c0bfe8a6d 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectIniActor.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectIniActor.java @@ -28,14 +28,13 @@ */ public class LayerObjectIniActor extends LayerObjectActor { - private static EnumMap ICONS = new EnumMap(Allegiance.class) {{ + private static final EnumMap ICONS = new EnumMap(Allegiance.class) {{ put(Allegiance.GOOD, new Image[] {Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_INI_ACTOR_G_1), Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_INI_ACTOR_G_2)}); put(Allegiance.NEUTRAL, new Image[] {Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_INI_ACTOR_B_1), Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_INI_ACTOR_B_2)}); put(Allegiance.ENEMY, new Image[] {Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_INI_ACTOR_R_1), Icons.getImage(ViewerIcons.class, ViewerIcons.ICON_ITM_INI_ACTOR_R_2)}); - }}; private static final Point CENTER = new Point(12, 40); diff --git a/src/org/infinity/resource/are/viewer/icon/ViewerIcons.java b/src/org/infinity/resource/are/viewer/icon/ViewerIcons.java index 4cc822e6f..392bd1115 100644 --- a/src/org/infinity/resource/are/viewer/icon/ViewerIcons.java +++ b/src/org/infinity/resource/are/viewer/icon/ViewerIcons.java @@ -61,6 +61,12 @@ public class ViewerIcons extends Icons public static final String ICON_ITM_AUTOMAP_2 = "itm_Automap2.png"; public static final String ICON_ITM_ENTRANCE_1 = "itm_Entrance1.png"; public static final String ICON_ITM_ENTRANCE_2 = "itm_Entrance2.png"; + public static final String ICON_ITM_GAM_ACTOR_B_1 = "itm_GamActorB1.png"; + public static final String ICON_ITM_GAM_ACTOR_B_2 = "itm_GamActorB2.png"; + public static final String ICON_ITM_GAM_ACTOR_G_1 = "itm_GamActorG1.png"; + public static final String ICON_ITM_GAM_ACTOR_G_2 = "itm_GamActorG2.png"; + public static final String ICON_ITM_GAM_ACTOR_R_1 = "itm_GamActorR1.png"; + public static final String ICON_ITM_GAM_ACTOR_R_2 = "itm_GamActorR2.png"; public static final String ICON_ITM_INI_ACTOR_B_1 = "itm_IniActorB1.png"; public static final String ICON_ITM_INI_ACTOR_B_2 = "itm_IniActorB2.png"; public static final String ICON_ITM_INI_ACTOR_G_1 = "itm_IniActorG1.png"; diff --git a/src/org/infinity/resource/are/viewer/icon/itm_GamActorB1.png b/src/org/infinity/resource/are/viewer/icon/itm_GamActorB1.png new file mode 100644 index 0000000000000000000000000000000000000000..89aa122d83839bb437e4de1b28602b25214632dc GIT binary patch literal 3011 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 FV1oQHyfy#; literal 0 HcmV?d00001 diff --git a/src/org/infinity/resource/are/viewer/icon/itm_GamActorB2.png b/src/org/infinity/resource/are/viewer/icon/itm_GamActorB2.png new file mode 100644 index 0000000000000000000000000000000000000000..e42ac90d22f0c0833d80c04c9837aa0234511e69 GIT binary patch literal 3024 zcmV;>3orDEP)3+}LA# zuHVdeKi9qY;^7-l#*TA=vG>b4du<)9|JiG;|N8H(p7`Ux<@ymw6c`c4S;pB5SFw%c zNu|tV5=F-W>*{JnOZYFzvwx)3$)wT52Z!LeaSZAk;v(icbUEN}o zwy5gan?M}LTwGp>$D&{P`{zG@;M9CfL`o4UIMPZu&`M~EU@_EpA4R--nY=O>v+HE?)z2~c>gTRGyw1MI_KtI1 zlr=?JQ`DZ7%d3yxee(X#Jao@dMx{fPLMw$Rg|UiQYtq=ziWG~9<&I8DH%$p;4HA&0 zLw9S>Gbp|5q)c1q!tqO2<7x%m&7 zMw3h16MBUsgg_0r5C}dHLZI}4Q7vrdo>Aqgy@vpVfS3re_A#lwxrs0J|0xnJva?GF zL8-e2Ma}Xk=hf|;wcIf+9n;#ARi3O4Y!{A|tY*Ee7!@@|=@4H5eg;A0AX?^bQV{Q1 z-q*suv)i*OcGV8K2Eol+rHZfnN@TRx@wJ?4pLuaNzxF=&0(v`9yJ`nuK!(Hr>2Gg6{YD&v1SXYdxfRi| zmc%H6f~(__3;ikQdn0<;l)9XNbJ*q_QdfjL|E~7pZniUf=Qo0XM}QK?vo~YVgTZ&) z%JTE;-7aO3vzb>cHewpq5=WXK!p68@b(GP|rsUa>kdH8N6O}dzD{o@*^4ro(-%om} zn}z+x-Lxd}TVKpC{6P15e^;(w`K8|60mgaG)hH%58e=pr2w7Q?S0%oj5V9dUiikS% zl*JIUwsl$3cvu-e;*Cgl_E(19PK6iNbb z{q!vm3#!cD##jPKtJya$d`E3>|As2ZU}7X~ASlQtgnS#K1}1d>(>IeJQWT)x2ig6f z_27YU&#$_12CJMC6L1hhgKlgs*@gxbqSf3i3i|c{RE@^X zWey7)95;= zZo|3LE-o(KP#Qv#By7~xvxk?Cv=$dS3>1v(nrT%~YKw9fUrrSgAdMn?cuquKxGg-m z7+s&-MjOWCAxijzpMLlRT5I}|qMw&+Oe|Z!Y%xkLn|VqzvKV97P%sGr6Kb5)`1$rxySDY&5JxW& zZZW`xB-+8INMgf!ZvfW*`LF)`X}Z?am`fQ|p1C9ZQt7#>c zc5GBLwj@!CZM4xgLe-A2Fh^)L{!mW}y>myiKY+H?;QZ?5$4)yRkr!-dl`8Lu>eoJTh{sF$Ryq4Z~ z^8TM~EG+V7Z%nUnjB3ZEc1-e`N$Kd9HND)ikvn>2#klko-s3_*!pqv~7==aAD;#Ti&82b0wGipU`?-TMk_cT?P`Ow34NzJ0N7c&ZPrF94P%6VHkBA~mtQA%p z0)f2t6t1EwD||J@*Eu$65z2g~tj!)eQ3{Du^yWsXYWKx`1GHMN#KHHbgY|`pPOyy* zx)d~emiWOF#3A0{iwp@hachB4mxOY96Y%oM23zG*xa? ze&BBAr<;VUZ&3F)$*YX27*b6KRMS3gIsh$1twn;bam56zMQntmO+;(-U=w@wJjy%# zp~EqXU*5l9T{<*aJ^#knJFVvDKk@kEY+YIA+`03t_1D0}gtP_iE_CKlI-+(3QfFW- z%6s&vj~@3?#T4mu@k_@LlM-E8+ZJoSzHfkOr!C^#ub=<^e}8|tvH9f_KY8NV&prMG z!|hGhx3<~b9xxiNldn(l(;>lmum)^I&=#yF#1UcfI3ev~`(yOhPJmC$Wo-8Vp2 zJ8YVg#L+ia&cE?D>z9^)`{;f5JbC!eJ3cmd;7%6qxQl5vp+6ij8jZ<{jKT*hr4gGD zl!aPQS$;WMyYeq)oW0~4jsKvPg5ST~va|d1fC_=RLkIIltNFE!D_6dD@$74lB%StC zNvrjdq}#nK*m$lawY3;k`^rgK7P=VrbXC1ZRlkhzPob51-)`=M#Fuu6Me SQqGb900007JhM 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 VZ^-cV-Z-1)Q*52xqfAI)=mjtMjfEdQp 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 FV1mmzu@C?N literal 0 HcmV?d00001 diff --git a/src/org/infinity/resource/are/viewer/icon/itm_GamActorR1.png b/src/org/infinity/resource/are/viewer/icon/itm_GamActorR1.png new file mode 100644 index 0000000000000000000000000000000000000000..c99e477b0c7f189ac62416ba93d3a0169af2cad0 GIT binary patch literal 2974 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 decompress() throws Exception } Files.createDirectory(tempFolder); + // placing content of .sav resource in the temporary folder final List entries = new ArrayList<>(fileEntries.size()); for (final SavResourceEntry entry: fileEntries) { Path file = tempFolder.resolve(entry.getResourceName()); @@ -111,6 +112,15 @@ public List decompress() throws Exception } entries.add(new FileResourceEntry(file)); } + + // placing copy of associated .gam resource in the temporary folder + String gamFile = Profile.getProperty(Profile.Key.GET_GAM_NAME); + Path srcFile = entry.getActualPath().getParent().resolve(gamFile); + if (Files.isRegularFile(srcFile)) { + Path dstFile = tempFolder.resolve(gamFile); + Files.copy(srcFile, dstFile); + } + return entries; } From 24a582710e6196e4e5372b33a061077d7426ce1a Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 9 Apr 2021 12:10:36 +0200 Subject: [PATCH 109/158] Area Viewer: Fix actors in saved areas not displayed in selected games --- .../are/viewer/LayerObjectAreActor.java | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/org/infinity/resource/are/viewer/LayerObjectAreActor.java b/src/org/infinity/resource/are/viewer/LayerObjectAreActor.java index 173904e57..f2085de41 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectAreActor.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectAreActor.java @@ -10,20 +10,20 @@ import org.infinity.datatype.Flag; import org.infinity.datatype.IsNumeric; +import org.infinity.datatype.IsReference; import org.infinity.datatype.IsTextual; -import org.infinity.datatype.ResourceRef; -import org.infinity.datatype.TextString; import org.infinity.gui.layeritem.AbstractLayerItem; import org.infinity.gui.layeritem.AnimatedLayerItem; import org.infinity.gui.layeritem.IconLayerItem; import org.infinity.icon.Icons; +import org.infinity.resource.Resource; import org.infinity.resource.ResourceFactory; -import org.infinity.resource.StructEntry; import org.infinity.resource.Viewable; import org.infinity.resource.are.Actor; import org.infinity.resource.are.AreResource; import org.infinity.resource.are.viewer.icon.ViewerIcons; import org.infinity.resource.cre.CreResource; +import org.infinity.resource.key.ResourceEntry; /** * Handles specific layer type: ARE/Actor @@ -57,16 +57,19 @@ public LayerObjectAreActor(AreResource parent, Actor actor) location.y = ((IsNumeric)actor.getAttribute(Actor.ARE_ACTOR_POS_Y)).getValue(); scheduleFlags = ((Flag)actor.getAttribute(Actor.ARE_ACTOR_PRESENT_AT)); - StructEntry obj = actor.getAttribute(Actor.ARE_ACTOR_CHARACTER); - if (obj instanceof TextString) { - // ARE in saved game - cre = (CreResource)actor.getAttribute(Actor.ARE_ACTOR_CRE_FILE); - } - else if (obj instanceof ResourceRef) { - final ResourceRef creRef = (ResourceRef)obj; - if (!creRef.isEmpty()) { - cre = new CreResource(ResourceFactory.getResourceEntry(creRef.getResourceName())); + boolean isReference = ((Flag)actor.getAttribute(Actor.ARE_ACTOR_FLAGS)).isFlagSet(0); + if (isReference) { + // external CRE resource? + ResourceEntry creEntry = ResourceFactory.getResourceEntry(((IsReference)actor.getAttribute(Actor.ARE_ACTOR_CHARACTER)).getResourceName()); + if (creEntry != null) { + Resource res = ResourceFactory.getResource(creEntry); + if (res instanceof CreResource) { + cre = (CreResource)res; + } } + } else { + // attached CRE resource? + cre = (CreResource)actor.getAttribute(Actor.ARE_ACTOR_CRE_FILE); } if (cre != null) { From 4da8a87bb907e206bd78eabf7c18bbea9aa03656 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 9 Apr 2021 13:18:37 +0200 Subject: [PATCH 110/158] SpriteDecoder: Exclude overlays from special coloring (stoneskin, ...) --- src/org/infinity/resource/cre/decoder/SpriteDecoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index 1ab075873..296fd42d3 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -121,7 +121,7 @@ public void accept(BamV1Control control, SegmentDef sd) applyColorTint(control, sd); } - if (isPaletteReplacementEnabled() && isFalseColor()) { + if (isPaletteReplacementEnabled() && isFalseColor() && sd.getSpriteType() == SegmentDef.SpriteType.AVATAR) { applyColorEffects(control, sd); } From b6e81e2d8f19922143ead6220b14edc9605a6a96 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 9 Apr 2021 14:54:26 +0200 Subject: [PATCH 111/158] Area Viewer: Fix display of global actors in non-saved areas --- .../infinity/resource/are/viewer/LayerObjectGlobalActor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/infinity/resource/are/viewer/LayerObjectGlobalActor.java b/src/org/infinity/resource/are/viewer/LayerObjectGlobalActor.java index 082a0d869..f39b02865 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectGlobalActor.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectGlobalActor.java @@ -59,7 +59,7 @@ public LayerObjectGlobalActor(GamResource parent, PartyNPC npc) } } else { // external resource? - String creRes = ((IsTextual)this.npc.getAttribute(PartyNPC.GAM_NPC_NAME)).getText(); + String creRes = ((IsTextual)this.npc.getAttribute(PartyNPC.GAM_NPC_CHARACTER)).getText(); ResourceEntry entry = ResourceFactory.getResourceEntry(creRes + ".CRE"); if (entry != null) { Resource res = ResourceFactory.getResource(entry); From 06a9a947f6718f76cfd24dba7a48794763d845a6 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 9 Apr 2021 18:53:24 +0200 Subject: [PATCH 112/158] SpriteDecoder: Show placeholder if creature animation is unavailable --- .../cre/decoder/PlaceholderDecoder.java | 161 ++++++++++++++++++ .../resource/cre/decoder/SpriteDecoder.java | 2 +- .../resource/cre/decoder/placeholder.bam | Bin 0 -> 1100 bytes .../cre/decoder/util/AnimationInfo.java | 9 +- .../cre/decoder/util/SpriteUtils.java | 6 + 5 files changed, 176 insertions(+), 2 deletions(-) create mode 100644 src/org/infinity/resource/cre/decoder/PlaceholderDecoder.java create mode 100644 src/org/infinity/resource/cre/decoder/placeholder.bam diff --git a/src/org/infinity/resource/cre/decoder/PlaceholderDecoder.java b/src/org/infinity/resource/cre/decoder/PlaceholderDecoder.java new file mode 100644 index 000000000..02a844950 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/PlaceholderDecoder.java @@ -0,0 +1,161 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.infinity.resource.Profile; +import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.util.AnimationInfo; +import org.infinity.resource.cre.decoder.util.DecoderAttribute; +import org.infinity.resource.cre.decoder.util.Direction; +import org.infinity.resource.cre.decoder.util.SegmentDef; +import org.infinity.resource.cre.decoder.util.SeqDef; +import org.infinity.resource.cre.decoder.util.Sequence; +import org.infinity.resource.key.BufferedResourceEntry; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.IniMap; +import org.infinity.util.IniMapEntry; +import org.infinity.util.IniMapSection; +import org.infinity.util.io.StreamUtils; + +/** + * General-purpose creature animation decoder for handling non-existing or unknown animation types. + * Available ranges: [0000,ffff] + */ +public class PlaceholderDecoder extends SpriteDecoder +{ + /** The animation type associated with this class definition. */ + public static final AnimationInfo.Type ANIMATION_TYPE = AnimationInfo.Type.PLACEHOLDER; + + /** ResourceEntry for the placeholder animation BAM file. */ + private static final ResourceEntry BAM_PLACEHOLDER = loadPlaceholderBam(); + + /** Loads the placeholder animation into memory as a virtual resource entry object. */ + private static ResourceEntry loadPlaceholderBam() + { + ResourceEntry retVal = null; + // REMEMBER: BAM file path is relative to PlaceholderDecoder class path + try (InputStream is = PlaceholderDecoder.class.getResourceAsStream("placeholder.bam")) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int nRead; + byte[] buf = new byte[1024]; + while ((nRead = is.read(buf, 0, buf.length)) != -1) { + baos.write(buf, 0, nRead); + } + baos.flush(); + retVal = new BufferedResourceEntry(StreamUtils.getByteBuffer(baos.toByteArray()), "placeholder.bam"); + } catch (IOException e) { + e.printStackTrace(); + } + return retVal; + } + + public PlaceholderDecoder(int animationId, IniMap ini) throws Exception + { + super(ANIMATION_TYPE, animationId, ini); + } + + public PlaceholderDecoder(CreResource cre) throws Exception + { + super(ANIMATION_TYPE, cre); + } + + @Override + public List getAnimationFiles(boolean essential) + { + String resref = getAnimationResref(); + ArrayList retVal = new ArrayList<>(); + retVal.add(resref + ".BAM"); + return retVal; + } + + @Override + public boolean isSequenceAvailable(Sequence seq) + { + return (getSequenceDefinition(seq) != null); + } + + @Override + protected IniMapSection getSpecificIniSection() + { + IniMapSection retVal = null; + IniMap ini = getAnimationInfo(); + if (ini != null) { + for (final Iterator iter = ini.iterator(); iter.hasNext(); ) { + IniMapSection section = iter.next(); + switch (section.getName().toLowerCase()) { + case "general": + case "sounds": + break; + default: + retVal = section; + } + if (retVal != null) { + break; + } + } + } + + if (retVal == null) { + retVal = new IniMapSection(getAnimationSectionName(), 0, null); + } + + return retVal; + } + + @Override + protected void init() throws Exception + { + // setting properties + initDefaults(getAnimationInfo()); + + // autodetecting animation attributes + IniMapSection section = getSpecificIniSection(); + setAttribute(KEY_ANIMATION_SECTION, section.getName()); // override with animation-specific name + for (final Iterator iter = section.iterator(); iter.hasNext(); ) { + IniMapEntry entry = iter.next(); + String key = entry.getKey(); + String value = entry.getValue(); + try { + int n = Integer.parseInt(value); + if (n == 0 || n == 1) { + setAttribute(new DecoderAttribute(key, DecoderAttribute.DataType.BOOLEAN), Boolean.valueOf(n != 0)); + } else { + setAttribute(new DecoderAttribute(key, DecoderAttribute.DataType.DECIMAL), Integer.valueOf(n)); + } + } catch (NumberFormatException e) { + setAttribute(new DecoderAttribute(key, DecoderAttribute.DataType.STRING), value); + } + } + } + + @Override + protected SeqDef getSequenceDefinition(Sequence seq) + { + SeqDef retVal = null; + + switch (Profile.getGame()) { + case PST: + case PSTEE: + if (seq == Sequence.PST_STAND) { + retVal = SeqDef.createSequence(seq, new Direction[] {Direction.S}, false, + BAM_PLACEHOLDER, 0, SegmentDef.SpriteType.AVATAR); + } + break; + default: + if (seq == Sequence.STAND) { + retVal = SeqDef.createSequence(seq, new Direction[] {Direction.S}, false, + BAM_PLACEHOLDER, 0, SegmentDef.SpriteType.AVATAR); + } + } + return retVal; + } +} diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index 296fd42d3..390b45782 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -257,7 +257,7 @@ protected SpriteDecoder(AnimationInfo.Type type, CreResource cre) throws Excepti setAttribute(KEY_ANIMATION_SECTION, type.getSectionName()); this.creInfo = new CreatureInfo(this, cre); this.ini = Objects.requireNonNull(SpriteUtils.getAnimationInfo(getAnimationId()), - "No INI data available for animation id: " + getAnimationId()); + String.format("No INI data available for animation id: 0x%04x", getAnimationId())); this.currentSequence = Sequence.NONE; this.showCircle = false; this.selectionCircleBitmap = (Profile.getGame() == Profile.Game.PST) || (Profile.getGame() == Profile.Game.PSTEE); diff --git a/src/org/infinity/resource/cre/decoder/placeholder.bam b/src/org/infinity/resource/cre/decoder/placeholder.bam new file mode 100644 index 0000000000000000000000000000000000000000..b8f107ec85c5b63cb54115629a708e648ac2d437 GIT binary patch literal 1100 zcmV-S1he}>K}|zeF(4r73;+OloTXMVXd71;{#-;Uq4ZTH6jOpxiU=XtB@R(_gjS@5 zTIZqQDLRby2)sz(APpXxNjF#^Pu5~c7SU0fbcp(G_HY&Khmt;tqq+hcQcoiZPr(xseGl9W{ersTKDe{v#G zA^-NaJ3;!Bp8yK|$xi^>YRD<`CztFp{|fU@CRcca`AnIQ&3wA3aK1^-cNJhf?&H&^ zQ+)jR3LifFfp+^>G#VMad9#UPF^zG5j8SWZtHKq|ch1qywb95laJY4dV!DX_c#OYC zBNVQ#uycNnT)T}-qk*l%L!^sEmSBt)@vmRgPJE8|4DnmUr->i;`{Zwtzd-&S^5@8( zA^#Tn)8y}?HENN+K>i)_=g6NS{}%bvkj@a&>Oq=yOlnowr4l+u0Zl!RvX+G-hZx8n zOj$>5Qboxw!RQpAH}lZSSquh4Sj4wlv=d(<-XLBlUL#&Qth>d!o2+|{Wh}9b2Fs|k zj2g(_g~d9XtaFWZF0sxA>#VcR8td!~B?dhSvo2AqPRMV=D0HCbo6xdl4Bksvd=FYF z+PP3AE|kHA(z#F?7fN;=uDiu`H)*ZW%K6cwNslr;oS_4&XOZ6|f6dFkV31!YzlQe% ziMN(Ss>Sy#Cc&ZO;L@^CH%(M)9=>20$m=??{D!*H!CPtZJu(qjfXjCddd%fp0j=7TBCsAz>{c)_`wLH4T z;@Z+-TS7!f_%Sk8FM*U|7wVYxQb9`!JXjgU_xj-uUasGIWVXRA((ydMU!HrsD}C=P z3fEx3%KjJMLeP_!ob5$64)t|IXg(F6k-N9nbSzSCxwcF5;)`7wk#aVJ#N9zEeyYvgoPY>#fKVdNwZVZtYO_?y{Wp2PfaAgp<+%>0=MkDiD4{J#+Y S&&2yvasOIauYUm6&$3Ltc{H>D literal 0 HcmV?d00001 diff --git a/src/org/infinity/resource/cre/decoder/util/AnimationInfo.java b/src/org/infinity/resource/cre/decoder/util/AnimationInfo.java index 94a35b52b..f7e6c239f 100644 --- a/src/org/infinity/resource/cre/decoder/util/AnimationInfo.java +++ b/src/org/infinity/resource/cre/decoder/util/AnimationInfo.java @@ -169,7 +169,14 @@ public enum Type { /** Animation type: F000 */ MONSTER_PLANESCAPE(0xf000, "monster_planescape", Arrays.asList( Couple.with(TYPE_GAME_PSTEE, RANGE_MONSTER_PLANESCAPE), - Couple.with(TYPE_GAME_PST, RANGE_MONSTER_PLANESCAPE_EX))); // type=18 + Couple.with(TYPE_GAME_PST, RANGE_MONSTER_PLANESCAPE_EX))), // type=18 + + /** Pseudo animation type: fallback option for non-existing animations */ + PLACEHOLDER(new int[] {0x0000, 0x1000, 0x2000, 0x3000, 0x4000, 0x5000, 0x6000, 0x7000, + 0x8000, 0x9000, 0xa000, 0xb000, 0xc000, 0xd000, 0xe000, 0xf000}, + "placeholder", + Arrays.asList(Couple.with(TYPE_GAME_ALL, Arrays.asList(new NumberRange(0x0000, 0xffff)))), + Arrays.asList(new NumberRange(0x0000, 0xffff))); private final EnumMap> rangeMap = new EnumMap<>(Profile.Game.class); private final List iaRanges; diff --git a/src/org/infinity/resource/cre/decoder/util/SpriteUtils.java b/src/org/infinity/resource/cre/decoder/util/SpriteUtils.java index 388b29680..d36d960c1 100644 --- a/src/org/infinity/resource/cre/decoder/util/SpriteUtils.java +++ b/src/org/infinity/resource/cre/decoder/util/SpriteUtils.java @@ -28,6 +28,7 @@ import org.infinity.resource.cre.decoder.AmbientStaticDecoder; import org.infinity.resource.cre.decoder.CharacterDecoder; import org.infinity.resource.cre.decoder.CharacterOldDecoder; +import org.infinity.resource.cre.decoder.PlaceholderDecoder; import org.infinity.resource.cre.decoder.EffectDecoder; import org.infinity.resource.cre.decoder.FlyingDecoder; import org.infinity.resource.cre.decoder.MonsterAnkhegDecoder; @@ -1091,6 +1092,11 @@ public static Class detectAnimationType(int animationId } } } + + if (retVal == null) { + // No luck yet? Fall back to placeholder animation! + retVal = PlaceholderDecoder.class; + } } return retVal; From 79983e7ea837aec77cdde4b8c715c0c72059e3e4 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sat, 10 Apr 2021 18:37:54 +0200 Subject: [PATCH 113/158] Fix issues with adding/removing AddRemovables in CRE V2.2 Note: Just removed a condition specifically for CRE V2.2 fields (No idea why it was there in the first place. Hopefully it doesn't introduce any unwanted side effects.) --- src/org/infinity/resource/AbstractStruct.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/org/infinity/resource/AbstractStruct.java b/src/org/infinity/resource/AbstractStruct.java index cb35e2be5..e417a2600 100644 --- a/src/org/infinity/resource/AbstractStruct.java +++ b/src/org/infinity/resource/AbstractStruct.java @@ -34,7 +34,6 @@ import org.infinity.gui.BrowserMenuBar; import org.infinity.gui.StructViewer; import org.infinity.resource.are.Actor; -import org.infinity.resource.cre.CreResource; import org.infinity.resource.dlg.AbstractCode; import org.infinity.resource.itm.ItmResource; import org.infinity.resource.key.ResourceEntry; @@ -136,8 +135,7 @@ else if (so.getValue() + superStruct.getExtraOffset() == datatype.getOffset()) { so.incValue(amount); } } - else if (!(so.getSection().equals(datatype.getClass()) || - (Profile.getEngine() == Profile.Engine.IWD2 && superStruct instanceof CreResource))) { + else if (!so.getSection().equals(datatype.getClass())) { so.incValue(amount); } } From fa8c9e8ceac2780e0c81cb87c753556bec551922 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sun, 11 Apr 2021 11:46:09 +0200 Subject: [PATCH 114/158] IWD2-related improvements to CRE and GAM resource structures --- .../infinity/resource/cre/CreResource.java | 35 ++++++++++++------- .../infinity/resource/gam/GamResource.java | 4 +-- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/org/infinity/resource/cre/CreResource.java b/src/org/infinity/resource/cre/CreResource.java index 6eb8681b5..22a3789d0 100644 --- a/src/org/infinity/resource/cre/CreResource.java +++ b/src/org/infinity/resource/cre/CreResource.java @@ -434,6 +434,14 @@ public final class CreResource extends AbstractStruct "Holding item", "Reset bit 16", null, null, "EE: No exploding death", null, "EE: Ignore nightmare mode", "EE: No tooltip", "Allegiance tracking", "General tracking", "Race tracking", "Class tracking", "Specifics tracking", "Gender tracking", "Alignment tracking", "Uninterruptible"}; + public static final String[] s_flag_iwd2 = { + "No flags set", "Damage don't stop casting", "No corpse", "Permanent corpse", + null, null, null, null, null, null, "Fallen paladin", "Fallen ranger", + "Export allowed", null, "Quest critical", "Can activate non-NPC triggers", "Enabled", + "Seen party", "Invulnerable", "Non threatening enemy", "No talk", "Ignore return to start", "Ignore inhibit AI", + null, null, "Allegiance tracking", "General tracking", "Race tracking", "Class tracking", + "Specifics tracking", "Gender tracking", "Alignment tracking", "Corpse related?" + }; public static final String[] s_feats1 = { "No feats selected", "Aegis of rime", "Ambidexterity", "Aqua mortis", "Armor proficiency", "Armored arcana", "Arterial strike", "Blind fight", "Bullheaded", "Cleave", "Combat casting", "Courteous magocracy", "Crippling strike", @@ -941,7 +949,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new Unknown(buffer, offset + 161, 1)); for (int i = 0; i < 3; i++) { addField(new IdsBitmap(buffer, offset + 162 + (i * 2), 2, - String.format(CHR_QUICK_ITEM_SLOT_FMT, i+1), "SLOTS.IDS")); + String.format(CHR_QUICK_ITEM_SLOT_FMT, i+1), "SLOTS.IDS", true, false, true)); } for (int i = 0; i < 3; i++) { addField(new DecNumber(buffer, offset + 168 + (i * 2), 2, @@ -1013,21 +1021,21 @@ private int readIWD2(ByteBuffer buffer, int offset) throws Exception { addField(new StringRef(buffer, offset, CRE_NAME)); addField(new StringRef(buffer, offset + 4, CRE_TOOLTIP)); - addField(new Flag(buffer, offset + 8, 4, CRE_FLAGS, s_flag)); // ToDo: figure these out whenever + addField(new Flag(buffer, offset + 8, 4, CRE_FLAGS, s_flag_iwd2)); addField(new DecNumber(buffer, offset + 12, 4, CRE_XP_VALUE)); addField(new DecNumber(buffer, offset + 16, 4, CRE_XP)); addField(new DecNumber(buffer, offset + 20, 4, CRE_GOLD)); addField(uniqueIdsFlag(new IdsFlag(buffer, offset + 24, 4, CRE_STATUS, "STATE.IDS"), "STATE.IDS", '_')); addField(new DecNumber(buffer, offset + 28, 2, CRE_HP_CURRENT)); addField(new DecNumber(buffer, offset + 30, 2, CRE_HP_MAX)); - addField(new IdsBitmap(buffer, offset + 32, 4, CRE_ANIMATION, "ANIMATE.IDS")); - addField(new ColorValue(buffer, offset + 36, 1, CRE_COLOR_METAL, true)); - addField(new ColorValue(buffer, offset + 37, 1, CRE_COLOR_MINOR, true)); - addField(new ColorValue(buffer, offset + 38, 1, CRE_COLOR_MAJOR, true)); - addField(new ColorValue(buffer, offset + 39, 1, CRE_COLOR_SKIN, true)); - addField(new ColorValue(buffer, offset + 40, 1, CRE_COLOR_LEATHER, true)); - addField(new ColorValue(buffer, offset + 41, 1, CRE_COLOR_ARMOR, true)); - addField(new ColorValue(buffer, offset + 42, 1, CRE_COLOR_HAIR, true)); + addField(new AnimateBitmap(buffer, offset + 32, 4, CRE_ANIMATION)); + addField(new ColorValue(buffer, offset + 36, 1, CRE_COLOR_METAL, false)); + addField(new ColorValue(buffer, offset + 37, 1, CRE_COLOR_MINOR, false)); + addField(new ColorValue(buffer, offset + 38, 1, CRE_COLOR_MAJOR, false)); + addField(new ColorValue(buffer, offset + 39, 1, CRE_COLOR_SKIN, false)); + addField(new ColorValue(buffer, offset + 40, 1, CRE_COLOR_LEATHER, false)); + addField(new ColorValue(buffer, offset + 41, 1, CRE_COLOR_ARMOR, false)); + addField(new ColorValue(buffer, offset + 42, 1, CRE_COLOR_HAIR, false)); Bitmap effect_version = addField(new Bitmap(buffer, offset + 43, 1, CRE_EFFECT_VERSION, s_effversion)); effect_version.addUpdateListener(this); addField(new ResourceRef(buffer, offset + 44, CRE_PORTRAIT_SMALL, "BMP")); @@ -1097,8 +1105,8 @@ private int readIWD2(ByteBuffer buffer, int offset) throws Exception addField(new ResourceRef(buffer, offset + 420, CRE_SCRIPT_TEAM, "BCS")); addField(new ResourceRef(buffer, offset + 428, CRE_SCRIPT_SPECIAL_1, "BCS")); - addField(new DecNumber(buffer, offset + 436, 2, CRE_ENCHANTMENT_LEVEL)); - addField(new Unknown(buffer, offset + 438, 2)); + addField(new DecNumber(buffer, offset + 436, 1, CRE_ENCHANTMENT_LEVEL)); + addField(new Unknown(buffer, offset + 437, 3)); addField(new Flag(buffer, offset + 440, 4, CRE_FEATS_1, s_feats1)); addField(new Flag(buffer, offset + 444, 4, CRE_FEATS_2, s_feats2)); addField(new Flag(buffer, offset + 448, 4, CRE_FEATS_3, s_feats3)); @@ -1205,7 +1213,8 @@ private int readIWD2(ByteBuffer buffer, int offset) throws Exception addField(new DecNumber(buffer, offset + 906, 2, CRE_IDENTIFIER_LOCAL)); addField(new TextString(buffer, offset + 908, 32, CRE_SCRIPT_NAME)); addField(new IdsBitmap(buffer, offset + 940, 2, CRE_CLASS_2, "CLASS.IDS")); - addField(new IdsBitmap(buffer, offset + 942, 4, CRE_CLASS_MASK, "CLASSMSK.IDS")); + addField(new IdsBitmap(buffer, offset + 942, 2, CRE_CLASS_MASK, "CLASSMSK.IDS")); + addField(new Unknown(buffer, offset + 944, 2)); // Bard spells for (int i = 0; i < 9; i++) { diff --git a/src/org/infinity/resource/gam/GamResource.java b/src/org/infinity/resource/gam/GamResource.java index 9cc6fb137..861b1e87a 100644 --- a/src/org/infinity/resource/gam/GamResource.java +++ b/src/org/infinity/resource/gam/GamResource.java @@ -121,7 +121,7 @@ public final class GamResource extends AbstractStruct implements Resource, HasCh "Normal windows", "Party AI disabled", "Larger text window", "Largest text window", null, "Fullscreen mode", "Left pane hidden", "Right pane hidden", "Unsupported"}; public static final String[] s_configuration_iwd2 = { - "Normal windows", "Party AI disabled", null, null, null, "Fullscreen mode", null, + "Normal windows", "Party AI enabled", null, null, null, "Fullscreen mode", "Toolbar hidden", "Console hidden", "Automap notes hidden"}; public static final String[] s_version_bg1 = {"Restrict XP to BG1 limit", "Restrict XP to TotSC limit"}; public static final String[] s_familiar_owner = { @@ -404,7 +404,7 @@ else if (Profile.getEngine() == Profile.Engine.BG2 || Profile.isEnhancedEdition( } } else if (Profile.getEngine() == Profile.Engine.IWD2) { // V2.2 (V1.1 & V2.0 in BIFF) - addField(new Unknown(buffer, offset + 84, 4)); + addField(new DecNumber(buffer, offset + 84, 4, GAM_REPUTATION)); addField(new ResourceRef(buffer, offset + 88, GAM_MASTER_AREA, "ARE")); addField(new Flag(buffer, offset + 96, 4, GAM_CONFIGURATION, s_configuration_iwd2)); numIWD2 = new SectionCount(buffer, offset + 100, 4, GAM_NUM_UNKNOWN, UnknownSection3.class); From 15689a1636a91d96ec7c405dc2b177e083606fa8 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sun, 11 Apr 2021 11:55:03 +0200 Subject: [PATCH 115/158] SpriteDecoder: Improve armor code check for character type animations --- .../infinity/resource/cre/decoder/CharacterBaseDecoder.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/org/infinity/resource/cre/decoder/CharacterBaseDecoder.java b/src/org/infinity/resource/cre/decoder/CharacterBaseDecoder.java index a99e9569c..dac991e14 100644 --- a/src/org/infinity/resource/cre/decoder/CharacterBaseDecoder.java +++ b/src/org/infinity/resource/cre/decoder/CharacterBaseDecoder.java @@ -94,7 +94,9 @@ public int getArmorCode() ItemInfo itm = getCreatureInfo().getEquippedArmor(); if (itm != null) { String code = itm.getAppearance(); - retVal = Math.max(1, Math.min(getMaxArmorCode(), Misc.toNumber(code.substring(0, 1), 1))); + if (!code.isEmpty()) { + retVal = Math.max(1, Math.min(getMaxArmorCode(), Misc.toNumber(code.substring(0, 1), 1))); + } } return retVal; } From 08e62ca04c04c7fbddf6644bb5b53efd58665d99 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sun, 11 Apr 2021 14:37:00 +0200 Subject: [PATCH 116/158] Integrate CRE animation quick viewer as separate tab --- .../infinity/resource/cre/CreResource.java | 29 ++- src/org/infinity/resource/cre/Viewer.java | 25 --- .../resource/cre/ViewerAnimation.java | 177 ++++++++++-------- 3 files changed, 115 insertions(+), 116 deletions(-) diff --git a/src/org/infinity/resource/cre/CreResource.java b/src/org/infinity/resource/cre/CreResource.java index 22a3789d0..a4591f2ec 100644 --- a/src/org/infinity/resource/cre/CreResource.java +++ b/src/org/infinity/resource/cre/CreResource.java @@ -48,7 +48,6 @@ import org.infinity.datatype.UpdateListener; import org.infinity.gui.ButtonPanel; import org.infinity.gui.ButtonPopupMenu; -import org.infinity.gui.ChildFrame; import org.infinity.gui.StructViewer; import org.infinity.gui.hexview.BasicColorMap; import org.infinity.gui.hexview.StructHexViewer; @@ -424,6 +423,12 @@ public final class CreResource extends AbstractStruct public static final String CRE_SELECTED_WEAPON_SLOT = "Weapon slot selected"; public static final String CRE_SELECTED_WEAPON_ABILITY = "Weapon ability selected"; + public static final String TAB_ANIMATION = "Animation"; + + public static final int TAB_INDEX_VIEW = 0; + public static final int TAB_INDEX_ANIMATION = 1; + public static final int TAB_INDEX_RAW = 2; + private static final LongIntegerHashMap m_magetype = new LongIntegerHashMap(); private static final LongIntegerHashMap m_colorPlacement = new LongIntegerHashMap(); public static final String[] s_flag = { @@ -741,9 +746,9 @@ public CreResource(AbstractStruct superStruct, String name, ByteBuffer data, int @Override public void close() throws Exception { - ViewerAnimation va = ChildFrame.getFirstFrame(ViewerAnimation.class); - if (va != null) { - va.close(); + JComponent c = getViewerTab(TAB_INDEX_ANIMATION); + if (c instanceof ViewerAnimation) { + ((ViewerAnimation)c).close(); } super.close(); } @@ -786,16 +791,18 @@ public boolean canRemove() @Override public int getViewerTabCount() { - return showRawTab() ? 2 : 1; + return showRawTab() ? 3 : 2; } @Override public String getViewerTabName(int index) { switch (index) { - case 0: + case TAB_INDEX_VIEW: return StructViewer.TAB_VIEW; - case 1: + case TAB_INDEX_ANIMATION: + return TAB_ANIMATION; + case TAB_INDEX_RAW: return showRawTab() ? StructViewer.TAB_RAW : null; } return null; @@ -805,9 +812,11 @@ public String getViewerTabName(int index) public JComponent getViewerTab(int index) { switch (index) { - case 0: + case TAB_INDEX_VIEW: return new Viewer(this); - case 1: + case TAB_INDEX_ANIMATION: + return new ViewerAnimation(this); + case TAB_INDEX_RAW: if (showRawTab() && hexViewer == null) { hexViewer = new StructHexViewer(this, new BasicColorMap(this, true)); } @@ -819,7 +828,7 @@ public JComponent getViewerTab(int index) @Override public boolean viewerTabAddedBefore(int index) { - return (index == 0); + return (index < TAB_INDEX_RAW); } // diff --git a/src/org/infinity/resource/cre/Viewer.java b/src/org/infinity/resource/cre/Viewer.java index 2df8e902c..698e3b2fa 100644 --- a/src/org/infinity/resource/cre/Viewer.java +++ b/src/org/infinity/resource/cre/Viewer.java @@ -11,12 +11,9 @@ import java.awt.GridBagLayout; import java.awt.GridLayout; import java.awt.Insets; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import javax.swing.BorderFactory; import javax.swing.DefaultListCellRenderer; -import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JList; @@ -28,10 +25,8 @@ import org.infinity.datatype.Flag; import org.infinity.datatype.IsNumeric; import org.infinity.datatype.ResourceRef; -import org.infinity.gui.ChildFrame; import org.infinity.gui.ViewerUtil; import org.infinity.gui.ViewerUtil.ListValueRenderer; -import org.infinity.icon.Icons; import org.infinity.resource.AbstractStruct; import org.infinity.resource.Effect; import org.infinity.resource.Effect2; @@ -197,24 +192,6 @@ private JPanel makeMainPanel(CreResource cre) imagePanel = ViewerUtil.makeImagePanel((ResourceRef)cre.getAttribute(CreResource.CRE_PORTRAIT_SMALL), true); } - JButton bViewAnimation = new JButton("View creature animation", Icons.getIcon(Icons.ICON_VOLUME_16)); - bViewAnimation.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) - { - ViewerAnimation va = ChildFrame.getFirstFrame(ViewerAnimation.class); - if (va == null) { - va = new ViewerAnimation(cre); - } else if (!va.isVisible()) { - va.setVisible(true); - va.toFront(); - } else { - va.toFront(); - } - } - }); - bViewAnimation.setMargin(new Insets(8, 8, 8, 4)); - GridBagLayout gbl = new GridBagLayout(); GridBagConstraints gbc = new GridBagConstraints(); JPanel leftPanel = new JPanel(gbl); @@ -225,8 +202,6 @@ public void actionPerformed(ActionEvent e) gbc.gridwidth = GridBagConstraints.REMAINDER; gbl.setConstraints(imagePanel, gbc); leftPanel.add(imagePanel); - gbl.setConstraints(bViewAnimation, gbc); - leftPanel.add(bViewAnimation); gbc.weighty = 1.0; gbl.setConstraints(effectPanel, gbc); leftPanel.add(effectPanel); diff --git a/src/org/infinity/resource/cre/ViewerAnimation.java b/src/org/infinity/resource/cre/ViewerAnimation.java index 488fba2fa..29fec6e5a 100644 --- a/src/org/infinity/resource/cre/ViewerAnimation.java +++ b/src/org/infinity/resource/cre/ViewerAnimation.java @@ -5,6 +5,7 @@ package org.infinity.resource.cre; import java.awt.AlphaComposite; +import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; @@ -14,6 +15,8 @@ import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; import java.awt.image.BufferedImage; import java.util.Objects; @@ -22,6 +25,7 @@ import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; +import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; @@ -33,9 +37,6 @@ import javax.swing.Timer; import org.infinity.NearInfinity; -import org.infinity.datatype.IsNumeric; -import org.infinity.datatype.IsTextual; -import org.infinity.gui.Center; import org.infinity.gui.ChildFrame; import org.infinity.gui.RenderCanvas; import org.infinity.gui.ViewerUtil; @@ -45,14 +46,12 @@ import org.infinity.resource.cre.decoder.util.Sequence; import org.infinity.resource.cre.decoder.util.SpriteUtils; import org.infinity.resource.cre.viewer.CreatureViewer; -import org.infinity.resource.gam.PartyNPC; import org.infinity.resource.graphics.PseudoBamDecoder.PseudoBamControl; -import org.infinity.util.StringTable; /** * A basic creature animation viewer. */ -public class ViewerAnimation extends ChildFrame implements ActionListener +public class ViewerAnimation extends JComponent implements ActionListener { private static final Color COLOR_TRANSPARENT = new Color(0, true); private static final int ANIM_DELAY = 1000 / 15; // 15 fps in milliseconds @@ -63,6 +62,7 @@ public class ViewerAnimation extends ChildFrame implements ActionListener private final CreResource cre; + private boolean initialized; private SpriteDecoder decoder; private PseudoBamControl bamControl; private RenderCanvas rcDisplay; @@ -77,21 +77,8 @@ public class ViewerAnimation extends ChildFrame implements ActionListener public ViewerAnimation(CreResource cre) { - super("", true); this.cre = Objects.requireNonNull(cre); - try { - this.decoder = SpriteDecoder.importSprite(getCre()); - - init(); - } catch (Exception e) { - e.printStackTrace(); - JOptionPane.showMessageDialog(this, "Creature animation could not be loaded.\nError message: " + e.getMessage(), - "Error", JOptionPane.ERROR_MESSAGE); - this.bamControl = null; - this.decoder = null; - close(); - return; - } + init(); } public CreResource getCre() @@ -240,21 +227,39 @@ public void rewind() showFrame(); } -//--------------------- Begin Class ChildFrame --------------------- + /** Loads the creature animation associated with the current CRE resource. */ + public void open() + { + // loading animation on demand + if (!isInitialized()) { + try { + WindowBlocker.blockWindow(true); + initAnimation(); + } catch (Exception ex) { + ex.printStackTrace(); + WindowBlocker.blockWindow(false); + JOptionPane.showMessageDialog(NearInfinity.getInstance(), + "Creature animation could not be loaded.\nError message: " + ex.getMessage(), + "Error", JOptionPane.ERROR_MESSAGE); + } finally { + WindowBlocker.blockWindow(false); + } + } + } - @Override - protected boolean windowClosing(boolean forced) throws Exception + /** Cleans up resources. */ + public void close() { pause(); + setInitialized(false); if (getDecoder() != null) { getDecoder().close(); } SpriteUtils.clearCache(); - return true; + this.bamControl = null; + this.decoder = null; } -//--------------------- End Class ChildFrame --------------------- - //--------------------- Begin Interface ActionListener --------------------- @Override @@ -269,7 +274,7 @@ public void actionPerformed(ActionEvent event) else if (cbSequences == event.getSource()) { Sequence seq = cbSequences.getModel().getElementAt(cbSequences.getSelectedIndex()); try { - WindowBlocker.blockWindow(this, true); + WindowBlocker.blockWindow(true); setAnimationSequence(seq); updateControls(); } catch (Exception e) { @@ -277,40 +282,40 @@ else if (cbSequences == event.getSource()) { JOptionPane.showMessageDialog(this, e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); cbSequences.setSelectedItem(getAnimationSequence()); } finally { - WindowBlocker.blockWindow(this, false); + WindowBlocker.blockWindow(false); } } else if (cbZoom == event.getSource()) { try { - WindowBlocker.blockWindow(this, true); + WindowBlocker.blockWindow(true); zoom = cbZoom.isSelected(); updateCanvasSize(); } finally { - WindowBlocker.blockWindow(this, false); + WindowBlocker.blockWindow(false); } } else if (cbShowCircle == event.getSource()) { try { - WindowBlocker.blockWindow(this, true); + WindowBlocker.blockWindow(true); showSelectionCircle = cbShowCircle.isSelected(); getDecoder().setSelectionCircleEnabled(showSelectionCircle); resetAnimationSequence(); } catch (Exception e) { e.printStackTrace(); } finally { - WindowBlocker.blockWindow(this, false); + WindowBlocker.blockWindow(false); } } else if (cbShowSpace == event.getSource()) { try { - WindowBlocker.blockWindow(this, true); + WindowBlocker.blockWindow(true); showPersonalSpace = cbShowSpace.isSelected(); getDecoder().setPersonalSpaceVisible(showPersonalSpace); resetAnimationSequence(); } catch (Exception e) { e.printStackTrace(); } finally { - WindowBlocker.blockWindow(this, false); + WindowBlocker.blockWindow(false); } } else if (bPrevCycle == event.getSource()) { @@ -360,18 +365,18 @@ else if (bPlay == event.getSource()) { else if (bOpenBrowser == event.getSource()) { CreatureViewer cv = ChildFrame.show(CreatureViewer.class, () -> new CreatureViewer(getCre())); if (cv != null) { + pause(); if (getCre() != cv.getCreResource()) { cv.setCreResource(getCre()); } cv.toFront(); } - close(); } } //--------------------- End Interface ActionListener --------------------- - private void init() throws Exception + private void init() { rcDisplay = new RenderCanvas(new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB)); rcDisplay.setHorizontalAlignment(SwingConstants.CENTER); @@ -403,21 +408,12 @@ private void init() throws Exception JLabel lSequence = new JLabel("Sequence:"); DefaultComboBoxModel modelSequences = new DefaultComboBoxModel<>(); cbSequences = new JComboBox<>(modelSequences); - for (final Sequence seq : Sequence.values()) { - if (getDecoder().isSequenceAvailable(seq)) { - modelSequences.addElement(seq); - } - } - cbSequences.setEnabled(cbSequences.getItemCount() > 0); - cbSequences.addActionListener(this); cbZoom = new JCheckBox("Zoom", zoom); cbZoom.addActionListener(this); - getDecoder().setSelectionCircleEnabled(showSelectionCircle); - cbShowCircle = new JCheckBox("Show selection circle", getDecoder().isSelectionCircleEnabled()); + cbShowCircle = new JCheckBox("Show selection circle", showSelectionCircle); cbShowCircle.addActionListener(this); - getDecoder().setPersonalSpaceVisible(showPersonalSpace); - cbShowSpace = new JCheckBox("Show personal space", getDecoder().isPersonalSpaceVisible()); + cbShowSpace = new JCheckBox("Show personal space", showPersonalSpace); cbShowSpace.addActionListener(this); bOpenBrowser = new JButton("Open in browser", Icons.getIcon(Icons.ICON_CRE_VIEWER_24)); @@ -488,49 +484,47 @@ private void init() throws Exception pRow2.add(new JPanel(), c); - setLayout(new GridBagLayout()); + JPanel pMain = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); - add(scrollDisplay, c); + pMain.add(scrollDisplay, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(8, 0, 0, 0), 0, 0); - add(pRow1, c); + pMain.add(pRow1, c); c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(8, 0, 8, 0), 0, 0); - add(pRow2, c); - - // determining creature resource and name - String resName, name; - if (getCre().getResourceEntry() != null) { - resName = getCre().getResourceEntry().getResourceName(); - } else if (getCre().getParent() != null) { - resName = getCre().getParent().getName(); - } else { - resName = getCre().getName(); - } + pMain.add(pRow2, c); - int strref = ((IsNumeric)getCre().getAttribute(CreResource.CRE_NAME)).getValue(); - if (!StringTable.isValidStringRef(strref)) { - strref = ((IsNumeric)getCre().getAttribute(CreResource.CRE_NAME)).getValue(); - } - if (StringTable.isValidStringRef(strref)) { - name = StringTable.getStringRef(strref); - } else if (getCre().getParent() instanceof PartyNPC) { - name = ((IsTextual)getCre().getParent().getAttribute(PartyNPC.GAM_NPC_NAME)).getText(); - } else { - name = ""; - } + setLayout(new BorderLayout()); + add(pMain, BorderLayout.CENTER); - if (!name.isEmpty()) { - setTitle(String.format("%s (%s)", resName, name)); - } else { - setTitle(resName); - } + setInitialized(false); - Dimension dim = NearInfinity.getInstance().getSize(); - setSize(dim.width - 200, dim.height - 45); - Center.center(this, NearInfinity.getInstance().getBounds()); - setVisible(true); + addComponentListener(new ComponentAdapter() { + @Override + public void componentShown(ComponentEvent e) + { + // loading animation on demand + open(); + } + }); + } + + private void initAnimation() throws Exception + { + this.decoder = SpriteDecoder.importSprite(getCre()); + getDecoder().setSelectionCircleEnabled(showSelectionCircle); + getDecoder().setPersonalSpaceVisible(showPersonalSpace); + + // preparing sequence list + DefaultComboBoxModel modelSequences = (DefaultComboBoxModel)cbSequences.getModel(); + for (final Sequence seq : Sequence.values()) { + if (getDecoder().isSequenceAvailable(seq)) { + modelSequences.addElement(seq); + } + } + cbSequences.setEnabled(cbSequences.getItemCount() > 0); + cbSequences.addActionListener(this); // loading animation sequence if (cbSequences.isEnabled()) { @@ -546,6 +540,27 @@ private void init() throws Exception cbSequences.setSelectedItem(seq); setAnimationSequence(seq); } + + setInitialized(true); + } + + private boolean isInitialized() + { + return initialized; + } + + private void setInitialized(boolean b) + { + initialized = b; + JComponent[] controls = new JComponent[] { + bNextCycle, bPrevCycle, bNextFrame, bPrevFrame, bOpenBrowser, bPlay, + lCurCycle, lCurFrame, cbSequences, cbShowCircle, cbShowSpace, cbZoom + }; + for (final JComponent c : controls) { + if (c != null) { + c.setEnabled(initialized); + } + } } private void showFrame() From 20f53bbefae206896d440df3398898f2ca0e7d6a Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sun, 11 Apr 2021 18:56:41 +0200 Subject: [PATCH 117/158] Creature Animation Browser: Allow user-defined color as background --- .../resource/cre/viewer/SettingsPanel.java | 59 ++++++++++++++++++- .../resource/cre/viewer/bg/Backgrounds.java | 25 ++++++-- 2 files changed, 79 insertions(+), 5 deletions(-) diff --git a/src/org/infinity/resource/cre/viewer/SettingsPanel.java b/src/org/infinity/resource/cre/viewer/SettingsPanel.java index eff1f1294..73cc20caa 100644 --- a/src/org/infinity/resource/cre/viewer/SettingsPanel.java +++ b/src/org/infinity/resource/cre/viewer/SettingsPanel.java @@ -6,6 +6,7 @@ import java.awt.AlphaComposite; import java.awt.BorderLayout; +import java.awt.Color; import java.awt.Composite; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; @@ -20,16 +21,20 @@ import javax.swing.JButton; import javax.swing.JCheckBox; +import javax.swing.JColorChooser; import javax.swing.JComboBox; +import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; +import javax.swing.colorchooser.AbstractColorChooserPanel; import org.infinity.gui.ViewerUtil; import org.infinity.resource.Profile; import org.infinity.resource.cre.viewer.bg.Backgrounds; import org.infinity.resource.cre.viewer.bg.Backgrounds.BackgroundInfo; import org.infinity.resource.cre.viewer.icon.Icons; +import org.infinity.util.tuples.Monuple; /** * This panel provides controls for visual settings and includes a table view of creature animation attributes. @@ -49,6 +54,8 @@ public class SettingsPanel extends JPanel add(Backgrounds.BG_COLOR_BLACK); add(Backgrounds.BG_COLOR_LIGHT_GRAY); add(Backgrounds.BG_COLOR_GRAY); + // REMEMBER: has to be last entry in list + add(new Backgrounds.BackgroundInfo("Customize color...", Color.WHITE)); }}; // Available items for zoom selection list @@ -187,8 +194,22 @@ private void setFrameRateIndex(int index) private void setBackgroundInfoIndex(int index) { - if (index != indexBackground) { + if (index != indexBackground || index == cbBackground.getModel().getSize() - 1) { if (index >= 0 && index < cbBackground.getModel().getSize()) { + if (index == cbBackground.getModel().getSize() - 1) { + // special: define custom color + Backgrounds.BackgroundInfo info = cbBackground.getModel().getElementAt(index); + Color color = getCustomColor(info.getColor()); + if (color != null) { + info = cbBackground.getModel().getElementAt(index); + info.setColor(color); + info.setLabel(String.format("Customize color: RGB(%d,%d,%d)", color.getRed(), color.getGreen(), color.getBlue())); + } else { + cbBackground.setSelectedIndex(indexBackground); + return; + } + } + indexBackground = index; cbBackground.setSelectedIndex(indexBackground); applyBackgroundInfo(); @@ -558,6 +579,42 @@ private void init() add(scroll, BorderLayout.CENTER); } + /** Returns a color from user input. */ + private Color getCustomColor(Color defColor) + { + final JColorChooser cc = new JColorChooser((defColor != null) ? defColor : Color.WHITE); + + // We only need the RGB panel + AbstractColorChooserPanel rgbPanel = null; + for (final AbstractColorChooserPanel panel : cc.getChooserPanels()) { + if (panel.getDisplayName().toUpperCase().contains("RGB")) { + rgbPanel = panel; + } + } + if (rgbPanel != null) { + for (final AbstractColorChooserPanel panel : cc.getChooserPanels()) { + if (panel != rgbPanel) { + cc.removeChooserPanel(panel); + } + } + } + + final Monuple retVal = Monuple.with(null); + JDialog dlg = null; + try { + // Returns color value without alpha component + dlg = JColorChooser.createDialog(getViewer(), "Choose background color", true, cc, + evt -> retVal.setValue0(new Color(cc.getColor().getRGB(), false)), null); + dlg.setVisible(true); + } finally { + if (dlg != null) { + dlg.dispose(); + dlg = null; + } + } + return retVal.getValue0(); + } + //-------------------------- INNER CLASSES -------------------------- private class Listeners implements ActionListener diff --git a/src/org/infinity/resource/cre/viewer/bg/Backgrounds.java b/src/org/infinity/resource/cre/viewer/bg/Backgrounds.java index 75207a563..2fb4f4ce3 100644 --- a/src/org/infinity/resource/cre/viewer/bg/Backgrounds.java +++ b/src/org/infinity/resource/cre/viewer/bg/Backgrounds.java @@ -111,10 +111,11 @@ private Backgrounds() {} public static class BackgroundInfo { private final EnumSet games; - private final String label; - private final String imageName; - private final Color color; - private final Point center; + + private String label; + private Color color; + private String imageName; + private Point center; public BackgroundInfo(String label, Color bgColor) { @@ -136,9 +137,19 @@ public BackgroundInfo(String label, Color bgColor, String imageName, Point cente /** Returns the descriptive label of the background entry. */ public String getLabel() { return label; } + public void setLabel(String l) + { + if (!this.label.equals(l)) { + this.label = Objects.requireNonNull(l, "Description label required"); + } + } + /** Returns the background image filename. Returns {@code null} if background image has not been defined. */ public String getImageName() { return imageName; } + /** Sets a new background image filename. Specify {@code null} to clear background image. */ + public void setImageName(String name) { this.imageName = name; } + /** Returns the background image. Returns {@code null} if no background image is defined. */ public Image getImage() { return hasImage() ? Backgrounds.getImage(imageName) : null; } @@ -151,9 +162,15 @@ public Color getColor() { return retVal; } + /** Sets a new background color. Specify {@code null} to use system-dependent color. */ + public void setColor(Color col) { this.color = col; } + /** Returns the center point of the associated image. Returns {@code null} if center has not been defined. */ public Point getCenter() { return center; } + /** Sets a new center point for the image. */ + public void setCenter(Point p) { this.center = p; } + /** Returns whether the background is available for the current game. */ public boolean isAvailable() { return (games == null) || games.contains(Profile.getGame()); } From db91da38847cfebe0436b5914a33fcd7b3d4715b Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sun, 11 Apr 2021 19:29:06 +0200 Subject: [PATCH 118/158] Cosmetic --- src/org/infinity/resource/gam/GamResource.java | 2 +- src/org/infinity/resource/gam/PartyNPC.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/org/infinity/resource/gam/GamResource.java b/src/org/infinity/resource/gam/GamResource.java index 861b1e87a..e33d944cc 100644 --- a/src/org/infinity/resource/gam/GamResource.java +++ b/src/org/infinity/resource/gam/GamResource.java @@ -306,7 +306,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new DecNumber(buffer, offset + 14 + (i * 2), 2, String.format(GAM_FORMATION_BUTTON_FMT, i+1))); } addField(new DecNumber(buffer, offset + 24, 4, GAM_PARTY_GOLD)); - addField(new HashBitmap(buffer, offset + 28, 2, GAM_VIEW_PLAYER_AREA, PartyNPC.m_partyOrder)); + addField(new HashBitmap(buffer, offset + 28, 2, GAM_VIEW_PLAYER_AREA, PartyNPC.m_partyOrder, true, true)); addField(new Flag(buffer, offset + 30, 2, GAM_WEATHER, s_weather)); SectionOffset offset_partynpc = new SectionOffset(buffer, offset + 32, GAM_OFFSET_PARTY_MEMBERS, PartyNPC.class); diff --git a/src/org/infinity/resource/gam/PartyNPC.java b/src/org/infinity/resource/gam/PartyNPC.java index 7744ddad7..b5a49cd28 100644 --- a/src/org/infinity/resource/gam/PartyNPC.java +++ b/src/org/infinity/resource/gam/PartyNPC.java @@ -87,7 +87,8 @@ public class PartyNPC extends AbstractStruct implements HasViewerTabs, AddRemova public static final LongIntegerHashMap m_partyOrder = new LongIntegerHashMap(); // private static final LongIntegerHashMap m_selected = new LongIntegerHashMap(); - private static final String[] s_selected = {"Not selected", "Selected", null, null, null, null, null, null, null, null, null, null, null, null, null, null, "Dead" }; + private static final String[] s_selected = {"Not selected", "Selected", null, null, null, null, null, null, null, + null, null, null, null, null, null, null, "Dead" }; static { m_partyOrder.put(0L, "Slot 1"); @@ -96,8 +97,7 @@ public class PartyNPC extends AbstractStruct implements HasViewerTabs, AddRemova m_partyOrder.put(3L, "Slot 4"); m_partyOrder.put(4L, "Slot 5"); m_partyOrder.put(5L, "Slot 6"); -// partyOrder.put(0x8000L, "In party, dead"); - m_partyOrder.put(0xffffL, "Not in party"); + m_partyOrder.put(-1L, "Not in party"); // m_selected.put(0L, "Not selected"); // m_selected.put(1L, "Selected"); @@ -194,7 +194,7 @@ void updateCREOffset() public int read(ByteBuffer buffer, int offset) throws Exception { addField(new Flag(buffer, offset, 2, GAM_NPC_SELECTION_STATE, s_selected)); - addField(new HashBitmap(buffer, offset + 2, 2, GAM_NPC_PARTY_POSITION, m_partyOrder)); + addField(new HashBitmap(buffer, offset + 2, 2, GAM_NPC_PARTY_POSITION, m_partyOrder, true, true)); HexNumber creOffset = new HexNumber(buffer, offset + 4, 4, GAM_NPC_OFFSET_CRE); addField(creOffset); addField(new DecNumber(buffer, offset + 8, 4, GAM_NPC_CRE_SIZE)); From d4743fbd6a3a8c3b2bee53da6c458e2ac46824aa Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Mon, 12 Apr 2021 11:23:33 +0200 Subject: [PATCH 119/158] SpriteDecoder: Fix IA detection issues --- src/org/infinity/resource/cre/decoder/util/AnimationInfo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/infinity/resource/cre/decoder/util/AnimationInfo.java b/src/org/infinity/resource/cre/decoder/util/AnimationInfo.java index f7e6c239f..0532d0c70 100644 --- a/src/org/infinity/resource/cre/decoder/util/AnimationInfo.java +++ b/src/org/infinity/resource/cre/decoder/util/AnimationInfo.java @@ -176,7 +176,7 @@ public enum Type { 0x8000, 0x9000, 0xa000, 0xb000, 0xc000, 0xd000, 0xe000, 0xf000}, "placeholder", Arrays.asList(Couple.with(TYPE_GAME_ALL, Arrays.asList(new NumberRange(0x0000, 0xffff)))), - Arrays.asList(new NumberRange(0x0000, 0xffff))); + null); private final EnumMap> rangeMap = new EnumMap<>(Profile.Game.class); private final List iaRanges; From f4fae3ddbd66f3e69d8c8af5e5d703e4d6481fd8 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Mon, 12 Apr 2021 15:13:05 +0200 Subject: [PATCH 120/158] Changed placement of Animation tab in CRE resource (Fixes tab selection issues.) --- src/org/infinity/resource/cre/CreResource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/infinity/resource/cre/CreResource.java b/src/org/infinity/resource/cre/CreResource.java index a4591f2ec..7c778b238 100644 --- a/src/org/infinity/resource/cre/CreResource.java +++ b/src/org/infinity/resource/cre/CreResource.java @@ -828,7 +828,7 @@ public JComponent getViewerTab(int index) @Override public boolean viewerTabAddedBefore(int index) { - return (index < TAB_INDEX_RAW); + return (index == 0); } // From d541fc3c8924682ec8ba1f62db94b52c368bbd56 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Mon, 12 Apr 2021 16:30:47 +0200 Subject: [PATCH 121/158] SpriteDecoder: Add animation import method based on animation id --- .../resource/cre/decoder/SpriteDecoder.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index 390b45782..1a9cf34c7 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -197,7 +197,22 @@ public void accept(DirDef dd, int frameIdx) private boolean autoApplyChanges; /** - * Convenience method for loading the animation of the specified CRE resource. + * Creates a new {@code SpriteDecoder} instance based on the specified animation id. + * @param animationId the creature animation id in the range [0, 0xffff]. + * @return A {@code SpriteDecoder} instance with processed animation data. + * @throws Exception if the creature animation could not be loaded. + */ + public static SpriteDecoder importSprite(int animationId) throws Exception + { + if (animationId < 0 || animationId > 0xffff) { + throw new IllegalArgumentException(String.format("Animation id is out of range: 0x%04x", animationId)); + } + CreResource cre = SpriteUtils.getPseudoCre(animationId, null, null); + return importSprite(cre); + } + + /** + * Creates a new {@code SpriteDecoder} instance based on the specified CRE resource. * @param cre The CRE resource instance. * @return A {@code SpriteDecoder} instance with processed animation data. * @throws Exception if the specified resource could not be processed. From 0fdcf0befeabc16987276787b090e1785e5cf743 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Mon, 12 Apr 2021 17:39:35 +0200 Subject: [PATCH 122/158] SpriteDecoder: Improve ellipse and personal space calculation --- .../resource/cre/decoder/SpriteDecoder.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index 1a9cf34c7..414435f7d 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -1296,7 +1296,7 @@ protected int createFrame(FrameInfo[] sourceFrames, BeforeSourceFrame beforeSrcF */ protected Dimension getPersonalSpaceSize(boolean scaled) { - int size = (Math.max(1, getPersonalSpace()) - 1) | 1; + int size = Math.max(0, (getPersonalSpace() - 1) | 1); if (scaled) { return new Dimension(size * 16, size * 12); } else { @@ -1331,12 +1331,16 @@ protected BufferedImage createPersonalSpace(Color color, float alpha) // creating personal space pattern (unscaled) Dimension dim = getPersonalSpaceSize(false); + if (dim.width == 0 || dim.height == 0) { + // personal space is not defined + return new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); + } BufferedImage image = new BufferedImage(dim.width, dim.height, BufferedImage.TYPE_INT_ARGB); int[] bitmap = ((DataBufferInt)image.getRaster().getDataBuffer()).getData(); int cx = dim.width / 2; int cy = dim.height / 2; int c = color.getRGB(); - int maxDist = dim.width * dim.width / 4; + float maxDist = (dim.width / 2.0f) * (dim.height / 2.0f); for (int y = 0; y < dim.height; y++) { for (int x = 0; x < dim.width; x++) { int ofs = y * dim.width + x; @@ -1372,6 +1376,9 @@ protected Dimension getSelectionCircleSize() { Dimension dim = new Dimension(); dim.width = Math.max(0, getEllipse()); + if (dim.width == 0) { + return dim; + } if (isSelectionCircleBitmap()) { dim.width += 4; // compensation for missing stroke size } @@ -1406,6 +1413,9 @@ protected void drawSelectionCircle(Graphics2D g, Point center, float strokeSize) { if (g != null) { Dimension dim = getSelectionCircleSize(); + if (dim.width == 0 || dim.height == 0) { + return; + } if (isSelectionCircleBitmap()) { Image image = getCreatureInfo().isStatusPanic() ? SpriteUtils.getAllegianceImage(-1) From 938dec9a68d4f42f479fa70aebbcb448df9e8a85 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Mon, 12 Apr 2021 22:37:10 +0200 Subject: [PATCH 123/158] Fix Infinity Animations v6 detection --- src/org/infinity/resource/Profile.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/infinity/resource/Profile.java b/src/org/infinity/resource/Profile.java index acfe9053c..2e1906434 100644 --- a/src/org/infinity/resource/Profile.java +++ b/src/org/infinity/resource/Profile.java @@ -2330,7 +2330,7 @@ private void initFeatures() try (RandomAccessFile raf = new RandomAccessFile(exeFile, "r")) { // checking key signatures final int[] sigCheckV1 = { 0x3db6d84, 0xc6004c48, 0x54464958, 0x004141de, 0xf9 }; - final int[] sigCheckV2 = { 0x3db6d84, 0xc6004c48, 0x54464958, 0x0041412d, 0xf9 }; + final int[] sigCheckV2 = { 0x3db6d84, 0x34004c48, 0x54464958, 0x0041412d, 0xf9 }; long ofs[] = { 0x40742cL, 0x40a8daL, 0x7536e7L, 0x407713L }; int sig[] = new int[ofs.length + 1]; for (int i = 0; i < ofs.length; i++) { From 9d128fdcb83033a7481010ea0bd22d1cee412f1f Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Mon, 12 Apr 2021 23:35:58 +0200 Subject: [PATCH 124/158] Creature Animation Browser: Stop playback when window is closed --- .../resource/cre/viewer/CreatureViewer.java | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/org/infinity/resource/cre/viewer/CreatureViewer.java b/src/org/infinity/resource/cre/viewer/CreatureViewer.java index ed2f66a2e..d2e82f8d5 100644 --- a/src/org/infinity/resource/cre/viewer/CreatureViewer.java +++ b/src/org/infinity/resource/cre/viewer/CreatureViewer.java @@ -5,6 +5,8 @@ package org.infinity.resource.cre.viewer; import java.awt.BorderLayout; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Objects; @@ -114,6 +116,7 @@ public void showErrorMessage(String msg, String title) @Override protected boolean windowClosing(boolean forced) throws Exception { + getMediaPanel().pause(); cleanup(); return true; } @@ -158,6 +161,7 @@ private void init() add(panelCreature, BorderLayout.PAGE_START); add(mainCentralPanel, BorderLayout.CENTER); + addComponentListener(listeners); setIconImage(Icons.getImage(Icons.ICON_CRE_VIEWER_24)); setTitle("Creature Animation Browser"); setSize(NearInfinity.getInstance().getPreferredSize()); @@ -251,7 +255,7 @@ protected TaskInfo doInBackground() throws Exception //-------------------------- INNER CLASSES -------------------------- - private class Listeners implements PropertyChangeListener + private class Listeners implements PropertyChangeListener, ComponentListener { public Listeners() { @@ -282,6 +286,31 @@ public void propertyChange(PropertyChangeEvent event) } //--------------------- End Interface PropertyChangeListener --------------------- + + //--------------------- Begin Interface ComponentListener --------------------- + + @Override + public void componentResized(ComponentEvent e) + { + } + + @Override + public void componentMoved(ComponentEvent e) + { + } + + @Override + public void componentShown(ComponentEvent e) + { + } + + @Override + public void componentHidden(ComponentEvent e) + { + getMediaPanel().pause(); + } + + //--------------------- End Interface ComponentListener --------------------- } /** Represents a supplier of results. */ From 077313c1d712791b16d9b42c7c64668825363aa8 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Tue, 13 Apr 2021 11:41:31 +0200 Subject: [PATCH 125/158] Sprite Decoder: Improve equipment filter to allow "meta-equipment" Meta-equipment includes undroppable items for special properties or effects. --- .../cre/decoder/util/CreatureInfo.java | 7 +- .../resource/cre/decoder/util/ItemInfo.java | 15 ++++ .../resource/cre/viewer/CreUtils.java | 75 ++++++++++++------- 3 files changed, 67 insertions(+), 30 deletions(-) diff --git a/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java b/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java index 706adb2d2..7e7a22a79 100644 --- a/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java +++ b/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java @@ -16,6 +16,7 @@ import java.util.Objects; import org.infinity.datatype.EffectType; +import org.infinity.datatype.Flag; import org.infinity.datatype.IsNumeric; import org.infinity.datatype.IsTextual; import org.infinity.resource.AbstractStruct; @@ -914,19 +915,23 @@ private void initEquipment(HashMap slotMap) private void initEquipmentItem(ItemSlots slot, int itemIndex, List itemList) { ResourceEntry itmEntry = null; + boolean isUndroppable = false; if (itemIndex == 1000) { // weapon: fists itmEntry = getFistWeapon(); } else if (itemIndex >= 0 && itemIndex < itemList.size()) { if (itemList.get(itemIndex) instanceof Item) { - String itmResref = ((IsTextual)((Item)itemList.get(itemIndex)).getAttribute(Item.CRE_ITEM_RESREF)).getText(); + Item itm = (Item)itemList.get(itemIndex); + String itmResref = ((IsTextual)itm.getAttribute(Item.CRE_ITEM_RESREF)).getText(); itmEntry = ResourceFactory.getResourceEntry(itmResref + ".ITM"); + isUndroppable = ((Flag)itm.getAttribute(Item.CRE_ITEM_FLAGS)).isFlagSet(3); } } if (itmEntry != null) { try { ItemInfo itemInfo = ItemInfo.get(itmEntry); + itemInfo.overrideDroppableFlag(isUndroppable); equipment.put(slot, itemInfo); } catch (Exception e) { e.printStackTrace(); diff --git a/src/org/infinity/resource/cre/decoder/util/ItemInfo.java b/src/org/infinity/resource/cre/decoder/util/ItemInfo.java index 903912609..6d5a29231 100644 --- a/src/org/infinity/resource/cre/decoder/util/ItemInfo.java +++ b/src/org/infinity/resource/cre/decoder/util/ItemInfo.java @@ -78,6 +78,10 @@ public enum SlotType { * This predicate returns {@code true} only if the item can be equipped in an equipment slot (except item slots). */ public static final ItemPredicate FILTER_EQUIPPABLE = (info) -> { + if ((info.getFlags() & (1 << 2)) == 0) { + // cleared bit 2 (droppable) indicates "meta-equipment" + return true; + } switch (info.getCategory()) { case 1: // Amulets case 2: // Armor @@ -499,6 +503,17 @@ private ItemInfo(ResourceEntry itmEntry) throws Exception /** Returns the item flags. */ public int getFlags() { return flags; } + /** + * Updates the undroppable flag (bit 2) of the item. The undroppable flag can be overridden by CRE item structures. + * @param override specify whether droppable flag should be overridden. + */ + public void overrideDroppableFlag(boolean override) + { + if (override) { + flags &= ~(1 << 2); // Note: item flags specify "droppable" bit + } + } + /** Returns the item category. */ public int getCategory() { return category; } diff --git a/src/org/infinity/resource/cre/viewer/CreUtils.java b/src/org/infinity/resource/cre/viewer/CreUtils.java index 03baa835e..175812fd7 100644 --- a/src/org/infinity/resource/cre/viewer/CreUtils.java +++ b/src/org/infinity/resource/cre/viewer/CreUtils.java @@ -10,6 +10,7 @@ import java.util.List; import java.util.Objects; +import org.infinity.datatype.Flag; import org.infinity.datatype.IsNumeric; import org.infinity.datatype.IsReference; import org.infinity.datatype.IsTextual; @@ -128,12 +129,16 @@ public static ItemInfo getEquipmentHelmet(CreResource cre) boolean isPST = (Profile.getGame() == Profile.Game.PSTEE) || ((IsTextual)cre.getAttribute(CreResource.COMMON_VERSION)).getText().equalsIgnoreCase("V1.2"); String fieldName = isPST ? CreResource.CRE_ITEM_SLOT_RIGHT_EARRING : CreResource.CRE_ITEM_SLOT_HELMET; - ResourceEntry itemEntry = getEquippedItem(cre, fieldName); - if (itemEntry != null) { - try { - retVal = ItemInfo.get(itemEntry); - } catch (Exception e) { - e.printStackTrace(); + Item item = getEquippedItem(cre, fieldName); + if (item != null) { + ResourceEntry itemEntry = ResourceFactory.getResourceEntry(((IsReference)item.getAttribute(Item.CRE_ITEM_RESREF)).getResourceName()); + if (itemEntry != null) { + try { + retVal = ItemInfo.get(itemEntry); + retVal.overrideDroppableFlag(((Flag)item.getAttribute(Item.CRE_ITEM_FLAGS)).isFlagSet(3)); + } catch (Exception e) { + e.printStackTrace(); + } } } return retVal; @@ -161,12 +166,16 @@ public static ItemInfo getEquipmentArmor(CreResource cre) boolean isPST = (Profile.getGame() == Profile.Game.PSTEE) || ((IsTextual)cre.getAttribute(CreResource.COMMON_VERSION)).getText().equalsIgnoreCase("V1.2"); String fieldName = isPST ? CreResource.CRE_ITEM_SLOT_CHEST : CreResource.CRE_ITEM_SLOT_ARMOR; - ResourceEntry itemEntry = getEquippedItem(cre, fieldName); - if (itemEntry != null) { - try { - retVal = ItemInfo.get(itemEntry); - } catch (Exception e) { - e.printStackTrace(); + Item item = getEquippedItem(cre, fieldName); + if (item != null) { + ResourceEntry itemEntry = ResourceFactory.getResourceEntry(((IsReference)item.getAttribute(Item.CRE_ITEM_RESREF)).getResourceName()); + if (itemEntry != null) { + try { + retVal = ItemInfo.get(itemEntry); + retVal.overrideDroppableFlag(((Flag)item.getAttribute(Item.CRE_ITEM_FLAGS)).isFlagSet(3)); + } catch (Exception e) { + e.printStackTrace(); + } } } return retVal; @@ -202,12 +211,16 @@ else if (isIWD2) { } else { fieldName = CreResource.CRE_ITEM_SLOT_SHIELD; } - ResourceEntry itemEntry = getEquippedItem(cre, fieldName); - if (itemEntry != null) { - try { - retVal = ItemInfo.get(itemEntry); - } catch (Exception e) { - e.printStackTrace(); + Item item = getEquippedItem(cre, fieldName); + if (item != null) { + ResourceEntry itemEntry = ResourceFactory.getResourceEntry(((IsReference)item.getAttribute(Item.CRE_ITEM_RESREF)).getResourceName()); + if (itemEntry != null) { + try { + retVal = ItemInfo.get(itemEntry); + retVal.overrideDroppableFlag(((Flag)item.getAttribute(Item.CRE_ITEM_FLAGS)).isFlagSet(3)); + } catch (Exception e) { + e.printStackTrace(); + } } } return retVal; @@ -241,12 +254,16 @@ public static ItemInfo getEquipmentWeapon(CreResource cre) Objects.requireNonNull(cre); ItemInfo retVal = null; String fieldName = String.format(CreResource.CRE_ITEM_SLOT_WEAPON_FMT, 1); - ResourceEntry itemEntry = getEquippedItem(cre, fieldName); - if (itemEntry != null) { - try { - retVal = ItemInfo.get(itemEntry); - } catch (Exception e) { - e.printStackTrace(); + Item item = getEquippedItem(cre, fieldName); + if (item != null) { + ResourceEntry itemEntry = ResourceFactory.getResourceEntry(((IsReference)item.getAttribute(Item.CRE_ITEM_RESREF)).getResourceName()); + if (itemEntry != null) { + try { + retVal = ItemInfo.get(itemEntry); + retVal.overrideDroppableFlag(((Flag)item.getAttribute(Item.CRE_ITEM_FLAGS)).isFlagSet(3)); + } catch (Exception e) { + e.printStackTrace(); + } } } return retVal; @@ -457,18 +474,18 @@ private static int getItemSlotCount(CreResource cre, boolean includeSelectionSlo return retVal; } - /** Helper method: Returns the ITM resource entry referenced by the specified item slot. */ - private static ResourceEntry getEquippedItem(CreResource cre, String slotName) + /** Helper method: Returns the item structure referenced by the specified item slot. */ + private static Item getEquippedItem(CreResource cre, String slotName) { - ResourceEntry retVal = null; + Item retVal = null; int idx = ((IsNumeric)Objects.requireNonNull(cre).getAttribute(Objects.requireNonNull(slotName))).getValue(); if (idx >= 0) { int numItems = ((IsNumeric)cre.getAttribute(CreResource.CRE_NUM_ITEMS)).getValue(); if (idx < numItems) { List itemList = cre.getFields(Item.class); if (idx < itemList.size() && itemList.get(idx) instanceof Item) { - Item item = (Item)itemList.get(idx); - retVal = ResourceFactory.getResourceEntry(((IsReference)item.getAttribute(Item.CRE_ITEM_RESREF)).getResourceName()); + retVal = (Item)itemList.get(idx); +// retVal = ResourceFactory.getResourceEntry(((IsReference)item.getAttribute(Item.CRE_ITEM_RESREF)).getResourceName()); } } } From 062b6e8d932c8771b5062eb60cd42836ba0cc2de Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Tue, 13 Apr 2021 17:29:44 +0200 Subject: [PATCH 126/158] Implement specialized cell editor for structured resource tables Added support for custom inline controls to InlineEditable interface. Default implementation uses simple JTextField control for compatibility reasons. --- src/org/infinity/datatype/DecNumber.java | 36 ++- src/org/infinity/datatype/InlineEditable.java | 37 +++ src/org/infinity/gui/StructCellEditor.java | 252 ++++++++++++++++++ src/org/infinity/gui/StructViewer.java | 13 +- 4 files changed, 323 insertions(+), 15 deletions(-) create mode 100644 src/org/infinity/gui/StructCellEditor.java diff --git a/src/org/infinity/datatype/DecNumber.java b/src/org/infinity/datatype/DecNumber.java index e09d37753..9e78a2984 100644 --- a/src/org/infinity/datatype/DecNumber.java +++ b/src/org/infinity/datatype/DecNumber.java @@ -165,22 +165,36 @@ public boolean equals(Object o) return retVal; } - /** Attempts to parse the specified string into a decimal or, optionally, hexadecimal number. */ - static long parseNumber(Object value, int size, boolean negativeAllowed, boolean hexAllowed) throws Exception + /** Attempts to parse the specified object into a decimal or, optionally, hexadecimal number. */ + public static long parseNumber(Object value, int size, boolean negativeAllowed, boolean hexAllowed) throws Exception { if (value == null) { throw new NullPointerException(); } - String s = value.toString().trim().toLowerCase(Locale.ENGLISH); - int radix = 10; - if (hexAllowed && s.startsWith("0x")) { - s = s.substring(2); - radix = 16; - } else if (hexAllowed && s.endsWith("h")) { - s = s.substring(0, s.length() - 1).trim(); - radix = 16; + + long newNumber; + if (value instanceof IsNumeric) { + newNumber = ((IsNumeric)value).getLongValue(); + } else { + String s; + if (value instanceof IsTextual) { + s = ((IsTextual)value).getText(); + } else { + s = (value != null) ? value.toString() : ""; + } + s = s.toLowerCase(Locale.ENGLISH); + + int radix = 10; + if (hexAllowed && s.startsWith("0x")) { + s = s.substring(2); + radix = 16; + } else if (hexAllowed && s.endsWith("h")) { + s = s.substring(0, s.length() - 1).trim(); + radix = 16; + } + newNumber = Long.parseLong(s, radix); } - long newNumber = Long.parseLong(s, radix); + long discard = negativeAllowed ? 1L : 0L; long maxNum = (1L << ((long)size*8L - discard)) - 1L; long minNum = negativeAllowed ? -(maxNum+1L) : 0; diff --git a/src/org/infinity/datatype/InlineEditable.java b/src/org/infinity/datatype/InlineEditable.java index fe80d80d9..a508bec5d 100644 --- a/src/org/infinity/datatype/InlineEditable.java +++ b/src/org/infinity/datatype/InlineEditable.java @@ -4,10 +4,47 @@ package org.infinity.datatype; +import java.awt.Color; + +import javax.swing.JComponent; +import javax.swing.JTextField; +import javax.swing.UIManager; +import javax.swing.border.LineBorder; + +import org.infinity.gui.BrowserMenuBar; import org.infinity.resource.StructEntry; +import org.infinity.util.Misc; public interface InlineEditable extends StructEntry { + /** The background color used for table cells. */ + static final Color GRID_BACKGROUND = + (UIManager.getColor("Table.focusCellBackground") != null) ? UIManager.getColor("Table.focusCellBackground") : Color.WHITE; + /** The border color used for table cells. */ + static final Color GRID_BORDER = + (UIManager.getColor("Table.gridColor") != null) ? UIManager.getColor("Table.gridColor") : Color.BLACK; + + /** The default component used for the inline editor. */ + static final JTextField DEFAULT_EDITOR = new JTextField() {{ + setFont(Misc.getScaledFont(BrowserMenuBar.getInstance().getScriptFont())); + setBorder(new LineBorder(GRID_BORDER, 1)); + setBackground(GRID_BACKGROUND); + }}; + + /** Called when the specified editor value should be applied to the {@code InlineEditable} object. */ boolean update(Object value); + + /** + * Returns the editing component that is used for editing a value within the table cell. + * This method is called whenever the editor is activated. + *

    Note: For performance reasons the returned component should only be created once and reused afterwards. + */ + default JComponent getEditor() { return DEFAULT_EDITOR; } + + /** This method is called to return the edited data in a format supported by the {@code InlineEditable} object. */ + default Object getEditorValue() { return DEFAULT_EDITOR.getText(); } + + /** This method is called to initialize the editor with the specified data. */ + default void setEditorValue(Object data) { DEFAULT_EDITOR.setText((data != null) ? data.toString() : ""); } } diff --git a/src/org/infinity/gui/StructCellEditor.java b/src/org/infinity/gui/StructCellEditor.java new file mode 100644 index 000000000..4c65da51d --- /dev/null +++ b/src/org/infinity/gui/StructCellEditor.java @@ -0,0 +1,252 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.gui; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.event.MouseEvent; +import java.util.EventObject; + +import javax.swing.AbstractCellEditor; +import javax.swing.JComponent; +import javax.swing.JTable; +import javax.swing.table.TableCellEditor; +import javax.swing.table.TableCellRenderer; + +import org.infinity.datatype.InlineEditable; + +/** + * A specialized cell renderer for the {@code StructViewer} table. + */ +public class StructCellEditor extends AbstractCellEditor implements TableCellEditor +{ + /** The delegate class which handles all methods sent from the {@code CellEditor} */ + protected final StructEditorDelegate delegate = new StructEditorDelegate(); + + /** The Swing component being edited. */ + protected InlineEditable editorComponent; + + /** + * An integer specifying the number of clicks needed to start editing. + * Even if {@codeclickCountToStart} is defined as zero, it will not initiate until a click occurs. + */ + protected int clickCountToStart; + + public StructCellEditor() + { + // use double-click to enable editor + clickCountToStart = 2; + } + + /** + * Returns a reference to the editor component. + * @return the editor Component + */ + public Component getComponent() + { + return (editorComponent != null) ? editorComponent.getEditor() : InlineEditable.DEFAULT_EDITOR; + } + + /** + * Specifies the number of clicks needed to start editing. + * @param count an int specifying the number of clicks needed to start editing + * @see #getClickCountToStart + */ + public void setClickCountToStart(int count) + { + clickCountToStart = count; + } + + /** + * Returns the number of clicks needed to start editing. + * @return the number of clicks needed to start editing + */ + public int getClickCountToStart() + { + return clickCountToStart; + } + + /** + * Forwards the message from the {@code CellEditor} to the {@code delegate}. + * @see StructEditorDelegate#getCellEditorValue + */ + @Override + public Object getCellEditorValue() + { + return delegate.getCellEditorValue(); + } + + /** + * Forwards the message from the {@code CellEditor} to the {@code delegate}. + * @see StructEditorDelegate#isCellEditable(EventObject) + */ + public boolean isCellEditable(EventObject anEvent) + { + return delegate.isCellEditable(anEvent); + } + + /** + * Forwards the message from the {@code CellEditor} to the {@code delegate}. + * @see StructEditorDelegate#shouldSelectCell(EventObject) + */ + public boolean shouldSelectCell(EventObject anEvent) + { + return delegate.shouldSelectCell(anEvent); + } + + /** + * Forwards the message from the {@code CellEditor} to the {@code delegate}. + * @see StructEditorDelegate#stopCellEditing + */ + public boolean stopCellEditing() + { + return delegate.stopCellEditing(); + } + + /** + * Forwards the message from the {@code CellEditor} to the {@code delegate}. + * @see StructEditorDelegate#cancelCellEditing + */ + public void cancelCellEditing() + { + delegate.cancelCellEditing(); + } + + @Override + public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) + { + if (value instanceof InlineEditable) { + editorComponent = (InlineEditable)value; + } + JComponent comp = (editorComponent != null) ? editorComponent.getEditor() : InlineEditable.DEFAULT_EDITOR; + + delegate.setValue(value); + + if (!comp.isOpaque()) { +// if (comp instanceof JCheckBox) { + //in order to avoid a "flashing" effect when clicking a checkbox + //in a table, it is important for the editor to have as a border + //the same border that the renderer has, and have as the background + //the same color as the renderer has. This is primarily only + //needed for JCheckBox since this editor doesn't fill all the + //visual space of the table cell, unlike a text field. + TableCellRenderer renderer = table.getCellRenderer(row, column); + Component c = renderer.getTableCellRendererComponent(table, value, isSelected, true, row, column); + if (c != null) { + comp.setOpaque(true); + comp.setBackground(c.getBackground()); + if (c instanceof JComponent) { + comp.setBorder(((JComponent)c).getBorder()); + } + } else { + comp.setOpaque(false); + } + } + + return comp; + } + +//-------------------------- INNER CLASSES -------------------------- + + protected class StructEditorDelegate implements ActionListener, ItemListener + { + /** + * Returns the value of this cell. + * @return the value of this cell + */ + public Object getCellEditorValue() + { + return editorComponent.getEditorValue(); + } + + /** + * Sets the value of this cell. + * @param value the new value of this cell + */ + public void setValue(Object value) + { + editorComponent.setEditorValue(value); + } + + /** + * Returns {@code true} if {@code anEvent} is not a {@code MouseEvent}. + * Otherwise, it returns {@code true} if the necessary number of clicks have occurred, + * and returns {@code false} otherwise. + * @param anEvent the event + * @return {@code true} if cell is ready for editing, {@code false} otherwise + * @see #setClickCountToStart + * @see #shouldSelectCell + */ + public boolean isCellEditable(EventObject anEvent) + { + if (anEvent instanceof MouseEvent) { + return ((MouseEvent)anEvent).getClickCount() >= clickCountToStart; + } + return true; + } + + /** + * Returns true to indicate that the editing cell may be selected. + * @param anEvent the event + * @return true + * @see #isCellEditable + */ + public boolean shouldSelectCell(EventObject anEvent) + { + return true; + } + + /** + * Returns true to indicate that editing has begun. + * @param anEvent the event + */ + public boolean startCellEditing(EventObject anEvent) + { + return true; + } + + /** + * Stops editing and returns true to indicate that editing has stopped. + * This method calls fireEditingStopped. + * @return true + */ + public boolean stopCellEditing() + { + fireEditingStopped(); + return true; + } + + /** + * Cancels editing. This method calls fireEditingCanceled. + */ + public void cancelCellEditing() + { + fireEditingCanceled(); + } + + /** + * When an action is performed, editing is ended. + * @param e the action event + * @see #stopCellEditing + */ + public void actionPerformed(ActionEvent e) + { + StructCellEditor.this.stopCellEditing(); + } + + /** + * When an item's state changes, editing is ended. + * @param e the action event + * @see #stopCellEditing + */ + public void itemStateChanged(ItemEvent e) + { + StructCellEditor.this.stopCellEditing(); + } + } +} diff --git a/src/org/infinity/gui/StructViewer.java b/src/org/infinity/gui/StructViewer.java index c9960416c..723c1b1c4 100644 --- a/src/org/infinity/gui/StructViewer.java +++ b/src/org/infinity/gui/StructViewer.java @@ -175,9 +175,9 @@ public final class StructViewer extends JPanel implements ListSelectionListener, private final ButtonPanel buttonPanel = new ButtonPanel(); private final JPopupMenu popupmenu = new JPopupMenu(); private final InfinityTextArea tatext = new InfinityTextArea(true); - private final StructTable table = new StructTable(); private final HashMap entryMap = new HashMap<>(); private final HashMap viewMap = new HashMap<>(); + private final StructTable table; private JMenuItem miFindAttribute, miFindReferences, miFindStateReferences, miFindRefToItem; private Editable editable; private JTabbedPane tabbedPane; @@ -220,7 +220,8 @@ private static JMenu createResrefMenu(String cmd, String text, String[] types, I public StructViewer(AbstractStruct struct) { this.struct = struct; - struct.addTableModelListener(this); + this.struct.addTableModelListener(this); + this.table = new StructTable(this.struct); table.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); table.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); table.getSelectionModel().addListSelectionListener(this); @@ -274,6 +275,7 @@ else if (fieldColors.containsKey(field.getClass())) // consider only referenced return this; } }); + table.setDefaultEditor(Object.class, new StructCellEditor()); popupmenu.add(miCopyValue); popupmenu.add(miPasteValue); @@ -1543,10 +1545,13 @@ private void maybeShowPopup(MouseEvent e) } } - private final class StructTable extends JTable implements Printable + private static final class StructTable extends JTable implements Printable { - private StructTable() + private final AbstractStruct struct; + + private StructTable(AbstractStruct struct) { + this.struct = struct; } @Override From c651a4ae710332850bf05f669bd253877c892e33 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Tue, 13 Apr 2021 21:44:31 +0200 Subject: [PATCH 127/158] Internal refactoring Creature Animation Viewer -> Creature Animation Browser --- src/org/infinity/gui/BrowserMenuBar.java | 4 +- .../resource/cre/ViewerAnimation.java | 4 +- .../{viewer => browser}/AttributesPanel.java | 20 +++--- .../ColorSelectionModel.java | 2 +- .../cre/{viewer => browser}/CreUtils.java | 2 +- .../CreatureAnimationModel.java | 2 +- .../CreatureBrowser.java} | 18 ++--- .../CreatureControlModel.java | 10 +-- .../CreatureControlPanel.java | 26 +++---- .../CreatureSelectionModel.java | 2 +- .../CreatureStatusModel.java | 2 +- .../DecoderAttributesTableModel.java | 2 +- .../ItemSelectionModel.java | 2 +- .../cre/{viewer => browser}/MediaPanel.java | 38 +++++----- .../cre/{viewer => browser}/RenderPanel.java | 15 ++-- .../{viewer => browser}/SettingsPanel.java | 68 +++++++++--------- .../{viewer => browser}/bg/Backgrounds.java | 2 +- .../cre/{viewer => browser}/bg/bg_cave.jpg | Bin .../{viewer => browser}/bg/bg_wilderness.jpg | Bin .../{viewer => browser}/bg/iwd_wilderness.jpg | Bin .../cre/{viewer => browser}/bg/pst_city.jpg | Bin .../{viewer => browser}/bg/pst_dungeon.jpg | Bin .../{viewer => browser}/bg/sod_city_night.jpg | Bin .../cre/{viewer => browser}/icon/Icons.java | 2 +- .../{viewer => browser}/icon/btn_center.png | Bin .../cre/{viewer => browser}/icon/btn_end.png | Bin .../cre/{viewer => browser}/icon/btn_home.png | Bin .../{viewer => browser}/icon/btn_pause.png | Bin .../cre/{viewer => browser}/icon/btn_play.png | Bin .../{viewer => browser}/icon/btn_resume.png | Bin .../icon/btn_step_back.png | Bin .../icon/btn_step_forward.png | Bin .../cre/{viewer => browser}/icon/btn_stop.png | Bin .../{viewer => browser}/icon/circle_blue.png | Bin .../{viewer => browser}/icon/circle_green.png | Bin .../{viewer => browser}/icon/circle_red.png | Bin .../icon/circle_yellow.png | Bin .../cre/decoder/util/SpriteUtils.java | 2 +- 38 files changed, 110 insertions(+), 113 deletions(-) rename src/org/infinity/resource/cre/{viewer => browser}/AttributesPanel.java (87%) rename src/org/infinity/resource/cre/{viewer => browser}/ColorSelectionModel.java (99%) rename src/org/infinity/resource/cre/{viewer => browser}/CreUtils.java (99%) rename src/org/infinity/resource/cre/{viewer => browser}/CreatureAnimationModel.java (99%) rename src/org/infinity/resource/cre/{viewer/CreatureViewer.java => browser/CreatureBrowser.java} (95%) rename src/org/infinity/resource/cre/{viewer => browser}/CreatureControlModel.java (98%) rename src/org/infinity/resource/cre/{viewer => browser}/CreatureControlPanel.java (96%) rename src/org/infinity/resource/cre/{viewer => browser}/CreatureSelectionModel.java (99%) rename src/org/infinity/resource/cre/{viewer => browser}/CreatureStatusModel.java (99%) rename src/org/infinity/resource/cre/{viewer => browser}/DecoderAttributesTableModel.java (99%) rename src/org/infinity/resource/cre/{viewer => browser}/ItemSelectionModel.java (99%) rename src/org/infinity/resource/cre/{viewer => browser}/MediaPanel.java (95%) rename src/org/infinity/resource/cre/{viewer => browser}/RenderPanel.java (98%) rename src/org/infinity/resource/cre/{viewer => browser}/SettingsPanel.java (92%) rename src/org/infinity/resource/cre/{viewer => browser}/bg/Backgrounds.java (99%) rename src/org/infinity/resource/cre/{viewer => browser}/bg/bg_cave.jpg (100%) rename src/org/infinity/resource/cre/{viewer => browser}/bg/bg_wilderness.jpg (100%) rename src/org/infinity/resource/cre/{viewer => browser}/bg/iwd_wilderness.jpg (100%) rename src/org/infinity/resource/cre/{viewer => browser}/bg/pst_city.jpg (100%) rename src/org/infinity/resource/cre/{viewer => browser}/bg/pst_dungeon.jpg (100%) rename src/org/infinity/resource/cre/{viewer => browser}/bg/sod_city_night.jpg (100%) rename src/org/infinity/resource/cre/{viewer => browser}/icon/Icons.java (98%) rename src/org/infinity/resource/cre/{viewer => browser}/icon/btn_center.png (100%) rename src/org/infinity/resource/cre/{viewer => browser}/icon/btn_end.png (100%) rename src/org/infinity/resource/cre/{viewer => browser}/icon/btn_home.png (100%) rename src/org/infinity/resource/cre/{viewer => browser}/icon/btn_pause.png (100%) rename src/org/infinity/resource/cre/{viewer => browser}/icon/btn_play.png (100%) rename src/org/infinity/resource/cre/{viewer => browser}/icon/btn_resume.png (100%) rename src/org/infinity/resource/cre/{viewer => browser}/icon/btn_step_back.png (100%) rename src/org/infinity/resource/cre/{viewer => browser}/icon/btn_step_forward.png (100%) rename src/org/infinity/resource/cre/{viewer => browser}/icon/btn_stop.png (100%) rename src/org/infinity/resource/cre/{viewer => browser}/icon/circle_blue.png (100%) rename src/org/infinity/resource/cre/{viewer => browser}/icon/circle_green.png (100%) rename src/org/infinity/resource/cre/{viewer => browser}/icon/circle_red.png (100%) rename src/org/infinity/resource/cre/{viewer => browser}/icon/circle_yellow.png (100%) diff --git a/src/org/infinity/gui/BrowserMenuBar.java b/src/org/infinity/gui/BrowserMenuBar.java index 5a3eb4535..aaf5d028a 100644 --- a/src/org/infinity/gui/BrowserMenuBar.java +++ b/src/org/infinity/gui/BrowserMenuBar.java @@ -84,7 +84,7 @@ import org.infinity.resource.StructureFactory; import org.infinity.resource.Viewable; import org.infinity.resource.ViewableContainer; -import org.infinity.resource.cre.viewer.CreatureViewer; +import org.infinity.resource.cre.browser.CreatureBrowser; import org.infinity.resource.key.FileResourceEntry; import org.infinity.resource.key.Keyfile; import org.infinity.resource.key.ResourceEntry; @@ -1690,7 +1690,7 @@ private void storePreferences() public void actionPerformed(ActionEvent event) { if (event.getSource() == toolCreatureBrowser) { - ChildFrame.show(CreatureViewer.class, () -> new CreatureViewer()); + ChildFrame.show(CreatureBrowser.class, () -> new CreatureBrowser()); } else if (event.getSource() == toolInfinityAmp) { ChildFrame.show(InfinityAmp.class, () -> new InfinityAmp()); diff --git a/src/org/infinity/resource/cre/ViewerAnimation.java b/src/org/infinity/resource/cre/ViewerAnimation.java index 29fec6e5a..301849ae0 100644 --- a/src/org/infinity/resource/cre/ViewerAnimation.java +++ b/src/org/infinity/resource/cre/ViewerAnimation.java @@ -42,10 +42,10 @@ import org.infinity.gui.ViewerUtil; import org.infinity.gui.WindowBlocker; import org.infinity.icon.Icons; +import org.infinity.resource.cre.browser.CreatureBrowser; import org.infinity.resource.cre.decoder.SpriteDecoder; import org.infinity.resource.cre.decoder.util.Sequence; import org.infinity.resource.cre.decoder.util.SpriteUtils; -import org.infinity.resource.cre.viewer.CreatureViewer; import org.infinity.resource.graphics.PseudoBamDecoder.PseudoBamControl; /** @@ -363,7 +363,7 @@ else if (bPlay == event.getSource()) { updateControls(); } else if (bOpenBrowser == event.getSource()) { - CreatureViewer cv = ChildFrame.show(CreatureViewer.class, () -> new CreatureViewer(getCre())); + CreatureBrowser cv = ChildFrame.show(CreatureBrowser.class, () -> new CreatureBrowser(getCre())); if (cv != null) { pause(); if (getCre() != cv.getCreResource()) { diff --git a/src/org/infinity/resource/cre/viewer/AttributesPanel.java b/src/org/infinity/resource/cre/browser/AttributesPanel.java similarity index 87% rename from src/org/infinity/resource/cre/viewer/AttributesPanel.java rename to src/org/infinity/resource/cre/browser/AttributesPanel.java index f2e0d3f2a..dc9162786 100644 --- a/src/org/infinity/resource/cre/viewer/AttributesPanel.java +++ b/src/org/infinity/resource/cre/browser/AttributesPanel.java @@ -2,7 +2,7 @@ // Copyright (C) 2001 - 2021 Jon Olav Hauglid // See LICENSE.txt for license information -package org.infinity.resource.cre.viewer; +package org.infinity.resource.cre.browser; import java.awt.Dimension; import java.awt.Font; @@ -33,26 +33,26 @@ */ public class AttributesPanel extends JPanel implements ActionListener { - private final CreatureViewer viewer; + private final CreatureBrowser browser; private JTable tableAnimation; private DecoderAttributesTableModel tableModel; private JButton bIni; - public AttributesPanel(CreatureViewer viewer) + public AttributesPanel(CreatureBrowser viewer) { super(); - this.viewer = Objects.requireNonNull(viewer); + this.browser = Objects.requireNonNull(viewer); init(); } /** Returns the associated {@code CreatureViewer} instance. */ - public CreatureViewer getViewer() { return viewer; } + public CreatureBrowser getBrowser() { return browser; } /** Discards and reloads the current settings and attributes list. */ public void reset() { - SpriteDecoder decoder = getViewer().getDecoder(); + SpriteDecoder decoder = getBrowser().getDecoder(); tableModel.setDecoder(decoder); bIni.setEnabled(getIniResource() != null); } @@ -64,8 +64,8 @@ public void reset() private ResourceEntry getIniResource() { ResourceEntry retVal = null; - if (getViewer().getDecoder() != null) { - String iniResref = String.format("%04X", getViewer().getDecoder().getAnimationId()); + if (getBrowser().getDecoder() != null) { + String iniResref = String.format("%04X", getBrowser().getDecoder().getAnimationId()); if (iniResref.length() == 4) { retVal = ResourceFactory.getResourceEntry(iniResref + ".INI"); } @@ -123,9 +123,9 @@ public void actionPerformed(ActionEvent e) if (e.getSource() == bIni) { ResourceEntry entry = getIniResource(); if (entry != null) { - new ViewFrame(getViewer(), ResourceFactory.getResource(entry)); + new ViewFrame(getBrowser(), ResourceFactory.getResource(entry)); } else { - JOptionPane.showMessageDialog(getViewer(), "Unable to open INI resource.", "View/Edit INI", + JOptionPane.showMessageDialog(getBrowser(), "Unable to open INI resource.", "View/Edit INI", JOptionPane.ERROR_MESSAGE); } } diff --git a/src/org/infinity/resource/cre/viewer/ColorSelectionModel.java b/src/org/infinity/resource/cre/browser/ColorSelectionModel.java similarity index 99% rename from src/org/infinity/resource/cre/viewer/ColorSelectionModel.java rename to src/org/infinity/resource/cre/browser/ColorSelectionModel.java index 9e5971cc4..f70b0ded5 100644 --- a/src/org/infinity/resource/cre/viewer/ColorSelectionModel.java +++ b/src/org/infinity/resource/cre/browser/ColorSelectionModel.java @@ -2,7 +2,7 @@ // Copyright (C) 2001 - 2021 Jon Olav Hauglid // See LICENSE.txt for license information -package org.infinity.resource.cre.viewer; +package org.infinity.resource.cre.browser; import java.awt.BasicStroke; import java.awt.Color; diff --git a/src/org/infinity/resource/cre/viewer/CreUtils.java b/src/org/infinity/resource/cre/browser/CreUtils.java similarity index 99% rename from src/org/infinity/resource/cre/viewer/CreUtils.java rename to src/org/infinity/resource/cre/browser/CreUtils.java index 175812fd7..227ec5feb 100644 --- a/src/org/infinity/resource/cre/viewer/CreUtils.java +++ b/src/org/infinity/resource/cre/browser/CreUtils.java @@ -2,7 +2,7 @@ // Copyright (C) 2001 - 2021 Jon Olav Hauglid // See LICENSE.txt for license information -package org.infinity.resource.cre.viewer; +package org.infinity.resource.cre.browser; import java.nio.ByteBuffer; import java.nio.ByteOrder; diff --git a/src/org/infinity/resource/cre/viewer/CreatureAnimationModel.java b/src/org/infinity/resource/cre/browser/CreatureAnimationModel.java similarity index 99% rename from src/org/infinity/resource/cre/viewer/CreatureAnimationModel.java rename to src/org/infinity/resource/cre/browser/CreatureAnimationModel.java index 75dc60222..310b781c2 100644 --- a/src/org/infinity/resource/cre/viewer/CreatureAnimationModel.java +++ b/src/org/infinity/resource/cre/browser/CreatureAnimationModel.java @@ -2,7 +2,7 @@ // Copyright (C) 2001 - 2021 Jon Olav Hauglid // See LICENSE.txt for license information -package org.infinity.resource.cre.viewer; +package org.infinity.resource.cre.browser; import java.util.ArrayList; import java.util.Collections; diff --git a/src/org/infinity/resource/cre/viewer/CreatureViewer.java b/src/org/infinity/resource/cre/browser/CreatureBrowser.java similarity index 95% rename from src/org/infinity/resource/cre/viewer/CreatureViewer.java rename to src/org/infinity/resource/cre/browser/CreatureBrowser.java index d2e82f8d5..c8952adae 100644 --- a/src/org/infinity/resource/cre/viewer/CreatureViewer.java +++ b/src/org/infinity/resource/cre/browser/CreatureBrowser.java @@ -2,7 +2,7 @@ // Copyright (C) 2001 - 2021 Jon Olav Hauglid // See LICENSE.txt for license information -package org.infinity.resource.cre.viewer; +package org.infinity.resource.cre.browser; import java.awt.BorderLayout; import java.awt.event.ComponentEvent; @@ -29,9 +29,9 @@ import org.infinity.resource.cre.decoder.util.SpriteUtils; /** - * The Creature Viewer implements a highly customizable viewer for creature animations. + * The Creature Browser implements a highly customizable browser and viewer for creature animations. */ -public class CreatureViewer extends ChildFrame +public class CreatureBrowser extends ChildFrame { private final ConcurrentLinkedQueue actionQueue = new ConcurrentLinkedQueue<>(); private final Listeners listeners = new Listeners(); @@ -44,18 +44,18 @@ public class CreatureViewer extends ChildFrame private SwingWorker worker; /** - * Creates an instance of the creature viewer with a (virtual) default creature. + * Creates an instance of the creature browser with a (virtual) default creature. */ - public CreatureViewer() + public CreatureBrowser() { this(null); } /** - * Creates an instance of the creature viewer and loads the specified CRE resource. + * Creates an instance of the creature browser and loads the specified CRE resource. * @param cre the CRE resource to load */ - public CreatureViewer(CreResource cre) + public CreatureBrowser(CreResource cre) { super(""); init(); @@ -175,7 +175,7 @@ private void cleanup() SpriteUtils.clearCache(); } - /** Background task: Loads the selected creature and initializes the viewer. */ + /** Background task: Loads the selected creature and initializes the browser. */ private Object taskSetCreResource() throws Exception { ProgressMonitor pm = new ProgressMonitor(this, "Initializing controls...", " ", 0, 2); @@ -229,7 +229,7 @@ protected TaskInfo doInBackground() throws Exception WindowBlocker blocker = null; try { if (retVal.blockWindow) { - blocker = new WindowBlocker(CreatureViewer.this); + blocker = new WindowBlocker(CreatureBrowser.this); blocker.setBlocked(true); } retVal.result = retVal.action.get(); diff --git a/src/org/infinity/resource/cre/viewer/CreatureControlModel.java b/src/org/infinity/resource/cre/browser/CreatureControlModel.java similarity index 98% rename from src/org/infinity/resource/cre/viewer/CreatureControlModel.java rename to src/org/infinity/resource/cre/browser/CreatureControlModel.java index d82266b62..6fa1a2025 100644 --- a/src/org/infinity/resource/cre/viewer/CreatureControlModel.java +++ b/src/org/infinity/resource/cre/browser/CreatureControlModel.java @@ -2,7 +2,7 @@ // Copyright (C) 2001 - 2021 Jon Olav Hauglid // See LICENSE.txt for license information -package org.infinity.resource.cre.viewer; +package org.infinity.resource.cre.browser; import java.util.ArrayList; import java.util.Iterator; @@ -11,14 +11,14 @@ import org.infinity.resource.Profile; import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.browser.ColorSelectionModel.ColorEntry; +import org.infinity.resource.cre.browser.CreatureAnimationModel.AnimateEntry; +import org.infinity.resource.cre.browser.CreatureSelectionModel.CreatureItem; +import org.infinity.resource.cre.browser.CreatureStatusModel.StatusEntry; import org.infinity.resource.cre.decoder.SpriteDecoder; import org.infinity.resource.cre.decoder.util.CreatureInfo; import org.infinity.resource.cre.decoder.util.ItemInfo; import org.infinity.resource.cre.decoder.util.ItemInfo.ItemPredicate; -import org.infinity.resource.cre.viewer.ColorSelectionModel.ColorEntry; -import org.infinity.resource.cre.viewer.CreatureStatusModel.StatusEntry; -import org.infinity.resource.cre.viewer.CreatureAnimationModel.AnimateEntry; -import org.infinity.resource.cre.viewer.CreatureSelectionModel.CreatureItem; import org.infinity.resource.key.ResourceEntry; /** diff --git a/src/org/infinity/resource/cre/viewer/CreatureControlPanel.java b/src/org/infinity/resource/cre/browser/CreatureControlPanel.java similarity index 96% rename from src/org/infinity/resource/cre/viewer/CreatureControlPanel.java rename to src/org/infinity/resource/cre/browser/CreatureControlPanel.java index 40d7ac2ed..a55757fc2 100644 --- a/src/org/infinity/resource/cre/viewer/CreatureControlPanel.java +++ b/src/org/infinity/resource/cre/browser/CreatureControlPanel.java @@ -2,7 +2,7 @@ // Copyright (C) 2001 - 2021 Jon Olav Hauglid // See LICENSE.txt for license information -package org.infinity.resource.cre.viewer; +package org.infinity.resource.cre.browser; import java.awt.BorderLayout; import java.awt.CardLayout; @@ -34,11 +34,11 @@ import org.infinity.gui.ViewerUtil; import org.infinity.icon.Icons; import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.browser.ColorSelectionModel.ColorEntry; +import org.infinity.resource.cre.browser.CreatureSelectionModel.CreatureItem; import org.infinity.resource.cre.decoder.MonsterPlanescapeDecoder; import org.infinity.resource.cre.decoder.SpriteDecoder; import org.infinity.resource.cre.decoder.util.ItemInfo; -import org.infinity.resource.cre.viewer.ColorSelectionModel.ColorEntry; -import org.infinity.resource.cre.viewer.CreatureSelectionModel.CreatureItem; import org.infinity.util.IdsMap; import org.infinity.util.IdsMapCache; import org.infinity.util.IdsMapEntry; @@ -58,7 +58,7 @@ public class CreatureControlPanel extends JPanel private final List> colorControls = new ArrayList<>(); private final Listeners listeners = new Listeners(); - private final CreatureViewer viewer; + private final CreatureBrowser browser; private CreatureControlModel model; private JComboBox cbCreSelection; @@ -73,15 +73,15 @@ public class CreatureControlPanel extends JPanel private JScrollPane scrollShown; private CardLayout layoutMain; - public CreatureControlPanel(CreatureViewer viewer) + public CreatureControlPanel(CreatureBrowser browser) { super(); - this.viewer = viewer; + this.browser = browser; init(); } - /** Returns the associated {@code CreatureViewer} instance. */ - public CreatureViewer getViewer() { return viewer; } + /** Returns the associated {@code CreatureBrowser} instance. */ + public CreatureBrowser getBrowser() { return browser; } public CreatureControlModel getControlModel() { return model; } @@ -136,11 +136,11 @@ public void applySettings() getControlModel().resetDecoder(cre); } catch (Exception e) { e.printStackTrace(); - getViewer().showErrorMessage(e.getMessage(), "Loading creature"); + getBrowser().showErrorMessage(e.getMessage(), "Loading creature"); } - getViewer().getSettingsPanel().reset(); - getViewer().getMediaPanel().reset(true); + getBrowser().getSettingsPanel().reset(); + getBrowser().getMediaPanel().reset(true); getControlModel().resetModified(); } @@ -412,7 +412,7 @@ private boolean confirmReset() { boolean retVal = true; if (getControlModel().canReset()) { - retVal = (JOptionPane.showConfirmDialog(getViewer(), + retVal = (JOptionPane.showConfirmDialog(getBrowser(), "Creature settings have been modified. Do you want to revert these changes?", "Revert changes", JOptionPane.YES_NO_OPTION, @@ -506,7 +506,7 @@ else if (e.getSource() == cbCreSelection) { updateToolTip(cbCreSelection); } catch (Exception ex) { ex.printStackTrace(); - getViewer().showErrorMessage(ex.getMessage(), "Creature selection"); + getBrowser().showErrorMessage(ex.getMessage(), "Creature selection"); } } else if (e.getSource() == cbCreAnimation) { diff --git a/src/org/infinity/resource/cre/viewer/CreatureSelectionModel.java b/src/org/infinity/resource/cre/browser/CreatureSelectionModel.java similarity index 99% rename from src/org/infinity/resource/cre/viewer/CreatureSelectionModel.java rename to src/org/infinity/resource/cre/browser/CreatureSelectionModel.java index 2b2e08b96..309bf940d 100644 --- a/src/org/infinity/resource/cre/viewer/CreatureSelectionModel.java +++ b/src/org/infinity/resource/cre/browser/CreatureSelectionModel.java @@ -2,7 +2,7 @@ // Copyright (C) 2001 - 2021 Jon Olav Hauglid // See LICENSE.txt for license information -package org.infinity.resource.cre.viewer; +package org.infinity.resource.cre.browser; import java.nio.ByteBuffer; import java.util.ArrayList; diff --git a/src/org/infinity/resource/cre/viewer/CreatureStatusModel.java b/src/org/infinity/resource/cre/browser/CreatureStatusModel.java similarity index 99% rename from src/org/infinity/resource/cre/viewer/CreatureStatusModel.java rename to src/org/infinity/resource/cre/browser/CreatureStatusModel.java index f9240b8d4..b5c72360c 100644 --- a/src/org/infinity/resource/cre/viewer/CreatureStatusModel.java +++ b/src/org/infinity/resource/cre/browser/CreatureStatusModel.java @@ -2,7 +2,7 @@ // Copyright (C) 2001 - 2021 Jon Olav Hauglid // See LICENSE.txt for license information -package org.infinity.resource.cre.viewer; +package org.infinity.resource.cre.browser; import java.util.ArrayList; import java.util.List; diff --git a/src/org/infinity/resource/cre/viewer/DecoderAttributesTableModel.java b/src/org/infinity/resource/cre/browser/DecoderAttributesTableModel.java similarity index 99% rename from src/org/infinity/resource/cre/viewer/DecoderAttributesTableModel.java rename to src/org/infinity/resource/cre/browser/DecoderAttributesTableModel.java index 4929de8cf..7829435fe 100644 --- a/src/org/infinity/resource/cre/viewer/DecoderAttributesTableModel.java +++ b/src/org/infinity/resource/cre/browser/DecoderAttributesTableModel.java @@ -2,7 +2,7 @@ // Copyright (C) 2001 - 2021 Jon Olav Hauglid // See LICENSE.txt for license information -package org.infinity.resource.cre.viewer; +package org.infinity.resource.cre.browser; import java.awt.Component; import java.util.Iterator; diff --git a/src/org/infinity/resource/cre/viewer/ItemSelectionModel.java b/src/org/infinity/resource/cre/browser/ItemSelectionModel.java similarity index 99% rename from src/org/infinity/resource/cre/viewer/ItemSelectionModel.java rename to src/org/infinity/resource/cre/browser/ItemSelectionModel.java index b1206ceea..e454697ed 100644 --- a/src/org/infinity/resource/cre/viewer/ItemSelectionModel.java +++ b/src/org/infinity/resource/cre/browser/ItemSelectionModel.java @@ -2,7 +2,7 @@ // Copyright (C) 2001 - 2021 Jon Olav Hauglid // See LICENSE.txt for license information -package org.infinity.resource.cre.viewer; +package org.infinity.resource.cre.browser; import java.util.ArrayList; import java.util.List; diff --git a/src/org/infinity/resource/cre/viewer/MediaPanel.java b/src/org/infinity/resource/cre/browser/MediaPanel.java similarity index 95% rename from src/org/infinity/resource/cre/viewer/MediaPanel.java rename to src/org/infinity/resource/cre/browser/MediaPanel.java index b5a0ed08c..a46921337 100644 --- a/src/org/infinity/resource/cre/viewer/MediaPanel.java +++ b/src/org/infinity/resource/cre/browser/MediaPanel.java @@ -2,7 +2,7 @@ // Copyright (C) 2001 - 2021 Jon Olav Hauglid // See LICENSE.txt for license information -package org.infinity.resource.cre.viewer; +package org.infinity.resource.cre.browser; import java.awt.BorderLayout; import java.awt.GridBagConstraints; @@ -34,10 +34,10 @@ import org.infinity.gui.ViewerUtil; import org.infinity.gui.WindowBlocker; +import org.infinity.resource.cre.browser.icon.Icons; import org.infinity.resource.cre.decoder.SpriteDecoder; import org.infinity.resource.cre.decoder.util.Direction; import org.infinity.resource.cre.decoder.util.Sequence; -import org.infinity.resource.cre.viewer.icon.Icons; import org.infinity.resource.graphics.PseudoBamDecoder.PseudoBamControl; /** @@ -54,7 +54,7 @@ public class MediaPanel extends JPanel // mapping of slider value to direction private final HashMap directionMap = new HashMap<>(); private final Listeners listeners = new Listeners(); - private final CreatureViewer viewer; + private final CreatureBrowser browser; private JButton bHome, bEnd, bStepBack, bStepForward, bPlay, bStop; private DefaultComboBoxModel modelSequences; @@ -67,15 +67,15 @@ public class MediaPanel extends JPanel private Timer timer; private int curFrame, curCycle; - public MediaPanel(CreatureViewer viewer) + public MediaPanel(CreatureBrowser browser) { super(); - this.viewer = Objects.requireNonNull(viewer); + this.browser = Objects.requireNonNull(browser); init(); } - /** Returns the associated {@code CreatureViewer} instance. */ - public CreatureViewer getViewer() { return viewer; } + /** Returns the associated {@code CreatureBrowser} instance. */ + public CreatureBrowser getBrowser() { return browser; } /** * Discards the current animation state and initializes a new animation. @@ -98,12 +98,12 @@ public void reset(boolean preserveState) curFrame = curCycle = -1; updateControls(); - SpriteDecoder decoder = getViewer().getDecoder(); + SpriteDecoder decoder = getBrowser().getDecoder(); if (decoder == null) { return; } - SettingsPanel settings = getViewer().getSettingsPanel(); + SettingsPanel settings = getBrowser().getSettingsPanel(); decoder.setAutoApplyChanges(false); decoder.setPaletteReplacementEnabled(settings.isPaletteReplacementEnabled()); decoder.setTintEnabled(settings.isTintEnabled()); @@ -120,7 +120,7 @@ public void reset(boolean preserveState) decoder.setAutoApplyChanges(true); setController(decoder.createControl()); - getViewer().getRenderPanel().setComposite(getViewer().getSettingsPanel().getComposite()); + getBrowser().getRenderPanel().setComposite(getBrowser().getSettingsPanel().getComposite()); initSequences(decoder, oldSequence); initDirection(decoder, oldDir); setCurrentFrame(oldFrameIdx); @@ -193,8 +193,8 @@ public void setCurrentFrame(int frameIdx) frameIdx = Math.max(0, Math.min(getController().cycleFrameCount() - 1, frameIdx)); getController().cycleSetFrameIndex(frameIdx); curFrame = getController().cycleGetFrameIndex(); - getViewer().getRenderPanel().setFrame(getController()); - getViewer().getRenderPanel().updateCanvas(); + getBrowser().getRenderPanel().setFrame(getController()); + getBrowser().getRenderPanel().updateCanvas(); updateLabels(); } } @@ -288,13 +288,13 @@ public void setSequence(Sequence seq) throws IllegalArgumentException public void loadSequence(Sequence seq) throws IllegalArgumentException { - SpriteDecoder decoder = getViewer().getDecoder(); - RenderPanel renderer = getViewer().getRenderPanel(); + SpriteDecoder decoder = getBrowser().getDecoder(); + RenderPanel renderer = getBrowser().getRenderPanel(); Direction oldDir = getDirection(getCurrentDirection()); boolean isRunning = isRunning(); pause(); try { - if (!getViewer().getDecoder().loadSequence(seq)) { + if (!getBrowser().getDecoder().loadSequence(seq)) { throw new Exception(); } } catch (Exception e) { @@ -765,7 +765,7 @@ public void stateChanged(ChangeEvent e) if (e.getSource() == slDirection) { Direction dir = getDirection(slDirection.getValue()); lDirection.setText(dir.toString()); - int cycle = getViewer().getDecoder().getDirectionMap().getOrDefault(dir, -1).intValue(); + int cycle = getBrowser().getDecoder().getDirectionMap().getOrDefault(dir, -1).intValue(); if (cycle >= 0) { setCurrentCycle(cycle); } @@ -784,13 +784,13 @@ public void itemStateChanged(ItemEvent e) e.getItem() instanceof Sequence) { final Sequence seq = (Sequence)e.getItem(); try { - WindowBlocker.blockWindow(getViewer(), true); + WindowBlocker.blockWindow(getBrowser(), true); loadSequence(seq); } catch (Exception ex) { ex.printStackTrace(); - getViewer().showErrorMessage(ex.getMessage(), "Sequence selection"); + getBrowser().showErrorMessage(ex.getMessage(), "Sequence selection"); } finally { - WindowBlocker.blockWindow(getViewer(), false); + WindowBlocker.blockWindow(getBrowser(), false); } } } diff --git a/src/org/infinity/resource/cre/viewer/RenderPanel.java b/src/org/infinity/resource/cre/browser/RenderPanel.java similarity index 98% rename from src/org/infinity/resource/cre/viewer/RenderPanel.java rename to src/org/infinity/resource/cre/browser/RenderPanel.java index a894d930d..f8c0700ef 100644 --- a/src/org/infinity/resource/cre/viewer/RenderPanel.java +++ b/src/org/infinity/resource/cre/browser/RenderPanel.java @@ -2,7 +2,7 @@ // Copyright (C) 2001 - 2021 Jon Olav Hauglid // See LICENSE.txt for license information -package org.infinity.resource.cre.viewer; +package org.infinity.resource.cre.browser; import java.awt.AlphaComposite; import java.awt.BorderLayout; @@ -41,7 +41,7 @@ public class RenderPanel extends JPanel private static final float POS_REL_Y = 2.0f / 3.0f; private final Listeners listeners = new Listeners(); - private final CreatureViewer viewer; + private final CreatureBrowser browser; // storage for scroll pane view size to track view size changes private Dimension viewSize = new Dimension(); @@ -62,18 +62,15 @@ public class RenderPanel extends JPanel // storage for background image content that is overwritten by the animation frame private Image frameBackground; - public RenderPanel(CreatureViewer viewer) + public RenderPanel(CreatureBrowser browser) { super(); - this.viewer = Objects.requireNonNull(viewer); + this.browser = Objects.requireNonNull(browser); init(); } - /** Returns the associated {@code CreatureViewer} instance. */ - public CreatureViewer getViewer() - { - return viewer; - } + /** Returns the associated {@code CreatureBrowser} instance. */ + public CreatureBrowser getBrowser() { return browser; } /** Returns the current background color of the panel. */ public Color getBackgroundColor() diff --git a/src/org/infinity/resource/cre/viewer/SettingsPanel.java b/src/org/infinity/resource/cre/browser/SettingsPanel.java similarity index 92% rename from src/org/infinity/resource/cre/viewer/SettingsPanel.java rename to src/org/infinity/resource/cre/browser/SettingsPanel.java index 73cc20caa..4444ac0fe 100644 --- a/src/org/infinity/resource/cre/viewer/SettingsPanel.java +++ b/src/org/infinity/resource/cre/browser/SettingsPanel.java @@ -2,7 +2,7 @@ // Copyright (C) 2001 - 2021 Jon Olav Hauglid // See LICENSE.txt for license information -package org.infinity.resource.cre.viewer; +package org.infinity.resource.cre.browser; import java.awt.AlphaComposite; import java.awt.BorderLayout; @@ -31,9 +31,9 @@ import org.infinity.gui.ViewerUtil; import org.infinity.resource.Profile; -import org.infinity.resource.cre.viewer.bg.Backgrounds; -import org.infinity.resource.cre.viewer.bg.Backgrounds.BackgroundInfo; -import org.infinity.resource.cre.viewer.icon.Icons; +import org.infinity.resource.cre.browser.bg.Backgrounds; +import org.infinity.resource.cre.browser.bg.Backgrounds.BackgroundInfo; +import org.infinity.resource.cre.browser.icon.Icons; import org.infinity.util.tuples.Monuple; /** @@ -103,7 +103,7 @@ public class SettingsPanel extends JPanel } private final Listeners listeners = new Listeners(); - private final CreatureViewer viewer; + private final CreatureBrowser browser; private JComboBox> cbZoom; private JComboBox> cbFrameRate; @@ -123,15 +123,15 @@ public static List getBackgrounds(Profile.Game game) .collect(Collectors.toList()); } - public SettingsPanel(CreatureViewer viewer) + public SettingsPanel(CreatureBrowser browser) { super(); - this.viewer = Objects.requireNonNull(viewer); + this.browser = Objects.requireNonNull(browser); init(); } - /** Returns the associated {@code CreatureViewer} instance. */ - public CreatureViewer getViewer() { return viewer; } + /** Returns the associated {@code CreatureBrowser} instance. */ + public CreatureBrowser getBrowser() { return browser; } /** Discards and reloads the current settings and attributes list. */ public void reset() @@ -157,8 +157,8 @@ private void setZoomIndex(int index) if (index >= 0 && index < cbZoom.getModel().getSize()) { indexZoom = index; cbZoom.setSelectedIndex(indexZoom); - getViewer().getRenderPanel().setZoom((float)getZoom() / 100.0f); - getViewer().getRenderPanel().updateCanvas(); + getBrowser().getRenderPanel().setZoom((float)getZoom() / 100.0f); + getBrowser().getRenderPanel().updateCanvas(); } else { throw new IndexOutOfBoundsException(); } @@ -182,7 +182,7 @@ private void setFrameRateIndex(int index) if (index >= 0 && index < cbFrameRate.getModel().getSize()) { indexFrameRate = index; cbFrameRate.setSelectedIndex(indexFrameRate); - getViewer().getMediaPanel().setFrameRate(getFrameRate()); + getBrowser().getMediaPanel().setFrameRate(getFrameRate()); } else { throw new IndexOutOfBoundsException(); } @@ -223,10 +223,10 @@ private void applyBackgroundInfo() { Backgrounds.BackgroundInfo info = getBackgroundInfo(); if (info != null) { - getViewer().getRenderPanel().setBackgroundColor(info.getColor()); - getViewer().getRenderPanel().setBackgroundImage(info.getImage()); - getViewer().getRenderPanel().setBackgroundCenter(info.getCenter()); - getViewer().getRenderPanel().updateCanvas(); + getBrowser().getRenderPanel().setBackgroundColor(info.getColor()); + getBrowser().getRenderPanel().setBackgroundImage(info.getImage()); + getBrowser().getRenderPanel().setBackgroundCenter(info.getCenter()); + getBrowser().getRenderPanel().updateCanvas(); } } @@ -238,7 +238,7 @@ private void setFilteringEnabled(boolean b) if (isFiltering != b) { isFiltering = b; cbFiltering.setSelected(isFiltering); - getViewer().getRenderPanel().setFilterEnabled(isFiltering); + getBrowser().getRenderPanel().setFilterEnabled(isFiltering); } } @@ -250,7 +250,7 @@ private void setBlendingEnabled(boolean b) if (isBlending != b) { isBlending = b; cbBlending.setSelected(isBlending); - getViewer().getRenderPanel().setComposite(getComposite()); + getBrowser().getRenderPanel().setComposite(getComposite()); } } @@ -262,7 +262,7 @@ private void setTranslucencyEnabled(boolean b) if (isTranslucent != b) { isTranslucent = b; cbTranslucent.setSelected(isTranslucent); - getViewer().getMediaPanel().reset(true); + getBrowser().getMediaPanel().reset(true); } } @@ -274,7 +274,7 @@ private void setSelectionCircleEnabled(boolean b) if (isSelectionCircle != b) { isSelectionCircle = b; cbSelectionCircle.setSelected(isSelectionCircle); - getViewer().getMediaPanel().reset(true); + getBrowser().getMediaPanel().reset(true); } } @@ -286,7 +286,7 @@ private void setOrnateSelectionCircle(boolean b) if (isOrnateSelectionCircle != b) { isOrnateSelectionCircle = b; cbOrnateSelectionCircle.setSelected(isOrnateSelectionCircle); - getViewer().getMediaPanel().reset(true); + getBrowser().getMediaPanel().reset(true); } } @@ -298,7 +298,7 @@ private void setPersonalSpaceEnabled(boolean b) if (isPersonalSpace != b) { isPersonalSpace = b; cbPersonalSpace.setSelected(isPersonalSpace); - getViewer().getMediaPanel().reset(true); + getBrowser().getMediaPanel().reset(true); } } @@ -310,7 +310,7 @@ private void setTintEnabled(boolean b) if (isTintEnabled != b) { isTintEnabled = b; cbTintEnabled.setSelected(isTintEnabled); - getViewer().getMediaPanel().reset(true); + getBrowser().getMediaPanel().reset(true); } } @@ -322,7 +322,7 @@ private void setPaletteReplacementEnabled(boolean b) if (isPaletteReplacementEnabled != b) { isPaletteReplacementEnabled = b; cbPaletteReplacementEnabled.setSelected(isPaletteReplacementEnabled); - getViewer().getMediaPanel().reset(true); + getBrowser().getMediaPanel().reset(true); } } @@ -334,7 +334,7 @@ private void setAvatarVisible(boolean b) if (isShowAvatar != b) { isShowAvatar = b; cbShowAvatar.setSelected(isShowAvatar); - getViewer().getMediaPanel().reset(true); + getBrowser().getMediaPanel().reset(true); } } @@ -346,7 +346,7 @@ private void setHelmetVisible(boolean b) if (isShowHelmet != b) { isShowHelmet = b; cbShowHelmet.setSelected(isShowHelmet); - getViewer().getMediaPanel().reset(true); + getBrowser().getMediaPanel().reset(true); } } @@ -358,7 +358,7 @@ private void setShieldVisible(boolean b) if (isShowShield != b) { isShowShield = b; cbShowShield.setSelected(isShowShield); - getViewer().getMediaPanel().reset(true); + getBrowser().getMediaPanel().reset(true); } } @@ -370,7 +370,7 @@ private void setWeaponVisible(boolean b) if (isShowWeapon != b) { isShowWeapon = b; cbShowWeapon.setSelected(isShowWeapon); - getViewer().getMediaPanel().reset(true); + getBrowser().getMediaPanel().reset(true); } } @@ -382,7 +382,7 @@ private void setOverlayBordersVisible(boolean b) if (isShowBorders != b) { isShowBorders = b; cbShowBorders.setSelected(isShowBorders); - getViewer().getMediaPanel().reset(true); + getBrowser().getMediaPanel().reset(true); } } @@ -390,8 +390,8 @@ private void setOverlayBordersVisible(boolean b) public Composite getComposite() { Composite retVal = AlphaComposite.SrcOver; - if (getViewer().getDecoder() != null && isBlendingEnabled()) { - retVal = getViewer().getDecoder().getComposite(); + if (getBrowser().getDecoder() != null && isBlendingEnabled()) { + retVal = getBrowser().getDecoder().getComposite(); } return retVal; } @@ -555,7 +555,7 @@ private void init() // attributes table panel - panelAttributes = new AttributesPanel(getViewer()); + panelAttributes = new AttributesPanel(getBrowser()); // combining panels @@ -603,7 +603,7 @@ private Color getCustomColor(Color defColor) JDialog dlg = null; try { // Returns color value without alpha component - dlg = JColorChooser.createDialog(getViewer(), "Choose background color", true, cc, + dlg = JColorChooser.createDialog(getBrowser(), "Choose background color", true, cc, evt -> retVal.setValue0(new Color(cc.getColor().getRGB(), false)), null); dlg.setVisible(true); } finally { @@ -629,7 +629,7 @@ public Listeners() public void actionPerformed(ActionEvent e) { if (e.getSource() == bCenter) { - getViewer().getRenderPanel().centerOnSprite(); + getBrowser().getRenderPanel().centerOnSprite(); } else if (e.getSource() == cbZoom) { setZoomIndex(cbZoom.getSelectedIndex()); diff --git a/src/org/infinity/resource/cre/viewer/bg/Backgrounds.java b/src/org/infinity/resource/cre/browser/bg/Backgrounds.java similarity index 99% rename from src/org/infinity/resource/cre/viewer/bg/Backgrounds.java rename to src/org/infinity/resource/cre/browser/bg/Backgrounds.java index 2fb4f4ce3..717473dcc 100644 --- a/src/org/infinity/resource/cre/viewer/bg/Backgrounds.java +++ b/src/org/infinity/resource/cre/browser/bg/Backgrounds.java @@ -2,7 +2,7 @@ // Copyright (C) 2001 - 2021 Jon Olav Hauglid // See LICENSE.txt for license information -package org.infinity.resource.cre.viewer.bg; +package org.infinity.resource.cre.browser.bg; import java.awt.Color; import java.awt.Image; diff --git a/src/org/infinity/resource/cre/viewer/bg/bg_cave.jpg b/src/org/infinity/resource/cre/browser/bg/bg_cave.jpg similarity index 100% rename from src/org/infinity/resource/cre/viewer/bg/bg_cave.jpg rename to src/org/infinity/resource/cre/browser/bg/bg_cave.jpg diff --git a/src/org/infinity/resource/cre/viewer/bg/bg_wilderness.jpg b/src/org/infinity/resource/cre/browser/bg/bg_wilderness.jpg similarity index 100% rename from src/org/infinity/resource/cre/viewer/bg/bg_wilderness.jpg rename to src/org/infinity/resource/cre/browser/bg/bg_wilderness.jpg diff --git a/src/org/infinity/resource/cre/viewer/bg/iwd_wilderness.jpg b/src/org/infinity/resource/cre/browser/bg/iwd_wilderness.jpg similarity index 100% rename from src/org/infinity/resource/cre/viewer/bg/iwd_wilderness.jpg rename to src/org/infinity/resource/cre/browser/bg/iwd_wilderness.jpg diff --git a/src/org/infinity/resource/cre/viewer/bg/pst_city.jpg b/src/org/infinity/resource/cre/browser/bg/pst_city.jpg similarity index 100% rename from src/org/infinity/resource/cre/viewer/bg/pst_city.jpg rename to src/org/infinity/resource/cre/browser/bg/pst_city.jpg diff --git a/src/org/infinity/resource/cre/viewer/bg/pst_dungeon.jpg b/src/org/infinity/resource/cre/browser/bg/pst_dungeon.jpg similarity index 100% rename from src/org/infinity/resource/cre/viewer/bg/pst_dungeon.jpg rename to src/org/infinity/resource/cre/browser/bg/pst_dungeon.jpg diff --git a/src/org/infinity/resource/cre/viewer/bg/sod_city_night.jpg b/src/org/infinity/resource/cre/browser/bg/sod_city_night.jpg similarity index 100% rename from src/org/infinity/resource/cre/viewer/bg/sod_city_night.jpg rename to src/org/infinity/resource/cre/browser/bg/sod_city_night.jpg diff --git a/src/org/infinity/resource/cre/viewer/icon/Icons.java b/src/org/infinity/resource/cre/browser/icon/Icons.java similarity index 98% rename from src/org/infinity/resource/cre/viewer/icon/Icons.java rename to src/org/infinity/resource/cre/browser/icon/Icons.java index d4a7152c7..968ef34cb 100644 --- a/src/org/infinity/resource/cre/viewer/icon/Icons.java +++ b/src/org/infinity/resource/cre/browser/icon/Icons.java @@ -2,7 +2,7 @@ // Copyright (C) 2001 - 2005 Jon Olav Hauglid // See LICENSE.txt for license information -package org.infinity.resource.cre.viewer.icon; +package org.infinity.resource.cre.browser.icon; import java.awt.Image; import java.net.URL; diff --git a/src/org/infinity/resource/cre/viewer/icon/btn_center.png b/src/org/infinity/resource/cre/browser/icon/btn_center.png similarity index 100% rename from src/org/infinity/resource/cre/viewer/icon/btn_center.png rename to src/org/infinity/resource/cre/browser/icon/btn_center.png diff --git a/src/org/infinity/resource/cre/viewer/icon/btn_end.png b/src/org/infinity/resource/cre/browser/icon/btn_end.png similarity index 100% rename from src/org/infinity/resource/cre/viewer/icon/btn_end.png rename to src/org/infinity/resource/cre/browser/icon/btn_end.png diff --git a/src/org/infinity/resource/cre/viewer/icon/btn_home.png b/src/org/infinity/resource/cre/browser/icon/btn_home.png similarity index 100% rename from src/org/infinity/resource/cre/viewer/icon/btn_home.png rename to src/org/infinity/resource/cre/browser/icon/btn_home.png diff --git a/src/org/infinity/resource/cre/viewer/icon/btn_pause.png b/src/org/infinity/resource/cre/browser/icon/btn_pause.png similarity index 100% rename from src/org/infinity/resource/cre/viewer/icon/btn_pause.png rename to src/org/infinity/resource/cre/browser/icon/btn_pause.png diff --git a/src/org/infinity/resource/cre/viewer/icon/btn_play.png b/src/org/infinity/resource/cre/browser/icon/btn_play.png similarity index 100% rename from src/org/infinity/resource/cre/viewer/icon/btn_play.png rename to src/org/infinity/resource/cre/browser/icon/btn_play.png diff --git a/src/org/infinity/resource/cre/viewer/icon/btn_resume.png b/src/org/infinity/resource/cre/browser/icon/btn_resume.png similarity index 100% rename from src/org/infinity/resource/cre/viewer/icon/btn_resume.png rename to src/org/infinity/resource/cre/browser/icon/btn_resume.png diff --git a/src/org/infinity/resource/cre/viewer/icon/btn_step_back.png b/src/org/infinity/resource/cre/browser/icon/btn_step_back.png similarity index 100% rename from src/org/infinity/resource/cre/viewer/icon/btn_step_back.png rename to src/org/infinity/resource/cre/browser/icon/btn_step_back.png diff --git a/src/org/infinity/resource/cre/viewer/icon/btn_step_forward.png b/src/org/infinity/resource/cre/browser/icon/btn_step_forward.png similarity index 100% rename from src/org/infinity/resource/cre/viewer/icon/btn_step_forward.png rename to src/org/infinity/resource/cre/browser/icon/btn_step_forward.png diff --git a/src/org/infinity/resource/cre/viewer/icon/btn_stop.png b/src/org/infinity/resource/cre/browser/icon/btn_stop.png similarity index 100% rename from src/org/infinity/resource/cre/viewer/icon/btn_stop.png rename to src/org/infinity/resource/cre/browser/icon/btn_stop.png diff --git a/src/org/infinity/resource/cre/viewer/icon/circle_blue.png b/src/org/infinity/resource/cre/browser/icon/circle_blue.png similarity index 100% rename from src/org/infinity/resource/cre/viewer/icon/circle_blue.png rename to src/org/infinity/resource/cre/browser/icon/circle_blue.png diff --git a/src/org/infinity/resource/cre/viewer/icon/circle_green.png b/src/org/infinity/resource/cre/browser/icon/circle_green.png similarity index 100% rename from src/org/infinity/resource/cre/viewer/icon/circle_green.png rename to src/org/infinity/resource/cre/browser/icon/circle_green.png diff --git a/src/org/infinity/resource/cre/viewer/icon/circle_red.png b/src/org/infinity/resource/cre/browser/icon/circle_red.png similarity index 100% rename from src/org/infinity/resource/cre/viewer/icon/circle_red.png rename to src/org/infinity/resource/cre/browser/icon/circle_red.png diff --git a/src/org/infinity/resource/cre/viewer/icon/circle_yellow.png b/src/org/infinity/resource/cre/browser/icon/circle_yellow.png similarity index 100% rename from src/org/infinity/resource/cre/viewer/icon/circle_yellow.png rename to src/org/infinity/resource/cre/browser/icon/circle_yellow.png diff --git a/src/org/infinity/resource/cre/decoder/util/SpriteUtils.java b/src/org/infinity/resource/cre/decoder/util/SpriteUtils.java index d36d960c1..4706122a7 100644 --- a/src/org/infinity/resource/cre/decoder/util/SpriteUtils.java +++ b/src/org/infinity/resource/cre/decoder/util/SpriteUtils.java @@ -24,6 +24,7 @@ import org.infinity.resource.Profile; import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.browser.icon.Icons; import org.infinity.resource.cre.decoder.AmbientDecoder; import org.infinity.resource.cre.decoder.AmbientStaticDecoder; import org.infinity.resource.cre.decoder.CharacterDecoder; @@ -46,7 +47,6 @@ import org.infinity.resource.cre.decoder.SpriteDecoder; import org.infinity.resource.cre.decoder.TownStaticDecoder; import org.infinity.resource.cre.decoder.tables.SpriteTables; -import org.infinity.resource.cre.viewer.icon.Icons; import org.infinity.resource.graphics.BamV1Decoder; import org.infinity.resource.graphics.ColorConvert; import org.infinity.resource.graphics.GraphicsResource; From 40bba750e684619d33882dd1a9bb82e6e1956c31 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Wed, 14 Apr 2021 16:07:32 +0200 Subject: [PATCH 128/158] SpriteDecoder: Add support for blur effect Added option to turn on/off blur effect in animation browser. --- .../resource/cre/browser/MediaPanel.java | 1 + .../resource/cre/browser/SettingsPanel.java | 35 ++++- .../resource/cre/decoder/EffectDecoder.java | 6 +- .../cre/decoder/MonsterPlanescapeDecoder.java | 6 +- .../resource/cre/decoder/SpriteDecoder.java | 141 +++++++++++++++--- .../resource/cre/decoder/util/ColorInfo.java | 4 + .../cre/decoder/util/CreatureInfo.java | 37 +++-- .../resource/cre/decoder/util/FrameInfo.java | 16 +- .../resource/cre/decoder/util/ItemInfo.java | 7 + .../cre/decoder/util/SpriteUtils.java | 8 +- 10 files changed, 212 insertions(+), 49 deletions(-) diff --git a/src/org/infinity/resource/cre/browser/MediaPanel.java b/src/org/infinity/resource/cre/browser/MediaPanel.java index a46921337..49f53694c 100644 --- a/src/org/infinity/resource/cre/browser/MediaPanel.java +++ b/src/org/infinity/resource/cre/browser/MediaPanel.java @@ -107,6 +107,7 @@ public void reset(boolean preserveState) decoder.setAutoApplyChanges(false); decoder.setPaletteReplacementEnabled(settings.isPaletteReplacementEnabled()); decoder.setTintEnabled(settings.isTintEnabled()); + decoder.setBlurEnabled(settings.isBlurEnabled()); decoder.setTranslucencyEnabled(settings.isTranslucencyEnabled()); decoder.setSelectionCircleEnabled(settings.isSelectionCircleEnabled()); decoder.setSelectionCircleBitmap(settings.isOrnateSelectionCircle()); diff --git a/src/org/infinity/resource/cre/browser/SettingsPanel.java b/src/org/infinity/resource/cre/browser/SettingsPanel.java index 4444ac0fe..b865bc6f1 100644 --- a/src/org/infinity/resource/cre/browser/SettingsPanel.java +++ b/src/org/infinity/resource/cre/browser/SettingsPanel.java @@ -80,8 +80,8 @@ public class SettingsPanel extends JPanel private static int indexZoom, indexFrameRate, indexBackground; private static boolean isFiltering, isBlending, isTranslucent, isSelectionCircle, isOrnateSelectionCircle, isPersonalSpace, - isTintEnabled, isPaletteReplacementEnabled, isShowAvatar, isShowHelmet, isShowShield, isShowWeapon, - isShowBorders; + isTintEnabled, isBlurEnabled, isPaletteReplacementEnabled, isShowBorders, + isShowAvatar, isShowHelmet, isShowShield, isShowWeapon; static { indexZoom = 1; // 100 % (original) @@ -99,6 +99,7 @@ public class SettingsPanel extends JPanel isShowWeapon = true; isShowBorders = false; isTintEnabled = true; + isBlurEnabled = true; isPaletteReplacementEnabled = true; } @@ -110,8 +111,8 @@ public class SettingsPanel extends JPanel private JComboBox cbBackground; private JButton bCenter; private JCheckBox cbFiltering, cbBlending, cbTranslucent, cbSelectionCircle, cbOrnateSelectionCircle, cbPersonalSpace, - cbTintEnabled, cbPaletteReplacementEnabled, cbShowAvatar, cbShowHelmet, cbShowShield, cbShowWeapon, - cbShowBorders; + cbTintEnabled, cbBlurEnabled, cbPaletteReplacementEnabled, cbShowBorders, + cbShowAvatar, cbShowHelmet, cbShowShield, cbShowWeapon; private AttributesPanel panelAttributes; /** Returns a list of background info instances available for the specified game. */ @@ -314,6 +315,18 @@ private void setTintEnabled(boolean b) } } + /** Returns whether blur effect (opcode 66) is enabled. */ + public boolean isBlurEnabled() { return isBlurEnabled; } + + private void setBlurEnabled(boolean b) + { + if (isBlurEnabled != b) { + isBlurEnabled = b; + cbBlurEnabled.setSelected(isBlurEnabled); + getBrowser().getMediaPanel().reset(true); + } + } + /** Returns whether palette replacement (full palette or false colors) is enabled. */ public boolean isPaletteReplacementEnabled() { return isPaletteReplacementEnabled; } @@ -466,10 +479,13 @@ private void init() cbTranslucent.setToolTipText("Affects only creature animations with translucency effect (e.g. ghosts or air elementals)."); cbTranslucent.addActionListener(listeners); - cbTintEnabled = new JCheckBox("Enable tint", isTintEnabled); + cbTintEnabled = new JCheckBox("Enable tint effect", isTintEnabled); cbTintEnabled.setToolTipText("Includes color modifications defined by effect opcodes 8, 51 and 52."); cbTintEnabled.addActionListener(listeners); + cbBlurEnabled = new JCheckBox("Enable blur effect", isBlurEnabled); + cbBlurEnabled.addActionListener(listeners); + cbPaletteReplacementEnabled = new JCheckBox("Enable palette replacement", isPaletteReplacementEnabled); cbPaletteReplacementEnabled.setToolTipText("Enable full palette or false color palette replacement."); cbPaletteReplacementEnabled.addActionListener(listeners); @@ -541,17 +557,17 @@ private void init() c = ViewerUtil.setGBC(c, 0, 5, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 0, 0, 0), 0, 0); - panel2.add(cbTintEnabled, c); + panel2.add(cbBlurEnabled, c); c = ViewerUtil.setGBC(c, 1, 5, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 16, 0, 0), 0, 0); panel2.add(cbShowBorders, c); c = ViewerUtil.setGBC(c, 0, 6, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 0, 0, 0), 0, 0); - panel2.add(cbPaletteReplacementEnabled, c); + panel2.add(cbTintEnabled, c); c = ViewerUtil.setGBC(c, 1, 6, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 16, 0, 0), 0, 0); - panel2.add(new JPanel(), c); + panel2.add(cbPaletteReplacementEnabled, c); // attributes table panel @@ -652,6 +668,9 @@ else if (e.getSource() == cbTranslucent) { else if (e.getSource() == cbTintEnabled) { setTintEnabled(cbTintEnabled.isSelected()); } + else if (e.getSource() == cbBlurEnabled) { + setBlurEnabled(cbBlurEnabled.isSelected()); + } else if (e.getSource() == cbPaletteReplacementEnabled) { setPaletteReplacementEnabled(cbPaletteReplacementEnabled.isSelected()); } diff --git a/src/org/infinity/resource/cre/decoder/EffectDecoder.java b/src/org/infinity/resource/cre/decoder/EffectDecoder.java index bf914fc39..27c1cb339 100644 --- a/src/org/infinity/resource/cre/decoder/EffectDecoder.java +++ b/src/org/infinity/resource/cre/decoder/EffectDecoder.java @@ -76,8 +76,10 @@ public void accept(BamV1Control control, SegmentDef sd) applyColorTint(control, sd); } - if (isTranslucencyEnabled() && isTranslucent()) { - applyTranslucency(control); + if ((isTranslucencyEnabled() && isTranslucent()) || + (isBlurEnabled() && isBlurred())) { + int minVal = (isBlurEnabled() && isBlurred()) ? 64 : 255; + applyTranslucency(control, minVal); } } }; diff --git a/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java index 293285cac..84659de68 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java @@ -78,8 +78,10 @@ public void accept(BamV1Control control, SegmentDef sd) applyFalseColors(control, sd); } - if (isTranslucent()) { - applyTranslucency(control); + if ((isTranslucencyEnabled() && isTranslucent()) || + (isBlurEnabled() && isBlurred())) { + int minVal = (isBlurEnabled() && isBlurred()) ? 64 : 255; + applyTranslucency(control, minVal); } } }; diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index 414435f7d..12704b6fb 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -125,8 +125,10 @@ public void accept(BamV1Control control, SegmentDef sd) applyColorEffects(control, sd); } - if (isTranslucencyEnabled() && isTranslucent()) { - applyTranslucency(control); + if ((isTranslucencyEnabled() && isTranslucent()) || + (isBlurEnabled() && isBlurred())) { + int minVal = (isBlurEnabled() && isBlurred()) ? 64 : 255; + applyTranslucency(control, minVal); } } }; @@ -188,6 +190,7 @@ public void accept(DirDef dd, int frameIdx) private boolean transparentShadow; private boolean translucencyEnabled; private boolean tintEnabled; + private boolean blurEnabled; private boolean paletteReplacementEnabled; private boolean renderSpriteAvatar; private boolean renderSpriteWeapon; @@ -281,6 +284,7 @@ protected SpriteDecoder(AnimationInfo.Type type, CreResource cre) throws Excepti this.transparentShadow = true; this.translucencyEnabled = true; this.tintEnabled = true; + this.blurEnabled = true; this.paletteReplacementEnabled = true; this.renderSpriteAvatar = true; this.renderSpriteWeapon = true; @@ -640,6 +644,22 @@ public void setTintEnabled(boolean b) } } + /** Returns whether blur effect is applied to the creature animation. */ + public boolean isBlurEnabled() + { + return blurEnabled; + } + + /** Sets whether blur effect is applied to the creature animation. */ + public void setBlurEnabled(boolean b) + { + if (blurEnabled != b) { + blurEnabled = b; + SpriteUtils.clearBamCache(); + spriteChanged(); + } + } + /** Returns whether any kind of palette replacement (full palette or false colors) is enabled. */ public boolean isPaletteReplacementEnabled() { @@ -838,6 +858,12 @@ protected void setTranslucent(boolean b) setAttribute(KEY_TRANSLUCENT, b); } + /** Returns whether the blur effect is active for the creature animation. */ + public boolean isBlurred() + { + return getCreatureInfo().isBlurEffect(); + } + /** Call this method whenever the visibility of the selection circle has been changed. */ public void selectionCircleChanged() { @@ -1110,6 +1136,58 @@ protected void createSequence(Sequence seq, Direction[] directions) throws Excep createAnimation(sd, Arrays.asList(directions), FN_BEFORE_SRC_BAM, FN_BEFORE_SRC_FRAME, FN_AFTER_SRC_FRAME, FN_AFTER_DST_FRAME); } + /** Returns the number of sprite instances to render by the current blur state of the creature animation. */ + protected int getBlurInstanceCount() + { + return isBlurred() ? 4 : 1; + } + + protected Point getBlurInstanceShift(Point pt, Direction dir, int idx) + { + Point retVal = (pt != null) ? pt : new Point(); + if (isBlurred()) { + int dist = Math.max(0, idx) * 9; // distance between images: 9 pixels + // shift position depends on sprite direction and specified index + int dirValue = ((dir != null) ? dir.getValue() : 0) & ~1; // truncate to nearest semi-cardinal direction + switch (dirValue) { + case 2: // SW + retVal.x = dist / 2; + retVal.y = -dist / 2; + break; + case 4: // W + retVal.x = dist; + retVal.y = 0; + break; + case 6: // NW + retVal.x = dist / 2; + retVal.y = dist / 2; + break; + case 8: // N + retVal.x = 0; + retVal.y = dist; + break; + case 10: // NE + retVal.x = -dist / 2; + retVal.y = dist / 2; + break; + case 12: // E + retVal.x = -dist; + retVal.y = 0; + break; + case 14: // SE + retVal.x = -dist / 2; + retVal.y = -dist / 2; + break; + default: // S + retVal.x = 0; + retVal.y = -dist; + } + } else { + retVal.x = retVal.y = 0; + } + return retVal; + } + protected void createAnimation(SeqDef definition, List directions, BeforeSourceBam beforeSrcBam, BeforeSourceFrame beforeSrcFrame, @@ -1144,27 +1222,37 @@ protected void createAnimation(SeqDef definition, List directions, final ArrayList frameInfo = new ArrayList<>(); for (int frame = 0; frame < frameCount; frame++) { frameInfo.clear(); - for (final SegmentDef sd : cd.getCycles()) { - // checking visibility of sprite types - boolean skip = (sd.getSpriteType() == SegmentDef.SpriteType.AVATAR) && !getRenderAvatar(); - skip |= (sd.getSpriteType() == SegmentDef.SpriteType.WEAPON) && !getRenderWeapon(); - skip |= (sd.getSpriteType() == SegmentDef.SpriteType.SHIELD) && !getRenderShield(); - skip |= (sd.getSpriteType() == SegmentDef.SpriteType.HELMET) && !getRenderHelmet(); - if (skip) { - continue; + int copyCount = isBlurEnabled() ? getBlurInstanceCount() : 1; + Point centerShift = new Point(); + for (int copyIdx = copyCount - 1; copyIdx >= 0; copyIdx--) { + if (isBlurEnabled()) { + centerShift = getBlurInstanceShift(centerShift, dd.getDirection(), copyIdx); } + for (final SegmentDef sd : cd.getCycles()) { + // checking visibility of sprite types + boolean skip = (sd.getSpriteType() == SegmentDef.SpriteType.AVATAR) && !getRenderAvatar(); + skip |= (sd.getSpriteType() == SegmentDef.SpriteType.WEAPON) && !getRenderWeapon(); + skip |= (sd.getSpriteType() == SegmentDef.SpriteType.SHIELD) && !getRenderShield(); + skip |= (sd.getSpriteType() == SegmentDef.SpriteType.HELMET) && !getRenderHelmet(); + if (skip) { + continue; + } - entry = sd.getEntry(); - srcCtrl = Objects.requireNonNull(SpriteUtils.loadBamController(entry)); - srcCtrl.cycleSet(sd.getCycleIndex()); + entry = sd.getEntry(); + srcCtrl = Objects.requireNonNull(SpriteUtils.loadBamController(entry)); + srcCtrl.cycleSet(sd.getCycleIndex()); - if (sd.getCurrentFrame() >= 0) { - if (beforeSrcBam != null && !bamControlSet.contains(srcCtrl)) { - bamControlSet.add(srcCtrl); - beforeSrcBam.accept(srcCtrl, sd); + if (sd.getCurrentFrame() >= 0) { + if (beforeSrcBam != null && !bamControlSet.contains(srcCtrl)) { + bamControlSet.add(srcCtrl); + beforeSrcBam.accept(srcCtrl, sd); + } + frameInfo.add(new FrameInfo(srcCtrl, sd, centerShift)); } - frameInfo.add(new FrameInfo(srcCtrl, sd)); } + } + + for (final SegmentDef sd : cd.getCycles()) { sd.advance(); } @@ -1243,8 +1331,8 @@ protected int createFrame(FrameInfo[] sourceFrames, BeforeSourceFrame beforeSrcF srcImage = beforeSrcFrame.apply(fi.getSegmentDefinition(), srcImage, g); } FrameEntry entry = ctrl.getDecoder().getFrameInfo(ctrl.cycleGetFrameIndexAbsolute()); - int x = -rect.x - entry.getCenterX(); - int y = -rect.y - entry.getCenterY(); + int x = -rect.x - entry.getCenterX() + fi.getCenterShift().x; + int y = -rect.y - entry.getCenterY() + fi.getCenterShift().y; if (isBoundingBoxVisible() && entry.getWidth() > 2 && entry.getHeight() > 2) { // drawing bounding box around sprite elements @@ -1706,11 +1794,18 @@ protected void flipImageHorizontal(int frameIndex) /** * Applies translucency to the specified paletted image. * @param control the BAM controller. + * @param minTranslucency the minimum amount of translucency to apply. */ - protected void applyTranslucency(BamV1Control control) + protected void applyTranslucency(BamV1Control control, int minTranslucency) { if (control != null) { - int alpha = getCreatureInfo().getEffectiveTranslucency(); + int alpha = minTranslucency; + if (isTranslucencyEnabled()) { + int value = getCreatureInfo().getEffectiveTranslucency(); + if (value > 0) { + alpha = Math.min(alpha, value); + } + } int[] palette = control.getCurrentPalette(); // shadow color (alpha relative to semi-transparency of shadow) @@ -1763,6 +1858,7 @@ public int hashCode() hash = 31 * hash + Boolean.valueOf(transparentShadow).hashCode(); hash = 31 * hash + Boolean.valueOf(translucencyEnabled).hashCode(); hash = 31 * hash + Boolean.valueOf(tintEnabled).hashCode(); + hash = 31 * hash + Boolean.valueOf(blurEnabled).hashCode(); hash = 31 * hash + Boolean.valueOf(paletteReplacementEnabled).hashCode(); hash = 31 * hash + Boolean.valueOf(renderSpriteAvatar).hashCode(); hash = 31 * hash + Boolean.valueOf(renderSpriteWeapon).hashCode(); @@ -1799,6 +1895,7 @@ public boolean equals(Object o) retVal &= (this.transparentShadow == other.transparentShadow); retVal &= (this.translucencyEnabled == other.translucencyEnabled); retVal &= (this.tintEnabled == other.tintEnabled); + retVal &= (this.blurEnabled == other.blurEnabled); retVal &= (this.paletteReplacementEnabled == other.paletteReplacementEnabled); retVal &= (this.renderSpriteAvatar == other.renderSpriteAvatar); retVal &= (this.renderSpriteWeapon == other.renderSpriteWeapon); diff --git a/src/org/infinity/resource/cre/decoder/util/ColorInfo.java b/src/org/infinity/resource/cre/decoder/util/ColorInfo.java index 0fa32d188..e7ab5217a 100644 --- a/src/org/infinity/resource/cre/decoder/util/ColorInfo.java +++ b/src/org/infinity/resource/cre/decoder/util/ColorInfo.java @@ -26,6 +26,10 @@ public class ColorInfo public static final int OPCODE_TINT_SOLID = 51; /** Effect opcode 52: Character tint bright */ public static final int OPCODE_TINT_BRIGHT = 52; + /** Effect opcode 64: Blur */ + public static final int OPCODE_BLUR = 65; + /** Effect opcode 64: Translucency */ + public static final int OPCODE_TRANSLUCENCY = 66; /** Effect opcode 134: Petrification */ public static final int OPCODE_PETRIFICATION = 134; /** Effect opcode 218: Stoneskin */ diff --git a/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java b/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java index 7e7a22a79..6c5ff9cee 100644 --- a/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java +++ b/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java @@ -29,7 +29,6 @@ import org.infinity.resource.cre.Item; import org.infinity.resource.cre.decoder.MonsterPlanescapeDecoder; import org.infinity.resource.cre.decoder.SpriteDecoder; -import org.infinity.resource.cre.decoder.util.ItemInfo.EffectInfo; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.Misc; import org.infinity.util.Table2da; @@ -125,6 +124,12 @@ public boolean isBurnedEffect() return ((getStatus() & (1 << 9)) != 0); } + /** Returns {@code true} if the creature is under the effect of blur. */ + public boolean isBlurEffect() + { + return isEffectActive(SegmentDef.SpriteType.AVATAR, ColorInfo.OPCODE_BLUR, -1); + } + /** Returns the creature animation id. */ public int getAnimationId() { return ((IsNumeric)cre.getAttribute(CreResource.CRE_ANIMATION)).getValue(); } @@ -160,20 +165,19 @@ public int getEffectiveTranslucency() for (final ItemSlots slot : ItemSlots.values()) { ItemInfo info = getItemInfo(slot); if (info != null) { - EffectInfo effectInfo = info.getEffectStream() - .filter(ei -> ei.getOpcode() == 66 && // translucency - ei.getTiming() == 2 && // when equipped - ei.getParameter2() == 0) // draw instantly - .findAny() - .orElse(null); - if (effectInfo != null) { - // we need inverted amount - int amount = Math.max(0, Math.min(255, effectInfo.getParameter1())); + int amount = info.getColorInfo().getValue(SegmentDef.SpriteType.AVATAR, ColorInfo.OPCODE_TRANSLUCENCY, -1); + if (amount >= 0) { retVal = Math.max(retVal, 255 - amount); } } } + // getting translucency from creature effect + int amount = getColorInfo().getValue(SegmentDef.SpriteType.AVATAR, ColorInfo.OPCODE_TRANSLUCENCY, -1); + if (amount >= 0) { + retVal = Math.max(retVal, 255 - amount); + } + if (retVal < 0) { // falling back to default translucency retVal = getTranslucency(); @@ -869,6 +873,19 @@ private void initEffect(AbstractStruct as) } break; } + case ColorInfo.OPCODE_TRANSLUCENCY: + { + se = as.getAttribute(as.getOffset() + ofsParam2); + int param2 = (se instanceof IsNumeric) ? ((IsNumeric)se).getValue() : 0; + if (param2 == 0) { + se = as.getAttribute(as.getOffset() + ofsParam1); + int param1 = (se instanceof IsNumeric) ? ((IsNumeric)se).getValue() : 0; + param1 = Math.max(0, Math.min(255, param1)); + getColorInfo().add(SegmentDef.SpriteType.AVATAR, opcode, -1, param1); + } + break; + } + case ColorInfo.OPCODE_BLUR: case ColorInfo.OPCODE_PETRIFICATION: case ColorInfo.OPCODE_STONESKIN: getColorInfo().add(SegmentDef.SpriteType.AVATAR, opcode, -1, 0); diff --git a/src/org/infinity/resource/cre/decoder/util/FrameInfo.java b/src/org/infinity/resource/cre/decoder/util/FrameInfo.java index 1d2f663d0..dd5ad8bcc 100644 --- a/src/org/infinity/resource/cre/decoder/util/FrameInfo.java +++ b/src/org/infinity/resource/cre/decoder/util/FrameInfo.java @@ -6,6 +6,7 @@ import java.awt.BasicStroke; import java.awt.Color; +import java.awt.Point; import java.awt.Stroke; import java.util.EnumMap; import java.util.Objects; @@ -33,11 +34,18 @@ public class FrameInfo private final BamV1Control bamControl; private final SegmentDef segmentDef; + private final Point centerShift; public FrameInfo(BamV1Control bamControl, SegmentDef sd) + { + this(bamControl, sd, null); + } + + public FrameInfo(BamV1Control bamControl, SegmentDef sd, Point centerShift) { this.bamControl = Objects.requireNonNull(bamControl, "BAM controller cannot be null"); this.segmentDef = Objects.requireNonNull(sd, "Segment definition cannot be null"); + this.centerShift = (centerShift != null) ? new Point(centerShift) : new Point(); } /** Returns the BAM control instance. */ @@ -51,10 +59,13 @@ public FrameInfo(BamV1Control bamControl, SegmentDef sd) /** Returns the frame index relative to the cycle. */ public int getFrame() { return segmentDef.getCurrentFrame(); } + /** Returns the amount of pixels the frame center deviates from the original position. */ + public Point getCenterShift() { return centerShift; } + @Override public String toString() { - return "cycle=" + getCycle() + ", frame=" + getFrame(); + return "cycle=" + getCycle() + ", frame=" + getFrame() + ", centerShift=" + centerShift.toString(); } @Override @@ -63,6 +74,7 @@ public int hashCode() int hash = 7; hash = 31 * hash + ((bamControl == null) ? 0 : bamControl.hashCode()); hash = 31 * hash + ((segmentDef == null) ? 0 : segmentDef.hashCode()); + hash = 31 * hash + ((centerShift == null) ? 0 : centerShift.hashCode()); return hash; } @@ -80,6 +92,8 @@ public boolean equals(Object o) (this.bamControl != null && this.bamControl.equals(other.bamControl)); retVal &= (this.segmentDef == null && other.segmentDef == null) || (this.segmentDef != null && this.segmentDef.equals(other.segmentDef)); + retVal &= (this.centerShift == null && other.centerShift == null) || + (this.centerShift != null && this.centerShift.equals(other.centerShift)); return retVal; } } diff --git a/src/org/infinity/resource/cre/decoder/util/ItemInfo.java b/src/org/infinity/resource/cre/decoder/util/ItemInfo.java index 6d5a29231..d662dd478 100644 --- a/src/org/infinity/resource/cre/decoder/util/ItemInfo.java +++ b/src/org/infinity/resource/cre/decoder/util/ItemInfo.java @@ -834,6 +834,13 @@ private void parseEffect(EffectInfo info) getColorInfo().add(type, info.getOpcode(), location, info.getParameter1()); } break; + case ColorInfo.OPCODE_TRANSLUCENCY: + if (info.getParameter2() == 0) { + int param1 = Math.max(0, Math.min(255, info.getParameter1())); + getColorInfo().add(SegmentDef.SpriteType.AVATAR, info.getOpcode(), -1, param1); + } + break; + case ColorInfo.OPCODE_BLUR: case ColorInfo.OPCODE_PETRIFICATION: case ColorInfo.OPCODE_STONESKIN: getColorInfo().add(SegmentDef.SpriteType.AVATAR, info.getOpcode(), -1, 0); diff --git a/src/org/infinity/resource/cre/decoder/util/SpriteUtils.java b/src/org/infinity/resource/cre/decoder/util/SpriteUtils.java index 4706122a7..207134d23 100644 --- a/src/org/infinity/resource/cre/decoder/util/SpriteUtils.java +++ b/src/org/infinity/resource/cre/decoder/util/SpriteUtils.java @@ -882,10 +882,10 @@ public static Rectangle getTotalFrameDimension(FrameInfo... frames) int frameIdx = fi.getFrame(); frameIdx = ctrl.cycleGetFrameIndexAbsolute(fi.getCycle(), frameIdx); FrameEntry entry = fi.getController().getDecoder().getFrameInfo(frameIdx); - left = Math.min(left, -entry.getCenterX()); - top = Math.min(top, -entry.getCenterY()); - right = Math.max(right, entry.getWidth() - entry.getCenterX()); - bottom = Math.max(bottom, entry.getHeight() - entry.getCenterY()); + left = Math.min(left, -entry.getCenterX() + fi.getCenterShift().x); + top = Math.min(top, -entry.getCenterY() + fi.getCenterShift().y); + right = Math.max(right, entry.getWidth() - entry.getCenterX() + fi.getCenterShift().x); + bottom = Math.max(bottom, entry.getHeight() - entry.getCenterY() + fi.getCenterShift().y); } retVal.x = left; From 114c817b68235bf0878f55988a5d551bf3be3853 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Wed, 14 Apr 2021 20:42:20 +0200 Subject: [PATCH 129/158] Generic compiler optimizations for DxtEncoder class --- .../resource/graphics/DxtEncoder.java | 484 +++++++++--------- 1 file changed, 243 insertions(+), 241 deletions(-) diff --git a/src/org/infinity/resource/graphics/DxtEncoder.java b/src/org/infinity/resource/graphics/DxtEncoder.java index 56adf1189..8a0ad2077 100644 --- a/src/org/infinity/resource/graphics/DxtEncoder.java +++ b/src/org/infinity/resource/graphics/DxtEncoder.java @@ -48,7 +48,8 @@ public static enum DxtType { DXT1, DXT3, DXT5 } * @return A data block containing the DXT-encoded image. * @throws Exception */ - static public byte[] encodeImage(int[] pixels, int width, int height, DxtType dxtType) throws Exception + static public byte[] encodeImage(final int[] pixels, final int width, final int height, + final DxtType dxtType) throws Exception { // consistency check if (dxtType == null) @@ -60,7 +61,7 @@ static public byte[] encodeImage(int[] pixels, int width, int height, DxtType dx if (pixels == null || pixels.length < width*height) throw new Exception("Insufficient source data."); - int size = calcImageSize(width, height, dxtType); + final int size = calcImageSize(width, height, dxtType); byte[] output = new byte[size]; try { encodeImage(pixels, width, height, output, dxtType); @@ -81,8 +82,8 @@ static public byte[] encodeImage(int[] pixels, int width, int height, DxtType dx * @param dxtType The compression type to use. * @throws Exception */ - static public void encodeImage(int[] pixels, int width, int height, byte[] output, - DxtType dxtType) throws Exception + static public void encodeImage(final int[] pixels, final int width, final int height, final byte[] output, + final DxtType dxtType) throws Exception { // consistency check if (dxtType == null) @@ -98,10 +99,10 @@ static public void encodeImage(int[] pixels, int width, int height, byte[] outpu calcImageSize(width, height, dxtType), (output == null) ? 0 : output.length)); int outputOfs = 0; // points to the end of encoded data - int bw = width / 4; - int bh = height / 4; - int[] inBlock = new int[16]; - byte[] outBlock = new byte[calcBlockSize(dxtType)]; + final int bw = width / 4; + final int bh = height / 4; + final int[] inBlock = new int[16]; + final byte[] outBlock = new byte[calcBlockSize(dxtType)]; for (int y = 0; y < bh; y++) { for (int x = 0; x < bw; x++) { // create 4x4 block of pixels for DXTn compression @@ -124,7 +125,7 @@ static public void encodeImage(int[] pixels, int width, int height, byte[] outpu * @param block Data block to store the compressed DXTn data in. * @param dxtType The DXT type to use. */ - public static void encodeBlock(int[] pixels, byte[] block, DxtType dxtType) throws Exception + public static void encodeBlock(final int[] pixels, final byte[] block, final DxtType dxtType) throws Exception { if (pixels == null || pixels.length < 16) throw new Exception("Insufficient source data."); @@ -132,14 +133,14 @@ public static void encodeBlock(int[] pixels, byte[] block, DxtType dxtType) thro throw new Exception(String.format("Insufficient output space. Needed: %d bytes, available: %d bytes", calcBlockSize(dxtType), (block == null) ? 0 : block.length)); - byte[] colorBlock = new byte[8]; + final byte[] colorBlock = new byte[8]; byte[] alphaBlock = null; if (dxtType == DxtType.DXT3 || dxtType == DxtType.DXT5) { alphaBlock = new byte[8]; } // create the minimal point set - ColorSet colors = new ColorSet(pixels, dxtType); + final ColorSet colors = new ColorSet(pixels, dxtType); // check the compression type and compress color ColorFit fit = null; @@ -179,7 +180,7 @@ public static void encodeBlock(int[] pixels, byte[] block, DxtType dxtType) thro * @param dxtType The desired compression. * @return The number of bytes to hold the compressed data of a 4x4 block of pixels. */ - public static int calcBlockSize(DxtType dxtType) + public static int calcBlockSize(final DxtType dxtType) { if (dxtType != null) { switch (dxtType) { @@ -200,7 +201,7 @@ public static int calcBlockSize(DxtType dxtType) * @param dxtType The desired compression type. * @return The number of bytes required to hold the compressed image data. */ - public static int calcImageSize(int width, int height, DxtType dxtType) + public static int calcImageSize(int width, int height, final DxtType dxtType) { if (width <= 0 || height <= 0 || dxtType == null) return 0; @@ -223,12 +224,12 @@ private static class ColorSet private boolean transparent; // Extracts the color component at the specified pos (0..3 = blue,green,red,alpha) - public static int argb(int color, int pos) + public static int argb(final int color, final int pos) { return (color >>> ((pos & 3) << 3)) & 0xff; } - public ColorSet(int[] pixels, DxtType dxtType) + public ColorSet(final int[] pixels, final DxtType dxtType) { points = new Vec3[16]; weights = new float[16]; @@ -236,7 +237,7 @@ public ColorSet(int[] pixels, DxtType dxtType) count = 0; transparent = false; - boolean isDXT1 = (dxtType == DxtType.DXT1); + final boolean isDXT1 = (dxtType == DxtType.DXT1); // create minimal set for (int i = 0; i < 16; i++) { @@ -252,12 +253,12 @@ public ColorSet(int[] pixels, DxtType dxtType) // allocate new points if (j == i) { // normalize coordinates to [0, 1] - float x = (float)argb(pixels[i], 2) / 255.0f; - float y = (float)argb(pixels[i], 1) / 255.0f; - float z = (float)argb(pixels[i], 0) / 255.0f; + final float x = (float)argb(pixels[i], 2) / 255.0f; + final float y = (float)argb(pixels[i], 1) / 255.0f; + final float z = (float)argb(pixels[i], 0) / 255.0f; // ensure there is always non-zero weight even for zero alpha - float w = (float)(argb(pixels[i], 3) + 1) / 256.0f; + final float w = (float)(argb(pixels[i], 3) + 1) / 256.0f; // add the points points[count] = new Vec3(x, y, z); @@ -269,15 +270,15 @@ public ColorSet(int[] pixels, DxtType dxtType) } // check for a match - boolean match = (argb(pixels[i], 0) == argb(pixels[j], 0) && - argb(pixels[i], 1) == argb(pixels[j], 1) && - argb(pixels[i], 2) == argb(pixels[j], 2) && - (argb(pixels[j], 3) >= 128 || !isDXT1)); + final boolean match = (argb(pixels[i], 0) == argb(pixels[j], 0) && + argb(pixels[i], 1) == argb(pixels[j], 1) && + argb(pixels[i], 2) == argb(pixels[j], 2) && + (argb(pixels[j], 3) >= 128 || !isDXT1)); if (match) { // get the index of the match - int index = remap[j]; + final int index = remap[j]; // ensure there is always non-zero weight even for zero alpha - float w = (float)(argb(pixels[i], 3) + 1) / 256.0f; + final float w = (float)(argb(pixels[i], 3) + 1) / 256.0f; // map to this point and increase the weight weights[index] += w; remap[i] = index; @@ -300,10 +301,10 @@ public ColorSet(int[] pixels, DxtType dxtType) public boolean isTransparent() { return transparent; } - public void remapIndices(int[] source, int[] target) + public void remapIndices(final int[] source, final int[] target) { for (int i = 0; i < 16; i++) { - int j = remap[i]; + final int j = remap[i]; target[i] = (j == -1) ? 3 : source[j]; } } @@ -312,8 +313,8 @@ public void remapIndices(int[] source, int[] target) private static abstract class ColorFit { - protected ColorSet colors; - protected DxtType dxtType; + protected final ColorSet colors; + protected final DxtType dxtType; public ColorFit(ColorSet colors, DxtType dxtType) { @@ -321,9 +322,9 @@ public ColorFit(ColorSet colors, DxtType dxtType) this.dxtType = dxtType; } - public void compress(byte[] block) + public void compress(final byte[] block) { - boolean isDXT1 = (dxtType == DxtType.DXT1); + final boolean isDXT1 = (dxtType == DxtType.DXT1); if (isDXT1) { compress3(block); if (!colors.isTransparent()) { @@ -334,8 +335,8 @@ public void compress(byte[] block) } } - protected abstract void compress3(byte[] block); - protected abstract void compress4(byte[] block); + protected abstract void compress3(final byte[] block); + protected abstract void compress4(final byte[] block); } @@ -348,7 +349,7 @@ private static class SingleColorFit extends ColorFit private int error; private int bestError; - public SingleColorFit(ColorSet colors, DxtType dxtType) + public SingleColorFit(final ColorSet colors, final DxtType dxtType) { super(colors, dxtType); color = new int[3]; @@ -356,7 +357,7 @@ public SingleColorFit(ColorSet colors, DxtType dxtType) end = new Vec3(); // grab the single color - Vec3 value = this.colors.getPoints()[0]; + final Vec3 value = this.colors.getPoints()[0]; color[0] = Misc.floatToInt(255.0f*value.x(), 255); color[1] = Misc.floatToInt(255.0f*value.y(), 255); color[2] = Misc.floatToInt(255.0f*value.z(), 255); @@ -366,10 +367,10 @@ public SingleColorFit(ColorSet colors, DxtType dxtType) } @Override - protected void compress3(byte[] block) + protected void compress3(final byte[] block) { // build the table of lookups - SingleColorLookup[][] lookups = new SingleColorLookup[][]{ + final SingleColorLookup[][] lookups = new SingleColorLookup[][]{ Lookups.lookup53, Lookups.lookup63, Lookups.lookup53 }; @@ -379,7 +380,7 @@ protected void compress3(byte[] block) // build the block if we win if (error < bestError) { // remap indices - int[] indices = new int[16]; + final int[] indices = new int[16]; colors.remapIndices(new int[]{index}, indices); // save the block @@ -391,10 +392,10 @@ protected void compress3(byte[] block) } @Override - protected void compress4(byte[] block) + protected void compress4(final byte[] block) { // build the table of lookups - SingleColorLookup[][] lookups = new SingleColorLookup[][]{ + final SingleColorLookup[][] lookups = new SingleColorLookup[][]{ Lookups.lookup54, Lookups.lookup64, Lookups.lookup54 }; @@ -404,7 +405,7 @@ protected void compress4(byte[] block) // build the block if we win if (error < bestError) { // remap indices - int[] indices = new int[16]; + final int[] indices = new int[16]; colors.remapIndices(new int[]{index}, indices); // save the block @@ -415,23 +416,23 @@ protected void compress4(byte[] block) } } - protected void computeEndPoints(SingleColorLookup[][] lookups) + protected void computeEndPoints(final SingleColorLookup[][] lookups) { // check each index combination (endpoint or intermediate) this.error = Integer.MAX_VALUE; for (int index = 0; index < 2; index++) { - SourceBlock[] sources = new SourceBlock[3]; + final SourceBlock[] sources = new SourceBlock[3]; int error = 0; for (int channel = 0; channel < 3; channel++) { // grab the lookup table and index for this channel - SingleColorLookup[] lookup = lookups[channel]; - int target = color[channel]; + final SingleColorLookup[] lookup = lookups[channel]; + final int target = color[channel]; // store a pointer to the source for this channel sources[channel] = lookup[target].sources[index]; // accumulate the error - int diff = sources[channel].error; + final int diff = sources[channel].error; error += diff * diff; } @@ -455,7 +456,7 @@ private static class RangeFit extends ColorFit private Vec3 metric, start, end; private float bestError; - public RangeFit(ColorSet colors, DxtType dxtType) + public RangeFit(final ColorSet colors, final DxtType dxtType) { super(colors, dxtType); @@ -466,15 +467,15 @@ public RangeFit(ColorSet colors, DxtType dxtType) bestError = Float.MAX_VALUE; // cache some values - int count = this.colors.getCount(); - Vec3[] values = this.colors.getPoints(); - float[] weights = this.colors.getWeights(); + final int count = this.colors.getCount(); + final Vec3[] values = this.colors.getPoints(); + final float[] weights = this.colors.getWeights(); // get the covariance matrix - Sym3x3 covariance = Sym3x3.computeWeightedCovariance(count, values, weights); + final Sym3x3 covariance = Sym3x3.computeWeightedCovariance(count, values, weights); // compute the principle component - Vec3 principle = Sym3x3.computePrincipleComponent(covariance); + final Vec3 principle = Sym3x3.computePrincipleComponent(covariance); // get the min and max range as the codebook endpoints Vec3 start = new Vec3(); @@ -486,7 +487,7 @@ public RangeFit(ColorSet colors, DxtType dxtType) start = end = values[0]; min = max = Vec3.dot(values[0], principle); for (int i = 1; i < count; i++) { - float val = Vec3.dot(values[i], principle); + final float val = Vec3.dot(values[i], principle); if (val < min) { start = values[i]; min = val; @@ -512,20 +513,20 @@ public RangeFit(ColorSet colors, DxtType dxtType) } @Override - protected void compress3(byte[] block) + protected void compress3(final byte[] block) { // cache some values - int count = colors.getCount(); - Vec3[] values = colors.getPoints(); + final int count = colors.getCount(); + final Vec3[] values = colors.getPoints(); // create a codebook - Vec3[] codes = new Vec3[3]; + final Vec3[] codes = new Vec3[3]; codes[0] = start; codes[1] = end; codes[2] = Vec3.mul(start, 0.5f).add(Vec3.mul(end, 0.5f)); // match each point to the closest code - int[] closest = new int[16]; + final int[] closest = new int[16]; float error = 0.0f; for (int i = 0; i < count; i++) { // find the closest code @@ -549,7 +550,7 @@ protected void compress3(byte[] block) // save this scheme if it wins if (error < bestError) { // remap the indices - int[] indices = new int[16]; + final int[] indices = new int[16]; colors.remapIndices(closest, indices); // save the block @@ -561,21 +562,21 @@ protected void compress3(byte[] block) } @Override - protected void compress4(byte[] block) + protected void compress4(final byte[] block) { // cache some values - int count = colors.getCount(); - Vec3[] values = colors.getPoints(); + final int count = colors.getCount(); + final Vec3[] values = colors.getPoints(); // create a codebook - Vec3[] codes = new Vec3[4]; + final Vec3[] codes = new Vec3[4]; codes[0] = start; codes[1] = end; codes[2] = Vec3.mul(start, 2.0f/3.0f).add(Vec3.mul(end, 1.0f/3.0f)); codes[3] = Vec3.mul(start, 1.0f/3.0f).add(Vec3.mul(end, 2.0f/3.0f)); // match each point to the closest code - int[] closest = new int[16]; + final int[] closest = new int[16]; float error = 0.0f; for (int i = 0; i < count; i++) { // find the closest code @@ -618,12 +619,13 @@ private static class ClusterFit extends ColorFit private final int[] order; private final Vec4[] pointsWeights; - private Vec3 principle; + private final Vec3 principle; + private final Vec4 metric; + private Vec4 xsum_wsum; - private Vec4 metric; private Vec4 bestError; - public ClusterFit(ColorSet colors, DxtType dxtType) + public ClusterFit(final ColorSet colors, final DxtType dxtType) { super(colors, dxtType); order = new int[16*IterationCount]; @@ -636,16 +638,16 @@ public ClusterFit(ColorSet colors, DxtType dxtType) metric = new Vec4(0.2126f, 0.7152f, 0.0722f, 0.0f); // get the covariance matrix - Sym3x3 covariance = Sym3x3.computeWeightedCovariance(this.colors.getCount(), - this.colors.getPoints(), - this.colors.getWeights()); + final Sym3x3 covariance = Sym3x3.computeWeightedCovariance(this.colors.getCount(), + this.colors.getPoints(), + this.colors.getWeights()); // compute the principle component principle = Sym3x3.computePrincipleComponent(covariance); } @Override - protected void compress3(byte[] block) + protected void compress3(final byte[] block) { // declare variables final int count = colors.getCount(); @@ -664,33 +666,33 @@ protected void compress3(byte[] block) Vec4 bestStart = new Vec4(); Vec4 bestEnd = new Vec4(); Vec4 bestError = this.bestError; - int[] bestIndices = new int[16]; + final int[] bestIndices = new int[16]; int bestIteration = 0; int bestI = 0, bestJ = 0; // loop over iterations (we avoid the case that all points in first or last cluster) for (int iterIndex = 0; ; ) { // first cluster [0, i) is at the start - Vec4 part0 = new Vec4(); + final Vec4 part0 = new Vec4(); for (int i = 0; i < count; i++) { // second cluster [i, j) is half along - Vec4 part1 = (i == 0) ? (Vec4)pointsWeights[0].clone() : new Vec4(); - int jmin = (i == 0) ? 1 : i; + final Vec4 part1 = (i == 0) ? (Vec4)pointsWeights[0].clone() : new Vec4(); + final int jmin = (i == 0) ? 1 : i; for (int j = jmin; ; ) { // last cluster [j, count) is at the end - Vec4 part2 = Vec4.sub(xsum_wsum, part1).sub(part0); + final Vec4 part2 = Vec4.sub(xsum_wsum, part1).sub(part0); // compute least squares terms directly - Vec4 alphaXSum = Vec4.multiplyAdd(part1, halfHalf2, part0); - Vec4 alpha2Sum = alphaXSum.splatW(); + final Vec4 alphaXSum = Vec4.multiplyAdd(part1, halfHalf2, part0); + final Vec4 alpha2Sum = alphaXSum.splatW(); - Vec4 betaXSum = Vec4.multiplyAdd(part1, halfHalf2, part2); - Vec4 beta2Sum = betaXSum.splatW(); + final Vec4 betaXSum = Vec4.multiplyAdd(part1, halfHalf2, part2); + final Vec4 beta2Sum = betaXSum.splatW(); - Vec4 alphaBetaSum = Vec4.mul(part1, halfHalf2).splatW(); + final Vec4 alphaBetaSum = Vec4.mul(part1, halfHalf2).splatW(); // compute the least squares optimal points - Vec4 factor = Vec4.reciprocal(Vec4.negMulSub(alphaBetaSum, alphaBetaSum, Vec4.mul(alpha2Sum, beta2Sum))); + final Vec4 factor = Vec4.reciprocal(Vec4.negMulSub(alphaBetaSum, alphaBetaSum, Vec4.mul(alpha2Sum, beta2Sum))); Vec4 a = Vec4.negMulSub(betaXSum, alphaBetaSum, Vec4.mul(alphaXSum, beta2Sum)).mul(factor); Vec4 b = Vec4.negMulSub(alphaXSum, alphaBetaSum, Vec4.mul(betaXSum, alpha2Sum)).mul(factor); @@ -701,14 +703,14 @@ protected void compress3(byte[] block) b = Vec4.truncate(Vec4.multiplyAdd(grid, b, half)).mul(gridrcp); // compute the error (we skip the constant xxsum) - Vec4 e1 = Vec4.multiplyAdd(Vec4.mul(a, a), alpha2Sum, Vec4.mul(b, b).mul(beta2Sum)); - Vec4 e2 = Vec4.negMulSub(a, alphaXSum, Vec4.mul(a, b).mul(alphaBetaSum)); - Vec4 e3 = Vec4.negMulSub(b, betaXSum, e2); - Vec4 e4 = Vec4.multiplyAdd(two, e3, e1); + final Vec4 e1 = Vec4.multiplyAdd(Vec4.mul(a, a), alpha2Sum, Vec4.mul(b, b).mul(beta2Sum)); + final Vec4 e2 = Vec4.negMulSub(a, alphaXSum, Vec4.mul(a, b).mul(alphaBetaSum)); + final Vec4 e3 = Vec4.negMulSub(b, betaXSum, e2); + final Vec4 e4 = Vec4.multiplyAdd(two, e3, e1); // apply the metric to the error terms - Vec4 e5 = Vec4.mul(e4, metric); - Vec4 error = Vec4.add(e5.splatX(), e5.splatY()).add(e5.splatZ()); + final Vec4 e5 = Vec4.mul(e4, metric); + final Vec4 error = Vec4.add(e5.splatX(), e5.splatY()).add(e5.splatZ()); // keep the solution if it wins if (Vec4.compareAnyLessThan(error, bestError)) { @@ -739,7 +741,7 @@ protected void compress3(byte[] block) break; // stop if a new iteration is an ordering that has already been tried - Vec3 axis = Vec4.sub(bestEnd, bestStart).getVec3(); + final Vec3 axis = Vec4.sub(bestEnd, bestStart).getVec3(); if (!constructOrdering(axis, iterIndex)) break; } @@ -747,8 +749,8 @@ protected void compress3(byte[] block) // save the block if necessary if (Vec4.compareAnyLessThan(bestError, this.bestError)) { // remap the indices - int orderIdx = 16*bestIteration; - int[] unordered = new int[16]; + final int orderIdx = 16*bestIteration; + final int[] unordered = new int[16]; for (int m = 0; m < bestI; m++) unordered[order[orderIdx+m]] = 0; for (int m = bestI; m < bestJ; m++) @@ -767,7 +769,7 @@ protected void compress3(byte[] block) } @Override - protected void compress4(byte[] block) + protected void compress4(final byte[] block) { // declare variables final int count = colors.getCount(); @@ -788,39 +790,39 @@ protected void compress4(byte[] block) Vec4 bestStart = new Vec4(); Vec4 bestEnd = new Vec4(); Vec4 bestError = this.bestError; - int[] bestIndices = new int[16]; + final int[] bestIndices = new int[16]; int bestIteration = 0; int bestI = 0, bestJ = 0, bestK = 0; // loop over iterations (we avoid the case all points in first or last cluster) for (int iterIndex = 0; ; ) { // first cluster [0, i) is at the start - Vec4 part0 = new Vec4(); + final Vec4 part0 = new Vec4(); for (int i = 0; i < count; i++) { // second cluster [i, j) is one third along - Vec4 part1 = new Vec4(); + final Vec4 part1 = new Vec4(); for (int j = i; ; ) { // third cluster [j, k) is two thirds along - Vec4 part2 = (j == 0) ? (Vec4)pointsWeights[0].clone() : new Vec4(); - int kmin = (j == 0) ? 1 : j; + final Vec4 part2 = (j == 0) ? (Vec4)pointsWeights[0].clone() : new Vec4(); + final int kmin = (j == 0) ? 1 : j; for (int k = kmin; ; ) { // last cluster [k, count) is at the end - Vec4 part3 = Vec4.sub(xsum_wsum, part2).sub(part1).sub(part0); + final Vec4 part3 = Vec4.sub(xsum_wsum, part2).sub(part1).sub(part0); // compute least squares terms directly - Vec4 alphaXSum = Vec4.multiplyAdd(part2, oneThirdOneThird2, - Vec4.multiplyAdd(part1, twoThirdsTwoThirds2, part0)); - Vec4 alpha2Sum = alphaXSum.splatW(); + final Vec4 alphaXSum = Vec4.multiplyAdd(part2, oneThirdOneThird2, + Vec4.multiplyAdd(part1, twoThirdsTwoThirds2, part0)); + final Vec4 alpha2Sum = alphaXSum.splatW(); - Vec4 betaXSum = Vec4.multiplyAdd(part1, oneThirdOneThird2, - Vec4.multiplyAdd(part2, twoThirdsTwoThirds2, part3)); - Vec4 beta2Sum = betaXSum.splatW(); + final Vec4 betaXSum = Vec4.multiplyAdd(part1, oneThirdOneThird2, + Vec4.multiplyAdd(part2, twoThirdsTwoThirds2, part3)); + final Vec4 beta2Sum = betaXSum.splatW(); - Vec4 alphaBetaSum = Vec4.mul(twoNineth, Vec4.add(part1, part2).splatW()); + final Vec4 alphaBetaSum = Vec4.mul(twoNineth, Vec4.add(part1, part2).splatW()); // compute the least-squares optimal points - Vec4 factor = Vec4.reciprocal(Vec4.negMulSub(alphaBetaSum, alphaBetaSum, - Vec4.mul(alpha2Sum, beta2Sum))); + final Vec4 factor = Vec4.reciprocal(Vec4.negMulSub(alphaBetaSum, alphaBetaSum, + Vec4.mul(alpha2Sum, beta2Sum))); Vec4 a = Vec4.negMulSub(betaXSum, alphaBetaSum, Vec4.mul(alphaXSum, beta2Sum)).mul(factor); Vec4 b = Vec4.negMulSub(alphaXSum, alphaBetaSum, Vec4.mul(betaXSum, alpha2Sum)).mul(factor); @@ -831,14 +833,14 @@ protected void compress4(byte[] block) b = Vec4.truncate(Vec4.multiplyAdd(grid, b, half)).mul(gridrcp); // compute the error (we skip the constant xxsum) - Vec4 e1 = Vec4.multiplyAdd(Vec4.mul(a, a), alpha2Sum, Vec4.mul(b, b).mul(beta2Sum)); - Vec4 e2 = Vec4.negMulSub(a, alphaXSum, Vec4.mul(a, b).mul(alphaBetaSum)); - Vec4 e3 = Vec4.negMulSub(b, betaXSum, e2); - Vec4 e4 = Vec4.multiplyAdd(two, e3, e1); + final Vec4 e1 = Vec4.multiplyAdd(Vec4.mul(a, a), alpha2Sum, Vec4.mul(b, b).mul(beta2Sum)); + final Vec4 e2 = Vec4.negMulSub(a, alphaXSum, Vec4.mul(a, b).mul(alphaBetaSum)); + final Vec4 e3 = Vec4.negMulSub(b, betaXSum, e2); + final Vec4 e4 = Vec4.multiplyAdd(two, e3, e1); // apply the metric to the error term - Vec4 e5 = Vec4.mul(e4, metric); - Vec4 error = Vec4.add(e5.splatX(), e5.splatY()).add(e5.splatZ()); + final Vec4 e5 = Vec4.mul(e4, metric); + final Vec4 error = Vec4.add(e5.splatX(), e5.splatY()).add(e5.splatZ()); // keep the solution if it wins if (Vec4.compareAnyLessThan(error, bestError)) { @@ -875,7 +877,7 @@ protected void compress4(byte[] block) break; // stop if a new iteration is an ordering that has already been tried - Vec3 axis = Vec4.sub(bestEnd, bestStart).getVec3(); + final Vec3 axis = Vec4.sub(bestEnd, bestStart).getVec3(); if (!constructOrdering(axis, iterIndex)) break; } @@ -883,8 +885,8 @@ protected void compress4(byte[] block) // save the block if necessary if (Vec4.compareAnyLessThan(bestError, this.bestError)) { // remap the indices - int orderIdx = 16*bestIteration; - int[] unordered = new int[16]; + final int orderIdx = 16*bestIteration; + final int[] unordered = new int[16]; for (int m = 0; m < bestI; m++) unordered[order[orderIdx+m]] = 0; for (int m = bestI; m < bestJ; m++) @@ -904,15 +906,15 @@ protected void compress4(byte[] block) } } - private boolean constructOrdering(Vec3 axis, int iteration) + private boolean constructOrdering(final Vec3 axis, final int iteration) { // cache some values final int count = colors.getCount(); final Vec3[] values = colors.getPoints(); // build list of dot products - float[] dps = new float[16]; - int orderIdx = 16*iteration; + final float[] dps = new float[16]; + final int orderIdx = 16*iteration; for (int i = 0; i < count; i++) { dps[i] = Vec3.dot(values[i], axis); order[orderIdx+i] = i; @@ -921,14 +923,14 @@ private boolean constructOrdering(Vec3 axis, int iteration) // stable sort using them for (int i = 0; i < count; i++) { for (int j = i; j > 0 && dps[j] < dps[j-1]; j--) { - float tmp1 = dps[j]; dps[j] = dps[j-1]; dps[j-1] = tmp1; - int tmp2 = order[orderIdx+j]; order[orderIdx+j] = order[orderIdx+j-1]; order[orderIdx+j-1] = tmp2; + final float tmp1 = dps[j]; dps[j] = dps[j-1]; dps[j-1] = tmp1; + final int tmp2 = order[orderIdx+j]; order[orderIdx+j] = order[orderIdx+j-1]; order[orderIdx+j-1] = tmp2; } } // check this ordering is unique for (int it = 0; it < iteration; it++) { - int prevIdx = 16*it; + final int prevIdx = 16*it; boolean same = true; for (int i = 0; i < count; i++) { if (order[orderIdx+i] != order[prevIdx+i]) { @@ -946,9 +948,9 @@ private boolean constructOrdering(Vec3 axis, int iteration) xsum_wsum = new Vec4(); for (int i = 0; i < count; i++) { int j = order[orderIdx+i]; - Vec4 p = new Vec4(unweighted[j].x(), unweighted[j].y(), unweighted[j].z(), 1.0f); - Vec4 w = new Vec4(weights[j]); - Vec4 x = Vec4.mul(p, w); + final Vec4 p = new Vec4(unweighted[j].x(), unweighted[j].y(), unweighted[j].z(), 1.0f); + final Vec4 w = new Vec4(weights[j]); + final Vec4 x = Vec4.mul(p, w); pointsWeights[i] = x; xsum_wsum.add(x); } @@ -959,20 +961,20 @@ private boolean constructOrdering(Vec3 axis, int iteration) private static final class ColorBlock { - public static void writeColorBlock3(Vec3 start, Vec3 end, int[] indices, byte[] block) + public static void writeColorBlock3(final Vec3 start, final Vec3 end, final int[] indices, final byte[] block) { // get the packed values int a = floatTo565(start); int b = floatTo565(end); // remap the indices - int[] remapped = new int[16]; + final int[] remapped = new int[16]; if (a <= b) { // use the indices directly System.arraycopy(indices, 0, remapped, 0, 16); } else { // swap a and b - int tmp = a; a = b; b = tmp; + final int tmp = a; a = b; b = tmp; for (int i = 0; i < 16; i++) { if (indices[i] == 0) { remapped[i] = 1; @@ -988,17 +990,17 @@ public static void writeColorBlock3(Vec3 start, Vec3 end, int[] indices, byte[] writeColorBlock(a, b, remapped, block); } - public static void writeColorBlock4(Vec3 start, Vec3 end, int[] indices, byte[] block) + public static void writeColorBlock4(final Vec3 start, final Vec3 end, final int[] indices, final byte[] block) { // get the packed values int a = floatTo565(start); int b = floatTo565(end); // remap the indices - int[] remapped = new int[16]; + final int[] remapped = new int[16]; if (a < b) { // swap a and b - int tmp = a; a = b; b = tmp; + final int tmp = a; a = b; b = tmp; for (int i = 0; i < 16; i++) { remapped[i] = (indices[i] ^ 1) & 3; } @@ -1016,18 +1018,18 @@ public static void writeColorBlock4(Vec3 start, Vec3 end, int[] indices, byte[] writeColorBlock(a, b, remapped, block); } - private static int floatTo565(Vec3 color) + private static int floatTo565(final Vec3 color) { // get the components in the correct range - int r = Misc.floatToInt(31.0f*color.x(), 31); - int g = Misc.floatToInt(63.0f*color.y(), 63); - int b = Misc.floatToInt(31.0f*color.z(), 31); + final int r = Misc.floatToInt(31.0f*color.x(), 31); + final int g = Misc.floatToInt(63.0f*color.y(), 63); + final int b = Misc.floatToInt(31.0f*color.z(), 31); // pack the color into a single value return ((r << 11) | (g << 5) | b) & 0xffff; } - private static void writeColorBlock(int a, int b, int[] indices, byte[] block) + private static void writeColorBlock(final int a, final int b, final int[] indices, final byte[] block) { // write the endpoints block[0] = (byte)(a & 0xff); @@ -1037,7 +1039,7 @@ private static void writeColorBlock(int a, int b, int[] indices, byte[] block) // write the indices for (int i = 0; i < 4; i++) { - int idx = 4*i; + final int idx = 4*i; block[i+4] = (byte)((indices[idx+0]) | (indices[idx+1] << 2) | (indices[idx+2] << 4) | (indices[idx+3] << 6)); } @@ -1046,22 +1048,22 @@ private static void writeColorBlock(int a, int b, int[] indices, byte[] block) private static final class Alpha { - public static void compressAlphaDxt3(int[] pixels, byte[] block) + public static void compressAlphaDxt3(final int[] pixels, final byte[] block) { // quantize and pack the alpha values pairwise for (int i = 0; i < 8; i++) { // quantize down to 4 bits - float alpha1 = (float)ColorSet.argb(pixels[2*i], 3) * (15.0f / 255.0f); - float alpha2 = (float)ColorSet.argb(pixels[2*i+1], 3) * (15.0f / 255.0f); - int quant1 = Misc.floatToInt(alpha1, 15); - int quant2 = Misc.floatToInt(alpha2, 15); + final float alpha1 = (float)ColorSet.argb(pixels[2*i], 3) * (15.0f / 255.0f); + final float alpha2 = (float)ColorSet.argb(pixels[2*i+1], 3) * (15.0f / 255.0f); + final int quant1 = Misc.floatToInt(alpha1, 15); + final int quant2 = Misc.floatToInt(alpha2, 15); // pack into the byte block[i] = (byte)(quant1 | (quant2 << 4)); } } - public static void compressAlphaDxt5(int[] pixels, byte[] block) + public static void compressAlphaDxt5(final int[] pixels, final byte[] block) { // get the range for 5-alpha and 7-alpha interpolation int min5 = 255; @@ -1088,7 +1090,7 @@ public static void compressAlphaDxt5(int[] pixels, byte[] block) min7 = max7; // fix the range to be the minimum in each case - int[] minmax = new int[2]; + final int[] minmax = new int[2]; minmax[0] = min5; minmax[1] = max5; fixRange(minmax, 5); min5 = minmax[0]; max5 = minmax[1]; @@ -1097,7 +1099,7 @@ public static void compressAlphaDxt5(int[] pixels, byte[] block) min7 = minmax[0]; max7 = minmax[1]; // set up the 5-alpha code book - int[] codes5 = new int[8]; + final int[] codes5 = new int[8]; codes5[0] = min5; codes5[1] = max5; for (int i = 1; i < 5; i++) { @@ -1107,7 +1109,7 @@ public static void compressAlphaDxt5(int[] pixels, byte[] block) codes5[7] = 255; // set up the 7-alpha code book - int[] codes7 = new int[8]; + final int[] codes7 = new int[8]; codes7[0] = min7; codes7[1] = max7; for (int i = 1; i < 7; i++) { @@ -1115,10 +1117,10 @@ public static void compressAlphaDxt5(int[] pixels, byte[] block) } // fit the data to both code books - int[] indices5 = new int[16]; - int[] indices7 = new int[16]; - int err5 = fitCodes(pixels, codes5, indices5); - int err7 = fitCodes(pixels, codes7, indices7); + final int[] indices5 = new int[16]; + final int[] indices7 = new int[16]; + final int err5 = fitCodes(pixels, codes5, indices5); + final int err7 = fitCodes(pixels, codes7, indices7); // save the block with the least error if (err5 <= err7) { @@ -1128,7 +1130,7 @@ public static void compressAlphaDxt5(int[] pixels, byte[] block) } } - private static void fixRange(int[] minMax, int steps) + private static void fixRange(final int[] minMax, final int steps) { if (minMax[1] - minMax[0] < steps) minMax[1] = Math.min(minMax[0] + steps, 255); @@ -1142,7 +1144,7 @@ private static int fitCodes(int[] pixels, int[] codes, int[] indices) int err = 0; for (int i = 0; i < 16; i++) { // find the least error and corresponding index - int value = ColorSet.argb(pixels[i], 3); + final int value = ColorSet.argb(pixels[i], 3); int least = Integer.MAX_VALUE; int index = 0; for (int j = 0; j < 8; j++) { @@ -1166,7 +1168,7 @@ private static int fitCodes(int[] pixels, int[] codes, int[] indices) return err; } - private static void writeAlphaBlock(int alpha0, int alpha1, int[] indices, byte[] block) + private static void writeAlphaBlock(final int alpha0, final int alpha1, final int[] indices, final byte[] block) { // write the first two bytes block[0] = (byte)(alpha0 & 0xff); @@ -1183,20 +1185,20 @@ private static void writeAlphaBlock(int alpha0, int alpha1, int[] indices, byte[ // store in 3 bytes for (int j = 0; j < 3; j++) { - byte b = (byte)((value >>> (8*j)) & 0xff); + final byte b = (byte)((value >>> (8*j)) & 0xff); block[dstIdx++] = b; } } } - private static void writeAlphaBlock5(int alpha0, int alpha1, int[] indices, byte[] block) + private static void writeAlphaBlock5(final int alpha0, final int alpha1, final int[] indices, final byte[] block) { // check the relative values of the endpoints if (alpha0 > alpha1) { // swap the indices - int[] swapped = new int[16]; + final int[] swapped = new int[16]; for (int i = 0; i < 16; i++) { - int index = indices[i]; + final int index = indices[i]; if (index == 0) { swapped[i] = 1; } else if (index == 1) { @@ -1217,13 +1219,13 @@ private static void writeAlphaBlock5(int alpha0, int alpha1, int[] indices, byte } } - private static void writeAlphaBlock7(int alpha0, int alpha1, int[] indices, byte[] block) + private static void writeAlphaBlock7(final int alpha0, final int alpha1, final int[] indices, final byte[] block) { if (alpha0 < alpha1) { // swap the indices - int[] swapped = new int[16]; + final int[] swapped = new int[16]; for (int i = 0; i < 16; i++) { - int index = indices[i]; + final int index = indices[i]; if (index == 0) { swapped[i] = 1; } else if (index == 1) { @@ -1247,7 +1249,7 @@ private static final class SourceBlock { public final int start, end, error; - public SourceBlock(int start, int end, int error) + public SourceBlock(final int start, final int end, final int error) { this.start = start; this.end = end; @@ -1259,7 +1261,7 @@ private static final class SingleColorLookup { public final SourceBlock[] sources; - public SingleColorLookup(SourceBlock sb1, SourceBlock sb2) + public SingleColorLookup(final SourceBlock sb1, final SourceBlock sb2) { sources = new SourceBlock[]{sb1, sb2}; } @@ -2308,67 +2310,67 @@ public static class Vec3 { float vx, vy, vz; - public static Vec3 neg(Vec3 v) + public static Vec3 neg(final Vec3 v) { return new Vec3(-v.vx, -v.vy, -v.vz); } - public static Vec3 add(Vec3 v1, Vec3 v2) + public static Vec3 add(final Vec3 v1, final Vec3 v2) { return new Vec3(v1.vx + v2.vx, v1.vy + v2.vy, v1.vz + v2.vz); } - public static Vec3 sub(Vec3 v1, Vec3 v2) + public static Vec3 sub(final Vec3 v1, final Vec3 v2) { return new Vec3(v1.vx - v2.vx, v1.vy - v2.vy, v1.vz - v2.vz); } - public static Vec3 mul(Vec3 v1, Vec3 v2) + public static Vec3 mul(final Vec3 v1, final Vec3 v2) { return new Vec3(v1.vx * v2.vx, v1.vy * v2.vy, v1.vz * v2.vz); } - public static Vec3 mul(Vec3 v, float s) + public static Vec3 mul(final Vec3 v, final float s) { return new Vec3(v.vx * s, v.vy * s, v.vz * s); } - public static Vec3 div(Vec3 v1, Vec3 v2) + public static Vec3 div(final Vec3 v1, final Vec3 v2) { return new Vec3(v1.vx / v2.vx, v1.vy / v2.vy, v1.vz / v2.vz); } - public static Vec3 div(Vec3 v, float s) + public static Vec3 div(final Vec3 v, final float s) { - float t = 1.0f / s; + final float t = 1.0f / s; return new Vec3(v.vx * t, v.vy * t, v.vz * t); } - public static float dot(Vec3 v1, Vec3 v2) + public static float dot(final Vec3 v1, final Vec3 v2) { return v1.vx*v2.vx + v1.vy*v2.vy + v1.vz*v2.vz; } - public static Vec3 min(Vec3 v1, Vec3 v2) + public static Vec3 min(final Vec3 v1, final Vec3 v2) { v1.fixNaN(); v2.fixNaN(); return new Vec3(Math.min(v1.vx, v2.vx), Math.min(v1.vy, v2.vy), Math.min(v1.vz, v2.vz)); } - public static Vec3 max(Vec3 v1, Vec3 v2) + public static Vec3 max(final Vec3 v1, final Vec3 v2) { v1.fixNaN(); v2.fixNaN(); return new Vec3(Math.max(v1.vx, v2.vx), Math.max(v1.vy, v2.vy), Math.max(v1.vz, v2.vz)); } - public static Vec3 truncate(Vec3 v) + public static Vec3 truncate(final Vec3 v) { return new Vec3((v.vx > 0.0f) ? (float)Math.floor(v.vx) : (float)Math.ceil(v.vx), (v.vy > 0.0f) ? (float)Math.floor(v.vy) : (float)Math.ceil(v.vy), (v.vz > 0.0f) ? (float)Math.floor(v.vz) : (float)Math.ceil(v.vz)); } - public static float lengthSquared(Vec3 v) + public static float lengthSquared(final Vec3 v) { return v.vx*v.vx + v.vy*v.vy + v.vz*v.vz; } @@ -2378,12 +2380,12 @@ public Vec3() vx = 0.0f; vy = 0.0f; vz = 0.0f; } - public Vec3(float s) + public Vec3(final float s) { vx = s; vy = s; vz = s; } - public Vec3(float x, float y, float z) + public Vec3(final float x, final float y, final float z) { vx = x; vy = y; vz = z; } @@ -2416,7 +2418,7 @@ public Object clone() public float y() { return vy; } public float z() { return vz; } - public Vec3 add(Vec3 v) + public Vec3 add(final Vec3 v) { vx += v.vx; vy += v.vy; @@ -2424,7 +2426,7 @@ public Vec3 add(Vec3 v) return this; } - public Vec3 sub(Vec3 v) + public Vec3 sub(final Vec3 v) { vx -= v.vx; vy -= v.vy; @@ -2432,7 +2434,7 @@ public Vec3 sub(Vec3 v) return this; } - public Vec3 mul(Vec3 v) + public Vec3 mul(final Vec3 v) { vx *= v.vx; vy *= v.vy; @@ -2440,7 +2442,7 @@ public Vec3 mul(Vec3 v) return this; } - public Vec3 mul(float s) + public Vec3 mul(final float s) { vx *= s; vy *= s; @@ -2448,7 +2450,7 @@ public Vec3 mul(float s) return this; } - public Vec3 div(Vec3 v) + public Vec3 div(final Vec3 v) { vx /= v.vx; vy /= v.vy; @@ -2456,16 +2458,16 @@ public Vec3 div(Vec3 v) return this; } - public Vec3 div(float s) + public Vec3 div(final float s) { - float t = 1.0f / s; + final float t = 1.0f / s; vx *= t; vy *= t; vz *= t; return this; } - public float dot(Vec3 v) + public float dot(final Vec3 v) { return vx*v.vx + vy*v.vy + vz*v.vz; } @@ -2490,37 +2492,37 @@ public static class Vec4 // return new Vec4(-v.vx, -v.vy, -v.vz, -v.vw); // } - public static Vec4 add(Vec4 v1, Vec4 v2) + public static Vec4 add(final Vec4 v1, final Vec4 v2) { return new Vec4(v1.vx + v2.vx, v1.vy + v2.vy, v1.vz + v2.vz, v1.vw + v2.vw); } - public static Vec4 sub(Vec4 v1, Vec4 v2) + public static Vec4 sub(final Vec4 v1, final Vec4 v2) { return new Vec4(v1.vx - v2.vx, v1.vy - v2.vy, v1.vz - v2.vz, v1.vw - v2.vw); } - public static Vec4 mul(Vec4 v1, Vec4 v2) + public static Vec4 mul(final Vec4 v1, final Vec4 v2) { return new Vec4(v1.vx * v2.vx, v1.vy * v2.vy, v1.vz * v2.vz, v1.vw * v2.vw); } - public static Vec4 multiplyAdd(Vec4 v1, Vec4 v2, Vec4 v3) + public static Vec4 multiplyAdd(final Vec4 v1, final Vec4 v2, final Vec4 v3) { return new Vec4(v1.vx*v2.vx + v3.vx, v1.vy*v2.vy + v3.vy, v1.vz*v2.vz + v3.vz, v1.vw*v2.vw + v3.vw); } - public static Vec4 negMulSub(Vec4 v1, Vec4 v2, Vec4 v3) + public static Vec4 negMulSub(final Vec4 v1, final Vec4 v2, final Vec4 v3) { return new Vec4(v3.vx - v1.vx*v2.vx, v3.vy - v1.vy*v2.vy, v3.vz - v1.vz*v2.vz, v3.vw - v1.vw*v2.vw); } - public static Vec4 reciprocal(Vec4 v) + public static Vec4 reciprocal(final Vec4 v) { return new Vec4(1.0f / v.vx, 1.0f / v.vy, 1.0f / v.vz, 1.0f / v.vw); } - public static Vec4 min(Vec4 v1, Vec4 v2) + public static Vec4 min(final Vec4 v1, final Vec4 v2) { v1.fixNaN(); v2.fixNaN(); return new Vec4(Math.min(v1.vx, v2.vx), @@ -2530,7 +2532,7 @@ public static Vec4 min(Vec4 v1, Vec4 v2) } - public static Vec4 max(Vec4 v1, Vec4 v2) + public static Vec4 max(final Vec4 v1, final Vec4 v2) { v1.fixNaN(); v2.fixNaN(); return new Vec4(Math.max(v1.vx, v2.vx), @@ -2539,7 +2541,7 @@ public static Vec4 max(Vec4 v1, Vec4 v2) Math.max(v1.vw, v2.vw)); } - public static Vec4 truncate(Vec4 v) + public static Vec4 truncate(final Vec4 v) { return new Vec4((v.vx > 0.0f) ? (float)Math.floor(v.vx) : (float)Math.ceil(v.vx), (v.vy > 0.0f) ? (float)Math.floor(v.vy) : (float)Math.ceil(v.vy), @@ -2547,7 +2549,7 @@ public static Vec4 truncate(Vec4 v) (v.vw > 0.0f) ? (float)Math.floor(v.vw) : (float)Math.ceil(v.vw)); } - public static boolean compareAnyLessThan(Vec4 v1, Vec4 v2) + public static boolean compareAnyLessThan(final Vec4 v1, final Vec4 v2) { return (v1.vx < v2.vx || v1.vy < v2.vy || v1.vz < v2.vz || v1.vw < v2.vw); } @@ -2557,12 +2559,12 @@ public Vec4() vx = 0.0f; vy = 0.0f; vz = 0.0f; vw = 0.0f; } - public Vec4(float s) + public Vec4(final float s) { vx = s; vy = s; vz = s; vw = s; } - public Vec4(float x, float y, float z, float w) + public Vec4(final float x, final float y, final float z, final float w) { vx = x; vy = y; vz = z; vw = w; } @@ -2603,7 +2605,7 @@ public Object clone() public float z() { return vz; } public float w() { return vw; } - public Vec4 add(Vec4 v) + public Vec4 add(final Vec4 v) { vx += v.vx; vy += v.vy; @@ -2612,7 +2614,7 @@ public Vec4 add(Vec4 v) return this; } - public Vec4 sub(Vec4 v) + public Vec4 sub(final Vec4 v) { vx -= v.vx; vy -= v.vy; @@ -2621,7 +2623,7 @@ public Vec4 sub(Vec4 v) return this; } - public Vec4 mul(Vec4 v) + public Vec4 mul(final Vec4 v) { vx *= v.vx; vy *= v.vy; @@ -2648,11 +2650,11 @@ public static class Sym3x3 private final float[] m; - public static Sym3x3 computeWeightedCovariance(int count, Vec3[] points, float[] weights) + public static Sym3x3 computeWeightedCovariance(final int count, final Vec3[] points, final float[] weights) { // computing the centroid float total = 0.0f; - Vec3 centroid = new Vec3(); + final Vec3 centroid = new Vec3(); for (int i = 0; i < count; i++) { total += weights[i]; centroid.add(Vec3.mul(points[i], weights[i])); @@ -2660,10 +2662,10 @@ public static Sym3x3 computeWeightedCovariance(int count, Vec3[] points, float[] centroid.div(total); // accumulating the covariance matrix - Sym3x3 covariance = new Sym3x3(0.0f); + final Sym3x3 covariance = new Sym3x3(0.0f); for (int i = 0; i < count; i++) { - Vec3 a = Vec3.sub(points[i], centroid); - Vec3 b = Vec3.mul(a, weights[i]); + final Vec3 a = Vec3.sub(points[i], centroid); + final Vec3 b = Vec3.mul(a, weights[i]); covariance.m[0] += a.x()*b.x(); covariance.m[1] += a.x()*b.y(); @@ -2676,28 +2678,28 @@ public static Sym3x3 computeWeightedCovariance(int count, Vec3[] points, float[] return covariance; } - public static Vec3 computePrincipleComponent(Sym3x3 matrix) + public static Vec3 computePrincipleComponent(final Sym3x3 matrix) { // computing the cubic coefficients - float c0 = matrix.m[0] * matrix.m[3] * matrix.m[5] + - 2.0f * matrix.m[1] * matrix.m[2] * matrix.m[4] - - matrix.m[0] * matrix.m[4] * matrix.m[4] - - matrix.m[3] * matrix.m[2] * matrix.m[2] - - matrix.m[5] * matrix.m[1] * matrix.m[1]; - float c1 = matrix.m[0] * matrix.m[3] + - matrix.m[0] * matrix.m[5] + - matrix.m[3] * matrix.m[5] - - matrix.m[1] * matrix.m[1] - - matrix.m[2] * matrix.m[2] - - matrix.m[4] * matrix.m[4]; - float c2 = matrix.m[0] + matrix.m[3] + matrix.m[5]; + final float c0 = matrix.m[0] * matrix.m[3] * matrix.m[5] + + 2.0f * matrix.m[1] * matrix.m[2] * matrix.m[4] - + matrix.m[0] * matrix.m[4] * matrix.m[4] - + matrix.m[3] * matrix.m[2] * matrix.m[2] - + matrix.m[5] * matrix.m[1] * matrix.m[1]; + final float c1 = matrix.m[0] * matrix.m[3] + + matrix.m[0] * matrix.m[5] + + matrix.m[3] * matrix.m[5] - + matrix.m[1] * matrix.m[1] - + matrix.m[2] * matrix.m[2] - + matrix.m[4] * matrix.m[4]; + final float c2 = matrix.m[0] + matrix.m[3] + matrix.m[5]; // computing the quadratic coefficients - float a = c1 - (1.0f/3.0f)*c2*c2; - float b = (-2.0f/27.0f)*c2*c2*c2 + (1.0f/3.0f)*c1*c2 - c0; + final float a = c1 - (1.0f/3.0f)*c2*c2; + final float b = (-2.0f/27.0f)*c2*c2*c2 + (1.0f/3.0f)*c1*c2 - c0; // computing the root count check - float Q = 0.25f*b*b + (1.0f/27.0f)*a*a*a; + final float Q = 0.25f*b*b + (1.0f/27.0f)*a*a*a; // testing the multiplicity if (FLT_EPSILON < Q) { @@ -2705,12 +2707,12 @@ public static Vec3 computePrincipleComponent(Sym3x3 matrix) return new Vec3(1.0f); } else if (Q < -FLT_EPSILON) { // three distinct roots - double theta = Math.atan2(Math.sqrt(-Q), -0.5*b); - double rho = Math.sqrt(0.25*b*b - Q); + final double theta = Math.atan2(Math.sqrt(-Q), -0.5*b); + final double rho = Math.sqrt(0.25*b*b - Q); - float rt = (float)Math.pow(rho, 1.0/3.0); - float ct = (float)Math.cos(theta/3.0); - float st = (float)Math.sin(theta/3.0); + final float rt = (float)Math.pow(rho, 1.0/3.0); + final float ct = (float)Math.cos(theta/3.0); + final float st = (float)Math.sin(theta/3.0); float l1 = (1.0f / 3.0f)*c2 + 2.0f*rt*ct; float l2 = (1.0f / 3.0f)*c2 - rt*(ct + (float)Math.sqrt(3.0)*st); @@ -2726,10 +2728,10 @@ public static Vec3 computePrincipleComponent(Sym3x3 matrix) return getMultiplicity1Evector(matrix, l1); } else { // if (-FLT_EPSILON <= Q && Q <= FLT_EPSILON) // two roots - float rt = (float)((b < 0.0f) ? -Math.pow(-0.5*b, 1.0/3.0) : Math.pow(0.5*b, 1.0/3.0)); + final float rt = (float)((b < 0.0f) ? -Math.pow(-0.5*b, 1.0/3.0) : Math.pow(0.5*b, 1.0/3.0)); - float l1 = (1.0f/3.0f)*c2 + rt; // repeated - float l2 = (1.0f/3.0f)*c2 - 2.0f*rt; + final float l1 = (1.0f/3.0f)*c2 + rt; // repeated + final float l2 = (1.0f/3.0f)*c2 - 2.0f*rt; // getting the eigenvector if (Math.abs(l1) > Math.abs(l2)) { @@ -2740,13 +2742,13 @@ public static Vec3 computePrincipleComponent(Sym3x3 matrix) } } - private static Vec3 getMultiplicity1Evector(Sym3x3 matrix, float evalue) + private static Vec3 getMultiplicity1Evector(final Sym3x3 matrix, final float evalue) { if (matrix == null) throw new NullPointerException(); // computing M - Sym3x3 m = new Sym3x3(); + final Sym3x3 m = new Sym3x3(); m.m[0] = matrix.m[0] - evalue; m.m[1] = matrix.m[1]; m.m[2] = matrix.m[2]; @@ -2755,7 +2757,7 @@ private static Vec3 getMultiplicity1Evector(Sym3x3 matrix, float evalue) m.m[5] = matrix.m[5] - evalue; // computing U - Sym3x3 u = new Sym3x3(); + final Sym3x3 u = new Sym3x3(); u.m[0] = m.m[3]*m.m[5] - m.m[4]*m.m[4]; u.m[1] = m.m[2]*m.m[4] - m.m[1]*m.m[5]; u.m[2] = m.m[1]*m.m[4] - m.m[2]*m.m[3]; @@ -2786,13 +2788,13 @@ private static Vec3 getMultiplicity1Evector(Sym3x3 matrix, float evalue) } } - private static Vec3 getMultiplicity2Evector(Sym3x3 matrix, float evalue) + private static Vec3 getMultiplicity2Evector(final Sym3x3 matrix, final float evalue) { if (matrix == null) throw new NullPointerException(); // computing M - Sym3x3 m = new Sym3x3(); + final Sym3x3 m = new Sym3x3(); m.m[0] = matrix.m[0] - evalue; m.m[1] = matrix.m[1]; m.m[2] = matrix.m[2]; @@ -2804,7 +2806,7 @@ private static Vec3 getMultiplicity2Evector(Sym3x3 matrix, float evalue) float mc = Math.abs(m.m[0]); int mi = 0; for (int i = 1; i < 6; i++) { - float c = Math.abs(m.m[i]); + final float c = Math.abs(m.m[i]); if (c > mc) { mc = c; mi = i; @@ -2834,7 +2836,7 @@ public Sym3x3() } } - public Sym3x3(float s) + public Sym3x3(final float s) { m = new float[6]; for (int i = 0; i < m.length; i++) { @@ -2851,17 +2853,17 @@ public String toString() @Override public Object clone() { - Sym3x3 s = new Sym3x3(); + final Sym3x3 s = new Sym3x3(); s.m[0] = m[0]; s.m[1] = m[1]; s.m[2] = m[2]; s.m[3] = m[3]; s.m[4] = m[4]; s.m[5] = m[5]; return s; } - public float get(int idx) + public float get(final int idx) { return m[idx]; } - public Sym3x3 set(int idx, float s) + public Sym3x3 set(final int idx, final float s) { m[idx] = s; return this; @@ -2870,7 +2872,7 @@ public Sym3x3 set(int idx, float s) private static final class Misc { - public static int floatToInt(float a, int limit) + public static int floatToInt(final float a, final int limit) { // use ANSI round-to-zero behavior to get round-to-nearest int i = (int)(a + 0.5f); From a8d09c410b708b2a359e6b49db73123b0fa8a77b Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Wed, 14 Apr 2021 23:40:31 +0200 Subject: [PATCH 130/158] SpriteDecoder: Fix detection of max. armor code for character-type animations --- src/org/infinity/resource/cre/decoder/CharacterDecoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/infinity/resource/cre/decoder/CharacterDecoder.java b/src/org/infinity/resource/cre/decoder/CharacterDecoder.java index 32501df2e..f6200fb16 100644 --- a/src/org/infinity/resource/cre/decoder/CharacterDecoder.java +++ b/src/org/infinity/resource/cre/decoder/CharacterDecoder.java @@ -289,7 +289,7 @@ protected void setMaxArmorCode(int v) { if (v < 0) { // autodetection: requires fully initialized resref definitions - for (int i = 1; i < 10 && v < 0; i++) { + for (int i = 9; i > 0 && v < 0; i--) { String resref = getArmorSpecificResref(); if (!resref.isEmpty() && ResourceFactory.resourceExists(resref + i + "G1.BAM")) { v = i; From e1a12f6f5dbe03bda3b53bc62ea23b9f8e3d1636 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Thu, 15 Apr 2021 21:02:40 +0200 Subject: [PATCH 131/158] SpriteDecoder: Improve detection of max. armor code for character-type animations --- .../resource/cre/decoder/CharacterDecoder.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/org/infinity/resource/cre/decoder/CharacterDecoder.java b/src/org/infinity/resource/cre/decoder/CharacterDecoder.java index f6200fb16..4e76f8f73 100644 --- a/src/org/infinity/resource/cre/decoder/CharacterDecoder.java +++ b/src/org/infinity/resource/cre/decoder/CharacterDecoder.java @@ -289,10 +289,17 @@ protected void setMaxArmorCode(int v) { if (v < 0) { // autodetection: requires fully initialized resref definitions - for (int i = 9; i > 0 && v < 0; i--) { - String resref = getArmorSpecificResref(); - if (!resref.isEmpty() && ResourceFactory.resourceExists(resref + i + "G1.BAM")) { - v = i; + final String[] resrefs = { getArmorBaseResref(), getArmorSpecificResref() }; + if (getArmorBaseResref().equalsIgnoreCase(getArmorSpecificResref())) { + resrefs[1] = null; + } + for (final String resref : resrefs) { + if (resref != null && !resref.isEmpty()) { + for (int i = 1; i < 10; i++) { + if (ResourceFactory.resourceExists(resref + i + "G1.BAM")) { + v = Math.max(v, i); + } + } } } } From 88b361780a39b1d0f75d3b7dd6b3d6050c932500 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 16 Apr 2021 22:11:57 +0200 Subject: [PATCH 132/158] CreatureDecoder: Fix unavailable character-type animations for selected creature animations Bug was triggered if specific resref didn't provide animations for max. armor code. --- .../resource/cre/decoder/CharacterDecoder.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/org/infinity/resource/cre/decoder/CharacterDecoder.java b/src/org/infinity/resource/cre/decoder/CharacterDecoder.java index 4e76f8f73..fff5cb232 100644 --- a/src/org/infinity/resource/cre/decoder/CharacterDecoder.java +++ b/src/org/infinity/resource/cre/decoder/CharacterDecoder.java @@ -319,8 +319,11 @@ public List getAnimationFiles(boolean essential) String suffix = SegmentDef.fixBehaviorSuffix(entry.getValue().getValue0()); if (suffix.startsWith("G")) { for (int i = 1; i <= getMaxArmorCode(); i++) { - String resref = (i < getMaxArmorCode()) ? resref1 : resref2; - files.add(resref + i + suffix + ".BAM"); + String resref = resref2 + i + suffix + ".BAM"; + if (!ResourceFactory.resourceExists(resref)) { + resref = resref1 + i + suffix + ".BAM"; + } + files.add(resref); } } } @@ -336,9 +339,12 @@ public List getAnimationFiles(boolean essential) // generating file list retVal = new ArrayList() {{ for (int i = 1; i <= getMaxArmorCode(); i++) { - String resref = (i < getMaxArmorCode()) ? resref1 : resref2; for (final String a : actionSet) { - add(resref + i + a + ".BAM"); + String resref = resref2 + i + a + ".BAM"; + if (!ResourceFactory.resourceExists(resref)) { + resref = resref1 + i + a + ".BAM"; + } + add(resref); } } }}; From 323691f3856f68ece0ae543cc86dd8bfffb32098 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 16 Apr 2021 22:27:27 +0200 Subject: [PATCH 133/158] SpriteDecoder: Rewrite effect manager ColorInfo class is replaced by EffectInfo class: - potential support of the full range of available effect opcodes - supports nested effects - improved opcode filtering - improved performance --- .../resource/cre/decoder/SpriteDecoder.java | 15 +- .../resource/cre/decoder/util/ColorInfo.java | 162 --- .../cre/decoder/util/CreatureInfo.java | 200 ++- .../resource/cre/decoder/util/EffectInfo.java | 1096 +++++++++++++++++ .../resource/cre/decoder/util/ItemInfo.java | 141 +-- .../cre/decoder/util/SpriteUtils.java | 12 +- 6 files changed, 1232 insertions(+), 394 deletions(-) delete mode 100644 src/org/infinity/resource/cre/decoder/util/ColorInfo.java create mode 100644 src/org/infinity/resource/cre/decoder/util/EffectInfo.java diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index 12704b6fb..b80ad2ba3 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -38,12 +38,12 @@ import org.infinity.resource.ResourceFactory; import org.infinity.resource.cre.CreResource; import org.infinity.resource.cre.decoder.util.AnimationInfo; -import org.infinity.resource.cre.decoder.util.ColorInfo; import org.infinity.resource.cre.decoder.util.CreatureInfo; import org.infinity.resource.cre.decoder.util.CycleDef; import org.infinity.resource.cre.decoder.util.DecoderAttribute; import org.infinity.resource.cre.decoder.util.DirDef; import org.infinity.resource.cre.decoder.util.Direction; +import org.infinity.resource.cre.decoder.util.EffectInfo; import org.infinity.resource.cre.decoder.util.FrameInfo; import org.infinity.resource.cre.decoder.util.ItemInfo; import org.infinity.resource.cre.decoder.util.SegmentDef; @@ -1600,12 +1600,15 @@ protected void applyFalseColors(BamV1Control control, SegmentDef sd) if (sd.getSpriteType() == SegmentDef.SpriteType.SHIELD) { ItemInfo itemInfo = getCreatureInfo().getEquippedShield(); if (itemInfo != null && itemInfo.getSlotType() == ItemInfo.SlotType.WEAPON) { - ColorInfo colorInfo = itemInfo.getColorInfo(); - for (int loc = 0; loc < 7; loc++) { + EffectInfo effectInfo = itemInfo.getEffectInfo(); + List fxList = effectInfo.getEffects(getCreatureInfo(), + SegmentDef.SpriteType.WEAPON, + EffectInfo.OPCODE_SET_COLOR); + for (final EffectInfo.Effect fx : fxList) { + int loc = fx.getParameter2() & 0xf; int ofs = getColorOffset(loc); - int colIdx = colorInfo.getValue(SegmentDef.SpriteType.WEAPON, ColorInfo.OPCODE_SET_COLOR, loc); - if (ofs > 0 && colIdx >= 0) { - int[] range = getColorData(colIdx, false); + if (ofs > 0) { + int[] range = getColorData(fx.getParameter1(), false); if (range != null) { colorRanges.put(ofs, range); } diff --git a/src/org/infinity/resource/cre/decoder/util/ColorInfo.java b/src/org/infinity/resource/cre/decoder/util/ColorInfo.java deleted file mode 100644 index e7ab5217a..000000000 --- a/src/org/infinity/resource/cre/decoder/util/ColorInfo.java +++ /dev/null @@ -1,162 +0,0 @@ -// Near Infinity - An Infinity Engine Browser and Editor -// Copyright (C) 2001 - 2021 Jon Olav Hauglid -// See LICENSE.txt for license information - -package org.infinity.resource.cre.decoder.util; - -import java.util.Arrays; -import java.util.Collections; -import java.util.EnumMap; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -import org.infinity.util.tuples.Couple; - -/** - * Provides information about color definitions for sprites and sprite overlays. - */ -public class ColorInfo -{ - /** Effect opcode 7: Set color */ - public static final int OPCODE_SET_COLOR = 7; - /** Effect opcode 8: Set color glow solid */ - public static final int OPCODE_SET_COLOR_GLOW = 8; - /** Effect opcode 51: Character tint solid */ - public static final int OPCODE_TINT_SOLID = 51; - /** Effect opcode 52: Character tint bright */ - public static final int OPCODE_TINT_BRIGHT = 52; - /** Effect opcode 64: Blur */ - public static final int OPCODE_BLUR = 65; - /** Effect opcode 64: Translucency */ - public static final int OPCODE_TRANSLUCENCY = 66; - /** Effect opcode 134: Petrification */ - public static final int OPCODE_PETRIFICATION = 134; - /** Effect opcode 218: Stoneskin */ - public static final int OPCODE_STONESKIN = 218; - - private static final int[] EMPTY_INT_ARRAY = new int[0]; - - // Maps value (Map, color value>) to an individual sprite overlay types (avatar, weapon, shield, ...) - private final EnumMap, Integer>> colorMap = new EnumMap<>(SegmentDef.SpriteType.class); - - public ColorInfo() - { - } - - /** Returns an iterator over the sprite overlay types for color definitions. */ - public Iterator getTypeIterator() { return colorMap.keySet().iterator(); } - - /** Returns an array of sprite overlay types for color definitions. */ - public SegmentDef.SpriteType[] getTypes() { return colorMap.keySet().toArray(new SegmentDef.SpriteType[colorMap.size()]); } - - /** Returns an iterator over the color locations for the specified sprite overlay type. */ - public Iterator> getEffectIterator(SegmentDef.SpriteType type) - { - Map, Integer> map = colorMap.get(type); - if (map != null) { - return map.keySet().iterator(); - } - return Collections.>emptyList().iterator(); // empty iterator - } - - /** Returns an array of color location indices for the specified sprite overlay type. */ - public int[] getLocations(SegmentDef.SpriteType type, int opcode) - { - int[] retVal = EMPTY_INT_ARRAY; - Map, Integer> map = colorMap.get(type); - if (map != null) { - retVal = new int[map.size()]; - int i = 0; - for (final Couple pair : map.keySet()) { - if (pair.getValue0().intValue() == opcode) { - retVal[i] = pair.getValue1().intValue(); - i++; - } - } - if (i < retVal.length) { - retVal = Arrays.copyOf(retVal, i); - } - } - return retVal; - } - - /** - * Returns the color value for the specified sprite overlay type and location index. - * Returns -1 if value is not available. - */ - public int getValue(SegmentDef.SpriteType type, int opcode, int index) - { - Map, Integer> map = colorMap.get(type); - if (map != null) { - Integer v = map.get(Couple.with(opcode, index)); - if (v != null) { - return v.intValue(); - } - } - return -1; - } - - /** - * Adds a color entry and associates it with a sprite overlay type and color location. - * Existing color entries will be updated. - * @param type the sprite overlay type. - * @param opcode the effect opcode. - * @param location the color location. - * @param value the unprocessed color value. - */ - public void add(SegmentDef.SpriteType type, int opcode, int location, int value) - { - if (type == null) { - return; - } - if (location >= -1 && location < 7) { - Map, Integer> map = colorMap.get(type); - if (map == null) { - map = new HashMap<>(); - } - - // swapping byte order of color value - switch (opcode) { - case OPCODE_SET_COLOR_GLOW: - case OPCODE_TINT_SOLID: - case OPCODE_TINT_BRIGHT: - { - int tmp = 0; - for (int i = 0; i < 4; i++) { - tmp <<= 8; - tmp |= value & 0xff; - value >>= 8; - } - value = tmp; - break; - } - } - - map.put(Couple.with(opcode, location), value); - colorMap.put(type, map); - } - } - - @Override - public int hashCode() - { - int hash = 7; - hash = 31 * hash + ((colorMap == null) ? 0 : colorMap.hashCode()); - return hash; - } - - @Override - public boolean equals(Object o) - { - if (o == this) { - return true; - } - if (!(o instanceof ColorInfo)) { - return false; - } - ColorInfo other = (ColorInfo)o; - return (this.colorMap == null && other.colorMap == null) || - (this.colorMap != null && this.colorMap.equals(other.colorMap)); - } -} diff --git a/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java b/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java index 6c5ff9cee..0a34bbc35 100644 --- a/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java +++ b/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java @@ -10,12 +10,11 @@ import java.util.EnumMap; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Objects; +import java.util.function.Predicate; -import org.infinity.datatype.EffectType; import org.infinity.datatype.Flag; import org.infinity.datatype.IsNumeric; import org.infinity.datatype.IsTextual; @@ -73,7 +72,7 @@ public enum ItemSlots { } private final EnumMap equipment = new EnumMap<>(ItemSlots.class); - private final ColorInfo colorInfo = new ColorInfo(); // storage for color-related effects applied to the creature + private final EffectInfo effectInfo; private final SpriteDecoder decoder; private final CreResource cre; @@ -81,12 +80,16 @@ public enum ItemSlots { public CreatureInfo(SpriteDecoder decoder, CreResource cre) throws Exception { + this.effectInfo = new EffectInfo(); this.decoder = Objects.requireNonNull(decoder, "SpriteDecoder instance cannot be null"); this.cre = Objects.requireNonNull(cre, "CRE resource cannot be null"); this.allegianceOverride = OVERRIDE_NONE; init(); } + /** Returns the {@code SpriteDecoder} instance associated with the creature resource. */ + public SpriteDecoder getDecoder() { return decoder; } + /** Returns the {@code CreResource} instance of the creature resource. */ public CreResource getCreResource() { return cre; } @@ -107,9 +110,7 @@ public boolean isStatusPanic() */ public boolean isStoneEffect() { - return ((getStatus() & (1 << 7)) != 0) || - isEffectActive(SegmentDef.SpriteType.AVATAR, ColorInfo.OPCODE_PETRIFICATION, -1) || - isEffectActive(SegmentDef.SpriteType.AVATAR, ColorInfo.OPCODE_STONESKIN, -1); + return ((getStatus() & (1 << 7)) != 0) || isEffectActive(SegmentDef.SpriteType.AVATAR, EffectInfo.FILTER_STONE_EFFECT); } /** Returns {@code true} if the creature status is set to "frozen death". */ @@ -127,7 +128,7 @@ public boolean isBurnedEffect() /** Returns {@code true} if the creature is under the effect of blur. */ public boolean isBlurEffect() { - return isEffectActive(SegmentDef.SpriteType.AVATAR, ColorInfo.OPCODE_BLUR, -1); + return isEffectActive(SegmentDef.SpriteType.AVATAR, EffectInfo.FILTER_BLUR_EFFECT); } /** Returns the creature animation id. */ @@ -165,16 +166,18 @@ public int getEffectiveTranslucency() for (final ItemSlots slot : ItemSlots.values()) { ItemInfo info = getItemInfo(slot); if (info != null) { - int amount = info.getColorInfo().getValue(SegmentDef.SpriteType.AVATAR, ColorInfo.OPCODE_TRANSLUCENCY, -1); - if (amount >= 0) { + EffectInfo.Effect fx = info.getEffectInfo().getFirstEffect(this, SegmentDef.SpriteType.AVATAR, EffectInfo.FILTER_TRANSLUCENCY_EFFECT); + if (fx != null && fx.getParameter2() == 0) { + int amount = Math.max(0, Math.min(255, fx.getParameter1())); retVal = Math.max(retVal, 255 - amount); } } } // getting translucency from creature effect - int amount = getColorInfo().getValue(SegmentDef.SpriteType.AVATAR, ColorInfo.OPCODE_TRANSLUCENCY, -1); - if (amount >= 0) { + EffectInfo.Effect fx = getEffectInfo().getFirstEffect(this, SegmentDef.SpriteType.AVATAR, EffectInfo.FILTER_TRANSLUCENCY_EFFECT); + if (fx != null && fx.getParameter2() == 0) { + int amount = Math.max(0, Math.min(255, fx.getParameter1())); retVal = Math.max(retVal, 255 - amount); } @@ -388,11 +391,8 @@ public ItemInfo[] getEffectiveItemInfo() return items.toArray(new ItemInfo[items.size()]); } - /** - * Provides access to the {@link ColorInfo} instance which contains color definitions - * set by effects applied to the creature. - */ - public ColorInfo getColorInfo() { return colorInfo; } + /** Provides access to the {@link EffectInfo} instance which manages effects attached to the current creature. */ + public EffectInfo getEffectInfo() { return effectInfo; } /** * Returns the number of defined color entries for the creature. @@ -449,11 +449,9 @@ public int getColorCount(SegmentDef.SpriteType type) if (itemInfo != null) { HashSet set = new HashSet<>(); - for (Iterator> iter = itemInfo.getColorInfo().getEffectIterator(type); iter.hasNext(); ) { - Couple pair = iter.next(); - if (pair.getValue0().intValue() == ColorInfo.OPCODE_SET_COLOR) { - set.add(pair.getValue1()); - } + List fxList = itemInfo.getEffectInfo().getEffects(this, type, EffectInfo.FILTER_COLOR_EFFECT); + for (final EffectInfo.Effect fx : fxList) { + set.add(fx.getParameter2() & 0xf); } retVal = set.size(); } @@ -514,7 +512,10 @@ public int getColorValue(SegmentDef.SpriteType type, int locationIndex) } if (itemInfo != null) { - retVal = itemInfo.getColorInfo().getValue(type, ColorInfo.OPCODE_SET_COLOR, locationIndex); + EffectInfo.Effect fx = itemInfo.getEffectInfo().getColorByLocation(this, type, EffectInfo.OPCODE_SET_COLOR, locationIndex); + if (fx != null) { + retVal = fx.getParameter1(); + } } return retVal; @@ -559,17 +560,17 @@ public Couple getEffectiveColorValue(SegmentDef.SpriteType typ // checking equipped items ItemInfo[] itemInfos = getEffectiveItemInfo(); for (final ItemInfo info : itemInfos) { - int v = info.getColorInfo().getValue(type, ColorInfo.OPCODE_SET_COLOR, locationIndex); - if (v >= 0) { - retVal.setValue0(v); + EffectInfo.Effect fx = info.getEffectInfo().getColorByLocation(this, type, EffectInfo.OPCODE_SET_COLOR, locationIndex); + if (fx != null) { + retVal.setValue0(fx.getParameter1()); retVal.setValue1(Boolean.FALSE); } } // checking creature effects - int v = getColorInfo().getValue(type, ColorInfo.OPCODE_SET_COLOR, locationIndex); - if (v >= 0) { - retVal.setValue0(v); + EffectInfo.Effect fx = getEffectInfo().getColorByLocation(this, type, EffectInfo.OPCODE_SET_COLOR, locationIndex); + if (fx != null) { + retVal.setValue0(fx.getParameter1()); retVal.setValue1(Boolean.FALSE); } @@ -589,7 +590,8 @@ public int getTintValue(SegmentDef.SpriteType type, int opcode, int locationInde type = SegmentDef.SpriteType.AVATAR; } - return getColorInfo().getValue(type, opcode, locationIndex); + EffectInfo.Effect fx = getEffectInfo().getColorByLocation(this, type, opcode, locationIndex); + return (fx != null) ? EffectInfo.swapBytes(fx.getParameter1()) : -1; } /** @@ -608,19 +610,18 @@ public Couple getEffectiveTintValue(SegmentDef.SpriteType type type = SegmentDef.SpriteType.AVATAR; } - int opcode, value; + final int[] opcodes = {EffectInfo.OPCODE_TINT_BRIGHT, EffectInfo.OPCODE_TINT_SOLID, EffectInfo.OPCODE_SET_COLOR_GLOW}; // checking equipped items + int opcode = -1, value = -1; ItemInfo[] itemInfos = getEffectiveItemInfo(); for (final ItemInfo info : itemInfos) { - opcode = ColorInfo.OPCODE_TINT_BRIGHT; - value = info.getColorInfo().getValue(type, opcode, locationIndex); - if (value == -1) { - opcode = ColorInfo.OPCODE_TINT_SOLID; - value = info.getColorInfo().getValue(type, opcode, locationIndex); - if (value == -1) { - opcode = ColorInfo.OPCODE_SET_COLOR_GLOW; - value = info.getColorInfo().getValue(type, opcode, locationIndex); + for (final int code : opcodes) { + final EffectInfo.Effect fx = info.getEffectInfo().getColorByLocation(this, type, code, locationIndex); + if (fx != null) { + opcode = code; + value = EffectInfo.swapBytes(fx.getParameter1()); + break; } } if (value != -1) { @@ -630,14 +631,13 @@ public Couple getEffectiveTintValue(SegmentDef.SpriteType type } // checking creature effects - opcode = ColorInfo.OPCODE_TINT_BRIGHT; - value = getColorInfo().getValue(type, opcode, locationIndex); - if (value == -1) { - opcode = ColorInfo.OPCODE_TINT_SOLID; - value = getColorInfo().getValue(type, opcode, locationIndex); - if (value == -1) { - opcode = ColorInfo.OPCODE_SET_COLOR_GLOW; - value = getColorInfo().getValue(type, opcode, locationIndex); + opcode = value = -1; + for (final int code : opcodes) { + final EffectInfo.Effect fx = getEffectInfo().getColorByLocation(this, type, code, locationIndex); + if (fx != null) { + opcode = code; + value = EffectInfo.swapBytes(fx.getParameter1()); + break; } } if (value != -1) { @@ -648,30 +648,38 @@ public Couple getEffectiveTintValue(SegmentDef.SpriteType type return retVal; } + /** + * Returns whether the specified effect is active. + * @param type the {@link SegmentDef.SpriteType SpriteType} target. + * @param opcode the effect opcode to filter. + * @return {@code true} if the effect with matching parameters exists. Returns {@code false} otherwise. + */ + public boolean isEffectActive(SegmentDef.SpriteType type, int opcode) + { + return isEffectActive(type, (fx) -> fx.getOpcode() == opcode); + } + /** * Returns whether the specified effect is active with the given parameters. * @param type the {@link SegmentDef.SpriteType SpriteType} target - * @param opcode the effect opcode to filter. - * @param location the color location index. Available range: [-1, 6] + * @param pred the predicate to filter. * @return {@code true} if the effect with matching parameters exists. Returns {@code false} otherwise. */ - public boolean isEffectActive(SegmentDef.SpriteType type, int opcode, int location) + public boolean isEffectActive(SegmentDef.SpriteType type, Predicate pred) { boolean retVal = false; - if (type == null) { - type = SegmentDef.SpriteType.AVATAR; - } - // checking creature effects - retVal = (getColorInfo().getValue(type, opcode, location) >= 0); + EffectInfo.Effect fx = getEffectInfo().getFirstEffect(this, type, pred); + retVal = (fx != null); if (!retVal) { // checking equipped items ItemInfo[] itemInfos = getEffectiveItemInfo(); for (final ItemInfo info : itemInfos) { - retVal = (info.getColorInfo().getValue(type, opcode, location) >= 0); - if (retVal) { + fx = info.getEffectInfo().getFirstEffect(this, type, pred); + if (fx != null) { + retVal = true; break; } } @@ -820,76 +828,10 @@ private void initEffects() private void initEffect(AbstractStruct as) { - if (as == null) { - return; - } - StructEntry se = as.getField(EffectType.class, 0); - if (!(se instanceof EffectType)) { - return; - } - - int fxType = (as instanceof Effect2) ? 1 : 0; - int ofsParam1 = (fxType == 1) ? 0x14 : 0x04; - int ofsParam2 = (fxType == 1) ? 0x18 : 0x08; -// int ofsSpecial = (fxType == 1) ? 0x40 : 0x2c; - - int opcode = ((IsNumeric)se).getValue(); - switch (opcode) { - case ColorInfo.OPCODE_SET_COLOR: - case ColorInfo.OPCODE_SET_COLOR_GLOW: - case ColorInfo.OPCODE_TINT_SOLID: - case ColorInfo.OPCODE_TINT_BRIGHT: - { - se = as.getAttribute(as.getOffset() + ofsParam1); - int param1 = (se instanceof IsNumeric) ? ((IsNumeric)se).getValue() : -1; - se = as.getAttribute(as.getOffset() + ofsParam2); - int param2 = (se instanceof IsNumeric) ? ((IsNumeric)se).getValue() : -1; - - if (param1 != -1 && param2 != -1) { - SegmentDef.SpriteType type = null; - int location = param2 & 0xf; - switch ((param2 >> 4) & 0xf) { - case 0: - type = SegmentDef.SpriteType.AVATAR; - break; - case 1: - type = SegmentDef.SpriteType.WEAPON; - break; - case 2: - type = SegmentDef.SpriteType.SHIELD; - break; - case 3: - type = SegmentDef.SpriteType.HELMET; - break; - default: - if ((param2 & 0xff) == 0xff) { - // affect all sprite colors - type = SegmentDef.SpriteType.AVATAR; - location = -1; - } - } - - getColorInfo().add(type, opcode, location, param1); - } - break; - } - case ColorInfo.OPCODE_TRANSLUCENCY: - { - se = as.getAttribute(as.getOffset() + ofsParam2); - int param2 = (se instanceof IsNumeric) ? ((IsNumeric)se).getValue() : 0; - if (param2 == 0) { - se = as.getAttribute(as.getOffset() + ofsParam1); - int param1 = (se instanceof IsNumeric) ? ((IsNumeric)se).getValue() : 0; - param1 = Math.max(0, Math.min(255, param1)); - getColorInfo().add(SegmentDef.SpriteType.AVATAR, opcode, -1, param1); - } - break; - } - case ColorInfo.OPCODE_BLUR: - case ColorInfo.OPCODE_PETRIFICATION: - case ColorInfo.OPCODE_STONESKIN: - getColorInfo().add(SegmentDef.SpriteType.AVATAR, opcode, -1, 0); - break; + if (as instanceof Effect) { + getEffectInfo().add(new EffectInfo.Effect((Effect)as)); + } else if (as instanceof Effect2) { + getEffectInfo().add(new EffectInfo.Effect((Effect2)as)); } } @@ -1173,7 +1115,7 @@ public int hashCode() { int hash = 7; hash = 31 * hash + ((equipment == null) ? 0 : equipment.hashCode()); - hash = 31 * hash + ((colorInfo == null) ? 0 : colorInfo.hashCode()); + hash = 31 * hash + ((effectInfo == null) ? 0 : effectInfo.hashCode()); hash = 31 * hash + ((decoder == null) ? 0 : decoder.hashCode()); hash = 31 * hash + ((cre == null) ? 0 : cre.hashCode()); hash = 31 * hash + Integer.valueOf(allegianceOverride).hashCode(); @@ -1192,8 +1134,8 @@ public boolean equals(Object o) CreatureInfo other = (CreatureInfo)o; boolean retVal = (this.equipment == null && other.equipment == null) || (this.equipment != null && this.equipment.equals(other.equipment)); - retVal &= (this.colorInfo == null && other.colorInfo == null) || - (this.colorInfo != null && this.colorInfo.equals(other.colorInfo)); + retVal &= (this.effectInfo == null && other.effectInfo == null) || + (this.effectInfo != null && this.effectInfo.equals(other.effectInfo)); retVal &= (this.decoder == null && other.decoder == null) || (this.decoder != null && this.decoder.equals(other.decoder)); retVal &= (this.cre == null && other.cre == null) || diff --git a/src/org/infinity/resource/cre/decoder/util/EffectInfo.java b/src/org/infinity/resource/cre/decoder/util/EffectInfo.java new file mode 100644 index 000000000..cbe499843 --- /dev/null +++ b/src/org/infinity/resource/cre/decoder/util/EffectInfo.java @@ -0,0 +1,1096 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2021 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre.decoder.util; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Predicate; + +import org.infinity.datatype.IsNumeric; +import org.infinity.datatype.IsTextual; +import org.infinity.resource.Profile; +import org.infinity.resource.ResourceFactory; +import org.infinity.resource.StructEntry; +import org.infinity.resource.cre.CreResource; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.resource.spl.SplResource; +import org.infinity.util.Misc; +import org.infinity.util.Table2da; +import org.infinity.util.Table2daCache; +import org.infinity.util.io.StreamUtils; + +/** + * Manages effects opcodes for a specific target. + */ +public class EffectInfo +{ + /** Effect opcode 7: Set color */ + public static final int OPCODE_SET_COLOR = 7; + /** Effect opcode 8: Set color glow solid */ + public static final int OPCODE_SET_COLOR_GLOW = 8; + /** Effect opcode 51: Character tint solid */ + public static final int OPCODE_TINT_SOLID = 51; + /** Effect opcode 52: Character tint bright */ + public static final int OPCODE_TINT_BRIGHT = 52; + /** Effect opcode 65: Blur */ + public static final int OPCODE_BLUR = 65; + /** Effect opcode 66: Translucency */ + public static final int OPCODE_TRANSLUCENCY = 66; + /** Effect opcode 134: Petrification */ + public static final int OPCODE_PETRIFICATION = 134; + /** Effect opcode 218: Stoneskin */ + public static final int OPCODE_STONESKIN = 218; + + /** A predicate for filtering opcode 7 effects (set color). */ + public static final Predicate FILTER_COLOR_EFFECT = (fx) -> (fx.getOpcode() == OPCODE_SET_COLOR); + /** A predicate for filtering opcode 8 effects (color glow). */ + public static final Predicate FILTER_COLOR_GLOW_EFFECT = (fx) -> (fx.getOpcode() == OPCODE_SET_COLOR_GLOW); + /** A predicate for filtering opcode 51 effects (tint solid). */ + public static final Predicate FILTER_TINT_SOLID_EFFECT = (fx) -> (fx.getOpcode() == OPCODE_TINT_SOLID); + /** A predicate for filtering opcode 52 effects (tint bright). */ + public static final Predicate FILTER_TINT_BRIGHT_EFFECT = (fx) -> (fx.getOpcode() == OPCODE_TINT_BRIGHT); + /** A predicate for filtering blur effects. */ + public static final Predicate FILTER_BLUR_EFFECT = (fx) -> (fx.getOpcode() == OPCODE_BLUR); + /** A predicate for filtering translucency effects. */ + public static final Predicate FILTER_TRANSLUCENCY_EFFECT = (fx) -> (fx.getOpcode() == OPCODE_TRANSLUCENCY); + /** A predicate for filtering all stone-like effects. */ + public static final Predicate FILTER_STONE_EFFECT = (fx) -> (fx.getOpcode() == OPCODE_PETRIFICATION || + fx.getOpcode() == OPCODE_STONESKIN); + + + private final EnumMap> effectMap = new EnumMap<>(SegmentDef.SpriteType.class); + + /** A helper method that reverses byte order of the specified parameter. */ + public static int swapBytes(int value) + { + int retVal = 0; + retVal |= (value >>> 24) & 0xff; + retVal |= (value >>> 8) & 0xff00; + retVal |= (value << 8) & 0xff0000; + retVal |= (value << 24) & 0xff000000; + return retVal; + } + + /** A helper method that reverses word order of the specified parameter. */ + public static int swapWords(int value) + { + int retVal = 0; + retVal |= (value >> 16) & 0xffff; + retVal |= (value << 16) & 0xffff0000; + return retVal; + } + + public EffectInfo() + { + } + + /** + * Returns a list of {@code SpriteType} definitions containing effect definitions. + * @param creInfo the creature target. + * @return List of sprite type definitions. + */ + public List getSpriteTypes(CreatureInfo creInfo) + { + return getSpriteTypes(creInfo, null); + } + + /** + * Returns a list of {@code SpriteType} definitions with effects matching the specified opcode. + * @param creInfo the creature target. + * @param opcode the opcode to match. + * @return List of sprite type definitions. + */ + public List getSpriteTypes(CreatureInfo creInfo, int opcode) + { + return getSpriteTypes(creInfo, (fx) -> fx.getOpcode() == opcode); + } + + /** + * Returns a list of {@code SpriteType} definitions with effects matching the specified predicate. + * @param creInfo the creature target. + * @param pred the predicate function object. Specify {@code null} to match any effects. + * @return List of sprite type definitions. + */ + public List getSpriteTypes(CreatureInfo creInfo, Predicate pred) + { + final List retVal = new ArrayList<>(); + + if (pred == null) { + pred = (fx) -> true; + } + + for (final Map.Entry> entry : effectMap.entrySet()) { + if (entry.getValue().parallelStream().anyMatch(pred.and(e -> isEffectValid(e, creInfo)))) { + retVal.add(entry.getKey()); + } + } + + return retVal; + } + + /** + * A convenience method that returns the first available effect associated with the + * specified {@code SpriteType}. + * @param creInfo the creature target. + * @param type the sprite type to filter. Specify {@code null} for non-specific effects. + * @return first matching {@code Effect} instance. Returns {@code null} if not available. + */ + public Effect getFirstEffect(CreatureInfo creInfo, SegmentDef.SpriteType type) + { + List list = getEffects(creInfo, type, null); + return !list.isEmpty() ? list.get(0) : null; + } + + /** + * A convenience method that returns the first available effect matching the specified {@code SpriteType} + * and opcode. + * @param creInfo the creature target. + * @param type the sprite type to filter. Specify {@code null} for non-specific effects. + * @param opcode the opcode to filter. + * @return first matching {@code Effect} instance. Returns {@code null} if not available. + */ + public Effect getFirstEffect(CreatureInfo creInfo, SegmentDef.SpriteType type, int opcode) + { + List list = getEffects(creInfo, type, (fx) -> fx.getOpcode() == opcode); + return !list.isEmpty() ? list.get(0) : null; + } + + /** + * A convenience method that returns the first available effect matching the specified {@code SpriteType} + * and opcode. + * @param creInfo the creature target. + * @param type the sprite type to filter. Specify {@code null} for non-specific effects. + * @param pred the predicate function object responsible for filtering. Specify {@code null} to return the + * first available effects without filtering. + * @return first matching {@code Effect} instance. Returns {@code null} if not available. + */ + public Effect getFirstEffect(CreatureInfo creInfo, SegmentDef.SpriteType type, Predicate pred) + { + if (creInfo == null) { + throw new NullPointerException("Creature info cannot be null"); + } + + Effect retVal = null; + + if (type == null) { + type = SegmentDef.SpriteType.AVATAR; + } + + if (pred == null) { + pred = (fx) -> true; + } + + Set set = effectMap.get(type); + if (set != null) { + retVal = set + .parallelStream() + .filter(pred.and(e -> isEffectValid(e, creInfo))) + .findAny() + .orElse(null); + } + + return retVal; + } + + /** + * Returns all available effects associated with the specified {@code SpriteType}. + * @param creInfo the creature target. + * @param type the sprite type to filter. Specify {@code null} for non-specific effects. + * @return list of effects associated with the sprite type. Empty list if no effects available. + */ + public List getEffects(CreatureInfo creInfo, SegmentDef.SpriteType type) + { + return getEffects(creInfo, type, null); + } + + /** + * Returns all available effects matching the specified {@code SpriteType} and opcode. + * @param creInfo the creature target. + * @param type the sprite type to filter. Specify {@code null} for non-specific effects. + * @param opcode the opcode to filter. + * @return list of effects associated with the sprite type and matching opcode. Empty list if no effects available. + */ + public List getEffects(CreatureInfo creInfo, SegmentDef.SpriteType type, int opcode) + { + return getEffects(creInfo, type, (fx) -> fx.getOpcode() == opcode); + } + + /** + * Returns all available effects matching the specified {@code SpriteType} and predicate. + * @param creInfo the creature target. + * @param type the sprite type to filter. Specify {@code null} for non-specific effects. + * @param pred the predicate function object. Effects passing the test are added to the results list. + * Specify {@code null} to return all effects associated with the specified sprite type. + * @return list of effects associated with the sprite type and matching predicate. Empty list if no effects available. + */ + public List getEffects(CreatureInfo creInfo, SegmentDef.SpriteType type, Predicate pred) + { + if (creInfo == null) { + throw new NullPointerException("Creature info cannot be null"); + } + + final List retVal = new ArrayList<>(); + + if (type == null) { + type = SegmentDef.SpriteType.AVATAR; + } + + if (pred == null) { + pred = (fx) -> true; + } + + Set set = effectMap.get(type); + if (set != null) { + set + .stream() + .filter(pred.and(e -> isEffectValid(e, creInfo))) + .forEach(fx -> retVal.add(fx)); + } + + return retVal; + } + + /** + * Returns {@code true} if the specified {@code SpriteType} contains one or more effects matching + * the specified opcode. + * @param type the sprite type to filter. Specify {@code null} for non-specific effects. + * @param opcode the opcode to match. + * @return {@code true} if match is found, {@code false} otherwise. + */ + public boolean hasEffect(SegmentDef.SpriteType type, int opcode) + { + return hasEffect(type, (fx) -> fx.getOpcode() == opcode); + } + + /** + * Returns {@code true} if the specified {@code SpriteType} contains one or more effects matching + * the specified predicate. + * @param type the sprite type to filter. Specify {@code null} for non-specific effects. + * @param pred the predicate function object. Specify {@code null} to match any effects. + * @return {@code true} if match is found, {@code false} otherwise. + */ + public boolean hasEffect(SegmentDef.SpriteType type, Predicate pred) + { + if (type == null) { + type = SegmentDef.SpriteType.AVATAR; + } + + if (pred == null) { + pred = (fx) -> true; + } + + Set set = effectMap.get(type); + if (set != null) { + set.parallelStream().anyMatch(pred); + } + + return false; + } + + /** + * A specialized method that returns the first available color-related effect matching the specified parameters. + * @param creInfo the creature target. + * @param type the sprite type to filter. Specify {@code null} for non-specific effects. + * @param opcode the opcode to filter. + * @param location color location index. See opcode description for more details. + * @return the first matching effect. Returns {@code null} if there is no match available. + */ + public Effect getColorByLocation(CreatureInfo creInfo, SegmentDef.SpriteType type, int opcode, int location) + { + final int locationIndex = (location == 255) ? location : (location & 0x0f); + + final Predicate pred = (fx) -> { + return (fx.getOpcode() == opcode) && + ((fx.getParameter2() & 0xf) == locationIndex); + }; + + List list = getEffects(creInfo, type, pred); + if (!list.isEmpty()) { + return list.get(0); + } + + return null; + } + + protected boolean isEffectValid(Effect effect, CreatureInfo creInfo) + { + boolean retVal = true; + if (effect == null) { + return retVal; + } + + switch (effect.getOpcode()) { + case 177: // Use EFF + case 283: // Use EFF as curse + retVal = evaluateIds(creInfo, effect); + break; + case 183: // Use EFF for item type + retVal = evaluateItemCategory(creInfo, effect.getParameter2()); + break; + case 326: // Apply effects list + retVal = evaluateSplProt(creInfo, effect.getParameter2(), effect.getParameter1()); + break; + } + + if (retVal) { + retVal = isEffectValid(effect.getParent(), creInfo); + } + + return retVal; + } + + /** + * Adds the specified effect and associates it with the {@code SpriteType} defined by the effect. + *

    Indirectly created effects (e.g. via opcode 146) are correctly resolved and added. + * @param effect the effect to add. + */ + public void add(Effect effect) + { + if (effect == null) { + throw new NullPointerException("Effect parameter cannot be null"); + } + + List effects = resolveEffect(null, effect); + for (final Effect fx : effects) { + SegmentDef.SpriteType type = getEffectType(fx); + Set set = effectMap.get(type); + if (set == null) { + set = new HashSet<>(); + effectMap.put(type, set); + } + set.add(fx); + } + } + + /** Determines the sprite type target defined by this effect. Only relevant for color-related effects. */ + private SegmentDef.SpriteType getEffectType(Effect effect) + { + switch (effect.getOpcode()) { + case 7: // Set color + case 8: // Set color glow solid + case 9: // Set color glow pulse + case 50: // Character color pulse + case 51: // Character tint solid + case 52: // Character tint bright + switch ((effect.getParameter2() >> 4) & 0xf) { + case 1: + return SegmentDef.SpriteType.WEAPON; + case 2: + return SegmentDef.SpriteType.SHIELD; + case 3: + return SegmentDef.SpriteType.HELMET; + default: + return SegmentDef.SpriteType.AVATAR; + } + default: + return SegmentDef.SpriteType.AVATAR; + } + } + + /** + * Resolves effects which reference additional effects via secondary resources (e.g. SPL, EFF). + * The primary effect is skipped in these cases. + */ + private List resolveEffect(Effect parent, Effect effect) + { + List retVal = new ArrayList<>(); + + effect.setParent(parent); + retVal.add(effect); + + switch (effect.getOpcode()) { + case 146: // Cast spell + resolveSPL(retVal, effect, ResourceFactory.getResourceEntry(effect.getResource() + ".SPL")); + break; + case 177: // Use EFF + resolveEFF(retVal, effect, ResourceFactory.getResourceEntry(effect.getResource() + ".EFF")); + break; + case 183: // Use EFF for item type + if (Profile.getEngine() != Profile.Engine.PST) { + resolveEFF(retVal, effect, ResourceFactory.getResourceEntry(effect.getResource() + ".EFF")); + } + break; + case 283: // Use EFF as curse + switch (Profile.getEngine()) { + case BG2: + case EE: + resolveEFF(retVal, effect, ResourceFactory.getResourceEntry(effect.getResource() + ".EFF")); + break; + default: + } + break; + case 326: // Apply effects list + if (Profile.getEngine() == Profile.Engine.EE) { + resolveSPL(retVal, effect, ResourceFactory.getResourceEntry(effect.getResource() + ".SPL")); + } + break; + default: + } + + return retVal; + } + + /** Resolves all effects (global effects and effects from first available ability) from the specified SPL resource. */ + private void resolveSPL(List list, Effect parent, ResourceEntry entry) + { + if (entry == null) { + return; + } + + try { + SplResource spl = new SplResource(entry); + int ofsAbil = ((IsNumeric)spl.getAttribute(SplResource.SPL_OFFSET_ABILITIES)).getValue(); + int numAbil = ((IsNumeric)spl.getAttribute(SplResource.SPL_NUM_ABILITIES)).getValue(); + int numGlobalFx = ((IsNumeric)spl.getAttribute(SplResource.SPL_NUM_GLOBAL_EFFECTS)).getValue(); + + // evaluating global effects + if (numGlobalFx > 0) { + List fxList = spl.getFields(org.infinity.resource.Effect.class); + if (fxList != null) { + for (final StructEntry se : fxList) { + if (se instanceof org.infinity.resource.Effect) { + final List retList = resolveEffect(parent, new Effect((org.infinity.resource.Effect)se)); + list.addAll(retList); + } + } + } + } + + // evaluating ability effects (first ability only) + if (numAbil > 0) { + StructEntry abil = spl.getField(org.infinity.resource.spl.Ability.class, ofsAbil + spl.getExtraOffset()); + if (abil instanceof org.infinity.resource.spl.Ability) { + List fxList = ((org.infinity.resource.spl.Ability)abil).getFields(org.infinity.resource.Effect.class); + for (final StructEntry se : fxList) { + if (se instanceof org.infinity.resource.Effect) { + final List retList = resolveEffect(parent, new Effect((org.infinity.resource.Effect)se)); + list.addAll(retList); + } + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** Resolves the effect from the specified EFF resource. */ + private void resolveEFF(List list, Effect parent, ResourceEntry entry) + { + if (entry == null) { + return; + } + + try { + final List retList = resolveEffect(parent, Effect.fromEffectV2(entry.getResourceBuffer(), 8)); + list.addAll(retList); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Evaluates the item category filter based on equipped items by the target. + * @param creInfo creature target + * @param cat the item category + * @return whether creature has equipped items with the specified item category. + */ + private boolean evaluateItemCategory(CreatureInfo creInfo, int cat) + { + boolean retVal = false; + + for (final CreatureInfo.ItemSlots slot : CreatureInfo.ItemSlots.values()) { + ItemInfo ii = creInfo.getItemInfo(slot); + if (ii != null) { + if (cat == ii.getCategory()) { + retVal = true; + break; + } + } + } + + return retVal; + } + + /** + * Evaluates the IDS filter specified by type and entry value for the given target. + * @param creInfo creature target + * @param type the IDS resource type + * @param entry the IDS entry index + * @return whether creature stat matches reference stat. + */ + private boolean evaluateIds(CreatureInfo creInfo, Effect effect) + { + boolean retVal = false; + + int type = effect.getParameter2(); + int entry = effect.getParameter1(); + switch (type) { + case 2: // EA.IDS + retVal = (entry == creInfo.getAllegiance()); + break; + case 3: // GENERAL.IDS + retVal = (entry == ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_GENERAL)).getValue()); + break; + case 4: // RACE.IDS + retVal = (entry == ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_RACE)).getValue()); + break; + case 5: // CLASS.IDS + retVal = (entry == ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_CLASS)).getValue()); + break; + case 6: // SPECIFIC.IDS + retVal = (entry == ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_SPECIFICS)).getValue()); + break; + case 7: // GENDER.IDS + retVal = (entry == ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_GENDER)).getValue()); + break; + case 8: // ALIGN.IDS + retVal = (entry == ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_ALIGNMENT)).getValue()); + break; + case 9: // KIT.IDS + if (Profile.isEnhancedEdition()) { + int kit = ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_RACE)).getValue(); + kit = ((kit & 0xffff) << 16) | ((kit >>> 16) & 0xffff); + retVal = (entry == kit); + } + break; + case 11: // Actor's script name + if (Profile.isEnhancedEdition()) { + String name = ((IsTextual)creInfo.getCreResource().getAttribute(CreResource.CRE_SCRIPT_NAME)).getText(); + retVal = (effect.getResource().equalsIgnoreCase(name)); + } + break; + } + + return retVal; + } + + /** + * Evaluates the SPLPROT filter specified by type and value for the given target. + * @param creInfo creature target + * @param type SPLPROT.2DA entry index + * @param refValue an optional reference value used by selected types + * @return whether creature stat matches reference stat. + */ + private boolean evaluateSplProt(CreatureInfo creInfo, int type, int refValue) + { + Table2da table = Table2daCache.get("SPLPROT.2DA"); + if (table == null) { + return true; + } + + String s = table.get(type, 1); + int radix = s.startsWith("0x") ? 16 : 10; + int stat = Misc.toNumber(s, radix, -1); + + s = table.get(type, 2); + radix = s.startsWith("0x") ? 16 : 10; + int value = Misc.toNumber(s, radix, -1); + if (value == -1) { + value = refValue; + } + + s = table.get(type, 3); + radix = s.startsWith("0x") ? 16 : 10; + int rel = Misc.toNumber(s, radix, -1); + + switch (stat) { + case 0x100: // source equals target + // irrelevant + break; + case 0x101: // source is not target + // irrelevant + break; + case 0x102: // circle size + return splProtRelation(rel, creInfo.getDecoder().getPersonalSpace(), value); + case 0x103: // use two rows + return evaluateSplProt(creInfo, value, -1) || evaluateSplProt(creInfo, rel, -1); + case 0x104: // negate 0x103 + return !(evaluateSplProt(creInfo, value, -1) || evaluateSplProt(creInfo, rel, -1)); + case 0x105: // source and target morale match + // not supported + break; + case 0x106: // AREATYPE.IDS + // irrelevant + break; + case 0x107: // time of day (hours) + // irrelevant + break; + case 0x108: // source and target ethical match + // not supported + break; + case 0x109: // evasion + // not supported + break; + case 0x10a: // EA.IDS + return splProtRelation(rel, creInfo.getAllegiance(), value); + case 0x10b: // GENERAL.IDS + return splProtRelation(rel, ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_GENERAL)).getValue(), value); + case 0x10c: // RACE.iDS + return splProtRelation(rel, ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_RACE)).getValue(), value); + case 0x10d: // CLASS.IDS + return splProtRelation(rel, ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_CLASS)).getValue(), value); + case 0x10e: // SPECIFIC.IDS + return splProtRelation(rel, ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_SPECIFICS)).getValue(), value); + case 0x10f: // GENDER.IDS + return splProtRelation(rel, ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_GENDER)).getValue(), value); + case 0x110: // ALIGN.IDS + return splProtRelation(rel, ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_ALIGNMENT)).getValue(), value); + case 0x111: // STATE.IDS + return splProtRelation(rel, creInfo.getStatus(), value); + case 0x112: // SPLSTATE.IDS + // not supported + break; + case 0x113: // source and target allies + // irrelevant + break; + case 0x114: // source and target enemies + // irrelevant + break; + case 0x115: // summon creature limit + // irrelevant + break; + case 0x116: // chapter check + // irrelevant + break; + default: // STATS.IDS + return splProtRelation(rel, getStatValue(creInfo, stat), value); + } + return true; + } + + /** + * Performs a relational operation on the value parameters and returns the result. + * @param relation the relation type + * @param creValue value retrieved from the target + * @param refValue value retrieved either from splprot table or custom effect value + */ + private boolean splProtRelation(int relation, int creValue, int refValue) + { + switch (relation) { + case 0: // less or equal + return (creValue <= refValue); + case 1: // equal + return (creValue == refValue); + case 2: // less + return (creValue < refValue); + case 3: // greater + return (creValue > refValue); + case 4: // greater or equal + return (creValue >= refValue); + case 5: // not equal + return (creValue != refValue); + case 6: // binary less or equal (stat doesn't contain extra bits not in value) + return (creValue & ~refValue) == 0; + case 7: // binary more or equal (stat contains all bits of value) + return (creValue & refValue) == refValue; + case 8: // binary match (at least one bit is common) + return (creValue & refValue) != 0; + case 9: // binary not match (none of the bits are common) + return (creValue & refValue) == 0; + case 10: // binary more (stat contains at least one bit not in value) + return (creValue & ~refValue) != 0; + case 11: // binary less (stat doesn't contain all the bits of value) + return (creValue & refValue) != refValue; + } + return false; + } + + /** Retrieves the specified stat from the given creature if available. Returns 0 otherwise. */ + private int getStatValue(CreatureInfo creInfo, int stat) + { + // only selected stat values are supported + switch (stat) { + case 1: // MAXHITPOINTS + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_HP_MAX)).getValue(); + case 2: // ARMORCLASS + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_AC_EFFECTIVE)).getValue(); + case 7: // THAC0 + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_THAC0)).getValue(); + case 8: // NUMBEROFATTACKS + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_ATTACKS_PER_ROUND)).getValue(); + case 9: // SAVEVSDEATH + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_SAVE_DEATH)).getValue(); + case 10: // SAVEVSWANDS + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_SAVE_WAND)).getValue(); + case 11: // SAVEVSPOLY + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_SAVE_POLYMORPH)).getValue(); + case 12: // SAVEVSBREATH + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_SAVE_BREATH)).getValue(); + case 13: // SAVEVSSPELL + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_SAVE_SPELL)).getValue(); + case 14: // RESISTFIRE + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_RESISTANCE_FIRE)).getValue(); + case 15: // RESISTCOLD + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_RESISTANCE_COLD)).getValue(); + case 16: // RESISTELECTRICITY + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_RESISTANCE_ELECTRICITY)).getValue(); + case 17: // RESISTACID + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_RESISTANCE_ACID)).getValue(); + case 18: // RESISTMAGIC + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_RESISTANCE_MAGIC)).getValue(); + case 19: // RESISTMAGICFIRE + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_RESISTANCE_MAGIC_FIRE)).getValue(); + case 20: // RESISTMAGICCOLD + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_RESISTANCE_MAGIC_COLD)).getValue(); + case 21: // RESISTSLASHING + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_RESISTANCE_SLASHING)).getValue(); + case 22: // RESISTCRUSHING + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_RESISTANCE_CRUSHING)).getValue(); + case 23: // RESISTPIERCING + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_RESISTANCE_PIERCING)).getValue(); + case 24: // RESISTMISSILE + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_RESISTANCE_MISSILE)).getValue(); + case 25: // LORE + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_LORE)).getValue(); + case 26: // LOCKPICKING + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_OPEN_LOCKS)).getValue(); + case 27: // STEALTH + { + int v1 = ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_MOVE_SILENTLY)).getValue(); + int v2; + if (Profile.getEngine() != Profile.Engine.BG1 && Profile.getEngine() != Profile.Engine.IWD) { + v2 = ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_HIDE_IN_SHADOWS)).getValue(); + } else { + v2 = v1; + } + return (v1 + v2) / 2; + } + case 28: // TRAPS + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_FIND_TRAPS)).getValue(); + case 29: // PICKPOCKET + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_PICK_POCKETS)).getValue(); + case 30: // FATIGUE + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_FATIGUE)).getValue(); + case 31: // INTOXICATION + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_INTOXICATION)).getValue(); + case 32: // LUCK + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_LUCK)).getValue(); + case 34: // LEVEL + { + int v1 = ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_LEVEL_FIRST_CLASS)).getValue(); + int v2 = ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_LEVEL_SECOND_CLASS)).getValue(); + int v3 = ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_LEVEL_THIRD_CLASS)).getValue(); + if ((creInfo.getFlags() & 0x1f8) != 0) { + // dual-classed? + return v2; + } else { + return Math.max(v1, Math.max(v2, v3)); + } + } + case 35: // SEX + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_GENDER)).getValue(); + case 36: // STR + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_STRENGTH)).getValue(); + case 37: // STREXTRA + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_STRENGTH_BONUS)).getValue(); + case 38: // INT + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_INTELLIGENCE)).getValue(); + case 39: // WIS + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_WISDOM)).getValue(); + case 40: // DEX + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_DEXTERITY)).getValue(); + case 41: // CON + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_CONSTITUTION)).getValue(); + case 42: // CHR + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_CHARISMA)).getValue(); + case 43: // XPVALUE + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_XP_VALUE)).getValue(); + case 44: // XP + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_XP)).getValue(); + case 45: // GOLD + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_GOLD)).getValue(); + case 46: // MORALEBREAK + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_MORALE_BREAK)).getValue(); + case 47: // MORALERECOVERYTIME + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_MORALE_RECOVERY)).getValue(); + case 48: // REPUTATION + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_REPUTATION)).getValue(); + case 60: // TRANSLUCENT + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_TRANSLUCENCY)).getValue(); + case 68: // LEVEL2 + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_LEVEL_SECOND_CLASS)).getValue(); + case 69: // LEVEL3 + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_LEVEL_THIRD_CLASS)).getValue(); + case 135: // HIDEINSHADOWS + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_HIDE_IN_SHADOWS)).getValue(); + case 136: // DETECTILLUSIONS + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_DETECT_ILLUSION)).getValue(); + case 137: // SETTRAPS + return ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_SET_TRAPS)).getValue(); + case 152: // KIT + { + int v = ((IsNumeric)creInfo.getCreResource().getAttribute(CreResource.CRE_KIT)).getValue(); + v = ((v >>> 16) & 0xffff) | ((v & 0xffff) << 16); + return v; + } + } + return 0; + } + + @Override + public int hashCode() + { + int hashCode = 7; + hashCode = 31 * hashCode + ((effectMap == null) ? 0 : effectMap.hashCode()); + return hashCode; + } + + @Override + public boolean equals(Object o) + { + if (o == this) { + return true; + } + if (!(o instanceof EffectInfo)) { + return false; + } + + EffectInfo other = (EffectInfo)o; + + boolean retVal = (effectMap == null && other.effectMap == null) || + (effectMap != null && effectMap.equals(other.effectMap)); + + return retVal; + } + +//-------------------------- INNER CLASSES -------------------------- + + public static class Effect + { + private final int opcode; + private final int[] probability = new int[2]; + + private int target; + private int power; + private int parameter1; + private int parameter2; + private int timing; + private int dispelResist; + private int duration; + private String resource; + private int diceCount; + private int diceSize; + private int saveFlags; + private int saveBonus; + private int special; + + private Effect parent; + + /** Convenience method for creating an {@code Effect} instance from a byte array containing EFF V1 data. */ + public static Effect fromEffectV1(ByteBuffer buf, int startOfs) + { + Effect retVal = new Effect(buf.order(ByteOrder.LITTLE_ENDIAN).getShort(0x0 + startOfs)); + retVal.initEffectV1(buf, startOfs); + return retVal; + } + + /** Convenience method for creating an {@code Effect} instance from a byte array containing EFF V2 data. */ + public static Effect fromEffectV2(ByteBuffer buf, int startOfs) + { + Effect retVal = new Effect(buf.order(ByteOrder.LITTLE_ENDIAN).getInt(0x8 + startOfs)); + retVal.initEffectV2(buf, startOfs); + return retVal; + } + + /** Initializes a new Effect instance. */ + public Effect(int opcode) + { + this.opcode = opcode; + setProbability(0, 100); + setTarget(1); // Self + setTiming(2); // Instant/While equipped + } + + /** Initializes a new Effect instance from an effect V1 resource structure. */ + public Effect(org.infinity.resource.Effect struct) + { + ByteBuffer bb = Objects.requireNonNull(struct).getDataBuffer().order(ByteOrder.LITTLE_ENDIAN); + + this.opcode = bb.getShort(0x0); + initEffectV1(bb, 0); + } + + /** Initializes a new Effect instance from an effect V2 resource structure. */ + public Effect(org.infinity.resource.Effect2 struct) + { + ByteBuffer bb = Objects.requireNonNull(struct).getDataBuffer().order(ByteOrder.LITTLE_ENDIAN); + + this.opcode = bb.getInt(0x8); + initEffectV2(bb, 0); + } + + /** Returns the parent effect which applies the current effect. */ + public Effect getParent() { return parent; } + /** Sets the parent effect which applies the current effect. */ + public Effect setParent(Effect parent) { this.parent = parent; return this; } + + public int getOpcode() { return opcode; } + + public int getTarget() { return target; } + public Effect setTarget(int v) { target = v; return this; } + + public int getPower() { return power; } + public Effect setPower(int v) { power = v; return this; } + + public int getParameter1() { return parameter1; } + public Effect setParameter1(int v) { parameter1 = v; return this; } + + public int getParameter2() { return parameter2; } + public Effect setParameter2(int v) { parameter2 = v; return this; } + + public int getTiming() { return timing; } + public Effect setTiming(int v) { timing = v; return this; } + + public int getDispelResist() { return dispelResist; } + public Effect setDispelResist(int v) { dispelResist = v; return this; } + + public int getDuration() { return duration; } + public Effect setDuration(int v) { duration = v; return this; } + + public Effect setProbability(int low, int high) + { + probability[0] = low; + probability[1] = high; + return this; + } + + public int getProbabilityLow() { return probability[0]; } + public Effect setProbabilityLow(int v) { probability[0] = v; return this; } + + public int getProbabilityHigh() { return probability[1]; } + public Effect setProbabilityHigh(int v) { probability[1] = v; return this; } + + public String getResource() { return resource; } + public Effect setResource(String s) { resource = (s != null) ? s : ""; return this; } + + public int getDiceCount() { return diceCount; } + public Effect setDiceCount(int v) { diceCount = v; return this; } + + public int getDiceSize() { return diceSize; } + public Effect setDiceSize(int v) { diceSize = v; return this; } + + public int getSaveFlags() { return saveFlags; } + public Effect setSaveFlags(int v) { saveFlags = v; return this; } + public Effect setSaveFlag(int bitIdx, boolean set) + { + if (bitIdx >= 0 && bitIdx < 32) { + if (set) { + saveFlags |= (1 << bitIdx); + } else { + saveFlags &= ~(1 << bitIdx); + } + } + return this; + } + + public int getSaveBonus() { return saveBonus; } + public Effect setSaveBonus(int v) { saveBonus = v; return this; } + + public int getSpecial() { return special; } + public Effect setSpecial(int v) { special = v; return this; } + + @Override + public int hashCode() + { + int hashCode = 7; + hashCode = 31 * hashCode + opcode; + hashCode = 31 * hashCode + probability[0]; + hashCode = 31 * hashCode + probability[1]; + hashCode = 31 * hashCode + target; + hashCode = 31 * hashCode + power; + hashCode = 31 * hashCode + parameter1; + hashCode = 31 * hashCode + parameter2; + hashCode = 31 * hashCode + timing; + hashCode = 31 * hashCode + dispelResist; + hashCode = 31 * hashCode + duration; + hashCode = 31 * hashCode + ((resource == null) ? 0 : resource.hashCode()); + hashCode = 31 * hashCode + diceCount; + hashCode = 31 * hashCode + diceSize; + hashCode = 31 * hashCode + saveFlags; + hashCode = 31 * hashCode + saveBonus; + hashCode = 31 * hashCode + special; + + return hashCode; + } + + @Override + public boolean equals(Object o) + { + if (this == o) { + return true; + } + if (!(o instanceof Effect)) { + return false; + } + + Effect other = (Effect)o; + boolean retVal = opcode == other.opcode; + retVal &= (probability[0] == other.probability[0]); + retVal &= (probability[1] == other.probability[1]); + retVal &= (target == other.target); + retVal &= (power == other.power); + retVal &= (parameter1 == other.parameter1); + retVal &= (parameter2 == other.parameter2); + retVal &= (timing == other.timing); + retVal &= (dispelResist == other.dispelResist); + retVal &= (duration == other.duration); + retVal &= (resource == null && other.resource == null) || + (resource != null && resource.equals(other.resource)); + retVal &= (diceCount == other.diceCount); + retVal &= (diceSize == other.diceSize); + retVal &= (saveFlags == other.saveFlags); + retVal &= (saveBonus == other.saveBonus); + retVal &= (special == other.special); + + return retVal; + } + + // Initializes effect parameters from EFF V1 structure + private void initEffectV1(ByteBuffer buf, int startOfs) + { + setTarget(buf.get(0x2 + startOfs)); + setPower(buf.get(0x3 + startOfs)); + setParameter1(buf.getInt(0x4 + startOfs)); + setParameter2(buf.getInt(0x8 + startOfs)); + setTiming(buf.get(0xc + startOfs)); + setDispelResist(buf.get(0xd + startOfs)); + setDuration(buf.getInt(0xe + startOfs)); + setProbability(buf.get(0x13 + startOfs), buf.get(0x12 + startOfs)); + setResource(StreamUtils.readString(buf, 0x14 + startOfs, 8)); + setDiceCount(buf.getInt(0x1c + startOfs)); + setDiceSize(buf.getInt(0x20 + startOfs)); + setSaveFlags(buf.getInt(0x24 + startOfs)); + setSaveBonus(buf.getInt(0x28 + startOfs)); + setSpecial(buf.getInt(0x2c + startOfs)); + } + + // Initializes effect parameters from EFF V2 structure. startOfs can be used to adjust offset for EFF resources. + private void initEffectV2(ByteBuffer buf, int startOfs) + { + setTarget(buf.getInt(0xc + startOfs)); + setPower(buf.getInt(0x10 + startOfs)); + setParameter1(buf.getInt(0x14 + startOfs)); + setParameter2(buf.getInt(0x18 + startOfs)); + setTiming(buf.getInt(0x1c + startOfs)); + setDispelResist(buf.get(0x54 + startOfs)); + setDuration(buf.getInt(0x20 + startOfs)); + setProbability(buf.getShort(0x26 + startOfs), buf.getShort(0x24 + startOfs)); + setResource(StreamUtils.readString(buf, 0x28 + startOfs, 8)); + setDiceCount(buf.getInt(0x30 + startOfs)); + setDiceSize(buf.getInt(0x34 + startOfs)); + setSaveFlags(buf.getInt(0x38 + startOfs)); + setSaveBonus(buf.getInt(0x3c + startOfs)); + setSpecial(buf.getInt(0x40 + startOfs)); + } + } +} diff --git a/src/org/infinity/resource/cre/decoder/util/ItemInfo.java b/src/org/infinity/resource/cre/decoder/util/ItemInfo.java index d662dd478..4a53dce40 100644 --- a/src/org/infinity/resource/cre/decoder/util/ItemInfo.java +++ b/src/org/infinity/resource/cre/decoder/util/ItemInfo.java @@ -6,6 +6,7 @@ import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; @@ -175,7 +176,7 @@ public enum SlotType { boolean retVal = FILTER_WEAPON.test(info); retVal &= (info.getAbilityCount() > 0); if (retVal) { - AbilityInfo ai = info.getAbility(0); + AbilityEntry ai = info.getAbility(0); retVal = (ai.getAbilityType() == 1) && (ai.getLauncher() == 0); } @@ -217,7 +218,7 @@ public enum SlotType { boolean retVal = FILTER_WEAPON.test(info); retVal &= (info.getAbilityCount() > 0); if (retVal) { - AbilityInfo ai = info.getAbility(0); + AbilityEntry ai = info.getAbility(0); retVal &= (ai.getLauncher() == 0); retVal &= (ai.getAbilityType() == 2) || (ai.getAbilityType() == 4); } @@ -232,7 +233,7 @@ public enum SlotType { boolean retVal = FILTER_WEAPON.test(info); retVal &= (info.getAbilityCount() > 0); if (retVal) { - AbilityInfo ai = info.getAbility(0); + AbilityEntry ai = info.getAbility(0); retVal &= (ai.getLauncher() == 0); retVal &= (ai.getAbilityType() == 4); } @@ -306,9 +307,9 @@ public enum SlotType { private static final HashMap ITEM_CACHE = new HashMap<>(); - private final ColorInfo colorInfo = new ColorInfo(); - private final List abilityInfo = new ArrayList<>(); - private final List effectsInfo = new ArrayList<>(); + private final EffectInfo effectInfo = new EffectInfo(); + private final List abilityEntries = new ArrayList<>(); + private final List effectsEntries = new ArrayList<>(); private final ResourceEntry itmEntry; private String name; @@ -325,21 +326,21 @@ public enum SlotType { /** * Returns the {@code ItemInfo} structure based on the specified item resource. Entries are retrieved from cache * for improved performance. - * @param entry the ITM {@code ResourceEntry} + * @param itmEntry the ITM {@code ResourceEntry} * @return the {@code ItemInfo} structure with relevant item details. * @throws Exception if the ITM resource could not be loaded. */ - public static ItemInfo get(ResourceEntry entry) throws Exception + public static ItemInfo get(ResourceEntry itmEntry) throws Exception { ItemInfo retVal = null; - if (entry == null) { + if (itmEntry == null) { return EMPTY; } synchronized (ITEM_CACHE) { - retVal = ITEM_CACHE.get(entry); + retVal = ITEM_CACHE.get(itmEntry); if (retVal == null) { - retVal = new ItemInfo(entry); - ITEM_CACHE.put(entry, retVal); + retVal = new ItemInfo(itmEntry); + ITEM_CACHE.put(itmEntry, retVal); } } return retVal; @@ -348,19 +349,19 @@ public static ItemInfo get(ResourceEntry entry) throws Exception /** * Functions the same as {@link #get(ResourceEntry)} excepts that available cache entries will be updated with the * new item data. - * @param entry the ITM {@code ResourceEntry} + * @param itmEntry the ITM {@code ResourceEntry} * @return the {@code ItemInfo} structure with relevant item details. * @throws Exception if the ITM resource could not be loaded. */ - public static ItemInfo getValidated(ResourceEntry entry) throws Exception + public static ItemInfo getValidated(ResourceEntry itmEntry) throws Exception { ItemInfo retVal = null; - if (entry == null) { + if (itmEntry == null) { return EMPTY; } synchronized (ITEM_CACHE) { - retVal = new ItemInfo(entry); - ITEM_CACHE.put(entry, retVal); + retVal = new ItemInfo(itmEntry); + ITEM_CACHE.put(itmEntry, retVal); } return retVal; } @@ -535,29 +536,29 @@ public void overrideDroppableFlag(boolean override) /** Returns the enchantment value of the item. */ public int getEnchantment() { return enchantment; } - /** Provides access to the {@link ColorInfo} instance associated with the item. */ - public ColorInfo getColorInfo() { return colorInfo; } + /** Provides access to the {@link EffectInfo} instance associated with the item. */ + public EffectInfo getEffectInfo() { return effectInfo; } /** Returns number of defined item abilities. */ - public int getAbilityCount() { return abilityInfo.size(); } + public int getAbilityCount() { return abilityEntries.size(); } /** Returns the specified ability structure. */ - public AbilityInfo getAbility(int index) throws IndexOutOfBoundsException { return abilityInfo.get(index); } + public AbilityEntry getAbility(int index) throws IndexOutOfBoundsException { return abilityEntries.get(index); } /** Returns a sequential {@link Stream} of the {@code AbilityInfo} list. */ - public Stream getAbilityStream() { return abilityInfo.stream(); } + public Stream getAbilityStream() { return abilityEntries.stream(); } /** Returns the type of the specified ability. */ - public int getAbilityType(int index) { return (index >= 0 && index < abilityInfo.size()) ? abilityInfo.get(index).getAbilityType() : -1; } + public int getAbilityType(int index) { return (index >= 0 && index < abilityEntries.size()) ? abilityEntries.get(index).getAbilityType() : -1; } /** Returns the number of defined global item effects. */ - public int getGlobalEffectsCount() { return effectsInfo.size(); } + public int getGlobalEffectsCount() { return effectsEntries.size(); } /** Returns the specified global item effect. */ - public EffectInfo getGlobalEffect(int index) throws IndexOutOfBoundsException { return effectsInfo.get(index); } + public EffectEntry getGlobalEffect(int index) throws IndexOutOfBoundsException { return effectsEntries.get(index); } /** Returns a sequential {@link Stream} of the {@code EffectInfo} list. */ - public Stream getEffectStream() { return effectsInfo.stream(); } + public Stream getEffectStream() { return effectsEntries.stream(); } /** Returns the most suitable item slot type compatible with the current item. */ public SlotType getSlotType() @@ -764,9 +765,9 @@ private void init() throws IOException, Exception for (int i = 0; i < numFx; i++) { Misc.requireCondition(is.read(effect) == effect.length, "Could not read effect " + i + ": " + itmEntry); curOfs += effect.length; - EffectInfo ei = new EffectInfo(effect); - effectsInfo.add(ei); - parseEffect(ei); + EffectEntry ei = new EffectEntry(effect); + effectsEntries.add(ei); + parseEffect(effect); } } @@ -779,7 +780,7 @@ private void init() throws IOException, Exception for (int i = 0; i < numAbil; i++) { Misc.requireCondition(is.read(ability) == ability.length, "Could not read ability " + i + ": " + itmEntry); curOfs += ability.length; - abilityInfo.add(new AbilityInfo(ability)); + abilityEntries.add(new AbilityEntry(ability)); } } @@ -792,60 +793,18 @@ private void init() throws IOException, Exception for (int i = 0; i < numFx; i++) { Misc.requireCondition(is.read(effect) == effect.length, "Could not read effect " + i + ": " + itmEntry); curOfs += effect.length; - EffectInfo ei = new EffectInfo(effect); - effectsInfo.add(ei); - parseEffect(ei); + EffectEntry ei = new EffectEntry(effect); + effectsEntries.add(ei); + parseEffect(effect); } } } } - // Processes a global effect: only "set color" effect is considered - private void parseEffect(EffectInfo info) + // Processes a global effect + private void parseEffect(byte[] data) { - switch (info.getOpcode()) { - case ColorInfo.OPCODE_SET_COLOR: - case ColorInfo.OPCODE_SET_COLOR_GLOW: - case ColorInfo.OPCODE_TINT_SOLID: - case ColorInfo.OPCODE_TINT_BRIGHT: - if (info.getTarget() == 1 && info.getTiming() == 2) { - // self target; when equipped - SegmentDef.SpriteType type = null; - int location = info.getParameter2() & 0xf; - switch ((info.getParameter2() >> 4) & 0xf) { - case 0: - type = SegmentDef.SpriteType.AVATAR; - break; - case 1: - type = SegmentDef.SpriteType.WEAPON; - break; - case 2: - type = SegmentDef.SpriteType.SHIELD; - break; - case 3: - type = SegmentDef.SpriteType.HELMET; - break; - default: - if ((info.getParameter2() & 0xff) == 0xff) { - type = SegmentDef.SpriteType.AVATAR; - location = -1; - } - } - getColorInfo().add(type, info.getOpcode(), location, info.getParameter1()); - } - break; - case ColorInfo.OPCODE_TRANSLUCENCY: - if (info.getParameter2() == 0) { - int param1 = Math.max(0, Math.min(255, info.getParameter1())); - getColorInfo().add(SegmentDef.SpriteType.AVATAR, info.getOpcode(), -1, param1); - } - break; - case ColorInfo.OPCODE_BLUR: - case ColorInfo.OPCODE_PETRIFICATION: - case ColorInfo.OPCODE_STONESKIN: - getColorInfo().add(SegmentDef.SpriteType.AVATAR, info.getOpcode(), -1, 0); - break; - } + getEffectInfo().add(EffectInfo.Effect.fromEffectV1(ByteBuffer.wrap(data), 0)); } //--------------------- Begin Interface Comparable --------------------- @@ -866,9 +825,9 @@ public int compareTo(ItemInfo o) public int hashCode() { int hash = 7; - hash = 31 * hash + colorInfo.hashCode(); - hash = 31 * hash + abilityInfo.hashCode(); - hash = 31 * hash + effectsInfo.hashCode(); + hash = 31 * hash + effectInfo.hashCode(); + hash = 31 * hash + abilityEntries.hashCode(); + hash = 31 * hash + effectsEntries.hashCode(); hash = 31 * hash + ((itmEntry == null) ? 0 : itmEntry.hashCode()); hash = 31 * hash + ((name == null) ? 0 : name.hashCode()); hash = 31 * hash + ((nameIdentified == null) ? 0 : nameIdentified.hashCode()); @@ -893,12 +852,12 @@ public boolean equals(Object o) return false; } ItemInfo other = (ItemInfo)o; - boolean retVal = (this.colorInfo == null && other.colorInfo == null) || - (this.colorInfo != null && this.colorInfo.equals(other.colorInfo)); - retVal &= (this.abilityInfo == null && other.abilityInfo == null) || - (this.abilityInfo != null && this.abilityInfo.equals(other.abilityInfo)); - retVal &= (this.effectsInfo == null && other.effectsInfo == null) || - (this.effectsInfo != null && this.effectsInfo.equals(other.effectsInfo)); + boolean retVal = (this.effectInfo == null && other.effectInfo == null) || + (this.effectInfo != null && this.effectInfo.equals(other.effectInfo)); + retVal &= (this.abilityEntries == null && other.abilityEntries == null) || + (this.abilityEntries != null && this.abilityEntries.equals(other.abilityEntries)); + retVal &= (this.effectsEntries == null && other.effectsEntries == null) || + (this.effectsEntries != null && this.effectsEntries.equals(other.effectsEntries)); retVal &= (this.itmEntry == null && other.itmEntry == null) || (this.itmEntry != null && this.itmEntry.equals(other.itmEntry)); retVal &= (this.name == null && other.name == null) || @@ -936,7 +895,7 @@ public String toString() //-------------------------- INNER CLASSES -------------------------- /** Storage class for relevant ability attributes. */ - public static class AbilityInfo + public static class AbilityEntry { private final int type; private final int location; @@ -953,7 +912,7 @@ public static class AbilityInfo private final boolean isBullet; /** Parses the item ability structure described by the byte array. */ - private AbilityInfo(byte[] ability) + private AbilityEntry(byte[] ability) { Objects.requireNonNull(ability); DynamicArray buf = DynamicArray.wrap(ability, DynamicArray.ElementType.BYTE); @@ -1016,7 +975,7 @@ private AbilityInfo(byte[] ability) } /** Storage class for relevant global effects attributes. */ - public static class EffectInfo + public static class EffectEntry { private final int opcode; private final int target; @@ -1032,7 +991,7 @@ public static class EffectInfo private final int special; /** Parses the EFF V1 structure described by the byte array. */ - private EffectInfo(byte[] effect) + private EffectEntry(byte[] effect) { Objects.requireNonNull(effect); DynamicArray buf = DynamicArray.wrap(effect, DynamicArray.ElementType.BYTE); diff --git a/src/org/infinity/resource/cre/decoder/util/SpriteUtils.java b/src/org/infinity/resource/cre/decoder/util/SpriteUtils.java index 207134d23..ae27789ae 100644 --- a/src/org/infinity/resource/cre/decoder/util/SpriteUtils.java +++ b/src/org/infinity/resource/cre/decoder/util/SpriteUtils.java @@ -820,9 +820,9 @@ public static int[] tintColors(int[] palette, int startOfs, int length, int opco int[] retVal = palette; switch (opcode) { - case ColorInfo.OPCODE_SET_COLOR_GLOW: - case ColorInfo.OPCODE_TINT_SOLID: - case ColorInfo.OPCODE_TINT_BRIGHT: + case EffectInfo.OPCODE_SET_COLOR_GLOW: + case EffectInfo.OPCODE_TINT_SOLID: + case EffectInfo.OPCODE_TINT_BRIGHT: break; default: return retVal; @@ -841,17 +841,17 @@ public static int[] tintColors(int[] palette, int startOfs, int length, int opco int sg = (retVal[startOfs + i] >> 8) & 0xff; int sb = retVal[startOfs + i] & 0xff; switch (opcode) { - case ColorInfo.OPCODE_SET_COLOR_GLOW: + case EffectInfo.OPCODE_SET_COLOR_GLOW: sr = Math.min(255, sr + dr - (sr >>> 2)); sg = Math.min(255, sg + dg - (sg >>> 2)); sb = Math.min(255, sb + db - (sb >>> 2)); break; - case ColorInfo.OPCODE_TINT_SOLID: + case EffectInfo.OPCODE_TINT_SOLID: sr = Math.min(255, dr * sr / 255); sg = Math.min(255, dg * sg / 255); sb = Math.min(255, db * sb / 255); break; - case ColorInfo.OPCODE_TINT_BRIGHT: + case EffectInfo.OPCODE_TINT_BRIGHT: sr = Math.min(255, sr + (dr * (sr >>> 3))); sg = Math.min(255, sg + (dg * (sg >>> 3))); sb = Math.min(255, sb + (db * (sb >>> 3))); From a17e6c30c5e636fa5322077c62171e57a330b0e5 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sat, 17 Apr 2021 14:19:44 +0200 Subject: [PATCH 134/158] SpriteDecoder: Fix bug in false color detection for guessed animation types --- src/org/infinity/resource/cre/decoder/util/SpriteUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/infinity/resource/cre/decoder/util/SpriteUtils.java b/src/org/infinity/resource/cre/decoder/util/SpriteUtils.java index ae27789ae..3fb3a2804 100644 --- a/src/org/infinity/resource/cre/decoder/util/SpriteUtils.java +++ b/src/org/infinity/resource/cre/decoder/util/SpriteUtils.java @@ -519,7 +519,7 @@ public static boolean bamHasFalseColors(ResourceEntry entry) boolean retVal = false; try { BamV1Decoder.BamV1Control control = loadBamController(entry); - int[] palette = control.getCurrentPalette(); + int[] palette = control.getPalette(); if (Profile.getGame() == Profile.Game.PST || Profile.getGame() == Profile.Game.PSTEE) { retVal = (palette[224] & 0xffffff) == 0x0000ff; retVal &= (palette[240] & 0xffffff) == 0x00009f; From 6b46b6953431d17d19fb57414231aaf230bb1119 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sat, 17 Apr 2021 22:40:58 +0200 Subject: [PATCH 135/158] Various generic fixes - Added missing hashCode() methods for classes with equals() implementations - Removed redundant typecasts - Fixed an unchecked array access in the area resource tileset renderer --- src/org/infinity/datatype/Bestiary.java | 2 +- src/org/infinity/datatype/Datatype.java | 4 +- src/org/infinity/datatype/ResourceBitmap.java | 12 ++++++ src/org/infinity/gui/BookmarkEditor.java | 4 +- src/org/infinity/gui/FontChooser.java | 4 +- src/org/infinity/gui/IdsBrowser.java | 4 +- .../infinity/gui/converter/ConvertToBam.java | 2 +- .../infinity/gui/hexview/BasicColorMap.java | 11 +++++ .../gui/layeritem/AbstractLayerItem.java | 2 +- src/org/infinity/resource/Profile.java | 3 +- .../resource/are/viewer/TilesetRenderer.java | 8 +++- src/org/infinity/resource/bcs/Signatures.java | 24 +++++++++++ src/org/infinity/resource/chu/Viewer.java | 12 +++--- src/org/infinity/resource/chu/Window.java | 6 +-- .../resource/graphics/DxtEncoder.java | 21 ++++++++++ .../resource/graphics/PseudoBamDecoder.java | 18 +++++++- src/org/infinity/resource/key/BIFFEntry.java | 41 +++++++++++++++---- .../resource/key/BIFFResourceEntry.java | 32 ++++++++++++--- src/org/infinity/resource/key/Keyfile.java | 13 ++++++ .../resource/key/ResourceTreeFolder.java | 8 ++-- .../infinity/search/ReferenceHitFrame.java | 13 ++++++ .../util/io/zip/ZipCentralHeader.java | 14 +++++++ src/org/infinity/util/io/zip/ZipNode.java | 11 +++++ 23 files changed, 224 insertions(+), 45 deletions(-) diff --git a/src/org/infinity/datatype/Bestiary.java b/src/org/infinity/datatype/Bestiary.java index 94f5bc7f5..ce2488a7e 100644 --- a/src/org/infinity/datatype/Bestiary.java +++ b/src/org/infinity/datatype/Bestiary.java @@ -218,7 +218,7 @@ public KillVariable findVariable(GamResource game) if (killVarName == null) return null; final SectionOffset offset = game.getSectionOffset(KillVariable.class); - KillVariable var = (KillVariable)game.getAttribute(offset.getValue(), KillVariable.class, false); + KillVariable var = game.getAttribute(offset.getValue(), KillVariable.class, false); if (var == null) return null; final List fields = game.getFields(); diff --git a/src/org/infinity/datatype/Datatype.java b/src/org/infinity/datatype/Datatype.java index 03370f6aa..cbd2cf62e 100644 --- a/src/org/infinity/datatype/Datatype.java +++ b/src/org/infinity/datatype/Datatype.java @@ -242,8 +242,8 @@ protected void fireValueUpdated(UpdateEvent event) */ protected void firePropertyChange(Object oldValue, Object newValue) { - if (parent instanceof AbstractStruct) { - ((AbstractStruct)parent).propertyChange(new PropertyChangeEvent(parent, getName(), oldValue, newValue)); + if (parent != null) { + parent.propertyChange(new PropertyChangeEvent(parent, getName(), oldValue, newValue)); } } diff --git a/src/org/infinity/datatype/ResourceBitmap.java b/src/org/infinity/datatype/ResourceBitmap.java index 817bce18b..59ec7aef2 100644 --- a/src/org/infinity/datatype/ResourceBitmap.java +++ b/src/org/infinity/datatype/ResourceBitmap.java @@ -219,6 +219,18 @@ public String toString() return desc; } + @Override + public int hashCode() + { + int hash = 7; + hash = 31 * hash + Long.hashCode(value); + hash = 31 * hash + ((name == null) ? 0 : name.hashCode()); + hash = 31 * hash + ((entry == null) ? 0 : entry.hashCode()); + hash = 31 * hash + ((searchString == null) ? 0 : searchString.hashCode()); + hash = 31 * hash + ((desc == null) ? 0 : desc.hashCode()); + return hash; + } + @Override public boolean equals(Object o) { diff --git a/src/org/infinity/gui/BookmarkEditor.java b/src/org/infinity/gui/BookmarkEditor.java index 8746fa85a..ad2ceb7f2 100644 --- a/src/org/infinity/gui/BookmarkEditor.java +++ b/src/org/infinity/gui/BookmarkEditor.java @@ -305,7 +305,7 @@ private void accept() // updating bookmark list listBookmarks.clear(); for (int i = 0, size = modelEntries.size(); i < size; i++) { - listBookmarks.add((BrowserMenuBar.Bookmark)modelEntries.get(i)); + listBookmarks.add(modelEntries.get(i)); } accepted = true; setVisible(false); @@ -534,7 +534,7 @@ public void focusLost(FocusEvent event) if (event.getSource() == tfName) { int idx = listEntries.getSelectedIndex(); if (idx >= 0) { - BrowserMenuBar.Bookmark bookmark = (BrowserMenuBar.Bookmark)modelEntries.get(idx); + BrowserMenuBar.Bookmark bookmark = modelEntries.get(idx); if (!tfName.getText().trim().isEmpty()) { // update name in selected entry bookmark.setName(tfName.getText().trim()); diff --git a/src/org/infinity/gui/FontChooser.java b/src/org/infinity/gui/FontChooser.java index 1dff7bb1f..f87c43a37 100644 --- a/src/org/infinity/gui/FontChooser.java +++ b/src/org/infinity/gui/FontChooser.java @@ -246,7 +246,7 @@ public JList getFontSizeList() **/ public String getSelectedFontFamily() { - String fontName = (String) getFontFamilyList().getSelectedValue(); + String fontName = getFontFamilyList().getSelectedValue(); return fontName; } @@ -284,7 +284,7 @@ public int getSelectedFontSize() fontSize = Integer.parseInt(fontSizeString); break; } catch (NumberFormatException e) { - fontSizeString = (String) getFontSizeList().getSelectedValue(); + fontSizeString = getFontSizeList().getSelectedValue(); getFontSizeTextField().setText(fontSizeString); } } diff --git a/src/org/infinity/gui/IdsBrowser.java b/src/org/infinity/gui/IdsBrowser.java index 6ab2ad096..a7f5bbe5b 100644 --- a/src/org/infinity/gui/IdsBrowser.java +++ b/src/org/infinity/gui/IdsBrowser.java @@ -60,7 +60,7 @@ public IdsBrowser() public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) - insertString(((IdsMapEntry)list.getSelectedValue()).getSymbol()); + insertString(list.getSelectedValue().getSymbol()); } }); @@ -99,7 +99,7 @@ public void actionPerformed(ActionEvent event) if (event.getSource() == idsfiles) refreshList(); else if (event.getSource() == binsert) - insertString(((IdsMapEntry)list.getSelectedValue()).getSymbol()); + insertString(list.getSelectedValue().getSymbol()); } // --------------------- End Interface ActionListener --------------------- diff --git a/src/org/infinity/gui/converter/ConvertToBam.java b/src/org/infinity/gui/converter/ConvertToBam.java index f9317c08a..8efefeb77 100644 --- a/src/org/infinity/gui/converter/ConvertToBam.java +++ b/src/org/infinity/gui/converter/ConvertToBam.java @@ -2566,7 +2566,7 @@ private boolean framesAddImage(int listIndex, ResourceEntry entry, int frameInde } } else { // Everything else - ImageReader reader = (ImageReader)ImageIO.getImageReadersBySuffix(entry.getExtension()).next(); + ImageReader reader = ImageIO.getImageReadersBySuffix(entry.getExtension()).next(); reader.setInput(ImageIO.createImageInputStream(is), false); int numFrames = reader.getNumImages(true); images = new BufferedImage[numFrames]; diff --git a/src/org/infinity/gui/hexview/BasicColorMap.java b/src/org/infinity/gui/hexview/BasicColorMap.java index 2bce42036..789f8a042 100644 --- a/src/org/infinity/gui/hexview/BasicColorMap.java +++ b/src/org/infinity/gui/hexview/BasicColorMap.java @@ -484,6 +484,17 @@ public ColoredBlock(int offset, int size, Coloring color, int index) public int getColorIndex() { return index; } // public void setColorIndex(int index) { this.index = index & 1; } + @Override + public int hashCode() + { + int hash = 7; + hash = 31 * hash + offset; + hash = 31 * hash + size; + hash = 31 * hash + index; + hash = 31 * hash + ((color == null) ? 0 : color.hashCode()); + return hash; + } + @Override public boolean equals(Object o) { diff --git a/src/org/infinity/gui/layeritem/AbstractLayerItem.java b/src/org/infinity/gui/layeritem/AbstractLayerItem.java index a8664da30..b0b7ccf53 100644 --- a/src/org/infinity/gui/layeritem/AbstractLayerItem.java +++ b/src/org/infinity/gui/layeritem/AbstractLayerItem.java @@ -156,7 +156,7 @@ public Viewable getViewable() public void showViewable() { if (viewable != null && getTopLevelAncestor() != null) { - new ViewFrame(getTopLevelAncestor(), (Viewable)viewable); + new ViewFrame(getTopLevelAncestor(), viewable); } } diff --git a/src/org/infinity/resource/Profile.java b/src/org/infinity/resource/Profile.java index 2e1906434..fbae5b449 100644 --- a/src/org/infinity/resource/Profile.java +++ b/src/org/infinity/resource/Profile.java @@ -47,7 +47,6 @@ import org.infinity.gui.BrowserMenuBar; import org.infinity.gui.ViewerUtil; import org.infinity.resource.key.ResourceEntry; -import org.infinity.resource.key.ResourceTreeFolder; import org.infinity.resource.key.ResourceTreeModel; import org.infinity.util.DataString; import org.infinity.util.Platform; @@ -2438,7 +2437,7 @@ private void initCampaigns() if (FileEx.create(path).isDirectory()) { String folderName = path.getFileName().toString(); if (model.getFolder(folderName) == null) { - model.addDirectory((ResourceTreeFolder)model.getRoot(), path, false); + model.addDirectory(model.getRoot(), path, false); } } } diff --git a/src/org/infinity/resource/are/viewer/TilesetRenderer.java b/src/org/infinity/resource/are/viewer/TilesetRenderer.java index fe36dfc80..33199857f 100644 --- a/src/org/infinity/resource/are/viewer/TilesetRenderer.java +++ b/src/org/infinity/resource/are/viewer/TilesetRenderer.java @@ -941,7 +941,11 @@ private synchronized void drawTile(Tile tile, boolean isDoorTile) int[] srcSec = null; tileIdx = tile.getSecondaryIndex(); if (tileIdx >= 0) { - srcSec = listTilesets.get(0).listTileData.get(tileIdx); + if (tileIdx < listTilesets.get(0).listTileData.size()) { + srcSec = listTilesets.get(0).listTileData.get(tileIdx); + } else { + System.err.println("Invalid tile index: " + tileIdx + " of " + listTilesets.get(0).listTileData.size()); + } } // determining correct rendering mode @@ -1167,7 +1171,7 @@ private void init(WedResource wed, Overlay ovl) // initializing overlay flags IsNumeric drawOverlays = (IsNumeric)tile.getAttribute(Tilemap.WED_TILEMAP_DRAW_OVERLAYS); - int flags = (int)drawOverlays.getValue() & 255; + int flags = drawOverlays.getValue() & 255; listTiles.add(new Tile(x, y, count, tileIdx, tileIdx2, flags, isTisPalette)); curOfs += tile.getSize(); diff --git a/src/org/infinity/resource/bcs/Signatures.java b/src/org/infinity/resource/bcs/Signatures.java index c03c3ec8f..67b46d86f 100644 --- a/src/org/infinity/resource/bcs/Signatures.java +++ b/src/org/infinity/resource/bcs/Signatures.java @@ -328,6 +328,17 @@ public int countStrings() return count; } + @Override + public int hashCode() + { + int hash = 7; + hash = 31 * hash + id; + hash = 31 * hash + ((name == null) ? 0 : name.hashCode()); + hash = 31 * hash + ((param == null) ? 0 : param.hashCode()); + hash = 31 * hash + ((type == null) ? 0 : type.hashCode()); + return hash; + } + @Override public boolean equals(Object obj) { @@ -595,6 +606,19 @@ private void setColonSeparatedString(boolean b) colonSeparated = b; } + @Override + public int hashCode() + { + int hash = 7; + hash = 31 * hash + type; + hash = 31 * hash + ((name == null) ? 0 : name.hashCode()); + hash = 31 * hash + ((idsRef == null) ? 0 : idsRef.hashCode()); + hash = 31 * hash + ((resType == null) ? 0 : resType.hashCode()); + hash = 31 * hash + Boolean.hashCode(combinedString); + hash = 31 * hash + Boolean.hashCode(colonSeparated); + return hash; + } + @Override public boolean equals(Object obj) { diff --git a/src/org/infinity/resource/chu/Viewer.java b/src/org/infinity/resource/chu/Viewer.java index b77bfae42..58da9a64a 100644 --- a/src/org/infinity/resource/chu/Viewer.java +++ b/src/org/infinity/resource/chu/Viewer.java @@ -182,7 +182,7 @@ public void valueChanged(ListSelectionEvent e) setPreview(p.getImage()); } } else if (e.getSource() == controlsList) { - BaseControl c = (BaseControl)controlsList.getSelectedValue(); + BaseControl c = controlsList.getSelectedValue(); if (c != null) { // updating properties panel getProperties().updateProperties(c); @@ -224,7 +224,7 @@ public void mouseClicked(MouseEvent e) { if (e.getSource() == panelsList) { if (e.getClickCount() == 2 && !e.isConsumed()) { - Panel p = (Panel)panelsList.getSelectedValue(); + Panel p = panelsList.getSelectedValue(); if (p != null) { StructViewer v = getResource().getViewer(); if (v != null) { @@ -234,9 +234,9 @@ public void mouseClicked(MouseEvent e) } } else if (e.getSource() == controlsList) { if (e.getClickCount() == 2 && !e.isConsumed()) { - Panel p = (Panel)panelsList.getSelectedValue(); + Panel p = panelsList.getSelectedValue(); if (p != null) { - BaseControl c = (BaseControl)controlsList.getSelectedValue(); + BaseControl c = controlsList.getSelectedValue(); if (c != null && !c.isEmpty()) { StructViewer v = getResource().getViewer(); if (v != null) { @@ -911,7 +911,7 @@ public void reset() // recreating controls int numControls = getViewer().getControls().getSize(); for (int i = 0; i < numControls; i++) { - BaseControl control = (BaseControl)getViewer().getControls().getElementAt(i); + BaseControl control = getViewer().getControls().getElementAt(i); if (control != null) { control.updateImage(); } @@ -985,7 +985,7 @@ private void updateImage() // 2. drawing control elements onto the panel int numControls = getViewer().getControls().getSize(); for (int i = 0; i < numControls; i++) { - BaseControl control = (BaseControl)getViewer().getControls().getElementAt(i); + BaseControl control = getViewer().getControls().getElementAt(i); if (control != null) { Image ctrlImage = control.getImage(); if (ctrlImage != null) { diff --git a/src/org/infinity/resource/chu/Window.java b/src/org/infinity/resource/chu/Window.java index e66386eec..7d566a53e 100644 --- a/src/org/infinity/resource/chu/Window.java +++ b/src/org/infinity/resource/chu/Window.java @@ -76,7 +76,7 @@ public ChuResource getParent() /** Returns the number of controls associated with this panel. */ public int getControlCount() { - return (int)((UnsignDecNumber)getAttribute(CHU_WINDOW_NUM_CONTROLS)).getValue(); + return ((UnsignDecNumber)getAttribute(CHU_WINDOW_NUM_CONTROLS)).getValue(); } /** Returns the given control. Index is relative to the controls associated with this panel. */ @@ -123,8 +123,8 @@ public String getBackgroundImage() public int readControls(ByteBuffer buffer) throws Exception { - int numctrl = (int)((UnsignDecNumber)getAttribute(CHU_WINDOW_NUM_CONTROLS)).getValue(); - int first = (int)((UnsignDecNumber)getAttribute(CHU_WINDOW_FIRST_CONTROL_INDEX)).getValue(); + int numctrl = ((UnsignDecNumber)getAttribute(CHU_WINDOW_NUM_CONTROLS)).getValue(); + int first = ((UnsignDecNumber)getAttribute(CHU_WINDOW_FIRST_CONTROL_INDEX)).getValue(); int controlsoffset = getParent().getControlsOffset() + (first*8); int endoffset = controlsoffset; for (int i = 0; i < numctrl; i++) { diff --git a/src/org/infinity/resource/graphics/DxtEncoder.java b/src/org/infinity/resource/graphics/DxtEncoder.java index 8a0ad2077..b5ad4d68d 100644 --- a/src/org/infinity/resource/graphics/DxtEncoder.java +++ b/src/org/infinity/resource/graphics/DxtEncoder.java @@ -2390,6 +2390,16 @@ public Vec3(final float x, final float y, final float z) vx = x; vy = y; vz = z; } + @Override + public int hashCode() + { + int hash = 7; + hash = 31 * hash + Float.hashCode(vx); + hash = 31 * hash + Float.hashCode(vy); + hash = 31 * hash + Float.hashCode(vz); + return hash; + } + @Override public boolean equals(Object o) { @@ -2569,6 +2579,17 @@ public Vec4(final float x, final float y, final float z, final float w) vx = x; vy = y; vz = z; vw = w; } + @Override + public int hashCode() + { + int hash = 7; + hash = 31 * hash + Float.hashCode(vx); + hash = 31 * hash + Float.hashCode(vy); + hash = 31 * hash + Float.hashCode(vz); + hash = 31 * hash + Float.hashCode(vw); + return hash; + } + @Override public boolean equals(Object o) { diff --git a/src/org/infinity/resource/graphics/PseudoBamDecoder.java b/src/org/infinity/resource/graphics/PseudoBamDecoder.java index 2f845da64..415f1fc81 100644 --- a/src/org/infinity/resource/graphics/PseudoBamDecoder.java +++ b/src/org/infinity/resource/graphics/PseudoBamDecoder.java @@ -751,7 +751,7 @@ public boolean exportBamV1(Path fileName, ProgressMonitor progress, int curProgr cnt++; srcIdx++; } - dstData[dstIdx++] = (byte)rleIndex; + dstData[dstIdx++] = rleIndex; dstData[dstIdx++] = (byte)cnt; } else { // uncompressed pixels @@ -807,7 +807,7 @@ public boolean exportBamV1(Path fileName, ProgressMonitor progress, int curProgr System.arraycopy("BAM V1 ".getBytes(), 0, bamData, 0, 8); DynamicArray.putShort(bamData, 0x08, (short)listFrames.size()); DynamicArray.putByte(bamData, 0x0a, (byte)listCycles.size()); - DynamicArray.putByte(bamData, 0x0b, (byte)rleIndex); + DynamicArray.putByte(bamData, 0x0b, rleIndex); DynamicArray.putInt(bamData, 0x0c, ofsFrameEntries); DynamicArray.putInt(bamData, 0x10, ofsPalette); DynamicArray.putInt(bamData, 0x14, ofsLookup); @@ -2138,6 +2138,20 @@ public FrameDataV2(int page, int sx, int sy, int width, int height, int dx, int this.dy = dy; } + @Override + public int hashCode() + { + int hash = 7; + hash = 31 * hash + page; + hash = 31 * hash + sx; + hash = 31 * hash + sy; + hash = 31 * hash + width; + hash = 31 * hash + height; + hash = 31 * hash + dx; + hash = 31 * hash + dy; + return hash; + } + @Override public boolean equals(Object o) { diff --git a/src/org/infinity/resource/key/BIFFEntry.java b/src/org/infinity/resource/key/BIFFEntry.java index 8188e7498..60ceceba9 100644 --- a/src/org/infinity/resource/key/BIFFEntry.java +++ b/src/org/infinity/resource/key/BIFFEntry.java @@ -86,22 +86,45 @@ public void write(OutputStream os) throws IOException //--------------------- End Interface Writeable --------------------- + @Override + public int hashCode() + { + int hash = 7; + hash = 31 * hash + location; + hash = 31 * hash + ((keyFile == null) ? 0 : keyFile.hashCode()); + hash = 31 * hash + ((biffFile == null) ? 0 : biffFile.hashCode()); + hash = 31 * hash + ((fileName == null) ? 0 : fileName.hashCode()); + hash = 31 * hash + index; + hash = 31 * hash + fileSize; + hash = 31 * hash + stringOffset; + hash = 31 * hash + (int)separatorChar; + return hash; + } + @Override public boolean equals(Object o) { if (this == o) { return true; - } else if (o instanceof BIFFEntry) { - BIFFEntry other = (BIFFEntry)o; - boolean bRet = (keyFile == null && other.keyFile == null) || - (keyFile != null && keyFile.equals(other.keyFile)); - bRet &= (biffFile == null && other.biffFile == null) || - (biffFile != null && biffFile.equals(other.biffFile)); - bRet &= (fileSize == other.fileSize) && (stringOffset == other.stringOffset); - return bRet; - } else { + } + + if (!(o instanceof BIFFEntry)) { return false; } + + BIFFEntry other = (BIFFEntry)o; + boolean retVal = (location == other.location); + retVal &= (keyFile == null && other.keyFile == null) || + (keyFile != null && keyFile.equals(other.keyFile)); + retVal &= (biffFile == null && other.biffFile == null) || + (biffFile != null && biffFile.equals(other.biffFile)); + retVal &= (fileName == null && other.fileName == null) || + (fileName != null && fileName.equals(other.fileName)); + retVal &= (index == other.index); + retVal &= (fileSize == other.fileSize); + retVal &= (stringOffset == other.stringOffset); + retVal &= (separatorChar == other.separatorChar); + return retVal; } @Override diff --git a/src/org/infinity/resource/key/BIFFResourceEntry.java b/src/org/infinity/resource/key/BIFFResourceEntry.java index c923481fc..f2f4bc721 100644 --- a/src/org/infinity/resource/key/BIFFResourceEntry.java +++ b/src/org/infinity/resource/key/BIFFResourceEntry.java @@ -85,18 +85,38 @@ public void write(OutputStream os) throws IOException // --------------------- End Interface Writeable --------------------- + @Override + public int hashCode() + { + int hash = super.hashCode(); + hash = 31 * hash + ((resourceName == null) ? 0 : resourceName.hashCode()); + hash = 31 * hash + type; + hash = 31 * hash + ((extension == null) ? 0 : extension.hashCode()); + hash = 31 * hash + Boolean.hashCode(hasOverride); + hash = 31 * hash + locator; + return hash; + } + @Override public boolean equals(Object o) { if (o == this) { return true; - } else if (o instanceof BIFFResourceEntry) { - BIFFResourceEntry other = (BIFFResourceEntry)o; - return (locator == other.locator) && - (type == other.type) && - resourceName.equalsIgnoreCase(other.resourceName); } - return false; + + if (!(o instanceof BIFFResourceEntry)) { + return false; + } + + BIFFResourceEntry other = (BIFFResourceEntry)o; + boolean retVal = (resourceName == null && other.resourceName == null) || + (resourceName != null && resourceName.equalsIgnoreCase(other.resourceName)); + retVal &= (type == other.type); + retVal &= (extension == null && other.extension == null) || + (extension != null && extension.equalsIgnoreCase(other.extension)); + retVal &= (hasOverride == other.hasOverride); + retVal &= (locator == other.locator); + return retVal; } @Override diff --git a/src/org/infinity/resource/key/Keyfile.java b/src/org/infinity/resource/key/Keyfile.java index aaef18511..a037e5448 100644 --- a/src/org/infinity/resource/key/Keyfile.java +++ b/src/org/infinity/resource/key/Keyfile.java @@ -222,6 +222,19 @@ public Keyfile(Path keyFile) throws FileNotFoundException resourceIcons.put("LOG", ICON_TEXT);// WeiDU log files - from Special category in the resource tree } + @Override + public int hashCode() + { + int hash = 7; + hash = 31 * hash + ((keyFile == null) ? 0 : keyFile.hashCode()); + hash = 31 * hash + ((keyList == null) ? 0 : keyList.hashCode()); + hash = 31 * hash + ((extMap == null) ? 0 : extMap.hashCode()); + hash = 31 * hash + ((resourceIcons == null) ? 0 : resourceIcons.hashCode()); + hash = 31 * hash + ((biffEntries == null) ? 0 : biffEntries.hashCode()); + hash = 31 * hash + ((resourceEntries == null) ? 0 : resourceEntries.hashCode()); + return hash; + } + @Override public boolean equals(Object o) { diff --git a/src/org/infinity/resource/key/ResourceTreeFolder.java b/src/org/infinity/resource/key/ResourceTreeFolder.java index 53d5f5383..0f7639c20 100644 --- a/src/org/infinity/resource/key/ResourceTreeFolder.java +++ b/src/org/infinity/resource/key/ResourceTreeFolder.java @@ -287,7 +287,7 @@ public T first() @Override public synchronized SortedSet headSet(T toElement) { - int toIdx = Collections.binarySearch(this, (T)toElement); + int toIdx = Collections.binarySearch(this, toElement); if (toIdx < 0) { toIdx = ~toIdx; } else { @@ -308,11 +308,11 @@ public T last() @Override public synchronized SortedSet subSet(T fromElement, T toElement) { - int fromIdx = Collections.binarySearch(this, (T)fromElement); + int fromIdx = Collections.binarySearch(this, fromElement); if (fromIdx < 0) { fromIdx = ~fromIdx; } - int toIdx = Collections.binarySearch(this, (T)toElement); + int toIdx = Collections.binarySearch(this, toElement); if (toIdx < 0) { toIdx = ~toIdx; } else { @@ -324,7 +324,7 @@ public synchronized SortedSet subSet(T fromElement, T toElement) @Override public synchronized SortedSet tailSet(T fromElement) { - int fromIdx = Collections.binarySearch(this, (T)fromElement); + int fromIdx = Collections.binarySearch(this, fromElement); if (fromIdx < 0) { fromIdx = ~fromIdx; } diff --git a/src/org/infinity/search/ReferenceHitFrame.java b/src/org/infinity/search/ReferenceHitFrame.java index e8957e501..2062a8e60 100644 --- a/src/org/infinity/search/ReferenceHitFrame.java +++ b/src/org/infinity/search/ReferenceHitFrame.java @@ -319,6 +319,19 @@ public String toString() } } + @Override + public int hashCode() + { + int hash = 7; + hash = 31 * hash + ((mode == null) ? 0 : mode.hashCode()); + hash = 31 * hash + ((entry == null) ? 0 : entry.hashCode()); + hash = 31 * hash + ((name == null) ? 0 : name.hashCode()); + hash = 31 * hash + ((ref == null) ? 0 : ref.hashCode()); + hash = 31 * hash + ((line == null) ? 0 : line.hashCode()); + hash = 31 * hash + lineNr; + return hash; + } + @Override public boolean equals(Object obj) { diff --git a/src/org/infinity/util/io/zip/ZipCentralHeader.java b/src/org/infinity/util/io/zip/ZipCentralHeader.java index 7df3aa3d2..48070ca79 100644 --- a/src/org/infinity/util/io/zip/ZipCentralHeader.java +++ b/src/org/infinity/util/io/zip/ZipCentralHeader.java @@ -98,6 +98,20 @@ public long getDataOffset(SeekableByteChannel ch) throws IOException } } + @Override + public int hashCode() + { + int hash = super.hashCode(); + hash = 31 * hash + versionCreated; + hash = 31 * hash + idxDisk; + hash = 31 * hash + attribInternal; + hash = 31 * hash + Long.hashCode(attribExternal); + hash = 31 * hash + Long.hashCode(ofsLocalHeader); + hash = 31 * hash + ((comment == null) ? 0 : comment.hashCode()); + hash = 31 * hash + ((localHeader == null) ? 0 : localHeader.hashCode()); + return hash; + } + @Override public boolean equals(Object o) { diff --git a/src/org/infinity/util/io/zip/ZipNode.java b/src/org/infinity/util/io/zip/ZipNode.java index 28695888f..5b2287287 100644 --- a/src/org/infinity/util/io/zip/ZipNode.java +++ b/src/org/infinity/util/io/zip/ZipNode.java @@ -205,6 +205,17 @@ public String toString() return new String(getName()); } + @Override + public int hashCode() + { + int hash = 7; + hash = 31 * hash + ((children == null) ? 0 : children.hashCode()); + hash = 31 * hash + ((header == null) ? 0 : header.hashCode()); + hash = 31 * hash + ((endHeader == null) ? 0 : endHeader.hashCode()); + hash = 31 * hash + ((name == null) ? 0 : name.hashCode()); + return hash; + } + @Override public boolean equals(Object o) { From 1fbf9b3f323f621df4967d5a18d9cee2cef06d6b Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Mon, 19 Apr 2021 14:50:16 +0200 Subject: [PATCH 136/158] Minor improvements --- src/org/infinity/gui/BrowserMenuBar.java | 2 +- .../resource/cre/decoder/util/EffectInfo.java | 55 ++++++++++--------- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/src/org/infinity/gui/BrowserMenuBar.java b/src/org/infinity/gui/BrowserMenuBar.java index aaf5d028a..6cfab7a1f 100644 --- a/src/org/infinity/gui/BrowserMenuBar.java +++ b/src/org/infinity/gui/BrowserMenuBar.java @@ -2305,7 +2305,7 @@ private OptionsMenu() try { // L&F description is only available from class instance Class cls = Class.forName(info[i].getClassName()); - Object o = cls.newInstance(); + Object o = cls.getDeclaredConstructor().newInstance(); if (o instanceof LookAndFeel) dbmi.setToolTipText(((LookAndFeel)o).getDescription()); } catch (Exception ex) { diff --git a/src/org/infinity/resource/cre/decoder/util/EffectInfo.java b/src/org/infinity/resource/cre/decoder/util/EffectInfo.java index cbe499843..fb774afdb 100644 --- a/src/org/infinity/resource/cre/decoder/util/EffectInfo.java +++ b/src/org/infinity/resource/cre/decoder/util/EffectInfo.java @@ -313,14 +313,38 @@ public Effect getColorByLocation(CreatureInfo creInfo, SegmentDef.SpriteType typ ((fx.getParameter2() & 0xf) == locationIndex); }; - List list = getEffects(creInfo, type, pred); - if (!list.isEmpty()) { - return list.get(0); + return getFirstEffect(creInfo, type, pred); + } + + /** + * Adds the specified effect and associates it with the {@code SpriteType} defined by the effect. + *

    Indirectly created effects (e.g. via opcode 146) are correctly resolved and added. + * @param effect the effect to add. + */ + public void add(Effect effect) + { + if (effect == null) { + throw new NullPointerException("Effect parameter cannot be null"); } - return null; + List effects = resolveEffect(null, effect); + for (final Effect fx : effects) { + SegmentDef.SpriteType type = getEffectType(fx); + Set set = effectMap.get(type); + if (set == null) { + set = new HashSet<>(); + effectMap.put(type, set); + } + set.add(fx); + } } + /** + * Checks if the specified effect is available for the given creature. + * @param effect the effect to check + * @param creInfo the creature target + * @return {@code true} if the effect is valid for the target. Returns {@code false} otherwise. + */ protected boolean isEffectValid(Effect effect, CreatureInfo creInfo) { boolean retVal = true; @@ -348,29 +372,6 @@ protected boolean isEffectValid(Effect effect, CreatureInfo creInfo) return retVal; } - /** - * Adds the specified effect and associates it with the {@code SpriteType} defined by the effect. - *

    Indirectly created effects (e.g. via opcode 146) are correctly resolved and added. - * @param effect the effect to add. - */ - public void add(Effect effect) - { - if (effect == null) { - throw new NullPointerException("Effect parameter cannot be null"); - } - - List effects = resolveEffect(null, effect); - for (final Effect fx : effects) { - SegmentDef.SpriteType type = getEffectType(fx); - Set set = effectMap.get(type); - if (set == null) { - set = new HashSet<>(); - effectMap.put(type, set); - } - set.add(fx); - } - } - /** Determines the sprite type target defined by this effect. Only relevant for color-related effects. */ private SegmentDef.SpriteType getEffectType(Effect effect) { From 7229815dbe1093b0aada202328cecb52acc7ddcc Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Mon, 19 Apr 2021 19:04:56 +0200 Subject: [PATCH 137/158] Area Viewer: Fix rendering issues with BG1-style tileset overlays --- src/org/infinity/resource/are/viewer/TilesetRenderer.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/org/infinity/resource/are/viewer/TilesetRenderer.java b/src/org/infinity/resource/are/viewer/TilesetRenderer.java index 33199857f..6f5cde96f 100644 --- a/src/org/infinity/resource/are/viewer/TilesetRenderer.java +++ b/src/org/infinity/resource/are/viewer/TilesetRenderer.java @@ -664,6 +664,12 @@ private boolean initWed(WedResource wed) if (so != null && sc != null) { for (int i = 0, count = sc.getValue(), curOfs = so.getValue(); i < count; i++) { Overlay ovl = (Overlay)wed.getAttribute(curOfs, false); + if (i == 0) { + if (Profile.getGame() == Profile.Game.BG1EE || Profile.getGame() == Profile.Game.BG1SoD) { + // updating overlay rendering mode (BG1-style or BG2-style) + blendedOverlays &= (((IsNumeric)ovl.getAttribute(Overlay.WED_OVERLAY_MOVEMENT_TYPE)).getValue() == 2); + } + } if (ovl != null) { listTilesets.add(new Tileset(wed, ovl)); curOfs += ovl.getSize(); From 8152c81220c7097e2516e48df01126fd96b90b55 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Mon, 19 Apr 2021 19:08:48 +0200 Subject: [PATCH 138/158] Fix loading performance issue for PVRZ-based TIS resources if associated PVRZ files were read from override folder Cause: PVRZ files in override folder were not cached at all --- .../resource/graphics/PvrDecoder.java | 82 +++++++++++-------- 1 file changed, 49 insertions(+), 33 deletions(-) diff --git a/src/org/infinity/resource/graphics/PvrDecoder.java b/src/org/infinity/resource/graphics/PvrDecoder.java index 03fc1288c..e9c678bca 100644 --- a/src/org/infinity/resource/graphics/PvrDecoder.java +++ b/src/org/infinity/resource/graphics/PvrDecoder.java @@ -93,11 +93,9 @@ public static PvrDecoder loadPvr(ResourceEntry entry) } else { key = entry.getResourceName(); } - PvrDecoder decoder = getCachedPvrDecoder(key); + PvrDecoder decoder = createPvrDecoder(key, entry.getResourceDataAsStream()); if (decoder != null) { return decoder; - } else { - return createPvrDecoder(key, entry.getResourceDataAsStream()); } } catch (Exception e) { e.printStackTrace(); @@ -117,11 +115,9 @@ public static PvrDecoder loadPvr(String fileName) } try { String key = fileName; - PvrDecoder decoder = getCachedPvrDecoder(key); + PvrDecoder decoder = createPvrDecoder(key, StreamUtils.getInputStream(FileManager.resolve(fileName))); if (decoder != null) { return decoder; - } else { - return createPvrDecoder(key, StreamUtils.getInputStream(FileManager.resolve(fileName))); } } catch (Exception e) { e.printStackTrace(); @@ -138,11 +134,9 @@ public static PvrDecoder loadPvr(Path file) { try { String key = file.getFileName().toString(); - PvrDecoder decoder = getCachedPvrDecoder(key); + PvrDecoder decoder = createPvrDecoder(key, StreamUtils.getInputStream(file)); if (decoder != null) { return decoder; - } else { - return createPvrDecoder(key, StreamUtils.getInputStream(file)); } } catch (Exception e) { e.printStackTrace(); @@ -162,11 +156,9 @@ public static PvrDecoder loadPvr(InputStream input) } try { String key = Integer.valueOf(input.hashCode()).toString(); - PvrDecoder decoder = getCachedPvrDecoder(key); + PvrDecoder decoder = createPvrDecoder(key, input); if (decoder != null) { return decoder; - } else { - return createPvrDecoder(key, input); } } catch (Exception e) { e.printStackTrace(); @@ -209,8 +201,24 @@ public static int getCacheLoad() } } - // Returns a PvrDecoder object only if it already exists in the cache. - private static synchronized PvrDecoder getCachedPvrDecoder(String key) +// // Returns a PvrDecoder object only if it already exists in the cache. +// private static synchronized PvrDecoder getCachedPvrDecoder(String key) +// { +// PvrDecoder retVal = null; +// if (key != null && !key.isEmpty()) { +// key = key.toUpperCase(Locale.ENGLISH); +// if (pvrCache.containsKey(key)) { +// retVal = pvrCache.get(key); +// // re-inserting entry to prevent premature removal from cache +// pvrCache.remove(key); +// pvrCache.put(key, retVal); +// } +// } +// return retVal; +// } + + // Returns a PvrDecoder object of the specified key if available, or creates and returns a new one otherwise. + private static synchronized PvrDecoder createPvrDecoder(String key, InputStream input) { PvrDecoder retVal = null; if (key != null && !key.isEmpty()) { @@ -220,30 +228,38 @@ private static synchronized PvrDecoder getCachedPvrDecoder(String key) // re-inserting entry to prevent premature removal from cache pvrCache.remove(key); pvrCache.put(key, retVal); - } - } - return retVal; - } - - // Returns a PvrDecoder object of the specified key if available, or creates and returns a new one otherwise. - private static synchronized PvrDecoder createPvrDecoder(String key, InputStream input) - { - PvrDecoder retVal = getCachedPvrDecoder(key); - if (retVal == null && input != null) { - try { - retVal = new PvrDecoder(input); - if (retVal != null) { - pvrCache.put(key, retVal); - // removing excess cache entries - while (pvrCache.size() > MaxCacheEntries) { - pvrCache.remove(pvrCache.keySet().iterator().next()); + } else { + try { + retVal = new PvrDecoder(input); + if (retVal != null) { + pvrCache.put(key, retVal); + // removing excess cache entries + while (pvrCache.size() > MaxCacheEntries) { + pvrCache.remove(pvrCache.keySet().iterator().next()); + } } + } catch (Exception e) { + e.printStackTrace(); } - } catch (Exception e) { - e.printStackTrace(); } } return retVal; +// PvrDecoder retVal = getCachedPvrDecoder(key); +// if (retVal == null && input != null) { +// try { +// retVal = new PvrDecoder(input); +// if (retVal != null) { +// pvrCache.put(key, retVal); +// // removing excess cache entries +// while (pvrCache.size() > MaxCacheEntries) { +// pvrCache.remove(pvrCache.keySet().iterator().next()); +// } +// } +// } catch (Exception e) { +// e.printStackTrace(); +// } +// } +// return retVal; } // Returns a rectangle that is aligned to the values specified as arguments 2 and 3 From fd3772585581a61c6c6cbcaea746acce52833954 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Wed, 21 Apr 2021 16:06:27 +0200 Subject: [PATCH 139/158] SpriteDecoder: Improve handling of visual markers (personal space, selection circle) Pros: - Markers are not affected by blending, lighting and filtering effects - Toggling visibility of markers doesn't require animation reload Cons: - One additional rendering pass required which might hurt performance somewhat --- .../are/viewer/ActorAnimationProvider.java | 34 ++- .../resource/cre/ViewerAnimation.java | 44 +++- .../resource/cre/browser/MediaPanel.java | 10 +- .../resource/cre/browser/RenderPanel.java | 10 +- .../resource/cre/browser/SettingsPanel.java | 50 +++-- .../resource/cre/decoder/SpriteDecoder.java | 212 ++++++++++++++---- 6 files changed, 272 insertions(+), 88 deletions(-) diff --git a/src/org/infinity/resource/are/viewer/ActorAnimationProvider.java b/src/org/infinity/resource/are/viewer/ActorAnimationProvider.java index 56cdebda2..07086872b 100644 --- a/src/org/infinity/resource/are/viewer/ActorAnimationProvider.java +++ b/src/org/infinity/resource/are/viewer/ActorAnimationProvider.java @@ -15,9 +15,9 @@ import java.util.Objects; import org.infinity.resource.cre.decoder.SpriteDecoder; +import org.infinity.resource.cre.decoder.SpriteDecoder.SpriteBamControl; import org.infinity.resource.cre.decoder.util.Direction; import org.infinity.resource.graphics.ColorConvert; -import org.infinity.resource.graphics.PseudoBamDecoder.PseudoBamControl; /** * Implements functionality for properly displaying actor sprites. @@ -27,7 +27,7 @@ public class ActorAnimationProvider extends AbstractAnimationProvider private static final Color TransparentColor = new Color(0, true); private SpriteDecoder decoder; - private PseudoBamControl control; + private SpriteBamControl control; private boolean isLooping, isSelectionCircleEnabled, isPersonalSpaceEnabled; private int lighting, orientation, cycle, startFrame, endFrame; private Rectangle imageRect; @@ -48,7 +48,7 @@ public void setDecoder(SpriteDecoder decoder) { this.decoder = Objects.requireNonNull(decoder, "Sprite decoder cannot be null"); control = this.decoder.createControl(); - control.setMode(PseudoBamControl.Mode.INDIVIDUAL); + control.setMode(SpriteBamControl.Mode.INDIVIDUAL); control.setSharedPerCycle(false); control.cycleSet(getCycle()); resetFrame(); @@ -283,18 +283,32 @@ protected synchronized void updateGraphics() { BufferedImage image = (BufferedImage)getImage(); if (image != null) { + Graphics2D g; if (isActive()) { - // clearing old content - Graphics2D g = image.createGraphics(); + g = image.createGraphics(); try { - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC)); + // clearing old content + g.setComposite(AlphaComposite.Src); g.setColor(TransparentColor); g.fillRect(0, 0, image.getWidth(), image.getHeight()); + } finally { + g.dispose(); + g = null; + } + + g = image.createGraphics(); + try { + int frameIndex = control.cycleGetFrameIndexAbsolute(); + int left = -imageRect.x - decoder.getFrameInfo(frameIndex).getCenterX(); + int top = -imageRect.y - decoder.getFrameInfo(frameIndex).getCenterY(); + Point pos = new Point(left, top); + + // rendering visual markers + control.getVisualMarkers(g, pos); // rendering frame // fetching frame data BufferedImage working = getWorkingImage(); - int frameIndex = control.cycleGetFrameIndexAbsolute(); int[] buffer = ((DataBufferInt)working.getRaster().getDataBuffer()).getData(); Arrays.fill(buffer, 0); decoder.frameGet(control, frameIndex, working); @@ -312,8 +326,6 @@ protected synchronized void updateGraphics() buffer = null; // rendering frame - int left = -imageRect.x - decoder.getFrameInfo(frameIndex).getCenterX(); - int top = -imageRect.y - decoder.getFrameInfo(frameIndex).getCenterY(); g.drawImage(working, left, top, left+frameWidth, top+frameHeight, 0, 0, frameWidth, frameHeight, null); } finally { g.dispose(); @@ -321,9 +333,9 @@ protected synchronized void updateGraphics() } } else { // draw placeholder instead - Graphics2D g = image.createGraphics(); + g = image.createGraphics(); try { - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC)); + g.setComposite(AlphaComposite.Src); g.setColor(TransparentColor); g.fillRect(0, 0, image.getWidth(), image.getHeight()); } finally { diff --git a/src/org/infinity/resource/cre/ViewerAnimation.java b/src/org/infinity/resource/cre/ViewerAnimation.java index 301849ae0..b199560de 100644 --- a/src/org/infinity/resource/cre/ViewerAnimation.java +++ b/src/org/infinity/resource/cre/ViewerAnimation.java @@ -44,9 +44,9 @@ import org.infinity.icon.Icons; import org.infinity.resource.cre.browser.CreatureBrowser; import org.infinity.resource.cre.decoder.SpriteDecoder; +import org.infinity.resource.cre.decoder.SpriteDecoder.SpriteBamControl; import org.infinity.resource.cre.decoder.util.Sequence; import org.infinity.resource.cre.decoder.util.SpriteUtils; -import org.infinity.resource.graphics.PseudoBamDecoder.PseudoBamControl; /** * A basic creature animation viewer. @@ -64,7 +64,8 @@ public class ViewerAnimation extends JComponent implements ActionListener private boolean initialized; private SpriteDecoder decoder; - private PseudoBamControl bamControl; + private SpriteBamControl bamControl; + private BufferedImage tmpImage; private RenderCanvas rcDisplay; private int curCycle, curFrame; private Timer timer; @@ -93,12 +94,12 @@ public SpriteDecoder getDecoder() } /** Returns the {@code BamControl} instance linked to the {@code SpriteDecoder}. */ - public PseudoBamControl getController() + public SpriteBamControl getController() { return bamControl; } - private void setController(PseudoBamControl ctrl) + private void setController(SpriteBamControl ctrl) { this.bamControl = Objects.requireNonNull(ctrl, "BamControl cannot be null"); } @@ -124,7 +125,7 @@ private void setAnimationSequence(Sequence seq) throws Exception private void resetAnimationSequence() throws Exception { setController(getDecoder().createControl()); - getController().setMode(PseudoBamControl.Mode.SHARED); + getController().setMode(SpriteBamControl.Mode.SHARED); getController().setSharedPerCycle(false); if (curCycle < getController().cycleCount()) { getController().cycleSet(curCycle); @@ -146,6 +147,7 @@ public void updateCanvasSize() boolean imageChanged = !dim.equals(new Dimension(rcDisplay.getImage().getWidth(null), rcDisplay.getImage().getHeight(null))); boolean sizeChanged = !dimDisplay.equals(rcDisplay.getPreferredSize()); if (imageChanged || sizeChanged) { + tmpImage = new BufferedImage(dim.width, dim.height, BufferedImage.TYPE_INT_ARGB); rcDisplay.setImage(new BufferedImage(dim.width, dim.height, BufferedImage.TYPE_INT_ARGB)); if (sizeChanged) { rcDisplay.setPreferredSize(dimDisplay); @@ -163,19 +165,36 @@ public void updateCanvasSize() /** Updates display with content of the current animation frame. */ public void updateCanvas() { - BufferedImage image = (BufferedImage)rcDisplay.getImage(); - Graphics2D g = image.createGraphics(); + // pre-rendering new frame + Graphics2D g = tmpImage.createGraphics(); try { g.setComposite(AlphaComposite.Src); g.setColor(COLOR_TRANSPARENT); - g.fillRect(0, 0, image.getWidth(), image.getHeight()); + g.fillRect(0, 0, tmpImage.getWidth(), tmpImage.getHeight()); + } finally { + g.dispose(); + g = null; + } + getController().cycleGetFrame(tmpImage); + + g = (Graphics2D)rcDisplay.getImage().getGraphics(); + try { + // clearing old content + g.setComposite(AlphaComposite.Src); + g.setColor(COLOR_TRANSPARENT); + g.fillRect(0, 0, rcDisplay.getImage().getWidth(null), rcDisplay.getImage().getHeight(null)); + + // drawing markers + getController().getVisualMarkers(g, null); + + // drawing animation frame + g.setComposite(AlphaComposite.SrcOver); + g.drawImage(tmpImage, 0, 0, null); } finally { g.dispose(); g = null; } - // rendering new frame - getController().cycleGetFrame(image); rcDisplay.repaint(); } @@ -299,7 +318,7 @@ else if (cbShowCircle == event.getSource()) { WindowBlocker.blockWindow(true); showSelectionCircle = cbShowCircle.isSelected(); getDecoder().setSelectionCircleEnabled(showSelectionCircle); - resetAnimationSequence(); + updateCanvas(); } catch (Exception e) { e.printStackTrace(); } finally { @@ -311,7 +330,7 @@ else if (cbShowSpace == event.getSource()) { WindowBlocker.blockWindow(true); showPersonalSpace = cbShowSpace.isSelected(); getDecoder().setPersonalSpaceVisible(showPersonalSpace); - resetAnimationSequence(); + updateCanvas(); } catch (Exception e) { e.printStackTrace(); } finally { @@ -378,6 +397,7 @@ else if (bOpenBrowser == event.getSource()) { private void init() { + tmpImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); rcDisplay = new RenderCanvas(new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB)); rcDisplay.setHorizontalAlignment(SwingConstants.CENTER); rcDisplay.setVerticalAlignment(SwingConstants.CENTER); diff --git a/src/org/infinity/resource/cre/browser/MediaPanel.java b/src/org/infinity/resource/cre/browser/MediaPanel.java index 49f53694c..dcd451271 100644 --- a/src/org/infinity/resource/cre/browser/MediaPanel.java +++ b/src/org/infinity/resource/cre/browser/MediaPanel.java @@ -36,9 +36,9 @@ import org.infinity.gui.WindowBlocker; import org.infinity.resource.cre.browser.icon.Icons; import org.infinity.resource.cre.decoder.SpriteDecoder; +import org.infinity.resource.cre.decoder.SpriteDecoder.SpriteBamControl; import org.infinity.resource.cre.decoder.util.Direction; import org.infinity.resource.cre.decoder.util.Sequence; -import org.infinity.resource.graphics.PseudoBamDecoder.PseudoBamControl; /** * This panel provides controls for animation playback and related visual options. @@ -63,7 +63,7 @@ public class MediaPanel extends JPanel private JSlider slDirection; private JLabel lDirection; private JLabel lFrameCur, lFrameMax; - private PseudoBamControl controller; + private SpriteBamControl controller; private Timer timer; private int curFrame, curCycle; @@ -130,17 +130,17 @@ public void reset(boolean preserveState) } /** Returns the currently assigned BAM controller for the creature animation. */ - public PseudoBamControl getController() { return controller; } + public SpriteBamControl getController() { return controller; } /** Sets the BAM controller for the creature animation. */ - protected void setController(PseudoBamControl controller) + protected void setController(SpriteBamControl controller) { if (this.controller == null && controller != null || this.controller != null && !this.controller.equals(controller)) { pause(); this.controller = controller; if (this.controller != null) { - this.controller.setMode(PseudoBamControl.Mode.SHARED); + this.controller.setMode(SpriteBamControl.Mode.SHARED); this.controller.setSharedPerCycle(false); } } diff --git a/src/org/infinity/resource/cre/browser/RenderPanel.java b/src/org/infinity/resource/cre/browser/RenderPanel.java index f8c0700ef..6f21bf73c 100644 --- a/src/org/infinity/resource/cre/browser/RenderPanel.java +++ b/src/org/infinity/resource/cre/browser/RenderPanel.java @@ -28,8 +28,8 @@ import org.infinity.gui.RenderCanvas; import org.infinity.gui.ViewerUtil; +import org.infinity.resource.cre.decoder.SpriteDecoder.SpriteBamControl; import org.infinity.resource.graphics.ColorConvert; -import org.infinity.resource.graphics.PseudoBamDecoder.PseudoBamControl; /** * This panel handles drawing background and creature animations. @@ -193,7 +193,7 @@ public void setComposite(Composite c) } /** Stores the active BAM frame and frame center from the specified {@code PseudoBamControl} object internally for display. */ - public void setFrame(PseudoBamControl ctrl) + public void setFrame(SpriteBamControl ctrl) { if (ctrl != null) { if (frameBounds == null) { @@ -374,7 +374,13 @@ private void drawFrame() int y = (backgroundCenter != null) ? backgroundCenter.y : -frameBounds.y; Graphics2D g = (Graphics2D)rcCanvas.getImage().getGraphics(); + Point pos = new Point(x + frameBounds.x, y + frameBounds.y); try { + // drawing markers + SpriteBamControl ctrl = getBrowser().getMediaPanel().getController(); + ctrl.getVisualMarkers(g, pos); + + // drawing frame g.setComposite(getComposite()); g.drawImage(frame, x + frameBounds.x, y + frameBounds.y, null); } finally { diff --git a/src/org/infinity/resource/cre/browser/SettingsPanel.java b/src/org/infinity/resource/cre/browser/SettingsPanel.java index b865bc6f1..8b61045b3 100644 --- a/src/org/infinity/resource/cre/browser/SettingsPanel.java +++ b/src/org/infinity/resource/cre/browser/SettingsPanel.java @@ -84,23 +84,7 @@ public class SettingsPanel extends JPanel isShowAvatar, isShowHelmet, isShowShield, isShowWeapon; static { - indexZoom = 1; // 100 % (original) - indexFrameRate = 3; // 15 fps (original) - indexBackground = 0; // System color - isFiltering = false; - isBlending = true; - isTranslucent = true; - isSelectionCircle = false; - isOrnateSelectionCircle = (Profile.getGame() == Profile.Game.PST) || (Profile.getGame() == Profile.Game.PSTEE); - isPersonalSpace = false; - isShowAvatar = true; - isShowHelmet = true; - isShowShield = true; - isShowWeapon = true; - isShowBorders = false; - isTintEnabled = true; - isBlurEnabled = true; - isPaletteReplacementEnabled = true; + resetSettings(); } private final Listeners listeners = new Listeners(); @@ -124,10 +108,33 @@ public static List getBackgrounds(Profile.Game game) .collect(Collectors.toList()); } + /** Initializes global settings with sane defaults. */ + private static void resetSettings() + { + indexZoom = 1; // 100 % (original) + indexFrameRate = 3; // 15 fps (original) + indexBackground = 0; // System color + isFiltering = false; + isBlending = true; + isTranslucent = true; + isSelectionCircle = false; + isOrnateSelectionCircle = (Profile.getGame() == Profile.Game.PST) || (Profile.getGame() == Profile.Game.PSTEE); + isPersonalSpace = false; + isShowAvatar = true; + isShowHelmet = true; + isShowShield = true; + isShowWeapon = true; + isShowBorders = false; + isTintEnabled = true; + isBlurEnabled = true; + isPaletteReplacementEnabled = true; + } + public SettingsPanel(CreatureBrowser browser) { super(); this.browser = Objects.requireNonNull(browser); + resetSettings(); init(); } @@ -275,7 +282,8 @@ private void setSelectionCircleEnabled(boolean b) if (isSelectionCircle != b) { isSelectionCircle = b; cbSelectionCircle.setSelected(isSelectionCircle); - getBrowser().getMediaPanel().reset(true); + getBrowser().getDecoder().setSelectionCircleEnabled(isSelectionCircle); + getBrowser().getRenderPanel().updateCanvas(); } } @@ -287,7 +295,8 @@ private void setOrnateSelectionCircle(boolean b) if (isOrnateSelectionCircle != b) { isOrnateSelectionCircle = b; cbOrnateSelectionCircle.setSelected(isOrnateSelectionCircle); - getBrowser().getMediaPanel().reset(true); + getBrowser().getDecoder().setSelectionCircleBitmap(isOrnateSelectionCircle); + getBrowser().getRenderPanel().updateCanvas(); } } @@ -299,7 +308,8 @@ private void setPersonalSpaceEnabled(boolean b) if (isPersonalSpace != b) { isPersonalSpace = b; cbPersonalSpace.setSelected(isPersonalSpace); - getBrowser().getMediaPanel().reset(true); + getBrowser().getDecoder().setPersonalSpaceVisible(isPersonalSpace); + getBrowser().getRenderPanel().updateCanvas(); } } diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index b80ad2ba3..917b4423a 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -53,6 +53,7 @@ import org.infinity.resource.graphics.ColorConvert; import org.infinity.resource.graphics.PseudoBamDecoder; import org.infinity.resource.graphics.BamV1Decoder.BamV1Control; +import org.infinity.resource.graphics.BamDecoder; import org.infinity.resource.graphics.BlendingComposite; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; @@ -182,6 +183,7 @@ public void accept(DirDef dd, int frameIdx) /** Cache for creature animation attributes. */ private final TreeMap attributesMap; + private BufferedImage imageCircle; private Sequence currentSequence; private boolean showCircle; private boolean selectionCircleBitmap; @@ -867,13 +869,14 @@ public boolean isBlurred() /** Call this method whenever the visibility of the selection circle has been changed. */ public void selectionCircleChanged() { - setAnimationChanged(); + // force recaching + imageCircle = null; } /** Call this method whenever the visibility of personal space has been changed. */ public void personalSpaceChanged() { - setAnimationChanged(); + // nothing to do } /** Call this method whenever the visibility of any sprite types has been changed. */ @@ -885,7 +888,8 @@ public void spriteChanged() /** Call this method whenever the allegiance value has been changed. */ public void allegianceChanged() { - setAnimationChanged(); + // force recaching + imageCircle = null; } /** @@ -966,6 +970,12 @@ public void resetAnimationChanged() animationChanged = false; } + @Override + public SpriteBamControl createControl() + { + return new SpriteBamControl(this); + } + /** * Returns the preferred compositor for rendering the sprite on the target surface. */ @@ -1286,17 +1296,13 @@ protected int createFrame(FrameInfo[] sourceFrames, BeforeSourceFrame beforeSrcF } // include personal space region in image size - if (isPersonalSpaceVisible()) { - rect = SpriteUtils.updateFrameDimension(rect, getPersonalSpaceSize(true)); - } + rect = SpriteUtils.updateFrameDimension(rect, getPersonalSpaceSize(true)); // include selection circle in image size float circleStrokeSize = getSelectionCircleStrokeSize(); - if (isSelectionCircleEnabled()) { - Dimension dim = getSelectionCircleSize(); - rect = SpriteUtils.updateFrameDimension(rect, new Dimension(2 * (dim.width + (int)circleStrokeSize), - 2 * (dim.height + (int)circleStrokeSize))); - } + Dimension dim = getSelectionCircleSize(); + rect = SpriteUtils.updateFrameDimension(rect, new Dimension(2 * (dim.width + (int)circleStrokeSize), + 2 * (dim.height + (int)circleStrokeSize))); // creating target image BufferedImage image; @@ -1308,18 +1314,6 @@ protected int createFrame(FrameInfo[] sourceFrames, BeforeSourceFrame beforeSrcF g.setColor(new Color(0, true)); g.fillRect(0, 0, image.getWidth(), image.getHeight()); - Point center = new Point(-rect.x, -rect.y); - - if (isPersonalSpaceVisible()) { - // Drawing personal space region - drawPersonalSpace(g, center, null, 0.5f); - } - - if (isSelectionCircleEnabled()) { - // Drawing selection circle - drawSelectionCircle(g, center, circleStrokeSize); - } - // drawing source frames to target image for (final FrameInfo fi : sourceFrames) { BamV1Control ctrl = fi.getController(); @@ -1468,7 +1462,7 @@ protected Dimension getSelectionCircleSize() return dim; } if (isSelectionCircleBitmap()) { - dim.width += 4; // compensation for missing stroke size + dim.width += 2; // compensation for missing stroke size } dim.height = dim.width * 4 / 7; // ratio 1.75 if (dim.height % 7 > 3) { @@ -1505,23 +1499,43 @@ protected void drawSelectionCircle(Graphics2D g, Point center, float strokeSize) return; } + Image image; if (isSelectionCircleBitmap()) { - Image image = getCreatureInfo().isStatusPanic() ? SpriteUtils.getAllegianceImage(-1) - : SpriteUtils.getAllegianceImage(getCreatureInfo().getAllegiance()); - Object oldHints = g.getRenderingHint(RenderingHints.KEY_INTERPOLATION); - g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); - g.drawImage(image, center.x - dim.width, center.y - dim.height, 2 * dim.width, 2 * dim.height, null); - g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, (oldHints != null) ? oldHints : RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); + // fetching ornate selection circle image + image = getCreatureInfo().isStatusPanic() ? SpriteUtils.getAllegianceImage(-1) + : SpriteUtils.getAllegianceImage(getCreatureInfo().getAllegiance()); } else { - Color color = getCreatureInfo().isStatusPanic() ? SpriteUtils.getAllegianceColor(-1) - : SpriteUtils.getAllegianceColor(getCreatureInfo().getAllegiance()); - Object oldHints = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - g.setColor(color); - g.setStroke(new BasicStroke(strokeSize)); - g.drawOval(center.x - dim.width, center.y - dim.height, 2 * dim.width, 2 * dim.height); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, (oldHints != null) ? oldHints : RenderingHints.VALUE_ANTIALIAS_DEFAULT); + if (imageCircle == null) { + // pregenerating circle graphics + int stroke = (int)Math.ceil(strokeSize); + imageCircle = ColorConvert.createCompatibleImage((dim.width + stroke) * 2 + 1, (dim.height + stroke) * 2 + 1, true); + Graphics2D g2 = imageCircle.createGraphics(); + try { + Color color = getCreatureInfo().isStatusPanic() ? SpriteUtils.getAllegianceColor(-1) + : SpriteUtils.getAllegianceColor(getCreatureInfo().getAllegiance()); + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2.setColor(color); + g2.setStroke(new BasicStroke(strokeSize)); + g2.drawOval((imageCircle.getWidth() / 2) - dim.width, + (imageCircle.getHeight() / 2) - dim.height, + 2 * dim.width, + 2 * dim.height); + } finally { + g2.dispose(); + g2 = null; + } + } + image = imageCircle; + // adjusting drawing size + dim.width = image.getWidth(null) / 2; + dim.height = image.getHeight(null) / 2; } + + // drawing selection circle + Object oldHints = g.getRenderingHint(RenderingHints.KEY_INTERPOLATION); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + g.drawImage(image, center.x - dim.width, center.y - dim.height, 2 * dim.width, 2 * dim.height, null); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, (oldHints != null) ? oldHints : RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); } } @@ -1912,6 +1926,128 @@ public boolean equals(Object o) //-------------------------- INNER CLASSES -------------------------- + /** + * Specialized controller for creature animations. + */ + public static class SpriteBamControl extends PseudoBamControl + { + protected SpriteBamControl(SpriteDecoder decoder) + { + super(decoder); + } + + @Override + public SpriteDecoder getDecoder() + { + return (SpriteDecoder)super.getDecoder(); + } + + /** + * A convenience method that draws personal space marker and/or selection circle onto the canvas specified by the + * {@code Graphics2D} instance based on current decoder settings. + * Frame dimension and center position are taken into account and based on the currently selected cycle frame. + * @param g the {@code Graphics2D} instance used to render the graphics. + * @param offset amount of pixels to move the center point by. Specify {@code null} for no change. + */ + public void getVisualMarkers(Graphics2D g, Point offset) + { + getVisualMarkers(g, offset, cycleGetFrameIndex(), getDecoder().isPersonalSpaceVisible(), + getDecoder().isSelectionCircleEnabled()); + } + + /** + * A convenience method that draws personal space marker and/or selection circle onto the canvas specified by the + * {@code Graphics2D} instance. + * Frame dimension and center position are taken into account and based on the currently selected cycle frame. + * @param g the {@code Graphics2D} instance used to render the graphics. + * @param offset amount of pixels to move the center point by. Specify {@code null} for no change. + * @param drawPersonalSpace whether to draw the personal space marker. + * @param drawSelectionCircle whether to draw the selection circle. + */ + public void getVisualMarkers(Graphics2D g, Point offset, boolean drawPersonalSpace, boolean drawSelectionCircle) + { + getVisualMarkers(g, offset, cycleGetFrameIndex(), drawPersonalSpace, drawSelectionCircle); + } + + /** + * A convenience method that draws personal space marker and/or selection circle onto the canvas specified by the + * {@code Graphics2D} instance based on current decoder settings. + * Frame dimension and center position are taken into account by the specified relative frame index. + * @param g the {@code Graphics2D} instance used to render the graphics. + * @param offset amount of pixels to move the center point by. Specify {@code null} for no change. + * @param frameIdx the frame index relative to current cycle. + */ + public void getVisualMarkers(Graphics2D g, Point offset, int frameIdx) + { + getVisualMarkers(g, offset, frameIdx, getDecoder().isPersonalSpaceVisible(), + getDecoder().isSelectionCircleEnabled()); + } + + /** + * A convenience method that draws personal space marker and/or selection circle onto the canvas specified by the + * {@code Graphics2D} instance. + * Frame dimension and center position are taken into account by the specified relative frame index. + * @param g the {@code Graphics2D} instance used to render the graphics. + * @param offset amount of pixels to move the center point by. Specify {@code null} for no change. + * @param frameIdx the frame index relative to current cycle. + * @param drawPersonalSpace whether to draw the personal space marker. + * @param drawSelectionCircle whether to draw the selection circle. + */ + public void getVisualMarkers(Graphics2D g, Point offset, int frameIdx, + boolean drawPersonalSpace, boolean drawSelectionCircle) + { + if (g == null || (drawPersonalSpace == false && drawSelectionCircle == false)) { + return; + } + + frameIdx = cycleGetFrameIndexAbsolute(frameIdx); + if (frameIdx < 0) { + return; + } + + // getting frame dimension and center + PseudoBamFrameEntry entry = getDecoder().getFrameInfo(frameIdx); + Point center; + int w, h; + if (getMode() == BamDecoder.BamControl.Mode.SHARED) { + updateSharedBamSize(); + Dimension d = getSharedDimension(); + center = getSharedOrigin(); + center.x = -center.x; + center.y = -center.y; + w = d.width; + h = d.height; + } else { + w = entry.getWidth(); + h = entry.getHeight(); + center = new Point(entry.getCenterX(), entry.getCenterY()); + } + + if (w <= 0 || h <= 0) { + return; + } + + if (offset != null) { + center.x += offset.x; + center.y += offset.y; + } + + // drawing markers + Composite comp = g.getComposite(); + g.setComposite(AlphaComposite.SrcOver); + if (drawPersonalSpace) { + getDecoder().drawPersonalSpace(g, center, null, 0.5f); + } + if (drawSelectionCircle) { + getDecoder().drawSelectionCircle(g, center, getDecoder().getSelectionCircleStrokeSize()); + } + if (comp != null) { + g.setComposite(comp); + } + } + } + + /** * Represents an operation that is called once per source BAM resource when creating a creature animation. */ From 5d06e3100c42d72a77aa115af4509fc7167b6500 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Wed, 21 Apr 2021 16:43:37 +0200 Subject: [PATCH 140/158] Creature animation viewers: Expand controls Browser: Addded more options to zoom and frame rate Quick viewer: Added option to show overlay borders --- .../resource/cre/ViewerAnimation.java | 37 +++++++++++-------- .../resource/cre/browser/SettingsPanel.java | 6 ++- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/org/infinity/resource/cre/ViewerAnimation.java b/src/org/infinity/resource/cre/ViewerAnimation.java index b199560de..6823993fd 100644 --- a/src/org/infinity/resource/cre/ViewerAnimation.java +++ b/src/org/infinity/resource/cre/ViewerAnimation.java @@ -59,6 +59,7 @@ public class ViewerAnimation extends JComponent implements ActionListener private static boolean zoom = false; private static boolean showSelectionCircle = false; private static boolean showPersonalSpace = false; + private static boolean showOverlayBorders = false; private final CreResource cre; @@ -74,7 +75,7 @@ public class ViewerAnimation extends JComponent implements ActionListener private JToggleButton bPlay; private JLabel lCurCycle, lCurFrame; private JComboBox cbSequences; - private JCheckBox cbShowCircle, cbShowSpace, cbZoom; + private JCheckBox cbShowCircle, cbShowSpace, cbShowBorders, cbZoom; public ViewerAnimation(CreResource cre) { @@ -314,23 +315,21 @@ else if (cbZoom == event.getSource()) { } } else if (cbShowCircle == event.getSource()) { - try { - WindowBlocker.blockWindow(true); - showSelectionCircle = cbShowCircle.isSelected(); - getDecoder().setSelectionCircleEnabled(showSelectionCircle); - updateCanvas(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - WindowBlocker.blockWindow(false); - } + showSelectionCircle = cbShowCircle.isSelected(); + getDecoder().setSelectionCircleEnabled(showSelectionCircle); + updateCanvas(); } else if (cbShowSpace == event.getSource()) { + showPersonalSpace = cbShowSpace.isSelected(); + getDecoder().setPersonalSpaceVisible(showPersonalSpace); + updateCanvas(); + } + else if (cbShowBorders == event.getSource()) { try { WindowBlocker.blockWindow(true); - showPersonalSpace = cbShowSpace.isSelected(); - getDecoder().setPersonalSpaceVisible(showPersonalSpace); - updateCanvas(); + showOverlayBorders = cbShowBorders.isSelected(); + getDecoder().setBoundingBoxVisible(showOverlayBorders); + resetAnimationSequence(); } catch (Exception e) { e.printStackTrace(); } finally { @@ -435,6 +434,8 @@ private void init() cbShowCircle.addActionListener(this); cbShowSpace = new JCheckBox("Show personal space", showPersonalSpace); cbShowSpace.addActionListener(this); + cbShowBorders = new JCheckBox("Show overlay borders", showOverlayBorders); + cbShowBorders.addActionListener(this); bOpenBrowser = new JButton("Open in browser", Icons.getIcon(Icons.ICON_CRE_VIEWER_24)); bOpenBrowser.setToolTipText("Open in Creature Animation Browser"); @@ -499,7 +500,10 @@ private void init() c = ViewerUtil.setGBC(c, 3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 8, 0, 0), 0, 0); pRow2.add(cbShowSpace, c); - c = ViewerUtil.setGBC(c, 4, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, + c = ViewerUtil.setGBC(c, 4, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.NONE, new Insets(0, 8, 0, 0), 0, 0); + pRow2.add(cbShowBorders, c); + c = ViewerUtil.setGBC(c, 5, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); pRow2.add(new JPanel(), c); @@ -533,6 +537,7 @@ public void componentShown(ComponentEvent e) private void initAnimation() throws Exception { this.decoder = SpriteDecoder.importSprite(getCre()); + getDecoder().setBoundingBoxVisible(showOverlayBorders); getDecoder().setSelectionCircleEnabled(showSelectionCircle); getDecoder().setPersonalSpaceVisible(showPersonalSpace); @@ -574,7 +579,7 @@ private void setInitialized(boolean b) initialized = b; JComponent[] controls = new JComponent[] { bNextCycle, bPrevCycle, bNextFrame, bPrevFrame, bOpenBrowser, bPlay, - lCurCycle, lCurFrame, cbSequences, cbShowCircle, cbShowSpace, cbZoom + lCurCycle, lCurFrame, cbSequences, cbShowCircle, cbShowSpace, cbShowBorders, cbZoom }; for (final JComponent c : controls) { if (c != null) { diff --git a/src/org/infinity/resource/cre/browser/SettingsPanel.java b/src/org/infinity/resource/cre/browser/SettingsPanel.java index 8b61045b3..f4405a26d 100644 --- a/src/org/infinity/resource/cre/browser/SettingsPanel.java +++ b/src/org/infinity/resource/cre/browser/SettingsPanel.java @@ -65,17 +65,21 @@ public class SettingsPanel extends JPanel add(ItemString.with("200 %", 200)); add(ItemString.with("300 %", 300)); add(ItemString.with("400 %", 400)); + add(ItemString.with("500 %", 500)); }}; // Available items for frame rate selection list private static final Vector> frameRateList = new Vector>() {{ add(ItemString.with("1 frames/sec.", 1)); + add(ItemString.with("2 frames/sec.", 2)); add(ItemString.with("5 frames/sec.", 5)); add(ItemString.with("10 frames/sec.", 10)); add(ItemString.with("15 frames/sec. (original)", 15)); add(ItemString.with("20 frames/sec.", 20)); add(ItemString.with("25 frames/sec.", 25)); add(ItemString.with("30 frames/sec.", 30)); + add(ItemString.with("50 frames/sec.", 50)); + add(ItemString.with("60 frames/sec.", 60)); }}; private static int indexZoom, indexFrameRate, indexBackground; @@ -526,7 +530,7 @@ private void init() cbShowWeapon.addActionListener(listeners); cbShowBorders = new JCheckBox("Show overlay borders", isShowBorders); - cbShowBorders.setToolTipText("Draw borders around individual segments of the creature animation."); + cbShowBorders.setToolTipText("Draw bounding boxes around individual segments of the creature animation."); cbShowBorders.addActionListener(listeners); JPanel panel2 = new JPanel(new GridBagLayout()); From 1b323708a7c48b006453b4063820bc8af4191f9a Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Wed, 21 Apr 2021 19:07:34 +0200 Subject: [PATCH 141/158] Improve 2DA table alignment feature (align on tab boundaries) --- .../resource/text/PlainTextResource.java | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/org/infinity/resource/text/PlainTextResource.java b/src/org/infinity/resource/text/PlainTextResource.java index 724839df2..029410413 100644 --- a/src/org/infinity/resource/text/PlainTextResource.java +++ b/src/org/infinity/resource/text/PlainTextResource.java @@ -204,7 +204,7 @@ public void itemStateChanged(ItemEvent event) if (bpmFormat.getSelectedItem() == miFormatTrim) { trimSpaces(); } else if (bpmFormat.getSelectedItem() == miFormatAlign) { - alignTableColumns(2, true); + alignTableColumns(2, true, 4); } else if (bpmFormat.getSelectedItem() == miFormatSort) { sortTable(true); } @@ -396,10 +396,13 @@ public void trimSpaces() * @param spaces Min. number of spaces between columns. * @param alignPerColumn specify {@code true} to calculate max width on a per column basis, * or {@code false} to calculate for the whole table. + * @param multipleOf ensures that column position is always a multiple of the specified value. + * (e.g. specify 2 to have every column start at an even horizontal position.) */ - public void alignTableColumns(int spaces, boolean alignPerColumn) + public void alignTableColumns(int spaces, boolean alignPerColumn, int multipleOf) { spaces = Math.max(1, spaces); + multipleOf = Math.max(1, multipleOf); // splitting text into lines String input = editor.getText(); @@ -438,12 +441,20 @@ public void alignTableColumns(int spaces, boolean alignPerColumn) } else { maxLen = maxTokenLength; } - columns[col] = maxLen + spaces; + int len = maxLen + spaces; + if (len % multipleOf != 0) { + len += multipleOf - (len % multipleOf); + } + columns[col] = len; } // normalizing data StringBuilder newText = new StringBuilder(); - String blank = new String(new char[maxTokenLength + spaces]).replace('\0', ' '); + int blankLen = maxTokenLength + spaces; + if (blankLen % multipleOf != 0) { + blankLen += multipleOf - (blankLen % multipleOf); + } + String blank = new String(new char[blankLen]).replace('\0', ' '); for (int row = 0, rows = matrix.size(); row < rows; row++) { StringBuilder sb = new StringBuilder(); for (int col = 0, cols = matrix.get(row).size(); col < cols; col++) { From e28a04f50fcc432ff33ce98963ab3b76c943aaea Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Wed, 21 Apr 2021 19:08:30 +0200 Subject: [PATCH 142/158] Fix SPLPROT list control: circle size -> personal space --- src/org/infinity/datatype/SpellProtType.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/infinity/datatype/SpellProtType.java b/src/org/infinity/datatype/SpellProtType.java index 3cc15bcf5..34fec4ee0 100644 --- a/src/org/infinity/datatype/SpellProtType.java +++ b/src/org/infinity/datatype/SpellProtType.java @@ -303,9 +303,9 @@ private static String[] getExternalizedTypeTable() break; case 0x102: // circle size if (isBitwiseRelation(rel) && value != -1) { - label = String.format("Circle size %s %d [0x%x]", getRelation(rel), value, value); + label = String.format("Personal space %s %d [0x%x]", getRelation(rel), value, value); } else { - label = String.format("Circle size %s %d", getRelation(rel), value); + label = String.format("Personal space %s %d", getRelation(rel), value); } break; case 0x103: // use two rows of splprot.2da From c3c8c380a20ad5a63b9f7fb06d7fa732cd0f344b Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Wed, 21 Apr 2021 20:19:45 +0200 Subject: [PATCH 143/158] Cosmetic - Improved display of "Mage type" field in CRE resources (BG1,IWD) - Improved display of item slot type selection list --- src/org/infinity/resource/Effect2.java | 4 +- src/org/infinity/resource/EffectFactory.java | 2 +- .../infinity/resource/cre/CreResource.java | 31 +++++++----- src/org/infinity/resource/gam/PartyNPC.java | 48 ++++++++++++------- 4 files changed, 52 insertions(+), 33 deletions(-) diff --git a/src/org/infinity/resource/Effect2.java b/src/org/infinity/resource/Effect2.java index 77759eeff..e0f02fadd 100644 --- a/src/org/infinity/resource/Effect2.java +++ b/src/org/infinity/resource/Effect2.java @@ -113,8 +113,8 @@ public static int readCommon(List list, ByteBuffer buffer, int offs } else { list.add(new DecNumber(buffer, offset + 84, 4, EFFECT_IMPACT_PROJECTILE)); } - IdsBitmap slot_type = new IdsBitmap(buffer, offset + 88, 4, EFFECT_SOURCE_ITEM_SLOT, "SLOTS.IDS"); - slot_type.addIdsMapEntry(new IdsMapEntry(4294967295L, "NONE")); + IdsBitmap slot_type = new IdsBitmap(buffer, offset + 88, 4, EFFECT_SOURCE_ITEM_SLOT, "SLOTS.IDS", true, false, true); + slot_type.addIdsMapEntry(new IdsMapEntry(-1L, "NONE")); list.add(slot_type); list.add(new TextString(buffer, offset + 92, 32, EFFECT_VARIABLE_NAME)); list.add(new DecNumber(buffer, offset + 124, 4, EFFECT_CASTER_LEVEL)); diff --git a/src/org/infinity/resource/EffectFactory.java b/src/org/infinity/resource/EffectFactory.java index 0a0c47b4f..22deb4aaa 100644 --- a/src/org/infinity/resource/EffectFactory.java +++ b/src/org/infinity/resource/EffectFactory.java @@ -3011,7 +3011,7 @@ private String makeEffectParamsGeneric(Datatype parent, ByteBuffer buffer, int o "Inventory 19", "Inventory 20", "Magic weapon", "Weapon 1", "Weapon 2", "Weapon 3", "Weapon 4"})); } else { - s.add(new IdsBitmap(buffer, offset, 4, "Slot", "SLOTS.IDS")); + s.add(new IdsBitmap(buffer, offset, 4, "Slot", "SLOTS.IDS", true, false, true)); } s.add(new DecNumber(buffer, offset + 4, 4, AbstractStruct.COMMON_UNUSED)); restype = "ITM"; diff --git a/src/org/infinity/resource/cre/CreResource.java b/src/org/infinity/resource/cre/CreResource.java index 7c778b238..740892fe1 100644 --- a/src/org/infinity/resource/cre/CreResource.java +++ b/src/org/infinity/resource/cre/CreResource.java @@ -930,16 +930,20 @@ public int read(ByteBuffer buffer, int offset) throws Exception TextString version = new TextString(buffer, offset + 4, 4, COMMON_VERSION); addField(version); if (signature.toString().equalsIgnoreCase("CHR ")) { + IdsBitmap bitmap; + final IdsMapEntry entryNone = new IdsMapEntry(-1L, "NONE"); addField(new TextString(buffer, offset + 8, 32, CHR_NAME)); HexNumber structOffset = new HexNumber(buffer, offset + 40, 4, CHR_OFFSET_CRE); addField(structOffset); addField(new HexNumber(buffer, offset + 44, 4, CHR_CRE_SIZE)); if (version.toString().equalsIgnoreCase("V2.2")) { for (int i = 0; i < 4; i++) { - addField(new IdsBitmap(buffer, offset + 48 + (i * 4), 2, - String.format(CHR_QUICK_WEAPON_SLOT_FMT, i+1), "SLOTS.IDS")); - addField(new IdsBitmap(buffer, offset + 50 + (i * 4), 2, - String.format(CHR_QUICK_SHIELD_SLOT_FMT, i+1), "SLOTS.IDS")); + bitmap = addField(new IdsBitmap(buffer, offset + 48 + (i * 4), 2, + String.format(CHR_QUICK_WEAPON_SLOT_FMT, i+1), "SLOTS.IDS", true, false, true)); + bitmap.addIdsMapEntry(entryNone); + bitmap = addField(new IdsBitmap(buffer, offset + 50 + (i * 4), 2, + String.format(CHR_QUICK_SHIELD_SLOT_FMT, i+1), "SLOTS.IDS", true, false, true)); + bitmap.addIdsMapEntry(entryNone); } for (int i = 0; i < 4; i++) { addField(new DecNumber(buffer, offset + 64 + (i * 4), 2, @@ -957,8 +961,9 @@ public int read(ByteBuffer buffer, int offset) throws Exception } addField(new Unknown(buffer, offset + 161, 1)); for (int i = 0; i < 3; i++) { - addField(new IdsBitmap(buffer, offset + 162 + (i * 2), 2, - String.format(CHR_QUICK_ITEM_SLOT_FMT, i+1), "SLOTS.IDS", true, false, true)); + bitmap = addField(new IdsBitmap(buffer, offset + 162 + (i * 2), 2, + String.format(CHR_QUICK_ITEM_SLOT_FMT, i+1), "SLOTS.IDS", true, false, true)); + bitmap.addIdsMapEntry(entryNone); } for (int i = 0; i < 3; i++) { addField(new DecNumber(buffer, offset + 168 + (i * 2), 2, @@ -985,8 +990,9 @@ else if (version.toString().equalsIgnoreCase("V1.0") || version.toString().equalsIgnoreCase("V2.0") || version.toString().equalsIgnoreCase("V2.1")) { for (int i = 0; i < 4; i++) { - addField(new IdsBitmap(buffer, offset + 48 + (i * 2), 2, - String.format(CHR_QUICK_WEAPON_SLOT_FMT, i+1), "SLOTS.IDS")); + bitmap = addField(new IdsBitmap(buffer, offset + 48 + (i * 2), 2, + String.format(CHR_QUICK_WEAPON_SLOT_FMT, i+1), "SLOTS.IDS", true, false, true)); + bitmap.addIdsMapEntry(entryNone); } for (int i = 0; i < 4; i++) { addField(new DecNumber(buffer, offset + 56 + (i * 2), 2, @@ -997,8 +1003,9 @@ else if (version.toString().equalsIgnoreCase("V1.0") || String.format(CHR_QUICK_SPELL_FMT, i+1), "SPL")); } for (int i = 0; i < 3; i++) { - addField(new IdsBitmap(buffer, offset + 88 + (i * 2), 2, - String.format(CHR_QUICK_ITEM_SLOT_FMT, i+1), "SLOTS.IDS")); + bitmap = addField(new IdsBitmap(buffer, offset + 88 + (i * 2), 2, + String.format(CHR_QUICK_ITEM_SLOT_FMT, i+1), "SLOTS.IDS", true, false, true)); + bitmap.addIdsMapEntry(entryNone); } for (int i = 0; i < 3; i++) { addField(new DecNumber(buffer, offset + 94 + (i * 2), 2, @@ -1648,9 +1655,9 @@ else if (version.equalsIgnoreCase("V9.0") || version.equalsIgnoreCase("V9.1")) { addField(new Unknown(buffer, offset + 572, 2)); } if (ResourceFactory.resourceExists("MAGESPEC.IDS")) { - addField(new IdsBitmap(buffer, offset + 574, 2, CRE_MAGE_TYPE, "MAGESPEC.IDS")); + addField(new IdsBitmap(buffer, offset + 574, 2, CRE_MAGE_TYPE, "MAGESPEC.IDS", true, true, false)); } else { - addField(new HashBitmap(buffer, offset + 574, 2, CRE_MAGE_TYPE, m_magetype)); + addField(new HashBitmap(buffer, offset + 574, 2, CRE_MAGE_TYPE, m_magetype, true, false, true)); } } addField(new ResourceRef(buffer, offset + 576, CRE_SCRIPT_OVERRIDE, "BCS")); diff --git a/src/org/infinity/resource/gam/PartyNPC.java b/src/org/infinity/resource/gam/PartyNPC.java index b5a49cd28..a1e0cc1eb 100644 --- a/src/org/infinity/resource/gam/PartyNPC.java +++ b/src/org/infinity/resource/gam/PartyNPC.java @@ -26,6 +26,7 @@ import org.infinity.resource.Profile; import org.infinity.resource.StructEntry; import org.infinity.resource.cre.CreResource; +import org.infinity.util.IdsMapEntry; import org.infinity.util.LongIntegerHashMap; import org.infinity.util.io.StreamUtils; @@ -210,13 +211,16 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new DecNumber(buffer, offset + 36, 2, GAM_NPC_VIEWPORT_X)); addField(new DecNumber(buffer, offset + 38, 2, GAM_NPC_VIEWPORT_Y)); + IdsBitmap bitmap; + final IdsMapEntry entryNone = new IdsMapEntry(-1L, "NONE"); if (Profile.getEngine() == Profile.Engine.BG1) { addField(new DecNumber(buffer, offset + 40, 2, GAM_NPC_MODAL_STATE)); addField(new DecNumber(buffer, offset + 42, 2, GAM_NPC_HAPPINESS)); addField(new Unknown(buffer, offset + 44, 96, COMMON_UNUSED)); for (int i = 0; i < 4; i++) { - addField(new IdsBitmap(buffer, offset + 140 + (i * 2), 2, - String.format(GAM_NPC_QUICK_WEAPON_SLOT_FMT, i+1), "SLOTS.IDS")); + bitmap = addField(new IdsBitmap(buffer, offset + 140 + (i * 2), 2, + String.format(GAM_NPC_QUICK_WEAPON_SLOT_FMT, i+1), "SLOTS.IDS", true, false, true)); + bitmap.addIdsMapEntry(entryNone); } for (int i = 0; i < 4; i++) { addField(new DecNumber(buffer, offset + 148 + (i * 2), 2, @@ -227,8 +231,9 @@ public int read(ByteBuffer buffer, int offset) throws Exception String.format(GAM_NPC_QUICK_SPELL_FMT, i+1), "SPL")); } for (int i = 0; i < 3; i++) { - addField(new IdsBitmap(buffer, offset + 180 + (i * 2), 2, - String.format(GAM_NPC_QUICK_ITEM_SLOT_FMT, i+1), "SLOTS.IDS")); + bitmap = addField(new IdsBitmap(buffer, offset + 180 + (i * 2), 2, + String.format(GAM_NPC_QUICK_ITEM_SLOT_FMT, i+1), "SLOTS.IDS", true, false, true)); + bitmap.addIdsMapEntry(entryNone); } for (int i = 0; i < 3; i++) { addField(new DecNumber(buffer, offset + 186 + (i * 2), 2, @@ -253,8 +258,9 @@ else if (Profile.getEngine() == Profile.Engine.BG2 || Profile.isEnhancedEdition( addField(new DecNumber(buffer, offset + 138, 2, COMMON_UNKNOWN)); } for (int i = 0; i < 4; i++) { - addField(new IdsBitmap(buffer, offset + 140 + (i * 2), 2, - String.format(GAM_NPC_QUICK_WEAPON_SLOT_FMT, i+1), "SLOTS.IDS")); + bitmap = addField(new IdsBitmap(buffer, offset + 140 + (i * 2), 2, + String.format(GAM_NPC_QUICK_WEAPON_SLOT_FMT, i+1), "SLOTS.IDS", true, false, true)); + bitmap.addIdsMapEntry(entryNone); } for (int i = 0; i < 4; i++) { addField(new DecNumber(buffer, offset + 148 + (i * 2), 2, @@ -265,8 +271,9 @@ else if (Profile.getEngine() == Profile.Engine.BG2 || Profile.isEnhancedEdition( String.format(GAM_NPC_QUICK_SPELL_FMT, i+1), "SPL")); } for (int i = 0; i < 3; i++) { - addField(new IdsBitmap(buffer, offset + 180 + (i * 2), 2, - String.format(GAM_NPC_QUICK_ITEM_SLOT_FMT, i+1), "SLOTS.IDS")); + bitmap = addField(new IdsBitmap(buffer, offset + 180 + (i * 2), 2, + String.format(GAM_NPC_QUICK_ITEM_SLOT_FMT, i+1), "SLOTS.IDS", true, false, true)); + bitmap.addIdsMapEntry(entryNone); } for (int i = 0; i < 3; i++) { addField(new DecNumber(buffer, offset + 186 + (i * 2), 2, @@ -312,8 +319,9 @@ else if (Profile.getEngine() == Profile.Engine.IWD) { addField(new DecNumber(buffer, offset + 40, 2, GAM_NPC_MODAL_STATE)); addField(new Unknown(buffer, offset + 42, 98)); for (int i = 0; i < 4; i++) { - addField(new IdsBitmap(buffer, offset + 140 + (i * 2), 2, - String.format(GAM_NPC_QUICK_WEAPON_SLOT_FMT, i+1), "SLOTS.IDS")); + bitmap = addField(new IdsBitmap(buffer, offset + 140 + (i * 2), 2, + String.format(GAM_NPC_QUICK_WEAPON_SLOT_FMT, i+1), "SLOTS.IDS", true, false, true)); + bitmap.addIdsMapEntry(entryNone); } for (int i = 0; i < 4; i++) { addField(new DecNumber(buffer, offset + 148 + (i * 2), 2, @@ -324,8 +332,9 @@ else if (Profile.getEngine() == Profile.Engine.IWD) { String.format(GAM_NPC_QUICK_SPELL_FMT, i+1), "SPL")); } for (int i = 0; i < 3; i++) { - addField(new IdsBitmap(buffer, offset + 180 + (i * 2), 2, - String.format(GAM_NPC_QUICK_ITEM_SLOT_FMT, i+1), "SLOTS.IDS")); + bitmap = addField(new IdsBitmap(buffer, offset + 180 + (i * 2), 2, + String.format(GAM_NPC_QUICK_ITEM_SLOT_FMT, i+1), "SLOTS.IDS", true, false, true)); + bitmap.addIdsMapEntry(entryNone); } for (int i = 0; i < 3; i++) { addField(new DecNumber(buffer, offset + 186 + (i * 2), 2, @@ -342,10 +351,12 @@ else if (Profile.getEngine() == Profile.Engine.IWD2) { addField(new DecNumber(buffer, offset + 40, 2, GAM_NPC_MODAL_STATE)); addField(new Unknown(buffer, offset + 42, 98)); for (int i = 0; i < 4; i++) { - addField(new IdsBitmap(buffer, offset + 140 + (i * 4), 2, - String.format(GAM_NPC_QUICK_WEAPON_SLOT_FMT, i+1), "SLOTS.IDS")); - addField(new IdsBitmap(buffer, offset + 142 + (i * 4), 2, - String.format(GAM_NPC_QUICK_SHIELD_SLOT_FMT, i+1), "SLOTS.IDS")); + bitmap = addField(new IdsBitmap(buffer, offset + 140 + (i * 4), 2, + String.format(GAM_NPC_QUICK_WEAPON_SLOT_FMT, i+1), "SLOTS.IDS", true, false, true)); + bitmap.addIdsMapEntry(entryNone); + bitmap = addField(new IdsBitmap(buffer, offset + 142 + (i * 4), 2, + String.format(GAM_NPC_QUICK_SHIELD_SLOT_FMT, i+1), "SLOTS.IDS", true, false, true)); + bitmap.addIdsMapEntry(entryNone); } for (int i = 0; i < 4; i++) { addField(new DecNumber(buffer, offset + 156 + (i * 4), 2, @@ -363,8 +374,9 @@ else if (Profile.getEngine() == Profile.Engine.IWD2) { } addField(new Unknown(buffer, offset + 253, 1)); for (int i = 0; i < 3; i++) { - addField(new IdsBitmap(buffer, offset + 254 + (i * 2), 2, - String.format(GAM_NPC_QUICK_ITEM_SLOT_FMT, i+1), "SLOTS.IDS")); + bitmap = addField(new IdsBitmap(buffer, offset + 254 + (i * 2), 2, + String.format(GAM_NPC_QUICK_ITEM_SLOT_FMT, i+1), "SLOTS.IDS", true, false, true)); + bitmap.addIdsMapEntry(entryNone); } for (int i = 0; i < 3; i++) { addField(new DecNumber(buffer, offset + 260 + (i * 2), 2, From 35002dcf2afad3f973e7edd055961295a1820b09 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Wed, 21 Apr 2021 21:57:26 +0200 Subject: [PATCH 144/158] Improve CRE V1.1 datatype of weapon color field --- src/org/infinity/resource/itm/ItmResource.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/org/infinity/resource/itm/ItmResource.java b/src/org/infinity/resource/itm/ItmResource.java index ff3d15e13..df697fbce 100644 --- a/src/org/infinity/resource/itm/ItmResource.java +++ b/src/org/infinity/resource/itm/ItmResource.java @@ -16,6 +16,7 @@ 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; @@ -480,7 +481,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception if (isV11) { addField(new ResourceRef(buffer, 114, ITM_DIALOG, "DLG")); addField(new StringRef(buffer, 122, ITM_SPEAKER_NAME)); - addField(new IdsBitmap(buffer, 126, 2, ITM_WEAPON_COLOR, "CLOWNCLR.IDS")); + addField(new ColorValue(buffer, 126, 2, ITM_WEAPON_COLOR, true, "PAL32.BMP")); addField(new Unknown(buffer, 128, 26)); } else if (isV20) { From cbe6b8ccaf293263ccea13f3b2fcd9266365c22e Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Wed, 21 Apr 2021 23:55:48 +0200 Subject: [PATCH 145/158] Creature Animation Browser: Fix default frame rate selection --- src/org/infinity/resource/cre/browser/SettingsPanel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/infinity/resource/cre/browser/SettingsPanel.java b/src/org/infinity/resource/cre/browser/SettingsPanel.java index f4405a26d..91a2b16f8 100644 --- a/src/org/infinity/resource/cre/browser/SettingsPanel.java +++ b/src/org/infinity/resource/cre/browser/SettingsPanel.java @@ -116,7 +116,7 @@ public static List getBackgrounds(Profile.Game game) private static void resetSettings() { indexZoom = 1; // 100 % (original) - indexFrameRate = 3; // 15 fps (original) + indexFrameRate = 4; // 15 fps (original) indexBackground = 0; // System color isFiltering = false; isBlending = true; From acb2ebef4d89fd2b9ad995ae090d9094e15223cc Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 23 Apr 2021 13:44:33 +0200 Subject: [PATCH 146/158] Fix Unknown datatype to properly update content --- src/org/infinity/datatype/Unknown.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/org/infinity/datatype/Unknown.java b/src/org/infinity/datatype/Unknown.java index 31a993f70..bfde32a97 100644 --- a/src/org/infinity/datatype/Unknown.java +++ b/src/org/infinity/datatype/Unknown.java @@ -159,10 +159,11 @@ public int read(ByteBuffer buffer, int offset) @Override public ByteBuffer getData() { - buffer.position(0); - ByteBuffer bb = StreamUtils.getByteBuffer(buffer.remaining()); - buffer.put(bb); - bb.position(0); + ByteBuffer bb = ByteBuffer.allocate(buffer.capacity()); + buffer.rewind(); + bb.put(buffer); + buffer.rewind(); + bb.flip(); return bb; } From 73bd0e803bb298cee6a635e45fa2354796f33fca Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 23 Apr 2021 17:44:30 +0200 Subject: [PATCH 147/158] Cosmetic (remove Netbeans tags) --- src/org/infinity/check/AbstractChecker.java | 2 - src/org/infinity/datatype/Bestiary.java | 18 ------- src/org/infinity/datatype/Flag.java | 10 ---- src/org/infinity/datatype/ResourceRef.java | 14 ------ src/org/infinity/gui/BIFFEditor.java | 6 --- src/org/infinity/gui/BIFFEditorTable.java | 2 - src/org/infinity/gui/BrowserMenuBar.java | 4 -- src/org/infinity/gui/LinkButton.java | 4 -- .../infinity/gui/hexview/StructHexViewer.java | 10 ---- .../gui/layeritem/AbstractLayerItem.java | 6 --- .../gui/layeritem/AnimatedLayerItem.java | 6 --- .../infinity/gui/layeritem/IconLayerItem.java | 2 - .../gui/layeritem/ShapedLayerItem.java | 2 - src/org/infinity/resource/AbstractStruct.java | 14 ------ .../infinity/resource/AbstractVariable.java | 2 - .../infinity/resource/are/AreResource.java | 12 ----- src/org/infinity/resource/are/Container.java | 10 ---- src/org/infinity/resource/are/Door.java | 10 ---- src/org/infinity/resource/are/ITEPoint.java | 11 ---- .../resource/are/viewer/AreaViewer.java | 6 --- .../resource/are/viewer/LayerObjectActor.java | 2 - .../are/viewer/LayerObjectAmbient.java | 2 - .../are/viewer/LayerObjectAnimation.java | 2 - .../are/viewer/LayerObjectAutomap.java | 2 - .../are/viewer/LayerObjectAutomapPST.java | 2 - .../are/viewer/LayerObjectContainer.java | 2 - .../resource/are/viewer/LayerObjectDoor.java | 2 - .../are/viewer/LayerObjectDoorPoly.java | 2 - .../are/viewer/LayerObjectEntrance.java | 2 - .../are/viewer/LayerObjectProTrap.java | 2 - .../are/viewer/LayerObjectRegion.java | 2 - .../are/viewer/LayerObjectSpawnPoint.java | 2 - .../are/viewer/LayerObjectTransition.java | 2 - .../are/viewer/LayerObjectWallPoly.java | 2 - src/org/infinity/resource/chu/Control.java | 4 -- .../infinity/resource/cre/CreResource.java | 15 ------ src/org/infinity/resource/cre/Iwd2Struct.java | 6 --- .../resource/cre/SpellMemorization.java | 8 --- src/org/infinity/resource/dlg/DlgItem.java | 4 -- .../infinity/resource/dlg/DlgResource.java | 12 ----- .../infinity/resource/dlg/DlgTreeModel.java | 16 ------ src/org/infinity/resource/dlg/ItemBase.java | 6 --- .../infinity/resource/dlg/OrphanStates.java | 6 --- .../resource/dlg/OrphanTransitions.java | 6 --- src/org/infinity/resource/dlg/State.java | 2 - src/org/infinity/resource/dlg/StateItem.java | 4 -- src/org/infinity/resource/dlg/Transition.java | 2 - .../infinity/resource/dlg/TransitionItem.java | 2 - src/org/infinity/resource/dlg/TreeViewer.java | 6 --- src/org/infinity/resource/dlg/Viewer.java | 6 --- .../infinity/resource/gam/GamResource.java | 10 ---- src/org/infinity/resource/itm/Ability.java | 8 --- .../infinity/resource/itm/ItmResource.java | 10 ---- .../infinity/resource/mus/MusResource.java | 18 ------- .../infinity/resource/pro/ProAreaType.java | 2 - .../infinity/resource/pro/ProResource.java | 8 --- src/org/infinity/resource/spl/Ability.java | 8 --- .../infinity/resource/spl/SplResource.java | 12 ----- .../infinity/resource/src/SrcResource.java | 8 --- .../infinity/resource/sto/StoResource.java | 8 --- .../infinity/resource/text/QuestsPanel.java | 4 -- .../resource/text/QuestsResource.java | 6 --- .../infinity/resource/var/VarResource.java | 8 --- .../infinity/resource/vef/VefResource.java | 8 --- src/org/infinity/resource/wed/Door.java | 8 --- src/org/infinity/resource/wed/Polygon.java | 8 --- .../infinity/resource/wed/WedResource.java | 10 ---- src/org/infinity/search/FileTypeSelector.java | 4 -- src/org/infinity/search/SearchResource.java | 50 ------------------- src/org/infinity/util/IniMapSection.java | 2 - 70 files changed, 484 deletions(-) diff --git a/src/org/infinity/check/AbstractChecker.java b/src/org/infinity/check/AbstractChecker.java index a21cf3d6d..8ddc7f4db 100644 --- a/src/org/infinity/check/AbstractChecker.java +++ b/src/org/infinity/check/AbstractChecker.java @@ -84,7 +84,6 @@ protected boolean runCheck(List entries) return runSearch("Checking", entries); } - // @Override public void actionPerformed(ActionEvent event) { @@ -99,5 +98,4 @@ else if (event.getSource() == bCancel) { settingsWindow.setVisible(false); } } - // } diff --git a/src/org/infinity/datatype/Bestiary.java b/src/org/infinity/datatype/Bestiary.java index ce2488a7e..b2d12993d 100644 --- a/src/org/infinity/datatype/Bestiary.java +++ b/src/org/infinity/datatype/Bestiary.java @@ -100,7 +100,6 @@ public final class Bestiary extends Datatype implements Editable, TableModel /** Color, used to highligth background of party members. */ private static final Color PARTY_COLOR = Color.CYAN; - // /** * List of used entries in a bestiary field. Other bytes in the {@link #known} * field are unused. @@ -109,7 +108,6 @@ public final class Bestiary extends Datatype implements Editable, TableModel * hardcoded in the game. */ private final List creatures; - // /** * If byte contains non zero value, then player knowns creature from {@link #creatures} * array. Only first 88 bytes is used ({@code creatures.length}). @@ -409,7 +407,6 @@ public Component getTableCellRendererComponent(JTable table, Object value, setPreferredSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE)); } - // @Override public void valueChanged(ListSelectionEvent e) { @@ -431,17 +428,13 @@ public void valueChanged(ListSelectionEvent e) revalidate(); } } - // - // @Override public void tableChanged(TableModelEvent e) { container.actionPerformed(new ActionEvent(e.getSource(), ACTION_PERFORMED, UPDATE_VALUE)); } - // - // @Override public void mouseClicked(MouseEvent e) { @@ -466,9 +459,7 @@ public void mouseEntered(MouseEvent e) {} @Override public void mouseExited(MouseEvent e) {} - // - // @Override public void actionPerformed(ActionEvent e) { @@ -516,7 +507,6 @@ public void actionPerformed(ActionEvent e) } } } - // /** * Setup context menu and show it for table. @@ -577,7 +567,6 @@ public Bestiary(ByteBuffer buffer, int offset, String name) creatures = readCreatures(); } - // @Override public JComponent edit(ActionListener container) { return new Viewer(container); } @@ -592,17 +581,13 @@ public boolean updateValue(AbstractStruct struct) return true; } - // - // @Override public void write(OutputStream os) throws IOException { os.write(known); } - // - // @Override public int read(ByteBuffer buffer, int offset) { @@ -610,9 +595,7 @@ public int read(ByteBuffer buffer, int offset) buffer.get(known); return offset + known.length; } - // - // @Override public int getRowCount() { return creatures.size(); } @Override @@ -679,7 +662,6 @@ public void removeTableModelListener(TableModelListener l) { listenerList.remove(TableModelListener.class, l); } - // /** * Returns {@code true} if specified creature is included in the bestiary. diff --git a/src/org/infinity/datatype/Flag.java b/src/org/infinity/datatype/Flag.java index 60eed848e..2e75aa112 100644 --- a/src/org/infinity/datatype/Flag.java +++ b/src/org/infinity/datatype/Flag.java @@ -74,7 +74,6 @@ public Flag(ByteBuffer buffer, int offset, int length, String name, String[] sta setFlagDescriptions(length, stable, 1); } - // @Override public void actionPerformed(ActionEvent event) { @@ -88,9 +87,7 @@ else if (event.getSource() == bNone) { } container.actionPerformed(new ActionEvent(this, 0, StructViewer.UPDATE_VALUE)); } - // - // @Override public JComponent edit(ActionListener container) { @@ -172,15 +169,12 @@ public boolean updateValue(AbstractStruct struct) return true; } - // @Override public void write(OutputStream os) throws IOException { writeLong(os, value); } - // - // @Override public int read(ByteBuffer buffer, int offset) { @@ -201,8 +195,6 @@ public int read(ByteBuffer buffer, int offset) return offset + getSize(); } - // - // @Override public String toString() @@ -258,7 +250,6 @@ public boolean isFlagSet(int i) return (value & bitnr) == bitnr; } - // @Override public long getLongValue() { @@ -270,7 +261,6 @@ public int getValue() { return (int)value; } - // public void setValue(long newValue) { diff --git a/src/org/infinity/datatype/ResourceRef.java b/src/org/infinity/datatype/ResourceRef.java index 29ecbd954..30d8291f5 100644 --- a/src/org/infinity/datatype/ResourceRef.java +++ b/src/org/infinity/datatype/ResourceRef.java @@ -94,7 +94,6 @@ private ResourceRef(ByteBuffer h_buffer, int offset, int length, String name, St read(h_buffer, offset); } - // @Override public void actionPerformed(ActionEvent event) { @@ -105,9 +104,7 @@ public void actionPerformed(ActionEvent event) } } } - // - // @Override public JComponent edit(final ActionListener container) { @@ -249,17 +246,13 @@ public boolean updateValue(AbstractStruct struct) return true; } - // - // @Override public void valueChanged(ListSelectionEvent e) { bView.setEnabled(isEditable(list.getSelectedValue())); } - // - // @Override public void write(OutputStream os) throws IOException { @@ -276,9 +269,7 @@ public void write(OutputStream os) throws IOException StreamUtils.writeString(os, resname, getSize()); } } - // - // @Override public int read(ByteBuffer buffer, int offset) { @@ -299,7 +290,6 @@ public int read(ByteBuffer buffer, int offset) return offset + getSize(); } - // @Override public String toString() @@ -335,15 +325,12 @@ public boolean equals(Object o) return retVal; } - // @Override public String getText() { return resname; } - // - // @Override public String getResourceName() { @@ -352,7 +339,6 @@ public String getResourceName() } return resname; } - // public boolean isEmpty() { diff --git a/src/org/infinity/gui/BIFFEditor.java b/src/org/infinity/gui/BIFFEditor.java index efd6bce95..2f417f5c4 100644 --- a/src/org/infinity/gui/BIFFEditor.java +++ b/src/org/infinity/gui/BIFFEditor.java @@ -71,7 +71,6 @@ public BIFFEditor() new ChooseBIFFrame(this); } - // @Override public void actionPerformed(ActionEvent event) { @@ -95,9 +94,7 @@ else if (event.getSource() == cbformat) { bsave.setEnabled(!biftable.isEmpty()); } } - // - // @Override public void valueChanged(ListSelectionEvent event) { @@ -106,9 +103,7 @@ public void valueChanged(ListSelectionEvent event) btobif.setEnabled(overridetable.getSelectedValues().length != 0); } } - // - // @Override public void run() { @@ -201,7 +196,6 @@ public void run() ResourceFactory.getResourceTreeModel().sort(); blocker.setBlocked(false); } - // public void makeEditor(BIFFEntry bifentry, AbstractBIFFReader.Type format) { diff --git a/src/org/infinity/gui/BIFFEditorTable.java b/src/org/infinity/gui/BIFFEditorTable.java index 7b852cf04..17203644c 100644 --- a/src/org/infinity/gui/BIFFEditorTable.java +++ b/src/org/infinity/gui/BIFFEditorTable.java @@ -142,7 +142,6 @@ public void mouseClicked(MouseEvent event) add(new JScrollPane(table), BorderLayout.CENTER); } - // @Override public void actionPerformed(ActionEvent event) { @@ -153,7 +152,6 @@ else if (event.getSource() == bupdated) else if (event.getSource() == bnew) tablemodel.fireTableDataChanged(); } - // public void addEntry(ResourceEntry entry, State state) { diff --git a/src/org/infinity/gui/BrowserMenuBar.java b/src/org/infinity/gui/BrowserMenuBar.java index 6cfab7a1f..88a141ece 100644 --- a/src/org/infinity/gui/BrowserMenuBar.java +++ b/src/org/infinity/gui/BrowserMenuBar.java @@ -130,7 +130,6 @@ public final class BrowserMenuBar extends JMenuBar implements KeyEventDispatcher private final HelpMenu helpMenu; private final Preferences prefsGui, prefsProfiles; - // /** Determines, in which virtual folder show resources from Override folder. */ public enum OverrideMode { @@ -204,7 +203,6 @@ public enum ViewMode private ViewMode(String title) { this.title = title; } } - // public static BrowserMenuBar getInstance() { @@ -668,7 +666,6 @@ public void storePreferences() gameMenu.storePreferences(); } - // @Override public boolean dispatchKeyEvent(KeyEvent e) { @@ -728,7 +725,6 @@ private static Frame findActiveFrame() } return null; } - // // -------------------------- INNER CLASSES -------------------------- diff --git a/src/org/infinity/gui/LinkButton.java b/src/org/infinity/gui/LinkButton.java index 858180b18..42186b9c2 100644 --- a/src/org/infinity/gui/LinkButton.java +++ b/src/org/infinity/gui/LinkButton.java @@ -208,7 +208,6 @@ public String getUrl() } } - // @Override public void actionPerformed(ActionEvent e) { @@ -229,9 +228,7 @@ public void actionPerformed(ActionEvent e) } } } - // - // @Override public void mouseClicked(MouseEvent e) { @@ -272,7 +269,6 @@ public void mouseEntered(MouseEvent e) public void mouseExited(MouseEvent e) { } - // public void removeActionListener(ActionListener listener) { diff --git a/src/org/infinity/gui/hexview/StructHexViewer.java b/src/org/infinity/gui/hexview/StructHexViewer.java index f9430dc98..332946c2f 100644 --- a/src/org/infinity/gui/hexview/StructHexViewer.java +++ b/src/org/infinity/gui/hexview/StructHexViewer.java @@ -189,7 +189,6 @@ public StructHexViewer(AbstractStruct struct, IColormap colorMap, IDataProvider initGui(); } - // @Override public void stateChanged(HexViewEvent event) { @@ -206,17 +205,13 @@ public void stateChanged(HexViewEvent event) updateStatusBar(offset); } } - // - // @Override public void dataChanged(DataChangedEvent event) { getStruct().setStructChanged(true); } - // - // @Override public void actionPerformed(ActionEvent event) { @@ -326,9 +321,7 @@ public void actionPerformed(ActionEvent event) getHexView().requestFocusInWindow(); } } - // - // @Override public void stateChanged(ChangeEvent e) { @@ -347,9 +340,7 @@ public void stateChanged(ChangeEvent e) } } } - // - // @Override public void close() throws Exception { @@ -371,7 +362,6 @@ public void close() throws Exception findData = null; } } - // /** Returns the associated resource structure. */ public AbstractStruct getStruct() diff --git a/src/org/infinity/gui/layeritem/AbstractLayerItem.java b/src/org/infinity/gui/layeritem/AbstractLayerItem.java index b0b7ccf53..7b497b0f8 100644 --- a/src/org/infinity/gui/layeritem/AbstractLayerItem.java +++ b/src/org/infinity/gui/layeritem/AbstractLayerItem.java @@ -160,7 +160,6 @@ public void showViewable() } } - // @Override public void mouseClicked(MouseEvent event) { @@ -197,9 +196,7 @@ public void mouseReleased(MouseEvent event) setItemState(ItemState.NORMAL); } } - // - // @Override public void mouseDragged(MouseEvent event) { @@ -214,16 +211,13 @@ public void mouseMoved(MouseEvent event) setItemState(ItemState.NORMAL); } } - // - // @Override public boolean contains(int x, int y) { // Non-visible parts of the component are disregarded by mouse events return isMouseOver(new Point(x, y)); } - // /** Returns whether the mouse cursor is over the relevant part of the component. */ protected boolean isMouseOver(Point pt) diff --git a/src/org/infinity/gui/layeritem/AnimatedLayerItem.java b/src/org/infinity/gui/layeritem/AnimatedLayerItem.java index b5a4a0a0d..1118f30f1 100644 --- a/src/org/infinity/gui/layeritem/AnimatedLayerItem.java +++ b/src/org/infinity/gui/layeritem/AnimatedLayerItem.java @@ -324,7 +324,6 @@ public void setVisible(boolean aFlag) super.setVisible(aFlag); } - // @Override public void layerItemChanged(LayerItemEvent event) { @@ -332,9 +331,7 @@ public void layerItemChanged(LayerItemEvent event) updateDisplay(false); } } - // - // @Override public void actionPerformed(ActionEvent event) { @@ -363,9 +360,7 @@ protected Void doInBackground() throws Exception { } } } - // - // @Override public void propertyChange(PropertyChangeEvent event) { @@ -377,7 +372,6 @@ public void propertyChange(PropertyChangeEvent event) } } } - // @Override public void repaint() diff --git a/src/org/infinity/gui/layeritem/IconLayerItem.java b/src/org/infinity/gui/layeritem/IconLayerItem.java index d4ada27f6..06a4b46e7 100644 --- a/src/org/infinity/gui/layeritem/IconLayerItem.java +++ b/src/org/infinity/gui/layeritem/IconLayerItem.java @@ -232,7 +232,6 @@ public void setLabelEnabled(boolean set) validate(); } - // @Override public void layerItemChanged(LayerItemEvent event) { @@ -240,7 +239,6 @@ public void layerItemChanged(LayerItemEvent event) setCurrentImage(getItemState()); } } - // /** Returns whether the mouse cursor is over the relevant part of the component. */ @Override diff --git a/src/org/infinity/gui/layeritem/ShapedLayerItem.java b/src/org/infinity/gui/layeritem/ShapedLayerItem.java index f589339be..8e9599676 100644 --- a/src/org/infinity/gui/layeritem/ShapedLayerItem.java +++ b/src/org/infinity/gui/layeritem/ShapedLayerItem.java @@ -246,7 +246,6 @@ private void updateShape() label.repaint(); } - // @Override public void layerItemChanged(LayerItemEvent event) { @@ -254,7 +253,6 @@ public void layerItemChanged(LayerItemEvent event) updateShape(); } } - // //----------------------------- INNER CLASSES ----------------------------- diff --git a/src/org/infinity/resource/AbstractStruct.java b/src/org/infinity/resource/AbstractStruct.java index e417a2600..d9aaaab28 100644 --- a/src/org/infinity/resource/AbstractStruct.java +++ b/src/org/infinity/resource/AbstractStruct.java @@ -193,7 +193,6 @@ protected AbstractStruct(AbstractStruct superStruct, String name, ByteBuffer buf } } - // @Override public void close() throws Exception { @@ -204,9 +203,7 @@ public void close() throws Exception viewer.close(); } } - // - // @Override public boolean isReferenceable() { @@ -218,17 +215,13 @@ public void searchReferences(Component parent) { new ReferenceSearcher(getResourceEntry(), parent); } - // - // @Override public int compareTo(StructEntry o) { return getOffset() - o.getOffset(); } - // - // @Override public AbstractStruct clone() throws CloneNotSupportedException { @@ -340,9 +333,7 @@ public void setParent(AbstractStruct parent) addPropertyChangeListener(parent); } } - // - // @Override public int getRowCount() { @@ -432,9 +423,7 @@ public void setValueAt(Object value, int row, int column) } } } - // - // @Override public JComponent makeViewer(ViewableContainer container) { @@ -444,9 +433,7 @@ public JComponent makeViewer(ViewableContainer container) } return viewer; } - // - // @Override public void write(OutputStream os) throws IOException { @@ -455,7 +442,6 @@ public void write(OutputStream os) throws IOException e.write(os); } } - // @Override public String toString() diff --git a/src/org/infinity/resource/AbstractVariable.java b/src/org/infinity/resource/AbstractVariable.java index 69d1ea6c2..ac067cd9e 100644 --- a/src/org/infinity/resource/AbstractVariable.java +++ b/src/org/infinity/resource/AbstractVariable.java @@ -90,13 +90,11 @@ protected AbstractVariable(AbstractStruct superStruct, String name, ByteBuffer b super(superStruct, name, buffer, offset); } - // @Override public boolean canRemove() { return true; } - // @Override public int read(ByteBuffer buffer, int offset) throws Exception diff --git a/src/org/infinity/resource/are/AreResource.java b/src/org/infinity/resource/are/AreResource.java index c30eec580..cc148cf09 100644 --- a/src/org/infinity/resource/are/AreResource.java +++ b/src/org/infinity/resource/are/AreResource.java @@ -385,7 +385,6 @@ public AreResource(ResourceEntry entry) throws Exception super(entry); } - // @Override public void close() throws Exception { @@ -395,9 +394,7 @@ public void close() throws Exception areaViewer = null; } } - // - // @Override public AddRemovable[] getPrototypes() throws Exception { @@ -425,9 +422,7 @@ public AddRemovable confirmAddEntry(AddRemovable entry) throws Exception { return entry; } - // - // @Override public int getViewerTabCount() { @@ -472,17 +467,13 @@ public boolean viewerTabAddedBefore(int index) { return (index == 0); } - // - // @Override public void write(OutputStream os) throws IOException { super.writeFlatFields(os); } - // - // @Override protected void viewerInitialized(StructViewer viewer) { @@ -582,9 +573,7 @@ protected void datatypeRemovedInChild(AbstractStruct child, AddRemovable datatyp hexViewer.dataModified(); } } - // - // @Override public int read(ByteBuffer buffer, int offset) throws Exception { @@ -905,7 +894,6 @@ else if (Profile.getEngine() == Profile.Engine.IWD2) { } return endoffset; } - // private void updateActorCREOffsets() { diff --git a/src/org/infinity/resource/are/Container.java b/src/org/infinity/resource/are/Container.java index 11ad7f0a7..94b367eea 100644 --- a/src/org/infinity/resource/are/Container.java +++ b/src/org/infinity/resource/are/Container.java @@ -72,7 +72,6 @@ public Container(AbstractStruct superStruct, ByteBuffer buffer, int offset, int super(superStruct, ARE_CONTAINER + " " + nr, buffer, offset); } - // @Override public AddRemovable[] getPrototypes() throws Exception { @@ -84,17 +83,13 @@ public AddRemovable confirmAddEntry(AddRemovable entry) throws Exception { return entry; } - // - // @Override public boolean canRemove() { return true; } - // - // @Override public int getViewerTabCount() { @@ -118,9 +113,7 @@ public boolean viewerTabAddedBefore(int index) { return true; } - // - // @Override public void readVertices(ByteBuffer buffer, int offset) throws Exception { @@ -148,7 +141,6 @@ public int updateVertices(int offset, int number) ((DecNumber)getAttribute(ARE_CONTAINER_NUM_VERTICES)).setValue(count); return count; } - // @Override protected void setAddRemovableOffset(AddRemovable datatype) @@ -196,7 +188,6 @@ public int updateItems(int offset, int number) return count; } - // @Override public int read(ByteBuffer buffer, int offset) throws Exception { @@ -229,5 +220,4 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new Unknown(buffer, offset + 136, 56)); return offset + 192; } - // } diff --git a/src/org/infinity/resource/are/Door.java b/src/org/infinity/resource/are/Door.java index 2ecc01878..06570b455 100644 --- a/src/org/infinity/resource/are/Door.java +++ b/src/org/infinity/resource/are/Door.java @@ -94,7 +94,6 @@ public Door(AbstractStruct superStruct, ByteBuffer buffer, int offset, int nr) t super(superStruct, ARE_DOOR + " " + nr, buffer, offset); } - // @Override public AddRemovable[] getPrototypes() throws Exception { @@ -107,17 +106,13 @@ public AddRemovable confirmAddEntry(AddRemovable entry) throws Exception { return entry; } - // - // @Override public boolean canRemove() { return true; } - // - // @Override public void readVertices(ByteBuffer buffer, int offset) throws Exception { @@ -168,9 +163,7 @@ public int updateVertices(int offset, int number) } return count; } - // - // @Override protected void setAddRemovableOffset(AddRemovable datatype) { @@ -196,9 +189,7 @@ else if (datatype instanceof ClosedVertexImpeded) { datatype.setOffset(offset + 4 * (index - 1)); } } - // - // @Override public int read(ByteBuffer buffer, int offset) throws Exception { @@ -256,5 +247,4 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new Unknown(buffer, offset + 192, 8)); return offset + 200; } - // } diff --git a/src/org/infinity/resource/are/ITEPoint.java b/src/org/infinity/resource/are/ITEPoint.java index 40bdded13..b3407c136 100644 --- a/src/org/infinity/resource/are/ITEPoint.java +++ b/src/org/infinity/resource/are/ITEPoint.java @@ -75,9 +75,7 @@ public ITEPoint(AbstractStruct superStruct, ByteBuffer buffer, int offset, int n { super(superStruct, ARE_TRIGGER + " " + number, buffer, offset); } - // - // @Override public AddRemovable[] getPrototypes() throws Exception { @@ -89,17 +87,13 @@ public AddRemovable confirmAddEntry(AddRemovable entry) throws Exception { return entry; } - // - // @Override public boolean canRemove() { return true; } - // - // @Override public void readVertices(ByteBuffer buffer, int offset) throws Exception { @@ -127,9 +121,7 @@ public int updateVertices(int offset, int number) ((DecNumber)getAttribute(ARE_TRIGGER_NUM_VERTICES)).setValue(count); return count; } - // - // @Override protected void setAddRemovableOffset(AddRemovable datatype) { @@ -141,9 +133,7 @@ protected void setAddRemovableOffset(AddRemovable datatype) ((AbstractStruct)datatype).realignStructOffsets(); } } - // - // @Override public int read(ByteBuffer buffer, int offset) throws Exception { @@ -200,5 +190,4 @@ else if (Profile.getEngine() == Profile.Engine.IWD || Profile.getEngine() == Pro } return offset + 196; } - // } diff --git a/src/org/infinity/resource/are/viewer/AreaViewer.java b/src/org/infinity/resource/are/viewer/AreaViewer.java index 96e51d6f2..eb505a139 100644 --- a/src/org/infinity/resource/are/viewer/AreaViewer.java +++ b/src/org/infinity/resource/are/viewer/AreaViewer.java @@ -230,7 +230,6 @@ protected Void doInBackground() throws Exception { workerInitGui.execute(); } - // @Override public void close() { @@ -256,7 +255,6 @@ public void close() System.gc(); super.close(); } - // /** * Returns the tileset renderer for this viewer instance. @@ -3007,7 +3005,6 @@ public ChangeListener[] getChangeListeners() return listeners.toArray(new ChangeListener[listeners.size()]); } - // @Override public void actionPerformed(ActionEvent event) { @@ -3021,9 +3018,7 @@ public void actionPerformed(ActionEvent event) } } } - // - // @Override public void stateChanged(ChangeEvent event) { @@ -3038,7 +3033,6 @@ public void stateChanged(ChangeEvent event) } } } - // /** Fires a stateChanged event for all registered listeners. */ private void fireStateChanged() diff --git a/src/org/infinity/resource/are/viewer/LayerObjectActor.java b/src/org/infinity/resource/are/viewer/LayerObjectActor.java index 8321dacf6..1bfcaa24c 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectActor.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectActor.java @@ -91,7 +91,6 @@ protected LayerObjectActor(Class classType, AbstractSt super("Actor", classType, parent); } - // @Override public void close() { @@ -322,5 +321,4 @@ protected static Sequence getMatchingSequence(SpriteDecoder decoder, Sequence[] return retVal; } - // } diff --git a/src/org/infinity/resource/are/viewer/LayerObjectAmbient.java b/src/org/infinity/resource/are/viewer/LayerObjectAmbient.java index f07fb881a..e738409ae 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectAmbient.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectAmbient.java @@ -107,7 +107,6 @@ public LayerObjectAmbient(AreResource parent, Ambient ambient) } } - // @Override public Viewable getViewable() { @@ -168,7 +167,6 @@ public boolean isScheduled(int schedule) return false; } } - // /** * Returns whether the ambient sound uses a local sound radius. diff --git a/src/org/infinity/resource/are/viewer/LayerObjectAnimation.java b/src/org/infinity/resource/are/viewer/LayerObjectAnimation.java index d296ee818..3f692080d 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectAnimation.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectAnimation.java @@ -198,7 +198,6 @@ public LayerObjectAnimation(AreResource parent, Animation anim) items[1] = item2; } - // @Override public void close() { @@ -264,7 +263,6 @@ public boolean isScheduled(int schedule) return false; } } - // /** * Sets the lighting condition of the animation. Does nothing if the animation is flagged as diff --git a/src/org/infinity/resource/are/viewer/LayerObjectAutomap.java b/src/org/infinity/resource/are/viewer/LayerObjectAutomap.java index da914c5e0..744785118 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectAutomap.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectAutomap.java @@ -131,7 +131,6 @@ public LayerObjectAutomap(AreResource parent, AutomapNote note) item.setVisible(isVisible()); } - // @Override public Viewable getViewable() { @@ -158,5 +157,4 @@ public void update(double zoomFactor) (int)(location.y*zoomFactor + (zoomFactor / 2.0))); } } - // } diff --git a/src/org/infinity/resource/are/viewer/LayerObjectAutomapPST.java b/src/org/infinity/resource/are/viewer/LayerObjectAutomapPST.java index 2fb8eacad..7f11b72d3 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectAutomapPST.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectAutomapPST.java @@ -57,7 +57,6 @@ public LayerObjectAutomapPST(AreResource parent, AutomapNotePST note) item.setVisible(isVisible()); } - // @Override public Viewable getViewable() { @@ -84,5 +83,4 @@ public void update(double zoomFactor) (int)(location.y*zoomFactor + (zoomFactor / 2.0))); } } - // } diff --git a/src/org/infinity/resource/are/viewer/LayerObjectContainer.java b/src/org/infinity/resource/are/viewer/LayerObjectContainer.java index e1296434f..138e2a627 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectContainer.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectContainer.java @@ -64,7 +64,6 @@ public LayerObjectContainer(AreResource parent, Container container) item.setVisible(isVisible()); } - // @Override public Viewable getViewable() { @@ -95,7 +94,6 @@ public void update(double zoomFactor) item.setShape(poly); } } - // private String getAttributes() { diff --git a/src/org/infinity/resource/are/viewer/LayerObjectDoor.java b/src/org/infinity/resource/are/viewer/LayerObjectDoor.java index 7e5b7ed3c..c3cb797e2 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectDoor.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectDoor.java @@ -73,7 +73,6 @@ public LayerObjectDoor(AreResource parent, Door door) } } - // @Override public Viewable getViewable() { @@ -114,7 +113,6 @@ public void update(double zoomFactor) items[i].setShape(poly); } } - // private String getAttributes() { diff --git a/src/org/infinity/resource/are/viewer/LayerObjectDoorPoly.java b/src/org/infinity/resource/are/viewer/LayerObjectDoorPoly.java index f423f7b46..720561379 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectDoorPoly.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectDoorPoly.java @@ -89,7 +89,6 @@ public LayerObjectDoorPoly(WedResource parent, Door doorPoly) } } - // @Override public Viewable getViewable() { @@ -130,7 +129,6 @@ public void update(double zoomFactor) items[i].setShape(poly); } } - // /** * Returns an array of layer items of the specified state. diff --git a/src/org/infinity/resource/are/viewer/LayerObjectEntrance.java b/src/org/infinity/resource/are/viewer/LayerObjectEntrance.java index 92f4b614c..9a3871687 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectEntrance.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectEntrance.java @@ -57,7 +57,6 @@ public LayerObjectEntrance(AreResource parent, Entrance entrance) item.setVisible(isVisible()); } - // @Override public Viewable getViewable() { @@ -84,5 +83,4 @@ public void update(double zoomFactor) (int)(location.y*zoomFactor + (zoomFactor / 2.0))); } } - // } diff --git a/src/org/infinity/resource/are/viewer/LayerObjectProTrap.java b/src/org/infinity/resource/are/viewer/LayerObjectProTrap.java index bf4c8c3d6..73fa304c2 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectProTrap.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectProTrap.java @@ -59,7 +59,6 @@ public LayerObjectProTrap(AreResource parent, ProTrap trap) item.setVisible(isVisible()); } - // @Override public Viewable getViewable() { @@ -86,5 +85,4 @@ public void update(double zoomFactor) (int)(location.y*zoomFactor + (zoomFactor / 2.0))); } } - // } diff --git a/src/org/infinity/resource/are/viewer/LayerObjectRegion.java b/src/org/infinity/resource/are/viewer/LayerObjectRegion.java index 9cc759bd3..ad2c3bda2 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectRegion.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectRegion.java @@ -74,7 +74,6 @@ ITEPoint.s_type[type], getAttributes(), item.setVisible(isVisible()); } - // @Override public Viewable getViewable() { @@ -104,7 +103,6 @@ public void update(double zoomFactor) item.setShape(poly); } } - // private String getAttributes() { diff --git a/src/org/infinity/resource/are/viewer/LayerObjectSpawnPoint.java b/src/org/infinity/resource/are/viewer/LayerObjectSpawnPoint.java index d1b5bbaab..68982c98b 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectSpawnPoint.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectSpawnPoint.java @@ -59,7 +59,6 @@ public LayerObjectSpawnPoint(AreResource parent, SpawnPoint sp) item.setVisible(isVisible()); } - // @Override public Viewable getViewable() { @@ -96,5 +95,4 @@ public boolean isScheduled(int schedule) return false; } } - // } diff --git a/src/org/infinity/resource/are/viewer/LayerObjectTransition.java b/src/org/infinity/resource/are/viewer/LayerObjectTransition.java index 051768146..b293f4136 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectTransition.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectTransition.java @@ -66,7 +66,6 @@ public LayerObjectTransition(AreResource parent, AreResource destination, int ed item.setVisible(isVisible()); } - // @Override public Viewable getViewable() { @@ -125,5 +124,4 @@ public void update(double zoomFactor) item.setShape(poly); } } - // } diff --git a/src/org/infinity/resource/are/viewer/LayerObjectWallPoly.java b/src/org/infinity/resource/are/viewer/LayerObjectWallPoly.java index c61b057cd..3530f65a9 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectWallPoly.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectWallPoly.java @@ -63,7 +63,6 @@ public LayerObjectWallPoly(WedResource parent, WallPolygon wall) item.setVisible(isVisible()); } - // @Override public Viewable getViewable() { @@ -94,5 +93,4 @@ public void update(double zoomFactor) item.setShape(poly); } } - // } diff --git a/src/org/infinity/resource/chu/Control.java b/src/org/infinity/resource/chu/Control.java index e32b721b1..84411187f 100644 --- a/src/org/infinity/resource/chu/Control.java +++ b/src/org/infinity/resource/chu/Control.java @@ -114,16 +114,13 @@ final class Control extends AbstractStruct // implements AddRemovable this.size = size; } - // @Override public void write(OutputStream os) throws IOException { getFields().get(0).write(os); getFields().get(1).write(os); } - // - // @Override public int read(ByteBuffer buffer, int offset) { @@ -131,7 +128,6 @@ public int read(ByteBuffer buffer, int offset) addField(new HexNumber(buffer, offset + 4, 4, CHU_CONTROL_LENGTH)); return offset + 8; } - // /** Returns the control id. */ public int getControlId() diff --git a/src/org/infinity/resource/cre/CreResource.java b/src/org/infinity/resource/cre/CreResource.java index 740892fe1..9973a3009 100644 --- a/src/org/infinity/resource/cre/CreResource.java +++ b/src/org/infinity/resource/cre/CreResource.java @@ -753,7 +753,6 @@ public void close() throws Exception super.close(); } - // @Override public AddRemovable[] getPrototypes() throws Exception { @@ -779,15 +778,12 @@ public AddRemovable confirmAddEntry(AddRemovable entry) throws Exception return entry; } - // @Override public boolean canRemove() { return true; } - // - // @Override public int getViewerTabCount() { @@ -830,7 +826,6 @@ public boolean viewerTabAddedBefore(int index) { return (index == 0); } - // // Needed for embedded CRE resources private boolean showRawTab() @@ -842,15 +837,12 @@ private boolean showRawTab() return hasRawTab.booleanValue(); } - // @Override public void write(OutputStream os) throws IOException { super.writeFlatFields(os); } - // - // @Override protected void viewerInitialized(StructViewer viewer) { @@ -918,9 +910,7 @@ protected void datatypeRemovedInChild(AbstractStruct child, AddRemovable datatyp hexViewer.dataModified(); } } - // - // @Override public int read(ByteBuffer buffer, int offset) throws Exception { @@ -1027,7 +1017,6 @@ else if (version.toString().equalsIgnoreCase("V1.0") || } return readOther(version.toString(), buffer, offset); } - // //////////////////////// // Icewind Dale 2 @@ -2161,7 +2150,6 @@ private boolean convertEffects(int version, SectionOffset so, SectionCount sc) return retVal; } - // @Override public void itemStateChanged(ItemEvent event) { @@ -2174,9 +2162,7 @@ public void itemStateChanged(ItemEvent event) } } } - // - // @Override public boolean valueUpdated(UpdateEvent event) { @@ -2199,7 +2185,6 @@ public boolean valueUpdated(UpdateEvent event) } return false; } - // // Called by "Extended Search" // Checks whether the specified resource entry matches all available search options. diff --git a/src/org/infinity/resource/cre/Iwd2Struct.java b/src/org/infinity/resource/cre/Iwd2Struct.java index ba3b0f548..8db855592 100644 --- a/src/org/infinity/resource/cre/Iwd2Struct.java +++ b/src/org/infinity/resource/cre/Iwd2Struct.java @@ -52,7 +52,6 @@ public Iwd2Struct(AbstractStruct superStruct, ByteBuffer buffer, int offset, Dec setOffset(offset); } - // @Override public AddRemovable[] getPrototypes() throws Exception { @@ -75,9 +74,7 @@ public AddRemovable confirmAddEntry(AddRemovable entry) throws Exception { return entry; } - // - // @Override protected void datatypeAdded(AddRemovable datatype) { @@ -95,13 +92,10 @@ protected int getInsertPosition() { return count.getValue(); } - // - // @Override public int read(ByteBuffer buffer, int offset) { return -1; } - // } diff --git a/src/org/infinity/resource/cre/SpellMemorization.java b/src/org/infinity/resource/cre/SpellMemorization.java index bcc61f2d3..a94a2f5f8 100644 --- a/src/org/infinity/resource/cre/SpellMemorization.java +++ b/src/org/infinity/resource/cre/SpellMemorization.java @@ -36,15 +36,12 @@ public final class SpellMemorization extends AbstractStruct implements AddRemova super(cre, CRE_MEMORIZATION + " " + nr, buffer, offset); } - // @Override public boolean canRemove() { return true; } - // - // @Override public AddRemovable[] getPrototypes() throws Exception { @@ -56,9 +53,7 @@ public AddRemovable confirmAddEntry(AddRemovable entry) throws Exception { return entry; } - // - // @Override protected void setAddRemovableOffset(AddRemovable datatype) { @@ -72,9 +67,7 @@ protected void setAddRemovableOffset(AddRemovable datatype) ((AbstractStruct)datatype).realignStructOffsets(); } } - // - // @Override public int read(ByteBuffer buffer, int offset) { @@ -86,7 +79,6 @@ public int read(ByteBuffer buffer, int offset) addField(new DecNumber(buffer, offset + 12, 4, CRE_MEMORIZATION_SPELL_COUNT)); return offset + 16; } - // public void readMemorizedSpells(ByteBuffer buffer, int offset) throws Exception { diff --git a/src/org/infinity/resource/dlg/DlgItem.java b/src/org/infinity/resource/dlg/DlgItem.java index 99c0629f4..a5ce144a3 100644 --- a/src/org/infinity/resource/dlg/DlgItem.java +++ b/src/org/infinity/resource/dlg/DlgItem.java @@ -88,7 +88,6 @@ public DlgItem(DlgTreeModel parent, DlgResource dlg) @Override public void traverseChildren(Consumer action) { states.forEach(action); } - // @Override public StateItem getChildAt(int childIndex) { return states.get(childIndex); } @@ -109,12 +108,9 @@ public DlgItem(DlgTreeModel parent, DlgResource dlg) @Override public Enumeration children() { return enumeration(states); } - // - // @Override public Iterator iterator() { return states.iterator(); } - // /** * Extracts specified {@link SectionCount} attribute from dialog. diff --git a/src/org/infinity/resource/dlg/DlgResource.java b/src/org/infinity/resource/dlg/DlgResource.java index 6307a127f..c7e1a36ac 100644 --- a/src/org/infinity/resource/dlg/DlgResource.java +++ b/src/org/infinity/resource/dlg/DlgResource.java @@ -122,7 +122,6 @@ public DlgResource(ResourceEntry entry) throws Exception super(entry); } - // @Override public AddRemovable[] getPrototypes() throws Exception { @@ -135,9 +134,7 @@ public AddRemovable confirmAddEntry(AddRemovable entry) throws Exception { return entry; } - // - // @Override public int getViewerTabCount() { @@ -181,9 +178,7 @@ public boolean viewerTabAddedBefore(int index) { return (index == 0); } - // - // @Override public void write(OutputStream os) throws IOException { @@ -209,9 +204,7 @@ public void write(OutputStream os) throws IOException } } } - // - // @Override public void actionPerformed(ActionEvent e) { @@ -238,9 +231,7 @@ public void actionPerformed(ActionEvent e) } } } - // - // @Override protected void viewerInitialized(StructViewer viewer) { @@ -278,9 +269,7 @@ protected void datatypeRemoved(AddRemovable datatype) { updateReferences(datatype, false); } - // - // @Override public int read(ByteBuffer buffer, int offset) throws Exception { @@ -357,7 +346,6 @@ public int read(ByteBuffer buffer, int offset) throws Exception } return offset + textSize; } - // /** * Returns state with specified number from this dialog. diff --git a/src/org/infinity/resource/dlg/DlgTreeModel.java b/src/org/infinity/resource/dlg/DlgTreeModel.java index cbb0f2d66..eb18ce482 100644 --- a/src/org/infinity/resource/dlg/DlgTreeModel.java +++ b/src/org/infinity/resource/dlg/DlgTreeModel.java @@ -57,7 +57,6 @@ final class DlgTreeModel implements TreeModel, TreeNode, TableModelListener, Pro @Override public String toString() { return "Dialogues"; } - // @Override public DlgTreeModel getRoot() { return this; } @@ -129,9 +128,7 @@ public void removeTreeModelListener(TreeModelListener l) listeners.remove(l); } } - // - // @Override public ItemBase getChildAt(int childIndex) { @@ -181,9 +178,7 @@ public ItemBase nextElement() } }; } - // - // @Override public void tableChanged(TableModelEvent e) { @@ -213,9 +208,7 @@ public void tableChanged(TableModelEvent e) } } } - // - // @Override public void propertyChange(PropertyChangeEvent e) { @@ -265,9 +258,7 @@ public void propertyChange(PropertyChangeEvent e) } } } - // - // /** * Updates tree when specified state or transition entry changed. * @@ -337,7 +328,6 @@ private void removeItem(ItemBase item) } } - // /** * Changes tree structure accourding to the changes in the * {@link State#DLG_STATE_FIRST_RESPONSE_INDEX} property of the state. @@ -481,9 +471,7 @@ private void removeChildTransitions(StateItem parent, int fromIndex, int toIndex items.clear(); fireTreeNodesRemoved(parent.getPath(), childIndices, children); } - // - // /** * Changes tree structure accourding to the changes in the * {@link Transition#DLG_TRANS_NEXT_DIALOG}, @@ -507,9 +495,7 @@ private void changeTransition(List items) fireTreeStructureChanged(item.getPath()); } } - // - // private void nodeChanged(ItemBase node) { final TreeNode parent = node.getParent(); @@ -561,8 +547,6 @@ private void fireTreeStructureChanged(TreePath path) } } } - // - // /** * Translates child struct of the dialog that this tree represents, to GUI item. diff --git a/src/org/infinity/resource/dlg/ItemBase.java b/src/org/infinity/resource/dlg/ItemBase.java index 4392dafc4..834f85400 100644 --- a/src/org/infinity/resource/dlg/ItemBase.java +++ b/src/org/infinity/resource/dlg/ItemBase.java @@ -88,13 +88,11 @@ public TreePath getPath() */ public abstract void traverseChildren(Consumer action); - // @Override public abstract ItemBase getChildAt(int childIndex); @Override public abstract Enumeration children(); - // /** * Returns string that can be used to display in the tree. @@ -129,23 +127,19 @@ private String getText(StringRef value) /** Auxiliary class, being the parent for states, for a type safety. */ abstract class StateOwnerItem extends ItemBase { - // @Override public abstract StateItem getChildAt(int childIndex); @Override public abstract Enumeration children(); - // } /** Auxiliary class, being the parent for transitions, for a type safety. */ abstract class TransitionOwnerItem extends ItemBase implements Iterable { - // @Override public abstract TransitionItem getChildAt(int childIndex); @Override public abstract Enumeration children(); - // } \ No newline at end of file diff --git a/src/org/infinity/resource/dlg/OrphanStates.java b/src/org/infinity/resource/dlg/OrphanStates.java index 1386a4790..17ce2f361 100644 --- a/src/org/infinity/resource/dlg/OrphanStates.java +++ b/src/org/infinity/resource/dlg/OrphanStates.java @@ -30,7 +30,6 @@ final class OrphanStates extends StateOwnerItem implements Iterable @Override public String toString() { return "Orphan states"; } - // @Override public StateItem getChildAt(int childIndex) { return states.get(childIndex); } @@ -51,14 +50,10 @@ final class OrphanStates extends StateOwnerItem implements Iterable @Override public Enumeration children() { return enumeration(states); } - // - // @Override public Iterator iterator() { return states.iterator(); } - // - // @Override public TreeItemEntry getEntry() { return null; } @@ -76,5 +71,4 @@ final class OrphanStates extends StateOwnerItem implements Iterable @Override public void traverseChildren(Consumer action) { states.forEach(action); } - // } diff --git a/src/org/infinity/resource/dlg/OrphanTransitions.java b/src/org/infinity/resource/dlg/OrphanTransitions.java index 7e4d0c893..dee179edd 100644 --- a/src/org/infinity/resource/dlg/OrphanTransitions.java +++ b/src/org/infinity/resource/dlg/OrphanTransitions.java @@ -30,7 +30,6 @@ final class OrphanTransitions extends TransitionOwnerItem @Override public String toString() { return "Orphan responses"; } - // @Override public TransitionItem getChildAt(int childIndex) { return trans.get(childIndex); } @@ -51,14 +50,10 @@ final class OrphanTransitions extends TransitionOwnerItem @Override public Enumeration children() { return enumeration(trans); } - // - // @Override public Iterator iterator() { return trans.iterator(); } - // - // @Override public TreeItemEntry getEntry() { return null; } @@ -76,5 +71,4 @@ final class OrphanTransitions extends TransitionOwnerItem @Override public void traverseChildren(Consumer action) { trans.forEach(action); } - // } diff --git a/src/org/infinity/resource/dlg/State.java b/src/org/infinity/resource/dlg/State.java index 0cff932eb..6a71e95a4 100644 --- a/src/org/infinity/resource/dlg/State.java +++ b/src/org/infinity/resource/dlg/State.java @@ -41,7 +41,6 @@ public final class State extends AbstractStruct implements AddRemovable, TreeIte nr = count; } - // @Override public DlgResource getParent() { return (DlgResource)super.getParent(); } @@ -53,7 +52,6 @@ public StringRef getAssociatedText() { return (StringRef)getAttribute(DLG_STATE_RESPONSE, false); } - // public int getFirstTrans() { diff --git a/src/org/infinity/resource/dlg/StateItem.java b/src/org/infinity/resource/dlg/StateItem.java index a72fcf687..d96c4e6c4 100644 --- a/src/org/infinity/resource/dlg/StateItem.java +++ b/src/org/infinity/resource/dlg/StateItem.java @@ -80,7 +80,6 @@ public void traverseChildren(Consumer action) /** Returns technical name of state item which uniquely identifying it within dialog. */ public String getName() { return state.getName(); } - // @Override public TransitionItem getChildAt(int childIndex) { @@ -113,12 +112,9 @@ public Enumeration children() { return enumeration(getAllowsChildren() ? trans : emptyList()); } - // - // @Override public Iterator iterator() { return trans.iterator(); } - // @Override public String toString() { return getText(state); } diff --git a/src/org/infinity/resource/dlg/Transition.java b/src/org/infinity/resource/dlg/Transition.java index 993e5eb7a..e73d9b50d 100644 --- a/src/org/infinity/resource/dlg/Transition.java +++ b/src/org/infinity/resource/dlg/Transition.java @@ -47,7 +47,6 @@ public final class Transition extends AbstractStruct implements AddRemovable, Tr this.nr = nr; } - // @Override public DlgResource getParent() { return (DlgResource)super.getParent(); } @@ -60,7 +59,6 @@ public StringRef getAssociatedText() { return (StringRef)getAttribute(DLG_TRANS_TEXT, false); } - // public int getActionIndex() { diff --git a/src/org/infinity/resource/dlg/TransitionItem.java b/src/org/infinity/resource/dlg/TransitionItem.java index 3abd05fd1..4015ec67f 100644 --- a/src/org/infinity/resource/dlg/TransitionItem.java +++ b/src/org/infinity/resource/dlg/TransitionItem.java @@ -86,7 +86,6 @@ public void traverseChildren(Consumer action) /** Returns technical name of transition item which uniquely identifying it within dialog. */ public String getName() { return trans.getName(); } - // @Override public StateItem getChildAt(int childIndex) { return isMain() && childIndex == 0 ? nextState : null; } @@ -111,7 +110,6 @@ public Enumeration children() { return enumeration(isLeaf() ? emptyList() : singletonList(nextState)); } - // @Override public String toString() diff --git a/src/org/infinity/resource/dlg/TreeViewer.java b/src/org/infinity/resource/dlg/TreeViewer.java index f54932e35..59974bf6c 100644 --- a/src/org/infinity/resource/dlg/TreeViewer.java +++ b/src/org/infinity/resource/dlg/TreeViewer.java @@ -208,7 +208,6 @@ private void maybeShowPopup(MouseEvent e) add(splitv, BorderLayout.CENTER); } - // @Override public void actionPerformed(ActionEvent e) { @@ -277,9 +276,7 @@ public void actionPerformed(ActionEvent e) } } } - // - // @Override public void valueChanged(TreeSelectionEvent e) { @@ -297,9 +294,7 @@ public void valueChanged(TreeSelectionEvent e) } } } - // - // @Override public void propertyChange(PropertyChangeEvent event) { @@ -314,7 +309,6 @@ public void propertyChange(PropertyChangeEvent event) } } } - // /** * Selects specified dialog state or transition in the tree. If such entry not diff --git a/src/org/infinity/resource/dlg/Viewer.java b/src/org/infinity/resource/dlg/Viewer.java index a81df11e8..7857bfe63 100644 --- a/src/org/infinity/resource/dlg/Viewer.java +++ b/src/org/infinity/resource/dlg/Viewer.java @@ -72,7 +72,6 @@ final class Viewer extends JPanel implements ActionListener, ItemListener, Table private static final Color NORMAL_COLOR = Color.BLACK; private static final Color ERROR_COLOR = Color.RED; - // private final DlgResource dlg; /** List of all states, found in {@link #dlg}. */ private final List stateList = new ArrayList<>(); @@ -93,14 +92,12 @@ final class Viewer extends JPanel implements ActionListener, ItemListener, Table * will be do when game process entering to related state. */ private final List actionList = new ArrayList<>(); - // /** State that editor shows right now. */ private State currentState; /** Transition that editor shows right now. */ private Transition currentTrans; - // /** * Stack of states, that were selected by the {@link #CtrlSelect} button. The * {@link #CtrlReturn} button allows return to one of this states together with @@ -115,9 +112,7 @@ final class Viewer extends JPanel implements ActionListener, ItemListener, Table private final ArrayDeque lastTransitions = new ArrayDeque<>(); private DlgResource undoDlg; private boolean alive = true; - // - // private final ButtonPanel buttonPanel = new ButtonPanel(); private final DlgPanel stateTextPanel, stateTriggerPanel, transTextPanel, transTriggerPanel, transActionPanel; private final JMenuItem ifindall = new JMenuItem("in all DLG files"); @@ -127,7 +122,6 @@ final class Viewer extends JPanel implements ActionListener, ItemListener, Table private final JTextField tfResponse = new JTextField(4); private final TitledBorder bostate = new TitledBorder("State"); private final TitledBorder botrans = new TitledBorder("Response"); - // Viewer(DlgResource dlg) { diff --git a/src/org/infinity/resource/gam/GamResource.java b/src/org/infinity/resource/gam/GamResource.java index e33d944cc..3ff8a389c 100644 --- a/src/org/infinity/resource/gam/GamResource.java +++ b/src/org/infinity/resource/gam/GamResource.java @@ -136,7 +136,6 @@ public GamResource(ResourceEntry entry) throws Exception super(entry); } - // @Override public AddRemovable[] getPrototypes() throws Exception { @@ -169,9 +168,7 @@ public AddRemovable confirmAddEntry(AddRemovable entry) throws Exception } return entry; } - // - // @Override public int getViewerTabCount() { @@ -226,17 +223,13 @@ public boolean viewerTabAddedBefore(int index) { return index == 0 || Profile.getEngine() == Profile.Engine.PST && index == 1; } - // - // @Override public void write(OutputStream os) throws IOException { super.writeFlatFields(os); } - // - // @Override protected void viewerInitialized(StructViewer viewer) { @@ -284,9 +277,7 @@ protected void datatypeRemovedInChild(AbstractStruct child, AddRemovable datatyp hexViewer.dataModified(); } } - // - // @Override public int read(ByteBuffer buffer, int offset) throws Exception { @@ -553,7 +544,6 @@ else if (Profile.getEngine() == Profile.Engine.IWD2) { // V2.2 (V1.1 & V2.0 in B return offset; } - // private void updateOffsets() { diff --git a/src/org/infinity/resource/itm/Ability.java b/src/org/infinity/resource/itm/Ability.java index 1e9a34d35..ebb884215 100644 --- a/src/org/infinity/resource/itm/Ability.java +++ b/src/org/infinity/resource/itm/Ability.java @@ -65,7 +65,6 @@ public final class Ability extends AbstractAbility implements AddRemovable, HasC super(superStruct, ITM_ABIL + " " + number, buffer, offset); } - // @Override public AddRemovable[] getPrototypes() throws Exception { @@ -77,17 +76,13 @@ public AddRemovable confirmAddEntry(AddRemovable entry) throws Exception { return entry; } - // - // @Override public boolean canRemove() { return true; } - // - // @Override public int getViewerTabCount() { @@ -111,9 +106,7 @@ public boolean viewerTabAddedBefore(int index) { return true; } - // - // @Override public int read(ByteBuffer buffer, int offset) throws Exception { @@ -174,6 +167,5 @@ public int read(ByteBuffer buffer, int offset) throws Exception return offset + 56; } - // } diff --git a/src/org/infinity/resource/itm/ItmResource.java b/src/org/infinity/resource/itm/ItmResource.java index df697fbce..bb836a60f 100644 --- a/src/org/infinity/resource/itm/ItmResource.java +++ b/src/org/infinity/resource/itm/ItmResource.java @@ -241,7 +241,6 @@ public ItmResource(ResourceEntry entry) throws Exception super(entry); } - // @Override public AddRemovable[] getPrototypes() throws Exception { @@ -253,9 +252,7 @@ public AddRemovable confirmAddEntry(AddRemovable entry) throws Exception { return entry; } - // - // @Override public int getViewerTabCount() { @@ -300,9 +297,7 @@ public boolean viewerTabAddedBefore(int index) { return (index == 0); } - // - // @Override public void write(OutputStream os) throws IOException { @@ -314,9 +309,7 @@ public void write(OutputStream os) throws IOException } } } - // - // @Override protected void viewerInitialized(StructViewer viewer) { @@ -384,9 +377,7 @@ protected void datatypeRemovedInChild(AbstractStruct child, AddRemovable datatyp super.datatypeRemovedInChild(child, datatype); incAbilityEffects(child, datatype, -1); } - // - // @Override public int read(ByteBuffer buffer, int offset) throws Exception { @@ -508,7 +499,6 @@ else if (isV20) { return Math.max(offset, offset2); } - // private void incAbilityEffects(StructEntry child, AddRemovable datatype, int value) { diff --git a/src/org/infinity/resource/mus/MusResource.java b/src/org/infinity/resource/mus/MusResource.java index 87e974d8c..e3e1c2176 100644 --- a/src/org/infinity/resource/mus/MusResource.java +++ b/src/org/infinity/resource/mus/MusResource.java @@ -185,7 +185,6 @@ public MusResource(ResourceEntry entry) throws Exception resourceChanged = false; } - // @Override public void actionPerformed(ActionEvent event) { @@ -199,9 +198,7 @@ else if (buttonPanel.getControlByType(ButtonPanel.Control.EXPORT_BUTTON) == even ResourceFactory.exportResource(entry, panel.getTopLevelAncestor()); } } - // - // @Override public void close() throws Exception { @@ -213,9 +210,7 @@ public void close() throws Exception viewer.close(); } } - // - // @Override public boolean isReferenceable() { @@ -227,9 +222,7 @@ public void searchReferences(Component parent) { new SongReferenceSearcher(entry, parent); } - // - // @Override public void insertUpdate(DocumentEvent event) { @@ -247,9 +240,7 @@ public void changedUpdate(DocumentEvent event) { setDocumentModified(true); } - // - // @Override public void itemStateChanged(ItemEvent event) { @@ -265,17 +256,13 @@ public void itemStateChanged(ItemEvent event) } } } - // - // @Override public ResourceEntry getResourceEntry() { return entry; } - // - // @Override public String getText() { @@ -315,9 +302,7 @@ public void highlightText(int startOfs, int endOfs) } catch (IllegalArgumentException e) { } } - // - // @Override public JComponent makeViewer(ViewableContainer container) { @@ -340,9 +325,7 @@ public JComponent makeViewer(ViewableContainer container) } return panel; } - // - // @Override public void write(OutputStream os) throws IOException { @@ -352,7 +335,6 @@ public void write(OutputStream os) throws IOException StreamUtils.writeString(os, editor.getText(), editor.getText().length()); } } - // public Viewer getViewer() { diff --git a/src/org/infinity/resource/pro/ProAreaType.java b/src/org/infinity/resource/pro/ProAreaType.java index 6ce55a1a3..cfa3b8e92 100644 --- a/src/org/infinity/resource/pro/ProAreaType.java +++ b/src/org/infinity/resource/pro/ProAreaType.java @@ -103,13 +103,11 @@ public ProAreaType(AbstractStruct superStruct, ByteBuffer buffer, int offset) th super(superStruct, PRO_AREA, buffer, offset); } - // @Override public boolean canRemove() { return false; // can not be removed manually } - // @Override public boolean valueUpdated(UpdateEvent event) diff --git a/src/org/infinity/resource/pro/ProResource.java b/src/org/infinity/resource/pro/ProResource.java index ebd249a14..6db7baa99 100644 --- a/src/org/infinity/resource/pro/ProResource.java +++ b/src/org/infinity/resource/pro/ProResource.java @@ -132,7 +132,6 @@ public ProResource(ResourceEntry entry) throws Exception super(entry); } - // @Override public boolean valueUpdated(UpdateEvent event) { @@ -198,9 +197,7 @@ else if (event.getSource() instanceof IsNumeric && } return false; } - // - // @Override public int getViewerTabCount() { @@ -230,9 +227,7 @@ public boolean viewerTabAddedBefore(int index) { return false; } - // - // @Override public int read(ByteBuffer buffer, int offset) throws Exception { @@ -306,9 +301,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception return offset; } - // - // @Override protected void viewerInitialized(StructViewer viewer) { @@ -348,7 +341,6 @@ protected void datatypeRemovedInChild(AbstractStruct child, AddRemovable datatyp hexViewer.dataModified(); } } - // /** Updates current IDS targeting to IWD style and returns true if changes have been made. */ private boolean setIwdStyleIdsType(AbstractStruct struct, int offset, int nr) diff --git a/src/org/infinity/resource/spl/Ability.java b/src/org/infinity/resource/spl/Ability.java index 795a245d8..3d5fc45bc 100644 --- a/src/org/infinity/resource/spl/Ability.java +++ b/src/org/infinity/resource/spl/Ability.java @@ -50,7 +50,6 @@ public final class Ability extends AbstractAbility implements AddRemovable, HasC super(superStruct, SPL_ABIL + " " + number, buffer, offset); } - // @Override public AddRemovable[] getPrototypes() throws Exception { @@ -62,17 +61,13 @@ public AddRemovable confirmAddEntry(AddRemovable entry) throws Exception { return entry; } - // - // @Override public boolean canRemove() { return true; } - // - // @Override public int getViewerTabCount() { @@ -96,9 +91,7 @@ public boolean viewerTabAddedBefore(int index) { return true; } - // - // @Override public int read(ByteBuffer buffer, int offset) throws Exception { @@ -131,6 +124,5 @@ public int read(ByteBuffer buffer, int offset) throws Exception } return offset + 40; } - // } diff --git a/src/org/infinity/resource/spl/SplResource.java b/src/org/infinity/resource/spl/SplResource.java index 0140a5340..2a0d35c0d 100644 --- a/src/org/infinity/resource/spl/SplResource.java +++ b/src/org/infinity/resource/spl/SplResource.java @@ -161,7 +161,6 @@ public SplResource(ResourceEntry entry) throws Exception super(entry); } - // @Override public AddRemovable[] getPrototypes() throws Exception { @@ -173,9 +172,7 @@ public AddRemovable confirmAddEntry(AddRemovable entry) throws Exception { return entry; } - // - // @Override public int getViewerTabCount() { @@ -220,9 +217,7 @@ public boolean viewerTabAddedBefore(int index) { return (index == 0); } - // - // @Override public void write(OutputStream os) throws IOException { @@ -234,9 +229,7 @@ public void write(OutputStream os) throws IOException } } } - // - // @Override public boolean valueUpdated(UpdateEvent event) { @@ -256,9 +249,7 @@ public boolean valueUpdated(UpdateEvent event) } return false; } - // - // @Override protected void viewerInitialized(StructViewer viewer) { @@ -326,9 +317,7 @@ protected void datatypeRemovedInChild(AbstractStruct child, AddRemovable datatyp super.datatypeRemovedInChild(child, datatype); incAbilityEffects(child, datatype, -1); } - // - // @Override public int read(ByteBuffer buffer, int offset) throws Exception { @@ -408,7 +397,6 @@ public int read(ByteBuffer buffer, int offset) throws Exception return Math.max(offset, offset2); } - // private void incAbilityEffects(StructEntry child, AddRemovable datatype, int value) { diff --git a/src/org/infinity/resource/src/SrcResource.java b/src/org/infinity/resource/src/SrcResource.java index c27d70416..c79421f19 100644 --- a/src/org/infinity/resource/src/SrcResource.java +++ b/src/org/infinity/resource/src/SrcResource.java @@ -43,7 +43,6 @@ public SrcResource(ResourceEntry entry) throws Exception super(entry); } - // @Override public AddRemovable[] getPrototypes() throws Exception { @@ -55,9 +54,7 @@ public AddRemovable confirmAddEntry(AddRemovable entry) throws Exception { return entry; } - // - // @Override public int getViewerTabCount() { @@ -86,9 +83,7 @@ public boolean viewerTabAddedBefore(int index) { return false; } - // - // @Override public int read(ByteBuffer buffer, int offset) throws Exception { @@ -102,9 +97,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception } return offset; } - // - // @Override protected void viewerInitialized(StructViewer viewer) { @@ -144,5 +137,4 @@ protected void datatypeRemovedInChild(AbstractStruct child, AddRemovable datatyp hexViewer.dataModified(); } } - // } diff --git a/src/org/infinity/resource/sto/StoResource.java b/src/org/infinity/resource/sto/StoResource.java index 57bdd19f4..f2d35b5b3 100644 --- a/src/org/infinity/resource/sto/StoResource.java +++ b/src/org/infinity/resource/sto/StoResource.java @@ -100,7 +100,6 @@ public StoResource(ResourceEntry entry) throws Exception super(entry); } - // @Override public AddRemovable[] getPrototypes() throws Exception { @@ -116,9 +115,7 @@ public AddRemovable confirmAddEntry(AddRemovable entry) throws Exception { return entry; } - // - // @Override public int getViewerTabCount() { @@ -163,9 +160,7 @@ public boolean viewerTabAddedBefore(int index) { return (index == 0); } - // - // @Override public int read(ByteBuffer buffer, int offset) throws Exception { @@ -290,9 +285,7 @@ else if (version.toString().equals("V1.1")) { } return endoffset; } - // - // @Override protected void viewerInitialized(StructViewer viewer) { @@ -332,7 +325,6 @@ protected void datatypeRemovedInChild(AbstractStruct child, AddRemovable datatyp hexViewer.dataModified(); } } - // /** * Checks whether the specified resource entry matches all available search options. diff --git a/src/org/infinity/resource/text/QuestsPanel.java b/src/org/infinity/resource/text/QuestsPanel.java index 469789180..bd8c623a4 100644 --- a/src/org/infinity/resource/text/QuestsPanel.java +++ b/src/org/infinity/resource/text/QuestsPanel.java @@ -67,7 +67,6 @@ public final class QuestsPanel extends JPanel implements ListSelectionListener private final JTable assignedChecks = new JTable(); private final JTable completeChecks = new JTable(); - // private static final class QuestsModel extends AbstractTableModel { private static final String[] COLUMNS = { @@ -167,7 +166,6 @@ protected void setupCompleted(boolean isSelected) setFont(font.deriveFont(attributes)); } } - // public QuestsPanel(List quests, Variables vars) { @@ -234,7 +232,6 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole setQuests(quests); } - // @Override public void valueChanged(ListSelectionEvent e) { @@ -261,7 +258,6 @@ public void valueChanged(ListSelectionEvent e) completeChecks.setModel(EMPTY_MODEL); } } - // public void setQuests(List quests) { diff --git a/src/org/infinity/resource/text/QuestsResource.java b/src/org/infinity/resource/text/QuestsResource.java index f96c43d58..6ffa939be 100644 --- a/src/org/infinity/resource/text/QuestsResource.java +++ b/src/org/infinity/resource/text/QuestsResource.java @@ -41,7 +41,6 @@ public class QuestsResource extends PlainTextResource implements ChangeListener */ private boolean dirty = false; - // /** Represents one quest in the file. */ public static final class Quest { @@ -214,7 +213,6 @@ public enum State /** Quest is finished. */ Completed; } - // public QuestsResource() throws Exception { @@ -238,7 +236,6 @@ public JComponent makeViewer(ViewableContainer container) return pane; } - // @Override public void changedUpdate(DocumentEvent event) { @@ -259,9 +256,7 @@ public void removeUpdate(DocumentEvent event) super.removeUpdate(event); dirty = true; } - // - // @Override public void stateChanged(ChangeEvent e) { @@ -272,7 +267,6 @@ public void stateChanged(ChangeEvent e) dirty = false; } } - // public List readQuests() { diff --git a/src/org/infinity/resource/var/VarResource.java b/src/org/infinity/resource/var/VarResource.java index 0fc6ea262..d2280a105 100644 --- a/src/org/infinity/resource/var/VarResource.java +++ b/src/org/infinity/resource/var/VarResource.java @@ -38,7 +38,6 @@ public VarResource(ResourceEntry entry) throws Exception super(entry); } - // @Override public AddRemovable[] getPrototypes() throws Exception { @@ -50,9 +49,7 @@ public AddRemovable confirmAddEntry(AddRemovable entry) throws Exception { return entry; } - // - // @Override public int getViewerTabCount() { @@ -81,9 +78,7 @@ public boolean viewerTabAddedBefore(int index) { return false; } - // - // @Override public int read(ByteBuffer buffer, int offset) throws Exception { @@ -92,9 +87,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new Entry(this, buffer, offset + i * 44, i)); return offset + count * 44; } - // - // @Override protected void viewerInitialized(StructViewer viewer) { @@ -134,5 +127,4 @@ protected void datatypeRemovedInChild(AbstractStruct child, AddRemovable datatyp hexViewer.dataModified(); } } - // } diff --git a/src/org/infinity/resource/vef/VefResource.java b/src/org/infinity/resource/vef/VefResource.java index 63f0d47ef..156eb630a 100644 --- a/src/org/infinity/resource/vef/VefResource.java +++ b/src/org/infinity/resource/vef/VefResource.java @@ -48,7 +48,6 @@ public VefResource(ResourceEntry entry) throws Exception super(entry); } - // @Override public AddRemovable[] getPrototypes() throws Exception { @@ -60,9 +59,7 @@ public AddRemovable confirmAddEntry(AddRemovable entry) throws Exception { return entry; } - // - // @Override public int getViewerTabCount() { @@ -89,9 +86,7 @@ public boolean viewerTabAddedBefore(int index) { return false; } - // - // @Override public int read(ByteBuffer buffer, int offset) throws Exception { @@ -131,9 +126,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception } return endoffset; } - // - // @Override protected void viewerInitialized(StructViewer viewer) { @@ -173,5 +166,4 @@ protected void datatypeRemovedInChild(AbstractStruct child, AddRemovable datatyp hexViewer.dataModified(); } } - // } diff --git a/src/org/infinity/resource/wed/Door.java b/src/org/infinity/resource/wed/Door.java index ede8c2ca9..ec16dacb9 100644 --- a/src/org/infinity/resource/wed/Door.java +++ b/src/org/infinity/resource/wed/Door.java @@ -43,7 +43,6 @@ public Door(AbstractStruct superStruct, ByteBuffer buffer, int offset, int numbe super(superStruct, WED_DOOR + " " + number, buffer, offset); } - // @Override public AddRemovable[] getPrototypes() throws Exception { @@ -55,17 +54,13 @@ public AddRemovable confirmAddEntry(AddRemovable entry) throws Exception { return entry; } - // - // @Override public boolean canRemove() { return true; } - // - // @Override protected void setAddRemovableOffset(AddRemovable datatype) { @@ -75,7 +70,6 @@ protected void setAddRemovableOffset(AddRemovable datatype) datatype.setOffset(offset + index * 2); } } - // public DecNumber getTilemapIndex() { @@ -115,7 +109,6 @@ public void updatePolygonsOffset(int offset) } } - // @Override public int read(ByteBuffer buffer, int offset) throws Exception { @@ -156,5 +149,4 @@ public int read(ByteBuffer buffer, int offset) throws Exception } return offset + 26; } - // } diff --git a/src/org/infinity/resource/wed/Polygon.java b/src/org/infinity/resource/wed/Polygon.java index 0a06bd54e..393c7468d 100644 --- a/src/org/infinity/resource/wed/Polygon.java +++ b/src/org/infinity/resource/wed/Polygon.java @@ -37,7 +37,6 @@ public Polygon(AbstractStruct superStruct, String name, ByteBuffer buffer, int o super(superStruct, name, buffer, offset, 8); } - // @Override public AddRemovable[] getPrototypes() throws Exception { @@ -49,17 +48,13 @@ public AddRemovable confirmAddEntry(AddRemovable entry) throws Exception { return entry; } - // - // @Override public boolean canRemove() { return true; } - // - // @Override protected void setAddRemovableOffset(AddRemovable datatype) { @@ -74,7 +69,6 @@ protected void setAddRemovableOffset(AddRemovable datatype) ((AbstractStruct)datatype).realignStructOffsets(); } } - // public void readVertices(ByteBuffer buffer, int offset) throws Exception { @@ -101,7 +95,6 @@ public int updateVertices(int offset, int startIndex) return count; } - // @Override public int read(ByteBuffer buffer, int offset) throws Exception { @@ -115,5 +108,4 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new DecNumber(buffer, offset + 16, 2, WED_POLY_MAX_COORD_Y)); return offset + 18; } - // } diff --git a/src/org/infinity/resource/wed/WedResource.java b/src/org/infinity/resource/wed/WedResource.java index b7d3c42ad..80351c8d6 100644 --- a/src/org/infinity/resource/wed/WedResource.java +++ b/src/org/infinity/resource/wed/WedResource.java @@ -84,7 +84,6 @@ public WedResource(ResourceEntry entry) throws Exception super(entry); } - // @Override public AddRemovable[] getPrototypes() throws Exception { @@ -96,17 +95,13 @@ public AddRemovable confirmAddEntry(AddRemovable entry) throws Exception { return entry; } - // - // @Override public void write(OutputStream os) throws IOException { super.writeFlatFields(os); } - // - // @Override public int getViewerTabCount() { @@ -133,9 +128,7 @@ public boolean viewerTabAddedBefore(int index) { return false; } - // - // @Override protected void viewerInitialized(StructViewer viewer) { @@ -203,9 +196,7 @@ else if (datatype instanceof RemovableDecNumber && child instanceof Door) { hexViewer.dataModified(); } } - // - // @Override public int read(ByteBuffer buffer, int offset) throws Exception { @@ -308,7 +299,6 @@ public int compare(HexNumber s1, HexNumber s2) } return endoffset; } - // private void updateSectionOffsets(AddRemovable datatype, int size) { diff --git a/src/org/infinity/search/FileTypeSelector.java b/src/org/infinity/search/FileTypeSelector.java index aa379cf2b..b80280e02 100644 --- a/src/org/infinity/search/FileTypeSelector.java +++ b/src/org/infinity/search/FileTypeSelector.java @@ -33,7 +33,6 @@ */ public class FileTypeSelector extends JPanel implements ActionListener { - // /** * Stores last chosed array of selected checkboxes for each resource type. * Each array has length equals to length of {@link #filetypes} @@ -54,7 +53,6 @@ public class FileTypeSelector extends JPanel implements ActionListener private final JButton bDefault = new JButton("Default", Icons.getIcon(Icons.ICON_UNDO_16)); /** Button that inverts check state of each checkbox. */ private final JButton bInvert = new JButton("Invert", Icons.getIcon(Icons.ICON_REFRESH_16)); - // /** * Creates panel with tho columns of checkboxex with abilities to check/unckeck all, @@ -150,7 +148,6 @@ public List getResources(String key) { return result; } - // @Override public void actionPerformed(ActionEvent e) { final Object src = e.getSource(); @@ -176,5 +173,4 @@ public void actionPerformed(ActionEvent e) { } } } - // } diff --git a/src/org/infinity/search/SearchResource.java b/src/org/infinity/search/SearchResource.java index ffcb23728..0c2cc27b1 100644 --- a/src/org/infinity/search/SearchResource.java +++ b/src/org/infinity/search/SearchResource.java @@ -136,7 +136,6 @@ public Void doInBackground() }).execute(); } - // @Override public void actionPerformed(ActionEvent event) { @@ -190,9 +189,7 @@ public void actionPerformed(ActionEvent event) } } } - // - // @Override public void propertyChange(PropertyChangeEvent event) { @@ -204,9 +201,7 @@ public void propertyChange(PropertyChangeEvent event) } } } - // - // @Override public void run() { @@ -276,7 +271,6 @@ public void run() } } } - // /** Initialize dialog. */ private void init() throws Exception @@ -658,7 +652,6 @@ public OptionsAREPanel(SearchResource searchResource) init(); } - // @Override public void actionPerformed(ActionEvent event) { @@ -699,7 +692,6 @@ public void actionPerformed(ActionEvent event) } } } - // @Override public SearchOptions getOptions() @@ -931,7 +923,6 @@ public OptionsCREPanel(SearchResource searchResource) init(); } - // @Override public void actionPerformed(ActionEvent event) { @@ -992,7 +983,6 @@ public void actionPerformed(ActionEvent event) } } } - // @Override public SearchOptions getOptions() @@ -1421,7 +1411,6 @@ public OptionsEFFPanel(SearchResource searchResource) init(); } - // @Override public void actionPerformed(ActionEvent event) { @@ -1473,7 +1462,6 @@ public void actionPerformed(ActionEvent event) } } } - // @Override public SearchOptions getOptions() @@ -1726,7 +1714,6 @@ public OptionsITMPanel(SearchResource searchResource) init(); } - // @Override public void actionPerformed(ActionEvent event) { @@ -1785,7 +1772,6 @@ public void actionPerformed(ActionEvent event) } } } - // @Override public SearchOptions getOptions() @@ -2175,7 +2161,6 @@ public OptionsPROPanel(SearchResource searchResource) init(); } - // @Override public void actionPerformed(ActionEvent event) { @@ -2231,7 +2216,6 @@ public void actionPerformed(ActionEvent event) } } } - // @Override public SearchOptions getOptions() @@ -2493,7 +2477,6 @@ public OptionsSPLPanel(SearchResource searchResource) init(); } - // @Override public void actionPerformed(ActionEvent event) { @@ -2551,7 +2534,6 @@ public void actionPerformed(ActionEvent event) } } } - // @Override public SearchOptions getOptions() @@ -2879,7 +2861,6 @@ public OptionsSTOPanel(SearchResource searchResource) init(); } - // @Override public void actionPerformed(ActionEvent event) { @@ -2945,7 +2926,6 @@ public void actionPerformed(ActionEvent event) } } } - // @Override public SearchOptions getOptions() @@ -3263,7 +3243,6 @@ public OptionsVVCPanel(SearchResource searchResource) init(); } - // @Override public void actionPerformed(ActionEvent event) { @@ -3300,7 +3279,6 @@ public void actionPerformed(ActionEvent event) } } } - // @Override public SearchOptions getOptions() @@ -3479,7 +3457,6 @@ public FlagsPanel(int size, String[] table) init(table); } - // @Override public void actionPerformed(ActionEvent event) { @@ -3497,7 +3474,6 @@ public void actionPerformed(ActionEvent event) } } } - // public Couple getOptionFlags() { @@ -3625,7 +3601,6 @@ public EffectsPanel(int effectCount, String label) init(); } - // @Override public void actionPerformed(ActionEvent event) { @@ -3640,7 +3615,6 @@ public void actionPerformed(ActionEvent event) } } } - // public boolean isActive(int id) { @@ -3764,7 +3738,6 @@ public CustomFilterPanel(int filterCount, String label) init(); } - // @Override public void actionPerformed(ActionEvent event) { @@ -3800,7 +3773,6 @@ public void actionPerformed(ActionEvent event) } } } - // public boolean isActive(int id) { @@ -3998,7 +3970,6 @@ public TimingModePanel() init(); } - // @Override public void actionPerformed(ActionEvent event) { @@ -4022,7 +3993,6 @@ public void actionPerformed(ActionEvent event) } } } - // public boolean isActive(int id) { @@ -4098,7 +4068,6 @@ public CreLevelPanel() init(); } - // @Override public void actionPerformed(ActionEvent event) { @@ -4113,7 +4082,6 @@ public void actionPerformed(ActionEvent event) } } } - // public boolean isActive(int classIdx) { @@ -4206,7 +4174,6 @@ public CreLevelIWD2Panel() init(); } - // @Override public void actionPerformed(ActionEvent event) { @@ -4221,7 +4188,6 @@ public void actionPerformed(ActionEvent event) } } } - // public boolean isActive(int id) { @@ -4308,7 +4274,6 @@ public CreTypePanel() init(); } - // @Override public void actionPerformed(ActionEvent event) { @@ -4322,7 +4287,6 @@ public void actionPerformed(ActionEvent event) } } } - // public boolean isActive(int id) { @@ -4430,7 +4394,6 @@ public CreGameSpecificPanel() init(); } - // @Override public void actionPerformed(ActionEvent event) { @@ -4444,7 +4407,6 @@ public void actionPerformed(ActionEvent event) } } } - // public boolean isActive(int id) { @@ -4561,7 +4523,6 @@ public ResourcesFilterPanel(int filterCount, String nameTemplate, String ext, bo add(panel, BorderLayout.CENTER); } - // @Override public void actionPerformed(ActionEvent event) { @@ -4575,7 +4536,6 @@ public void actionPerformed(ActionEvent event) } } } - // public boolean isActive(int id) { @@ -4616,7 +4576,6 @@ public ItmUsabilityPanel() init(); } - // @Override public void actionPerformed(ActionEvent event) { @@ -4630,7 +4589,6 @@ public void actionPerformed(ActionEvent event) } } } - // public boolean isActive(int id) { @@ -4741,7 +4699,6 @@ public ItmStatsPanel() init(); } - // @Override public void actionPerformed(ActionEvent event) { @@ -4756,7 +4713,6 @@ public void actionPerformed(ActionEvent event) } } } - // public boolean isActive(int statID) { @@ -4872,7 +4828,6 @@ public ItmAbilityPanel() init(); } - // @Override public void actionPerformed(ActionEvent event) { @@ -4940,7 +4895,6 @@ public void actionPerformed(ActionEvent event) } } } - // public boolean isActive(int itemID) { @@ -5211,7 +5165,6 @@ public SplAbilityPanel() init(); } - // @Override public void actionPerformed(ActionEvent event) { @@ -5261,7 +5214,6 @@ public void actionPerformed(ActionEvent event) } } } - // public boolean isActive(int spellID) { @@ -5460,7 +5412,6 @@ public StoCategoriesPanel(int purchasedCount) init(); } - // @Override public void actionPerformed(ActionEvent event) { @@ -5474,7 +5425,6 @@ public void actionPerformed(ActionEvent event) } } } - // public boolean isActive(int index) { diff --git a/src/org/infinity/util/IniMapSection.java b/src/org/infinity/util/IniMapSection.java index 758d8216d..0e7a13d57 100644 --- a/src/org/infinity/util/IniMapSection.java +++ b/src/org/infinity/util/IniMapSection.java @@ -75,7 +75,6 @@ public int getEntryCount() return entries.size(); } - // /** * Returns the first instance with {@code "key"} matching the key value of the * entry. Performs case-insensitive search. @@ -188,7 +187,6 @@ public StringRef getAsStringRef(String key) final IniMapEntry entry = getEntry(key); return entry == null ? null : entry.getStringRefValue(); } - // @Override public Iterator iterator() { return entries.iterator(); } From 18591734705c825bbfe4eff44d2a578864cae2c3 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 23 Apr 2021 23:54:23 +0200 Subject: [PATCH 148/158] Use try-with-resources statements for resource access --- src/org/infinity/gui/InfinityTextArea.java | 29 +++----- .../infinity/resource/ResourceFactory.java | 14 ++-- .../resource/graphics/BamDecoder.java | 24 +++---- .../resource/graphics/BamResource.java | 69 +++++++++---------- .../resource/graphics/MosDecoder.java | 25 +++---- .../resource/graphics/MosResource.java | 63 ++++++++--------- .../resource/graphics/PvrzResource.java | 6 +- src/org/infinity/updater/Updater.java | 28 ++++---- src/org/infinity/updater/Utils.java | 29 +++----- src/org/infinity/util/StringTable.java | 10 +-- .../util/io/zip/DlcFileAttributes.java | 44 ++++++------ 11 files changed, 145 insertions(+), 196 deletions(-) diff --git a/src/org/infinity/gui/InfinityTextArea.java b/src/org/infinity/gui/InfinityTextArea.java index 7c9d2770c..e45a1da2e 100644 --- a/src/org/infinity/gui/InfinityTextArea.java +++ b/src/org/infinity/gui/InfinityTextArea.java @@ -366,28 +366,15 @@ public static void applyExtendedSettings(RSyntaxTextArea edit, Language language } } - InputStream is = null; - try { - is = ClassLoader.getSystemResourceAsStream(schemePath); - if (is != null) { - try { - Theme theme = Theme.load(is); - if (theme != null) { - theme.apply(edit); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - } finally { - if (is != null) { - try { - is.close(); - } catch (IOException e) { - e.printStackTrace(); - } - is = null; + try (InputStream is = ClassLoader.getSystemResourceAsStream(schemePath)) { + Theme theme = Theme.load(is); + if (theme != null) { + theme.apply(edit); } + } catch (NullPointerException e) { + // ignore + } catch (IOException e) { + e.printStackTrace(); } diff --git a/src/org/infinity/resource/ResourceFactory.java b/src/org/infinity/resource/ResourceFactory.java index 5af28ee65..d6401e773 100644 --- a/src/org/infinity/resource/ResourceFactory.java +++ b/src/org/infinity/resource/ResourceFactory.java @@ -26,8 +26,8 @@ import java.util.List; import java.util.Locale; import java.util.regex.Pattern; -import javax.swing.JComponent; +import javax.swing.JComponent; import javax.swing.JFileChooser; import javax.swing.JOptionPane; import javax.swing.filechooser.FileSystemView; @@ -85,11 +85,11 @@ import org.infinity.resource.video.WbmResource; import org.infinity.resource.wed.WedResource; import org.infinity.resource.wmp.WmpResource; -import org.infinity.util.StaticSimpleXorDecryptor; import org.infinity.util.CreMapCache; import org.infinity.util.DynamicArray; import org.infinity.util.IdsMapCache; import org.infinity.util.Misc; +import org.infinity.util.StaticSimpleXorDecryptor; import org.infinity.util.io.FileEx; import org.infinity.util.io.FileManager; import org.infinity.util.io.FileWatcher; @@ -783,14 +783,16 @@ static Path getHomeRoot(boolean allowMissing) try { Process p = Runtime.getRuntime().exec("reg query \"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders\" /v personal"); p.waitFor(); - InputStream in = p.getInputStream(); - byte[] b = new byte[in.available()]; - in.read(b); - in.close(); + byte[] b; + try (InputStream in = p.getInputStream()) { + b = new byte[in.available()]; + in.read(b); + } String[] splitted = new String(b).split("\\s\\s+"); userPrefix = splitted[splitted.length-1]; userPath = FileManager.resolve(userPrefix, EE_DIR); } catch (Throwable t) { + t.printStackTrace(); return null; } } else if (osName.contains("mac") || osName.contains("darwin")) { diff --git a/src/org/infinity/resource/graphics/BamDecoder.java b/src/org/infinity/resource/graphics/BamDecoder.java index ca45111e0..6a605fff4 100644 --- a/src/org/infinity/resource/graphics/BamDecoder.java +++ b/src/org/infinity/resource/graphics/BamDecoder.java @@ -46,20 +46,16 @@ public static Type getType(ResourceEntry bamEntry) { Type retVal = Type.INVALID; if (bamEntry != null) { - try { - InputStream is = bamEntry.getResourceDataAsStream(); - if (is != null) { - String signature = StreamUtils.readString(is, 4); - String version = StreamUtils.readString(is, 4); - is.close(); - if ("BAMC".equals(signature)) { - retVal = Type.BAMC; - } else if ("BAM ".equals(signature)) { - if ("V1 ".equals(version)) { - retVal = Type.BAMV1; - } else if ("V2 ".equals(version)) { - retVal = Type.BAMV2; - } + try (InputStream is = bamEntry.getResourceDataAsStream()) { + String signature = StreamUtils.readString(is, 4); + String version = StreamUtils.readString(is, 4); + if ("BAMC".equals(signature)) { + retVal = Type.BAMC; + } else if ("BAM ".equals(signature)) { + if ("V1 ".equals(version)) { + retVal = Type.BAMV1; + } else if ("V2 ".equals(version)) { + retVal = Type.BAMV2; } } } catch (Exception e) { diff --git a/src/org/infinity/resource/graphics/BamResource.java b/src/org/infinity/resource/graphics/BamResource.java index 3a77160b7..2c4885a2a 100644 --- a/src/org/infinity/resource/graphics/BamResource.java +++ b/src/org/infinity/resource/graphics/BamResource.java @@ -4,9 +4,6 @@ package org.infinity.resource.graphics; -import tv.porst.jhexview.DataChangedEvent; -import tv.porst.jhexview.IDataChangedListener; - import java.awt.AlphaComposite; import java.awt.BorderLayout; import java.awt.Color; @@ -85,6 +82,9 @@ import org.infinity.util.io.FileManager; import org.infinity.util.io.StreamUtils; +import tv.porst.jhexview.DataChangedEvent; +import tv.porst.jhexview.IDataChangedListener; + /** * This resource describes animated graphics. Such files are used for animations * (both {@link CreResource creature} animations, {@link ItmResource item} and @@ -1278,44 +1278,37 @@ public boolean containsPvrzReference(int index) { boolean retVal = false; if (index >= 0 && index <= 99999) { - try { - InputStream is = entry.getResourceDataAsStream(); - if (is != null) { - try { - // parsing resource header - byte[] sig = new byte[8]; - byte[] buf = new byte[24]; - long len; - long curOfs = 0; - if ((len = is.read(sig)) != sig.length) throw new Exception(); - if (!"BAM V2 ".equals(DynamicArray.getString(sig, 0, 8))) throw new Exception(); - curOfs += len; - if ((len = is.read(buf)) != buf.length) throw new Exception(); - curOfs += len; - int numBlocks = DynamicArray.getInt(buf, 8); - int ofsBlocks = DynamicArray.getInt(buf, 20); - curOfs = ofsBlocks - curOfs; - if (curOfs > 0) { - do { - len = is.skip(curOfs); - if (len <= 0) throw new Exception(); - curOfs -= len; - } while (curOfs > 0); - } + try (InputStream is = entry.getResourceDataAsStream()) { + // parsing resource header + byte[] sig = new byte[8]; + byte[] buf = new byte[24]; + long len; + long curOfs = 0; + if ((len = is.read(sig)) != sig.length) throw new Exception(); + if (!"BAM V2 ".equals(DynamicArray.getString(sig, 0, 8))) throw new Exception(); + curOfs += len; + if ((len = is.read(buf)) != buf.length) throw new Exception(); + curOfs += len; + int numBlocks = DynamicArray.getInt(buf, 8); + int ofsBlocks = DynamicArray.getInt(buf, 20); + curOfs = ofsBlocks - curOfs; + if (curOfs > 0) { + do { + len = is.skip(curOfs); + if (len <= 0) throw new Exception(); + curOfs -= len; + } while (curOfs > 0); + } - // parsing blocks - buf = new byte[28]; - for (int i = 0; i < numBlocks && !retVal; i++) { - if (is.read(buf) != buf.length) throw new Exception(); - int curIndex = DynamicArray.getInt(buf, 0); - retVal = (curIndex == index); - } - } finally { - is.close(); - is = null; - } + // parsing blocks + buf = new byte[28]; + for (int i = 0; i < numBlocks && !retVal; i++) { + if (is.read(buf) != buf.length) throw new Exception(); + int curIndex = DynamicArray.getInt(buf, 0); + retVal = (curIndex == index); } } catch (Exception e) { + e.printStackTrace(); } } return retVal; diff --git a/src/org/infinity/resource/graphics/MosDecoder.java b/src/org/infinity/resource/graphics/MosDecoder.java index 930681d06..7c09907ca 100644 --- a/src/org/infinity/resource/graphics/MosDecoder.java +++ b/src/org/infinity/resource/graphics/MosDecoder.java @@ -39,20 +39,17 @@ public static Type getType(ResourceEntry mosEntry) { Type retVal = Type.INVALID; if (mosEntry != null) { - try { - InputStream is = mosEntry.getResourceDataAsStream(); - if (is != null) { - String signature = StreamUtils.readString(is, 4); - String version = StreamUtils.readString(is, 4); - is.close(); - if ("MOSC".equals(signature)) { - retVal = Type.MOSC; - } else if ("MOS ".equals(signature)) { - if ("V1 ".equals(version)) { - retVal = Type.MOSV1; - } else if ("V2 ".equals(version)) { - retVal = Type.MOSV2; - } + try (InputStream is = mosEntry.getResourceDataAsStream()) { + String signature = StreamUtils.readString(is, 4); + String version = StreamUtils.readString(is, 4); + is.close(); + if ("MOSC".equals(signature)) { + retVal = Type.MOSC; + } else if ("MOS ".equals(signature)) { + if ("V1 ".equals(version)) { + retVal = Type.MOSV1; + } else if ("V2 ".equals(version)) { + retVal = Type.MOSV2; } } } catch (Exception e) { diff --git a/src/org/infinity/resource/graphics/MosResource.java b/src/org/infinity/resource/graphics/MosResource.java index b308ca545..bd703241d 100644 --- a/src/org/infinity/resource/graphics/MosResource.java +++ b/src/org/infinity/resource/graphics/MosResource.java @@ -592,44 +592,37 @@ public boolean containsPvrzReference(int index) { boolean retVal = false; if (index >= 0 && index <= 99999) { - try { - InputStream is = entry.getResourceDataAsStream(); - if (is != null) { - try { - // parsing resource header - byte[] sig = new byte[8]; - byte[] buf = new byte[16]; - long len; - long curOfs = 0; - if ((len = is.read(sig)) != sig.length) throw new Exception(); - if (!"MOS V2 ".equals(DynamicArray.getString(sig, 0, 8))) throw new Exception(); - curOfs += len; - if ((len = is.read(buf)) != buf.length) throw new Exception(); - curOfs += len; - int numBlocks = DynamicArray.getInt(buf, 8); - int ofsBlocks = DynamicArray.getInt(buf, 12); - curOfs = ofsBlocks - curOfs; - if (curOfs > 0) { - do { - len = is.skip(curOfs); - if (len <= 0) throw new Exception(); - curOfs -= len; - } while (curOfs > 0); - } + try (InputStream is = entry.getResourceDataAsStream()) { + // parsing resource header + byte[] sig = new byte[8]; + byte[] buf = new byte[16]; + long len; + long curOfs = 0; + if ((len = is.read(sig)) != sig.length) throw new Exception(); + if (!"MOS V2 ".equals(DynamicArray.getString(sig, 0, 8))) throw new Exception(); + curOfs += len; + if ((len = is.read(buf)) != buf.length) throw new Exception(); + curOfs += len; + int numBlocks = DynamicArray.getInt(buf, 8); + int ofsBlocks = DynamicArray.getInt(buf, 12); + curOfs = ofsBlocks - curOfs; + if (curOfs > 0) { + do { + len = is.skip(curOfs); + if (len <= 0) throw new Exception(); + curOfs -= len; + } while (curOfs > 0); + } - // parsing blocks - buf = new byte[28]; - for (int i = 0; i < numBlocks && !retVal; i++) { - if (is.read(buf) != buf.length) throw new Exception(); - int curIndex = DynamicArray.getInt(buf, 0); - retVal = (curIndex == index); - } - } finally { - is.close(); - is = null; - } + // parsing blocks + buf = new byte[28]; + for (int i = 0; i < numBlocks && !retVal; i++) { + if (is.read(buf) != buf.length) throw new Exception(); + int curIndex = DynamicArray.getInt(buf, 0); + retVal = (curIndex == index); } } catch (Exception e) { + e.printStackTrace(); } } return retVal; diff --git a/src/org/infinity/resource/graphics/PvrzResource.java b/src/org/infinity/resource/graphics/PvrzResource.java index 35e4ea472..b8a7533f8 100644 --- a/src/org/infinity/resource/graphics/PvrzResource.java +++ b/src/org/infinity/resource/graphics/PvrzResource.java @@ -100,8 +100,7 @@ public void actionPerformed(ActionEvent event) JOptionPane.ERROR_MESSAGE); } } else if (event.getSource() == miPNG) { - try { - ByteArrayOutputStream os = new ByteArrayOutputStream(); + try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { final String fileName = StreamUtils.replaceFileExtension(entry.getResourceName(), "PNG"); BufferedImage image = getImage(); if (ImageIO.write(image, "png", os)) { @@ -112,9 +111,6 @@ public void actionPerformed(ActionEvent event) "Error while exporting " + entry, "Error", JOptionPane.ERROR_MESSAGE); } - os.close(); - os = null; - image = null; } catch (Exception e) { e.printStackTrace(); } diff --git a/src/org/infinity/updater/Updater.java b/src/org/infinity/updater/Updater.java index fb4348ca7..dd59b7543 100644 --- a/src/org/infinity/updater/Updater.java +++ b/src/org/infinity/updater/Updater.java @@ -11,7 +11,12 @@ import java.net.Proxy; import java.net.URL; import java.nio.file.Path; -import java.util.*; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; import java.util.jar.JarFile; import java.util.prefs.Preferences; import java.util.zip.ZipEntry; @@ -160,21 +165,16 @@ static Calendar getJarFileDate() { String jarPath = Utils.getJarFileName(NearInfinity.class); if (jarPath != null && !jarPath.isEmpty()) { - try { - JarFile jf = new JarFile(jarPath); - try { - ZipEntry manifest = jf.getEntry("META-INF/MANIFEST.MF"); - if (manifest != null) { - Calendar cal = Calendar.getInstance(); - if (manifest.getTime() >= 0L) { - cal.setTimeInMillis(manifest.getTime()); - return cal; - } + try (JarFile jf = new JarFile(jarPath)) { + ZipEntry manifest = jf.getEntry("META-INF/MANIFEST.MF"); + if (manifest != null) { + Calendar cal = Calendar.getInstance(); + if (manifest.getTime() >= 0L) { + cal.setTimeInMillis(manifest.getTime()); + return cal; } - } finally { - jf.close(); } - } catch (Exception e) { + } catch (IOException e) { e.printStackTrace(); } } diff --git a/src/org/infinity/updater/Utils.java b/src/org/infinity/updater/Utils.java index 0bc13ccff..4a456d44c 100644 --- a/src/org/infinity/updater/Utils.java +++ b/src/org/infinity/updater/Utils.java @@ -4,6 +4,8 @@ package org.infinity.updater; +import static org.infinity.util.Misc.toNumber; + import java.awt.Desktop; import java.io.ByteArrayOutputStream; import java.io.File; @@ -48,8 +50,6 @@ import org.infinity.util.io.FileEx; -import static org.infinity.util.Misc.toNumber; - /** * Generic collection of updater-related methods. */ @@ -322,7 +322,6 @@ public static boolean isUrlAvailable(URL url, Proxy proxy) } } else { // more generic method - InputStream is = null; URLConnection conn = null; if (proxy != null) { conn = url.openConnection(proxy); @@ -330,9 +329,10 @@ public static boolean isUrlAvailable(URL url, Proxy proxy) conn = url.openConnection(); } if (conn != null) { - is = url.openStream(); - is.close(); - return true; + try (InputStream is = url.openStream()) { + return true; + } catch (IOException e) { + } } } } @@ -554,9 +554,8 @@ public static boolean downloadFromUrl(URL url, Proxy proxy, OutputStream os, Upd if (conn != null) { int timeout = conn.getConnectTimeout(); conn.setConnectTimeout(6000); // wait max. 6 seconds - InputStream is = conn.getInputStream(); - conn.setConnectTimeout(timeout); - try { + try (InputStream is = conn.getInputStream()) { + conn.setConnectTimeout(timeout); switch (type) { case ORIGINAL: return downloadRaw(is, os, url, proxy, listeners); @@ -567,9 +566,6 @@ public static boolean downloadFromUrl(URL url, Proxy proxy, OutputStream os, Upd case UNKNOWN: return false; } - } finally { - is.close(); - is = null; } } } @@ -624,9 +620,8 @@ static boolean downloadZip(InputStream is, OutputStream os, URL url, Proxy proxy throws IOException, ZipException { if (is != null && os != null) { - ZipInputStream zis = new ZipInputStream(is); - byte[] buffer = new byte[4096]; - try { + try (ZipInputStream zis = new ZipInputStream(is)) { + byte[] buffer = new byte[4096]; ZipEntry entry = zis.getNextEntry(); if (entry != null) { int totalSize = (int)entry.getSize(); @@ -644,10 +639,6 @@ static boolean downloadZip(InputStream is, OutputStream os, URL url, Proxy proxy fireProgressEvent(listeners, url, curSize, totalSize, true); return true; } - } finally { - zis.close(); - zis = null; - buffer = null; } } return false; diff --git a/src/org/infinity/util/StringTable.java b/src/org/infinity/util/StringTable.java index fcc802785..42f81b1f0 100644 --- a/src/org/infinity/util/StringTable.java +++ b/src/org/infinity/util/StringTable.java @@ -869,9 +869,7 @@ public static boolean exportTra(Path outFile, ProgressCallback callback) if (callback != null) { callback.init(tableMale._getNumEntries()); } boolean retVal = true; - PrintWriter writer = null; - try { - writer = new PrintWriter(outFile.toFile(), getCharset().name()); + try (PrintWriter writer = new PrintWriter(outFile.toFile(), getCharset().name())) { String newline = System.getProperty("line.separator"); // writing header String niPath = Utils.getJarFileName(NearInfinity.class); @@ -947,7 +945,6 @@ public static boolean exportTra(Path outFile, ProgressCallback callback) e.printStackTrace(); retVal = false; } finally { - if (writer != null) { writer.close(); } if (callback != null) { callback.done(retVal); } } @@ -1452,9 +1449,7 @@ private void _exportText(Path outFile, ProgressCallback callback) throws IOExcep synchronized (entries) { if (callback != null) { callback.init(_getNumEntries()); } boolean success = false; - PrintWriter writer = null; - try { - writer = new PrintWriter(outFile.toFile(), getCharset().name()); + try (PrintWriter writer = new PrintWriter(outFile.toFile(), getCharset().name())) { String newline = System.getProperty("line.separator"); for (int idx = 0; idx < _getNumEntries(); idx++) { if (callback != null) { @@ -1472,7 +1467,6 @@ private void _exportText(Path outFile, ProgressCallback callback) throws IOExcep success = false; throw e; } finally { - writer.close(); if (callback != null) { callback.done(success); } } } diff --git a/src/org/infinity/util/io/zip/DlcFileAttributes.java b/src/org/infinity/util/io/zip/DlcFileAttributes.java index af73305c3..6ca60cb1f 100644 --- a/src/org/infinity/util/io/zip/DlcFileAttributes.java +++ b/src/org/infinity/util/io/zip/DlcFileAttributes.java @@ -113,29 +113,29 @@ public byte[] comment() public String toString() { StringBuilder sb = new StringBuilder(1024); - Formatter fm = new Formatter(sb); - if (creationTime() != null) { - fm.format(" creationTime : %tc%n", creationTime().toMillis()); - } else { - fm.format(" creationTime : null%n"); + try (Formatter fm = new Formatter(sb)) { + if (creationTime() != null) { + fm.format(" creationTime : %tc%n", creationTime().toMillis()); + } else { + fm.format(" creationTime : null%n"); + } + + if (lastAccessTime() != null) { + fm.format(" lastAccessTime : %tc%n", lastAccessTime().toMillis()); + } else { + fm.format(" lastAccessTime : null%n"); + } + fm.format(" lastModifiedTime: %tc%n", lastModifiedTime().toMillis()); + fm.format(" isRegularFile : %b%n", isRegularFile()); + fm.format(" isDirectory : %b%n", isDirectory()); + fm.format(" isSymbolicLink : %b%n", isSymbolicLink()); + fm.format(" isOther : %b%n", isOther()); + fm.format(" fileKey : %s%n", fileKey()); + fm.format(" size : %d%n", size()); + fm.format(" compressedSize : %d%n", compressedSize()); + fm.format(" crc : %x%n", crc()); + fm.format(" method : %d%n", method()); } - - if (lastAccessTime() != null) { - fm.format(" lastAccessTime : %tc%n", lastAccessTime().toMillis()); - } else { - fm.format(" lastAccessTime : null%n"); - } - fm.format(" lastModifiedTime: %tc%n", lastModifiedTime().toMillis()); - fm.format(" isRegularFile : %b%n", isRegularFile()); - fm.format(" isDirectory : %b%n", isDirectory()); - fm.format(" isSymbolicLink : %b%n", isSymbolicLink()); - fm.format(" isOther : %b%n", isOther()); - fm.format(" fileKey : %s%n", fileKey()); - fm.format(" size : %d%n", size()); - fm.format(" compressedSize : %d%n", compressedSize()); - fm.format(" crc : %x%n", crc()); - fm.format(" method : %d%n", method()); - fm.close(); return sb.toString(); } } From b837bac5d554facedb7270714358906e9e91e9f7 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sat, 24 Apr 2021 14:54:14 +0200 Subject: [PATCH 149/158] Cosmetic (cleaned up sources) - Removed inferred generic types - Removed redundant super interfaces - Removed unnecessary typecasts and 'instanceof' checks - Added missing hashCode() when equals() was overridden - Added missing @Override annotations --- src/org/infinity/NearInfinity.java | 2 +- src/org/infinity/check/StructChecker.java | 2 +- src/org/infinity/datatype/PriTypeBitmap.java | 2 +- .../infinity/datatype/UnsignDecNumber.java | 2 +- src/org/infinity/gui/BookmarkEditor.java | 10 +++--- src/org/infinity/gui/BrowserMenuBar.java | 14 ++++---- src/org/infinity/gui/ButtonPanel.java | 2 +- src/org/infinity/gui/ButtonPopupMenu.java | 10 +++--- src/org/infinity/gui/ButtonPopupWindow.java | 2 +- src/org/infinity/gui/ColorGrid.java | 10 +++--- .../gui/FixedFocusTraversalPolicy.java | 2 +- src/org/infinity/gui/FontChooser.java | 11 ++++++ src/org/infinity/gui/GameProperties.java | 2 +- src/org/infinity/gui/InfinityAmp.java | 6 ++-- src/org/infinity/gui/InfinityTextArea.java | 2 +- src/org/infinity/gui/NewResSettings.java | 12 +++---- src/org/infinity/gui/QuickSearch.java | 9 +++-- src/org/infinity/gui/ResourceChooser.java | 3 +- src/org/infinity/gui/StructCellEditor.java | 6 ++++ src/org/infinity/gui/StructViewer.java | 6 ++-- src/org/infinity/gui/TileGrid.java | 6 ++-- src/org/infinity/gui/ViewerUtil.java | 4 +-- .../infinity/gui/converter/BamFilterBase.java | 2 +- .../gui/converter/BamFilterBaseColor.java | 4 +-- .../gui/converter/BamFilterFactory.java | 8 ++--- .../gui/converter/BamFilterOutputImage.java | 2 +- .../converter/BamFilterOutputSplitted.java | 4 +-- .../gui/converter/BamOptionsDialog.java | 2 +- .../gui/converter/BamPaletteDialog.java | 2 +- .../infinity/gui/converter/ConvertToBam.java | 24 ++++++------- .../infinity/gui/converter/ConvertToBmp.java | 10 +++--- .../infinity/gui/converter/ConvertToMos.java | 10 +++--- .../infinity/gui/converter/ConvertToPvrz.java | 8 ++--- .../infinity/gui/converter/ConvertToTis.java | 8 ++--- .../infinity/gui/hexview/BasicColorMap.java | 10 +++--- .../infinity/gui/hexview/FindDataDialog.java | 2 +- .../gui/hexview/ResourceDataProvider.java | 4 +-- .../gui/hexview/ResourceMenuCreator.java | 6 ++-- .../infinity/gui/hexview/StructHexViewer.java | 2 +- .../gui/hexview/VariableDataProvider.java | 2 +- src/org/infinity/icon/Icons.java | 2 +- src/org/infinity/resource/Effect.java | 2 +- src/org/infinity/resource/Effect2.java | 2 +- src/org/infinity/resource/EffectFactory.java | 26 +++++++------- src/org/infinity/resource/Profile.java | 20 +++++------ .../infinity/resource/ResourceFactory.java | 2 +- .../infinity/resource/StructureFactory.java | 34 +++++++++---------- .../resource/are/viewer/LayerManager.java | 2 +- .../resource/are/viewer/Settings.java | 2 +- .../resource/are/viewer/SettingsDialog.java | 4 +-- .../are/viewer/SharedResourceCache.java | 3 +- .../resource/are/viewer/TilesetRenderer.java | 14 ++++---- .../infinity/resource/bcs/BafResource.java | 2 +- .../infinity/resource/bcs/BcsResource.java | 4 +-- src/org/infinity/resource/bcs/ScriptInfo.java | 2 +- .../bcs/parser/BafNodeTransformer.java | 18 +++++----- src/org/infinity/resource/chu/Viewer.java | 14 ++++---- .../infinity/resource/cre/CreResource.java | 8 ++--- .../resource/cre/browser/MediaPanel.java | 4 +-- .../resource/cre/browser/SettingsPanel.java | 8 ++--- .../resource/cre/browser/icon/Icons.java | 2 +- .../resource/cre/decoder/SpriteDecoder.java | 11 +++--- .../cre/decoder/tables/SpriteTables.java | 4 +-- .../resource/cre/decoder/util/ItemInfo.java | 1 + src/org/infinity/resource/dlg/Viewer.java | 2 +- .../infinity/resource/gam/JournalEntry.java | 2 +- .../infinity/resource/gam/KillVariable.java | 3 +- src/org/infinity/resource/gam/PartyNPC.java | 4 +-- .../resource/graphics/BamResource.java | 10 +++--- .../resource/graphics/BamV1Decoder.java | 4 +-- .../resource/graphics/BamV2Decoder.java | 4 +-- .../resource/graphics/BlendingComposite.java | 2 ++ .../resource/graphics/ColorConvert.java | 2 +- .../resource/graphics/GifSequenceReader.java | 2 +- .../resource/graphics/GifSequenceWriter.java | 1 + .../resource/graphics/MosResource.java | 8 ++--- .../resource/graphics/PseudoBamDecoder.java | 30 ++++++++-------- .../resource/graphics/PvrDecoder.java | 2 +- .../resource/graphics/TisResource.java | 14 ++++---- src/org/infinity/resource/key/BIFFReader.java | 6 ++-- src/org/infinity/resource/key/BIFReader.java | 2 +- .../resource/key/FileResourceEntry.java | 1 + src/org/infinity/resource/key/Keyfile.java | 2 +- .../infinity/resource/key/ResourceEntry.java | 10 +++++- .../resource/key/ResourceTreeFolder.java | 6 ++-- .../resource/key/ResourceTreeModel.java | 2 +- src/org/infinity/resource/mus/Viewer.java | 4 +-- .../infinity/resource/other/FntResource.java | 3 +- .../infinity/resource/pro/ProAreaType.java | 2 +- .../infinity/resource/pro/ProResource.java | 2 +- .../infinity/resource/pro/ProSingleType.java | 2 +- .../infinity/resource/sound/WavBuffer.java | 2 +- src/org/infinity/resource/spl/Viewer.java | 4 +-- .../resource/text/modes/BCSFoldParser.java | 2 +- .../resource/text/modes/GLSLTokenMaker.java | 2 ++ .../resource/vef/AbstractComponent.java | 2 +- .../infinity/resource/video/AudioQueue.java | 2 +- .../infinity/resource/video/MveDecoder.java | 4 +-- .../infinity/resource/video/MveResource.java | 2 +- src/org/infinity/search/SearchFrame.java | 2 +- src/org/infinity/search/SearchOptions.java | 2 +- src/org/infinity/search/SearchResource.java | 28 +++++++-------- .../search/advanced/AdvancedSearchWorker.java | 4 +-- src/org/infinity/updater/UpdateInfo.java | 10 +++--- src/org/infinity/updater/Updater.java | 2 +- src/org/infinity/updater/UpdaterSettings.java | 6 ++-- src/org/infinity/util/BinPack2D.java | 4 +-- src/org/infinity/util/DataString.java | 4 +-- src/org/infinity/util/FilteredListModel.java | 6 ++-- src/org/infinity/util/IdsMap.java | 4 +-- src/org/infinity/util/IniMapCache.java | 2 +- src/org/infinity/util/IniMapEntry.java | 6 ++-- src/org/infinity/util/LuaParser.java | 6 ++-- src/org/infinity/util/MapTree.java | 14 ++++---- src/org/infinity/util/MassExporter.java | 2 +- src/org/infinity/util/ResourceStructure.java | 4 +-- src/org/infinity/util/SimpleListModel.java | 2 +- src/org/infinity/util/StringTable.java | 2 +- src/org/infinity/util/Table2da.java | 6 ++-- src/org/infinity/util/Table2daCache.java | 2 +- src/org/infinity/util/io/DlcManager.java | 4 +-- .../util/io/zip/DlcFileAttributes.java | 1 + .../infinity/util/io/zip/DlcFileStore.java | 2 +- .../infinity/util/io/zip/DlcFileSystem.java | 17 +++++----- src/org/infinity/util/io/zip/DlcPath.java | 22 +++++------- .../infinity/util/io/zip/ZipBaseHeader.java | 10 ++++++ .../util/io/zip/ZipCentralEndHeader.java | 28 +++++++++++++-- .../util/io/zip/ZipCentralHeader.java | 9 +++-- .../infinity/util/io/zip/ZipLocalHeader.java | 7 ++-- src/org/infinity/util/tuples/Couple.java | 8 ++--- src/org/infinity/util/tuples/Monuple.java | 8 ++--- src/org/infinity/util/tuples/Quadruple.java | 8 ++--- src/org/infinity/util/tuples/Quintuple.java | 8 ++--- src/org/infinity/util/tuples/Sextuple.java | 8 ++--- src/org/infinity/util/tuples/Triple.java | 8 ++--- src/org/infinity/util/tuples/Tuple.java | 3 +- 136 files changed, 455 insertions(+), 392 deletions(-) diff --git a/src/org/infinity/NearInfinity.java b/src/org/infinity/NearInfinity.java index 0fa032659..354e819dc 100644 --- a/src/org/infinity/NearInfinity.java +++ b/src/org/infinity/NearInfinity.java @@ -1111,7 +1111,7 @@ private void storePreferences() private void setAppIcon() { - List list = new ArrayList(); + List list = new ArrayList<>(); for (int i = 4; i < 8; i++) { list.add(Icons.getImage(String.format("App%d.png", 1 << i))); } diff --git a/src/org/infinity/check/StructChecker.java b/src/org/infinity/check/StructChecker.java index b114cd597..c00b7ed15 100644 --- a/src/org/infinity/check/StructChecker.java +++ b/src/org/infinity/check/StructChecker.java @@ -48,7 +48,7 @@ public final class StructChecker extends AbstractChecker implements ListSelectio { private static final String[] FILETYPES = {"ARE", "CHR", "CHU", "CRE", "DLG", "EFF", "GAM", "ITM", "PRO", "SPL", "STO", "VEF", "VVC", "WED", "WMP"}; - private static final HashMap fileInfo = new HashMap(); + private static final HashMap fileInfo = new HashMap<>(); static { fileInfo.put("ARE", new StructInfo("AREA", new String[]{"V1.0", "V9.1"})); fileInfo.put("CHR", new StructInfo("CHR ", new String[]{"V1.0", "V1.2", "V2.0", "V2.1", "V2.2", "V9.0"})); diff --git a/src/org/infinity/datatype/PriTypeBitmap.java b/src/org/infinity/datatype/PriTypeBitmap.java index 81c1ecd77..9253977db 100644 --- a/src/org/infinity/datatype/PriTypeBitmap.java +++ b/src/org/infinity/datatype/PriTypeBitmap.java @@ -23,7 +23,7 @@ public class PriTypeBitmap extends HashBitmap private static final String[] s_school = {"None", "Abjurer", "Conjurer", "Diviner", "Enchanter", "Illusionist", "Invoker", "Necromancer", "Transmuter", "Generalist"}; - private static final LongIntegerHashMap typeMap = new LongIntegerHashMap(); + private static final LongIntegerHashMap typeMap = new LongIntegerHashMap<>(); static { if (ResourceFactory.resourceExists("MSCHOOL.2DA")) { diff --git a/src/org/infinity/datatype/UnsignDecNumber.java b/src/org/infinity/datatype/UnsignDecNumber.java index 712b0705a..00f4f2ee1 100644 --- a/src/org/infinity/datatype/UnsignDecNumber.java +++ b/src/org/infinity/datatype/UnsignDecNumber.java @@ -6,7 +6,7 @@ import java.nio.ByteBuffer; -public class UnsignDecNumber extends DecNumber implements InlineEditable +public class UnsignDecNumber extends DecNumber { public UnsignDecNumber(ByteBuffer buffer, int offset, int length, String name) { diff --git a/src/org/infinity/gui/BookmarkEditor.java b/src/org/infinity/gui/BookmarkEditor.java index ad2ceb7f2..ed0587eb0 100644 --- a/src/org/infinity/gui/BookmarkEditor.java +++ b/src/org/infinity/gui/BookmarkEditor.java @@ -60,8 +60,8 @@ */ public class BookmarkEditor extends JDialog implements ActionListener, FocusListener, ListSelectionListener, ItemListener { - private final SimpleListModel modelEntries = new SimpleListModel(); - private final JList listEntries = new JList(modelEntries); + private final SimpleListModel modelEntries = new SimpleListModel<>(); + private final JList listEntries = new JList<>(modelEntries); private final JButton bUp = new JButton("Up"); private final JButton bDown = new JButton("Down"); private final JButton bRemove = new JButton("Remove"); @@ -73,12 +73,12 @@ public class BookmarkEditor extends JDialog implements ActionListener, FocusList private final JTextField tfName = new JTextField(); private final JTextField tfPath = createReadOnlyField(null, true); private final DefaultComboBoxModel cbPlatformModel = - new DefaultComboBoxModel(BrowserMenuBar.Bookmark.getSupportedOS()); + new DefaultComboBoxModel<>(BrowserMenuBar.Bookmark.getSupportedOS()); private final JComboBox cbPlatform = new JComboBox<>(cbPlatformModel); private final EnumMap> listBinPathModels = new EnumMap<>(Platform.OS.class); - private final JList listBinPaths = new JList(); + private final JList listBinPaths = new JList<>(); - private final List listBookmarks = new ArrayList(); + private final List listBookmarks = new ArrayList<>(); private boolean accepted; diff --git a/src/org/infinity/gui/BrowserMenuBar.java b/src/org/infinity/gui/BrowserMenuBar.java index 88a141ece..230129596 100644 --- a/src/org/infinity/gui/BrowserMenuBar.java +++ b/src/org/infinity/gui/BrowserMenuBar.java @@ -737,11 +737,11 @@ private static final class GameMenu extends JMenu implements ActionListener gameProperties, gameBookmarkAdd, gameBookmarkEdit, gameRecentClear; private final JMenu gameRecent = new JMenu("Recently opened games"); - private final List recentList = new ArrayList(); + private final List recentList = new ArrayList<>(); private final JPopupMenu.Separator gameRecentSeparator = new JPopupMenu.Separator(); private final JMenu gameBookmarks = new JMenu("Bookmarked games"); - private final List bookmarkList = new ArrayList(); + private final List bookmarkList = new ArrayList<>(); private final JPopupMenu.Separator gameBookmarkSeparator = new JPopupMenu.Separator(); private GameMenu() @@ -783,7 +783,7 @@ private GameMenu() String path = getPrefsProfiles().get(Bookmark.getBinaryPathKey(os, i), null); if (path != null) { if (binPaths == null) - binPaths = new EnumMap>(Platform.OS.class); + binPaths = new EnumMap<>(Platform.OS.class); List list = Bookmark.unpackBinPaths(os, path); binPaths.put(os, list); } @@ -1725,9 +1725,9 @@ public void windowClosing(WindowEvent e) else if (event.getSource() == dumpDebugInfo) { dumpDebugInfo(); } - else if (event.getSource() == toolCleanKeyfile) + else if (event.getSource() == toolCleanKeyfile) { // cleanKeyfile(); - ; + } else if (event.getSource() == toolDropZone) { ChildFrame.show(BcsDropFrame.class, () -> new BcsDropFrame()); } @@ -1782,7 +1782,7 @@ private static final class OptionsMenu extends JMenu implements ActionListener, new Font(Font.MONOSPACED, Font.PLAIN, 12), new Font(Font.SERIF, Font.PLAIN, 12), new Font(Font.SANS_SERIF, Font.PLAIN, 12), new Font(Font.DIALOG, Font.PLAIN, 12), null}; private static final String DefaultCharset = "Auto"; - private static final List CharsetsUsed = new ArrayList(); + private static final List CharsetsUsed = new ArrayList<>(); /** BCS indentations to use when decompiling (indent, title). */ private static final String[][] BCSINDENT = { {" ", "2 Spaces"}, {" ", "4 Spaces"}, @@ -1919,7 +1919,7 @@ private static final class OptionsMenu extends JMenu implements ActionListener, private int optionFixedInternal; /** Stores available languages in BG(2)EE. */ - private final HashMap gameLanguage = new HashMap(); + private final HashMap gameLanguage = new HashMap<>(); private OptionsMenu() { diff --git a/src/org/infinity/gui/ButtonPanel.java b/src/org/infinity/gui/ButtonPanel.java index 962892ad7..198844733 100644 --- a/src/org/infinity/gui/ButtonPanel.java +++ b/src/org/infinity/gui/ButtonPanel.java @@ -88,7 +88,7 @@ public enum Control { private static final int DefaultGapSize = 4; - private final List listControls = new ArrayList(); + private final List listControls = new ArrayList<>(); private int gapSize; diff --git a/src/org/infinity/gui/ButtonPopupMenu.java b/src/org/infinity/gui/ButtonPopupMenu.java index b54f591f4..aabe6e1bb 100644 --- a/src/org/infinity/gui/ButtonPopupMenu.java +++ b/src/org/infinity/gui/ButtonPopupMenu.java @@ -45,7 +45,7 @@ public int compare(JComponent item1, JComponent item2) private final ScrollPopupMenu menu = new ScrollPopupMenu(); private final PopupListener listener = new PopupListener(); private final PopupItemListener itemListener = new PopupItemListener(); - private List items = new ArrayList(); + private List items = new ArrayList<>(); private JMenuItem selected; private Align menuAlign; @@ -218,7 +218,7 @@ public void setMenuItems(JMenuItem[] menuItems) */ public void setMenuItems(JMenuItem[] menuItems, boolean sorted) { - List list = new ArrayList(); + List list = new ArrayList<>(); if (menuItems != null) { Collections.addAll(list, menuItems); } @@ -245,7 +245,7 @@ public void setMenuItems(List menuItems, boolean sorted) if (menuItems != null) { List preparedList; if (sorted) { - preparedList = new ArrayList(menuItems); + preparedList = new ArrayList<>(menuItems); Collections.sort(preparedList, menuItemComparator); } else { preparedList = menuItems; @@ -270,7 +270,7 @@ public List getItems() */ public List getMenuItems() { - List list = new ArrayList(items.size()); + List list = new ArrayList<>(items.size()); for (final JComponent c: items) { if (c instanceof JMenuItem) { list.add((JMenuItem)c); @@ -328,6 +328,7 @@ public void addSeparator() /** * Removes the menu item at the specified index from the popup menu. */ + @Override public void remove(int pos) { if (pos < 0 || pos >= items.size()) { @@ -341,6 +342,7 @@ public void remove(int pos) /** * Removes all items from the popup menu. */ + @Override public void removeAll() { for (int i = items.size() - 1; i >= 0; i--) { diff --git a/src/org/infinity/gui/ButtonPopupWindow.java b/src/org/infinity/gui/ButtonPopupWindow.java index 9ecb97292..5f01755db 100644 --- a/src/org/infinity/gui/ButtonPopupWindow.java +++ b/src/org/infinity/gui/ButtonPopupWindow.java @@ -56,7 +56,7 @@ public enum Align { } private final PopupWindow window = new PopupWindow(this); - private final List listeners = new ArrayList(); + private final List listeners = new ArrayList<>(); private PopupWindow ignoredWindow; // used to determine whether to hide the current window on lost focus private Align windowAlign; diff --git a/src/org/infinity/gui/ColorGrid.java b/src/org/infinity/gui/ColorGrid.java index a91be27fd..05b4ec13e 100644 --- a/src/org/infinity/gui/ColorGrid.java +++ b/src/org/infinity/gui/ColorGrid.java @@ -64,12 +64,12 @@ public enum Frame { SINGLE_LINE, DOUBLE_LINE } // Background pattern is visible for semi-transparent color entries private static final TexturePaint BackgroundPattern = createBackgroundPattern(); - private final List listColors = new ArrayList(); - private final List listActionListeners = new ArrayList(); - private final List listMouseOverListeners = new ArrayList(); - private final List listChangeListeners = new ArrayList(); + private final List listColors = new ArrayList<>(); + private final List listActionListeners = new ArrayList<>(); + private final List listMouseOverListeners = new ArrayList<>(); + private final List listChangeListeners = new ArrayList<>(); // stores selected color entry indices - private final List listSelection = new ArrayList(); + private final List listSelection = new ArrayList<>(); private Frame frame; // frame type private Color frameColor; // frame color diff --git a/src/org/infinity/gui/FixedFocusTraversalPolicy.java b/src/org/infinity/gui/FixedFocusTraversalPolicy.java index 1ef948435..856cc4b2f 100644 --- a/src/org/infinity/gui/FixedFocusTraversalPolicy.java +++ b/src/org/infinity/gui/FixedFocusTraversalPolicy.java @@ -14,7 +14,7 @@ */ public class FixedFocusTraversalPolicy extends FocusTraversalPolicy { - private final Vector order = new Vector(); + private final Vector order = new Vector<>(); private int defaultIndex; /** diff --git a/src/org/infinity/gui/FontChooser.java b/src/org/infinity/gui/FontChooser.java index f87c43a37..4e0543165 100644 --- a/src/org/infinity/gui/FontChooser.java +++ b/src/org/infinity/gui/FontChooser.java @@ -408,6 +408,7 @@ public int showDialog(Component parent) dialogResultValue = ERROR_OPTION; JDialog dialog = createDialog(parent); dialog.addWindowListener(new WindowAdapter() { + @Override public void windowClosing(WindowEvent e) { dialogResultValue = CANCEL_OPTION; @@ -432,6 +433,7 @@ protected class ListSelectionHandler implements ListSelectionListener this.textComponent = textComponent; } + @Override public void valueChanged(ListSelectionEvent e) { if (e.getValueIsAdjusting() == false) { @@ -459,11 +461,13 @@ public TextFieldFocusHandlerForTextSelection(JTextComponent textComponent) this.textComponent = textComponent; } + @Override public void focusGained(FocusEvent e) { textComponent.selectAll(); } + @Override public void focusLost(FocusEvent e) { textComponent.select(0, 0); @@ -480,6 +484,7 @@ public TextFieldKeyHandlerForListSelectionUpDown(JList list) this.targetList = list; } + @Override public void keyPressed(KeyEvent e) { int i = targetList.getSelectedIndex(); @@ -514,16 +519,19 @@ public ListSearchTextFieldDocumentHandler(JList targetList) this.targetList = targetList; } + @Override public void insertUpdate(DocumentEvent e) { update(e); } + @Override public void removeUpdate(DocumentEvent e) { update(e); } + @Override public void changedUpdate(DocumentEvent e) { update(e); @@ -564,6 +572,7 @@ public ListSelector(int index) this.index = index; } + @Override public void run() { targetList.setSelectedIndex(this.index); @@ -584,6 +593,7 @@ protected DialogOKAction(JDialog dialog) putValue(Action.NAME, (ACTION_NAME)); } + @Override public void actionPerformed(ActionEvent e) { dialogResultValue = OK_OPTION; @@ -604,6 +614,7 @@ protected DialogCancelAction(JDialog dialog) putValue(Action.NAME, (ACTION_NAME)); } + @Override public void actionPerformed(ActionEvent e) { dialogResultValue = CANCEL_OPTION; diff --git a/src/org/infinity/gui/GameProperties.java b/src/org/infinity/gui/GameProperties.java index 3877dafb2..526b37959 100644 --- a/src/org/infinity/gui/GameProperties.java +++ b/src/org/infinity/gui/GameProperties.java @@ -279,7 +279,7 @@ private void init() FlowLayout flow = new FlowLayout(FlowLayout.LEFT, 8, 4); JPanel pSupportList = new JPanel(flow); pSupportList.setBorder(BorderFactory.createLineBorder(Color.BLACK)); - List listTypes = new ArrayList(); + List listTypes = new ArrayList<>(); int maxWidth = 0, maxHeight = 0; // preparing entries for (Iterator iter = RES_TYPES.keySet().iterator(); iter.hasNext();) { diff --git a/src/org/infinity/gui/InfinityAmp.java b/src/org/infinity/gui/InfinityAmp.java index 985444620..86ca3ca07 100644 --- a/src/org/infinity/gui/InfinityAmp.java +++ b/src/org/infinity/gui/InfinityAmp.java @@ -44,8 +44,8 @@ public final class InfinityAmp extends ChildFrame implements ActionListener, ListSelectionListener, Runnable, Closeable { - private final SimpleListModel allMusModel = new SimpleListModel(); - private final SimpleListModel selectedMusModel = new SimpleListModel(); + private final SimpleListModel allMusModel = new SimpleListModel<>(); + private final SimpleListModel selectedMusModel = new SimpleListModel<>(); private final JButton bPlay = new JButton(Icons.getIcon(Icons.ICON_PLAY_16)); private final JButton bStop = new JButton(Icons.getIcon(Icons.ICON_STOP_16)); private final JButton bAdd = new JButton(Icons.getIcon(Icons.ICON_FORWARD_16)); @@ -57,7 +57,7 @@ public final class InfinityAmp extends ChildFrame private final JList selectedMusList; private final JTextField tfNowPlaying = new JTextField(10); private final AudioPlayer player = new AudioPlayer(); - private List entryList = new ArrayList(); + private List entryList = new ArrayList<>(); private boolean keepPlaying = true; public InfinityAmp() diff --git a/src/org/infinity/gui/InfinityTextArea.java b/src/org/infinity/gui/InfinityTextArea.java index e45a1da2e..90263022d 100644 --- a/src/org/infinity/gui/InfinityTextArea.java +++ b/src/org/infinity/gui/InfinityTextArea.java @@ -104,7 +104,7 @@ public enum Scheme { /** BCS color scheme based on WeiDU Highlighter for Notepad++ */ public static final String SchemeBCS = "org/infinity/resource/text/modes/ThemeBCSLight.xml"; - private static EnumMap SchemeMap = new EnumMap(Scheme.class); + private static EnumMap SchemeMap = new EnumMap<>(Scheme.class); static { // adding custom code folding definitions diff --git a/src/org/infinity/gui/NewResSettings.java b/src/org/infinity/gui/NewResSettings.java index 06599d985..72d5fcdcd 100644 --- a/src/org/infinity/gui/NewResSettings.java +++ b/src/org/infinity/gui/NewResSettings.java @@ -32,20 +32,20 @@ public final class NewResSettings extends NewAbstractSettings implements KeyList private enum GameType { UNKNOWN, BG2, IWD, IWD2, IWDEE } private static final EnumMap> STRREF_ITEM = - new EnumMap>(GameType.class); + new EnumMap<>(GameType.class); static { Vector list; // creating maps for unknown, BG2, IWD and IWD2 // initializing 'unknown' items - STRREF_ITEM.put(GameType.UNKNOWN, (list = new Vector())); + STRREF_ITEM.put(GameType.UNKNOWN, (list = new Vector<>())); list.add(new StrrefItem(-1, "User-defined biography")); // initializing BG2 items - STRREF_ITEM.put(GameType.BG2, (list = new Vector())); + STRREF_ITEM.put(GameType.BG2, (list = new Vector<>())); list.add(new StrrefItem(-1, "User-defined biography")); list.add(new StrrefItem(33347, "Biography of the protagonist")); list.add(new StrrefItem(15882, "Biography of a generic NPC")); // initializing IWD items - STRREF_ITEM.put(GameType.IWD, (list = new Vector())); + STRREF_ITEM.put(GameType.IWD, (list = new Vector<>())); list.add(new StrrefItem(-1, "User-defined biography")); list.add(new StrrefItem(19423, "Biography of a fighter")); list.add(new StrrefItem(19429, "Biography of a ranger")); @@ -56,7 +56,7 @@ private enum GameType { UNKNOWN, BG2, IWD, IWD2, IWDEE } list.add(new StrrefItem(19428, "Biography of a thief")); list.add(new StrrefItem(19425, "Biography of a bard")); // initializing IWD2 items - STRREF_ITEM.put(GameType.IWD2, (list = new Vector())); + STRREF_ITEM.put(GameType.IWD2, (list = new Vector<>())); list.add(new StrrefItem(-1, "User-defined biography")); list.add(new StrrefItem(27862, "Biography of a barbarian")); list.add(new StrrefItem(19425, "Biography of a bard")); @@ -70,7 +70,7 @@ private enum GameType { UNKNOWN, BG2, IWD, IWD2, IWDEE } list.add(new StrrefItem(27863, "Biography of a sorcerer")); list.add(new StrrefItem(19430, "Biography of a wizard")); // initializing IWDEE items - STRREF_ITEM.put(GameType.IWDEE, (list = new Vector())); + STRREF_ITEM.put(GameType.IWDEE, (list = new Vector<>())); list.add(new StrrefItem(-1, "User-defined biography")); list.add(new StrrefItem(19423, "Biography of a fighter")); list.add(new StrrefItem(19429, "Biography of a ranger")); diff --git a/src/org/infinity/gui/QuickSearch.java b/src/org/infinity/gui/QuickSearch.java index 5e51d3fb0..ef809105b 100644 --- a/src/org/infinity/gui/QuickSearch.java +++ b/src/org/infinity/gui/QuickSearch.java @@ -75,7 +75,7 @@ public QuickSearch(ButtonPopupWindow parent, ResourceTree tree) } this.parent = parent; this.tree = tree; - this.resourceTree = new MapTree>(Character.valueOf('\0'), null); + this.resourceTree = new MapTree<>(Character.valueOf('\0'), null); this.command = Command.IDLE; new Thread(this).start(); // updating list of matching resources is done in the background init(); @@ -256,7 +256,7 @@ private void generateRootNode() if (list != null) { list.clear(); } else { - list = new Vector(); + list = new Vector<>(); resourceTree.setValue(list); } @@ -274,7 +274,7 @@ private void generateRootNode() private SortedSet generateResourceList(ResourceTreeFolder folder, SortedSet set) { if (set == null) { - set = new TreeSet(); + set = new TreeSet<>(); } if (folder != null) { @@ -309,8 +309,7 @@ private MapTree> generateNode(MapTree()); } } else { - retVal = new MapTree>(Character.valueOf(ch), - new Vector()); + retVal = new MapTree<>(Character.valueOf(ch), new Vector()); } node.addChild(retVal); diff --git a/src/org/infinity/gui/ResourceChooser.java b/src/org/infinity/gui/ResourceChooser.java index 32a3bdeb9..2b72bd30b 100644 --- a/src/org/infinity/gui/ResourceChooser.java +++ b/src/org/infinity/gui/ResourceChooser.java @@ -214,8 +214,7 @@ private void init(String initialExtension) private void resetResourceList(String ext) { - final List resources = (ext != null) ? ResourceFactory.getResources(ext) - : new ArrayList(); + final List resources = (ext != null) ? ResourceFactory.getResources(ext) : new ArrayList<>(); if (lpResources != null) { // switching type in existing list panel RootPaneContainer rpc = (RootPaneContainer)SwingUtilities.getAncestorOfClass(RootPaneContainer.class, this); diff --git a/src/org/infinity/gui/StructCellEditor.java b/src/org/infinity/gui/StructCellEditor.java index 4c65da51d..e9ead60a3 100644 --- a/src/org/infinity/gui/StructCellEditor.java +++ b/src/org/infinity/gui/StructCellEditor.java @@ -85,6 +85,7 @@ public Object getCellEditorValue() * Forwards the message from the {@code CellEditor} to the {@code delegate}. * @see StructEditorDelegate#isCellEditable(EventObject) */ + @Override public boolean isCellEditable(EventObject anEvent) { return delegate.isCellEditable(anEvent); @@ -94,6 +95,7 @@ public boolean isCellEditable(EventObject anEvent) * Forwards the message from the {@code CellEditor} to the {@code delegate}. * @see StructEditorDelegate#shouldSelectCell(EventObject) */ + @Override public boolean shouldSelectCell(EventObject anEvent) { return delegate.shouldSelectCell(anEvent); @@ -103,6 +105,7 @@ public boolean shouldSelectCell(EventObject anEvent) * Forwards the message from the {@code CellEditor} to the {@code delegate}. * @see StructEditorDelegate#stopCellEditing */ + @Override public boolean stopCellEditing() { return delegate.stopCellEditing(); @@ -112,6 +115,7 @@ public boolean stopCellEditing() * Forwards the message from the {@code CellEditor} to the {@code delegate}. * @see StructEditorDelegate#cancelCellEditing */ + @Override public void cancelCellEditing() { delegate.cancelCellEditing(); @@ -234,6 +238,7 @@ public void cancelCellEditing() * @param e the action event * @see #stopCellEditing */ + @Override public void actionPerformed(ActionEvent e) { StructCellEditor.this.stopCellEditing(); @@ -244,6 +249,7 @@ public void actionPerformed(ActionEvent e) * @param e the action event * @see #stopCellEditing */ + @Override public void itemStateChanged(ItemEvent e) { StructCellEditor.this.stopCellEditing(); diff --git a/src/org/infinity/gui/StructViewer.java b/src/org/infinity/gui/StructViewer.java index 723c1b1c4..197e25638 100644 --- a/src/org/infinity/gui/StructViewer.java +++ b/src/org/infinity/gui/StructViewer.java @@ -102,9 +102,9 @@ import org.infinity.search.advanced.SearchOptions; import org.infinity.util.Misc; import org.infinity.util.StructClipboard; -import org.infinity.util.tuples.Couple; import org.infinity.util.io.ByteBufferOutputStream; import org.infinity.util.io.StreamUtils; +import org.infinity.util.tuples.Couple; public final class StructViewer extends JPanel implements ListSelectionListener, ActionListener, ItemListener, ChangeListener, TableModelListener, @@ -801,7 +801,7 @@ public void valueChanged(ListSelectionEvent event) selected instanceof TextBitmap) && !(selected instanceof AbstractCode)); miReset.setEnabled(isDataType && isReadable && - getCachedStructEntry(((Datatype)selected).getOffset()) instanceof Readable && + getCachedStructEntry(((Datatype)selected).getOffset()) != null && !(selected instanceof AbstractCode)); miAddToAdvSearch.setEnabled(!(selected instanceof AbstractStruct || selected instanceof Unknown)); miGotoOffset.setEnabled(selected instanceof SectionOffset|| selected instanceof SectionCount); @@ -1251,7 +1251,7 @@ private void convertAttribute(int index, JMenuItem menuitem) newentry = new TextString(bb, 0, entry.getSize(), entry.getName()); } else if (menuitem == miReset) { newentry = removeCachedStructEntry(entry.getOffset()); - if (newentry == null || !(newentry instanceof Readable)) { + if (newentry == null) { newentry = entry; } else { ((Readable)newentry).read(bb, 0); diff --git a/src/org/infinity/gui/TileGrid.java b/src/org/infinity/gui/TileGrid.java index 9910985e9..43eab863b 100644 --- a/src/org/infinity/gui/TileGrid.java +++ b/src/org/infinity/gui/TileGrid.java @@ -229,7 +229,7 @@ public Image getImage(int index) throws IndexOutOfBoundsException */ public List getImageList() { - return new ArrayList(imageList); + return new ArrayList<>(imageList); } /** @@ -608,8 +608,8 @@ private void init(int rows, int cols, int tw, int th) this.tileWidth = Math.max(tw, 1); this.tileHeight = Math.max(th, 1); this.bgColor = null; - this.imageList = new ArrayList(1); - this.tileList = new ArrayList(); + this.imageList = new ArrayList<>(1); + this.tileList = new ArrayList<>(); this.tileBorder = null; this.bShowGrid = false; this.bShowIcons = true; diff --git a/src/org/infinity/gui/ViewerUtil.java b/src/org/infinity/gui/ViewerUtil.java index ec407e73a..d9b6cf459 100644 --- a/src/org/infinity/gui/ViewerUtil.java +++ b/src/org/infinity/gui/ViewerUtil.java @@ -55,10 +55,10 @@ import org.infinity.resource.StructEntry; import org.infinity.resource.Viewable; import org.infinity.resource.graphics.BamDecoder; +import org.infinity.resource.graphics.BamDecoder.BamControl; import org.infinity.resource.graphics.BamResource; import org.infinity.resource.graphics.GraphicsResource; import org.infinity.resource.graphics.MosResource; -import org.infinity.resource.graphics.BamDecoder.BamControl; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.Misc; import org.infinity.util.SimpleListModel; @@ -405,7 +405,7 @@ public static final class StructListPanel extends JPanel implements TableModelLi private final AbstractStruct struct; private final Class listClass; private final JList list; - private final SimpleListModel listModel = new SimpleListModel(); + private final SimpleListModel listModel = new SimpleListModel<>(); private final JButton bOpen = new JButton("View/Edit", Icons.getIcon(Icons.ICON_ZOOM_16)); private StructListPanel(String title, AbstractStruct struct, diff --git a/src/org/infinity/gui/converter/BamFilterBase.java b/src/org/infinity/gui/converter/BamFilterBase.java index 966985966..9ec457448 100644 --- a/src/org/infinity/gui/converter/BamFilterBase.java +++ b/src/org/infinity/gui/converter/BamFilterBase.java @@ -31,7 +31,7 @@ public enum Type { private final ConvertToBam converter; private final String name, description; private final Type type; - private final List listChangeListeners = new ArrayList(); + private final List listChangeListeners = new ArrayList<>(); private final JPanel controls; protected BamFilterBase(ConvertToBam converter, String name, String desc, Type type) diff --git a/src/org/infinity/gui/converter/BamFilterBaseColor.java b/src/org/infinity/gui/converter/BamFilterBaseColor.java index 1ded3c02b..621e413d2 100644 --- a/src/org/infinity/gui/converter/BamFilterBaseColor.java +++ b/src/org/infinity/gui/converter/BamFilterBaseColor.java @@ -23,9 +23,9 @@ import javax.swing.event.ChangeListener; import org.infinity.gui.ColorGrid; -import org.infinity.gui.ViewerUtil; import org.infinity.gui.ColorGrid.MouseOverEvent; import org.infinity.gui.ColorGrid.MouseOverListener; +import org.infinity.gui.ViewerUtil; import org.infinity.util.Misc; /** @@ -105,7 +105,7 @@ public static class ExcludeColorsPanel extends JPanel } } - private final List listChangeListeners = new ArrayList(); + private final List listChangeListeners = new ArrayList<>(); private ColorGrid cgPalette; private JLabel lInfoIndex, lInfoRGB, lInfoHexRGB; diff --git a/src/org/infinity/gui/converter/BamFilterFactory.java b/src/org/infinity/gui/converter/BamFilterFactory.java index 077d0f983..b0633dfa8 100644 --- a/src/org/infinity/gui/converter/BamFilterFactory.java +++ b/src/org/infinity/gui/converter/BamFilterFactory.java @@ -11,7 +11,7 @@ public class BamFilterFactory { - private static final List FilterInfoList = new ArrayList(); + private static final List FilterInfoList = new ArrayList<>(); static { // Registering individual BAM filters @@ -101,7 +101,7 @@ public static FilterInfo getFilterInfo(String filterName) /** Returns a list of all class types compatible with the specified class. */ public static Collection> getFiltersOf(Class classType) { - Collection> retVal = new ArrayList>(); + Collection> retVal = new ArrayList<>(); if (classType != null) { for (int i = 0; i < FilterInfoList.size(); i++) { if (classType.isAssignableFrom(FilterInfoList.get(i).getFilterClass())) { @@ -136,8 +136,8 @@ public static BamFilterBase createInstance(ConvertToBam parent, Class normalizeFilterList(ConvertToBam parent, List filterList) { - List retList = new ArrayList(); - List tmpList = new ArrayList(); + List retList = new ArrayList<>(); + List tmpList = new ArrayList<>(); if (filterList != null) { for (int i = 0; i < filterList.size(); i++) { if (filterList.get(i) instanceof BamFilterBaseOutput) { diff --git a/src/org/infinity/gui/converter/BamFilterOutputImage.java b/src/org/infinity/gui/converter/BamFilterOutputImage.java index 5a49c1b39..1c106b9a5 100644 --- a/src/org/infinity/gui/converter/BamFilterOutputImage.java +++ b/src/org/infinity/gui/converter/BamFilterOutputImage.java @@ -133,7 +133,7 @@ protected JPanel loadControls() JLabel l1 = new JLabel("Image output format:"); JLabel l2 = new JLabel("Digits for frame index:"); - cbImageType = new JComboBox(new String[]{"PNG", "BMP"}); + cbImageType = new JComboBox<>(new String[]{"PNG", "BMP"}); cbImageType.setEditable(false); cbImageType.setPreferredSize(new Dimension(cbImageType.getPreferredSize().width + 16, cbImageType.getPreferredSize().height)); cbImageType.setSelectedIndex(0); diff --git a/src/org/infinity/gui/converter/BamFilterOutputSplitted.java b/src/org/infinity/gui/converter/BamFilterOutputSplitted.java index ac2d4a26f..ba4e10035 100644 --- a/src/org/infinity/gui/converter/BamFilterOutputSplitted.java +++ b/src/org/infinity/gui/converter/BamFilterOutputSplitted.java @@ -340,7 +340,7 @@ private boolean applyEffect(PseudoBamDecoder decoder) throws Exception } // calculating individual splits for each frame - List> listSegments = new ArrayList>(decoder.frameCount()); + List> listSegments = new ArrayList<>(decoder.frameCount()); int segmentCount = segmentsX*segmentsY; for (int frameIdx = 0; frameIdx < decoder.frameCount(); frameIdx++) { listSegments.add(new ArrayList(segmentCount)); @@ -386,7 +386,7 @@ private boolean applyEffect(PseudoBamDecoder decoder) throws Exception // for each segment... for (int segIdx = 0; segIdx < segmentCount; segIdx++) { // creating segmented frames list - List framesList = new ArrayList(decoder.getFramesList().size()); + List framesList = new ArrayList<>(decoder.getFramesList().size()); for (int i = 0; i < listSegments.size(); i++) { framesList.add(createFrameSegment(decoder.getFramesList().get(i), listSegments.get(i).get(segIdx))); } diff --git a/src/org/infinity/gui/converter/BamOptionsDialog.java b/src/org/infinity/gui/converter/BamOptionsDialog.java index e7b7eb4fd..4a9efcb27 100644 --- a/src/org/infinity/gui/converter/BamOptionsDialog.java +++ b/src/org/infinity/gui/converter/BamOptionsDialog.java @@ -418,7 +418,7 @@ private void init() cbUseAlpha.addItemListener(this); cbUseAlpha.setSelectedIndex(getUseAlpha()); itemStateChanged(new ItemEvent(cbUseAlpha, ItemEvent.ITEM_FIRST, cbUseAlpha.getSelectedItem(), ItemEvent.SELECTED)); - cbSortPalette = new JComboBox(ColorConvert.SortType.values()); + cbSortPalette = new JComboBox<>(ColorConvert.SortType.values()); cbSortPalette.setSelectedItem(getSortPalette()); cbCompressBam = new JCheckBox("Select \"Compress BAM\" by default", getCompressBam()); JPanel pBamV1 = new JPanel(new GridBagLayout()); diff --git a/src/org/infinity/gui/converter/BamPaletteDialog.java b/src/org/infinity/gui/converter/BamPaletteDialog.java index fcd8e54f9..869853ee3 100644 --- a/src/org/infinity/gui/converter/BamPaletteDialog.java +++ b/src/org/infinity/gui/converter/BamPaletteDialog.java @@ -69,7 +69,7 @@ class BamPaletteDialog extends JDialog private static final String FmtInfoHexRGB = "#%02X%02X%02X%02X"; // Stores all available color values of the current BAM and their number of occurence for faster palette creation - private final LinkedHashMap colorMap = new LinkedHashMap(); + private final LinkedHashMap colorMap = new LinkedHashMap<>(); private final int[][] palettes = new int[2][]; private ConvertToBam converter; diff --git a/src/org/infinity/gui/converter/ConvertToBam.java b/src/org/infinity/gui/converter/ConvertToBam.java index 8efefeb77..71c6600ee 100644 --- a/src/org/infinity/gui/converter/ConvertToBam.java +++ b/src/org/infinity/gui/converter/ConvertToBam.java @@ -100,12 +100,12 @@ import org.infinity.resource.Profile; import org.infinity.resource.ResourceFactory; import org.infinity.resource.graphics.BamDecoder; +import org.infinity.resource.graphics.BamDecoder.BamControl; import org.infinity.resource.graphics.BamV1Decoder; import org.infinity.resource.graphics.ColorConvert; import org.infinity.resource.graphics.DxtEncoder; import org.infinity.resource.graphics.GifSequenceReader; import org.infinity.resource.graphics.PseudoBamDecoder; -import org.infinity.resource.graphics.BamDecoder.BamControl; import org.infinity.resource.graphics.PseudoBamDecoder.PseudoBamControl; import org.infinity.resource.graphics.PseudoBamDecoder.PseudoBamCycleEntry; import org.infinity.resource.graphics.PseudoBamDecoder.PseudoBamFrameEntry; @@ -176,7 +176,7 @@ public class ConvertToBam extends ChildFrame // BamDecoder instance containing the final result of the current BAM structure private final PseudoBamDecoder bamDecoderFinal = new PseudoBamDecoder(); // Frame image lists (use BAM_ORIGINAL/BAM_FINAL constants for access) - private final List> listFrameEntries = new ArrayList>(2); + private final List> listFrameEntries = new ArrayList<>(2); // Frame entry used for preview in filter tab private final PseudoBamFrameEntry entryFilterPreview = new PseudoBamFrameEntry(null, 0, 0); // The palette dialog instance for BAM v1 export @@ -1637,7 +1637,7 @@ private JPanel createFiltersTab() GridBagConstraints c = new GridBagConstraints(); // creating "Filters" section - Vector filters = new Vector(); + Vector filters = new Vector<>(); for (int i = 0; i < BamFilterFactory.getFilterInfoSize(); i++) { filters.add(BamFilterFactory.getFilterInfo(i)); } @@ -1697,7 +1697,7 @@ private JPanel createFiltersTab() GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0); pFiltersDesc.add(taFiltersDesc, c); - modelFilters = new SimpleListModel(); + modelFilters = new SimpleListModel<>(); listFilters = new JList<>(modelFilters); listFilters.setCellRenderer(new IndexedCellRenderer()); listFilters.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); @@ -3881,7 +3881,7 @@ private DxtEncoder.DxtType getAutoDxtType() private List convert() { - List result = new Vector(2); + List result = new Vector<>(2); try { updateFilteredBamDecoder(getBamVersion(), false); List outList = createOutputFilterList(); @@ -3930,7 +3930,7 @@ private PseudoBamFrameEntry getFilteredBamFrame(int bamVersion, int frameIdx, bo // processing each filter that exists before the selected filter PseudoBamFrameEntry entry = entryFilterPreview; for (int i = 0; i < curFilterIdx; i++) { - if (modelFilters.get(i) instanceof BamFilterBase) { + if (modelFilters.get(i) != null) { BamFilterBase filter = modelFilters.get(i); entry = filter.updatePreview(entry); } @@ -4021,8 +4021,8 @@ private void updateFilteredBamDecoder(int bamVersion, boolean force) throws Exce /** Creates a sorted list including all selected filters in the post-processing tab. */ private List createFilterList(boolean includeOutputFilters) { - List retVal = new ArrayList(); - List outFilters = new ArrayList(); + List retVal = new ArrayList<>(); + List outFilters = new ArrayList<>(); for (int i = 0; i < modelFilters.size(); i++) { BamFilterBase filter = modelFilters.get(i); if (filter instanceof BamFilterBaseOutput) { @@ -4047,7 +4047,7 @@ private List createFilterList(boolean includeOutputFilters) /** Creates a list of selected output filters only. */ private List createOutputFilterList() { - List retVal = new ArrayList(); + List retVal = new ArrayList<>(); for (int i = 0; i < modelFilters.size(); i++) { if (modelFilters.get(i) instanceof BamFilterBaseOutput) { retVal.add((BamFilterBaseOutput)modelFilters.get(i)); @@ -4101,7 +4101,7 @@ private void updateFinalBamDecoder(int bamVersion) throws Exception if (transIndex < 0) { transIndex = 0; } - HashMap colorCache = new HashMap(4096); + HashMap colorCache = new HashMap<>(4096); for (int i = 0; i < palette.length; i++) { if (i != transIndex) { colorCache.put(Integer.valueOf(palette[i]), Byte.valueOf((byte)i)); @@ -4196,7 +4196,7 @@ private void updateFinalBamFrame(int bamVersion, int frameIdx) if (transIndex < 0) { transIndex = 0; } - HashMap colorCache = new HashMap(4096); + HashMap colorCache = new HashMap<>(4096); for (int i = 0; i < palette.length; i++) { if (i != transIndex) { colorCache.put(Integer.valueOf(palette[i]), Byte.valueOf((byte)i)); @@ -5401,7 +5401,7 @@ public SourceData(Path image) bam.getPaletteDialog().clear(); // applying frames - HashMap sourceMap = new HashMap(); + HashMap sourceMap = new HashMap<>(); for (int i = 0; i < frames.length; i++) { SourceFrame frame = frames[i]; SourceData data = sourceMap.get(frame.entry); diff --git a/src/org/infinity/gui/converter/ConvertToBmp.java b/src/org/infinity/gui/converter/ConvertToBmp.java index 91a119f6e..c77a50b5a 100644 --- a/src/org/infinity/gui/converter/ConvertToBmp.java +++ b/src/org/infinity/gui/converter/ConvertToBmp.java @@ -18,9 +18,9 @@ import java.awt.image.DataBufferInt; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.io.File; import java.io.IOException; import java.io.OutputStream; -import java.io.File; import java.nio.ByteBuffer; import java.nio.file.DirectoryStream; import java.nio.file.Files; @@ -324,7 +324,7 @@ private void init() GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); pInputButtons.add(pRemove, c); - modelInputFiles = new SimpleListModel(); + modelInputFiles = new SimpleListModel<>(); listInputFiles = new JList<>(modelInputFiles); JScrollPane scroll = new JScrollPane(listInputFiles); JPanel pInputFrame = new JPanel(new GridBagLayout()); @@ -482,7 +482,7 @@ private void inputAdd() } Path[] files = getOpenFileName(this, "Choose file(s)", rootPath, true, getGraphicsFilters(), 0); if (files != null) { - List skippedFiles = new ArrayList(); + List skippedFiles = new ArrayList<>(); int idx = listInputFiles.getSelectedIndex() + 1; for (final Path file: files) { if (isValidInput(file)) { @@ -524,7 +524,7 @@ private void inputAddFolder() if (path != null && FileEx.create(path).isDirectory()) { // adding all files in the directory FileNameExtensionFilter[] filters = getGraphicsFilters(); - List skippedFiles = new ArrayList(); + List skippedFiles = new ArrayList<>(); int idx = listInputFiles.getSelectedIndex() + 1; try (DirectoryStream dstream = Files.newDirectoryStream(path)) { for (final Path file: dstream) { @@ -610,7 +610,7 @@ private void setOutput() private List convert() { - List result = new ArrayList(2); + List result = new ArrayList<>(2); final String progressMsg = "Converting file %d / %d"; int progressIdx = 0, progressMax = modelInputFiles.size() + 1; ProgressMonitor progress = new ProgressMonitor(this, "Converting files...", "Preparing", 0, progressMax); diff --git a/src/org/infinity/gui/converter/ConvertToMos.java b/src/org/infinity/gui/converter/ConvertToMos.java index 98959f23e..57af8805f 100644 --- a/src/org/infinity/gui/converter/ConvertToMos.java +++ b/src/org/infinity/gui/converter/ConvertToMos.java @@ -139,7 +139,7 @@ public static boolean convertV1(Component parent, BufferedImage img, String mosF } // creating list of tiles as int[] arrays - List tileList = new ArrayList(cols*rows); + List tileList = new ArrayList<>(cols*rows); for (int y = 0; y < rows; y++) { for (int x = 0; x < cols; x++) { int tileX = x * 64; @@ -157,7 +157,7 @@ public static boolean convertV1(Component parent, BufferedImage img, String mosF byte[] tilePalette = new byte[1024]; byte[] tileData = new byte[64*64]; int curPalOfs = palOfs, curTableOfs = tableOfs, curDataOfs = dataOfs; - IntegerHashMap colorCache = new IntegerHashMap(1536); // caching RGBColor -> index + IntegerHashMap colorCache = new IntegerHashMap<>(1536); // caching RGBColor -> index for (int tileIdx = 0; tileIdx < tileList.size(); tileIdx++) { colorCache.clear(); if (showProgress) { @@ -287,8 +287,8 @@ public static boolean convertV2(Component parent, BufferedImage img, String mosF ProgressMonitor progress = null; int width = img.getWidth(); int height = img.getHeight(); - List pageList = new ArrayList(); - List entryList = new ArrayList(); + List pageList = new ArrayList<>(); + List entryList = new ArrayList<>(); try { if (showProgress) { @@ -972,7 +972,7 @@ private String getMosFileName(Path path) private List convert() { - List result = new Vector(2); + List result = new Vector<>(2); // validating input file Path inFile = FileManager.resolve(tfInputV1.getText()); diff --git a/src/org/infinity/gui/converter/ConvertToPvrz.java b/src/org/infinity/gui/converter/ConvertToPvrz.java index 995420dd9..4567a0421 100644 --- a/src/org/infinity/gui/converter/ConvertToPvrz.java +++ b/src/org/infinity/gui/converter/ConvertToPvrz.java @@ -542,14 +542,14 @@ private List convert() targetPath = FileManager.resolve(tfTargetDir.getText()); } if (!FileEx.create(targetPath).isDirectory()) { - List l = new Vector(2); + List l = new Vector<>(2); l.add(null); l.add("Invalid target directory specified. No conversion takes place."); return l; } if (lInputModel.isEmpty()) { - List l = new Vector(2); + List l = new Vector<>(2); l.add(null); l.add("No source file(s) specified. No conversion takes place."); return l; @@ -720,7 +720,7 @@ private List convert() if (progress.isCanceled()) { progress.close(); progress = null; - List l = new Vector(2); + List l = new Vector<>(2); l.add(null); l.add("Conversion cancelled."); return l; @@ -773,7 +773,7 @@ private List convert() progress = null; // constructing failure/success message - List l = new Vector(2); + List l = new Vector<>(2); StringBuilder sb = new StringBuilder(); if (warnings == 0 && errors == 0) { sb.append("Conversion finished successfully."); diff --git a/src/org/infinity/gui/converter/ConvertToTis.java b/src/org/infinity/gui/converter/ConvertToTis.java index 4577f7d80..95404db77 100644 --- a/src/org/infinity/gui/converter/ConvertToTis.java +++ b/src/org/infinity/gui/converter/ConvertToTis.java @@ -150,7 +150,7 @@ public static boolean convertV1(Component parent, BufferedImage img, String tisF progress.setMillisToPopup(0); } - IntegerHashMap colorCache = new IntegerHashMap(2048); // caching RGBColor -> index + IntegerHashMap colorCache = new IntegerHashMap<>(2048); // caching RGBColor -> index for (int tileIdx = 0; tileIdx < tileCount; tileIdx++) { if (showProgress) { if (progress.isCanceled()) { @@ -293,8 +293,8 @@ public static boolean convertV2(Component parent, BufferedImage img, String tisF // preparing variables ProgressMonitor progress = null; - List pageList = new ArrayList(); - List entryList = new ArrayList(tileCount); + List pageList = new ArrayList<>(); + List entryList = new ArrayList<>(tileCount); byte[] dst = new byte[24 + tileCount*12]; // header + tiles int dstOfs = 0; // current start offset for write operations @@ -1068,7 +1068,7 @@ private boolean validateInput(String inputFile) // Return value: First list element is used for success message, second element for error message. private List convert() { - List ret = new Vector(2); + List ret = new Vector<>(2); // validating input file Path inFile = FileManager.resolve(inFileName); diff --git a/src/org/infinity/gui/hexview/BasicColorMap.java b/src/org/infinity/gui/hexview/BasicColorMap.java index 789f8a042..95c5933ee 100644 --- a/src/org/infinity/gui/hexview/BasicColorMap.java +++ b/src/org/infinity/gui/hexview/BasicColorMap.java @@ -39,7 +39,7 @@ public enum Coloring { // Color definitions. Each entry consists of two slightly different color tones // that will be used alternately. - private static final EnumMap colorMap = new EnumMap(Coloring.class); + private static final EnumMap colorMap = new EnumMap<>(Coloring.class); static { // Populating color map @@ -86,8 +86,8 @@ public enum Coloring { // Contains color definitions for specific data types. // Works only on top-level datatypes that are preferably described by a section offset and count. - private final EnumMap typeMap = new EnumMap(Coloring.class); - private final MapEntry cachedColor = new MapEntry(); + private final EnumMap typeMap = new EnumMap<>(Coloring.class); + private final MapEntry cachedColor = new MapEntry<>(); private final AbstractStruct struct; private List listBlocks; @@ -156,7 +156,7 @@ public void reset() close(); if (listBlocks == null) { - listBlocks = new ArrayList(); + listBlocks = new ArrayList<>(); } if (!listBlocks.isEmpty()) { @@ -345,7 +345,7 @@ public int compare(ColoredBlock obj, ColoredBlock key) private class Structure { // only used if isTable = true - private final List structures = new ArrayList(); + private final List structures = new ArrayList<>(); private final Class classType; diff --git a/src/org/infinity/gui/hexview/FindDataDialog.java b/src/org/infinity/gui/hexview/FindDataDialog.java index b0166ff09..317865eed 100644 --- a/src/org/infinity/gui/hexview/FindDataDialog.java +++ b/src/org/infinity/gui/hexview/FindDataDialog.java @@ -251,7 +251,7 @@ public void actionPerformed(ActionEvent e) // Attempts to parse useful byte values from the specified text string private byte[] parseBytes(String text) { - List list = new ArrayList(); + List list = new ArrayList<>(); // parsing text string StringBuilder sb = new StringBuilder(); diff --git a/src/org/infinity/gui/hexview/ResourceDataProvider.java b/src/org/infinity/gui/hexview/ResourceDataProvider.java index feae67a32..9ce86803b 100644 --- a/src/org/infinity/gui/hexview/ResourceDataProvider.java +++ b/src/org/infinity/gui/hexview/ResourceDataProvider.java @@ -20,8 +20,8 @@ */ public class ResourceDataProvider implements IDataProvider { - private final ArrayList listeners = new ArrayList(); - private final HashMap modifiedMap = new HashMap(); + private final ArrayList listeners = new ArrayList<>(); + private final HashMap modifiedMap = new HashMap<>(); private final ResourceEntry entry; private int size; diff --git a/src/org/infinity/gui/hexview/ResourceMenuCreator.java b/src/org/infinity/gui/hexview/ResourceMenuCreator.java index 5f8f2f693..8510331bd 100644 --- a/src/org/infinity/gui/hexview/ResourceMenuCreator.java +++ b/src/org/infinity/gui/hexview/ResourceMenuCreator.java @@ -5,7 +5,6 @@ package org.infinity.gui.hexview; import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.List; @@ -18,13 +17,12 @@ import org.infinity.resource.AbstractStruct; import org.infinity.resource.StructEntry; -import tv.porst.jhexview.IMenuCreator; import tv.porst.jhexview.JHexView; /** * Provides a dynamic popupmenu for the StructHexViewer component. */ -public class ResourceMenuCreator extends MenuCreator implements IMenuCreator, ActionListener +public class ResourceMenuCreator extends MenuCreator { private final AbstractStruct struct; @@ -118,7 +116,7 @@ public AbstractStruct getStruct() // Creates a list of all structures containing the specified offset, starting from topmost level. private List createStructEntries(int offset) { - List list = new ArrayList(); + List list = new ArrayList<>(); if (getHexView().getData() instanceof StructuredDataProvider) { StructEntry curEntry = ((StructuredDataProvider)getHexView().getData()).getFieldAt(offset); if (curEntry != null) { diff --git a/src/org/infinity/gui/hexview/StructHexViewer.java b/src/org/infinity/gui/hexview/StructHexViewer.java index 332946c2f..21b9812e5 100644 --- a/src/org/infinity/gui/hexview/StructHexViewer.java +++ b/src/org/infinity/gui/hexview/StructHexViewer.java @@ -612,7 +612,7 @@ private void updatePanel(int offset) list.remove(0); } } else { - list = new ArrayList(); + list = new ArrayList<>(); } // removing invalid models and controls diff --git a/src/org/infinity/gui/hexview/VariableDataProvider.java b/src/org/infinity/gui/hexview/VariableDataProvider.java index cd1fecf20..b598afd2f 100644 --- a/src/org/infinity/gui/hexview/VariableDataProvider.java +++ b/src/org/infinity/gui/hexview/VariableDataProvider.java @@ -19,7 +19,7 @@ */ public class VariableDataProvider implements IDataProvider { - private final List listeners = new ArrayList(); + private final List listeners = new ArrayList<>(); private byte[] buffer; diff --git a/src/org/infinity/icon/Icons.java b/src/org/infinity/icon/Icons.java index a6734b0b2..8ef6faf70 100644 --- a/src/org/infinity/icon/Icons.java +++ b/src/org/infinity/icon/Icons.java @@ -82,7 +82,7 @@ public class Icons public static final String ICON_YELLOW_CIRCLE_20 = "YellowCircle20.gif"; public static final String ICON_ZOOM_16 = "Zoom16.gif"; - private static final Map ICONMAP = new HashMap(100); + private static final Map ICONMAP = new HashMap<>(100); /** * Returns an ImageIcon object of the specified graphics filename. diff --git a/src/org/infinity/resource/Effect.java b/src/org/infinity/resource/Effect.java index 962b537f4..d6b24d44e 100644 --- a/src/org/infinity/resource/Effect.java +++ b/src/org/infinity/resource/Effect.java @@ -119,7 +119,7 @@ public Object clone(boolean asV2) throws Exception retVal.setOffset(offset); ((AbstractStruct)retVal).realignStructOffsets(); } else { - retVal = (StructEntry)clone(); + retVal = clone(); } return retVal; diff --git a/src/org/infinity/resource/Effect2.java b/src/org/infinity/resource/Effect2.java index e0f02fadd..446539457 100644 --- a/src/org/infinity/resource/Effect2.java +++ b/src/org/infinity/resource/Effect2.java @@ -208,7 +208,7 @@ public Object clone(boolean asV1) throws Exception retVal.setOffset(offset); ((AbstractStruct)retVal).realignStructOffsets(); } else { - retVal = (StructEntry)clone(); + retVal = clone(); } return retVal; diff --git a/src/org/infinity/resource/EffectFactory.java b/src/org/infinity/resource/EffectFactory.java index 22deb4aaa..4fd7f8231 100644 --- a/src/org/infinity/resource/EffectFactory.java +++ b/src/org/infinity/resource/EffectFactory.java @@ -130,10 +130,10 @@ public static enum EffectEntry { } // contains IDS mappings for BGEE's opcode 319 "Item Usability" - public static final LongIntegerHashMap m_duration = new LongIntegerHashMap(); - public static final LongIntegerHashMap m_colorloc = new LongIntegerHashMap(); - public static final LongIntegerHashMap m_proj_iwd = new LongIntegerHashMap(); - public static final LongIntegerHashMap m_inctype = new LongIntegerHashMap(); + public static final LongIntegerHashMap m_duration = new LongIntegerHashMap<>(); + public static final LongIntegerHashMap m_colorloc = new LongIntegerHashMap<>(); + public static final LongIntegerHashMap m_proj_iwd = new LongIntegerHashMap<>(); + public static final LongIntegerHashMap m_inctype = new LongIntegerHashMap<>(); public static final String[] s_inctype = {"Increment", "Set", "Set % of"}; public static final String[] s_buttontype = { "Unknown", "Unknown", "Bard Song", "Cast Spell", "Find Traps", @@ -2104,7 +2104,7 @@ private String makeEffectParamsGeneric(Datatype parent, ByteBuffer buffer, int o if (Profile.getEngine() == Profile.Engine.PST || Profile.getEngine() == Profile.Engine.IWD2) { s.add(new DecNumber(buffer, offset + 4, 4, AbstractStruct.COMMON_UNUSED)); } else { - final LongIntegerHashMap idsmap = new LongIntegerHashMap(); + final LongIntegerHashMap idsmap = new LongIntegerHashMap<>(); idsmap.put(0L, "Charmed (neutral)"); idsmap.put(1L, "Charmed (hostile)"); idsmap.put(2L, "Dire charmed (neutral)"); @@ -2702,7 +2702,7 @@ private String makeEffectParamsGeneric(Datatype parent, ByteBuffer buffer, int o if (Profile.getEngine() == Profile.Engine.IWD || Profile.getEngine() == Profile.Engine.IWD2) { idsmap = m_proj_iwd; } else { - idsmap = new LongIntegerHashMap(); + idsmap = new LongIntegerHashMap<>(); idsmap.put(0L, "None"); idsmap.put(4L, "Arrow"); idsmap.put(9L, "Axe"); @@ -2958,7 +2958,7 @@ private String makeEffectParamsGeneric(Datatype parent, ByteBuffer buffer, int o case 140: // Casting glow { - final LongIntegerHashMap m_castglow = new LongIntegerHashMap(); + final LongIntegerHashMap m_castglow = new LongIntegerHashMap<>(); if (Profile.isEnhancedEdition()) { m_castglow.put(0L, "Use projectile"); } @@ -3660,7 +3660,7 @@ private String makeEffectParamsBG2(Datatype parent, ByteBuffer buffer, int offse case 237: // Set image type { - final LongIntegerHashMap map = new LongIntegerHashMap(); + final LongIntegerHashMap map = new LongIntegerHashMap<>(); map.put(0L, "Player1"); map.put(1L, "Player2"); map.put(2L, "Player3"); @@ -3695,7 +3695,7 @@ private String makeEffectParamsBG2(Datatype parent, ByteBuffer buffer, int offse case 241: // Control creature { - final LongIntegerHashMap map = new LongIntegerHashMap(); + final LongIntegerHashMap map = new LongIntegerHashMap<>(); map.put(0L, "Charmed (neutral)"); map.put(1L, "Charmed (hostile)"); map.put(2L, "Dire charmed (neutral)"); @@ -3975,7 +3975,7 @@ private String makeEffectParamsBG2(Datatype parent, ByteBuffer buffer, int offse case 303: // Backstab every hit s.add(new DecNumber(buffer, offset, 4, AbstractStruct.COMMON_UNUSED)); if (isTobEx) { - LongIntegerHashMap idsmap = new LongIntegerHashMap(); + LongIntegerHashMap idsmap = new LongIntegerHashMap<>(); idsmap.put(0L, "Normal conditions"); idsmap.put(1L, "Ignore visual state and position"); idsmap.put(2L, "Ignore visual state only"); @@ -4695,7 +4695,7 @@ private String makeEffectParamsPST(Datatype parent, ByteBuffer buffer, int offse case 195: // Tint screen { - final LongIntegerHashMap m_fadeType = new LongIntegerHashMap(); + final LongIntegerHashMap m_fadeType = new LongIntegerHashMap<>(); m_fadeType.put(0L, "Quick fade light->dark->light"); m_fadeType.put(1L, "Quick fade light->dark->light"); m_fadeType.put(2L, "Quick fade light->dark, instant fade light"); @@ -4988,7 +4988,7 @@ private String makeEffectParamsIWD(Datatype parent, ByteBuffer buffer, int offse case 263: // Evil turn undead { - final LongIntegerHashMap map = new LongIntegerHashMap(); + final LongIntegerHashMap map = new LongIntegerHashMap<>(); map.put(0L, "Charmed (neutral)"); map.put(1L, "Charmed (hostile)"); map.put(2L, "Dire charmed (neutral)"); @@ -5232,7 +5232,7 @@ private String makeEffectParamsIWD2(Datatype parent, ByteBuffer buffer, int offs case 263: // Evil turn undead { - final LongIntegerHashMap map = new LongIntegerHashMap(); + final LongIntegerHashMap map = new LongIntegerHashMap<>(); map.put(0L, "Charmed (neutral)"); map.put(1L, "Charmed (hostile)"); map.put(2L, "Dire charmed (neutral)"); diff --git a/src/org/infinity/resource/Profile.java b/src/org/infinity/resource/Profile.java index fbae5b449..8d9f8d7d0 100644 --- a/src/org/infinity/resource/Profile.java +++ b/src/org/infinity/resource/Profile.java @@ -432,13 +432,13 @@ public enum Key { // Container for Property entries private static final EnumMap properties = new EnumMap<>(Key.class); // Unique titles for all supported games - private static final EnumMap GAME_TITLE = new EnumMap(Game.class); + private static final EnumMap GAME_TITLE = new EnumMap<>(Game.class); // List of supported extra folders for all supported games - private static final EnumMap> GAME_EXTRA_FOLDERS = new EnumMap>(Game.class); + private static final EnumMap> GAME_EXTRA_FOLDERS = new EnumMap<>(Game.class); // List of supported saved game folders for all supported games - private static final EnumMap> GAME_SAVE_FOLDERS = new EnumMap>(Game.class); + private static final EnumMap> GAME_SAVE_FOLDERS = new EnumMap<>(Game.class); // Home folder name for Enhanced Edition Games - private static final EnumMap GAME_HOME_FOLDER = new EnumMap(Game.class); + private static final EnumMap GAME_HOME_FOLDER = new EnumMap<>(Game.class); // Set of resource extensions supported by Infinity Engine games private static final HashSet SUPPORTED_RESOURCE_TYPES = new HashSet<>(); private static final HashMap KNOWN_EQUIPPED_APPEARANCE = new HashMap<>(); @@ -972,7 +972,7 @@ public static String[] getAvailableResourceTypes() */ public static String[] getAvailableResourceTypes(boolean ignoreGame) { - ArrayList list = new ArrayList(); + ArrayList list = new ArrayList<>(); if (ignoreGame || (Boolean)getProperty(Key.IS_SUPPORTED_2DA)) { list.add("2DA"); } if (ignoreGame || @@ -1136,7 +1136,7 @@ public static Map getEquippedAppearanceMap() // space for "no type" codes.add(" "); - retVal = new TreeMap(); + retVal = new TreeMap<>(); for (final String code: codes) { String desc = KNOWN_EQUIPPED_APPEARANCE.get(code); if (desc != null) { @@ -1346,7 +1346,7 @@ private static void initStaticProperties() addEntry(Key.GET_GLOBAL_NEARINFINITY_VERSION, Type.STRING, NearInfinity.getVersion()); // setting list of supported games and associated data - List gameList = new ArrayList(); + List gameList = new ArrayList<>(); Collections.addAll(gameList, Game.values()); addEntry(Key.GET_GLOBAL_GAMES, Type.LIST, gameList); @@ -1371,7 +1371,7 @@ private static void initDefaultGameBinaries() { DEFAULT_GAME_BINARIES.clear(); EnumMap> osMap; - List emptyList = new ArrayList<>();; + List emptyList = new ArrayList<>(); List list; // BG1 & BG1TotSC (Windows) @@ -1687,7 +1687,7 @@ private void initGame() throws Exception Game game = null; // Preparing available root paths - List gameRoots = new ArrayList(); + List gameRoots = new ArrayList<>(); if (Profile.getGameRoot() != null) { gameRoots.add(Profile.getGameRoot()); } @@ -1984,7 +1984,7 @@ private void initRootDirs() addEntry(Key.GET_GAME_LANG_FOLDER, Type.PATH, langRoot); List langPaths = ResourceFactory.getAvailableGameLanguages(); addEntry(Key.GET_GAME_LANG_FOLDERS_AVAILABLE, Type.LIST, langPaths); - List languages = new ArrayList(langPaths.size()); + List languages = new ArrayList<>(langPaths.size()); langPaths.forEach((path) -> languages.add(path.getFileName().toString())); addEntry(Key.GET_GAME_LANG_FOLDER_NAMES_AVAILABLE, Type.LIST, languages); listRoots.add(langRoot); diff --git a/src/org/infinity/resource/ResourceFactory.java b/src/org/infinity/resource/ResourceFactory.java index d6401e773..edf5ed2f6 100644 --- a/src/org/infinity/resource/ResourceFactory.java +++ b/src/org/infinity/resource/ResourceFactory.java @@ -263,7 +263,7 @@ public static Resource getResource(ResourceEntry entry, String forcedExtension) if (cls != null) { Constructor con = cls.getConstructor(ResourceEntry.class); if (con != null) - res = (Resource)con.newInstance(entry); + res = con.newInstance(entry); } } catch (Exception e) { if (NearInfinity.getInstance() != null && !BrowserMenuBar.getInstance().ignoreReadErrors()) { diff --git a/src/org/infinity/resource/StructureFactory.java b/src/org/infinity/resource/StructureFactory.java index 08360b4d2..d83dbfbed 100644 --- a/src/org/infinity/resource/StructureFactory.java +++ b/src/org/infinity/resource/StructureFactory.java @@ -36,7 +36,7 @@ public static enum ResType { RES_VEF, RES_VVC, RES_WED, RES_WFX, RES_WMAP } - private static final EnumMap resExt = new EnumMap(ResType.class); + private static final EnumMap resExt = new EnumMap<>(ResType.class); private static StructureFactory sfactory; static { @@ -178,7 +178,7 @@ public ResourceStructure createStructure(ResType type, String fileName, Window p } } - private ResourceStructure create2DA() throws StructureException + private ResourceStructure create2DA() { ResourceStructure s_2da = new ResourceStructure(); final String s = normalizeString("2DA V1.0\n0\n COLUMN1\nROW1 0\n"); @@ -187,7 +187,7 @@ private ResourceStructure create2DA() throws StructureException return s_2da; } - private ResourceStructure createARE(String fileName) throws StructureException + private ResourceStructure createARE(String fileName) { ResourceStructure s_are = new ResourceStructure(); @@ -242,7 +242,7 @@ private ResourceStructure createARE(String fileName) throws StructureException return s_are; } - private ResourceStructure createBAF() throws StructureException + private ResourceStructure createBAF() { ResourceStructure s_baf = new ResourceStructure(); final String s = "// Empty BCS script" + Misc.LINE_SEPARATOR; @@ -251,7 +251,7 @@ private ResourceStructure createBAF() throws StructureException return s_baf; } - private ResourceStructure createBCS() throws StructureException + private ResourceStructure createBCS() { ResourceStructure s_bcs = new ResourceStructure(); final String s = normalizeString("SC\nSC\n"); @@ -295,7 +295,7 @@ private ResourceStructure createCHR(Window parent) throws StructureException return cancelOperation(); } - private ResourceStructure createCRE() throws StructureException + private ResourceStructure createCRE() { final String[] version = {"V1.0", "V1.2", "V2.2", "V9.0"}; final int[] ofs = {0x2d4, 0x378, 0, 0x33c}; @@ -388,7 +388,7 @@ private ResourceStructure createCRE() throws StructureException return s_cre; } - private ResourceStructure createEFF() throws StructureException + private ResourceStructure createEFF() { ResourceStructure s_eff = new ResourceStructure(); s_eff.add(ResourceStructure.ID_STRING, 4, "EFF "); // Signature @@ -400,7 +400,7 @@ private ResourceStructure createEFF() throws StructureException return s_eff; } - private ResourceStructure createIDS() throws StructureException + private ResourceStructure createIDS() { ResourceStructure s_ids = new ResourceStructure(); final String s = normalizeString("1\n0 Identifier1\n"); @@ -409,7 +409,7 @@ private ResourceStructure createIDS() throws StructureException return s_ids; } - private ResourceStructure createINI() throws StructureException + private ResourceStructure createINI() { // TODO: distinguish between games ResourceStructure s_ini = new ResourceStructure(); @@ -419,7 +419,7 @@ private ResourceStructure createINI() throws StructureException return s_ini; } - private ResourceStructure createITM() throws StructureException + private ResourceStructure createITM() { final String[] version = {"V1 ", "V1.1", "V2.0"}; final int[] ofs = {0x72, 0x9a, 0x82}; @@ -485,7 +485,7 @@ private ResourceStructure createRES(Window parent) throws StructureException return cancelOperation(); } - private ResourceStructure createSPL() throws StructureException + private ResourceStructure createSPL() { final String[] version = {"V1 ", "V2.0"}; final int[] ofs = {0x72, 0x82}; @@ -533,7 +533,7 @@ private ResourceStructure createSRC() throws StructureException return unsupportedFormat(ResType.RES_SRC); } - private ResourceStructure createSTO() throws StructureException + private ResourceStructure createSTO() { final String[] version = {"V1.0", "V1.1", "V9.0"}; final int[] ofs = {0x9c, 0x9c, 0xf0}; @@ -564,7 +564,7 @@ private ResourceStructure createSTO() throws StructureException return s_sto; } - private ResourceStructure createVEF() throws StructureException + private ResourceStructure createVEF() { ResourceStructure s_vef = new ResourceStructure(); s_vef.add(ResourceStructure.ID_STRING, 4, "VEF "); // Signature @@ -577,7 +577,7 @@ private ResourceStructure createVEF() throws StructureException return s_vef; } - private ResourceStructure createVVC() throws StructureException + private ResourceStructure createVVC() { ResourceStructure s_vvc = new ResourceStructure(); s_vvc.add(ResourceStructure.ID_STRING, 4, "VVC "); // Signature @@ -587,7 +587,7 @@ private ResourceStructure createVVC() throws StructureException return s_vvc; } - private ResourceStructure createWED() throws StructureException + private ResourceStructure createWED() { ResourceStructure s_wed = new ResourceStructure(); s_wed.add(ResourceStructure.ID_STRING, 4, "WED "); // Signature @@ -612,7 +612,7 @@ private ResourceStructure createWED() throws StructureException return s_wed; } - private ResourceStructure createWFX() throws StructureException + private ResourceStructure createWFX() { ResourceStructure s_wfx = new ResourceStructure(); s_wfx.add(ResourceStructure.ID_STRING, 4, "WFX "); // Signature @@ -622,7 +622,7 @@ private ResourceStructure createWFX() throws StructureException return s_wfx; } - private ResourceStructure createWMAP() throws StructureException + private ResourceStructure createWMAP() { ResourceStructure s_wmp = new ResourceStructure(); s_wmp.add(ResourceStructure.ID_STRING, 4, "WMAP"); // Signature diff --git a/src/org/infinity/resource/are/viewer/LayerManager.java b/src/org/infinity/resource/are/viewer/LayerManager.java index d0d138c9e..9d281b3b6 100644 --- a/src/org/infinity/resource/are/viewer/LayerManager.java +++ b/src/org/infinity/resource/are/viewer/LayerManager.java @@ -33,7 +33,7 @@ public final class LayerManager LayerType.WALL_POLY }; - private static final EnumMap LayerLabels = new EnumMap(LayerType.class); + private static final EnumMap LayerLabels = new EnumMap<>(LayerType.class); static { LayerLabels.put(LayerType.ACTOR, "Actors"); LayerLabels.put(LayerType.REGION, "Regions"); diff --git a/src/org/infinity/resource/are/viewer/Settings.java b/src/org/infinity/resource/are/viewer/Settings.java index 1849c083b..a9909170d 100644 --- a/src/org/infinity/resource/are/viewer/Settings.java +++ b/src/org/infinity/resource/are/viewer/Settings.java @@ -334,7 +334,7 @@ private static void validateSettings() public static List getDefaultLayerOrder() { - List list = new ArrayList(); + List list = new ArrayList<>(); Collections.addAll(list, DefaultLayerOrder); return list; } diff --git a/src/org/infinity/resource/are/viewer/SettingsDialog.java b/src/org/infinity/resource/are/viewer/SettingsDialog.java index 020fd6bc3..36f3085a8 100644 --- a/src/org/infinity/resource/are/viewer/SettingsDialog.java +++ b/src/org/infinity/resource/are/viewer/SettingsDialog.java @@ -266,7 +266,7 @@ private void init() settingsChanged = false; // Initializing layer items order - modelLayers = new SimpleListModel(); + modelLayers = new SimpleListModel<>(); listLayers = new JList<>(modelLayers); listLayers.setCellRenderer(new IndexedCellRenderer(1)); listLayers.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); @@ -490,7 +490,7 @@ private void init() JPanel pMiniMap = new JPanel(new GridBagLayout()); pMiniMap.setBorder(BorderFactory.createTitledBorder("Mini map opacity: ")); - Hashtable table = new Hashtable(); + Hashtable table = new Hashtable<>(); for (int i = 0; i <= 100; i+=25) { table.put(Integer.valueOf(i), new JLabel(String.format("%d%%", i))); } diff --git a/src/org/infinity/resource/are/viewer/SharedResourceCache.java b/src/org/infinity/resource/are/viewer/SharedResourceCache.java index ec7901005..49d471bcf 100644 --- a/src/org/infinity/resource/are/viewer/SharedResourceCache.java +++ b/src/org/infinity/resource/are/viewer/SharedResourceCache.java @@ -20,8 +20,7 @@ public static enum Type { ACTOR } - private static EnumMap> tables = - new EnumMap>(Type.class); + private static EnumMap> tables = new EnumMap<>(Type.class); static { for (final Type type : Type.values()) { diff --git a/src/org/infinity/resource/are/viewer/TilesetRenderer.java b/src/org/infinity/resource/are/viewer/TilesetRenderer.java index 6f5cde96f..830ef6aff 100644 --- a/src/org/infinity/resource/are/viewer/TilesetRenderer.java +++ b/src/org/infinity/resource/are/viewer/TilesetRenderer.java @@ -22,8 +22,8 @@ import org.infinity.gui.RenderCanvas; import org.infinity.resource.Profile; import org.infinity.resource.ResourceFactory; -import org.infinity.resource.graphics.GraphicsResource; import org.infinity.resource.graphics.ColorConvert; +import org.infinity.resource.graphics.GraphicsResource; import org.infinity.resource.graphics.TisDecoder; import org.infinity.resource.key.ResourceEntry; import org.infinity.resource.wed.Door; @@ -66,11 +66,11 @@ public enum RenderMode { public static final int LightingAdjustmentShift = 10; // use in place of division // keeps track of registered listener objects - private final List listChangeListener = new ArrayList(); + private final List listChangeListener = new ArrayList<>(); // graphics data for all tiles of each overlay - private final List listTilesets = new ArrayList(MaxOverlays); + private final List listTilesets = new ArrayList<>(MaxOverlays); // array of tile indices used for closed door states for each door structure - private final List listDoorTileIndices = new ArrayList(); + private final List listDoorTileIndices = new ArrayList<>(); private final BufferedImage workingTile = ColorConvert.createCompatibleImage(64, 64, true); // internally used for drawing tile graphics private WedResource wed; // current wed resource @@ -1084,11 +1084,11 @@ private void notifyChangeListeners() private static class Tileset { // graphics data for all tiles of this overlay (as int arrays of 64*64 pixels) - public final List listTileData = new ArrayList(); + public final List listTileData = new ArrayList<>(); // info structures for all tiles of this overlay - public final List listTiles = new ArrayList(); + public final List listTiles = new ArrayList<>(); // lists references to all tiles containing overlays from listTiles - public final List listOverlayTiles = new ArrayList(); + public final List listOverlayTiles = new ArrayList<>(); public int tilesX, tilesY; // stores number of tiles per row/column public boolean isTisPalette; // whether tileset is palette-based diff --git a/src/org/infinity/resource/bcs/BafResource.java b/src/org/infinity/resource/bcs/BafResource.java index 7c3e4b39f..649cf0e9d 100644 --- a/src/org/infinity/resource/bcs/BafResource.java +++ b/src/org/infinity/resource/bcs/BafResource.java @@ -186,7 +186,7 @@ public void itemStateChanged(ItemEvent event) java.util.List files = ResourceFactory.getResources("BAF"); new TextResourceSearcher(files, panel.getTopLevelAncestor()); } else if (bpmFind.getSelectedItem() == ifindthis) { - java.util.List files = new ArrayList(1); + java.util.List files = new ArrayList<>(1); files.add(entry); new TextResourceSearcher(files, panel.getTopLevelAncestor()); } diff --git a/src/org/infinity/resource/bcs/BcsResource.java b/src/org/infinity/resource/bcs/BcsResource.java index 5a7c340d6..5e91444a5 100644 --- a/src/org/infinity/resource/bcs/BcsResource.java +++ b/src/org/infinity/resource/bcs/BcsResource.java @@ -60,8 +60,8 @@ import org.infinity.search.ScriptReferenceSearcher; import org.infinity.search.TextResourceSearcher; import org.infinity.util.IdsMap; -import org.infinity.util.StaticSimpleXorDecryptor; import org.infinity.util.Misc; +import org.infinity.util.StaticSimpleXorDecryptor; import org.infinity.util.io.StreamUtils; /** @@ -487,7 +487,7 @@ public void itemStateChanged(ItemEvent event) files.addAll(ResourceFactory.getResources("BS")); new TextResourceSearcher(files, panel.getTopLevelAncestor()); } else if (bpmFind.getSelectedItem() == ifindthis) { - List files = new ArrayList(1); + List files = new ArrayList<>(1); files.add(entry); new TextResourceSearcher(files, panel.getTopLevelAncestor()); } else if (bpmFind.getSelectedItem() == ifindusage) { diff --git a/src/org/infinity/resource/bcs/ScriptInfo.java b/src/org/infinity/resource/bcs/ScriptInfo.java index fa289bf07..1de4165f2 100644 --- a/src/org/infinity/resource/bcs/ScriptInfo.java +++ b/src/org/infinity/resource/bcs/ScriptInfo.java @@ -511,7 +511,7 @@ protected ScriptInfo(ScriptInfo obj, String[] objectSpecifierIds) for (final Map.Entry> entry : obj.FUNCTION_SIGNATURES.entrySet()) { List oldList = entry.getValue(); if (oldList != null) { - this.FUNCTION_SIGNATURES.put(entry.getKey(), new ArrayList(oldList)); + this.FUNCTION_SIGNATURES.put(entry.getKey(), new ArrayList<>(oldList)); } } this.FUNCTION_PARAM_COMMENT.putAll(obj.FUNCTION_PARAM_COMMENT); diff --git a/src/org/infinity/resource/bcs/parser/BafNodeTransformer.java b/src/org/infinity/resource/bcs/parser/BafNodeTransformer.java index ae4e74a32..ec61117f9 100644 --- a/src/org/infinity/resource/bcs/parser/BafNodeTransformer.java +++ b/src/org/infinity/resource/bcs/parser/BafNodeTransformer.java @@ -326,7 +326,7 @@ private void processPARAM(ScriptNode funcNode, Signatures.Function function, Baf } } - private void processParamString(ScriptNode funcNode, Signatures.Function.Parameter param, BafNode baf) throws ParseException + private void processParamString(ScriptNode funcNode, Signatures.Function.Parameter param, BafNode baf) { if (baf.getId() != BafParser.JJTPARAM_STR) { errors.add(new ScriptMessage("Invalid string: " + baf.jjtGetFirstToken(), baf.jjtGetFirstToken(), baf.jjtGetLastToken())); @@ -337,7 +337,7 @@ private void processParamString(ScriptNode funcNode, Signatures.Function.Paramet funcNode.strings.push(s); } - private String processString(BafNode baf) throws ParseException + private String processString(BafNode baf) { if (baf.getId() != BafParser.JJTSTRING) { errors.add(new ScriptMessage("Invalid string parameter: " + baf.jjtGetFirstToken(), @@ -381,7 +381,7 @@ private String processString(BafNode baf) throws ParseException return s; } - private void processParamNumeric(ScriptNode funcNode, Signatures.Function.Parameter param, BafNode baf) throws ParseException + private void processParamNumeric(ScriptNode funcNode, Signatures.Function.Parameter param, BafNode baf) { if (baf.getId() != BafParser.JJTPARAM_NUM && baf.getId() != BafParser.JJTPARAM_SYM && @@ -410,7 +410,7 @@ private void processParamNumeric(ScriptNode funcNode, Signatures.Function.Parame funcNode.numbers.push(Long.valueOf(value)); } - private long processNumberExpression(String ids, BafNode baf) throws ParseException + private long processNumberExpression(String ids, BafNode baf) { if (baf.getId() != BafParser.JJTSINT && baf.getId() != BafParser.JJTUINT && @@ -484,7 +484,7 @@ private long processNumberExpression(String ids, BafNode baf) throws ParseExcept } // Processes numeric or symbolic OR expressions - private long processOrExpression(Signatures.Function.Parameter param, BafNode baf) throws ParseException + private long processOrExpression(Signatures.Function.Parameter param, BafNode baf) { if (baf.getId() != BafParser.JJTOR_EXPR) { errors.add(new ScriptMessage("Invalid numeric or symbolic expression: " + baf.jjtGetFirstToken(), @@ -502,7 +502,7 @@ private long processOrExpression(Signatures.Function.Parameter param, BafNode ba } } - private void processParamPoint(ScriptNode funcNode, Signatures.Function.Parameter param, BafNode baf) throws ParseException + private void processParamPoint(ScriptNode funcNode, Signatures.Function.Parameter param, BafNode baf) { if (baf.getId() != BafParser.JJTPARAM_TGT) { errors.add(new ScriptMessage("Invalid point: " + baf.jjtGetFirstToken(), @@ -589,7 +589,7 @@ private void processObject(ScriptNode objNode, BafNode baf) throws ParseExceptio } } - private void processIdsTarget(ScriptNode objNode, BafNode baf) throws ParseException + private void processIdsTarget(ScriptNode objNode, BafNode baf) { if (baf.getId() != BafParser.JJTTARGET) { errors.add(new ScriptMessage("Invalid IDS target: " + baf.jjtGetFirstToken(), @@ -669,7 +669,7 @@ private void processObjectIdentifierTRAC(ScriptNode objNode, BafNode baf) throws } } - private void processObjectName(ScriptNode objNode, BafNode baf) throws ParseException + private void processObjectName(ScriptNode objNode, BafNode baf) { if (baf.getId() != BafParser.JJTNAME) { errors.add(new ScriptMessage("Invalid object name: " + baf.jjtGetFirstToken(), @@ -688,7 +688,7 @@ private void processObjectName(ScriptNode objNode, BafNode baf) throws ParseExce } } - private void processRectangle(ScriptNode objNode, BafNode baf) throws ParseException + private void processRectangle(ScriptNode objNode, BafNode baf) { if (baf.getId() != BafParser.JJTRECT) { errors.add(new ScriptMessage("Invalid region: " + baf.jjtGetFirstToken(), diff --git a/src/org/infinity/resource/chu/Viewer.java b/src/org/infinity/resource/chu/Viewer.java index 58da9a64a..7c3c44bb5 100644 --- a/src/org/infinity/resource/chu/Viewer.java +++ b/src/org/infinity/resource/chu/Viewer.java @@ -54,11 +54,11 @@ import org.infinity.resource.Profile; import org.infinity.resource.ResourceFactory; import org.infinity.resource.graphics.BamDecoder; -import org.infinity.resource.graphics.MosDecoder; -import org.infinity.resource.graphics.MosV1Decoder; import org.infinity.resource.graphics.BamDecoder.BamControl; import org.infinity.resource.graphics.BamDecoder.FrameEntry; import org.infinity.resource.graphics.BamV1Decoder.BamV1Control; +import org.infinity.resource.graphics.MosDecoder; +import org.infinity.resource.graphics.MosV1Decoder; import org.infinity.util.StringTable; @@ -426,7 +426,7 @@ private void initControls() // Data model for the panels list private class ListPanelsModel extends AbstractListModel { - private final List listPanels = new ArrayList(); + private final List listPanels = new ArrayList<>(); private final Viewer viewer; public ListPanelsModel(Viewer viewer) @@ -474,7 +474,7 @@ public ChuResource getResource() // Data model for the controls list private class ListControlsModel extends AbstractListModel { - private final List listControls = new ArrayList(); + private final List listControls = new ArrayList<>(); private final Viewer viewer; private Window panel; @@ -574,7 +574,7 @@ private static class PropertiesPanel extends JPanel implements ActionListener private static final String FMT_POSITION = "X: %d, Y: %d"; private static final String FMT_SIZE = "W: %d, H: %d"; - private final List listeners = new ArrayList(); + private final List listeners = new ArrayList<>(); private final JRadioButton[] rbButtonState = new JRadioButton[4]; private JLabel lPosition, lSize; @@ -1344,7 +1344,7 @@ private static class ButtonControl extends BaseControl private static final int SELECTED = 2; private static final int DISABLED = 3; - private static final HashSet ignoreResourceSet = new HashSet(); + private static final HashSet ignoreResourceSet = new HashSet<>(); static { // XXX: ignore a set of known background BAMs with cycle and frame indices @@ -1586,7 +1586,7 @@ public void updateImage() // Manages the visual appearance of text fields private static class TextFieldControl extends BaseControl { - private static final HashSet ignoreResourceSet = new HashSet(); + private static final HashSet ignoreResourceSet = new HashSet<>(); static { // XXX: ignore a set of known background MOS resources diff --git a/src/org/infinity/resource/cre/CreResource.java b/src/org/infinity/resource/cre/CreResource.java index 9973a3009..d6b66e32c 100644 --- a/src/org/infinity/resource/cre/CreResource.java +++ b/src/org/infinity/resource/cre/CreResource.java @@ -429,8 +429,8 @@ public final class CreResource extends AbstractStruct public static final int TAB_INDEX_ANIMATION = 1; public static final int TAB_INDEX_RAW = 2; - private static final LongIntegerHashMap m_magetype = new LongIntegerHashMap(); - private static final LongIntegerHashMap m_colorPlacement = new LongIntegerHashMap(); + private static final LongIntegerHashMap m_magetype = new LongIntegerHashMap<>(); + private static final LongIntegerHashMap m_colorPlacement = new LongIntegerHashMap<>(); public static final String[] s_flag = { "No flags set", "Identified", "No corpse", "Permanent corpse", "Original class: Fighter", "Original class: Mage", "Original class: Cleric", "Original class: Thief", @@ -576,7 +576,7 @@ public static void addScriptName(Map> scriptNames, entries.add(entry); } } else { - Set entries = new HashSet(); + Set entries = new HashSet<>(); entries.add(entry); synchronized (scriptNames) { scriptNames.put(scriptName, entries); @@ -2237,7 +2237,7 @@ public static boolean matchSearchOptions(ResourceEntry entry, SearchOptions sear SearchOptions.getResourceName(SearchOptions.CRE_IWD2SpellDomain)}; final String spellTypesStruct = SearchOptions.getResourceName(SearchOptions.CRE_IWD2SpellBard_Spell); final String spellTypesRef = SearchOptions.getResourceName(SearchOptions.CRE_IWD2SpellBard_Spell_ResRef); - List listSpells = new ArrayList(64); + List listSpells = new ArrayList<>(64); for (int i = 0; i < spellTypes.length; i++) { for (int j = 1; j < 10; j++) { String label = String.format(spellTypes[i], j); diff --git a/src/org/infinity/resource/cre/browser/MediaPanel.java b/src/org/infinity/resource/cre/browser/MediaPanel.java index dcd451271..75e68a9f8 100644 --- a/src/org/infinity/resource/cre/browser/MediaPanel.java +++ b/src/org/infinity/resource/cre/browser/MediaPanel.java @@ -580,7 +580,7 @@ private void updateLabels() lDirection.setText(dir.toString()); String text = ""; if (getController() != null) { - int cycle = ((SpriteDecoder)getController().getDecoder()).getDirectionMap().getOrDefault(dir, -1); + int cycle = getController().getDecoder().getDirectionMap().getOrDefault(dir, -1); text = "Cycle: " + cycle; } lDirection.setToolTipText(text); @@ -643,7 +643,7 @@ private void initDirection(SpriteDecoder decoder, Direction defDir) int min = -directions.size() + 1; int max = directions.size(); // duplicate list entries - directions.addAll(new ArrayList(directions)); + directions.addAll(new ArrayList<>(directions)); // remove excess entries from left (negative) side while (directions.size() > 1 && directions.get(0) > Direction.N.getValue()) { directions.remove(0); diff --git a/src/org/infinity/resource/cre/browser/SettingsPanel.java b/src/org/infinity/resource/cre/browser/SettingsPanel.java index 91a2b16f8..1562da58c 100644 --- a/src/org/infinity/resource/cre/browser/SettingsPanel.java +++ b/src/org/infinity/resource/cre/browser/SettingsPanel.java @@ -432,7 +432,7 @@ private void init() // selection controls JLabel l1 = new JLabel("Zoom:"); - cbZoom = new JComboBox>(zoomList); + cbZoom = new JComboBox<>(zoomList); cbZoom.setPrototypeDisplayValue(zoomList.get(1)); cbZoom.setSelectedIndex(indexZoom); cbZoom.addActionListener(listeners); @@ -442,14 +442,14 @@ private void init() bCenter.addActionListener(listeners); JLabel l2 = new JLabel("Frame rate:"); - cbFrameRate = new JComboBox>(frameRateList); + cbFrameRate = new JComboBox<>(frameRateList); cbFrameRate.setPrototypeDisplayValue(frameRateList.get(2)); cbFrameRate.setSelectedIndex(indexFrameRate); cbFrameRate.addActionListener(listeners); JLabel l3 = new JLabel("Background:"); List bgList = getBackgrounds(Profile.getGame()); - cbBackground = new JComboBox(bgList.toArray(new Backgrounds.BackgroundInfo[bgList.size()])); + cbBackground = new JComboBox<>(bgList.toArray(new Backgrounds.BackgroundInfo[bgList.size()])); cbBackground.setPrototypeDisplayValue(backgroundList.get(1)); cbBackground.setSelectedIndex(indexBackground); cbBackground.addActionListener(listeners); @@ -729,7 +729,7 @@ private static class ItemString public static ItemString with(String text, T data) { - return new ItemString(text, data); + return new ItemString<>(text, data); } public ItemString(String text, T data) diff --git a/src/org/infinity/resource/cre/browser/icon/Icons.java b/src/org/infinity/resource/cre/browser/icon/Icons.java index 968ef34cb..956b16b04 100644 --- a/src/org/infinity/resource/cre/browser/icon/Icons.java +++ b/src/org/infinity/resource/cre/browser/icon/Icons.java @@ -28,7 +28,7 @@ public class Icons public static final String ICON_CIRCLE_RED = "circle_red.png"; public static final String ICON_CIRCLE_YELLOW = "circle_yellow.png"; - private static final Map ICONMAP = new HashMap(20); + private static final Map ICONMAP = new HashMap<>(20); /** * Returns an ImageIcon object of the specified graphics filename. diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index 917b4423a..34446834b 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -50,17 +50,19 @@ import org.infinity.resource.cre.decoder.util.SeqDef; import org.infinity.resource.cre.decoder.util.Sequence; import org.infinity.resource.cre.decoder.util.SpriteUtils; -import org.infinity.resource.graphics.ColorConvert; -import org.infinity.resource.graphics.PseudoBamDecoder; -import org.infinity.resource.graphics.BamV1Decoder.BamV1Control; import org.infinity.resource.graphics.BamDecoder; +import org.infinity.resource.graphics.BamV1Decoder.BamV1Control; import org.infinity.resource.graphics.BlendingComposite; +import org.infinity.resource.graphics.ColorConvert; +import org.infinity.resource.graphics.PseudoBamDecoder; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapSection; import org.infinity.util.Misc; import org.infinity.util.tuples.Couple; +import sun.security.rsa.RSAUtil.KeyType; + /** * Specialized BAM decoder for creature animation sprites. */ @@ -979,6 +981,7 @@ public SpriteBamControl createControl() /** * Returns the preferred compositor for rendering the sprite on the target surface. */ + @Override public Composite getComposite() { int blending = ((isBrightest() ? 1 : 0) << 0) | ((isMultiplyBlend() ? 1 : 0) << 1); @@ -1595,7 +1598,7 @@ protected void applyFalseColors(BamV1Control control, SegmentDef sd) } // preparations - final Map colorRanges = new HashMap(); + final Map colorRanges = new HashMap<>(); for (int loc = 0; loc < 7; loc++) { int ofs = getColorOffset(loc); Couple colorInfo = diff --git a/src/org/infinity/resource/cre/decoder/tables/SpriteTables.java b/src/org/infinity/resource/cre/decoder/tables/SpriteTables.java index 9444105b2..668090836 100644 --- a/src/org/infinity/resource/cre/decoder/tables/SpriteTables.java +++ b/src/org/infinity/resource/cre/decoder/tables/SpriteTables.java @@ -276,7 +276,7 @@ private static List processInfinityAnimations(int animationId) if (Profile.getProperty(Profile.Key.GET_INFINITY_ANIMATIONS) > 0) { return InfinityTables.createIniMaps(animationId); } - return new ArrayList(); + return new ArrayList<>(); } @@ -356,7 +356,7 @@ public static List findTables(Profile.Game game) { List retVal = TableMaps.get(game); if (retVal == null) { - retVal = new ArrayList(); + retVal = new ArrayList<>(); } return retVal; } diff --git a/src/org/infinity/resource/cre/decoder/util/ItemInfo.java b/src/org/infinity/resource/cre/decoder/util/ItemInfo.java index 4a53dce40..7c9a4c4b8 100644 --- a/src/org/infinity/resource/cre/decoder/util/ItemInfo.java +++ b/src/org/infinity/resource/cre/decoder/util/ItemInfo.java @@ -1060,6 +1060,7 @@ default ItemPredicate and(ItemPredicate other) return (t) -> test(t) && other.test(t); } + @Override default ItemPredicate negate() { return (t) -> !test(t); diff --git a/src/org/infinity/resource/dlg/Viewer.java b/src/org/infinity/resource/dlg/Viewer.java index 7857bfe63..fda071300 100644 --- a/src/org/infinity/resource/dlg/Viewer.java +++ b/src/org/infinity/resource/dlg/Viewer.java @@ -311,7 +311,7 @@ public void itemStateChanged(ItemEvent event) List files = ResourceFactory.getResources("DLG"); new DialogSearcher(files, getTopLevelAncestor()); } else if (bpmFind.getSelectedItem() == ifindthis) { - List files = new ArrayList(); + List files = new ArrayList<>(); files.add(dlg.getResourceEntry()); new DialogSearcher(files, getTopLevelAncestor()); } diff --git a/src/org/infinity/resource/gam/JournalEntry.java b/src/org/infinity/resource/gam/JournalEntry.java index 9571a4adf..9355b79ff 100644 --- a/src/org/infinity/resource/gam/JournalEntry.java +++ b/src/org/infinity/resource/gam/JournalEntry.java @@ -28,7 +28,7 @@ public final class JournalEntry extends AbstractStruct implements AddRemovable public static final String GAM_JOURNAL_SECTION = "Section"; public static final String GAM_JOURNAL_SOURCE = "Text source"; - private static final LongIntegerHashMap m_source = new LongIntegerHashMap(); + private static final LongIntegerHashMap m_source = new LongIntegerHashMap<>(); public static final String s_section[] = new String[]{"User notes", "Quests", "Done quests", "Journal"}; diff --git a/src/org/infinity/resource/gam/KillVariable.java b/src/org/infinity/resource/gam/KillVariable.java index 70c52d92f..6d91d1d8a 100644 --- a/src/org/infinity/resource/gam/KillVariable.java +++ b/src/org/infinity/resource/gam/KillVariable.java @@ -7,10 +7,9 @@ import java.nio.ByteBuffer; import org.infinity.resource.AbstractStruct; -import org.infinity.resource.AddRemovable; import org.infinity.util.io.StreamUtils; -public final class KillVariable extends Variable implements AddRemovable +public final class KillVariable extends Variable { // GAM/KillVariable-specific field labels public static final String GAM_KILLVAR = "Kill variable"; diff --git a/src/org/infinity/resource/gam/PartyNPC.java b/src/org/infinity/resource/gam/PartyNPC.java index a1e0cc1eb..7d6cee010 100644 --- a/src/org/infinity/resource/gam/PartyNPC.java +++ b/src/org/infinity/resource/gam/PartyNPC.java @@ -85,8 +85,8 @@ public class PartyNPC extends AbstractStruct implements HasViewerTabs, AddRemova public static final String GAM_NPC_STAT_FAV_WEAPON_FMT = "Favorite weapon %d"; public static final String GAM_NPC_STAT_FAV_WEAPON_COUNT_FMT = "Favorite weapon counter %d"; - public static final LongIntegerHashMap m_partyOrder = new LongIntegerHashMap(); -// private static final LongIntegerHashMap m_selected = new LongIntegerHashMap(); + public static final LongIntegerHashMap m_partyOrder = new LongIntegerHashMap<>(); +// private static final LongIntegerHashMap m_selected = new LongIntegerHashMap<>(); private static final String[] s_selected = {"Not selected", "Selected", null, null, null, null, null, null, null, null, null, null, null, null, null, null, "Dead" }; diff --git a/src/org/infinity/resource/graphics/BamResource.java b/src/org/infinity/resource/graphics/BamResource.java index 2c4885a2a..7f9778de5 100644 --- a/src/org/infinity/resource/graphics/BamResource.java +++ b/src/org/infinity/resource/graphics/BamResource.java @@ -549,7 +549,7 @@ public JComponent makeViewer(ViewableContainer container) miExportFramesPNG.addActionListener(this); } - List list = new ArrayList(); + List list = new ArrayList<>(); if (miExport != null) { list.add(miExport); } @@ -955,7 +955,7 @@ private boolean checkCompatibility(Component parent) int numCycles = bamControl.cycleCount(); boolean hasSemiTrans = false; int maxWidth = 0, maxHeight = 0; - List issues = new ArrayList(10); + List issues = new ArrayList<>(10); // checking for issues BamDecoder.BamControl control = decoder.createControl(); @@ -1079,7 +1079,7 @@ private byte[] convertToBamV1(boolean compressed) throws Exception int[] chainedImageData = ((DataBufferInt)composedImage.getRaster().getDataBuffer()).getData(); int[] palette = ColorConvert.medianCut(chainedImageData, hasTransparency ? 255 : 256, ignoreAlpha); // initializing color cache - IntegerHashMap colorCache = new IntegerHashMap(1536); + IntegerHashMap colorCache = new IntegerHashMap<>(1536); for (int i = 0; i < palette.length; i++) { colorCache.put(palette[i], (byte)i); } @@ -1093,7 +1093,7 @@ private byte[] convertToBamV1(boolean compressed) throws Exception } // 2. encoding frames - List frameList = new ArrayList(frameCount); + List frameList = new ArrayList<>(frameCount); int colorShift = hasTransparency ? 1 : 0; // considers transparent color index for (int i = 0; i < frameCount; i++) { if (decoder.frameGet(control, i) != null) { @@ -1257,7 +1257,7 @@ private void startConversion(boolean compressed) @Override public List doInBackground() { - List list = new Vector(1); + List list = new Vector<>(1); try { byte[] buf = convertToBamV1(exportCompressed); if (buf != null) { diff --git a/src/org/infinity/resource/graphics/BamV1Decoder.java b/src/org/infinity/resource/graphics/BamV1Decoder.java index 0f06babc8..45d3c8635 100644 --- a/src/org/infinity/resource/graphics/BamV1Decoder.java +++ b/src/org/infinity/resource/graphics/BamV1Decoder.java @@ -25,8 +25,8 @@ */ public class BamV1Decoder extends BamDecoder { - private final List listFrames = new ArrayList(); - private final List listCycles = new ArrayList(); + private final List listFrames = new ArrayList<>(); + private final List listCycles = new ArrayList<>(); private final BamV1FrameEntry defaultFrameInfo = new BamV1FrameEntry(null, 0); private BamV1Control defaultControl; diff --git a/src/org/infinity/resource/graphics/BamV2Decoder.java b/src/org/infinity/resource/graphics/BamV2Decoder.java index 57c6fd6cb..907e90589 100644 --- a/src/org/infinity/resource/graphics/BamV2Decoder.java +++ b/src/org/infinity/resource/graphics/BamV2Decoder.java @@ -33,8 +33,8 @@ public class BamV2Decoder extends BamDecoder { private final TreeSet pvrIndices = new TreeSet<>(); - private final List listFrames = new ArrayList(); - private final List listCycles = new ArrayList(); + private final List listFrames = new ArrayList<>(); + private final List listCycles = new ArrayList<>(); private final BamV2FrameEntry defaultFrameInfo = new BamV2FrameEntry(null, 0, 0); private BamV2Control defaultControl; diff --git a/src/org/infinity/resource/graphics/BlendingComposite.java b/src/org/infinity/resource/graphics/BlendingComposite.java index 0e78a6c41..05a93761b 100644 --- a/src/org/infinity/resource/graphics/BlendingComposite.java +++ b/src/org/infinity/resource/graphics/BlendingComposite.java @@ -44,6 +44,7 @@ void blend(int[] src, int[] dst, int[] result) { * src={@code GL_DST_COLOR}, dst={@code GL_ONE}. */ MULTIPLY { + @Override void blend(int[] src, int[] dst, int[] result) { result[0] = Math.min(255, ((src[0] * dst[0]) + (dst[0] << 8)) >>> 8); result[1] = Math.min(255, ((src[1] * dst[1]) + (dst[1] << 8)) >>> 8); @@ -58,6 +59,7 @@ void blend(int[] src, int[] dst, int[] result) { * src={@code GL_SRC_COLOR}, dst={@code GL_ONE}. */ BRIGHTEST_MULTIPLY { + @Override void blend(int[] src, int[] dst, int[] result) { result[0] = Math.min(255, ((src[0] * src[0]) + (dst[0] << 8)) >>> 8); result[1] = Math.min(255, ((src[1] * src[1]) + (dst[1] << 8)) >>> 8); diff --git a/src/org/infinity/resource/graphics/ColorConvert.java b/src/org/infinity/resource/graphics/ColorConvert.java index cb2a12d31..0404416d5 100644 --- a/src/org/infinity/resource/graphics/ColorConvert.java +++ b/src/org/infinity/resource/graphics/ColorConvert.java @@ -684,7 +684,7 @@ public static boolean medianCut(int[] pixels, int desiredColors, int[] palette, if (desiredColors > 0 && desiredColors <= MAX_COLORS && palette.length >= desiredColors) { final PriorityQueue blockQueue = - new PriorityQueue(desiredColors, PixelBlock.PixelBlockComparator); + new PriorityQueue<>(desiredColors, PixelBlock.PixelBlockComparator); final Pixel[] p = new Pixel[pixels.length]; int mask = ignoreAlpha ? 0xff000000: 0; diff --git a/src/org/infinity/resource/graphics/GifSequenceReader.java b/src/org/infinity/resource/graphics/GifSequenceReader.java index 4d5809096..bb3dca100 100644 --- a/src/org/infinity/resource/graphics/GifSequenceReader.java +++ b/src/org/infinity/resource/graphics/GifSequenceReader.java @@ -200,7 +200,7 @@ private Frame initFrame(int index, IIOImage ioimg) throws Exception } // Updates the specified Frame object with metadata - private Frame updateMetadata(Frame frame, IIOMetadata meta) throws IOException + private Frame updateMetadata(Frame frame, IIOMetadata meta) { if (frame != null && meta != null) { Node tree = meta.getAsTree(metaFormatName); diff --git a/src/org/infinity/resource/graphics/GifSequenceWriter.java b/src/org/infinity/resource/graphics/GifSequenceWriter.java index 70955e93b..92dd244df 100644 --- a/src/org/infinity/resource/graphics/GifSequenceWriter.java +++ b/src/org/infinity/resource/graphics/GifSequenceWriter.java @@ -99,6 +99,7 @@ public void writeToSequence(RenderedImage img) throws IOException * Close this GifSequenceWriter object. This does not close the underlying * stream, just finishes off the GIF. */ + @Override public void close() throws IOException { gifWriter.endWriteSequence(); diff --git a/src/org/infinity/resource/graphics/MosResource.java b/src/org/infinity/resource/graphics/MosResource.java index bd703241d..a36cd5c24 100644 --- a/src/org/infinity/resource/graphics/MosResource.java +++ b/src/org/infinity/resource/graphics/MosResource.java @@ -281,7 +281,7 @@ public JComponent makeViewer(ViewableContainer container) } } } - List list = new ArrayList(); + List list = new ArrayList<>(); if (miExport != null) list.add(miExport); if (miExportMOSV1 != null) @@ -478,7 +478,7 @@ private byte[] convertToMosV1(boolean compressed) throws Exception progress.setMillisToPopup(2000); // creating list of tiles as int[] arrays - List tileList = new ArrayList(cols*rows); + List tileList = new ArrayList<>(cols*rows); for (int y = 0; y < rows; y++) { for (int x = 0; x < cols; x++) { int tileX = x * 64; @@ -497,7 +497,7 @@ private byte[] convertToMosV1(boolean compressed) throws Exception byte[] tilePalette = new byte[1024]; byte[] tileData = new byte[64*64]; int curPalOfs = palOfs, curTableOfs = tableOfs, curDataOfs = dataOfs; - IntegerHashMap colorCache = new IntegerHashMap(1536); // caching RGBColor -> index + IntegerHashMap colorCache = new IntegerHashMap<>(1536); // caching RGBColor -> index for (int tileIdx = 0; tileIdx < tileList.size(); tileIdx++) { colorCache.clear(); if (progress.isCanceled()) { @@ -571,7 +571,7 @@ private void startConversion(boolean compressed) @Override public List doInBackground() { - List list = new Vector(1); + List list = new Vector<>(1); try { byte[] buf = convertToMosV1(exportCompressed); if (buf != null) { diff --git a/src/org/infinity/resource/graphics/PseudoBamDecoder.java b/src/org/infinity/resource/graphics/PseudoBamDecoder.java index 415f1fc81..fc8919377 100644 --- a/src/org/infinity/resource/graphics/PseudoBamDecoder.java +++ b/src/org/infinity/resource/graphics/PseudoBamDecoder.java @@ -57,9 +57,9 @@ public class PseudoBamDecoder extends BamDecoder private static final Color TransparentColor = new Color(0, true); private final PseudoBamFrameEntry defaultFrameInfo = new PseudoBamFrameEntry(null, 0, 0); - private final HashMap mapOptions = new HashMap(); + private final HashMap mapOptions = new HashMap<>(); - private List listCycles = new ArrayList(); + private List listCycles = new ArrayList<>(); private List listFrames; private PseudoBamControl defaultControl; @@ -163,7 +163,7 @@ public void setFramesList(List framesList) if (framesList != null) { listFrames = framesList; } else { - listFrames = new ArrayList(); + listFrames = new ArrayList<>(); } } @@ -185,7 +185,7 @@ public void setCyclesList(List cyclesList) if (cyclesList != null) { listCycles = cyclesList; } else { - listCycles = new ArrayList(); + listCycles = new ArrayList<>(); } } @@ -726,7 +726,7 @@ public boolean exportBamV1(Path fileName, ProgressMonitor progress, int curProgr control.setSharedPerCycle(false); Dimension dimFrame = control.calculateSharedCanvas(false).getSize(); int maxImageSize = (dimFrame.width*dimFrame.height*3) / 2; // about 1.5x of max. size - List listFrameData = new ArrayList(listFrames.size()); + List listFrameData = new ArrayList<>(listFrames.size()); // encoding frames Object o = getOption(OPTION_INT_RLEINDEX); @@ -779,7 +779,7 @@ public boolean exportBamV1(Path fileName, ProgressMonitor progress, int curProgr } // creating cycles table and frame lookup table - List listFrameLookup = new ArrayList(); + List listFrameLookup = new ArrayList<>(); int lookupSize = 0; for (int i = 0; i < listCycles.size(); i++) { listFrameLookup.add(Integer.valueOf(lookupSize)); @@ -908,8 +908,8 @@ public boolean exportBamV2(Path fileName, DxtEncoder.DxtType dxtType, int pvrzIn // preparing output path for PVRZ files Path pvrzFilePath = fileName.toAbsolutePath().getParent(); - List listFrameData = new ArrayList(listFrames.size()); - List listGrid = new ArrayList(); + List listFrameData = new ArrayList<>(listFrames.size()); + List listGrid = new ArrayList<>(); // initializing progress monitor if (progress != null) { @@ -926,8 +926,8 @@ public boolean exportBamV2(Path fileName, DxtEncoder.DxtType dxtType, int pvrzIn } // generating remaining info blocks - List listFrameDataBlocks = new ArrayList(); - List listFrameEntries = new ArrayList(); + List listFrameDataBlocks = new ArrayList<>(); + List listFrameEntries = new ArrayList<>(); List> listCycleData = new ArrayList<>(listCycles.size()); int frameStartIndex = 0; // keeps track of current start index of frame entries int blockStartIndex = 0; // keeps track of current start index of frame data blocks @@ -1046,7 +1046,7 @@ public int[] createGlobalPalette(HashMap colorMap) // adding pixels of all available frames to the hashset HashMap newMap; if (colorMap == null) { - newMap = new HashMap(); + newMap = new HashMap<>(); for (int i = 0; i < listFrames.size(); i++) { registerColors(newMap, listFrames.get(i).frame); } @@ -1080,7 +1080,7 @@ public int[] createGlobalPalette(HashMap colorMap) } // removing duplicate entries from the palette - HashSet colorSet = new HashSet(); + HashSet colorSet = new HashSet<>(); for (int i = 0; i < retVal.length; i++) { colorSet.add(Integer.valueOf(retVal[i])); } @@ -1400,7 +1400,7 @@ public boolean equals(Object o) /** Provides information for a single frame entry */ public static class PseudoBamFrameEntry implements FrameEntry { - private final HashMap mapOptions = new HashMap(); + private final HashMap mapOptions = new HashMap<>(); private int width, height, centerX, centerY; private int overrideCenterX, overrideCenterY; @@ -2005,11 +2005,11 @@ private void update() public static class PseudoBamCycleEntry { private final List frames; // stores abs. frame indices that define this cycle - private final HashMap mapOptions = new HashMap(); + private final HashMap mapOptions = new HashMap<>(); protected PseudoBamCycleEntry(int[] indices) { - frames = new ArrayList(); + frames = new ArrayList<>(); add(indices); } diff --git a/src/org/infinity/resource/graphics/PvrDecoder.java b/src/org/infinity/resource/graphics/PvrDecoder.java index e9c678bca..7c498c7d0 100644 --- a/src/org/infinity/resource/graphics/PvrDecoder.java +++ b/src/org/infinity/resource/graphics/PvrDecoder.java @@ -63,7 +63,7 @@ public enum ChannelType { } // The global cache list for PVR objects. The "key" has to be a unique String (e.g. filename or integer as string) - private static final Map pvrCache = new LinkedHashMap(); + private static final Map pvrCache = new LinkedHashMap<>(); // The max. number of cache entries to hold private static int MaxCacheEntries = 32; diff --git a/src/org/infinity/resource/graphics/TisResource.java b/src/org/infinity/resource/graphics/TisResource.java index e7ee96707..7a3a3b464 100644 --- a/src/org/infinity/resource/graphics/TisResource.java +++ b/src/org/infinity/resource/graphics/TisResource.java @@ -509,7 +509,7 @@ public JComponent makeViewer(ViewableContainer container) miExportPNG = new JMenuItem("as PNG"); miExportPNG.addActionListener(this); - List list = new ArrayList(); + List list = new ArrayList<>(); if (miExport != null) list.add(miExport); if (miExportPaletteTis != null) { @@ -618,7 +618,7 @@ private void initTileset() if (decoder != null) { int tileCount = decoder.getTileCount(); defaultWidth = calcTileWidth(entry, tileCount); - tileImages = new ArrayList(tileCount); + tileImages = new ArrayList<>(tileCount); for (int tileIdx = 0; tileIdx < tileCount; tileIdx++) { BufferedImage image = ColorConvert.createCompatibleImage(64, 64, Transparency.BITMASK); decoder.getTile(tileIdx, image); @@ -632,7 +632,7 @@ private void initTileset() e.printStackTrace(); WindowBlocker.blockWindow(false); if (tileImages == null) - tileImages = new ArrayList(); + tileImages = new ArrayList<>(); if (tileImages.isEmpty()) tileImages.add(ColorConvert.createCompatibleImage(1, 1, Transparency.BITMASK)); JOptionPane.showMessageDialog(NearInfinity.getInstance(), @@ -676,7 +676,7 @@ public Status convertToPaletteTis(Path output, boolean showProgress) BufferedImage image = ColorConvert.createCompatibleImage(decoder.getTileWidth(), decoder.getTileHeight(), Transparency.BITMASK); - IntegerHashMap colorCache = new IntegerHashMap(1800); // caching RGBColor -> index + IntegerHashMap colorCache = new IntegerHashMap<>(1800); // caching RGBColor -> index for (int tileIdx = 0; tileIdx < decoder.getTileCount(); tileIdx++) { colorCache.clear(); if (progress != null && progress.isCanceled()) { @@ -816,7 +816,7 @@ public Status convertToPvrzTis(Path output, boolean showProgress) } boolean[] markedTiles = new boolean[numTiles]; Arrays.fill(markedTiles, false); - List listRegions = new ArrayList(256); + List listRegions = new ArrayList<>(256); // divide primary tiles into regions int pw = (tisWidth + pageDim - 1) / pageDim; @@ -924,8 +924,8 @@ public Status convertToPvrzTis(Path output, boolean showProgress) } // packing tileset regions - List entryList = new ArrayList(numTiles); - List pageList = new ArrayList(); + List entryList = new ArrayList<>(numTiles); + List pageList = new ArrayList<>(); for (TileRect rect: listRegions) { Dimension space = new Dimension(rect.bounds); int pageIndex = -1; diff --git a/src/org/infinity/resource/key/BIFFReader.java b/src/org/infinity/resource/key/BIFFReader.java index cd95ae80d..20d3038ac 100644 --- a/src/org/infinity/resource/key/BIFFReader.java +++ b/src/org/infinity/resource/key/BIFFReader.java @@ -103,13 +103,13 @@ public ByteBuffer getResourceBuffer(int locator) throws IOException buffer = StreamUtils.getByteBuffer(remaining); StreamUtils.copyBytes(header, buffer, header.limit()); remaining -= header.limit(); - while (channel.read(buffer) > 0); + while (channel.read(buffer) > 0) {} } finally { blocker.setBlocked(false); } } else { buffer = StreamUtils.getByteBuffer(entry.size); - while (channel.read(buffer) > 0); + while (channel.read(buffer) > 0) {} } buffer.position(0); @@ -139,7 +139,7 @@ public InputStream getResourceAsStream(int locator) throws IOException } } - private void init(ByteBuffer buffer, int numFiles, int numTilesets) throws IOException + private void init(ByteBuffer buffer, int numFiles, int numTilesets) { // reading file entries for (int i = 0; i < numFiles; i++) { diff --git a/src/org/infinity/resource/key/BIFReader.java b/src/org/infinity/resource/key/BIFReader.java index acc216eeb..fdd2193a5 100644 --- a/src/org/infinity/resource/key/BIFReader.java +++ b/src/org/infinity/resource/key/BIFReader.java @@ -175,7 +175,7 @@ private void init() throws Exception } // Returns an inflater input stream - private InflaterInputStream getInflaterInputStream() throws IOException + private InflaterInputStream getInflaterInputStream() { return new InflaterInputStream(new ByteBufferInputStream(mappedBuffer.duplicate())); } diff --git a/src/org/infinity/resource/key/FileResourceEntry.java b/src/org/infinity/resource/key/FileResourceEntry.java index 6e0ac03d5..508cb5557 100644 --- a/src/org/infinity/resource/key/FileResourceEntry.java +++ b/src/org/infinity/resource/key/FileResourceEntry.java @@ -103,6 +103,7 @@ public String getResourceName() return file.getFileName().toString(); } + @Override public String getResourceRef() { String fileName = file.getFileName().toString(); diff --git a/src/org/infinity/resource/key/Keyfile.java b/src/org/infinity/resource/key/Keyfile.java index a037e5448..fc5783b32 100644 --- a/src/org/infinity/resource/key/Keyfile.java +++ b/src/org/infinity/resource/key/Keyfile.java @@ -270,7 +270,7 @@ public List getDlcKeyfiles() * Overrides current key file mapping with data from the specified key file. * @param keyFile The key file containing new entries. */ - public void addKeyfile(Path keyFile) throws IOException + public void addKeyfile(Path keyFile) { if (keyFile == null) { throw new NullPointerException("No DLC keyfile specified"); diff --git a/src/org/infinity/resource/key/ResourceEntry.java b/src/org/infinity/resource/key/ResourceEntry.java index 49cb603a6..d2e62f0b0 100644 --- a/src/org/infinity/resource/key/ResourceEntry.java +++ b/src/org/infinity/resource/key/ResourceEntry.java @@ -37,7 +37,7 @@ public abstract class ResourceEntry implements Comparable { // list of file extensions not shown in the resource tree - private static final HashSet skippedExtensions = new HashSet(); + private static final HashSet skippedExtensions = new HashSet<>(); static { skippedExtensions.add("BAK"); @@ -95,6 +95,14 @@ static int[] getLocalFileInfo(Path file) return null; } + @Override + public int hashCode() + { + int hash = 7; + hash = 31 * hash + ((searchString == null) ? 0 : searchString.hashCode()); + return hash; + } + @Override public boolean equals(Object o) { diff --git a/src/org/infinity/resource/key/ResourceTreeFolder.java b/src/org/infinity/resource/key/ResourceTreeFolder.java index 0f7639c20..ef297463e 100644 --- a/src/org/infinity/resource/key/ResourceTreeFolder.java +++ b/src/org/infinity/resource/key/ResourceTreeFolder.java @@ -16,7 +16,7 @@ public final class ResourceTreeFolder implements Comparable { private final SortedListSet resourceEntries = new SortedListSet<>(); - private final List folders = new ArrayList(); + private final List folders = new ArrayList<>(); private final ResourceTreeFolder parentFolder; private final String folderName; @@ -54,7 +54,7 @@ public List getResourceEntries() public List getResourceEntries(String type) { - List list = new ArrayList(); + List list = new ArrayList<>(); resourceEntries.forEach((entry) -> { if (entry.getExtension().equalsIgnoreCase(type)) { list.add(entry); @@ -333,7 +333,7 @@ public synchronized SortedSet tailSet(T fromElement) private SortedSet getSortedSet(int fromIdx, int toIdx) { - SortedListSet retVal = new SortedListSet(); + SortedListSet retVal = new SortedListSet<>(); for (int idx = fromIdx; idx <= toIdx; idx++) { retVal.add(get(idx)); } diff --git a/src/org/infinity/resource/key/ResourceTreeModel.java b/src/org/infinity/resource/key/ResourceTreeModel.java index f0116c17a..8cbdbeac1 100644 --- a/src/org/infinity/resource/key/ResourceTreeModel.java +++ b/src/org/infinity/resource/key/ResourceTreeModel.java @@ -147,7 +147,7 @@ public List getBIFFResourceEntries() public List getBIFFResourceEntries(Path keyFile) { - List list = new ArrayList(); + List list = new ArrayList<>(); for (int i = 0; i < root.getFolders().size(); i++) { List entries = root.getFolders().get(i).getResourceEntries(); for (int j = 0; j < entries.size(); j++) { diff --git a/src/org/infinity/resource/mus/Viewer.java b/src/org/infinity/resource/mus/Viewer.java index 734a8a942..a6775ec74 100644 --- a/src/org/infinity/resource/mus/Viewer.java +++ b/src/org/infinity/resource/mus/Viewer.java @@ -33,10 +33,10 @@ public class Viewer extends JPanel implements Runnable, ActionListener { - private final SimpleListModel listModel = new SimpleListModel(); + private final SimpleListModel listModel = new SimpleListModel<>(); private final JList list = new JList<>(listModel); private final AudioPlayer player = new AudioPlayer(); - private final List entryList = new Vector(); + private final List entryList = new Vector<>(); private JLabel playList; private JButton bPlay, bEnd, bStop; diff --git a/src/org/infinity/resource/other/FntResource.java b/src/org/infinity/resource/other/FntResource.java index df65d50e3..41001db18 100644 --- a/src/org/infinity/resource/other/FntResource.java +++ b/src/org/infinity/resource/other/FntResource.java @@ -17,13 +17,12 @@ import org.infinity.gui.hexview.ResourceDataProvider; import org.infinity.gui.hexview.StructHexViewer; import org.infinity.resource.AbstractStruct; -import org.infinity.resource.Closeable; import org.infinity.resource.HasViewerTabs; import org.infinity.resource.Resource; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.io.StreamUtils; -public final class FntResource extends AbstractStruct implements Resource, Closeable, HasViewerTabs +public final class FntResource extends AbstractStruct implements Resource, HasViewerTabs { // FNT-specific field labels public static final String FNT_NUM_EXTRA_LETTERS = "# extra letters"; diff --git a/src/org/infinity/resource/pro/ProAreaType.java b/src/org/infinity/resource/pro/ProAreaType.java index cfa3b8e92..1cff0aa61 100644 --- a/src/org/infinity/resource/pro/ProAreaType.java +++ b/src/org/infinity/resource/pro/ProAreaType.java @@ -54,7 +54,7 @@ public final class ProAreaType extends AbstractStruct implements AddRemovable, U public static final String PRO_AREA_ANIMATION_GRANULARITY = "Animation granularity"; public static final String PRO_AREA_ANIMATION_GRANULARITY_DIVIDER = "Animation granularity divider"; - public static final LongIntegerHashMap m_proj = new LongIntegerHashMap(); + public static final LongIntegerHashMap m_proj = new LongIntegerHashMap<>(); public static final String[] s_areaflags = {"Trap not visible", "Trap visible", "Triggered by inanimates", "Triggered by condition", "Delayed trigger", "Secondary projectile", "Fragments", "Affect only enemies", "Affect only allies*;Only in combination with \"Affect only enemies (6)\"", diff --git a/src/org/infinity/resource/pro/ProResource.java b/src/org/infinity/resource/pro/ProResource.java index 6db7baa99..869ca4b20 100644 --- a/src/org/infinity/resource/pro/ProResource.java +++ b/src/org/infinity/resource/pro/ProResource.java @@ -97,7 +97,7 @@ public final class ProResource extends AbstractStruct implements Resource, HasVi "Touch projectile", "Negate IDS1", "Negate IDS2", "Use either IDS", "Delayed payload", "Limited path count", "IWD style check", "Caster affected"}; - public static final LongIntegerHashMap m_projtype = new LongIntegerHashMap(); + public static final LongIntegerHashMap m_projtype = new LongIntegerHashMap<>(); static { m_projtype.put(1L, "No BAM"); m_projtype.put(2L, "Single target"); diff --git a/src/org/infinity/resource/pro/ProSingleType.java b/src/org/infinity/resource/pro/ProSingleType.java index 51909ad60..75631c4e4 100644 --- a/src/org/infinity/resource/pro/ProSingleType.java +++ b/src/org/infinity/resource/pro/ProSingleType.java @@ -40,7 +40,7 @@ public final class ProSingleType extends AbstractStruct implements AddRemovable public static final String PRO_SINGLE_TRAILING_ANIMATION_DELAY_FMT = "Trailing animation delay %d"; public static final String PRO_SINGLE_TRAIL_FLAGS = "Trail flags"; - public static final LongIntegerHashMap m_facetarget = new LongIntegerHashMap(); + public static final LongIntegerHashMap m_facetarget = new LongIntegerHashMap<>(); public static final String[] s_flags = {"No flags set", "Colored BAM", "Creates smoke", "Colored smoke", "Not light source", "Modify for height", "Casts shadow", "Light spot enabled", "Translucent", "Mid-level brighten", "Blended"}; diff --git a/src/org/infinity/resource/sound/WavBuffer.java b/src/org/infinity/resource/sound/WavBuffer.java index 756ce74f6..610a0450a 100644 --- a/src/org/infinity/resource/sound/WavBuffer.java +++ b/src/org/infinity/resource/sound/WavBuffer.java @@ -152,7 +152,7 @@ private static class WaveFmt private static final short ID_TYPE_PCM = 0x01; // PCM private static final short ID_TYPE_ADPCM = 0x11; // IMA ADPCM private static final HashSet s_audioTypes = - new HashSet(Arrays.asList(new Short[]{ID_TYPE_PCM, ID_TYPE_ADPCM})); + new HashSet<>(Arrays.asList(new Short[]{ID_TYPE_PCM, ID_TYPE_ADPCM})); private final AudioOverride override; private int sampleRate, samplesPerBlock, samplesPerChannel, dataSize; diff --git a/src/org/infinity/resource/spl/Viewer.java b/src/org/infinity/resource/spl/Viewer.java index 303188b52..ce2fbf65e 100644 --- a/src/org/infinity/resource/spl/Viewer.java +++ b/src/org/infinity/resource/spl/Viewer.java @@ -28,8 +28,8 @@ public final class Viewer extends JPanel { - private static final HashMap SpellPrefix = new HashMap(); - private static final HashMap SpellType = new HashMap(); + private static final HashMap SpellPrefix = new HashMap<>(); + private static final HashMap SpellType = new HashMap<>(); static { SpellPrefix.put(Integer.valueOf(0), "MARW"); SpellPrefix.put(Integer.valueOf(1), "SPPR"); diff --git a/src/org/infinity/resource/text/modes/BCSFoldParser.java b/src/org/infinity/resource/text/modes/BCSFoldParser.java index 2b21ad00a..f121393b7 100644 --- a/src/org/infinity/resource/text/modes/BCSFoldParser.java +++ b/src/org/infinity/resource/text/modes/BCSFoldParser.java @@ -32,7 +32,7 @@ public BCSFoldParser() @Override public List getFolds(RSyntaxTextArea textArea) { - List folds = new ArrayList(); + List folds = new ArrayList<>(); Fold curFold = null; int lineCount = textArea.getLineCount(); diff --git a/src/org/infinity/resource/text/modes/GLSLTokenMaker.java b/src/org/infinity/resource/text/modes/GLSLTokenMaker.java index 5ab5660e6..c7023d22b 100644 --- a/src/org/infinity/resource/text/modes/GLSLTokenMaker.java +++ b/src/org/infinity/resource/text/modes/GLSLTokenMaker.java @@ -3781,6 +3781,7 @@ public String[] getLineCommentStartAndEnd(int languageIndex) { * @return The first {@code Token} in a linked list representing * the syntax highlighted text. */ + @Override public Token getTokenList(Segment text, int initialTokenType, int startOffset) { resetTokenList(); @@ -3919,6 +3920,7 @@ public final int yystate() { * * @param newState the new lexical state */ + @Override public final void yybegin(int newState) { zzLexicalState = newState; } diff --git a/src/org/infinity/resource/vef/AbstractComponent.java b/src/org/infinity/resource/vef/AbstractComponent.java index 7bb599cbd..e1a0da887 100644 --- a/src/org/infinity/resource/vef/AbstractComponent.java +++ b/src/org/infinity/resource/vef/AbstractComponent.java @@ -52,7 +52,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception VefType type = new VefType(buffer, offset + 12, 4); addField(type); - List list = new ArrayList(); + List list = new ArrayList<>(); offset = type.readAttributes(buffer, offset + 16, list); addFields(getFields().size() - 1, list); diff --git a/src/org/infinity/resource/video/AudioQueue.java b/src/org/infinity/resource/video/AudioQueue.java index c0541aaa5..c18281f2d 100644 --- a/src/org/infinity/resource/video/AudioQueue.java +++ b/src/org/infinity/resource/video/AudioQueue.java @@ -19,7 +19,7 @@ public class AudioQueue */ public AudioQueue() { - deque = new ArrayDeque(); + deque = new ArrayDeque<>(); } /** diff --git a/src/org/infinity/resource/video/MveDecoder.java b/src/org/infinity/resource/video/MveDecoder.java index 683737c63..24dda18b4 100644 --- a/src/org/infinity/resource/video/MveDecoder.java +++ b/src/org/infinity/resource/video/MveDecoder.java @@ -577,7 +577,7 @@ private MveInfo() width = height = 0; isPalette = true; - audioOutput = new Vector(AUDIOSTREAMS_MAX); + audioOutput = new Vector<>(AUDIOSTREAMS_MAX); for (int i = 0; i < AUDIOSTREAMS_MAX; i++) { audioOutput.add(null); } @@ -643,7 +643,7 @@ public static class MveChunk private MveChunk() { initialized = false; - segments = new ArrayList(10); + segments = new ArrayList<>(10); segmentIndex = 0; } diff --git a/src/org/infinity/resource/video/MveResource.java b/src/org/infinity/resource/video/MveResource.java index af236f073..6f34d6266 100644 --- a/src/org/infinity/resource/video/MveResource.java +++ b/src/org/infinity/resource/video/MveResource.java @@ -378,7 +378,7 @@ public static boolean convertAvi(ResourceEntry inEntry, Path outFile, Window par decoder.setDefaultAudioOutput(new AudioQueue()); // prebuffering audio and searching for first video frame - LinkedList audioQueue = new LinkedList(); + LinkedList audioQueue = new LinkedList<>(); while (decoder.hasNextFrame()) { decoder.processNextFrame(); if (!decoder.frameHasVideo()) { diff --git a/src/org/infinity/search/SearchFrame.java b/src/org/infinity/search/SearchFrame.java index e773d689b..93615f2be 100644 --- a/src/org/infinity/search/SearchFrame.java +++ b/src/org/infinity/search/SearchFrame.java @@ -294,7 +294,7 @@ else if (rbsto.isSelected()) List resources = ResourceFactory.getResources(selectedtype); String expr = tfield.getText().toLowerCase(Locale.ENGLISH); - List found = new ArrayList(); + List found = new ArrayList<>(); cards.show(bpanel, "Progress"); progress.setMaximum(resources.size()); for (int i = 0; i < resources.size(); i++) { diff --git a/src/org/infinity/search/SearchOptions.java b/src/org/infinity/search/SearchOptions.java index ffe445361..1a5afa891 100644 --- a/src/org/infinity/search/SearchOptions.java +++ b/src/org/infinity/search/SearchOptions.java @@ -278,7 +278,7 @@ public class SearchOptions public static final String VVC_Custom4 = "VVC.+Custom.3"; - private final HashMap mapOptions = new HashMap(); + private final HashMap mapOptions = new HashMap<>(); private final String resourceType; /** diff --git a/src/org/infinity/search/SearchResource.java b/src/org/infinity/search/SearchResource.java index 0c2cc27b1..27ead6983 100644 --- a/src/org/infinity/search/SearchResource.java +++ b/src/org/infinity/search/SearchResource.java @@ -3440,7 +3440,7 @@ protected void triggerActions(Object[] sources) /** Creates a dialog that allows to specify flags. */ - private static final class FlagsPanel extends BasePanel implements ActionListener + private static final class FlagsPanel extends BasePanel { private final int size; private final JCheckBox[] cbFlags; @@ -3581,7 +3581,7 @@ private void init(String[] table) /** Creates a dialog that allows to specify effect opcodes for CRE resources. */ - private static final class EffectsPanel extends BasePanel implements ActionListener + private static final class EffectsPanel extends BasePanel { private static final int MaxEntryCount = 16; @@ -3670,7 +3670,7 @@ private void init() /** Creates a dialog that allows to specify custom filters for the selected resource type. */ - private static final class CustomFilterPanel extends BasePanel implements ActionListener + private static final class CustomFilterPanel extends BasePanel { private static final int FILTER_STRING = 0; private static final int FILTER_NUMBER = 1; @@ -3950,7 +3950,7 @@ private void init() /** Creates a dialog that allows to specify spell effect timing modes. */ - private static final class TimingModePanel extends BasePanel implements ActionListener + private static final class TimingModePanel extends BasePanel { public static final int TIMING_MODE = 0; public static final int TIMING_DURATION = 1; @@ -4051,7 +4051,7 @@ private void init() /** Creates a dialog that allows to specify creature level ranges. */ - private static final class CreLevelPanel extends BasePanel implements ActionListener + private static final class CreLevelPanel extends BasePanel { private static final String[] LABELS = { "First class level:", @@ -4135,7 +4135,7 @@ private void init() /** Creates a dialog that allows to specify creature level ranges (IWD2-specific). */ - private static final class CreLevelIWD2Panel extends BasePanel implements ActionListener + private static final class CreLevelIWD2Panel extends BasePanel { public static final int LEVEL_TOTAL = 0; public static final int LEVEL_BARBARIAN = 1; @@ -4238,7 +4238,7 @@ private void init() /** Creates a dialog that allows to specify creature types. */ - private static final class CreTypePanel extends BasePanel implements ActionListener + private static final class CreTypePanel extends BasePanel { public static final int TYPE_GENERAL = 0; public static final int TYPE_CLASS = 1; @@ -4364,7 +4364,7 @@ private void init() /** Creates a dialog that allows to specify game-specific settings for CRE resources. */ - private static final class CreGameSpecificPanel extends BasePanel implements ActionListener + private static final class CreGameSpecificPanel extends BasePanel { public static final int TYPE_FEATS1 = 0; public static final int TYPE_FEATS2 = 1; @@ -4475,7 +4475,7 @@ private void init() } /** Panel with several filters by resource reference. */ - private static class ResourcesFilterPanel extends BasePanel implements ActionListener + private static class ResourcesFilterPanel extends BasePanel { private final JCheckBox[] cbLabel; private final JComboBox[] cbItems; @@ -4550,7 +4550,7 @@ public String getResourceName(int id) /** Creates a dialog that allows to specify usability flags. */ - private static final class ItmUsabilityPanel extends BasePanel implements ActionListener + private static final class ItmUsabilityPanel extends BasePanel { public static final int ITEM_UNUSABLE = 0; public static final int ITEM_KITSUNUSABLE1 = 1; @@ -4667,7 +4667,7 @@ private void init() /** Creates a dialog that allows to specify minimum stats ranges. */ - private static final class ItmStatsPanel extends BasePanel implements ActionListener + private static final class ItmStatsPanel extends BasePanel { // supported stats public static final int STAT_LEVEL = 0; @@ -4778,7 +4778,7 @@ private void init() /** Creates a dialog that allows to specify item ability properties. */ - private static final class ItmAbilityPanel extends BasePanel implements ActionListener + private static final class ItmAbilityPanel extends BasePanel { private static final int ITEM_TYPE = 0; private static final int ITEM_TARGET = 1; @@ -5127,7 +5127,7 @@ private void init() /** Creates a dialog that allows to specify spell ability properties. */ - private static final class SplAbilityPanel extends BasePanel implements ActionListener + private static final class SplAbilityPanel extends BasePanel { private static final int SPELL_TYPE = 0; private static final int SPELL_LOCATION = 1; @@ -5393,7 +5393,7 @@ private void init() /** Creates a dialog that allows to specify item categories allowed in STO resources. */ - private static final class StoCategoriesPanel extends BasePanel implements ActionListener + private static final class StoCategoriesPanel extends BasePanel { private static final int MaxEntryCount = 16; diff --git a/src/org/infinity/search/advanced/AdvancedSearchWorker.java b/src/org/infinity/search/advanced/AdvancedSearchWorker.java index e4a51c017..76ffc0293 100644 --- a/src/org/infinity/search/advanced/AdvancedSearchWorker.java +++ b/src/org/infinity/search/advanced/AdvancedSearchWorker.java @@ -186,7 +186,7 @@ private void collapseGroupFilters(Map, Set> groupCache Set groupSet = entry.getValue(); Map> structureMap = new HashMap<>(); for (StructEntry se : groupSet) { - Set structureSet = structureMap.computeIfAbsent(se.getParent(), e -> new HashSet()); + Set structureSet = structureMap.computeIfAbsent(se.getParent(), e -> new HashSet<>()); structureSet.add(se); } @@ -225,7 +225,7 @@ private void collapseGroupFilters(Map, Set> groupCache // Collect filters grouped by structure private void addGroupFilter(Map, Set> groupCache, StructEntry se, SearchOptions so) { - Set set = groupCache.computeIfAbsent(so.getStructure(), s -> new HashSet()); + Set set = groupCache.computeIfAbsent(so.getStructure(), s -> new HashSet<>()); set.add(se); } diff --git a/src/org/infinity/updater/UpdateInfo.java b/src/org/infinity/updater/UpdateInfo.java index b53a2603b..713945b7e 100644 --- a/src/org/infinity/updater/UpdateInfo.java +++ b/src/org/infinity/updater/UpdateInfo.java @@ -78,7 +78,7 @@ public enum FileType { private static final String ATTR_VERSION = "version"; private static final String ATTR_TYPE = "type"; - private final EnumMap releases = new EnumMap(ReleaseType.class); + private final EnumMap releases = new EnumMap<>(ReleaseType.class); private General general; private int version; @@ -279,7 +279,7 @@ private void parseGeneral(Element elemGeneral) throws Exception throw new Exception("Update.xml: Node \"" + NODE_GENERAL + "\" expected"); } - List serverList = new ArrayList(); + List serverList = new ArrayList<>(); List> infoList = new ArrayList<>(); NodeList children = elemGeneral.getChildNodes(); @@ -402,7 +402,7 @@ private void parseRelease(Element elemRelease) throws Exception // processing optional element "changelog" if (elemChangelog != null) { - changelog = new ArrayList(); + changelog = new ArrayList<>(); children = elemChangelog.getElementsByTagName(NODE_ENTRY); for (int idx = 0, size = children.getLength(); idx < size; idx++) { Element elem = (Element)children.item(idx); @@ -427,7 +427,7 @@ private void parseRelease(Element elemRelease) throws Exception // Manages "General" information public static class General { - private final List servers = new ArrayList(); + private final List servers = new ArrayList<>(); private final List> information = new ArrayList<>(); private General(List servers, List> information) throws Exception @@ -500,7 +500,7 @@ public boolean isValid() // Manages "Release" information public static class Release { - private final List changelog = new ArrayList(); + private final List changelog = new ArrayList<>(); private final ReleaseType type; private String fileName, link, linkManual, version, hash; private FileType linkType; diff --git a/src/org/infinity/updater/Updater.java b/src/org/infinity/updater/Updater.java index dd59b7543..380794d28 100644 --- a/src/org/infinity/updater/Updater.java +++ b/src/org/infinity/updater/Updater.java @@ -86,7 +86,7 @@ public class Updater private static Updater instance = null; - private final List serverList = new ArrayList(); + private final List serverList = new ArrayList<>(); private Preferences prefs; private String hash, version, timestamp; diff --git a/src/org/infinity/updater/UpdaterSettings.java b/src/org/infinity/updater/UpdaterSettings.java index 594a04039..2dbfd7c1a 100644 --- a/src/org/infinity/updater/UpdaterSettings.java +++ b/src/org/infinity/updater/UpdaterSettings.java @@ -480,9 +480,9 @@ public void changedUpdate(DocumentEvent e) // Manages server URL entries private class Server { - private final List listServer = new ArrayList(); - private final List listCheck = new ArrayList(); - private final List listValidated = new ArrayList(); + private final List listServer = new ArrayList<>(); + private final List listCheck = new ArrayList<>(); + private final List listValidated = new ArrayList<>(); private final int numServers; diff --git a/src/org/infinity/util/BinPack2D.java b/src/org/infinity/util/BinPack2D.java index 5b71d45fb..ab9e7542e 100644 --- a/src/org/infinity/util/BinPack2D.java +++ b/src/org/infinity/util/BinPack2D.java @@ -35,8 +35,8 @@ public enum HeuristicRules { CONTACT_POINT_RULE, } - private final List usedRectangles = new ArrayList(); - private final List freeRectangles = new ArrayList(); + private final List usedRectangles = new ArrayList<>(); + private final List freeRectangles = new ArrayList<>(); private int binWidth, binHeight; diff --git a/src/org/infinity/util/DataString.java b/src/org/infinity/util/DataString.java index d98854951..92dccb170 100644 --- a/src/org/infinity/util/DataString.java +++ b/src/org/infinity/util/DataString.java @@ -43,7 +43,7 @@ public class DataString implements CharSequence, Comparable */ public static DataString with(String s, T data) { - return new DataString(s, data); + return new DataString<>(s, data); } /** @@ -57,7 +57,7 @@ public static DataString with(String s, T data) */ public static DataString with(String s, T data, String fmt) { - return new DataString(s, data, fmt); + return new DataString<>(s, data, fmt); } /** diff --git a/src/org/infinity/util/FilteredListModel.java b/src/org/infinity/util/FilteredListModel.java index ed1b29a80..bb011e787 100644 --- a/src/org/infinity/util/FilteredListModel.java +++ b/src/org/infinity/util/FilteredListModel.java @@ -22,8 +22,8 @@ */ public class FilteredListModel extends AbstractListModel { - private final Vector list = new Vector(); - private final Vector filteredList = new Vector(); + private final Vector list = new Vector<>(); + private final Vector filteredList = new Vector<>(); private final ArrayList listeners = new ArrayList<>(); private boolean filtered; @@ -94,7 +94,7 @@ public ChangeListener[] getFilterChangeListeners() public void removeFilterChangeListener(ChangeListener listener) { if (listener != null) { - while (listeners.remove(listener)) ; + while (listeners.remove(listener)) {} } } diff --git a/src/org/infinity/util/IdsMap.java b/src/org/infinity/util/IdsMap.java index ab2b0ca32..5804da5d7 100644 --- a/src/org/infinity/util/IdsMap.java +++ b/src/org/infinity/util/IdsMap.java @@ -61,13 +61,13 @@ public int size() /** Returns a copy of the values contained in the IDS map. */ public List getAllValues() { - return new ArrayList(idsMap.values()); + return new ArrayList<>(idsMap.values()); } /** Returns a copy of the keys contained in the IDS map as a sorted set. */ public SortedSet getKeys() { - return new TreeSet(idsMap.keySet()); + return new TreeSet<>(idsMap.keySet()); } /** Returns the entry structure defined by the specified IDS value, or {@code null} otherwise. */ diff --git a/src/org/infinity/util/IniMapCache.java b/src/org/infinity/util/IniMapCache.java index 2f9799ade..907c42985 100644 --- a/src/org/infinity/util/IniMapCache.java +++ b/src/org/infinity/util/IniMapCache.java @@ -12,7 +12,7 @@ public class IniMapCache { - private static final HashMap map = new HashMap(); + private static final HashMap map = new HashMap<>(); public static void cacheInvalid(ResourceEntry entry) { diff --git a/src/org/infinity/util/IniMapEntry.java b/src/org/infinity/util/IniMapEntry.java index 9ea5739e8..156e94728 100644 --- a/src/org/infinity/util/IniMapEntry.java +++ b/src/org/infinity/util/IniMapEntry.java @@ -84,7 +84,7 @@ public static String[] splitValues(String value, String pattern) { String[] retVal = null; if (value != null) { - List results = new ArrayList(); + List results = new ArrayList<>(); try { Matcher matcher = Pattern.compile(pattern).matcher(value); while (matcher.find()) { @@ -112,7 +112,7 @@ public static int[] splitObjectValue(String value) { int[] retVal = null; if (value != null && Pattern.matches("^\\[(-?\\d+\\.?)+\\]$", value)) { - List results = new ArrayList(); + List results = new ArrayList<>(); Pattern p = Pattern.compile("-?\\d+"); Matcher m = p.matcher(value); while (m.find()) { @@ -141,7 +141,7 @@ public static int[] splitPositionValue(String value) { int[] retVal = null; if (value != null && Pattern.matches("^\\[[-0-9]+\\.[-0-9]+(:[0-9]+)?\\]$", value)) { - List results = new ArrayList(); + List results = new ArrayList<>(); Pattern p = Pattern.compile("-?\\d+"); Matcher m = p.matcher(value); while (m.find()) { diff --git a/src/org/infinity/util/LuaParser.java b/src/org/infinity/util/LuaParser.java index b96b6abe2..210738302 100644 --- a/src/org/infinity/util/LuaParser.java +++ b/src/org/infinity/util/LuaParser.java @@ -91,7 +91,7 @@ public static LuaEntry Parse(String data, String name, boolean exactMatch) throw name = Pattern.quote(name); LuaEntry root = new LuaEntry(0); - root.children = new ArrayList(); + root.children = new ArrayList<>(); // search for table definitions // Example: mytable = {"value1", "value2"} @@ -153,7 +153,7 @@ private static LuaEntry ParseElement(CharBuffer buffer, LuaEntry parent) throws if (retVal == null) retVal = (key.length() > 0) ? new LuaEntry(key.toString()) : new LuaEntry(parent); if (retVal.children == null) - retVal.children = new ArrayList(); + retVal.children = new ArrayList<>(); LuaEntry el = ParseElement(buffer, retVal); if (el != null) retVal.children.add(el); @@ -223,7 +223,7 @@ private static LuaEntry ParseElement(CharBuffer buffer, LuaEntry parent) throws buffer.position(buffer.position() + 4); } else if (ch == '{') { if (retVal.children == null) - retVal.children = new ArrayList(); + retVal.children = new ArrayList<>(); LuaEntry el = ParseElement(buffer, retVal); if (el != null) retVal.children.add(el); diff --git a/src/org/infinity/util/MapTree.java b/src/org/infinity/util/MapTree.java index c9144d61a..087225d4a 100644 --- a/src/org/infinity/util/MapTree.java +++ b/src/org/infinity/util/MapTree.java @@ -31,9 +31,9 @@ public class MapTree implements Cloneable /** Creates a path of MapTree objects, starting from the root node up to the specified node. */ public static Collection> getNodePath(MapTree node) { - Collection> retVal = new Vector>(); + Collection> retVal = new Vector<>(); if (node != null) { - Stack> stack = new Stack>(); + Stack> stack = new Stack<>(); MapTree curNode = node; while (curNode != null) { stack.push(curNode); @@ -71,7 +71,7 @@ public MapTree(K key, V value) throw new NullPointerException("key must not be null"); } this.parent = null; - this.children = new HashMap>(); + this.children = new HashMap<>(); this.key = key; this.value = value; } @@ -86,7 +86,7 @@ public MapTree(K key, V value) @SuppressWarnings("unchecked") public Object clone() { - MapTree node = new MapTree(key, value); + MapTree node = new MapTree<>(key, value); for (Iterator> iter = children.values().iterator(); iter.hasNext();) { node.addChild((MapTree)iter.next().clone()); } @@ -172,7 +172,7 @@ public MapTree findNode(K key) */ public Collection> findNodes(K key) { - Collection> retVal = new Vector>(); + Collection> retVal = new Vector<>(); if (key != null) { retVal = findNodesRecursive(retVal, parent, key, false); } @@ -246,7 +246,7 @@ public MapTree removeChild(K key) */ public Collection> removeChildren(Collection keys) { - Collection> retVal = new Vector>(); + Collection> retVal = new Vector<>(); if (keys != null && !keys.isEmpty()) { for (Iterator iter = keys.iterator(); iter.hasNext();) { MapTree node = removeChild(iter.next()); @@ -302,7 +302,7 @@ private static Collection> findNodesRecursive(Collection>(); + retVal = new Vector<>(); } if (firstMatch && !retVal.isEmpty()) { diff --git a/src/org/infinity/util/MassExporter.java b/src/org/infinity/util/MassExporter.java index 5b7c43005..5b61dd454 100644 --- a/src/org/infinity/util/MassExporter.java +++ b/src/org/infinity/util/MassExporter.java @@ -288,7 +288,7 @@ public void run() } } - selectedFiles = new ArrayList(1000); + selectedFiles = new ArrayList<>(1000); for (final String newVar : selectedTypes) { selectedFiles.addAll(ResourceFactory.getResources(newVar, extraDirs)); } diff --git a/src/org/infinity/util/ResourceStructure.java b/src/org/infinity/util/ResourceStructure.java index a074595e2..ec92d4297 100644 --- a/src/org/infinity/util/ResourceStructure.java +++ b/src/org/infinity/util/ResourceStructure.java @@ -29,7 +29,7 @@ public class ResourceStructure implements Cloneable public ResourceStructure() { super(); - list = new Vector(); + list = new Vector<>(); cursize = 0; } @@ -329,7 +329,7 @@ private Item(int type, int size, Object value) if (value instanceof byte[]) { this.value = StreamUtils.getByteBuffer((byte[])value); } else if (value instanceof ByteBuffer) { - this.value = (ByteBuffer)value; + this.value = value; } else { this.value = StreamUtils.getByteBuffer(size); } diff --git a/src/org/infinity/util/SimpleListModel.java b/src/org/infinity/util/SimpleListModel.java index cf0ad2ec9..b46ac3f1d 100644 --- a/src/org/infinity/util/SimpleListModel.java +++ b/src/org/infinity/util/SimpleListModel.java @@ -17,7 +17,7 @@ */ public class SimpleListModel extends AbstractListModel { - private final Vector delegate = new Vector(); + private final Vector delegate = new Vector<>(); /** Constructs an empty ListModel object with a default capacity of 10 elements. */ public SimpleListModel() diff --git a/src/org/infinity/util/StringTable.java b/src/org/infinity/util/StringTable.java index 42f81b1f0..f6539390b 100644 --- a/src/org/infinity/util/StringTable.java +++ b/src/org/infinity/util/StringTable.java @@ -1475,7 +1475,7 @@ private void _exportText(Path outFile, ProgressCallback callback) throws IOExcep //-------------------------- INNER CLASSES -------------------------- // Manages a single string entry - public static class StringEntry extends AbstractStruct implements Cloneable + public static class StringEntry extends AbstractStruct { // Default entry for non-existing indices private static final StringEntry INVALID = new StringEntry(null, FLAGS_HAS_TEXT, "", 0, 0, "No such index"); diff --git a/src/org/infinity/util/Table2da.java b/src/org/infinity/util/Table2da.java index 656ce8cb3..57e717c7a 100644 --- a/src/org/infinity/util/Table2da.java +++ b/src/org/infinity/util/Table2da.java @@ -21,8 +21,8 @@ public class Table2da /** Row index pointing to row labels. */ public static final int ROW_HEADER = 0; - private final List header = new ArrayList(); - private final List> table = new ArrayList>(); + private final List header = new ArrayList<>(); + private final List> table = new ArrayList<>(); private final ResourceEntry entry; private String defaultValue; @@ -142,7 +142,7 @@ private void init(ResourceEntry entry) String curLine = lines[idx].trim(); String[] elements = curLine.split("\\s+"); if (elements.length > 0 && !elements[0].isEmpty()) { - List listLine = new ArrayList(); + List listLine = new ArrayList<>(); for (final String s: elements) { if (!s.isEmpty()) { listLine.add(s); diff --git a/src/org/infinity/util/Table2daCache.java b/src/org/infinity/util/Table2daCache.java index 8dbc6c294..49a9023e8 100644 --- a/src/org/infinity/util/Table2daCache.java +++ b/src/org/infinity/util/Table2daCache.java @@ -11,7 +11,7 @@ public class Table2daCache { - private static final HashMap map = new HashMap(); + private static final HashMap map = new HashMap<>(); /** Removes the specified 2DA resource from the cache. */ public static synchronized void cacheInvalid(ResourceEntry entry) diff --git a/src/org/infinity/util/io/DlcManager.java b/src/org/infinity/util/io/DlcManager.java index 36c22f7b6..3de12e136 100644 --- a/src/org/infinity/util/io/DlcManager.java +++ b/src/org/infinity/util/io/DlcManager.java @@ -76,7 +76,7 @@ private DlcManager() this.fileSystems = new HashMap<>(); } - private FileSystem _register(Path dlcFile) throws IOException + private FileSystem _register(Path dlcFile) { FileSystem fs = _getDlc(dlcFile); if (fs == null) { @@ -124,7 +124,7 @@ private Path _queryKey(Path path) return null; } - private FileSystem _validateDlc(Path dlcFile) throws IOException + private FileSystem _validateDlc(Path dlcFile) { if (dlcFile == null || !FileEx.create(dlcFile).isFile()) { return null; diff --git a/src/org/infinity/util/io/zip/DlcFileAttributes.java b/src/org/infinity/util/io/zip/DlcFileAttributes.java index 6ca60cb1f..aa7025beb 100644 --- a/src/org/infinity/util/io/zip/DlcFileAttributes.java +++ b/src/org/infinity/util/io/zip/DlcFileAttributes.java @@ -110,6 +110,7 @@ public byte[] comment() return null; } + @Override public String toString() { StringBuilder sb = new StringBuilder(1024); diff --git a/src/org/infinity/util/io/zip/DlcFileStore.java b/src/org/infinity/util/io/zip/DlcFileStore.java index aa230e320..4ebacba14 100644 --- a/src/org/infinity/util/io/zip/DlcFileStore.java +++ b/src/org/infinity/util/io/zip/DlcFileStore.java @@ -113,7 +113,7 @@ public V getFileStoreAttributeView(Class t if (type == null) { throw new NullPointerException(); } - return (V) null; + return null; } @Override diff --git a/src/org/infinity/util/io/zip/DlcFileSystem.java b/src/org/infinity/util/io/zip/DlcFileSystem.java index 01e839336..40520bfcc 100644 --- a/src/org/infinity/util/io/zip/DlcFileSystem.java +++ b/src/org/infinity/util/io/zip/DlcFileSystem.java @@ -11,11 +11,11 @@ import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; +import java.nio.channels.FileChannel.MapMode; import java.nio.channels.FileLock; import java.nio.channels.ReadableByteChannel; import java.nio.channels.SeekableByteChannel; import java.nio.channels.WritableByteChannel; -import java.nio.channels.FileChannel.MapMode; import java.nio.file.AccessMode; import java.nio.file.ClosedFileSystemException; import java.nio.file.DirectoryStream; @@ -61,9 +61,8 @@ */ public class DlcFileSystem extends FileSystem { - private static final Set supportedFileAttributeViews = - Collections.unmodifiableSet(new HashSet( - Arrays.asList(DlcFileAttributeView.VIEW_BASIC, DlcFileAttributeView.VIEW_ZIP))); + private static final Set supportedFileAttributeViews = Collections.unmodifiableSet( + new HashSet<>(Arrays.asList(DlcFileAttributeView.VIEW_BASIC, DlcFileAttributeView.VIEW_ZIP))); private static final String GLOB_SYNTAX = "glob"; private static final String REGEX_SYNTAX = "regex"; @@ -265,7 +264,7 @@ FileStore getFileStore(DlcPath path) return new DlcFileStore(path); } - DlcFileAttributes getFileAttributes(byte[] path) throws IOException + DlcFileAttributes getFileAttributes(byte[] path) { ZipNode folder = null; beginRead(); @@ -282,7 +281,7 @@ DlcFileAttributes getFileAttributes(byte[] path) throws IOException } } - boolean exists(byte[] path) throws IOException + boolean exists(byte[] path) { beginRead(); try { @@ -293,7 +292,7 @@ boolean exists(byte[] path) throws IOException } } - boolean isDirectory(byte[] path) throws IOException + boolean isDirectory(byte[] path) { beginRead(); try { @@ -688,7 +687,7 @@ private void checkOpen() throws IOException } } - private void checkWritable() throws IOException + private void checkWritable() { throw new ReadOnlyFileSystemException(); } @@ -717,7 +716,7 @@ private final void endRead() rwlock.readLock().unlock(); } - private void ensureOpen() throws IOException + private void ensureOpen() { if (!isOpen) { throw new ClosedFileSystemException(); diff --git a/src/org/infinity/util/io/zip/DlcPath.java b/src/org/infinity/util/io/zip/DlcPath.java index 8f783319f..4297dc168 100644 --- a/src/org/infinity/util/io/zip/DlcPath.java +++ b/src/org/infinity/util/io/zip/DlcPath.java @@ -741,7 +741,7 @@ private byte[] resolve0() ///////////////////////////////////////////////////////////////////// - void createDirectory(FileAttribute... attrs) throws IOException + void createDirectory(FileAttribute... attrs) { throw new UnsupportedOperationException(); } @@ -763,12 +763,12 @@ DirectoryStream newDirectoryStream(Filter filter) throws IOE return new DlcDirectoryStream(this, filter); } - void delete() throws IOException + void delete() { throw new UnsupportedOperationException(); } - void deleteIfExists() throws IOException + void deleteIfExists() { throw new UnsupportedOperationException(); } @@ -782,12 +782,12 @@ DlcFileAttributes getAttributes() throws IOException return zfas; } - void setAttribute(String attribute, Object value, LinkOption... options) throws IOException + void setAttribute(String attribute, Object value, LinkOption... options) { throw new UnsupportedOperationException(); } - void setTimes(FileTime mtime, FileTime atime, FileTime ctime) throws IOException + void setTimes(FileTime mtime, FileTime atime, FileTime ctime) { throw new UnsupportedOperationException(); } @@ -882,24 +882,20 @@ boolean exists() if (path.length == 1 && path[0] == '/') { return true; } - try { - return dfs.exists(getResolvedPath()); - } catch (IOException x) { - } - return false; + return dfs.exists(getResolvedPath()); } - OutputStream newOutputStream(OpenOption... options) throws IOException + OutputStream newOutputStream(OpenOption... options) { throw new UnsupportedOperationException(); } - void move(DlcPath target, CopyOption... options) throws IOException + void move(DlcPath target, CopyOption... options) { throw new UnsupportedOperationException(); } - void copy(DlcPath target, CopyOption... options) throws IOException + void copy(DlcPath target, CopyOption... options) { throw new UnsupportedOperationException(); } diff --git a/src/org/infinity/util/io/zip/ZipBaseHeader.java b/src/org/infinity/util/io/zip/ZipBaseHeader.java index f423439a7..a98d123fd 100644 --- a/src/org/infinity/util/io/zip/ZipBaseHeader.java +++ b/src/org/infinity/util/io/zip/ZipBaseHeader.java @@ -34,6 +34,16 @@ protected ZipBaseHeader(long offset, long signature) this.signature = signature; } + @Override + public int hashCode() + { + int hash = 7; + hash = 31 * hash + Long.hashCode(offset); + hash = 31 * hash + Long.hashCode(size); + hash = 31 * hash + Long.hashCode(signature); + return hash; + } + @Override public boolean equals(Object o) { diff --git a/src/org/infinity/util/io/zip/ZipCentralEndHeader.java b/src/org/infinity/util/io/zip/ZipCentralEndHeader.java index ca67adb51..f46431735 100644 --- a/src/org/infinity/util/io/zip/ZipCentralEndHeader.java +++ b/src/org/infinity/util/io/zip/ZipCentralEndHeader.java @@ -4,7 +4,31 @@ package org.infinity.util.io.zip; -import static org.infinity.util.io.zip.ZipConstants.*; +import static org.infinity.util.io.zip.ZipConstants.CENCOM; +import static org.infinity.util.io.zip.ZipConstants.CENEXT; +import static org.infinity.util.io.zip.ZipConstants.CENHDR; +import static org.infinity.util.io.zip.ZipConstants.CENNAM; +import static org.infinity.util.io.zip.ZipConstants.CENSIG; +import static org.infinity.util.io.zip.ZipConstants.ENDCOM; +import static org.infinity.util.io.zip.ZipConstants.ENDHDR; +import static org.infinity.util.io.zip.ZipConstants.ENDSIG; +import static org.infinity.util.io.zip.ZipConstants.END_MAXLEN; +import static org.infinity.util.io.zip.ZipConstants.LL; +import static org.infinity.util.io.zip.ZipConstants.LOCHDR; +import static org.infinity.util.io.zip.ZipConstants.LOCLEN; +import static org.infinity.util.io.zip.ZipConstants.LOCSIG; +import static org.infinity.util.io.zip.ZipConstants.LOCSIZ; +import static org.infinity.util.io.zip.ZipConstants.READBLOCKSZ; +import static org.infinity.util.io.zip.ZipConstants.ZIP64_ENDHDR; +import static org.infinity.util.io.zip.ZipConstants.ZIP64_ENDOFF; +import static org.infinity.util.io.zip.ZipConstants.ZIP64_ENDSIG; +import static org.infinity.util.io.zip.ZipConstants.ZIP64_ENDSIZ; +import static org.infinity.util.io.zip.ZipConstants.ZIP64_ENDTOT; +import static org.infinity.util.io.zip.ZipConstants.ZIP64_LOCHDR; +import static org.infinity.util.io.zip.ZipConstants.ZIP64_LOCOFF; +import static org.infinity.util.io.zip.ZipConstants.ZIP64_LOCSIG; +import static org.infinity.util.io.zip.ZipConstants.ZIP64_MINVAL; +import static org.infinity.util.io.zip.ZipConstants.ZIP64_MINVAL32; import java.io.IOException; import java.nio.ByteBuffer; @@ -40,7 +64,7 @@ public class ZipCentralEndHeader extends ZipBaseHeader public byte[] comment; - public ZipCentralEndHeader(ByteBuffer buffer, long absOffset) throws IOException + public ZipCentralEndHeader(ByteBuffer buffer, long absOffset) { super(absOffset, buffer.getInt()); long headerStart = buffer.position() - 4L; diff --git a/src/org/infinity/util/io/zip/ZipCentralHeader.java b/src/org/infinity/util/io/zip/ZipCentralHeader.java index 48070ca79..0f49b6784 100644 --- a/src/org/infinity/util/io/zip/ZipCentralHeader.java +++ b/src/org/infinity/util/io/zip/ZipCentralHeader.java @@ -4,7 +4,10 @@ package org.infinity.util.io.zip; -import static org.infinity.util.io.zip.ZipConstants.*; +import static org.infinity.util.io.zip.ZipConstants.CENSIG; +import static org.infinity.util.io.zip.ZipConstants.LOCEXT; +import static org.infinity.util.io.zip.ZipConstants.LOCHDR; +import static org.infinity.util.io.zip.ZipConstants.LOCNAM; import java.io.IOException; import java.nio.ByteBuffer; @@ -39,7 +42,7 @@ public class ZipCentralHeader extends ZipLocalHeader // Cached local header private ZipLocalHeader localHeader; - public ZipCentralHeader(ByteBuffer buffer, long absOffset) throws IOException + public ZipCentralHeader(ByteBuffer buffer, long absOffset) { super(absOffset, buffer.getInt() & 0xffffffffL); long headerStart = buffer.position() - 4L; @@ -137,7 +140,7 @@ public int compareTo(ZipBaseHeader o) } else { return 0; } - } else if (o instanceof ZipBaseHeader) { + } else if (o != null) { return super.compareTo(o); } else { throw new NullPointerException(); diff --git a/src/org/infinity/util/io/zip/ZipLocalHeader.java b/src/org/infinity/util/io/zip/ZipLocalHeader.java index 726a79d84..3602586b6 100644 --- a/src/org/infinity/util/io/zip/ZipLocalHeader.java +++ b/src/org/infinity/util/io/zip/ZipLocalHeader.java @@ -4,9 +4,8 @@ package org.infinity.util.io.zip; -import static org.infinity.util.io.zip.ZipConstants.*; +import static org.infinity.util.io.zip.ZipConstants.LOCSIG; -import java.io.IOException; import java.nio.ByteBuffer; /** @@ -47,12 +46,12 @@ public class ZipLocalHeader extends ZipBaseHeader /** Optional extra data. (Is never {@code null}) */ public byte[] extra; - protected ZipLocalHeader(long offset, long signature) throws IOException + protected ZipLocalHeader(long offset, long signature) { super(offset, signature); } - public ZipLocalHeader(ByteBuffer buffer, long absOffset) throws IOException + public ZipLocalHeader(ByteBuffer buffer, long absOffset) { super(absOffset, buffer.getInt()); long headerStart = buffer.position() - 4L; diff --git a/src/org/infinity/util/tuples/Couple.java b/src/org/infinity/util/tuples/Couple.java index 3ef353563..2432b2281 100644 --- a/src/org/infinity/util/tuples/Couple.java +++ b/src/org/infinity/util/tuples/Couple.java @@ -26,7 +26,7 @@ public class Couple extends Tuple implements TupleValue0, TupleValue1 Couple with(A value0, B value1) { - return new Couple(value0, value1); + return new Couple<>(value0, value1); } /** @@ -43,7 +43,7 @@ public static Couple fromArray(T[] arr) if (arr.length < SIZE) { throw new IllegalArgumentException(String.format("Array must contain at least %d elements", SIZE)); } - return new Couple(arr[0], arr[1]); + return new Couple<>(arr[0], arr[1]); } /** @@ -63,7 +63,7 @@ public static Couple fromCollection(Collection col) Iterator iter = col.iterator(); T el0 = iter.next(); T el1 = iter.next(); - return new Couple(el0, el1); + return new Couple<>(el0, el1); } /** @@ -100,7 +100,7 @@ public static Couple fromIterable(Iterable iterator, int index) T el0 = iter.hasNext() ? iter.next() : null; T el1 = iter.hasNext() ? iter.next() : null; - return new Couple(el0, el1); + return new Couple<>(el0, el1); } /** diff --git a/src/org/infinity/util/tuples/Monuple.java b/src/org/infinity/util/tuples/Monuple.java index 50e770cee..a1da228aa 100644 --- a/src/org/infinity/util/tuples/Monuple.java +++ b/src/org/infinity/util/tuples/Monuple.java @@ -24,7 +24,7 @@ public class Monuple extends Tuple implements TupleValue0 */ public static Monuple with(A value0) { - return new Monuple(value0); + return new Monuple<>(value0); } /** @@ -41,7 +41,7 @@ public static Monuple fromArray(T[] arr) if (arr.length < SIZE) { throw new IllegalArgumentException("Array must contain at least 1 element"); } - return new Monuple(arr[0]); + return new Monuple<>(arr[0]); } /** @@ -59,7 +59,7 @@ public static Monuple fromCollection(Collection col) throw new IllegalArgumentException("Collection must contain at least 1 element"); } Iterator iter = col.iterator(); - return new Monuple(iter.next()); + return new Monuple<>(iter.next()); } /** @@ -100,7 +100,7 @@ public static Monuple fromIterable(Iterable iterator, int index) } else { el0 = null; } - return new Monuple(el0); + return new Monuple<>(el0); } /** diff --git a/src/org/infinity/util/tuples/Quadruple.java b/src/org/infinity/util/tuples/Quadruple.java index 37efcb4ae..6cd0f825a 100644 --- a/src/org/infinity/util/tuples/Quadruple.java +++ b/src/org/infinity/util/tuples/Quadruple.java @@ -31,7 +31,7 @@ public class Quadruple extends Tuple */ public static Quadruple with(A value0, B value1, C value2, D value3) { - return new Quadruple(value0, value1, value2, value3); + return new Quadruple<>(value0, value1, value2, value3); } /** @@ -48,7 +48,7 @@ public static Quadruple fromArray(T[] arr) if (arr.length < SIZE) { throw new IllegalArgumentException(String.format("Array must contain at least %d elements", SIZE)); } - return new Quadruple(arr[0], arr[1], arr[2], arr[3]); + return new Quadruple<>(arr[0], arr[1], arr[2], arr[3]); } /** @@ -70,7 +70,7 @@ public static Quadruple fromCollection(Collection col) T el1 = iter.next(); T el2 = iter.next(); T el3 = iter.next(); - return new Quadruple(el0, el1, el2, el3); + return new Quadruple<>(el0, el1, el2, el3); } /** @@ -109,7 +109,7 @@ public static Quadruple fromIterable(Iterable iterator, int i T el1 = iter.hasNext() ? iter.next() : null; T el2 = iter.hasNext() ? iter.next() : null; T el3 = iter.hasNext() ? iter.next() : null; - return new Quadruple(el0, el1, el2, el3); + return new Quadruple<>(el0, el1, el2, el3); } /** diff --git a/src/org/infinity/util/tuples/Quintuple.java b/src/org/infinity/util/tuples/Quintuple.java index 18d2381dd..b3b862303 100644 --- a/src/org/infinity/util/tuples/Quintuple.java +++ b/src/org/infinity/util/tuples/Quintuple.java @@ -32,7 +32,7 @@ public class Quintuple extends Tuple */ public static Quintuple with(A value0, B value1, C value2, D value3, E value4) { - return new Quintuple(value0, value1, value2, value3, value4); + return new Quintuple<>(value0, value1, value2, value3, value4); } /** @@ -49,7 +49,7 @@ public static Quintuple fromArray(T[] arr) if (arr.length < SIZE) { throw new IllegalArgumentException(String.format("Array must contain at least %d elements", SIZE)); } - return new Quintuple(arr[0], arr[1], arr[2], arr[3], arr[4]); + return new Quintuple<>(arr[0], arr[1], arr[2], arr[3], arr[4]); } /** @@ -72,7 +72,7 @@ public static Quintuple fromCollection(Collection col) T el2 = iter.next(); T el3 = iter.next(); T el4 = iter.next(); - return new Quintuple(el0, el1, el2, el3, el4); + return new Quintuple<>(el0, el1, el2, el3, el4); } /** @@ -112,7 +112,7 @@ public static Quintuple fromIterable(Iterable iterator, in T el2 = iter.hasNext() ? iter.next() : null; T el3 = iter.hasNext() ? iter.next() : null; T el4 = iter.hasNext() ? iter.next() : null; - return new Quintuple(el0, el1, el2, el3, el4); + return new Quintuple<>(el0, el1, el2, el3, el4); } /** diff --git a/src/org/infinity/util/tuples/Sextuple.java b/src/org/infinity/util/tuples/Sextuple.java index 890eae308..43a0c5ac3 100644 --- a/src/org/infinity/util/tuples/Sextuple.java +++ b/src/org/infinity/util/tuples/Sextuple.java @@ -33,7 +33,7 @@ public class Sextuple extends Tuple */ public static Sextuple with(A value0, B value1, C value2, D value3, E value4, F value5) { - return new Sextuple(value0, value1, value2, value3, value4, value5); + return new Sextuple<>(value0, value1, value2, value3, value4, value5); } /** @@ -50,7 +50,7 @@ public static Sextuple fromArray(T[] arr) if (arr.length < SIZE) { throw new IllegalArgumentException(String.format("Array must contain at least %d elements", SIZE)); } - return new Sextuple(arr[0], arr[1], arr[2], arr[3], arr[4], arr[5]); + return new Sextuple<>(arr[0], arr[1], arr[2], arr[3], arr[4], arr[5]); } /** @@ -74,7 +74,7 @@ public static Sextuple fromCollection(Collection col) T el3 = iter.next(); T el4 = iter.next(); T el5 = iter.next(); - return new Sextuple(el0, el1, el2, el3, el4, el5); + return new Sextuple<>(el0, el1, el2, el3, el4, el5); } /** @@ -115,7 +115,7 @@ public static Sextuple fromIterable(Iterable iterator, T el3 = iter.hasNext() ? iter.next() : null; T el4 = iter.hasNext() ? iter.next() : null; T el5 = iter.hasNext() ? iter.next() : null; - return new Sextuple(el0, el1, el2, el3, el4, el5); + return new Sextuple<>(el0, el1, el2, el3, el4, el5); } /** diff --git a/src/org/infinity/util/tuples/Triple.java b/src/org/infinity/util/tuples/Triple.java index 5b7b89916..9739086de 100644 --- a/src/org/infinity/util/tuples/Triple.java +++ b/src/org/infinity/util/tuples/Triple.java @@ -28,7 +28,7 @@ public class Triple extends Tuple implements TupleValue0, TupleValue */ public static Triple with(A value0, B value1, C value2) { - return new Triple(value0, value1, value2); + return new Triple<>(value0, value1, value2); } /** @@ -45,7 +45,7 @@ public static Triple fromArray(T[] arr) if (arr.length < SIZE) { throw new IllegalArgumentException(String.format("Array must contain at least %d elements", SIZE)); } - return new Triple(arr[0], arr[1], arr[2]); + return new Triple<>(arr[0], arr[1], arr[2]); } /** @@ -66,7 +66,7 @@ public static Triple fromCollection(Collection col) T el0 = iter.next(); T el1 = iter.next(); T el2 = iter.next(); - return new Triple(el0, el1, el2); + return new Triple<>(el0, el1, el2); } /** @@ -104,7 +104,7 @@ public static Triple fromIterable(Iterable iterator, int index) T el0 = iter.hasNext() ? iter.next() : null; T el1 = iter.hasNext() ? iter.next() : null; T el2 = iter.hasNext() ? iter.next() : null; - return new Triple(el0, el1, el2); + return new Triple<>(el0, el1, el2); } /** diff --git a/src/org/infinity/util/tuples/Tuple.java b/src/org/infinity/util/tuples/Tuple.java index 1c12b66c9..6ced051c4 100644 --- a/src/org/infinity/util/tuples/Tuple.java +++ b/src/org/infinity/util/tuples/Tuple.java @@ -66,6 +66,7 @@ public Object setValue(int pos, Object value) /** * Returns an iterator over the elements in this tuple in proper sequence. */ + @Override public Iterator iterator() { return this.valueList.iterator(); @@ -204,7 +205,7 @@ public int lastIndexOf(Object o) */ public List toList() { - return Collections.unmodifiableList(new ArrayList(valueList)); + return Collections.unmodifiableList(new ArrayList<>(valueList)); } /** From a5d74d983d7c2834bfcbe47653fd969168301fdb Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sat, 24 Apr 2021 18:38:01 +0200 Subject: [PATCH 150/158] Apply try-with-resources statements to more resources --- .../infinity/gui/converter/ConvertToBam.java | 3 +- .../resource/cre/decoder/SpriteDecoder.java | 4 +- .../resource/graphics/GraphicsResource.java | 5 ++- .../resource/graphics/PltResource.java | 5 ++- .../resource/graphics/PvrDecoder.java | 12 +++--- .../infinity/resource/other/TtfResource.java | 3 +- .../infinity/resource/sound/AudioPlayer.java | 40 +++++++++---------- 7 files changed, 33 insertions(+), 39 deletions(-) diff --git a/src/org/infinity/gui/converter/ConvertToBam.java b/src/org/infinity/gui/converter/ConvertToBam.java index 71c6600ee..485236861 100644 --- a/src/org/infinity/gui/converter/ConvertToBam.java +++ b/src/org/infinity/gui/converter/ConvertToBam.java @@ -2553,8 +2553,7 @@ private boolean framesAddImage(int listIndex, ResourceEntry entry, int frameInde { boolean retVal = false; if (listIndex >= 0 && entry != null) { - try { - InputStream is = entry.getResourceDataAsStream(); + try (InputStream is = entry.getResourceDataAsStream()) { BufferedImage[] images; if (entry.getExtension().equalsIgnoreCase("gif")) { // Potential GIF animation diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index 34446834b..3c13aa64e 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -61,8 +61,6 @@ import org.infinity.util.Misc; import org.infinity.util.tuples.Couple; -import sun.security.rsa.RSAUtil.KeyType; - /** * Specialized BAM decoder for creature animation sprites. */ @@ -327,7 +325,7 @@ public T getAttribute(DecoderAttribute att) /** * Stores the attribute key and value along with the autodetected data type. * @param key the attribute name. - * @param value the value in one of the data types covered by {@link KeyType}. + * @param value the value in one of the data types covered by {@link DecoderAttribute.DataType}. */ protected void setAttribute(DecoderAttribute att, Object value) { diff --git a/src/org/infinity/resource/graphics/GraphicsResource.java b/src/org/infinity/resource/graphics/GraphicsResource.java index ca5c46522..1195eb1d3 100644 --- a/src/org/infinity/resource/graphics/GraphicsResource.java +++ b/src/org/infinity/resource/graphics/GraphicsResource.java @@ -9,6 +9,7 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.image.BufferedImage; +import java.io.InputStream; import java.nio.ByteBuffer; import javax.imageio.ImageIO; @@ -160,8 +161,8 @@ private void init() throws Exception } } if (image == null) { - try { - image = ImageIO.read(entry.getResourceDataAsStream()); + try (InputStream is = entry.getResourceDataAsStream()) { + image = ImageIO.read(is); } catch (Exception e) { image = null; throw new Exception("Unsupported graphics format"); diff --git a/src/org/infinity/resource/graphics/PltResource.java b/src/org/infinity/resource/graphics/PltResource.java index 57ed6813d..0e249d1e4 100644 --- a/src/org/infinity/resource/graphics/PltResource.java +++ b/src/org/infinity/resource/graphics/PltResource.java @@ -19,6 +19,7 @@ import java.awt.image.DataBufferInt; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -650,8 +651,8 @@ private void init() { ResourceEntry entry = ResourceFactory.getResourceEntry(PAL_RESOURCE); if (entry != null) { - try { - BufferedImage image = ImageIO.read(entry.getResourceDataAsStream()); + try (InputStream is = entry.getResourceDataAsStream()) { + BufferedImage image = ImageIO.read(is); // preparing fixed color ranges int[] buffer = null; diff --git a/src/org/infinity/resource/graphics/PvrDecoder.java b/src/org/infinity/resource/graphics/PvrDecoder.java index 7c498c7d0..31ccb5515 100644 --- a/src/org/infinity/resource/graphics/PvrDecoder.java +++ b/src/org/infinity/resource/graphics/PvrDecoder.java @@ -86,14 +86,14 @@ public static PvrDecoder loadPvr(ResourceEntry entry) if (entry == null) { throw new NullPointerException(); } - try { + try (InputStream is = entry.getResourceDataAsStream()) { String key = null; if (entry instanceof FileResourceEntry) { key = ((FileResourceEntry)entry).getActualPath().toString(); } else { key = entry.getResourceName(); } - PvrDecoder decoder = createPvrDecoder(key, entry.getResourceDataAsStream()); + PvrDecoder decoder = createPvrDecoder(key, is); if (decoder != null) { return decoder; } @@ -113,9 +113,9 @@ public static PvrDecoder loadPvr(String fileName) if (fileName == null) { throw new NullPointerException(); } - try { + try (InputStream is = StreamUtils.getInputStream(FileManager.resolve(fileName))) { String key = fileName; - PvrDecoder decoder = createPvrDecoder(key, StreamUtils.getInputStream(FileManager.resolve(fileName))); + PvrDecoder decoder = createPvrDecoder(key, is); if (decoder != null) { return decoder; } @@ -132,9 +132,9 @@ public static PvrDecoder loadPvr(String fileName) */ public static PvrDecoder loadPvr(Path file) { - try { + try (InputStream is = StreamUtils.getInputStream(file)) { String key = file.getFileName().toString(); - PvrDecoder decoder = createPvrDecoder(key, StreamUtils.getInputStream(file)); + PvrDecoder decoder = createPvrDecoder(key, is); if (decoder != null) { return decoder; } diff --git a/src/org/infinity/resource/other/TtfResource.java b/src/org/infinity/resource/other/TtfResource.java index b9fc1c7dc..3d976475f 100644 --- a/src/org/infinity/resource/other/TtfResource.java +++ b/src/org/infinity/resource/other/TtfResource.java @@ -61,8 +61,7 @@ public TtfResource(ResourceEntry entry) throws Exception { this.entry = entry; - try { - InputStream is = this.entry.getResourceDataAsStream(); + try (InputStream is = this.entry.getResourceDataAsStream()) { font = Font.createFont(Font.TRUETYPE_FONT, is); } catch (Exception e) { font = null; diff --git a/src/org/infinity/resource/sound/AudioPlayer.java b/src/org/infinity/resource/sound/AudioPlayer.java index d1e8f9040..20bb1793d 100644 --- a/src/org/infinity/resource/sound/AudioPlayer.java +++ b/src/org/infinity/resource/sound/AudioPlayer.java @@ -33,31 +33,27 @@ public void play(AudioBuffer audioBuffer) throws Exception setPlaying(true); setStopped(false); - AudioInputStream ais = null; - try { - ais = AudioSystem.getAudioInputStream(new ByteArrayInputStream(audioBuffer.getAudioData())); - } catch (UnsupportedAudioFileException e) { - throw new Exception("Unsupported audio format"); - } - - if (dataLine == null || !ais.getFormat().matches(audioFormat)) { - audioFormat = ais.getFormat(); - DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat); - if (!AudioSystem.isLineSupported(info)) { - throw new Exception("Unsupported audio format"); + try (AudioInputStream ais = AudioSystem.getAudioInputStream(new ByteArrayInputStream(audioBuffer.getAudioData()))) { + if (dataLine == null || !ais.getFormat().matches(audioFormat)) { + audioFormat = ais.getFormat(); + DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat); + if (!AudioSystem.isLineSupported(info)) { + throw new UnsupportedAudioFileException("Unsupported audio format"); + } + dataLine = (SourceDataLine)AudioSystem.getLine(info); + dataLine.open(ais.getFormat(), 16384); } - dataLine = (SourceDataLine)AudioSystem.getLine(info); - dataLine.open(ais.getFormat(), 16384); - } - dataLine.start(); + dataLine.start(); - while (isPlaying()) { - int numBytesRead = ais.read(buffer, 0, buffer.length); - if (numBytesRead < 0) - break; - dataLine.write(buffer, 0, numBytesRead); + while (isPlaying()) { + int numBytesRead = ais.read(buffer, 0, buffer.length); + if (numBytesRead < 0) + break; + dataLine.write(buffer, 0, numBytesRead); + } + } catch (UnsupportedAudioFileException e) { + throw new UnsupportedAudioFileException("Unsupported audio format"); } - ais.close(); if (!isPlaying()) { dataLine.drain(); From b458b72c5ce79748fde95d0fa9d3bd0f197e28d5 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sat, 24 Apr 2021 19:13:55 +0200 Subject: [PATCH 151/158] Improve compiled JAR file compression - Should have no noticeable impact on loading time on modern systems - Requires Ant 1.7 or higher --- build.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.xml b/build.xml index 1cd8a94b7..db1f615ad 100644 --- a/build.xml +++ b/build.xml @@ -55,7 +55,8 @@ + includes="**/*" + level="9"> From 355c1f25f0d17fd2580d5dc358718a11bf1250a9 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sat, 24 Apr 2021 22:19:51 +0200 Subject: [PATCH 152/158] Revert method of instantiating "Look and Feel" objects Fixes reflection warnings when invoked by more recent Java versions. --- src/org/infinity/gui/BrowserMenuBar.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/infinity/gui/BrowserMenuBar.java b/src/org/infinity/gui/BrowserMenuBar.java index 230129596..fe126397d 100644 --- a/src/org/infinity/gui/BrowserMenuBar.java +++ b/src/org/infinity/gui/BrowserMenuBar.java @@ -2301,7 +2301,7 @@ private OptionsMenu() try { // L&F description is only available from class instance Class cls = Class.forName(info[i].getClassName()); - Object o = cls.getDeclaredConstructor().newInstance(); + Object o = cls.newInstance(); if (o instanceof LookAndFeel) dbmi.setToolTipText(((LookAndFeel)o).getDescription()); } catch (Exception ex) { From c06fef3d6bac9c64601502f4007a5b9029843047 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Mon, 26 Apr 2021 17:54:14 +0200 Subject: [PATCH 153/158] Add workaround for file reading issues on Linux Added command line parameter -i to force skipping case-sensitivity checks. Workaround should be removed when case check routine has been fixed. --- src/org/infinity/NearInfinity.java | 9 +++++ src/org/infinity/resource/Profile.java | 5 +++ src/org/infinity/util/io/FileManager.java | 44 +++++++++++++++-------- 3 files changed, 44 insertions(+), 14 deletions(-) diff --git a/src/org/infinity/NearInfinity.java b/src/org/infinity/NearInfinity.java index 354e819dc..373906bfb 100644 --- a/src/org/infinity/NearInfinity.java +++ b/src/org/infinity/NearInfinity.java @@ -38,6 +38,7 @@ import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.ArrayList; +import java.util.Arrays; import java.util.Enumeration; import java.util.List; import java.util.Locale; @@ -211,6 +212,8 @@ public static void printHelp(String jarFile) System.out.println("\nOptions:"); System.out.println(" -v, -version Display version information."); System.out.println(" -h, -help Display this help."); + System.out.println(" -i Disable support of case-sensitive filesystems"); + System.out.println(" (temporary workaround for buggy file access on Linux systems)"); System.out.println(" -t type Force the current or specified game to be of"); System.out.println(" specific type. (Use with care!)"); System.out.println(" Supported game types:"); @@ -235,6 +238,12 @@ public static void advanceProgress(String note) public static void main(String args[]) { + // TODO: remove option when detection of case-sensitive filesystems has been fixed + if (Arrays.asList(args).contains("-i")) { + // must be set before first file access through FileManager class + Profile.addProperty(Profile.Key.GET_GLOBAL_FILE_CASE_CHECK, Profile.Type.BOOLEAN, Boolean.valueOf(false)); + } + Profile.Game forcedGame = null; Path gameOverride = null; diff --git a/src/org/infinity/resource/Profile.java b/src/org/infinity/resource/Profile.java index 8d9f8d7d0..1e9eb9656 100644 --- a/src/org/infinity/resource/Profile.java +++ b/src/org/infinity/resource/Profile.java @@ -159,6 +159,8 @@ public enum Key { GET_GLOBAL_DIALOG_NAME, /** Property: ({@code String}) Returns "{@code dialogf.tlk}". */ GET_GLOBAL_DIALOG_NAME_FEMALE, + /** Property: ({@code Boolean}) Returns whether NI checks for case-sensitive filesystems before accessing files. */ + GET_GLOBAL_FILE_CASE_CHECK, // Static properties which require an additional parameter. /** Property: ({@code String}) Returns the game's title. Extra parameter: Desired {@link Game}. */ @@ -1364,6 +1366,9 @@ private static void initStaticProperties() // setting dialog.tlk file names addEntry(Key.GET_GLOBAL_DIALOG_NAME, Type.STRING, "dialog.tlk"); addEntry(Key.GET_GLOBAL_DIALOG_NAME_FEMALE, Type.STRING, "dialogf.tlk"); + + // setting misc. properties + addEntry(Key.GET_GLOBAL_FILE_CASE_CHECK, Type.BOOLEAN, Boolean.valueOf(true)); } // Initializes a list of potential executable filenames for each game and platform diff --git a/src/org/infinity/util/io/FileManager.java b/src/org/infinity/util/io/FileManager.java index 69880d2ca..8280b7dda 100644 --- a/src/org/infinity/util/io/FileManager.java +++ b/src/org/infinity/util/io/FileManager.java @@ -18,6 +18,7 @@ import java.util.Locale; import java.util.stream.Stream; +import org.infinity.resource.Profile; import org.infinity.util.io.FileWatcher.FileWatchEvent; import org.infinity.util.io.FileWatcher.FileWatchListener; @@ -562,8 +563,17 @@ private static Path _resolveExisting(Path path) if (list == null) { list = _cacheDirectory(folder, false); } - if (list == null || !list.contains(retVal)) { + if (list == null) { retVal = null; + } else { + final String pathString = path.getFileName().toString(); + final boolean isCase = isFileSystemCaseSensitive(path.getFileSystem()); + boolean match = list + .parallelStream() + .anyMatch(p -> isCase ? pathString.equals(p.getFileName().toString()) : pathString.equalsIgnoreCase(p.getFileName().toString())); + if (!match) { + retVal = null; + } } } // if (retVal != null && !FileEx.fromPath(retVal).exists()) { @@ -600,20 +610,26 @@ private static boolean isFileSystemCaseSensitive(FileSystem fs) if (fs != null) { retVal = getInstance().mapCaseSensitive.get(fs); if (retVal == null) { - final char[] separators = { '/', '\\', ':' }; - final String name = "/tmp/aaaBBB"; - for (final char sep: separators) { - String s = (sep != '/') ? name.replace('/', sep) : name; - try { - Path path = fs.getPath(s); - Path path2 = path.getParent().resolve(path.getFileName().toString().toUpperCase(Locale.ENGLISH)); - Path path3 = path.getParent().resolve(path.getFileName().toString().toLowerCase(Locale.ENGLISH)); - retVal = Boolean.valueOf(!(path.equals(path2) && path.equals(path3))); - getInstance().mapCaseSensitive.put(fs, retVal); - break; - } catch (Throwable t) { - retVal = Boolean.TRUE; + if (Profile.getProperty(Profile.Key.GET_GLOBAL_FILE_CASE_CHECK)) { + final char[] separators = { '/', '\\', ':' }; + final String name = "/tmp/aaaBBB"; + for (final char sep: separators) { + String s = (sep != '/') ? name.replace('/', sep) : name; + try { + Path path = fs.getPath(s); + Path path2 = path.getParent().resolve(path.getFileName().toString().toUpperCase(Locale.ENGLISH)); + Path path3 = path.getParent().resolve(path.getFileName().toString().toLowerCase(Locale.ENGLISH)); + retVal = Boolean.valueOf(!(path.equals(path2) && path.equals(path3))); + getInstance().mapCaseSensitive.put(fs, retVal); + break; + } catch (Throwable t) { + retVal = Boolean.TRUE; + } } + } else { + // forced + retVal = Boolean.valueOf(false); + getInstance().mapCaseSensitive.put(fs, retVal); } } } From 218584b6a27c5835916391ac7d0e295b0de8b249 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Tue, 27 Apr 2021 16:06:31 +0200 Subject: [PATCH 154/158] Sprite Decoder: Improve handling of selected weapon ability Invalid ability selection should not prevent weapon from being rendered. --- src/org/infinity/resource/cre/decoder/util/CreatureInfo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java b/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java index 0a34bbc35..c814870cd 100644 --- a/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java +++ b/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java @@ -978,7 +978,7 @@ private int getEffectiveWeaponIndex(int slotIndex) // check if item requires a launcher int abilityIndex = ((IsNumeric)cre.getAttribute(CreResource.CRE_SELECTED_WEAPON_ABILITY)).getValue(); int numAbil = info.getAbilityCount(); - abilityIndex = Math.min(abilityIndex, numAbil - 1); + abilityIndex = Math.max(0, Math.min(abilityIndex, numAbil - 1)); if (abilityIndex < 0) { return retVal; } From cdbf797e739b1ea7c85873ad922562acce68dd27 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Tue, 27 Apr 2021 22:18:42 +0200 Subject: [PATCH 155/158] Sprite Decoder: Improve handling of selected weapon ability (update) --- src/org/infinity/resource/cre/decoder/util/CreatureInfo.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java b/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java index c814870cd..f36bbf41e 100644 --- a/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java +++ b/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java @@ -977,8 +977,9 @@ private int getEffectiveWeaponIndex(int slotIndex) // check if item requires a launcher int abilityIndex = ((IsNumeric)cre.getAttribute(CreResource.CRE_SELECTED_WEAPON_ABILITY)).getValue(); + abilityIndex = Math.max(0, abilityIndex); int numAbil = info.getAbilityCount(); - abilityIndex = Math.max(0, Math.min(abilityIndex, numAbil - 1)); + abilityIndex = Math.min(abilityIndex, numAbil - 1); if (abilityIndex < 0) { return retVal; } From 04d7e96f87bebb52d627de5397cab33050e672ec Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Wed, 28 Apr 2021 23:39:17 +0200 Subject: [PATCH 156/158] Use regular Java TreeMap class for list-based datatypes --- src/org/infinity/datatype/AbstractBitmap.java | 12 ++--- src/org/infinity/datatype/Bitmap.java | 7 +-- src/org/infinity/datatype/HashBitmap.java | 11 ++--- src/org/infinity/datatype/IdsBitmap.java | 8 ++-- src/org/infinity/datatype/PriTypeBitmap.java | 8 ++-- src/org/infinity/datatype/ResourceBitmap.java | 7 +-- src/org/infinity/datatype/SecTypeBitmap.java | 8 ++-- src/org/infinity/datatype/SpellProtType.java | 4 +- .../infinity/datatype/Summon2daBitmap.java | 6 +-- src/org/infinity/resource/EffectFactory.java | 34 +++++++------- .../infinity/resource/cre/CreResource.java | 6 +-- .../infinity/resource/gam/JournalEntry.java | 4 +- src/org/infinity/resource/gam/PartyNPC.java | 6 +-- .../infinity/resource/pro/ProAreaType.java | 4 +- .../infinity/resource/pro/ProResource.java | 4 +- .../infinity/resource/pro/ProSingleType.java | 4 +- src/org/infinity/util/LongIntegerHashMap.java | 44 ------------------- 17 files changed, 68 insertions(+), 109 deletions(-) delete mode 100644 src/org/infinity/util/LongIntegerHashMap.java diff --git a/src/org/infinity/datatype/AbstractBitmap.java b/src/org/infinity/datatype/AbstractBitmap.java index 35a81109f..79c17bab4 100644 --- a/src/org/infinity/datatype/AbstractBitmap.java +++ b/src/org/infinity/datatype/AbstractBitmap.java @@ -16,6 +16,7 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; +import java.util.TreeMap; import java.util.function.BiFunction; import javax.swing.JButton; @@ -29,7 +30,6 @@ import org.infinity.gui.ViewerUtil; import org.infinity.icon.Icons; import org.infinity.resource.AbstractStruct; -import org.infinity.util.LongIntegerHashMap; import org.infinity.util.Misc; /** @@ -65,7 +65,7 @@ public class AbstractBitmap extends Datatype implements Editable, IsNumeric } }; - private final LongIntegerHashMap itemMap; + private final TreeMap itemMap; private final List buttonList; private BiFunction formatter; @@ -85,7 +85,7 @@ public class AbstractBitmap extends Datatype implements Editable, IsNumeric * @param name field name * @param items a collection of number-to-symbol mappings */ - public AbstractBitmap(ByteBuffer buffer, int offset, int length, String name, LongIntegerHashMap items) + public AbstractBitmap(ByteBuffer buffer, int offset, int length, String name, TreeMap items) { this(buffer, offset, length, name, items, null, false); } @@ -100,7 +100,7 @@ public AbstractBitmap(ByteBuffer buffer, int offset, int length, String name, Lo * @param items a collection of number-to-symbol mappings * @param formatter a function that is used to produce the textual output */ - public AbstractBitmap(ByteBuffer buffer, int offset, int length, String name, LongIntegerHashMap items, + public AbstractBitmap(ByteBuffer buffer, int offset, int length, String name, TreeMap items, BiFunction formatter) { this(buffer, offset, length, name, items, formatter, false); @@ -117,7 +117,7 @@ public AbstractBitmap(ByteBuffer buffer, int offset, int length, String name, Lo * @param formatter a function that is used to produce the textual output * @param signed indicates whether numeric value is treated as a signed value */ - public AbstractBitmap(ByteBuffer buffer, int offset, int length, String name, LongIntegerHashMap items, + public AbstractBitmap(ByteBuffer buffer, int offset, int length, String name, TreeMap items, BiFunction formatter, boolean signed) { super(offset, length, name); @@ -319,7 +319,7 @@ public JComponent getUiControl() /** * Returns the bitmap table used to associated numeric values with symbolic data. */ - public LongIntegerHashMap getBitmap() + public TreeMap getBitmap() { return itemMap; } diff --git a/src/org/infinity/datatype/Bitmap.java b/src/org/infinity/datatype/Bitmap.java index 9c9e47a85..5743e954a 100644 --- a/src/org/infinity/datatype/Bitmap.java +++ b/src/org/infinity/datatype/Bitmap.java @@ -5,13 +5,14 @@ package org.infinity.datatype; import java.awt.event.ActionListener; +import java.beans.PropertyChangeEvent; import java.nio.ByteBuffer; +import java.util.TreeMap; import java.util.function.BiFunction; import javax.swing.JComponent; import org.infinity.resource.AbstractStruct; -import org.infinity.util.LongIntegerHashMap; /** * Field that represents an integer enumeration of some values. @@ -73,9 +74,9 @@ public JComponent edit(ActionListener container) //--------------------- End Interface Editable --------------------- - private static LongIntegerHashMap createMap(String[] symbols) + private static TreeMap createMap(String[] symbols) { - LongIntegerHashMap retVal = new LongIntegerHashMap<>(); + TreeMap retVal = new TreeMap<>(); for (int i = 0; i < symbols.length; i++) { String symbol = (symbols[i] != null) ? symbols[i] : "(Unlabeled)"; retVal.put(Long.valueOf(i), symbol); diff --git a/src/org/infinity/datatype/HashBitmap.java b/src/org/infinity/datatype/HashBitmap.java index f01ae07cc..040af18f2 100644 --- a/src/org/infinity/datatype/HashBitmap.java +++ b/src/org/infinity/datatype/HashBitmap.java @@ -4,11 +4,12 @@ package org.infinity.datatype; +import java.beans.PropertyChangeEvent; import java.nio.ByteBuffer; +import java.util.TreeMap; import java.util.function.BiFunction; import org.infinity.resource.AbstractStruct; -import org.infinity.util.LongIntegerHashMap; /** * Field that represents an integer enumeration of some values. @@ -39,24 +40,24 @@ public class HashBitmap extends AbstractBitmap } }; - public HashBitmap(ByteBuffer buffer, int offset, int length, String name, LongIntegerHashMap idsmap) + public HashBitmap(ByteBuffer buffer, int offset, int length, String name, TreeMap idsmap) { this(buffer, offset, length, name, idsmap, true, false, false); } - public HashBitmap(ByteBuffer buffer, int offset, int length, String name, LongIntegerHashMap idsmap, + public HashBitmap(ByteBuffer buffer, int offset, int length, String name, TreeMap idsmap, boolean sortByName) { this(buffer, offset, length, name, idsmap, sortByName, false, false); } - public HashBitmap(ByteBuffer buffer, int offset, int length, String name, LongIntegerHashMap idsmap, + public HashBitmap(ByteBuffer buffer, int offset, int length, String name, TreeMap idsmap, boolean sortByName, boolean signed) { this(buffer, offset, length, name, idsmap, sortByName, signed, false); } - public HashBitmap(ByteBuffer buffer, int offset, int length, String name, LongIntegerHashMap idsmap, + public HashBitmap(ByteBuffer buffer, int offset, int length, String name, TreeMap idsmap, boolean sortByName, boolean signed, boolean showAsHex) { super(buffer, offset, length, name, idsmap, null, signed); diff --git a/src/org/infinity/datatype/IdsBitmap.java b/src/org/infinity/datatype/IdsBitmap.java index a4bf3abc5..6e281f64d 100644 --- a/src/org/infinity/datatype/IdsBitmap.java +++ b/src/org/infinity/datatype/IdsBitmap.java @@ -6,6 +6,7 @@ import java.awt.event.ActionListener; import java.nio.ByteBuffer; +import java.util.TreeMap; import java.util.function.BiFunction; import javax.swing.JComponent; @@ -13,7 +14,6 @@ import org.infinity.util.IdsMap; import org.infinity.util.IdsMapCache; import org.infinity.util.IdsMapEntry; -import org.infinity.util.LongIntegerHashMap; public class IdsBitmap extends AbstractBitmap { @@ -75,12 +75,12 @@ public void addIdsMapEntry(IdsMapEntry entry) getBitmap().putIfAbsent(entry.getID(), entry); } - private static LongIntegerHashMap createResourceList(String resource) + private static TreeMap createResourceList(String resource) { - LongIntegerHashMap retVal = null; + TreeMap retVal = null; IdsMap idsMap = IdsMapCache.get(resource); if (idsMap != null) { - retVal = new LongIntegerHashMap<>(); + retVal = new TreeMap<>(); for (final IdsMapEntry e: idsMap.getAllValues()) { final long id = e.getID(); retVal.put(id, new IdsMapEntry(id, e.getSymbol())); diff --git a/src/org/infinity/datatype/PriTypeBitmap.java b/src/org/infinity/datatype/PriTypeBitmap.java index 9253977db..3619834c1 100644 --- a/src/org/infinity/datatype/PriTypeBitmap.java +++ b/src/org/infinity/datatype/PriTypeBitmap.java @@ -6,13 +6,13 @@ 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.LongIntegerHashMap; import org.infinity.util.Table2da; import org.infinity.util.Table2daCache; @@ -23,7 +23,7 @@ public class PriTypeBitmap extends HashBitmap private static final String[] s_school = {"None", "Abjurer", "Conjurer", "Diviner", "Enchanter", "Illusionist", "Invoker", "Necromancer", "Transmuter", "Generalist"}; - private static final LongIntegerHashMap typeMap = new LongIntegerHashMap<>(); + private static final TreeMap typeMap = new TreeMap<>(); static { if (ResourceFactory.resourceExists("MSCHOOL.2DA")) { @@ -47,11 +47,11 @@ public static String getTableName() public static String[] getTypeArray() { - final LongIntegerHashMap map = getTypeTable(); + final TreeMap map = getTypeTable(); return map.values().toArray(new String[map.size()]); } - private static synchronized LongIntegerHashMap getTypeTable() + private static synchronized TreeMap getTypeTable() { if (typeMap.isEmpty()) { if (ResourceFactory.resourceExists(TableName) && TableName.endsWith(".2DA")) { diff --git a/src/org/infinity/datatype/ResourceBitmap.java b/src/org/infinity/datatype/ResourceBitmap.java index 59ec7aef2..e743134ad 100644 --- a/src/org/infinity/datatype/ResourceBitmap.java +++ b/src/org/infinity/datatype/ResourceBitmap.java @@ -6,9 +6,11 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.beans.PropertyChangeEvent; import java.nio.ByteBuffer; import java.nio.file.Path; import java.util.List; +import java.util.TreeMap; import java.util.function.BiFunction; import javax.swing.JButton; @@ -18,7 +20,6 @@ import org.infinity.resource.AbstractStruct; import org.infinity.resource.ResourceFactory; import org.infinity.resource.key.ResourceEntry; -import org.infinity.util.LongIntegerHashMap; import org.infinity.util.Misc; /** @@ -161,9 +162,9 @@ protected String getDefaultLabel() return defaultLabel; } -private static LongIntegerHashMap createMap(List resources) +private static TreeMap createMap(List resources) { - LongIntegerHashMap retVal = new LongIntegerHashMap<>(); + TreeMap retVal = new TreeMap<>(); if (resources != null) { for (final RefEntry entry : resources) { if (entry != null) { diff --git a/src/org/infinity/datatype/SecTypeBitmap.java b/src/org/infinity/datatype/SecTypeBitmap.java index 94a9451f7..4832a5c16 100644 --- a/src/org/infinity/datatype/SecTypeBitmap.java +++ b/src/org/infinity/datatype/SecTypeBitmap.java @@ -6,9 +6,9 @@ import java.nio.ByteBuffer; import java.util.Locale; +import java.util.TreeMap; import org.infinity.resource.ResourceFactory; -import org.infinity.util.LongIntegerHashMap; import org.infinity.util.Table2da; import org.infinity.util.Table2daCache; @@ -21,7 +21,7 @@ public class SecTypeBitmap extends HashBitmap "Divination attack", "Conjuration", "Combat protections", "Contingency", "Battleground", "Offensive damage", "Disabling", "Combination", "Non-combat"}; - private static final LongIntegerHashMap typeMap = new LongIntegerHashMap<>(); + private static final TreeMap typeMap = new TreeMap<>(); public SecTypeBitmap(ByteBuffer buffer, int offset, int length, String name) { @@ -35,11 +35,11 @@ public static String getTableName() public static String[] getTypeArray() { - final LongIntegerHashMap map = getTypeTable(); + final TreeMap map = getTypeTable(); return map.values().toArray(new String[map.size()]); } - private static synchronized LongIntegerHashMap getTypeTable() + private static synchronized TreeMap getTypeTable() { if (typeMap.isEmpty()) { if (ResourceFactory.resourceExists(TableName)) { diff --git a/src/org/infinity/datatype/SpellProtType.java b/src/org/infinity/datatype/SpellProtType.java index 34fec4ee0..caf417a2a 100644 --- a/src/org/infinity/datatype/SpellProtType.java +++ b/src/org/infinity/datatype/SpellProtType.java @@ -7,6 +7,7 @@ import java.nio.ByteBuffer; import java.util.Arrays; import java.util.ListIterator; +import java.util.TreeMap; import org.infinity.resource.AbstractStruct; import org.infinity.resource.Profile; @@ -15,7 +16,6 @@ import org.infinity.util.IdsMap; import org.infinity.util.IdsMapCache; import org.infinity.util.IdsMapEntry; -import org.infinity.util.LongIntegerHashMap; import org.infinity.util.Table2da; import org.infinity.util.Table2daCache; @@ -64,7 +64,7 @@ public class SpellProtType extends Bitmap "ALIGN.IDS", "KIT.IDS" }; private static final String tableName = "SPLPROT.2DA"; - private static final LongIntegerHashMap statIds = new LongIntegerHashMap<>(); + private static final TreeMap statIds = new TreeMap<>(); private static String[] creType; static { diff --git a/src/org/infinity/datatype/Summon2daBitmap.java b/src/org/infinity/datatype/Summon2daBitmap.java index 6c3715f29..9564d11ba 100644 --- a/src/org/infinity/datatype/Summon2daBitmap.java +++ b/src/org/infinity/datatype/Summon2daBitmap.java @@ -6,9 +6,9 @@ import java.nio.ByteBuffer; import java.util.Locale; +import java.util.TreeMap; import org.infinity.resource.ResourceFactory; -import org.infinity.util.LongIntegerHashMap; import org.infinity.util.Table2da; import org.infinity.util.Table2daCache; @@ -16,7 +16,7 @@ public class Summon2daBitmap extends HashBitmap { private static final String TableName = "SMTABLES.2DA"; - private static final LongIntegerHashMap summonMap = new LongIntegerHashMap<>(); + private static final TreeMap summonMap = new TreeMap<>(); public Summon2daBitmap(ByteBuffer buffer, int offset, int length, String name) { @@ -28,7 +28,7 @@ public static String getTableName() return TableName; } - private static synchronized LongIntegerHashMap getSummonTable() + private static synchronized TreeMap getSummonTable() { if (summonMap.isEmpty()) { if (ResourceFactory.resourceExists(TableName)) { diff --git a/src/org/infinity/resource/EffectFactory.java b/src/org/infinity/resource/EffectFactory.java index 4fd7f8231..86b56cb5b 100644 --- a/src/org/infinity/resource/EffectFactory.java +++ b/src/org/infinity/resource/EffectFactory.java @@ -10,6 +10,7 @@ import java.util.Arrays; import java.util.EnumMap; import java.util.List; +import java.util.TreeMap; import java.util.stream.Stream; import org.infinity.datatype.AnimateBitmap; @@ -40,7 +41,6 @@ import org.infinity.resource.are.AutomapNote; import org.infinity.resource.itm.ItmResource; import org.infinity.util.IdsMapEntry; -import org.infinity.util.LongIntegerHashMap; import org.infinity.util.StringTable; import org.infinity.util.Table2da; import org.infinity.util.Table2daCache; @@ -130,10 +130,10 @@ public static enum EffectEntry { } // contains IDS mappings for BGEE's opcode 319 "Item Usability" - public static final LongIntegerHashMap m_duration = new LongIntegerHashMap<>(); - public static final LongIntegerHashMap m_colorloc = new LongIntegerHashMap<>(); - public static final LongIntegerHashMap m_proj_iwd = new LongIntegerHashMap<>(); - public static final LongIntegerHashMap m_inctype = new LongIntegerHashMap<>(); + public static final TreeMap m_duration = new TreeMap<>(); + public static final TreeMap m_colorloc = new TreeMap<>(); + public static final TreeMap m_proj_iwd = new TreeMap<>(); + public static final TreeMap m_inctype = new TreeMap<>(); public static final String[] s_inctype = {"Increment", "Set", "Set % of"}; public static final String[] s_buttontype = { "Unknown", "Unknown", "Bard Song", "Cast Spell", "Find Traps", @@ -928,7 +928,7 @@ private static boolean updateOpcode78(AbstractStruct struct) throws Exception private static boolean updateOpcode232(AbstractStruct struct) throws Exception { if (struct != null) { - if (Profile.getEngine() == Profile.Engine.BG2 || Profile.isEnhancedEdition()) { + if (Profile.isEnhancedEdition()) { int opcode = ((IsNumeric)getEntry(struct, EffectEntry.IDX_OPCODE)).getValue(); if (opcode == 232) { int param2 = ((IsNumeric)getEntry(struct, EffectEntry.IDX_PARAM2)).getValue(); @@ -2104,7 +2104,7 @@ private String makeEffectParamsGeneric(Datatype parent, ByteBuffer buffer, int o if (Profile.getEngine() == Profile.Engine.PST || Profile.getEngine() == Profile.Engine.IWD2) { s.add(new DecNumber(buffer, offset + 4, 4, AbstractStruct.COMMON_UNUSED)); } else { - final LongIntegerHashMap idsmap = new LongIntegerHashMap<>(); + final TreeMap idsmap = new TreeMap<>(); idsmap.put(0L, "Charmed (neutral)"); idsmap.put(1L, "Charmed (hostile)"); idsmap.put(2L, "Dire charmed (neutral)"); @@ -2698,11 +2698,11 @@ private String makeEffectParamsGeneric(Datatype parent, ByteBuffer buffer, int o ids.addIdsMapEntry(new IdsMapEntry(0L, "None")); s.add(ids); } else { - LongIntegerHashMap idsmap; + TreeMap idsmap; if (Profile.getEngine() == Profile.Engine.IWD || Profile.getEngine() == Profile.Engine.IWD2) { idsmap = m_proj_iwd; } else { - idsmap = new LongIntegerHashMap<>(); + idsmap = new TreeMap<>(); idsmap.put(0L, "None"); idsmap.put(4L, "Arrow"); idsmap.put(9L, "Axe"); @@ -2958,7 +2958,7 @@ private String makeEffectParamsGeneric(Datatype parent, ByteBuffer buffer, int o case 140: // Casting glow { - final LongIntegerHashMap m_castglow = new LongIntegerHashMap<>(); + final TreeMap m_castglow = new TreeMap<>(); if (Profile.isEnhancedEdition()) { m_castglow.put(0L, "Use projectile"); } @@ -3660,7 +3660,7 @@ private String makeEffectParamsBG2(Datatype parent, ByteBuffer buffer, int offse case 237: // Set image type { - final LongIntegerHashMap map = new LongIntegerHashMap<>(); + final TreeMap map = new TreeMap<>(); map.put(0L, "Player1"); map.put(1L, "Player2"); map.put(2L, "Player3"); @@ -3695,7 +3695,7 @@ private String makeEffectParamsBG2(Datatype parent, ByteBuffer buffer, int offse case 241: // Control creature { - final LongIntegerHashMap map = new LongIntegerHashMap<>(); + final TreeMap map = new TreeMap<>(); map.put(0L, "Charmed (neutral)"); map.put(1L, "Charmed (hostile)"); map.put(2L, "Dire charmed (neutral)"); @@ -3731,7 +3731,7 @@ private String makeEffectParamsBG2(Datatype parent, ByteBuffer buffer, int offse case 248: // Melee hit effect s.add(new DecNumber(buffer, offset, 4, AbstractStruct.COMMON_UNUSED)); if (Profile.isEnhancedEdition()) { - LongIntegerHashMap map = new LongIntegerHashMap<>(); + TreeMap map = new TreeMap<>(); map.put(0L, "Default"); map.put(4L, "Fists only"); s.add(new HashBitmap(buffer, offset + 4, 4, "Type", map, false)); @@ -3975,7 +3975,7 @@ private String makeEffectParamsBG2(Datatype parent, ByteBuffer buffer, int offse case 303: // Backstab every hit s.add(new DecNumber(buffer, offset, 4, AbstractStruct.COMMON_UNUSED)); if (isTobEx) { - LongIntegerHashMap idsmap = new LongIntegerHashMap<>(); + TreeMap idsmap = new TreeMap<>(); idsmap.put(0L, "Normal conditions"); idsmap.put(1L, "Ignore visual state and position"); idsmap.put(2L, "Ignore visual state only"); @@ -4695,7 +4695,7 @@ private String makeEffectParamsPST(Datatype parent, ByteBuffer buffer, int offse case 195: // Tint screen { - final LongIntegerHashMap m_fadeType = new LongIntegerHashMap<>(); + final TreeMap m_fadeType = new TreeMap<>(); m_fadeType.put(0L, "Quick fade light->dark->light"); m_fadeType.put(1L, "Quick fade light->dark->light"); m_fadeType.put(2L, "Quick fade light->dark, instant fade light"); @@ -4988,7 +4988,7 @@ private String makeEffectParamsIWD(Datatype parent, ByteBuffer buffer, int offse case 263: // Evil turn undead { - final LongIntegerHashMap map = new LongIntegerHashMap<>(); + final TreeMap map = new TreeMap<>(); map.put(0L, "Charmed (neutral)"); map.put(1L, "Charmed (hostile)"); map.put(2L, "Dire charmed (neutral)"); @@ -5232,7 +5232,7 @@ private String makeEffectParamsIWD2(Datatype parent, ByteBuffer buffer, int offs case 263: // Evil turn undead { - final LongIntegerHashMap map = new LongIntegerHashMap<>(); + final TreeMap map = new TreeMap<>(); map.put(0L, "Charmed (neutral)"); map.put(1L, "Charmed (hostile)"); map.put(2L, "Dire charmed (neutral)"); diff --git a/src/org/infinity/resource/cre/CreResource.java b/src/org/infinity/resource/cre/CreResource.java index d6b66e32c..18212ad7f 100644 --- a/src/org/infinity/resource/cre/CreResource.java +++ b/src/org/infinity/resource/cre/CreResource.java @@ -17,6 +17,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.TreeMap; import javax.swing.JButton; import javax.swing.JComponent; @@ -73,7 +74,6 @@ import org.infinity.util.IniMapCache; import org.infinity.util.IniMapEntry; import org.infinity.util.IniMapSection; -import org.infinity.util.LongIntegerHashMap; import org.infinity.util.Misc; import org.infinity.util.StringTable; import org.infinity.util.Table2da; @@ -429,8 +429,8 @@ public final class CreResource extends AbstractStruct public static final int TAB_INDEX_ANIMATION = 1; public static final int TAB_INDEX_RAW = 2; - private static final LongIntegerHashMap m_magetype = new LongIntegerHashMap<>(); - private static final LongIntegerHashMap m_colorPlacement = new LongIntegerHashMap<>(); + private static final TreeMap m_magetype = new TreeMap<>(); + private static final TreeMap m_colorPlacement = new TreeMap<>(); public static final String[] s_flag = { "No flags set", "Identified", "No corpse", "Permanent corpse", "Original class: Fighter", "Original class: Mage", "Original class: Cleric", "Original class: Thief", diff --git a/src/org/infinity/resource/gam/JournalEntry.java b/src/org/infinity/resource/gam/JournalEntry.java index 9355b79ff..534a52fd6 100644 --- a/src/org/infinity/resource/gam/JournalEntry.java +++ b/src/org/infinity/resource/gam/JournalEntry.java @@ -5,6 +5,7 @@ package org.infinity.resource.gam; import java.nio.ByteBuffer; +import java.util.TreeMap; import org.infinity.datatype.DecNumber; import org.infinity.datatype.Flag; @@ -15,7 +16,6 @@ import org.infinity.resource.AbstractStruct; import org.infinity.resource.AddRemovable; import org.infinity.resource.Profile; -import org.infinity.util.LongIntegerHashMap; import org.infinity.util.io.StreamUtils; public final class JournalEntry extends AbstractStruct implements AddRemovable @@ -28,7 +28,7 @@ public final class JournalEntry extends AbstractStruct implements AddRemovable public static final String GAM_JOURNAL_SECTION = "Section"; public static final String GAM_JOURNAL_SOURCE = "Text source"; - private static final LongIntegerHashMap m_source = new LongIntegerHashMap<>(); + private static final TreeMap m_source = new TreeMap<>(); public static final String s_section[] = new String[]{"User notes", "Quests", "Done quests", "Journal"}; diff --git a/src/org/infinity/resource/gam/PartyNPC.java b/src/org/infinity/resource/gam/PartyNPC.java index 7d6cee010..1978ef1ae 100644 --- a/src/org/infinity/resource/gam/PartyNPC.java +++ b/src/org/infinity/resource/gam/PartyNPC.java @@ -5,6 +5,7 @@ package org.infinity.resource.gam; import java.nio.ByteBuffer; +import java.util.TreeMap; import javax.swing.JComponent; @@ -27,7 +28,6 @@ import org.infinity.resource.StructEntry; import org.infinity.resource.cre.CreResource; import org.infinity.util.IdsMapEntry; -import org.infinity.util.LongIntegerHashMap; import org.infinity.util.io.StreamUtils; public class PartyNPC extends AbstractStruct implements HasViewerTabs, AddRemovable @@ -85,8 +85,8 @@ public class PartyNPC extends AbstractStruct implements HasViewerTabs, AddRemova public static final String GAM_NPC_STAT_FAV_WEAPON_FMT = "Favorite weapon %d"; public static final String GAM_NPC_STAT_FAV_WEAPON_COUNT_FMT = "Favorite weapon counter %d"; - public static final LongIntegerHashMap m_partyOrder = new LongIntegerHashMap<>(); -// private static final LongIntegerHashMap m_selected = new LongIntegerHashMap<>(); + public static final TreeMap m_partyOrder = new TreeMap<>(); +// private static final TreeMap m_selected = new TreeMap<>(); private static final String[] s_selected = {"Not selected", "Selected", null, null, null, null, null, null, null, null, null, null, null, null, null, null, "Dead" }; diff --git a/src/org/infinity/resource/pro/ProAreaType.java b/src/org/infinity/resource/pro/ProAreaType.java index 1cff0aa61..7c9b3c418 100644 --- a/src/org/infinity/resource/pro/ProAreaType.java +++ b/src/org/infinity/resource/pro/ProAreaType.java @@ -5,6 +5,7 @@ package org.infinity.resource.pro; import java.nio.ByteBuffer; +import java.util.TreeMap; import org.infinity.datatype.ColorValue; import org.infinity.datatype.DecNumber; @@ -22,7 +23,6 @@ import org.infinity.resource.AddRemovable; import org.infinity.resource.Profile; import org.infinity.resource.StructEntry; -import org.infinity.util.LongIntegerHashMap; import org.infinity.util.io.StreamUtils; public final class ProAreaType extends AbstractStruct implements AddRemovable, UpdateListener @@ -54,7 +54,7 @@ public final class ProAreaType extends AbstractStruct implements AddRemovable, U public static final String PRO_AREA_ANIMATION_GRANULARITY = "Animation granularity"; public static final String PRO_AREA_ANIMATION_GRANULARITY_DIVIDER = "Animation granularity divider"; - public static final LongIntegerHashMap m_proj = new LongIntegerHashMap<>(); + public static final TreeMap m_proj = new TreeMap<>(); public static final String[] s_areaflags = {"Trap not visible", "Trap visible", "Triggered by inanimates", "Triggered by condition", "Delayed trigger", "Secondary projectile", "Fragments", "Affect only enemies", "Affect only allies*;Only in combination with \"Affect only enemies (6)\"", diff --git a/src/org/infinity/resource/pro/ProResource.java b/src/org/infinity/resource/pro/ProResource.java index 869ca4b20..fee61f8d2 100644 --- a/src/org/infinity/resource/pro/ProResource.java +++ b/src/org/infinity/resource/pro/ProResource.java @@ -5,6 +5,7 @@ package org.infinity.resource.pro; import java.nio.ByteBuffer; +import java.util.TreeMap; import javax.swing.JComponent; @@ -37,7 +38,6 @@ import org.infinity.resource.StructEntry; import org.infinity.resource.key.ResourceEntry; import org.infinity.search.SearchOptions; -import org.infinity.util.LongIntegerHashMap; /** * This resource describes projectiles, and the files are referenced spells and @@ -97,7 +97,7 @@ public final class ProResource extends AbstractStruct implements Resource, HasVi "Touch projectile", "Negate IDS1", "Negate IDS2", "Use either IDS", "Delayed payload", "Limited path count", "IWD style check", "Caster affected"}; - public static final LongIntegerHashMap m_projtype = new LongIntegerHashMap<>(); + public static final TreeMap m_projtype = new TreeMap<>(); static { m_projtype.put(1L, "No BAM"); m_projtype.put(2L, "Single target"); diff --git a/src/org/infinity/resource/pro/ProSingleType.java b/src/org/infinity/resource/pro/ProSingleType.java index 75631c4e4..4db1d8ed8 100644 --- a/src/org/infinity/resource/pro/ProSingleType.java +++ b/src/org/infinity/resource/pro/ProSingleType.java @@ -5,6 +5,7 @@ package org.infinity.resource.pro; import java.nio.ByteBuffer; +import java.util.TreeMap; import org.infinity.datatype.ColorValue; import org.infinity.datatype.DecNumber; @@ -15,7 +16,6 @@ import org.infinity.datatype.Unknown; import org.infinity.resource.AbstractStruct; import org.infinity.resource.AddRemovable; -import org.infinity.util.LongIntegerHashMap; import org.infinity.util.io.StreamUtils; public final class ProSingleType extends AbstractStruct implements AddRemovable @@ -40,7 +40,7 @@ public final class ProSingleType extends AbstractStruct implements AddRemovable public static final String PRO_SINGLE_TRAILING_ANIMATION_DELAY_FMT = "Trailing animation delay %d"; public static final String PRO_SINGLE_TRAIL_FLAGS = "Trail flags"; - public static final LongIntegerHashMap m_facetarget = new LongIntegerHashMap<>(); + public static final TreeMap m_facetarget = new TreeMap<>(); public static final String[] s_flags = {"No flags set", "Colored BAM", "Creates smoke", "Colored smoke", "Not light source", "Modify for height", "Casts shadow", "Light spot enabled", "Translucent", "Mid-level brighten", "Blended"}; diff --git a/src/org/infinity/util/LongIntegerHashMap.java b/src/org/infinity/util/LongIntegerHashMap.java deleted file mode 100644 index c1d420a2b..000000000 --- a/src/org/infinity/util/LongIntegerHashMap.java +++ /dev/null @@ -1,44 +0,0 @@ -// Near Infinity - An Infinity Engine Browser and Editor -// Copyright (C) 2001 - 2019 Jon Olav Hauglid -// See LICENSE.txt for license information - -package org.infinity.util; - -import java.util.Iterator; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; - -public final class LongIntegerHashMap extends TreeMap -{ - public LongIntegerHashMap() - { - super(); - } - - @Override - public String toString() - { - final StringBuilder buf = new StringBuilder(); - buf.append('{'); - Set> set = entrySet(); - Iterator> i = set.iterator(); - boolean hasNext = i.hasNext(); - while (hasNext) { - Map.Entry e = i.next(); - long key = e.getKey(); - V value = e.getValue(); - buf.append(key); - buf.append('='); - if (value == this) - buf.append("(this Map)"); - else - buf.append(value); - hasNext = i.hasNext(); - if (hasNext) - buf.append(", "); - } - buf.append('}'); - return buf.toString(); - } -} From 4befff9bb7f96e7b6dbd9d3898c3af93c54cb9f3 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Thu, 29 Apr 2021 16:20:56 +0200 Subject: [PATCH 157/158] Improve opcode 232 parameter definitions --- src/org/infinity/resource/EffectFactory.java | 58 +++++++++++--------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/src/org/infinity/resource/EffectFactory.java b/src/org/infinity/resource/EffectFactory.java index 86b56cb5b..8ed9f44e9 100644 --- a/src/org/infinity/resource/EffectFactory.java +++ b/src/org/infinity/resource/EffectFactory.java @@ -3582,37 +3582,45 @@ private String makeEffectParamsBG2(Datatype parent, ByteBuffer buffer, int offse case 232: // Cast spell on condition { - s.add(new Bitmap(buffer, offset, 4, "Target", - new String[]{"Caster", "Last hit by", "Nearest enemy", "Anyone"})); + s.add(new Bitmap(buffer, offset, 4, "Target", new String[]{"Myself", "LastHitter", "NearestEnemyOf", "Anyone"})); + final List cndList = new ArrayList() {{ + add("HitBy([ANYONE]) / instant"); + add("See(NearestEnemyOf(Myself)) / per round"); + add("HPPercentLT(Myself,50) / per round"); + add("HPPercentLT(Myself,25) / per round"); + add("HPPercentLT(Myself,10) / per round"); + add("StateCheck(Myself,STATE_HELPLESS) / per round"); + add("StateCheck(Myself,STATE_POISONED) / per round"); + add("AttackedBy([ANYONE]) / instant"); + add("Range([ANYONE],4) / per round"); + add("Range([ANYONE],10) / per round"); + add("-Crash- / per round"); + add("TookDamage() / instant"); + if (Profile.isEnhancedEdition()) { + add("Killed([ANYONE]) / instant"); + add("TimeOfDay('Special') / per round"); + add("Range([ANYONE],'Special') / per round"); + add("StateCheck(Myself,'Special') / per round"); + add("Died(Myself) / instant"); + add("Died([ANYONE]) / instant"); + add("TurnedBy([ANYONE]) / instant"); + add("HPLT(Myself,'Special') / per round"); + add("HPPercentLT(Myself,'Special') / per round"); + add("CheckSpellState(Myself,'Special') / per round"); + } + }}; + final String[] conditions = cndList.toArray(new String[cndList.size()]); if (Profile.isEnhancedEdition()) { - Bitmap item = new Bitmap(buffer, offset + 4, 4, "Condition", - new String[]{"Target hit", "Enemy sighted", "HP below 50%", - "HP below 25%", "HP below 10%", "If helpless", - "If poisoned", "When attacked", - "Target in range 4'", "Target in range 10'", - "Unknown (every round)", "Took damage", "Actor killed", - "Time of day is 'Special'", - "Target in 'Special' range", - "Target's state is 'Special'", "Target dies", - "Target died", "Target turned by", - "Target HP < 'Special'", "Target HP % < 'Special'", - "Target's spell state is 'Special'"}); + Bitmap item = new Bitmap(buffer, offset + 4, 4, "Condition", conditions); s.add(item); if (parent != null && parent instanceof UpdateListener) { item.addUpdateListener((UpdateListener)parent); } + } else if (isTobEx) { + s.add(new Bitmap(buffer, offset + 4, 2, "Condition", conditions)); + s.add(new DecNumber(buffer, offset + 6, 2, "Trigger check period")); } else { - String[] condition = new String[]{"Target hit", "Enemy sighted", "HP below 50%", - "HP below 25%", "HP below 10%", "If helpless", - "If poisoned", "When attacked", - "Target in range 4'", "Target in range 10'", - "Unknown (every round)", "Took damage"}; - if (isTobEx) { - s.add(new Bitmap(buffer, offset + 4, 2, "Condition", condition)); - s.add(new DecNumber(buffer, offset + 6, 2, "Trigger check period")); - } else { - s.add(new Bitmap(buffer, offset + 4, 4, "Condition", condition)); - } + s.add(new Bitmap(buffer, offset + 4, 4, "Condition", conditions)); } restype = "SPL"; break; From 6d0d1a587866bfba790820f15a807e81740d031c Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sat, 1 May 2021 09:59:37 +0200 Subject: [PATCH 158/158] Version 2.2-20210501 --- src/org/infinity/gui/BrowserMenuBar.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/infinity/gui/BrowserMenuBar.java b/src/org/infinity/gui/BrowserMenuBar.java index fe126397d..7340e4762 100644 --- a/src/org/infinity/gui/BrowserMenuBar.java +++ b/src/org/infinity/gui/BrowserMenuBar.java @@ -109,7 +109,7 @@ public final class BrowserMenuBar extends JMenuBar implements KeyEventDispatcher { - public static final String VERSION = "v2.1-20210123"; + public static final String VERSION = "v2.2-20210501"; public static final LookAndFeelInfo DEFAULT_LOOKFEEL = new LookAndFeelInfo("Metal", "javax.swing.plaf.metal.MetalLookAndFeel");