Compare commits

..

9 Commits

Author SHA1 Message Date
7fcac021a5 feat(webui): EvolutionProgress support
All checks were successful
Build / build (pull_request) Successful in 1m33s
Closes #1815
2025-04-25 10:41:01 +02:00
100aefcee4 fix: give corresponding weapon when crafting Hound (#1816)
All checks were successful
Build Docker image / docker (push) Successful in 34s
Build / build (push) Successful in 1m43s
Reviewed-on: #1816
Reviewed-by: Sainan <sainan@calamity.inc>
Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
2025-04-24 11:24:53 -07:00
409c089d11 feat: handle account already owning a nightwave skin item (#1814)
Some checks failed
Build Docker image / docker (push) Has been cancelled
Build / build (push) Has been cancelled
Closes #1811

Reviewed-on: #1814
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-24 11:24:38 -07:00
8c32dc2670 fix: add MoaPets into sellController (#1813)
Some checks failed
Build / build (push) Has been cancelled
Build Docker image / docker (push) Has been cancelled
Reviewed-on: #1813
Reviewed-by: Sainan <sainan@calamity.inc>
Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
2025-04-24 11:24:25 -07:00
a67f99b665 chore: don't use sequential values as RNG seeds directly (#1812)
Some checks failed
Build / build (push) Has been cancelled
Build Docker image / docker (push) Has been cancelled
This should help get a slightly better distribution

Reviewed-on: #1812
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-24 11:24:11 -07:00
756a01d270 fix: pass Emblem field on in getGuildClient
All checks were successful
Build Docker image / docker (push) Successful in 1m3s
Build / build (push) Successful in 1m26s
2025-04-24 05:36:16 +02:00
efc7467a99 chore: remove unused MoaPets array from getItemLists
All checks were successful
Build Docker image / docker (push) Successful in 33s
Build / build (push) Successful in 1m31s
2025-04-24 02:00:27 +02:00
99e1a66da8 chore: improve typings in getItemLists
All checks were successful
Build / build (push) Successful in 44s
Build Docker image / docker (push) Successful in 51s
2025-04-24 00:46:33 +02:00
370f8c1008 fix: getItemLists fixup
All checks were successful
Build Docker image / docker (push) Successful in 53s
Build / build (push) Successful in 1m32s
2025-04-24 00:42:33 +02:00
8 changed files with 110 additions and 52 deletions

View File

@ -17,7 +17,7 @@ import { getDefaultUpgrades } from "@/src/services/itemDataService";
import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper"; import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper";
import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes"; import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { getRandomInt } from "@/src/services/rngService"; import { getRandomInt } from "@/src/services/rngService";
import { ExportSentinels, IDefaultUpgrade } from "warframe-public-export-plus"; import { ExportSentinels, ExportWeapons, IDefaultUpgrade } from "warframe-public-export-plus";
import { Status } from "@/src/types/inventoryTypes/inventoryTypes"; import { Status } from "@/src/types/inventoryTypes/inventoryTypes";
interface IModularCraftRequest { interface IModularCraftRequest {
@ -138,6 +138,18 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
} else { } else {
defaultUpgrades = getDefaultUpgrades(data.Parts); defaultUpgrades = getDefaultUpgrades(data.Parts);
} }
if (category == "MoaPets") {
const weapon = ExportSentinels[data.WeaponType].defaultWeapon;
if (weapon) {
const category = ExportWeapons[weapon].productCategory;
addEquipment(inventory, category, weapon, undefined, inventoryChanges);
combineInventoryChanges(
inventoryChanges,
occupySlot(inventory, productCategoryToInventoryBin(category)!, !!data.isWebUi)
);
}
}
defaultOverwrites.Configs = applyDefaultUpgrades(inventory, defaultUpgrades); defaultOverwrites.Configs = applyDefaultUpgrades(inventory, defaultUpgrades);
addEquipment(inventory, category, data.WeaponType, data.Parts, inventoryChanges, defaultOverwrites); addEquipment(inventory, category, data.WeaponType, data.Parts, inventoryChanges, defaultOverwrites);
combineInventoryChanges( combineInventoryChanges(

View File

@ -45,7 +45,7 @@ export const sellController: RequestHandler = async (req, res) => {
if (payload.Items.SpaceGuns || payload.Items.SpaceMelee) { if (payload.Items.SpaceGuns || payload.Items.SpaceMelee) {
requiredFields.add(InventorySlot.SPACEWEAPONS); requiredFields.add(InventorySlot.SPACEWEAPONS);
} }
if (payload.Items.Sentinels || payload.Items.SentinelWeapons) { if (payload.Items.Sentinels || payload.Items.SentinelWeapons || payload.Items.MoaPets) {
requiredFields.add(InventorySlot.SENTINELS); requiredFields.add(InventorySlot.SENTINELS);
} }
if (payload.Items.OperatorAmps) { if (payload.Items.OperatorAmps) {
@ -148,6 +148,12 @@ export const sellController: RequestHandler = async (req, res) => {
freeUpSlot(inventory, InventorySlot.SENTINELS); freeUpSlot(inventory, InventorySlot.SENTINELS);
}); });
} }
if (payload.Items.MoaPets) {
payload.Items.MoaPets.forEach(sellItem => {
inventory.MoaPets.pull({ _id: sellItem.String });
freeUpSlot(inventory, InventorySlot.SENTINELS);
});
}
if (payload.Items.OperatorAmps) { if (payload.Items.OperatorAmps) {
payload.Items.OperatorAmps.forEach(sellItem => { payload.Items.OperatorAmps.forEach(sellItem => {
inventory.OperatorAmps.pull({ _id: sellItem.String }); inventory.OperatorAmps.pull({ _id: sellItem.String });
@ -281,6 +287,7 @@ interface ISellRequest {
SpaceMelee?: ISellItem[]; SpaceMelee?: ISellItem[];
Sentinels?: ISellItem[]; Sentinels?: ISellItem[];
SentinelWeapons?: ISellItem[]; SentinelWeapons?: ISellItem[];
MoaPets?: ISellItem[];
OperatorAmps?: ISellItem[]; OperatorAmps?: ISellItem[];
Hoverboards?: ISellItem[]; Hoverboards?: ISellItem[];
Drones?: ISellItem[]; Drones?: ISellItem[];

View File

@ -6,6 +6,9 @@ import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
import { addMiscItems, combineInventoryChanges, getInventory, updateCurrency } from "@/src/services/inventoryService"; import { addMiscItems, combineInventoryChanges, getInventory, updateCurrency } from "@/src/services/inventoryService";
import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { isStoreItem, toStoreItem } from "@/src/services/itemDataService"; import { isStoreItem, toStoreItem } from "@/src/services/itemDataService";
import { logger } from "@/src/utils/logger";
const nightwaveCredsItemType = ExportNightwave.rewards[ExportNightwave.rewards.length - 1].uniqueName;
export const syndicateSacrificeController: RequestHandler = async (request, response) => { export const syndicateSacrificeController: RequestHandler = async (request, response) => {
const accountId = await getAccountIdForRequest(request); const accountId = await getAccountIdForRequest(request);
@ -74,10 +77,14 @@ export const syndicateSacrificeController: RequestHandler = async (request, resp
if (!isStoreItem(rewardType)) { if (!isStoreItem(rewardType)) {
rewardType = toStoreItem(rewardType); rewardType = toStoreItem(rewardType);
} }
combineInventoryChanges( const rewardInventoryChanges = (await handleStoreItemAcquisition(rewardType, inventory, reward.itemCount))
res.InventoryChanges, .InventoryChanges;
(await handleStoreItemAcquisition(rewardType, inventory, reward.itemCount)).InventoryChanges if (Object.keys(rewardInventoryChanges).length == 0) {
); logger.debug(`nightwave rank up reward did not seem to get added, giving 50 creds instead`);
rewardInventoryChanges.MiscItems = [{ ItemType: nightwaveCredsItemType, ItemCount: 50 }];
addMiscItems(inventory, rewardInventoryChanges.MiscItems);
}
combineInventoryChanges(res.InventoryChanges, rewardInventoryChanges);
} }
} }

View File

@ -33,6 +33,29 @@ interface ListedItem {
parazon?: boolean; parazon?: boolean;
} }
interface ItemLists {
archonCrystalUpgrades: Record<string, string>;
uniqueLevelCaps: Record<string, number>;
Suits: ListedItem[];
LongGuns: ListedItem[];
Melee: ListedItem[];
ModularParts: ListedItem[];
Pistols: ListedItem[];
Sentinels: ListedItem[];
SentinelWeapons: ListedItem[];
SpaceGuns: ListedItem[];
SpaceMelee: ListedItem[];
SpaceSuits: ListedItem[];
MechSuits: ListedItem[];
miscitems: ListedItem[];
Syndicates: ListedItem[];
OperatorAmps: ListedItem[];
QuestKeys: ListedItem[];
KubrowPets: ListedItem[];
EvolutionProgress: ListedItem[];
mods: ListedItem[];
}
const relicQualitySuffixes: Record<TRelicQuality, string> = { const relicQualitySuffixes: Record<TRelicQuality, string> = {
VPQ_BRONZE: "", VPQ_BRONZE: "",
VPQ_SILVER: " [Flawless]", VPQ_SILVER: " [Flawless]",
@ -42,24 +65,28 @@ const relicQualitySuffixes: Record<TRelicQuality, string> = {
const getItemListsController: RequestHandler = (req, response) => { const getItemListsController: RequestHandler = (req, response) => {
const lang = getDict(typeof req.query.lang == "string" ? req.query.lang : "en"); const lang = getDict(typeof req.query.lang == "string" ? req.query.lang : "en");
const res: Record<string, ListedItem[]> = {}; const res: ItemLists = {
res.Suits = []; archonCrystalUpgrades,
res.LongGuns = []; uniqueLevelCaps: ExportMisc.uniqueLevelCaps,
res.Melee = []; Suits: [],
res.ModularParts = []; LongGuns: [],
res.Pistols = []; Melee: [],
res.Sentinels = []; ModularParts: [],
res.SentinelWeapons = []; Pistols: [],
res.SpaceGuns = []; Sentinels: [],
res.SpaceMelee = []; SentinelWeapons: [],
res.SpaceSuits = []; SpaceGuns: [],
res.MechSuits = []; SpaceMelee: [],
res.miscitems = []; SpaceSuits: [],
res.Syndicates = []; MechSuits: [],
res.OperatorAmps = []; miscitems: [],
res.QuestKeys = []; Syndicates: [],
res.KubrowPets = []; OperatorAmps: [],
res.EvolutionProgress = []; QuestKeys: [],
KubrowPets: [],
EvolutionProgress: [],
mods: []
};
for (const [uniqueName, item] of Object.entries(ExportWarframes)) { for (const [uniqueName, item] of Object.entries(ExportWarframes)) {
res[item.productCategory].push({ res[item.productCategory].push({
uniqueName, uniqueName,
@ -68,7 +95,7 @@ const getItemListsController: RequestHandler = (req, response) => {
}); });
} }
for (const [uniqueName, item] of Object.entries(ExportSentinels)) { for (const [uniqueName, item] of Object.entries(ExportSentinels)) {
if (item.productCategory != "SpecialItems") { if (item.productCategory == "Sentinels" || item.productCategory == "KubrowPets") {
res[item.productCategory].push({ res[item.productCategory].push({
uniqueName, uniqueName,
name: getString(item.name, lang), name: getString(item.name, lang),
@ -202,7 +229,6 @@ const getItemListsController: RequestHandler = (req, response) => {
}); });
} }
res.mods = [];
for (const [uniqueName, upgrade] of Object.entries(ExportUpgrades)) { for (const [uniqueName, upgrade] of Object.entries(ExportUpgrades)) {
const mod: ListedItem = { const mod: ListedItem = {
uniqueName, uniqueName,
@ -267,11 +293,7 @@ const getItemListsController: RequestHandler = (req, response) => {
}); });
} }
response.json({ response.json(res);
archonCrystalUpgrades,
uniqueLevelCaps: ExportMisc.uniqueLevelCaps,
...res
});
}; };
export { getItemListsController }; export { getItemListsController };

View File

@ -105,6 +105,7 @@ export const getGuildClient = async (guild: TGuildDatabaseDocument, accountId: s
Members: members, Members: members,
Ranks: guild.Ranks, Ranks: guild.Ranks,
Tier: guild.Tier, Tier: guild.Tier,
Emblem: guild.Emblem,
Vault: getGuildVault(guild), Vault: getGuildVault(guild),
ActiveDojoColorResearch: guild.ActiveDojoColorResearch, ActiveDojoColorResearch: guild.ActiveDojoColorResearch,
Class: guild.Class, Class: guild.Class,

View File

@ -1236,12 +1236,16 @@ export const addSkin = (
typeName: string, typeName: string,
inventoryChanges: IInventoryChanges = {} inventoryChanges: IInventoryChanges = {}
): IInventoryChanges => { ): IInventoryChanges => {
const index = inventory.WeaponSkins.push({ ItemType: typeName, IsNew: true }) - 1; if (inventory.WeaponSkins.find(x => x.ItemType == typeName)) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition logger.debug(`refusing to add WeaponSkin ${typeName} because account already owns it`);
inventoryChanges.WeaponSkins ??= []; } else {
(inventoryChanges.WeaponSkins as IWeaponSkinClient[]).push( const index = inventory.WeaponSkins.push({ ItemType: typeName, IsNew: true }) - 1;
inventory.WeaponSkins[index].toJSON<IWeaponSkinClient>() // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
); inventoryChanges.WeaponSkins ??= [];
(inventoryChanges.WeaponSkins as IWeaponSkinClient[]).push(
inventory.WeaponSkins[index].toJSON<IWeaponSkinClient>()
);
}
return inventoryChanges; return inventoryChanges;
}; };

View File

@ -225,7 +225,8 @@ const pushSortieIfRelevant = (worldState: IWorldState, day: number): void => {
return; return;
} }
const rng = new CRng(day); const seed = new CRng(day).randomInt(0, 0xffff);
const rng = new CRng(seed);
const boss = rng.randomElement(sortieBosses); const boss = rng.randomElement(sortieBosses);
@ -354,7 +355,7 @@ const pushSortieIfRelevant = (worldState: IWorldState, day: number): void => {
Activation: { $date: { $numberLong: dayStart.toString() } }, Activation: { $date: { $numberLong: dayStart.toString() } },
Expiry: { $date: { $numberLong: dayEnd.toString() } }, Expiry: { $date: { $numberLong: dayEnd.toString() } },
Reward: "/Lotus/Types/Game/MissionDecks/SortieRewards", Reward: "/Lotus/Types/Game/MissionDecks/SortieRewards",
Seed: day, Seed: seed,
Boss: boss, Boss: boss,
Variants: selectedNodes Variants: selectedNodes
}); });
@ -374,7 +375,7 @@ const dailyChallenges = Object.keys(ExportNightwave.challenges).filter(x =>
const getSeasonDailyChallenge = (day: number): ISeasonChallenge => { const getSeasonDailyChallenge = (day: number): ISeasonChallenge => {
const dayStart = EPOCH + day * 86400000; const dayStart = EPOCH + day * 86400000;
const dayEnd = EPOCH + (day + 3) * 86400000; const dayEnd = EPOCH + (day + 3) * 86400000;
const rng = new CRng(day); const rng = new CRng(new CRng(day).randomInt(0, 0xffff));
return { return {
_id: { $oid: "67e1b5ca9d00cb47" + day.toString().padStart(8, "0") }, _id: { $oid: "67e1b5ca9d00cb47" + day.toString().padStart(8, "0") },
Daily: true, Daily: true,
@ -394,7 +395,7 @@ const getSeasonWeeklyChallenge = (week: number, id: number): ISeasonChallenge =>
const weekStart = EPOCH + week * 604800000; const weekStart = EPOCH + week * 604800000;
const weekEnd = weekStart + 604800000; const weekEnd = weekStart + 604800000;
const challengeId = week * 7 + id; const challengeId = week * 7 + id;
const rng = new CRng(challengeId); const rng = new CRng(new CRng(challengeId).randomInt(0, 0xffff));
return { return {
_id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") }, _id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") },
Activation: { $date: { $numberLong: weekStart.toString() } }, Activation: { $date: { $numberLong: weekStart.toString() } },
@ -411,7 +412,7 @@ const getSeasonWeeklyHardChallenge = (week: number, id: number): ISeasonChalleng
const weekStart = EPOCH + week * 604800000; const weekStart = EPOCH + week * 604800000;
const weekEnd = weekStart + 604800000; const weekEnd = weekStart + 604800000;
const challengeId = week * 7 + id; const challengeId = week * 7 + id;
const rng = new CRng(challengeId); const rng = new CRng(new CRng(challengeId).randomInt(0, 0xffff));
return { return {
_id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") }, _id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") },
Activation: { $date: { $numberLong: weekStart.toString() } }, Activation: { $date: { $numberLong: weekStart.toString() } },
@ -476,7 +477,7 @@ const getCalendarSeason = (week: number): ICalendarSeason => {
//logger.debug(`birthday on day ${day}`); //logger.debug(`birthday on day ${day}`);
eventDays.push({ day, events: [] }); // This is how CET_PLOT looks in worldState as of around 38.5.0 eventDays.push({ day, events: [] }); // This is how CET_PLOT looks in worldState as of around 38.5.0
} }
const rng = new CRng(week); const rng = new CRng(new CRng(week).randomInt(0, 0xffff));
const challenges = [ const challenges = [
"/Lotus/Types/Challenges/Calendar1999/CalendarKillEnemiesEasy", "/Lotus/Types/Challenges/Calendar1999/CalendarKillEnemiesEasy",
"/Lotus/Types/Challenges/Calendar1999/CalendarKillEnemiesMedium", "/Lotus/Types/Challenges/Calendar1999/CalendarKillEnemiesMedium",
@ -710,7 +711,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
} }
// Elite Sanctuary Onslaught cycling every week // Elite Sanctuary Onslaught cycling every week
worldState.NodeOverrides.find(x => x.Node == "SolNode802")!.Seed = week; // unfaithful worldState.NodeOverrides.find(x => x.Node == "SolNode802")!.Seed = new CRng(week).randomInt(0, 0xffff);
// Holdfast, Cavia, & Hex bounties cycling every 2.5 hours; unfaithful implementation // Holdfast, Cavia, & Hex bounties cycling every 2.5 hours; unfaithful implementation
let bountyCycle = Math.trunc(Date.now() / 9000000); let bountyCycle = Math.trunc(Date.now() / 9000000);
@ -749,14 +750,16 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
// TODO: xpAmounts need to be calculated based on the jobType somehow? // TODO: xpAmounts need to be calculated based on the jobType somehow?
const seed = new CRng(bountyCycle).randomInt(0, 0xffff);
{ {
const rng = new CRng(bountyCycle); const rng = new CRng(seed);
worldState.SyndicateMissions.push({ worldState.SyndicateMissions.push({
_id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000008" }, _id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000008" },
Activation: { $date: { $numberLong: bountyCycleStart.toString(10) } }, Activation: { $date: { $numberLong: bountyCycleStart.toString(10) } },
Expiry: { $date: { $numberLong: bountyCycleEnd.toString(10) } }, Expiry: { $date: { $numberLong: bountyCycleEnd.toString(10) } },
Tag: "CetusSyndicate", Tag: "CetusSyndicate",
Seed: bountyCycle, Seed: seed,
Nodes: [], Nodes: [],
Jobs: [ Jobs: [
{ {
@ -820,13 +823,13 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
} }
{ {
const rng = new CRng(bountyCycle); const rng = new CRng(seed);
worldState.SyndicateMissions.push({ worldState.SyndicateMissions.push({
_id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000025" }, _id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000025" },
Activation: { $date: { $numberLong: bountyCycleStart.toString(10) } }, Activation: { $date: { $numberLong: bountyCycleStart.toString(10) } },
Expiry: { $date: { $numberLong: bountyCycleEnd.toString(10) } }, Expiry: { $date: { $numberLong: bountyCycleEnd.toString(10) } },
Tag: "SolarisSyndicate", Tag: "SolarisSyndicate",
Seed: bountyCycle, Seed: seed,
Nodes: [], Nodes: [],
Jobs: [ Jobs: [
{ {
@ -890,13 +893,13 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
} }
{ {
const rng = new CRng(bountyCycle); const rng = new CRng(seed);
worldState.SyndicateMissions.push({ worldState.SyndicateMissions.push({
_id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000002" }, _id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000002" },
Activation: { $date: { $numberLong: bountyCycleStart.toString(10) } }, Activation: { $date: { $numberLong: bountyCycleStart.toString(10) } },
Expiry: { $date: { $numberLong: bountyCycleEnd.toString(10) } }, Expiry: { $date: { $numberLong: bountyCycleEnd.toString(10) } },
Tag: "EntratiSyndicate", Tag: "EntratiSyndicate",
Seed: bountyCycle, Seed: seed,
Nodes: [], Nodes: [],
Jobs: [ Jobs: [
{ {
@ -1119,7 +1122,8 @@ export const getLiteSortie = (week: number): ILiteSortie => {
} }
} }
const rng = new CRng(week); const seed = new CRng(week).randomInt(0, 0xffff);
const rng = new CRng(seed);
const firstNodeIndex = rng.randomInt(0, nodes.length - 1); const firstNodeIndex = rng.randomInt(0, nodes.length - 1);
const firstNode = nodes[firstNodeIndex]; const firstNode = nodes[firstNodeIndex];
nodes.splice(firstNodeIndex, 1); nodes.splice(firstNodeIndex, 1);
@ -1133,7 +1137,7 @@ export const getLiteSortie = (week: number): ILiteSortie => {
Activation: { $date: { $numberLong: weekStart.toString() } }, Activation: { $date: { $numberLong: weekStart.toString() } },
Expiry: { $date: { $numberLong: weekEnd.toString() } }, Expiry: { $date: { $numberLong: weekEnd.toString() } },
Reward: "/Lotus/Types/Game/MissionDecks/ArchonSortieRewards", Reward: "/Lotus/Types/Game/MissionDecks/ArchonSortieRewards",
Seed: week, Seed: seed,
Boss: boss, Boss: boss,
Missions: [ Missions: [
{ {

View File

@ -11,6 +11,7 @@ export interface IGuildClient {
Members: IGuildMemberClient[]; Members: IGuildMemberClient[];
Ranks: IGuildRank[]; Ranks: IGuildRank[];
Tier: number; Tier: number;
Emblem?: boolean;
Vault: IGuildVault; Vault: IGuildVault;
ActiveDojoColorResearch: string; ActiveDojoColorResearch: string;
Class: number; Class: number;