|
From: <xer...@us...> - 2026-07-17 18:19:24
|
Revision: 1668
http://sourceforge.net/p/seq/svn/1668
Author: xerxes00
Date: 2026-07-17 18:19:21 +0000 (Fri, 17 Jul 2026)
Log Message:
-----------
Add Alternate Advancement window
Adds a dockable AA list window (View -> Alternate Advancement) showing
owned AAs with name, rank, points spent, and unspent/assigned/total
totals. AA names resolve via a new DBStr loader for dbstr_us.txt
(graceful fallback to ids without it). Builds the table from
OP_SendAATable and the character profile.
Modified Paths:
--------------
showeq/branches/xerxes-trunk-dev/README
showeq/branches/xerxes-trunk-dev/conf/zoneopcodes.xml
showeq/branches/xerxes-trunk-dev/src/Makefile.am
showeq/branches/xerxes-trunk-dev/src/everquest.h
showeq/branches/xerxes-trunk-dev/src/interface.cpp
showeq/branches/xerxes-trunk-dev/src/interface.h
showeq/branches/xerxes-trunk-dev/src/s_everquest.h
Added Paths:
-----------
showeq/branches/xerxes-trunk-dev/src/aalist.cpp
showeq/branches/xerxes-trunk-dev/src/aalist.h
showeq/branches/xerxes-trunk-dev/src/aamgr.cpp
showeq/branches/xerxes-trunk-dev/src/aamgr.h
showeq/branches/xerxes-trunk-dev/src/dbstr.cpp
showeq/branches/xerxes-trunk-dev/src/dbstr.h
Modified: showeq/branches/xerxes-trunk-dev/README
===================================================================
--- showeq/branches/xerxes-trunk-dev/README 2026-07-17 17:48:07 UTC (rev 1667)
+++ showeq/branches/xerxes-trunk-dev/README 2026-07-17 18:19:21 UTC (rev 1668)
@@ -32,3 +32,39 @@
shows current command line arguments. Also look at the
conf/seqdef.xml file for configuration file options and defaults.
Most configuration changes can be done via the user interface.
+
+DATA FILES
+----------
+Some features read string/data files copied from the EverQuest client
+directory. Place these in a ShowEQ data directory - either your per-user
+dir ($HOME/.showeq) or the installed package data dir (pkgdatadir,
+e.g. /usr/local/share/showeq/). ShowEQ searches the per-user dir first,
+then pkgdatadir.
+
+ spells_us.txt spell data
+ eqstr_us.txt formatted message strings
+ dbstr_us.txt string database (AA titles, etc.) - required for the
+ Alternate Advancement window to show ability NAMES.
+ Without it, that window still works but lists abilities
+ as "AA title <id>". The filename is dbstr_us.txt (the
+ US-English database); there is no dbstr_en.txt.
+
+The file name each feature looks for can be overridden in the config
+(e.g. the DBStrFile setting under [Interface]).
+
+ALTERNATE ADVANCEMENT WINDOW
+----------------------------
+View -> Alternate Advancement lists the AAs your character owns, with the
+ability name, current rank (rank / max rank), points spent, and a footer
+of unspent / assigned / total AA points. Its purpose is to see at a glance
+which AAs you are actively leveling without scrolling the in-game AA UI.
+
+A "Hide AAs at max rank" checkbox at the bottom hides abilities already at
+their maximum rank so only in-progress ones remain. Its state is saved to
+the config file (HideMaxedAA under the [AAList] section) and restored on
+the next run; it defaults to off.
+
+Caveat: "max rank" is the ability's cap, not necessarily what you can buy
+right now - higher ranks may carry a character-level requirement, so an
+ability shown below its max (e.g. 3/4) does not guarantee the next rank is
+purchasable yet.
Modified: showeq/branches/xerxes-trunk-dev/conf/zoneopcodes.xml
===================================================================
--- showeq/branches/xerxes-trunk-dev/conf/zoneopcodes.xml 2026-07-17 17:48:07 UTC (rev 1667)
+++ showeq/branches/xerxes-trunk-dev/conf/zoneopcodes.xml 2026-07-17 18:19:21 UTC (rev 1668)
@@ -634,8 +634,9 @@
<opcode id="ffff" name="OP_VeteranRewards" updated="04/18/11">
<comment></comment>
</opcode>
- <opcode id="ffff" name="OP_SendAATable" updated="08/12/09">
- <comment></comment>
+ <opcode id="2719" name="OP_SendAATable" updated="07/15/26">
+ <comment>AA window</comment>
+ <payload dir="server" typename="uint8_t" sizechecktype="none"/>
</opcode>
<opcode id="ffff" name="OP_SendAAStats" updated="08/12/09">
<comment></comment>
Modified: showeq/branches/xerxes-trunk-dev/src/Makefile.am
===================================================================
--- showeq/branches/xerxes-trunk-dev/src/Makefile.am 2026-07-17 17:48:07 UTC (rev 1667)
+++ showeq/branches/xerxes-trunk-dev/src/Makefile.am 2026-07-17 18:19:21 UTC (rev 1668)
@@ -5,6 +5,9 @@
bin_PROGRAMS = showeq
showeq_SOURCES = \
+ aalist.cpp \
+ aamgr.cpp \
+ dbstr.cpp \
bazaarlog.cpp \
category.cpp \
combatlog.cpp \
@@ -74,6 +77,8 @@
showeq_moc_SRCS = \
+ aalist.moc \
+ aamgr.moc \
bazaarlog.moc \
category.moc \
combatlog.moc \
@@ -127,6 +132,8 @@
nodist_showeq_SOURCES = ui_mapicondialog.h $(showeq_moc_SRCS)
$(srcdir)/bazaarlog.cpp: bazaarlog.moc
+$(srcdir)/aalist.cpp: aalist.moc
+$(srcdir)/aamgr.cpp: aamgr.moc
$(srcdir)/category.cpp: category.moc
$(srcdir)/combatlog.cpp: combatlog.moc
$(srcdir)/compass.cpp: compass.moc
Added: showeq/branches/xerxes-trunk-dev/src/aalist.cpp
===================================================================
--- showeq/branches/xerxes-trunk-dev/src/aalist.cpp (rev 0)
+++ showeq/branches/xerxes-trunk-dev/src/aalist.cpp 2026-07-17 18:19:21 UTC (rev 1668)
@@ -0,0 +1,156 @@
+/*
+ * aalist.cpp
+ * Copyright 2026 by the respective ShowEQ Developers
+ *
+ * This file is part of ShowEQ.
+ * http://www.sourceforge.net/projects/seq
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "aalist.h"
+#include "aamgr.h"
+#include "main.h" // for pSEQPrefs
+
+#include <QHeaderView>
+#include <QWidget>
+#include <QVBoxLayout>
+#include <QLabel>
+#include <QCheckBox>
+
+AAList::AAList(AAMgr* aaMgr, QWidget* parent, const char* name)
+ : SEQListView("AAList", parent, name),
+ m_aaMgr(aaMgr),
+ m_hideMaxed(false)
+{
+ m_hideMaxed = pSEQPrefs->getPrefBool("HideMaxedAA", preferenceName(), false);
+
+ addColumn("Ability");
+ addColumn("Rank");
+ addColumn("Points");
+
+ restoreColumns();
+
+ // Column sizing, applied AFTER restoreColumns so it is not overridden:
+ // the Ability column auto-fits the widest displayed name; Rank and
+ // Points are pinned to the same small width; nothing stretches to fill
+ // the pane (empty space to the right).
+ header()->setStretchLastSection(false);
+ header()->setSectionResizeMode(AACOL_NAME, QHeaderView::ResizeToContents);
+ header()->setSectionResizeMode(AACOL_RANK, QHeaderView::Interactive);
+ header()->setSectionResizeMode(AACOL_POINTS, QHeaderView::Interactive);
+ setColumnWidth(AACOL_RANK, 56);
+ setColumnWidth(AACOL_POINTS, 56);
+
+ // refresh only when the AA data actually changes
+ connect(m_aaMgr, SIGNAL(aaChanged()), this, SLOT(rebuild()));
+
+ rebuild();
+}
+
+AAList::~AAList()
+{
+}
+
+void AAList::setHideMaxed(bool hide)
+{
+ m_hideMaxed = hide;
+ pSEQPrefs->setPrefBool("HideMaxedAA", preferenceName(), hide);
+ rebuild();
+}
+
+void AAList::rebuild()
+{
+ // owned set is small; a full repopulate is simplest and cheap
+ clear();
+
+ const QList<AAMgr::Row> rows = m_aaMgr->ownedRows();
+ for (int i = 0; i < rows.size(); i++)
+ {
+ const AAMgr::Row& r = rows[i];
+
+ // optionally hide abilities already at their max rank. NOTE: max rank
+ // is the ability's cap, not necessarily obtainable right now - higher
+ // ranks can carry level requirements - so a non-maxed ability is not a
+ // guarantee the next rank is buyable yet.
+ if (m_hideMaxed && r.maxRank && r.rank >= r.maxRank)
+ continue;
+
+ QString rankStr;
+ if (r.maxRank)
+ rankStr = QString("%1/%2").arg(r.rank).arg(r.maxRank);
+ else if (r.rank)
+ rankStr = QString::number(r.rank);
+
+ QStringList vals;
+ vals << r.name << rankStr << QString::number(r.points);
+ SEQListViewItem* item = new SEQListViewItem(this, vals);
+ item->setTextAlignment(AACOL_RANK, Qt::AlignRight);
+ item->setTextAlignment(AACOL_POINTS, Qt::AlignRight);
+ }
+}
+
+AAListWindow::AAListWindow(AAMgr* aaMgr, QWidget* parent, const char* name)
+ : SEQWindow("AAList", "ShowEQ - Alternate Advancement", parent, name),
+ m_aaMgr(aaMgr)
+{
+ // container holds the list plus a totals footer line
+ QWidget* container = new QWidget(this);
+ QVBoxLayout* layout = new QVBoxLayout(container);
+ layout->setContentsMargins(2, 2, 2, 2);
+ layout->setSpacing(2);
+
+ m_aaList = new AAList(aaMgr, container, name);
+ m_totals = new QLabel(container);
+ m_hideMaxed = new QCheckBox("Hide AAs at max rank", container);
+ m_hideMaxed->setChecked(m_aaList->hideMaxed());
+ m_hideMaxed->setToolTip("Hide abilities already at their maximum rank.\n"
+ "Note: an ability that is not maxed may still be\n"
+ "un-buyable right now - higher ranks can require a\n"
+ "higher character level.");
+
+ layout->addWidget(m_aaList);
+ layout->addWidget(m_totals);
+ layout->addWidget(m_hideMaxed);
+
+ setWidget(container);
+
+ connect(m_hideMaxed, SIGNAL(toggled(bool)), m_aaList, SLOT(setHideMaxed(bool)));
+
+ connect(m_aaMgr, SIGNAL(aaChanged()), this, SLOT(updateTotals()));
+ updateTotals();
+}
+
+AAListWindow::~AAListWindow()
+{
+}
+
+void AAListWindow::updateTotals(void)
+{
+ m_totals->setText(QString("Unspent: %1 Assigned: %2 Total: %3")
+ .arg(m_aaMgr->unspent())
+ .arg(m_aaMgr->assigned())
+ .arg(m_aaMgr->spent()));
+}
+
+void AAListWindow::savePrefs(void)
+{
+ SEQWindow::savePrefs();
+ m_aaList->savePrefs();
+}
+
+#ifndef QMAKEBUILD
+#include "aalist.moc"
+#endif
Added: showeq/branches/xerxes-trunk-dev/src/aalist.h
===================================================================
--- showeq/branches/xerxes-trunk-dev/src/aalist.h (rev 0)
+++ showeq/branches/xerxes-trunk-dev/src/aalist.h 2026-07-17 18:19:21 UTC (rev 1668)
@@ -0,0 +1,80 @@
+/*
+ * aalist.h
+ * Copyright 2026 by the respective ShowEQ Developers
+ *
+ * This file is part of ShowEQ.
+ * http://www.sourceforge.net/projects/seq
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef EQAALIST_H
+#define EQAALIST_H
+
+#include <QWidget>
+
+#include "seqlistview.h"
+#include "seqwindow.h"
+
+#define AACOL_NAME 0
+#define AACOL_RANK 1
+#define AACOL_POINTS 2
+
+class AAMgr;
+class QLabel;
+class QCheckBox;
+
+class AAList : public SEQListView
+{
+ Q_OBJECT
+
+ public:
+ AAList(AAMgr* aaMgr, QWidget* parent = 0, const char* name = 0);
+ ~AAList();
+
+ public slots:
+ void rebuild();
+ void setHideMaxed(bool hide);
+
+ public:
+ bool hideMaxed() const { return m_hideMaxed; }
+
+ private:
+ AAMgr* m_aaMgr;
+ bool m_hideMaxed;
+};
+
+class AAListWindow : public SEQWindow
+{
+ Q_OBJECT
+
+ public:
+ AAListWindow(AAMgr* aaMgr, QWidget* parent = 0, const char* name = 0);
+ ~AAListWindow();
+
+ AAList* aaList() { return m_aaList; }
+
+ public slots:
+ void savePrefs(void);
+ void updateTotals(void);
+
+ private:
+ AAMgr* m_aaMgr;
+ AAList* m_aaList;
+ QLabel* m_totals;
+ QCheckBox* m_hideMaxed;
+};
+
+#endif // EQAALIST_H
Added: showeq/branches/xerxes-trunk-dev/src/aamgr.cpp
===================================================================
--- showeq/branches/xerxes-trunk-dev/src/aamgr.cpp (rev 0)
+++ showeq/branches/xerxes-trunk-dev/src/aamgr.cpp 2026-07-17 18:19:21 UTC (rev 1668)
@@ -0,0 +1,163 @@
+/*
+ * aamgr.cpp
+ * Copyright 2026 by the respective ShowEQ Developers
+ *
+ * This file is part of ShowEQ.
+ * http://www.sourceforge.net/projects/seq
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "aamgr.h"
+#include "dbstr.h"
+#include "diagnosticmessages.h"
+
+#include <cstring>
+#include <algorithm>
+
+#include <QTimer>
+
+AAMgr::AAMgr(DBStr* dbstr, QObject* parent, const char* name)
+ : QObject(parent),
+ m_dbstr(dbstr),
+ m_spent(0),
+ m_assigned(0),
+ m_unspent(0),
+ m_changeScheduled(false)
+{
+ setObjectName(name);
+}
+
+AAMgr::~AAMgr()
+{
+}
+
+// Coalesce a whole record burst into a single UI refresh: schedule one
+// deferred emit rather than emitting per changed record.
+void AAMgr::markChanged()
+{
+ if (m_changeScheduled)
+ return;
+ m_changeScheduled = true;
+ QTimer::singleShot(0, this, SLOT(emitChanged()));
+}
+
+void AAMgr::emitChanged()
+{
+ m_changeScheduled = false;
+ emit aaChanged();
+}
+
+void AAMgr::aaInfo(const uint8_t* data, size_t len)
+{
+ // Only the fixed head (through rank, 37 bytes) is needed. The rest is a
+ // variable count-prefixed skills-requirement block plus prev/next ids.
+ if (len < sizeof(aaInfoStruct))
+ return;
+
+ const aaInfoStruct* r = (const aaInfoStruct*)data;
+
+ AADef def;
+ def.titleSID = r->titleSID;
+ def.rank = r->rank;
+ def.cost = r->cost;
+ def.aaID = r->aaID;
+
+ // update definition only if new or changed (avoids churn on re-sent bursts)
+ QHash<int32_t, AADef>::const_iterator it = m_defs.constFind(r->descID);
+ if (it == m_defs.constEnd() ||
+ it->titleSID != def.titleSID || it->rank != def.rank ||
+ it->cost != def.cost || it->aaID != def.aaID)
+ {
+ m_defs.insert(r->descID, def);
+
+ // track highest rank seen per AA title (ranks only grow within a session)
+ uint32_t& mx = m_maxRank[def.titleSID];
+ if (def.rank > mx)
+ mx = def.rank;
+
+ markChanged();
+ }
+}
+
+void AAMgr::player(const charProfileStruct* profile)
+{
+ if (!profile)
+ return;
+
+ // Rebuild the owned set from the profile's aa_array. Each nonzero entry
+ // is an owned AA: AA = current-rank descID, value = cumulative points.
+ QHash<int32_t, uint32_t> owned;
+ for (int i = 0; i < MAX_AA; i++)
+ {
+ const uint32_t val = profile->profile.aa_array[i].value;
+ if (val)
+ owned.insert((int32_t)profile->profile.aa_array[i].AA, val);
+ }
+
+ if (owned != m_owned ||
+ profile->profile.aa_spent != m_spent ||
+ profile->profile.aa_assigned != m_assigned ||
+ profile->profile.aa_unspent != m_unspent)
+ {
+ m_owned = owned;
+ m_spent = profile->profile.aa_spent;
+ m_assigned = profile->profile.aa_assigned;
+ m_unspent = profile->profile.aa_unspent;
+ markChanged();
+ }
+}
+
+QList<AAMgr::Row> AAMgr::ownedRows() const
+{
+ QList<Row> rows;
+
+ for (QHash<int32_t, uint32_t>::const_iterator it = m_owned.constBegin();
+ it != m_owned.constEnd(); ++it)
+ {
+ Row row;
+ row.points = it.value();
+
+ QHash<int32_t, AADef>::const_iterator dit = m_defs.constFind(it.key());
+ if (dit != m_defs.constEnd())
+ {
+ row.rank = dit->rank;
+ row.maxRank = m_maxRank.value(dit->titleSID, dit->rank);
+ row.name = m_dbstr ? m_dbstr->find(dit->titleSID) : QString();
+ if (row.name.isEmpty())
+ row.name = QString("AA title %1").arg(dit->titleSID);
+ }
+ else
+ {
+ // definition not received yet (profile arrived before the table)
+ row.rank = 0;
+ row.maxRank = 0;
+ row.name = QString("AA #%1").arg(it.key());
+ }
+
+ rows.append(row);
+ }
+
+ std::sort(rows.begin(), rows.end(),
+ [](const Row& a, const Row& b) {
+ return a.name.localeAwareCompare(b.name) < 0;
+ });
+
+ return rows;
+}
+
+#ifndef QMAKEBUILD
+#include "aamgr.moc"
+#endif
Added: showeq/branches/xerxes-trunk-dev/src/aamgr.h
===================================================================
--- showeq/branches/xerxes-trunk-dev/src/aamgr.h (rev 0)
+++ showeq/branches/xerxes-trunk-dev/src/aamgr.h 2026-07-17 18:19:21 UTC (rev 1668)
@@ -0,0 +1,104 @@
+/*
+ * aamgr.h
+ * Copyright 2026 by the respective ShowEQ Developers
+ *
+ * This file is part of ShowEQ.
+ * http://www.sourceforge.net/projects/seq
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef _AAMGR_H_
+#define _AAMGR_H_
+
+#include <cstdint>
+#include <QObject>
+#include <QHash>
+#include <QList>
+#include <QString>
+
+#include "everquest.h"
+
+class DBStr;
+
+//----------------------------------------------------------------------
+// AAMgr
+//
+// Builds an in-memory Alternate Advancement table from the OP_SendAATable
+// record burst the server sends at zone-in (one aaInfoStruct per AA rank),
+// and tracks which AAs the player owns from the character profile. Holds
+// no state on disk; the table is rebuilt from the wire each session. It
+// only signals aaChanged() when the definitions or the owned set actually
+// change, so ordinary zoning (an identical burst) does not churn the UI.
+class AAMgr : public QObject
+{
+ Q_OBJECT
+
+ public:
+ // per-rank definition
+ struct AADef
+ {
+ int32_t titleSID;
+ uint32_t rank;
+ uint32_t cost;
+ uint32_t aaID;
+ };
+
+ // one resolved row for display (an owned AA)
+ struct Row
+ {
+ QString name;
+ uint32_t rank;
+ uint32_t maxRank;
+ uint32_t points; // cumulative points spent (aa_array.value)
+ };
+
+ AAMgr(DBStr* dbstr, QObject* parent = 0, const char* name = 0);
+ ~AAMgr();
+
+ // owned AAs, resolved to name/rank/points, sorted by name
+ QList<Row> ownedRows() const;
+
+ // AA point totals from the character profile
+ uint32_t spent() const { return m_spent; }
+ uint32_t assigned() const { return m_assigned; }
+ uint32_t unspent() const { return m_unspent; }
+
+ public slots:
+ // OP_SendAATable record (one AA rank). Variable length; reads fixed head.
+ void aaInfo(const uint8_t* data, size_t len);
+ // character profile - supplies the owned AA set (descID -> cumulative pts)
+ void player(const charProfileStruct* profile);
+
+ signals:
+ void aaChanged();
+
+ private slots:
+ void emitChanged();
+
+ private:
+ void markChanged();
+
+ DBStr* m_dbstr;
+ QHash<int32_t, AADef> m_defs; // descID -> definition
+ QHash<int32_t, uint32_t> m_maxRank; // titleSID -> highest rank seen
+ QHash<int32_t, uint32_t> m_owned; // descID -> cumulative points
+ uint32_t m_spent; // profile aa_spent
+ uint32_t m_assigned; // profile aa_assigned
+ uint32_t m_unspent; // profile aa_unspent
+ bool m_changeScheduled;
+};
+
+#endif // _AAMGR_H_
Added: showeq/branches/xerxes-trunk-dev/src/dbstr.cpp
===================================================================
--- showeq/branches/xerxes-trunk-dev/src/dbstr.cpp (rev 0)
+++ showeq/branches/xerxes-trunk-dev/src/dbstr.cpp 2026-07-17 18:19:21 UTC (rev 1668)
@@ -0,0 +1,104 @@
+/*
+ * dbstr.cpp
+ * Copyright 2026 by the respective ShowEQ Developers
+ *
+ * This file is part of ShowEQ.
+ * http://www.sourceforge.net/projects/seq
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "dbstr.h"
+#include "diagnosticmessages.h"
+
+#include <QFile>
+#include <QByteArray>
+#include <QStringList>
+#if (QT_VERSION >= QT_VERSION_CHECK(5,5,0))
+#include <QRegularExpression>
+#else
+#include <QRegExp>
+#endif
+
+DBStr::DBStr()
+ : m_loaded(false)
+{
+}
+
+DBStr::~DBStr()
+{
+}
+
+bool DBStr::load(const QString& fileName, int keepType)
+{
+ m_strings.clear();
+ m_loaded = false;
+
+ QFile dbFile(fileName);
+ if (!dbFile.open(QIODevice::ReadOnly))
+ {
+ seqWarn("DBStr: Failed to open '%s'", fileName.toLatin1().data());
+ return false;
+ }
+
+ QByteArray textData(dbFile.size() + 1, '\0');
+ dbFile.read(textData.data(), textData.size());
+
+ // handle either line-termination style
+#if (QT_VERSION >= QT_VERSION_CHECK(5,5,0))
+ QRegularExpression lineTerm("[\r\n]{1,2}");
+#else
+ QRegExp lineTerm("[\r\n]{1,2}");
+#endif
+
+#if (QT_VERSION >= QT_VERSION_CHECK(5,14,0))
+ QStringList lines = QString::fromUtf8(textData).split(lineTerm, Qt::SkipEmptyParts);
+#else
+ QStringList lines = QString::fromUtf8(textData).split(lineTerm, QString::SkipEmptyParts);
+#endif
+
+ // lines are: id^type^text^flag^ . Keep only the requested type. The text
+ // itself never contains '^', so a plain split is safe (id/type/text are
+ // the first three fields).
+ for (QStringList::Iterator it = lines.begin(); it != lines.end(); ++it)
+ {
+ const QStringList f = (*it).split('^');
+ if (f.size() < 3)
+ continue;
+
+ bool okId = false, okType = false;
+ const int type = f[1].toInt(&okType);
+ if (!okType || type != keepType)
+ continue;
+
+ const uint32_t id = f[0].toUInt(&okId);
+ if (!okId)
+ continue;
+
+ m_strings.insert(id, f[2]);
+ }
+
+ m_loaded = true;
+
+ seqInfo("DBStr: loaded %d type-%d strings from '%s'",
+ m_strings.count(), keepType, fileName.toLatin1().data());
+
+ return true;
+}
+
+QString DBStr::find(uint32_t id) const
+{
+ return m_strings.value(id);
+}
Added: showeq/branches/xerxes-trunk-dev/src/dbstr.h
===================================================================
--- showeq/branches/xerxes-trunk-dev/src/dbstr.h (rev 0)
+++ showeq/branches/xerxes-trunk-dev/src/dbstr.h 2026-07-17 18:19:21 UTC (rev 1668)
@@ -0,0 +1,55 @@
+/*
+ * dbstr.h
+ * Copyright 2026 by the respective ShowEQ Developers
+ *
+ * This file is part of ShowEQ.
+ * http://www.sourceforge.net/projects/seq
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef _DBSTR_H_
+#define _DBSTR_H_
+
+#include <cstdint>
+#include <QHash>
+#include <QString>
+
+//----------------------------------------------------------------------
+// DBStr
+//
+// Loader for the client's dbstr_us.txt string database. Lines are
+// '^'-delimited: id^type^text^flag^ . The (id, type) pair is the key;
+// type 1 is the display name used by AA titles, spells, etc.
+class DBStr
+{
+ public:
+ DBStr();
+ ~DBStr();
+
+ // load dbstr_us.txt; retains only the requested type (default 1, names)
+ bool load(const QString& dbstrFile, int keepType = 1);
+
+ // look up a string by its dbstr id; empty QString if absent
+ QString find(uint32_t id) const;
+
+ bool loaded() const { return m_loaded; }
+
+ protected:
+ QHash<uint32_t, QString> m_strings;
+ bool m_loaded;
+};
+
+#endif // _DBSTR_H_
Modified: showeq/branches/xerxes-trunk-dev/src/everquest.h
===================================================================
--- showeq/branches/xerxes-trunk-dev/src/everquest.h 2026-07-17 17:48:07 UTC (rev 1667)
+++ showeq/branches/xerxes-trunk-dev/src/everquest.h 2026-07-17 18:19:21 UTC (rev 1668)
@@ -479,6 +479,28 @@
/*012*/
};
+/*
+ * OP_SendAATable. One record per AA rank, sent as a burst at zone-in. The
+ * record is variable length: a count-prefixed skills-requirement block and
+ * prev/next rank ids follow the fixed head below. Only the fixed head is
+ * needed to build the descID -> name/rank table, so the payload size check
+ * is SZC_None and the handler reads just this head.
+ */
+struct aaInfoStruct
+{
+/*000*/ int32_t descID; // per-rank id; matches charProfileStruct.aa_array[].AA
+/*004*/ uint8_t unknown004; // always 1
+/*005*/ int32_t hotKeySID;
+/*009*/ int32_t hotKeySID2;
+/*013*/ int32_t titleSID; // dbstr type-1 name id (shared by all ranks of an AA)
+/*017*/ int32_t descSID2; // dbstr description id (per-rank)
+/*021*/ uint32_t reqLevel;
+/*025*/ uint32_t cost; // points for THIS rank (aa_array.value is cumulative)
+/*029*/ uint32_t aaID; // activation ability id
+/*033*/ uint32_t rank; // current rank this record describes
+/*037*/
+};
+
/**
* Used in charProfileStruct. An item inline in the stream, used in Bandolier and Potion Belt.
* Size: 72 Octets
Modified: showeq/branches/xerxes-trunk-dev/src/interface.cpp
===================================================================
--- showeq/branches/xerxes-trunk-dev/src/interface.cpp 2026-07-17 17:48:07 UTC (rev 1667)
+++ showeq/branches/xerxes-trunk-dev/src/interface.cpp 2026-07-17 18:19:21 UTC (rev 1668)
@@ -37,6 +37,9 @@
#include "spelllist.h"
#include "player.h"
#include "skilllist.h"
+#include "aalist.h"
+#include "aamgr.h"
+#include "dbstr.h"
#include "statlist.h"
#include "group.h"
#include "netdiag.h"
@@ -125,6 +128,9 @@
m_spawnList2(0),
m_spellList(0),
m_skillList(0),
+ m_aaList(0),
+ m_aaMgr(0),
+ m_dbStr(0),
m_statList(0),
m_spawnPointList(0),
m_packet(0),
@@ -295,6 +301,16 @@
// Create our player object
m_player = new Player(this, m_zoneMgr, m_guildmgr);
+ // Create the AA string database (dbstr_us.txt) and the AA manager. The
+ // manager builds an in-memory AA name/rank table from OP_SendAATable and
+ // tracks owned AAs from the character profile.
+ QString dbstrFileName = pSEQPrefs->getPrefString("DBStrFile", "Interface",
+ "dbstr_us.txt");
+ QFileInfo dbstrInfo = m_dataLocationMgr->findExistingFile(".", dbstrFileName);
+ m_dbStr = new DBStr();
+ m_dbStr->load(dbstrInfo.absoluteFilePath());
+ m_aaMgr = new AAMgr(m_dbStr, this, "aamgr");
+
// Create the filter manager
m_filterMgr = new FilterMgr(m_dataLocationMgr,
pSEQPrefs->getPrefString("FilterFile",
@@ -476,6 +492,10 @@
if (pSEQPrefs->getPrefBool("ShowPlayerSkills", section, true))
showSkillList();
+ // Alternate Advancement window (created only when shown)
+ if (pSEQPrefs->getPrefBool("ShowPlayerAA", section, false))
+ showAAList();
+
//
// Create the Player Status listview (ONLY CREATED WHEN NEEDED FLOYD!!!!)
//
@@ -571,6 +591,14 @@
m_packet->connect2("OP_PlayerProfile", SP_Zone, DIR_Server,
"uint8_t", SZC_None,
m_zoneMgr, SLOT(zonePlayer(const uint8_t*, size_t)));
+
+ // AA definition table (one record per AA rank, burst at zone-in) and
+ // the owned-AA set from the character profile feed the AA manager.
+ m_packet->connect2("OP_SendAATable", SP_Zone, DIR_Server,
+ "uint8_t", SZC_None,
+ m_aaMgr, SLOT(aaInfo(const uint8_t*, size_t)));
+ connect(m_zoneMgr, SIGNAL(playerProfile(const charProfileStruct*)),
+ m_aaMgr, SLOT(player(const charProfileStruct*)));
m_packet->connect2("OP_ZoneChange", SP_Zone, DIR_Client|DIR_Server,
"zoneChangeStruct", SZC_Match,
m_zoneMgr, SLOT(zoneChange(const uint8_t*, size_t, uint8_t)));
@@ -1216,6 +1244,10 @@
if (m_eqStrings != 0)
delete m_eqStrings;
+ // m_aaMgr is parented to this and freed by Qt; m_dbStr is not a QObject
+ if (m_dbStr != 0)
+ delete m_dbStr;
+
if (m_player != 0)
delete m_player;
@@ -1311,6 +1343,10 @@
SLOT(toggle_view_PlayerSkills()));
m_action_view_PlayerSkills->setCheckable(true);
+ m_action_view_PlayerAA = pViewMenu->addAction("Alternate Advancement", this,
+ SLOT(toggle_view_PlayerAA()));
+ m_action_view_PlayerAA->setCheckable(true);
+
m_action_view_Compass = pViewMenu->addAction("Compass", this,
SLOT(toggle_view_Compass()));
m_action_view_Compass->setCheckable(true);
@@ -4388,6 +4424,24 @@
pSEQPrefs->setPrefBool("ShowPlayerSkills", "Interface", !wasVisible);
}
+void EQInterface::toggle_view_PlayerAA(void)
+{
+ bool wasVisible = ((m_aaList != 0) && m_aaList->isVisible());
+
+ if (!wasVisible)
+ showAAList();
+ else
+ {
+ m_aaList->savePrefs();
+ m_aaList->hide();
+ removeWindowMenu(m_aaList);
+ delete m_aaList;
+ m_aaList = 0;
+ }
+
+ pSEQPrefs->setPrefBool("ShowPlayerAA", "Interface", !wasVisible);
+}
+
void
EQInterface::toggle_view_Compass(void)
{
@@ -5532,6 +5586,8 @@
m_skillList->isVisible());
m_action_view_PlayerSkills_Options->setEnabled((m_skillList !=0) &&
m_skillList->isVisible());
+ m_action_view_PlayerAA->setChecked((m_aaList != 0) &&
+ m_aaList->isVisible());
m_action_view_PlayerStats->setChecked((m_statList != 0) &&
m_statList->isVisible());
@@ -6238,6 +6294,32 @@
m_skillList->activateWindow();
}
+void EQInterface::showAAList(void)
+{
+ // if it doesn't exist, create it
+ if (m_aaList == 0)
+ {
+ m_aaList = new AAListWindow(m_aaMgr, 0, "aalist");
+ setDockEnabled(m_aaList,
+ pSEQPrefs->getPrefBool("DockablePlayerAA", "Interface", true));
+ Qt::DockWidgetArea edge = (Qt::DockWidgetArea)pSEQPrefs->getPrefInt("Dock",
+ m_aaList->preferenceName(), Qt::LeftDockWidgetArea);
+ addDockWidget(edge, m_aaList);
+ m_aaList->setFloating(!pSEQPrefs->getPrefBool("DockedPlayerAA", "Interface", true));
+
+ // connect aa list window slots to interface signals
+ connect(this, SIGNAL(saveAllPrefs(void)), m_aaList, SLOT(savePrefs(void)));
+ connect(this, SIGNAL(restoreFonts(void)), m_aaList, SLOT(restoreFont(void)));
+
+ // insert its menu into the window menu
+ insertWindowMenu(m_aaList);
+ }
+
+ // make sure it's visible
+ m_aaList->show();
+ m_aaList->activateWindow();
+}
+
void EQInterface::showSpellList(void)
{
// if it doesn't exist, create it
Modified: showeq/branches/xerxes-trunk-dev/src/interface.h
===================================================================
--- showeq/branches/xerxes-trunk-dev/src/interface.h 2026-07-17 17:48:07 UTC (rev 1667)
+++ showeq/branches/xerxes-trunk-dev/src/interface.h 2026-07-17 18:19:21 UTC (rev 1668)
@@ -54,6 +54,9 @@
class SpellListWindow;
class SkillListWindow;
class StatListWindow;
+class AAListWindow;
+class AAMgr;
+class DBStr;
class SpawnPointWindow;
class EQPacket;
class ZoneMgr;
@@ -277,6 +280,7 @@
void toggle_view_PlayerStats();
void toggle_view_Compass();
void toggle_view_PlayerSkills();
+ void toggle_view_PlayerAA();
void toggle_view_Map(QAction* map);
void toggle_view_NetDiag();
void toggle_view_GuildList();
@@ -336,6 +340,7 @@
void showSpawnPointList(void);
void showStatList(void);
void showSkillList(void);
+ void showAAList(void);
void showSpellList(void);
void showCompass(void);
void showNetDiag(void);
@@ -378,6 +383,9 @@
SpawnListWindow2* m_spawnList2;
SpellListWindow* m_spellList;
SkillListWindow* m_skillList;
+ AAListWindow* m_aaList;
+ AAMgr* m_aaMgr;
+ DBStr* m_dbStr;
StatListWindow* m_statList;
SpawnPointWindow* m_spawnPointList;
EQPacket* m_packet;
@@ -481,6 +489,7 @@
QAction* m_action_view_SpawnPointList;
QAction* m_action_view_PlayerStats;
QAction* m_action_view_PlayerSkills;
+ QAction* m_action_view_PlayerAA;
QAction* m_action_view_Compass;
QAction* m_action_view_Map[maxNumMaps];
QAction* m_action_view_MessageWindow[maxNumMessageWindows];
Modified: showeq/branches/xerxes-trunk-dev/src/s_everquest.h
===================================================================
--- showeq/branches/xerxes-trunk-dev/src/s_everquest.h 2026-07-17 17:48:07 UTC (rev 1667)
+++ showeq/branches/xerxes-trunk-dev/src/s_everquest.h 2026-07-17 18:19:21 UTC (rev 1668)
@@ -19,6 +19,7 @@
AddStruct(Color_Struct);
AddStruct(spellBuff);
AddStruct(AA_Array);
+AddStruct(aaInfoStruct);
AddStruct(InlineItem);
AddStruct(BandolierStruct);
AddStruct(TributeStruct);
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|