Compare commits

..

12 Commits

Author SHA1 Message Date
6a1530f746 feat: give kahl standing when completing the new war
All checks were successful
Build / build (18) (push) Successful in 1m15s
Build / build (22) (push) Successful in 1m10s
Build / build (18) (pull_request) Successful in 51s
Build / build (20) (push) Successful in 1m25s
Build / build (22) (pull_request) Successful in 50s
Build / build (20) (pull_request) Successful in 1m11s
2025-03-27 11:34:42 +01:00
fd93f34538 chore: simplify logoutController (#1342)
All checks were successful
Build / build (20) (push) Successful in 45s
Build / build (18) (push) Successful in 1m14s
Build / build (22) (push) Successful in 1m23s
Build Docker image / docker (push) Successful in 1m9s
Reducing 3-4 MongoDB operations to only 1.

Reviewed-on: #1342
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-03-27 03:33:39 -07:00
a622393933 chore: don't validate Nonce in query (#1341)
Some checks failed
Build Docker image / docker (push) Waiting to run
Build / build (22) (push) Has been cancelled
Build / build (20) (push) Has been cancelled
Build / build (18) (push) Has been cancelled
By asking MongoDB to simply find the account by the ID and then validating the nonce ourselves, we save roughly 1ms.

Reviewed-on: #1341
2025-03-27 03:33:27 -07:00
d9b944175a feat: view clan contributions (#1340)
Some checks failed
Build Docker image / docker (push) Waiting to run
Build / build (20) (push) Has been cancelled
Build / build (22) (push) Has been cancelled
Build / build (18) (push) Has been cancelled
Reviewed-on: #1340
2025-03-27 03:33:08 -07:00
36c7b6f8f8 feat: handle DiscoveredMarkers in missionInventoryUpdate (#1339)
Some checks failed
Build Docker image / docker (push) Waiting to run
Build / build (22) (push) Has been cancelled
Build / build (20) (push) Has been cancelled
Build / build (18) (push) Has been cancelled
Closes #679

Reviewed-on: #1339
2025-03-27 03:32:50 -07:00
5f9475f750 fix: only give normal variant blueprints from daily tribute (#1332)
All checks were successful
Build / build (20) (push) Successful in 49s
Build / build (18) (push) Successful in 1m17s
Build / build (22) (push) Successful in 1m11s
Build Docker image / docker (push) Successful in 50s
you definitely shouldn't get prime or kuva variants

Reviewed-on: #1332
2025-03-26 16:09:05 -07:00
7492ddaad7 feat: handle CapturedAnimals in missionInventoryUpdate (#1337)
Some checks failed
Build / build (22) (push) Waiting to run
Build Docker image / docker (push) Waiting to run
Build / build (18) (push) Has been cancelled
Build / build (20) (push) Has been cancelled
Reviewed-on: #1337
2025-03-26 16:08:33 -07:00
926b87dda0 chore: cleanup leaderboards stuff
All checks were successful
Build / build (22) (push) Successful in 37s
Build / build (18) (push) Successful in 1m13s
Build Docker image / docker (push) Successful in 36s
Build / build (20) (push) Successful in 1m19s
2025-03-26 22:46:30 +01:00
83b267bcf5 fix: restrict transmutation polarity when a transmute core is being used (#1336)
All checks were successful
Build / build (18) (push) Successful in 43s
Build / build (22) (push) Successful in 37s
Build / build (20) (push) Successful in 1m18s
Build Docker image / docker (push) Successful in 32s
Reviewed-on: #1336
2025-03-26 14:22:09 -07:00
401f1ed229 feat: hubBlessing.php (#1335)
Some checks failed
Build / build (22) (push) Waiting to run
Build Docker image / docker (push) Waiting to run
Build / build (20) (push) Has been cancelled
Build / build (18) (push) Has been cancelled
Reviewed-on: #1335
2025-03-26 14:21:58 -07:00
049f709713 feat(leaderboard): missions & guilds leaderboard (#1338)
Some checks failed
Build / build (22) (push) Waiting to run
Build Docker image / docker (push) Waiting to run
Build / build (20) (push) Has been cancelled
Build / build (18) (push) Has been cancelled
Reviewed-on: #1338
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-03-26 14:21:22 -07:00
0f7866a575 fix: handle weapon meta having an empty defaultUpgrades array (#1333)
All checks were successful
Build / build (18) (push) Successful in 50s
Build / build (22) (push) Successful in 1m11s
Build / build (20) (push) Successful in 1m13s
Build Docker image / docker (push) Successful in 31s
Reviewed-on: #1333
2025-03-26 05:09:48 -07:00
24 changed files with 778 additions and 49 deletions

View File

@ -53,6 +53,7 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
RARE: 0,
LEGENDARY: 0
};
let forcedPolarity: string | undefined;
payload.Consumed.forEach(upgrade => {
const meta = ExportUpgrades[upgrade.ItemType];
counts[meta.rarity] += upgrade.ItemCount;
@ -62,6 +63,13 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
ItemCount: upgrade.ItemCount * -1
}
]);
if (upgrade.ItemType == "/Lotus/Upgrades/Mods/TransmuteCores/AttackTransmuteCore") {
forcedPolarity = "AP_ATTACK";
} else if (upgrade.ItemType == "/Lotus/Upgrades/Mods/TransmuteCores/DefenseTransmuteCore") {
forcedPolarity = "AP_DEFENSE";
} else if (upgrade.ItemType == "/Lotus/Upgrades/Mods/TransmuteCores/TacticTransmuteCore") {
forcedPolarity = "AP_TACTIC";
}
});
// Based on the table on https://wiki.warframe.com/w/Transmutation
@ -74,7 +82,7 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
const options: { uniqueName: string; rarity: TRarity }[] = [];
Object.entries(ExportUpgrades).forEach(([uniqueName, upgrade]) => {
if (upgrade.canBeTransmutation) {
if (upgrade.canBeTransmutation && (!forcedPolarity || upgrade.polarity == forcedPolarity)) {
options.push({ uniqueName, rarity: upgrade.rarity });
}
});

View File

@ -1,4 +1,4 @@
import { TGuildDatabaseDocument } from "@/src/models/guildModel";
import { GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import {
getDojoClient,
@ -10,7 +10,7 @@ import {
} from "@/src/services/guildService";
import { addMiscItems, getInventory, updateCurrency } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { IDojoContributable } from "@/src/types/guildTypes";
import { IDojoContributable, IGuildMemberDatabase } from "@/src/types/guildTypes";
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { RequestHandler } from "express";
@ -35,6 +35,10 @@ export const contributeToDojoComponentController: RequestHandler = async (req, r
return;
}
const guild = await getGuildForRequestEx(req, inventory);
const guildMember = (await GuildMember.findOne(
{ accountId, guildId: guild._id },
"RegularCreditsContributed MiscItemsContributed"
))!;
const request = JSON.parse(String(req.body)) as IContributeToDojoComponentRequest;
const component = guild.DojoComponents.id(request.ComponentId)!;
@ -45,7 +49,7 @@ export const contributeToDojoComponentController: RequestHandler = async (req, r
throw new Error("attempt to contribute to a deco in an unfinished room?!");
}
const meta = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == component.pf)!;
processContribution(guild, request, inventory, inventoryChanges, meta, component);
processContribution(guild, guildMember, request, inventory, inventoryChanges, meta, component);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (component.CompletionTime) {
setDojoRoomLogFunded(guild, component);
@ -55,12 +59,13 @@ export const contributeToDojoComponentController: RequestHandler = async (req, r
if (request.DecoId) {
const deco = component.Decos!.find(x => x._id.equals(request.DecoId))!;
const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == deco.Type)!;
processContribution(guild, request, inventory, inventoryChanges, meta, deco);
processContribution(guild, guildMember, request, inventory, inventoryChanges, meta, deco);
}
}
await guild.save();
await inventory.save();
await guildMember.save();
res.json({
...(await getDojoClient(guild, 0, component._id)),
InventoryChanges: inventoryChanges
@ -69,6 +74,7 @@ export const contributeToDojoComponentController: RequestHandler = async (req, r
const processContribution = (
guild: TGuildDatabaseDocument,
guildMember: IGuildMemberDatabase,
request: IContributeToDojoComponentRequest,
inventory: TInventoryDatabaseDocument,
inventoryChanges: IInventoryChanges,
@ -80,6 +86,9 @@ const processContribution = (
component.RegularCredits += request.RegularCredits;
inventoryChanges.RegularCredits = -request.RegularCredits;
updateCurrency(inventory, request.RegularCredits, false);
guildMember.RegularCreditsContributed ??= 0;
guildMember.RegularCreditsContributed += request.RegularCredits;
}
if (request.VaultCredits) {
component.RegularCredits += request.VaultCredits;
@ -133,6 +142,9 @@ const processContribution = (
ItemType: ingredientContribution.ItemType,
ItemCount: ingredientContribution.ItemCount * -1
});
guildMember.MiscItemsContributed ??= [];
guildMember.MiscItemsContributed.push(ingredientContribution);
}
addMiscItems(inventory, miscItemChanges);
inventoryChanges.MiscItems = miscItemChanges;

View File

@ -1,3 +1,4 @@
import { GuildMember } from "@/src/models/guildModel";
import { getGuildForRequestEx } from "@/src/services/guildService";
import { addFusionTreasures, addMiscItems, addShipDecorations, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
@ -8,23 +9,34 @@ export const contributeToVaultController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const guild = await getGuildForRequestEx(req, inventory);
const guildMember = (await GuildMember.findOne(
{ accountId, guildId: guild._id },
"RegularCreditsContributed MiscItemsContributed ShipDecorationsContributed"
))!;
const request = JSON.parse(String(req.body)) as IContributeToVaultRequest;
if (request.RegularCredits) {
guild.VaultRegularCredits ??= 0;
guild.VaultRegularCredits += request.RegularCredits;
guildMember.RegularCreditsContributed ??= 0;
guildMember.RegularCreditsContributed += request.RegularCredits;
}
if (request.MiscItems.length) {
guild.VaultMiscItems ??= [];
guildMember.MiscItemsContributed ??= [];
for (const item of request.MiscItems) {
guild.VaultMiscItems.push(item);
guildMember.MiscItemsContributed.push(item);
addMiscItems(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
}
}
if (request.ShipDecorations.length) {
guild.VaultShipDecorations ??= [];
guildMember.ShipDecorationsContributed ??= [];
for (const item of request.ShipDecorations) {
guild.VaultShipDecorations.push(item);
guildMember.ShipDecorationsContributed.push(item);
addShipDecorations(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
}
}
@ -38,6 +50,7 @@ export const contributeToVaultController: RequestHandler = async (req, res) => {
await guild.save();
await inventory.save();
await guildMember.save();
res.end();
};

View File

@ -1,3 +1,4 @@
import { GuildMember } from "@/src/models/guildModel";
import { getDojoClient, getGuildForRequestEx, hasAccessToDojo, scaleRequiredCount } from "@/src/services/guildService";
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
@ -48,6 +49,12 @@ export const dojoComponentRushController: RequestHandler = async (req, res) => {
await guild.save();
await inventory.save();
const guildMember = (await GuildMember.findOne({ accountId, guildId: guild._id }, "PremiumCreditsContributed"))!;
guildMember.PremiumCreditsContributed ??= 0;
guildMember.PremiumCreditsContributed += request.Amount;
await guildMember.save();
res.json({
...(await getDojoClient(guild, 0, component._id)),
InventoryChanges: inventoryChanges

View File

@ -0,0 +1,18 @@
import { GuildMember } from "@/src/models/guildModel";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { RequestHandler } from "express";
export const getGuildContributionsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const guildId = (await getInventory(accountId, "GuildId")).GuildId;
const guildMember = (await GuildMember.findOne({ guildId, accountId: req.query.buddyId }))!;
res.json({
_id: { $oid: req.query.buddyId },
RegularCreditsContributed: guildMember.RegularCreditsContributed,
PremiumCreditsContributed: guildMember.PremiumCreditsContributed,
MiscItemsContributed: guildMember.MiscItemsContributed,
ConsumablesContributed: [], // ???
ShipDecorationsContributed: guildMember.ShipDecorationsContributed
});
};

View File

@ -21,7 +21,7 @@ import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { config } from "@/src/services/configService";
import { GuildPermission, ITechProjectClient, ITechProjectDatabase } from "@/src/types/guildTypes";
import { TGuildDatabaseDocument } from "@/src/models/guildModel";
import { GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
import { logger } from "@/src/utils/logger";
@ -90,6 +90,12 @@ export const guildTechController: RequestHandler = async (req, res) => {
res.status(400).send("-1").end();
return;
}
const guildMember = (await GuildMember.findOne(
{ accountId, guildId: guild._id },
"RegularCreditsContributed MiscItemsContributed"
))!;
const contributions = data;
const techProject = guild.TechProjects!.find(x => x.ItemType == contributions.RecipeType)!;
@ -106,6 +112,9 @@ export const guildTechController: RequestHandler = async (req, res) => {
}
techProject.ReqCredits -= contributions.RegularCredits;
guildMember.RegularCreditsContributed ??= 0;
guildMember.RegularCreditsContributed += contributions.RegularCredits;
if (contributions.VaultMiscItems.length) {
for (const miscItem of contributions.VaultMiscItems) {
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
@ -133,6 +142,9 @@ export const guildTechController: RequestHandler = async (req, res) => {
ItemType: miscItem.ItemType,
ItemCount: miscItem.ItemCount * -1
});
guildMember.MiscItemsContributed ??= [];
guildMember.MiscItemsContributed.push(miscItem);
}
}
addMiscItems(inventory, miscItemChanges);
@ -151,6 +163,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
await guild.save();
await inventory.save();
await guildMember.save();
res.json({
InventoryChanges: inventoryChanges,
Vault: getGuildVault(guild)

View File

@ -0,0 +1,45 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { addBooster, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getRandomInt } from "@/src/services/rngService";
import { RequestHandler } from "express";
import { ExportBoosters } from "warframe-public-export-plus";
export const hubBlessingController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const data = getJSONfromString<IHubBlessingRequest>(String(req.body));
const boosterType = ExportBoosters[data.booster].typeName;
if (req.query.mode == "send") {
const inventory = await getInventory(accountId, "BlessingCooldown Boosters");
inventory.BlessingCooldown = new Date(Date.now() + 86400000);
addBooster(boosterType, 3 * 3600, inventory);
await inventory.save();
let token = "";
for (let i = 0; i != 32; ++i) {
token += getRandomInt(0, 15).toString(16);
}
res.json({
BlessingCooldown: inventory.BlessingCooldown,
SendTime: Math.trunc(Date.now() / 1000).toString(),
Token: token
});
} else {
const inventory = await getInventory(accountId, "Boosters");
addBooster(boosterType, 3 * 3600, inventory);
await inventory.save();
res.json({
BoosterType: data.booster,
Sender: data.senderId
});
}
};
interface IHubBlessingRequest {
booster: string;
senderId?: string; // mode=request
sendTime?: string; // mode=request
token?: string; // mode=request
}

View File

@ -1,19 +1,28 @@
import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { Account } from "@/src/models/loginModel";
const logoutController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const account = await Account.findById(accountId);
if (account) {
account.Nonce = 0;
await account.save();
export const logoutController: RequestHandler = async (req, res) => {
if (!req.query.accountId) {
throw new Error("Request is missing accountId parameter");
}
const nonce: number = parseInt(req.query.nonce as string);
if (!nonce) {
throw new Error("Request is missing nonce parameter");
}
await Account.updateOne(
{
_id: req.query.accountId,
Nonce: nonce
},
{
Nonce: 0
}
);
res.writeHead(200, {
"Content-Type": "text/html",
"Content-Length": 1
});
res.end("1");
};
export { logoutController };

View File

@ -1,12 +1,17 @@
import { getLeaderboard } from "@/src/services/leaderboardService";
import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express";
export const leaderboardController: RequestHandler = async (req, res) => {
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
const payload = JSON.parse(String(req.body)) as ILeaderboardRequest;
res.json({
results: await getLeaderboard(payload.field, payload.before, payload.after, payload.guildId, payload.pivotId)
results: await getLeaderboard(
payload.field,
payload.before,
payload.after,
payload.pivotId,
payload.guildId,
payload.guildTier
)
});
};
@ -14,6 +19,7 @@ interface ILeaderboardRequest {
field: string;
before: number;
after: number;
guildId?: string;
pivotId?: string;
guildId?: string;
guildTier?: number;
}

View File

@ -215,7 +215,11 @@ const guildMemberSchema = new Schema<IGuildMemberDatabase>({
accountId: Types.ObjectId,
guildId: Types.ObjectId,
status: { type: Number, required: true },
rank: { type: Number, default: 7 }
rank: { type: Number, default: 7 },
RegularCreditsContributed: Number,
PremiumCreditsContributed: Number,
MiscItemsContributed: { type: [typeCountSchema], default: undefined },
ShipDecorationsContributed: { type: [typeCountSchema], default: undefined }
});
guildMemberSchema.index({ accountId: 1, guildId: 1 }, { unique: true });

View File

@ -82,6 +82,7 @@ import {
INemesisDatabase,
INemesisClient,
IInfNode,
IDiscoveredMarker,
IWeeklyMission
} from "../../types/inventoryTypes/inventoryTypes";
import { IOid } from "../../types/commonTypes";
@ -378,6 +379,14 @@ droneSchema.set("toJSON", {
}
});
const discoveredMarkerSchema = new Schema<IDiscoveredMarker>(
{
tag: String,
discoveryState: [Number]
},
{ _id: false }
);
const challengeProgressSchema = new Schema<IChallengeProgress>(
{
Progress: Number,
@ -1348,7 +1357,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
ActiveAvatarImageType: { type: String, default: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageDefault" },
// open location store like EidolonPlainsDiscoverable or OrbVallisCaveDiscoverable
DiscoveredMarkers: [Schema.Types.Mixed],
DiscoveredMarkers: [discoveredMarkerSchema],
//Open location mission like "JobId" + "StageCompletions"
CompletedJobs: [Schema.Types.Mixed],

View File

@ -8,7 +8,8 @@ const leaderboardEntrySchema = new Schema<ILeaderboardEntryDatabase>(
displayName: { type: String, required: true },
score: { type: Number, required: true },
guildId: Schema.Types.ObjectId,
expiry: { type: Date, required: true }
expiry: { type: Date, required: true },
guildTier: Number
},
{ id: false }
);

View File

@ -42,6 +42,7 @@ import { genericUpdateController } from "@/src/controllers/api/genericUpdateCont
import { getAllianceController } from "@/src/controllers/api/getAllianceController";
import { getDailyDealStockLevelsController } from "@/src/controllers/api/getDailyDealStockLevelsController";
import { getFriendsController } from "@/src/controllers/api/getFriendsController";
import { getGuildContributionsController } from "@/src/controllers/api/getGuildContributionsController";
import { getGuildController } from "@/src/controllers/api/getGuildController";
import { getGuildDojoController } from "@/src/controllers/api/getGuildDojoController";
import { getGuildLogController } from "@/src/controllers/api/getGuildLogController";
@ -57,6 +58,7 @@ import { giveQuestKeyRewardController } from "@/src/controllers/api/giveQuestKey
import { giveStartingGearController } from "@/src/controllers/api/giveStartingGearController";
import { guildTechController } from "@/src/controllers/api/guildTechController";
import { hostSessionController } from "@/src/controllers/api/hostSessionController";
import { hubBlessingController } from "@/src/controllers/api/hubBlessingController";
import { hubController } from "@/src/controllers/api/hubController";
import { hubInstancesController } from "@/src/controllers/api/hubInstancesController";
import { inboxController } from "@/src/controllers/api/inboxController";
@ -138,6 +140,7 @@ apiRouter.get("/drones.php", dronesController);
apiRouter.get("/getDailyDealStockLevels.php", getDailyDealStockLevelsController);
apiRouter.get("/getFriends.php", getFriendsController);
apiRouter.get("/getGuild.php", getGuildController);
apiRouter.get("/getGuildContributions.php", getGuildContributionsController);
apiRouter.get("/getGuildDojo.php", getGuildDojoController);
apiRouter.get("/getGuildLog.php", getGuildLogController);
apiRouter.get("/getIgnoredUsers.php", getIgnoredUsersController);
@ -207,6 +210,7 @@ apiRouter.post("/giveQuestKeyReward.php", giveQuestKeyRewardController);
apiRouter.post("/giveStartingGear.php", giveStartingGearController);
apiRouter.post("/guildTech.php", guildTechController);
apiRouter.post("/hostSession.php", hostSessionController);
apiRouter.post("/hubBlessing.php", hubBlessingController);
apiRouter.post("/infestedFoundry.php", infestedFoundryController);
apiRouter.post("/inventorySlots.php", inventorySlotsController);
apiRouter.post("/joinSession.php", joinSessionController);

View File

@ -58,12 +58,7 @@ export const getGuildClient = async (guild: TGuildDatabaseDocument, accountId: s
if (guildMember.accountId.equals(accountId)) {
missingEntry = false;
} else {
member.DisplayName = (await Account.findOne(
{
_id: guildMember.accountId
},
"DisplayName"
))!.DisplayName;
member.DisplayName = (await Account.findById(guildMember.accountId, "DisplayName"))!.DisplayName;
await fillInInventoryDataForGuildMember(member);
}
members.push(member);

View File

@ -378,8 +378,8 @@ export const addItem = async (
defaultOverwrites.Features = EquipmentFeatures.DOUBLE_CAPACITY;
}
if (
weapon.defaultUpgrades &&
weapon.defaultUpgrades[0].ItemType == "/Lotus/Weapons/Grineer/KuvaLich/Upgrades/InnateDamageRandomMod"
weapon.defaultUpgrades?.[0]?.ItemType ==
"/Lotus/Weapons/Grineer/KuvaLich/Upgrades/InnateDamageRandomMod"
) {
defaultOverwrites.UpgradeType = "/Lotus/Weapons/Grineer/KuvaLich/Upgrades/InnateDamageRandomMod";
defaultOverwrites.UpgradeFingerprint = JSON.stringify({

View File

@ -1,14 +1,15 @@
import { Guild } from "../models/guildModel";
import { Leaderboard, TLeaderboardEntryDocument } from "../models/leaderboardModel";
import { ILeaderboardEntryClient } from "../types/leaderboardTypes";
export const submitLeaderboardScore = async (
schedule: "weekly" | "daily",
leaderboard: string,
ownerId: string,
displayName: string,
score: number,
guildId?: string
): Promise<void> => {
const schedule = leaderboard.split(".")[0] as "daily" | "weekly";
let expiry: Date;
if (schedule == "daily") {
expiry = new Date(Math.trunc(Date.now() / 86400000) * 86400000 + 86400000);
@ -21,23 +22,35 @@ export const submitLeaderboardScore = async (
expiry = new Date(weekEnd);
}
await Leaderboard.findOneAndUpdate(
{ leaderboard, ownerId },
{ leaderboard: `${schedule}.accounts.${leaderboard}`, ownerId },
{ $max: { score }, $set: { displayName, guildId, expiry } },
{ upsert: true }
);
if (guildId) {
const guild = (await Guild.findById(guildId, "Name Tier"))!;
await Leaderboard.findOneAndUpdate(
{ leaderboard: `${schedule}.guilds.${leaderboard}`, ownerId: guildId },
{ $max: { score }, $set: { displayName: guild.Name, guildTier: guild.Tier, expiry } },
{ upsert: true }
);
}
};
export const getLeaderboard = async (
leaderboard: string,
before: number,
after: number,
pivotId?: string,
guildId?: string,
pivotId?: string
guildTier?: number
): Promise<ILeaderboardEntryClient[]> => {
const filter: { leaderboard: string; guildId?: string } = { leaderboard };
const filter: { leaderboard: string; guildId?: string; guildTier?: number } = { leaderboard };
if (guildId) {
filter.guildId = guildId;
}
if (guildTier) {
filter.guildTier = guildTier;
}
let entries: TLeaderboardEntryDocument[];
let r: number;

View File

@ -83,13 +83,13 @@ const getRandomLoginReward = (rng: CRng, day: number, inventory: TInventoryDatab
masteredItems.add(entry.ItemType);
}
const unmasteredItems = new Set();
for (const uniqueName of Object.keys(ExportWeapons)) {
if (!masteredItems.has(uniqueName)) {
for (const [uniqueName, data] of Object.entries(ExportWeapons)) {
if (data.variantType == "VT_NORMAL" && !masteredItems.has(uniqueName)) {
unmasteredItems.add(uniqueName);
}
}
for (const uniqueName of Object.keys(ExportWarframes)) {
if (!masteredItems.has(uniqueName)) {
for (const [uniqueName, data] of Object.entries(ExportWarframes)) {
if (data.variantType == "VT_NORMAL" && !masteredItems.has(uniqueName)) {
unmasteredItems.add(uniqueName);
}
}

View File

@ -74,11 +74,8 @@ export const getAccountForRequest = async (req: Request): Promise<TAccountDocume
throw new Error("Request is missing nonce parameter");
}
const account = await Account.findOne({
_id: req.query.accountId,
Nonce: nonce
});
if (!account) {
const account = await Account.findById(req.query.accountId);
if (!account || account.Nonce != nonce) {
throw new Error("Invalid accountId-nonce pair");
}
if (account.Dropped && req.query.ct) {

View File

@ -43,6 +43,7 @@ import { createMessage } from "./inboxService";
import kuriaMessage50 from "@/static/fixed_responses/kuriaMessages/fiftyPercent.json";
import kuriaMessage75 from "@/static/fixed_responses/kuriaMessages/seventyFivePercent.json";
import kuriaMessage100 from "@/static/fixed_responses/kuriaMessages/oneHundredPercent.json";
import conservationAnimals from "@/static/fixed_responses/conservationAnimals.json";
import { getInfNodes } from "@/src/helpers/nemesisHelpers";
const getRotations = (rotationCount: number): number[] => {
@ -325,6 +326,50 @@ export const addMissionInventoryUpdates = async (
inventory.DeathMarks = value;
break;
}
case "CapturedAnimals": {
for (const capturedAnimal of value) {
const meta = conservationAnimals[capturedAnimal.AnimalType as keyof typeof conservationAnimals];
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (meta) {
if (capturedAnimal.NumTags) {
addMiscItems(inventory, [
{
ItemType: meta.tag,
ItemCount: capturedAnimal.NumTags
}
]);
}
if (capturedAnimal.NumExtraRewards) {
if ("extraReward" in meta) {
addMiscItems(inventory, [
{
ItemType: meta.extraReward,
ItemCount: capturedAnimal.NumExtraRewards
}
]);
} else {
logger.warn(
`client attempted to claim unknown extra rewards for conservation of ${capturedAnimal.AnimalType}`
);
}
}
} else {
logger.warn(`ignoring conservation of unknown AnimalType: ${capturedAnimal.AnimalType}`);
}
}
break;
}
case "DiscoveredMarkers": {
for (const clientMarker of value) {
const dbMarker = inventory.DiscoveredMarkers.find(x => x.tag == clientMarker.tag);
if (dbMarker) {
dbMarker.discoveryState = clientMarker.discoveryState;
} else {
inventory.DiscoveredMarkers.push(clientMarker);
}
}
break;
}
default:
// Equipment XP updates
if (equipmentKeys.includes(key as TEquipmentKey)) {

View File

@ -286,6 +286,15 @@ export const updateStats = async (accountOwnerId: string, payload: IStatsUpdate)
} else {
playerStats.Missions.push({ type: type, highScore });
}
await submitLeaderboardScore(
"weekly",
type,
accountOwnerId,
payload.displayName,
highScore,
payload.guildId
);
break;
}
break;
@ -304,27 +313,42 @@ export const updateStats = async (accountOwnerId: string, payload: IStatsUpdate)
}
await submitLeaderboardScore(
"daily.accounts." + race,
"daily",
race,
accountOwnerId,
payload.displayName,
highScore
highScore,
payload.guildId
);
}
break;
case "ZephyrScore":
case "SentinelGameScore":
case "CaliberChicksScore":
playerStats[category] ??= 0;
if (data > playerStats[category]) playerStats[category] = data as number;
break;
case "SentinelGameScore":
playerStats[category] ??= 0;
if (data > playerStats[category]) playerStats[category] = data as number;
await submitLeaderboardScore(
"weekly",
category,
accountOwnerId,
payload.displayName,
data as number,
payload.guildId
);
break;
case "DojoObstacleScore":
playerStats[category] ??= 0;
if (data > playerStats[category]) playerStats[category] = data as number;
await submitLeaderboardScore(
"weekly.accounts." + category,
"weekly",
category,
accountOwnerId,
payload.displayName,
data as number,
@ -350,7 +374,8 @@ export const updateStats = async (accountOwnerId: string, payload: IStatsUpdate)
}
if (data > playerStats[category]) playerStats[category] = data as number;
await submitLeaderboardScore(
"weekly.accounts." + category,
"weekly",
category,
accountOwnerId,
payload.displayName,
data as number

View File

@ -88,6 +88,10 @@ export interface IGuildMemberDatabase {
guildId: Types.ObjectId;
status: number;
rank: number;
RegularCreditsContributed?: number;
PremiumCreditsContributed?: number;
MiscItemsContributed?: IMiscItem[];
ShipDecorationsContributed?: ITypeCount[];
}
export interface IGuildMemberClient {

View File

@ -7,6 +7,7 @@ export interface ILeaderboardEntryDatabase {
score: number;
guildId?: Types.ObjectId;
expiry: Date;
guildTier?: number;
}
export interface ILeaderboardEntryClient {

View File

@ -16,7 +16,8 @@ import {
IQuestKeyDatabase,
ILoreFragmentScan,
IUpgradeClient,
ICollectibleEntry
ICollectibleEntry,
IDiscoveredMarker
} from "./inventoryTypes/inventoryTypes";
export interface IAffiliationChange {
@ -99,6 +100,14 @@ export type IMissionInventoryUpdateRequest = {
DeathMarks?: string[];
Nemesis?: number;
Boosters?: IBooster[];
CapturedAnimals?: {
AnimalType: string;
CaptureRating: number;
NumTags: number;
NumExtraRewards: number;
Count: number;
}[];
DiscoveredMarkers?: IDiscoveredMarker[];
} & {
[K in TEquipmentKey]?: IEquipmentClient[];
};

View File

@ -0,0 +1,491 @@
{
"/Lotus/Types/NeutralCreatures/Conservation/BirdOfPrey/CommonBirdOfPreyAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagCondrocCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/BirdOfPrey/CommonFemaleBirdOfPreyAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagCondrocCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/BirdOfPrey/CommonMaleBirdOfPreyAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagCondrocCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/BirdOfPrey/RareBirdOfPreyAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagCondrocRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/BirdOfPrey/RareFemaleBirdOfPreyAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagCondrocRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/BirdOfPrey/RareMaleBirdOfPreyAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagCondrocRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/BirdOfPrey/UncommonBirdOfPreyAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagCondrocUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/BirdOfPrey/UncommonFemaleBirdOfPreyAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagCondrocUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/BirdOfPrey/UncommonMaleBirdOfPreyAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagCondrocUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedCritter/BaseInfestedCritterAvatar": {
"tag": "/Lotus/Types/Items/Conservation/ConservationTagItem",
"extraReward": "/Lotus/Types/Items/Conservation/WoundedAnimalRewardItem"
},
"/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedCritter/CommonInfestedCritterAvatar": {
"tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedCritterCommon",
"extraReward": "/Lotus/Types/Items/Deimos/WoundedInfestedCritterCommonRewardItem"
},
"/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedCritter/RareInfestedCritterAvatar": {
"tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedCritterRare",
"extraReward": "/Lotus/Types/Items/Deimos/WoundedInfestedCritterRareRewardItem"
},
"/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedCritter/UncommonInfestedCritterAvatar": {
"tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedCritterUncommon",
"extraReward": "/Lotus/Types/Items/Deimos/WoundedInfestedCritterUncommonRewardItem"
},
"/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedKDrive/GrottoInfKDriveAvatar": {
"tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedKdriveUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedKDrive/HighlandInfKDriveAvatar": {
"tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedKdriveRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedKDrive/SwampInfKDriveAvatar": {
"tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedKdriveCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedMaggot/CommonInfestedMaggotAvatar": {
"tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedMaggotCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedMaggot/RareInfestedMaggotAvatar": {
"tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedMaggotRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedMaggot/UncommonInfestedMaggotAvatar": {
"tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedMaggotUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedMergoo/CommonInfestedMergooAvatar": {
"tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedMergooCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedMergoo/RareInfestedMergooAvatar": {
"tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedMergooRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedMergoo/UncommonInfestedMergooAvatar": {
"tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedMergooUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedNexifera/BaseInfestedNexiferaAvatar": {
"tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedNexiferaCommon",
"extraReward": "/Lotus/Types/Items/Conservation/WoundedAnimalRewardItem"
},
"/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedNexifera/CommonInfestedNexiferaAvatar": {
"tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedNexiferaCommon",
"extraReward": "/Lotus/Types/Items/Conservation/WoundedAnimalRewardItem"
},
"/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedNexifera/RareInfestedNexiferaAvatar": {
"tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedNexiferaRare",
"extraReward": "/Lotus/Types/Items/Conservation/WoundedAnimalRewardItem"
},
"/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedNexifera/UncommonInfestedNexiferaAvatar": {
"tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedNexiferaUncommon",
"extraReward": "/Lotus/Types/Items/Conservation/WoundedAnimalRewardItem"
},
"/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedPredator/BaseInfestedPredatorAvatar": {
"tag": "/Lotus/Types/Items/Conservation/ConservationTagItem",
"extraReward": "/Lotus/Types/Items/Conservation/WoundedAnimalRewardItem"
},
"/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedPredator/CommonInfestedPredatorAvatar": {
"tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedPredatorCommon",
"extraReward": "/Lotus/Types/Items/Deimos/WoundedInfestedPredatorCommonRewardItem"
},
"/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedPredator/RareInfestedPredatorAvatar": {
"tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedPredatorRare",
"extraReward": "/Lotus/Types/Items/Deimos/WoundedInfestedPredatorRareRewardItem"
},
"/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedPredator/UncommonInfestedPredatorAvatar": {
"tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedPredatorUncommon",
"extraReward": "/Lotus/Types/Items/Deimos/WoundedInfestedPredatorUncommonRewardItem"
},
"/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedUndazoa/BaseUndazoaAvatar": {
"tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedZongroCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedUndazoa/CommonUndazoaAvatar": {
"tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedZongroCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedUndazoa/RareUndazoaAvatar": {
"tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedZongroRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/Deimos/InfestedUndazoa/UncommonUndazoaAvatar": {
"tag": "/Lotus/Types/Items/Deimos/AnimalTagInfestedZongroUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/Duviri/Rabbit/BaseDuviriRabbitAvatar": {
"tag": "/Lotus/Types/Items/Conservation/ConservationTagItem"
},
"/Lotus/Types/NeutralCreatures/Conservation/Duviri/Rabbit/TeshinRabbitAvatar": {
"tag": "/Lotus/Types/Items/Conservation/ConservationTagItem"
},
"/Lotus/Types/NeutralCreatures/Conservation/Duviri/Rabbit/TeshinRabbitOnHandAvatar": {
"tag": "/Lotus/Types/Items/Conservation/ConservationTagItem"
},
"/Lotus/Types/NeutralCreatures/Conservation/Duviri/Wolf/DuviriWolfAvatar": {
"tag": "/Lotus/Types/Items/Conservation/ConservationTagItem"
},
"/Lotus/Types/NeutralCreatures/Conservation/Duviri/Wolf/DuviriWolfConservationAvatar": {
"tag": "/Lotus/Types/Items/Conservation/ConservationTagItem"
},
"/Lotus/Types/NeutralCreatures/Conservation/ForestRodent/CommonFemaleForestRodentAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagKuakaCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/ForestRodent/CommonForestRodentAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagKuakaCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/ForestRodent/CommonMaleForestRodentAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagKuakaCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/ForestRodent/RareFemaleForestRodentAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagKuakaRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/ForestRodent/RareForestRodentAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagKuakaRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/ForestRodent/RareMaleForestRodentAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagKuakaRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/ForestRodent/TutorialForestRodentAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagKuakaCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/ForestRodent/UncommonFemaleForestRodentAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagKuakaUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/ForestRodent/UncommonForestRodentAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagKuakaUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/ForestRodent/UncommonMaleForestRodentAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagKuakaUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/LegendaryKubrow/BaseLegendaryKubrowAvatar": {
"tag": "/Lotus/Types/Items/Conservation/ConservationTagItem"
},
"/Lotus/Types/NeutralCreatures/Conservation/LegendaryKubrow/CommonFemaleLegendaryKubrowAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagKubrodonCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/LegendaryKubrow/CommonLegendaryKubrowAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagKubrodonCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/LegendaryKubrow/CommonMaleLegendaryKubrowAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagKubrodonCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/LegendaryKubrow/CommonPupLegendaryKubrowAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagKubrodonCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/LegendaryKubrow/RareFemaleLegendaryKubrowAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagKubrodonRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/LegendaryKubrow/RareLegendaryKubrowAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagKubrodonRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/LegendaryKubrow/RareMaleLegendaryKubrowAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagKubrodonRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/LegendaryKubrow/RarePupLegendaryKubrowAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagKubrodonRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/LegendaryKubrow/UncommonFemaleLegendaryKubrowAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagKubrodonUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/LegendaryKubrow/UncommonLegendaryKubrowAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagKubrodonUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/LegendaryKubrow/UncommonMaleLegendaryKubrowAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagKubrodonUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/LegendaryKubrow/UncommonPupLegendaryKubrowAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagKubrodonUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/OrokinKubrow/BaseOrokinKubrowAvatar": {
"tag": "/Lotus/Types/Items/Conservation/ConservationTagItem"
},
"/Lotus/Types/NeutralCreatures/Conservation/OrokinKubrow/CommonFemaleOrokinKubrowAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagStoverCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/OrokinKubrow/CommonMaleOrokinKubrowAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagStoverCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/OrokinKubrow/CommonOrokinKubrowAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagStoverCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/OrokinKubrow/CommonPupOrokinKubrowAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagStoverCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/OrokinKubrow/RareFemaleOrokinKubrowAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagStoverRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/OrokinKubrow/RareMaleOrokinKubrowAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagStoverRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/OrokinKubrow/RareOrokinKubrowAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagStoverRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/OrokinKubrow/RarePupOrokinKubrowAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagStoverRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/OrokinKubrow/UncommonFemaleOrokinKubrowAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagStoverUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/OrokinKubrow/UncommonMaleOrokinKubrowAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagStoverUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/OrokinKubrow/UncommonOrokinKubrowAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagStoverUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/OrokinKubrow/UncommonPupOrokinKubrowAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagStoverUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/OstronSeaBird/CommonFemaleOstronSeaBirdAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagMergooCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/OstronSeaBird/CommonMaleOstronSeaBirdAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagMergooCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/OstronSeaBird/CommonOstronSeaBirdAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagMergooCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/OstronSeaBird/RareFemaleOstronSeaBirdAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagMergooRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/OstronSeaBird/RareMaleOstronSeaBirdAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagMergooRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/OstronSeaBird/RareOstronSeaBirdAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagMergooRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/OstronSeaBird/UncommonFemaleOstronSeaBirdAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagMergooUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/OstronSeaBird/UncommonMaleOstronSeaBirdAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagMergooUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/OstronSeaBird/UncommonOstronSeaBirdAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagMergooUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowArmadillo/BaseSnowArmadilloAvatar": {
"tag": "/Lotus/Types/Items/Conservation/ConservationTagItem"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowArmadillo/CommonFemaleSnowArmadilloAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagBolarolaCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowArmadillo/CommonMaleSnowArmadilloAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagBolarolaCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowArmadillo/CommonPupSnowArmadilloAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagBolarolaCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowArmadillo/CommonSnowArmadilloAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagBolarolaCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowArmadillo/RareFemaleSnowArmadilloAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagBolarolaRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowArmadillo/RareMaleSnowArmadilloAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagBolarolaRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowArmadillo/RarePupSnowArmadilloAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagBolarolaRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowArmadillo/RareSnowArmadilloAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagBolarolaRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowArmadillo/UncommonFemaleSnowArmadilloAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagBolarolaUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowArmadillo/UncommonMaleSnowArmadilloAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagBolarolaUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowArmadillo/UncommonPupSnowArmadilloAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagBolarolaUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowArmadillo/UncommonSnowArmadilloAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagBolarolaUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowBird/BaseSnowBirdAvatar": {
"tag": "/Lotus/Types/Items/Conservation/ConservationTagItem"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowBird/CommonFemaleSnowBirdAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagSawgawCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowBird/CommonMaleSnowBirdAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagSawgawCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowBird/CommonPupSnowBirdAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagSawgawCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowBird/CommonSnowBirdAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagSawgawCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowBird/RareFemaleSnowBirdAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagSawgawRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowBird/RareMaleSnowBirdAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagSawgawRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowBird/RarePupSnowBirdAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagSawgawRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowBird/RareSnowBirdAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagSawgawRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowBird/UncommonFemaleSnowBirdAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagSawgawUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowBird/UncommonMaleSnowBirdAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagSawgawUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowBird/UncommonPupSnowBirdAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagSawgawUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowBird/UncommonSnowBirdAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagSawgawUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowCritter/BaseSnowCritterAvatar": {
"tag": "/Lotus/Types/Items/Conservation/ConservationTagItem"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowCritter/CommonFemaleSnowCritterAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagVirminkCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowCritter/CommonMaleSnowCritterAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagVirminkCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowCritter/CommonPupSnowCritterAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagVirminkCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowCritter/CommonSnowCritterAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagVirminkCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowCritter/RareFemaleSnowCritterAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagVirminkRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowCritter/RareMaleSnowCritterAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagVirminkRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowCritter/RarePupSnowCritterAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagVirminkRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowCritter/RareSnowCritterAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagVirminkRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowCritter/UncommonFemaleSnowCritterAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagVirminkUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowCritter/UncommonMaleSnowCritterAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagVirminkUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowCritter/UncommonPupSnowCritterAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagVirminkUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowCritter/UncommonSnowCritterAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagVirminkUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowPredator/BaseSnowPredatorAvatar": {
"tag": "/Lotus/Types/Items/Conservation/ConservationTagItem"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowPredator/CommonFemaleSnowPredatorAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagHorrasqueCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowPredator/CommonMaleSnowPredatorAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagHorrasqueCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowPredator/CommonPupSnowPredatorAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagHorrasqueCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowPredator/CommonSnowPredatorAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagHorrasqueCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowPredator/RareFemaleSnowPredatorAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagHorrasqueRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowPredator/RareMaleSnowPredatorAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagHorrasqueRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowPredator/RarePupSnowPredatorAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagHorrasqueRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowPredator/RareSnowPredatorAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagHorrasqueRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowPredator/UncommonFemaleSnowPredatorAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagHorrasqueUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowPredator/UncommonMaleSnowPredatorAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagHorrasqueUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowPredator/UncommonPupSnowPredatorAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagHorrasqueUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowPredator/UncommonSnowPredatorAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagHorrasqueUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowRodent/BaseSnowRodentAvatar": {
"tag": "/Lotus/Types/Items/Conservation/ConservationTagItem"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowRodent/CommonFemaleSnowRodentAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagPobbersCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowRodent/CommonMaleSnowRodentAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagPobbersCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowRodent/CommonSnowRodentAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagPobbersCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowRodent/RareFemaleSnowRodentAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagPobbersRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowRodent/RareMaleSnowRodentAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagPobbersRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowRodent/RareSnowRodentAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagPobbersRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowRodent/UncommonFemaleSnowRodentAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagPobbersUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowRodent/UncommonMaleSnowRodentAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagPobbersUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/SnowRodent/UncommonSnowRodentAvatar": {
"tag": "/Lotus/Types/Items/Solaris/AnimalTagPobbersUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/VampireKavat/BaseVampireKavatAvatar": {
"tag": "/Lotus/Types/Items/Conservation/ConservationTagItem"
},
"/Lotus/Types/NeutralCreatures/Conservation/VampireKavat/CommonVampireKavatAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagVampireKavatCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/VampireKavat/CommonVampireKavatCubAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagVampireKavatCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/VampireKavat/CommonVampireKavatFemaleAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagVampireKavatCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/VampireKavat/CommonVampireKavatMaleAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagVampireKavatCommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/VampireKavat/RareVampireKavatAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagVampireKavatRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/VampireKavat/RareVampireKavatCubAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagVampireKavatRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/VampireKavat/RareVampireKavatFemaleAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagVampireKavatRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/VampireKavat/RareVampireKavatMaleAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagVampireKavatRare"
},
"/Lotus/Types/NeutralCreatures/Conservation/VampireKavat/UncommonVampireKavatAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagVampireKavatUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/VampireKavat/UncommonVampireKavatCubAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagVampireKavatUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/VampireKavat/UncommonVampireKavatFemaleAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagVampireKavatUncommon"
},
"/Lotus/Types/NeutralCreatures/Conservation/VampireKavat/UncommonVampireKavatMaleAvatar": {
"tag": "/Lotus/Types/Items/Eidolon/AnimalTagVampireKavatUncommon"
}
}