// Near Infinity - An Infinity Engine Browser and Editor
// Copyright (C) 2001 - 2005 Jon Olav Hauglid
// See LICENSE.txt for license information
package org.infinity.datatype;
import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.infinity.resource.Profile;
import org.infinity.resource.ResourceFactory;
import org.infinity.resource.StructEntry;
import org.infinity.util.IdsMapCache;
import org.infinity.util.IdsMapEntry;
import org.infinity.util.LongIntegerHashMap;
import org.infinity.util.Pair;
import org.infinity.util.Table2da;
import org.infinity.util.Table2daCache;
import org.infinity.util.io.FileManager;
/** Specialized ResourceBitmap type for parsing ARE song reference entries. */
public class Song2daBitmap extends ResourceBitmap
{
// Cached content of the song entries
private static final ArrayList<RefEntry> SongList = new ArrayList<ResourceBitmap.RefEntry>();
// Source of music resource references
private static String TableName;
// Textual representation of the song reference field data
private static String FormatString = FMT_REF_NAME_VALUE;
public Song2daBitmap(ByteBuffer buffer, int offset, int length)
{
this(null, buffer, offset, length);
}
public Song2daBitmap(StructEntry parent, ByteBuffer buffer, int offset, int length)
{
this(parent, buffer, offset, length, "Song");
}
public Song2daBitmap(ByteBuffer buffer, int offset, int length, String name)
{
this(null, buffer, offset, length, name);
}
public Song2daBitmap(StructEntry parent, ByteBuffer buffer, int offset, int length, String name)
{
super(parent, buffer, offset, length, name, createSongList(), "Unknown", FormatString);
}
public static synchronized List<RefEntry> createSongList()
{
if (SongList.isEmpty()) {
// search "music" subfolder as well
List<Path> searchDirs = new ArrayList<>();
searchDirs.add(FileManager.query(Profile.getGameRoot(), "Music"));
if (ResourceFactory.resourceExists("SONGLIST.2DA")) {
TableName = "SONGLIST.2DA";
FormatString = FMT_REF_NAME_VALUE;
createSongList_SONGLIST(searchDirs);
} else if (ResourceFactory.resourceExists("MUSICLIS.IDS")) {
TableName = "MUSICLIS.IDS";
FormatString = FMT_REF_HYPHEN_VALUE;
createSongList_MUSIC(searchDirs);
} else if (ResourceFactory.resourceExists("MUSIC.IDS")) {
TableName = "MUSIC.IDS";
FormatString = FMT_REF_HYPHEN_VALUE;
createSongList_MUSIC(searchDirs);
} else if (ResourceFactory.resourceExists("SONGS.IDS")) {
TableName = "SONGS.IDS";
FormatString = FMT_REF_NAME_VALUE;
createSongList_PST(searchDirs);
} else {
TableName = "";
FormatString = FMT_REF_HYPHEN_VALUE;
createSongList_BG1(searchDirs);
}
}
return SongList;
}
// Create song references for BG2 and Enhanced Edition games
private static List<RefEntry> createSongList_SONGLIST(List<Path> searchDirs)
{
if (SongList.isEmpty()) {
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"));
for (int i = 0, size = table.getRowCount(); i < size; i++) {
String name = table.get(i, 1);
String ref = table.get(i, 2);
if (defValue.equalsIgnoreCase(ref)) {
ref = "None";
}
SongList.add(new RefEntry((long)i, ref, name, searchDirs));
}
}
return SongList;
}
// Create song references for IWD and IWD2
private static List<RefEntry> createSongList_MUSIC(List<Path> searchDirs)
{
if (SongList.isEmpty()) {
LongIntegerHashMap<IdsMapEntry> map = IdsMapCache.get(TableName).getMap();
SongList.ensureCapacity(2 + map.size());
SongList.add(new RefEntry(0xfffffffeL, "Continue area music"));
SongList.add(new RefEntry(0xffffffffL, "Continue outside music"));
long[] keys = map.keys();
for (final long key: keys) {
String name = map.get(key).getString();
name = Character.toUpperCase(name.charAt(0)) + name.substring(1);
String ref = name.replaceAll("_", "").toUpperCase(Locale.ENGLISH) + ".MUS";
if (key == 0L) {
ref = name;
}
SongList.add(new RefEntry(key, ref, null, searchDirs));
}
}
return SongList;
}
// Create song references for PST
private static List<RefEntry> createSongList_PST(List<Path> searchDirs)
{
// PST used static associations of SONGS.IDS with MUS resources (source: songlist.txt in game's music folder)
final LongIntegerHashMap<Pair<String>> map = new LongIntegerHashMap<Pair<String>>();
map.put(0L, new Pair<String>(null, "No music"));
map.put(1L, new Pair<String>("MAIN.MUS", "Planescape Main Theme"));
map.put(2L, new Pair<String>("CHAR_01.MUS", "Char: Nameless One"));
map.put(3L, new Pair<String>("CHAR_02.MUS", "Char: Transcendent One"));
map.put(4L, new Pair<String>("CHAR_03.MUS", "Char: The Shadows"));
map.put(5L, new Pair<String>("CHAR_04.MUS", "Char: Deionarra"));
map.put(6L, new Pair<String>("CHAR_05.MUS", "Char: Ravel"));
map.put(7L, new Pair<String>("CHAR_06.MUS", "Char: Fhjull Forked-Tongue"));
map.put(8L, new Pair<String>("CHAR_07.MUS", "Char: Trias"));
map.put(9L, new Pair<String>("CHAR_09.MUS", "Char: Annah"));
map.put(10L, new Pair<String>("CHAR_10.MUS", "Char: Dak'kon"));
map.put(11L, new Pair<String>("CHAR_11.MUS", "Char: Morte"));
map.put(12L, new Pair<String>("CHAR_12.MUS", "Char: Nordom"));
map.put(13L, new Pair<String>("CHAR_13.MUS", "Char: Fall-from-Grace"));
map.put(14L, new Pair<String>("CHAR_14.MUS", "Char: Vhailor"));
map.put(15L, new Pair<String>("CHAR_15.MUS", "Char: Ignus"));
map.put(16L, new Pair<String>("MORT.MUS", "Area: Mortuary"));
map.put(17L, new Pair<String>("SIG.MUS", "Area: Sigil"));
map.put(18L, new Pair<String>("SMOL.MUS", "Area: Smoldering Corpse Bar"));
map.put(19L, new Pair<String>("BONE.MUS", "Area: Bones of the Night"));
map.put(20L, new Pair<String>("CIVIC.MUS", "Area: Civic Festhall"));
map.put(21L, new Pair<String>("RAVE.MUS", "Area: Ravel's Maze"));
map.put(22L, new Pair<String>("MODR.MUS", "Area: Modron Maze"));
map.put(23L, new Pair<String>("CUR.MUS", "Area: Curst"));
map.put(24L, new Pair<String>("BAAT.MUS", "Area: Baator"));
map.put(25L, new Pair<String>("FORT.MUS", "Area: Fortress of Regrets"));
map.put(26L, new Pair<String>("CHAR13b.MUS", "Area: Fall-from-Grace"));
map.put(27L, new Pair<String>("BT1.MUS", "Battle: Sigil"));
map.put(28L, new Pair<String>("BT2.MUS", "Battle: Catacombs"));
map.put(29L, new Pair<String>("BT3.MUS", "Battle: Curst"));
map.put(30L, new Pair<String>("BT4.MUS", "Battle: Modron Cube"));
map.put(31L, new Pair<String>("BT5.MUS", "Battle: Fortress of Regrets"));
map.put(32L, new Pair<String>("BT5.MUS", "Battle: Fortress of Regrets - Final)"));
map.put(33L, new Pair<String>("END_01G.MUS", "Ending: Good"));
map.put(34L, new Pair<String>("END_01B.MUS", "Ending: Bad"));
map.put(35L, new Pair<String>("END_02.MUS", "Ending: Indifferent"));
map.put(36L, new Pair<String>("SMO2.MUS", "Smoldering Corpse Alternate"));
if (SongList.isEmpty()) {
SongList.ensureCapacity(2 + map.size());
SongList.add(new RefEntry(0xfffffffeL, "Special", "Continue area music"));
SongList.add(new RefEntry(0xffffffffL, "Special", "Continue outside music"));
long[] keys = map.keys();
for (final long key: keys) {
String name = map.get(key).getSecond();
String ref = map.get(key).getFirst();
if (ref != null) {
SongList.add(new RefEntry(key, ref, name, searchDirs));
} else {
SongList.add(new RefEntry(key, "None", name));
}
}
}
return SongList;
}
// Create song references for BG1
private static List<RefEntry> createSongList_BG1(List<Path> searchDirs)
{
// BG1 used static list of songs (source: GemRB's unhardcoded music.2da)
final LongIntegerHashMap<String> map = new LongIntegerHashMap<String>();
map.put(0L, "None");
map.put(1L, "CDAY1.MUS");
map.put(2L, "CDAY2.MUS");
map.put(3L, "CNITE.MUS");
map.put(4L, "CHANTS.MUS");
map.put(5L, "DREAM.MUS");
map.put(6L, "FESTI.MUS");
map.put(7L, "FDAY.MUS");
map.put(8L, "FNITE.MUS");
map.put(9L, "PDAY.MUS");
map.put(10L, "PNITE.MUS");
map.put(11L, "TEMPLE.MUS");
map.put(12L, "THEME.MUS");
map.put(13L, "TDAY1.MUS");
map.put(14L, "TDAY2.MUS");
map.put(15L, "TNITE.MUS");
map.put(16L, "DUNG1.MUS");
map.put(17L, "DUNG2.MUS");
map.put(18L, "DUNG3.MUS");
map.put(19L, "BC1.MUS");
map.put(20L, "BC2.MUS");
map.put(21L, "BD1.MUS");
map.put(22L, "BD2.MUS");
map.put(23L, "BL1.MUS");
map.put(24L, "BL2.MUS");
map.put(25L, "BF1.MUS");
map.put(26L, "BF2.MUS");
map.put(27L, "BP1.MUS");
map.put(28L, "BP2.MUS");
map.put(29L, "BW1.MUS");
map.put(30L, "BRIDGE.MUS");
map.put(31L, "FORT.MUS");
map.put(32L, "CHAPTER.MUS");
map.put(33L, "MPO900.MUS");
map.put(34L, "TAV1.MUS");
map.put(35L, "TAV2.MUS");
map.put(36L, "TAV3.MUS");
map.put(37L, "TAV4.MUS");
map.put(38L, "DEATH.MUS");
if (SongList.isEmpty()) {
SongList.add(new RefEntry(0xfffffffeL, "Continue area music"));
SongList.add(new RefEntry(0xffffffffL, "Continue outside music"));
long[] keys = map.keys();
for (final long key: keys) {
String ref = map.get(key);
SongList.add(new RefEntry(key, ref, null, searchDirs));
}
}
return SongList;
}
public static String getTableName()
{
return TableName;
}
public static synchronized void resetSonglist()
{
Table2daCache.cacheInvalid(ResourceFactory.getResourceEntry(TableName));
SongList.clear();
TableName = "";
FormatString = FMT_REF_NAME_VALUE;
}
}