// 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.gam;
import java.nio.ByteBuffer;
import javax.swing.JComponent;
import org.infinity.datatype.Bitmap;
import org.infinity.datatype.DecNumber;
import org.infinity.datatype.HashBitmap;
import org.infinity.datatype.HexNumber;
import org.infinity.datatype.IdsBitmap;
import org.infinity.datatype.ResourceRef;
import org.infinity.datatype.StringRef;
import org.infinity.datatype.TextString;
import org.infinity.datatype.Unknown;
import org.infinity.datatype.UnsignDecNumber;
import org.infinity.gui.StructViewer;
import org.infinity.resource.AbstractStruct;
import org.infinity.resource.AddRemovable;
import org.infinity.resource.HasAddRemovable;
import org.infinity.resource.HasViewerTabs;
import org.infinity.resource.Profile;
import org.infinity.resource.StructEntry;
import org.infinity.resource.are.Actor;
import org.infinity.resource.cre.CreResource;
import org.infinity.util.LongIntegerHashMap;
import org.infinity.util.io.StreamUtils;
public class PartyNPC extends AbstractStruct implements HasViewerTabs, HasAddRemovable, AddRemovable
{
// GAM/PartyNPC-specific field labels
public static final String GAM_NPC = "Party member";
public static final String GAM_NPC_SELECTION_STATE = "Selection state";
public static final String GAM_NPC_PARTY_POSITION = "Party position";
public static final String GAM_NPC_OFFSET_CRE = CreResource.CHR_OFFSET_CRE;
public static final String GAM_NPC_CRE_SIZE = CreResource.CHR_CRE_SIZE;
public static final String GAM_NPC_CHARACTER = "Character";
public static final String GAM_NPC_ORIENTATION = "Orientation";
public static final String GAM_NPC_CURRENT_AREA = "Current area";
public static final String GAM_NPC_LOCATION_X = "Location: X";
public static final String GAM_NPC_LOCATION_Y = "Location: Y";
public static final String GAM_NPC_VIEWPORT_X = "Viewport location: X";
public static final String GAM_NPC_VIEWPORT_Y = "Viewport location: Y";
public static final String GAM_NPC_MODAL_STATE = "Modal state";
public static final String GAM_NPC_HAPPINESS = "Happiness";
public static final String GAM_NPC_QUICK_WEAPON_SLOT_FMT = CreResource.CHR_QUICK_WEAPON_SLOT_FMT;
public static final String GAM_NPC_QUICK_SHIELD_SLOT_FMT = CreResource.CHR_QUICK_SHIELD_SLOT_FMT;
public static final String GAM_NPC_QUICK_WEAPON_ABILITY_FMT = CreResource.CHR_QUICK_WEAPON_ABILITY_FMT;
public static final String GAM_NPC_QUICK_SHIELD_ABILITY_FMT = CreResource.CHR_QUICK_SHIELD_ABILITY_FMT;
public static final String GAM_NPC_QUICK_SPELL_FMT = CreResource.CHR_QUICK_SPELL_FMT;
public static final String GAM_NPC_QUICK_SPELL_CLASS_FMT = CreResource.CHR_QUICK_SPELL_CLASS_FMT;
public static final String GAM_NPC_QUICK_ITEM_SLOT_FMT = CreResource.CHR_QUICK_ITEM_SLOT_FMT;
public static final String GAM_NPC_QUICK_ITEM_ABILITY_FMT = CreResource.CHR_QUICK_ITEM_ABILITY_FMT;
public static final String GAM_NPC_QUICK_ABILITY_FMT = CreResource.CHR_QUICK_ABILITY_FMT;
public static final String GAM_NPC_QUICK_SONG_FMT = CreResource.CHR_QUICK_SONG_FMT;
public static final String GAM_NPC_QUICK_BUTTON_FMT = CreResource.CHR_QUICK_BUTTON_FMT;
public static final String GAM_NPC_NAME = CreResource.CHR_NAME;
public static final String GAM_NPC_VOICE_SET = CreResource.CHR_VOICE_SET;
public static final String GAM_NPC_VOICE_SET_PREFIX = CreResource.CHR_VOICE_SET_PREFIX;
public static final String GAM_NPC_NUM_TIMES_TALKED_TO = "# times talked to";
public static final String GAM_NPC_EXPERTISE = "Expertise";
public static final String GAM_NPC_POWER_ATTACK = "Power attack";
public static final String GAM_NPC_ARTERIAL_STRIKE = "Arterial strike";
public static final String GAM_NPC_HAMSTRING = "Hamstring";
public static final String GAM_NPC_RAPID_SHOT = "Rapid shot";
public static final String GAM_NPC_CRE_RESOURCE = "CRE resource";
public static final String GAM_NPC_STAT_FOE_VANQUISHED = "Most powerful foe vanquished";
public static final String GAM_NPC_STAT_XP_FOE_VANQUISHED = "XP for most powerful foe";
public static final String GAM_NPC_STAT_TIME_IN_PARTY = "Time in party (ticks)";
public static final String GAM_NPC_STAT_JOIN_TIME = "Join time (ticks)";
public static final String GAM_NPC_STAT_IN_PARTY = "Currently in party?";
public static final String GAM_NPC_STAT_INITIAL_CHAR = "Initial character";
public static final String GAM_NPC_STAT_KILLS_XP_CHAPTER = "Kill XP (chapter)";
public static final String GAM_NPC_STAT_NUM_KILLS_CHAPTER = "# kills (chapter)";
public static final String GAM_NPC_STAT_KILLS_XP_GAME = "Kill XP (game)";
public static final String GAM_NPC_STAT_NUM_KILLS_GAME = "# kills (game)";
public static final String GAM_NPC_STAT_FAV_SPELL_FMT = "Favorite spell %d";
public static final String GAM_NPC_STAT_FAV_SPELL_COUNT_FMT = "Favorite spell count %d";
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";
private static final LongIntegerHashMap<String> partyOrder = new LongIntegerHashMap<String>();
private static final LongIntegerHashMap<String> m_selected = new LongIntegerHashMap<String>();
private static final String s_noyes[] = {"No", "Yes"};
static {
partyOrder.put(0L, "Slot 1");
partyOrder.put(1L, "Slot 2");
partyOrder.put(2L, "Slot 3");
partyOrder.put(3L, "Slot 4");
partyOrder.put(4L, "Slot 5");
partyOrder.put(5L, "Slot 6");
// partyOrder.put(0x8000L, "In party, dead");
partyOrder.put(new Long(0xffff), "Not in party");
m_selected.put(0L, "Not selected");
m_selected.put(1L, "Selected");
m_selected.put(32768L, "Dead");
}
PartyNPC() throws Exception
{
super(null, GAM_NPC, createEmptyBuffer(), 0);
}
PartyNPC(AbstractStruct superStruct, ByteBuffer buffer, int offset, int nr) throws Exception
{
super(superStruct, GAM_NPC + " " + nr, buffer, offset);
}
PartyNPC(AbstractStruct superStruct, String name, ByteBuffer buffer, int offset) throws Exception
{
super(superStruct, name, buffer, offset);
}
// --------------------- Begin Interface HasAddRemovable ---------------------
@Override
public AddRemovable[] getAddRemovables() throws Exception
{
return new AddRemovable[]{};
}
@Override
public AddRemovable confirmAddEntry(AddRemovable struct) throws Exception
{
return struct;
}
@Override
public boolean confirmRemoveEntry(AddRemovable entry) throws Exception
{
return true;
}
// --------------------- End Interface HasAddRemovable ---------------------
//--------------------- Begin Interface AddRemovable ---------------------
@Override
public boolean canRemove()
{
return true;
}
//--------------------- End Interface AddRemovable ---------------------
// --------------------- Begin Interface HasViewerTabs ---------------------
@Override
public int getViewerTabCount()
{
return 1;
}
@Override
public String getViewerTabName(int index)
{
return StructViewer.TAB_VIEW;
}
@Override
public JComponent getViewerTab(int index)
{
return new ViewerNPC(this);
}
@Override
public boolean viewerTabAddedBefore(int index)
{
return true;
}
// --------------------- End Interface HasViewerTabs ---------------------
@Override
protected void datatypeAddedInChild(AbstractStruct child, AddRemovable datatype)
{
((DecNumber)getAttribute(GAM_NPC_CRE_SIZE)).setValue(getField(getFieldCount() - 1).getSize());
super.datatypeAddedInChild(child, datatype);
}
@Override
protected void datatypeRemoved(AddRemovable datatype)
{
if (datatype instanceof CreResource) {
((DecNumber)getAttribute(GAM_NPC_CRE_SIZE)).setValue(0);
((HexNumber)getAttribute(GAM_NPC_OFFSET_CRE)).setValue(0);
}
}
@Override
protected void datatypeRemovedInChild(AbstractStruct child, AddRemovable datatype)
{
((DecNumber)getAttribute(GAM_NPC_CRE_SIZE)).setValue(getField(getFieldCount() - 1).getSize());
super.datatypeRemovedInChild(child, datatype);
}
void updateCREOffset()
{
StructEntry entry = getField(getFieldCount() - 1);
if (entry instanceof CreResource)
((HexNumber)getAttribute(GAM_NPC_OFFSET_CRE)).setValue(entry.getOffset());
}
@Override
public int read(ByteBuffer buffer, int offset) throws Exception
{
addField(new HashBitmap(buffer, offset, 2, GAM_NPC_SELECTION_STATE, m_selected));
addField(new HashBitmap(buffer, offset + 2, 2, GAM_NPC_PARTY_POSITION, partyOrder));
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));
if (buffer.get(offset + 12) == 0x2A) {
addField(new TextString(buffer, offset + 12, 8, GAM_NPC_CHARACTER));
} else {
addField(new ResourceRef(buffer, offset + 12, GAM_NPC_CHARACTER, "CRE"));
}
addField(new Bitmap(buffer, offset + 20, 4, GAM_NPC_ORIENTATION, Actor.s_orientation));
addField(new ResourceRef(buffer, offset + 24, GAM_NPC_CURRENT_AREA, "ARE"));
addField(new DecNumber(buffer, offset + 32, 2, GAM_NPC_LOCATION_X));
addField(new DecNumber(buffer, offset + 34, 2, GAM_NPC_LOCATION_Y));
addField(new DecNumber(buffer, offset + 36, 2, GAM_NPC_VIEWPORT_X));
addField(new DecNumber(buffer, offset + 38, 2, GAM_NPC_VIEWPORT_Y));
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));
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"));
}
for (int i = 0; i < 4; i++) {
addField(new DecNumber(buffer, offset + 148 + (i * 2), 2,
String.format(GAM_NPC_QUICK_WEAPON_ABILITY_FMT, i+1)));
}
for (int i = 0; i < 3; i++) {
addField(new ResourceRef(buffer, offset + 156 + (i * 8),
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"));
}
for (int i = 0; i < 3; i++) {
addField(new DecNumber(buffer, offset + 186 + (i * 2), 2,
String.format(GAM_NPC_QUICK_ITEM_ABILITY_FMT, i+1)));
}
addField(new TextString(buffer, offset + 192, 32, GAM_NPC_NAME));
addField(new DecNumber(buffer, offset + 224, 4, GAM_NPC_NUM_TIMES_TALKED_TO));
offset = readCharStats(buffer, offset + 228);
addField(new TextString(buffer, offset, 8, GAM_NPC_VOICE_SET));
offset += 8;
}
else if (Profile.getEngine() == Profile.Engine.BG2 || Profile.isEnhancedEdition()) {
addField(new IdsBitmap(buffer, offset + 40, 2, GAM_NPC_MODAL_STATE, "MODAL.IDS"));
addField(new DecNumber(buffer, offset + 42, 2, GAM_NPC_HAPPINESS));
addField(new Unknown(buffer, offset + 44, 96));
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"));
}
for (int i = 0; i < 4; i++) {
addField(new DecNumber(buffer, offset + 148 + (i * 2), 2,
String.format(GAM_NPC_QUICK_WEAPON_ABILITY_FMT, i+1)));
}
for (int i = 0; i < 3; i++) {
addField(new ResourceRef(buffer, offset + 156 + (i * 8),
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"));
}
for (int i = 0; i < 3; i++) {
addField(new DecNumber(buffer, offset + 186 + (i * 2), 2,
String.format(GAM_NPC_QUICK_ITEM_ABILITY_FMT, i+1)));
}
addField(new TextString(buffer, offset + 192, 32, GAM_NPC_NAME));
addField(new DecNumber(buffer, offset + 224, 4, GAM_NPC_NUM_TIMES_TALKED_TO));
offset = readCharStats(buffer, offset + 228);
addField(new TextString(buffer, offset, 8, GAM_NPC_VOICE_SET));
offset += 8;
}
else if (Profile.getEngine() == Profile.Engine.PST) {
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));
for (int i = 0; i < 4; i++) {
addField(new DecNumber(buffer, offset + 140 + (i * 2), 2,
String.format(GAM_NPC_QUICK_WEAPON_SLOT_FMT, i+1)));
}
for (int i = 0; i < 4; i++) {
addField(new DecNumber(buffer, offset + 148 + (i * 2), 2,
String.format(GAM_NPC_QUICK_WEAPON_ABILITY_FMT, i+1)));
}
for (int i = 0; i < 3; i++) {
addField(new ResourceRef(buffer, offset + 156 + (i * 8),
String.format(GAM_NPC_QUICK_SPELL_FMT, i+1), "SPL"));
}
for (int i = 0; i < 5; i++) {
addField(new DecNumber(buffer, offset + 180 + (i * 2), 2,
String.format(GAM_NPC_QUICK_ITEM_SLOT_FMT, i+1)));
}
for (int i = 0; i < 5; i++) {
addField(new DecNumber(buffer, offset + 190 + (i * 2), 2,
String.format(GAM_NPC_QUICK_ITEM_ABILITY_FMT, i+1)));
}
addField(new TextString(buffer, offset + 200, 32, GAM_NPC_NAME));
addField(new DecNumber(buffer, offset + 232, 4, GAM_NPC_NUM_TIMES_TALKED_TO));
offset = readCharStats(buffer, offset + 236);
addField(new Unknown(buffer, offset, 8));
offset += 8;
}
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"));
}
for (int i = 0; i < 4; i++) {
addField(new DecNumber(buffer, offset + 148 + (i * 2), 2,
String.format(GAM_NPC_QUICK_WEAPON_ABILITY_FMT, i+1)));
}
for (int i = 0; i < 3; i++) {
addField(new ResourceRef(buffer, offset + 156 + (i * 8),
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"));
}
for (int i = 0; i < 3; i++) {
addField(new DecNumber(buffer, offset + 186 + (i * 2), 2,
String.format(GAM_NPC_QUICK_ITEM_ABILITY_FMT, i+1)));
}
addField(new TextString(buffer, offset + 192, 32, GAM_NPC_NAME));
addField(new Unknown(buffer, offset + 224, 4));
offset = readCharStats(buffer, offset + 228);
addField(new TextString(buffer, offset, 8, GAM_NPC_VOICE_SET_PREFIX));
addField(new TextString(buffer, offset + 8, 32, GAM_NPC_VOICE_SET));
offset += 40;
}
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"));
}
for (int i = 0; i < 4; i++) {
addField(new DecNumber(buffer, offset + 156 + (i * 4), 2,
String.format(GAM_NPC_QUICK_WEAPON_ABILITY_FMT, i+1)));
addField(new DecNumber(buffer, offset + 158 + (i * 4), 2,
String.format(GAM_NPC_QUICK_SHIELD_ABILITY_FMT, i+1)));
}
for (int i = 0; i < 9; i++) {
addField(new ResourceRef(buffer, offset + 172 + (i * 8),
String.format(GAM_NPC_QUICK_SPELL_FMT, i+1), "SPL"));
}
for (int i = 0; i < 9; i++) {
addField(new IdsBitmap(buffer, offset + 244 + i, 1,
String.format(GAM_NPC_QUICK_SPELL_CLASS_FMT, i+1), "CLASS.IDS"));
}
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"));
}
for (int i = 0; i < 3; i++) {
addField(new DecNumber(buffer, offset + 260 + (i * 2), 2,
String.format(GAM_NPC_QUICK_ITEM_ABILITY_FMT, i+1)));
}
for (int i = 0; i < 9; i++) {
addField(new ResourceRef(buffer, offset + 266 + (i * 8),
String.format(GAM_NPC_QUICK_ABILITY_FMT, i+1), "SPL"));
}
for (int i = 0; i < 9; i++) {
addField(new ResourceRef(buffer, offset + 338 + (i * 8),
String.format(GAM_NPC_QUICK_SONG_FMT, i+1), "SPL"));
}
for (int i = 0; i < 9; i++) {
addField(new DecNumber(buffer, offset + 410 + (i * 4), 4,
String.format(GAM_NPC_QUICK_BUTTON_FMT, i+1)));
}
addField(new TextString(buffer, offset + 446, 32, GAM_NPC_NAME));
addField(new Unknown(buffer, offset + 478, 4));
offset = readCharStats(buffer, offset + 482);
addField(new TextString(buffer, offset, 8, GAM_NPC_VOICE_SET_PREFIX));
addField(new TextString(buffer, offset + 8, 32, GAM_NPC_VOICE_SET));
addField(new Unknown(buffer, offset + 40, 12));
addField(new DecNumber(buffer, offset + 52, 4, GAM_NPC_EXPERTISE));
addField(new DecNumber(buffer, offset + 56, 4, GAM_NPC_POWER_ATTACK));
addField(new DecNumber(buffer, offset + 60, 4, GAM_NPC_ARTERIAL_STRIKE));
addField(new DecNumber(buffer, offset + 64, 4, GAM_NPC_HAMSTRING));
addField(new DecNumber(buffer, offset + 68, 4, GAM_NPC_RAPID_SHOT));
addField(new Unknown(buffer, offset + 72, 162));
offset += 234;
}
if (creOffset.getValue() != 0) {
addField(new CreResource(this, GAM_NPC_CRE_RESOURCE, buffer, creOffset.getValue()));
}
return offset;
}
private int readCharStats(ByteBuffer buffer, int offset)
{
addField(new StringRef(buffer, offset, GAM_NPC_STAT_FOE_VANQUISHED));
addField(new DecNumber(buffer, offset + 4, 4, GAM_NPC_STAT_XP_FOE_VANQUISHED));
addField(new DecNumber(buffer, offset + 8, 4, GAM_NPC_STAT_TIME_IN_PARTY));
addField(new DecNumber(buffer, offset + 12, 4, GAM_NPC_STAT_JOIN_TIME));
addField(new Bitmap(buffer, offset + 16, 1, GAM_NPC_STAT_IN_PARTY, s_noyes));
addField(new Unknown(buffer, offset + 17, 2));
addField(new TextString(buffer, offset + 19, 1, GAM_NPC_STAT_INITIAL_CHAR));
addField(new DecNumber(buffer, offset + 20, 4, GAM_NPC_STAT_KILLS_XP_CHAPTER));
addField(new DecNumber(buffer, offset + 24, 4, GAM_NPC_STAT_NUM_KILLS_CHAPTER));
addField(new DecNumber(buffer, offset + 28, 4, GAM_NPC_STAT_KILLS_XP_GAME));
addField(new DecNumber(buffer, offset + 32, 4, GAM_NPC_STAT_NUM_KILLS_GAME));
for (int i = 0; i < 4; i++) {
addField(new ResourceRef(buffer, offset + 36 + (i * 8),
String.format(GAM_NPC_STAT_FAV_SPELL_FMT, i+1), "SPL"));
}
for (int i = 0; i < 4; i++) {
addField(new UnsignDecNumber(buffer, offset + 68 + (i * 2), 2,
String.format(GAM_NPC_STAT_FAV_SPELL_COUNT_FMT, i+1)));
}
for (int i = 0; i < 4; i++) {
addField(new ResourceRef(buffer, offset + 76 + (i * 8),
String.format(GAM_NPC_STAT_FAV_WEAPON_FMT, i+1), "ITM"));
}
for (int i = 0; i < 4; i++) {
addField(new UnsignDecNumber(buffer, offset + 108 + (i * 2), 2,
String.format(GAM_NPC_STAT_FAV_WEAPON_COUNT_FMT, i+1)));
}
return offset + 116;
}
protected static ByteBuffer createEmptyBuffer()
{
int size = 0;
if (Profile.getEngine() == Profile.Engine.BG1 ||
Profile.getEngine() == Profile.Engine.BG2 ||
Profile.isEnhancedEdition()) {
size = 352;
} else if (Profile.getEngine() == Profile.Engine.PST) {
size = 360;
} else if (Profile.getEngine() == Profile.Engine.IWD2) {
size = 832;
} else {
size = 384;
}
return StreamUtils.getByteBuffer(size);
}
}