2024-06-06 16:55:37 +02:00
|
|
|
import { Request } from "express";
|
|
|
|
import { getAccountIdForRequest } from "@/src/services/loginService";
|
2025-03-10 16:40:40 -07:00
|
|
|
import { addRecipes, getInventory } from "@/src/services/inventoryService";
|
2025-03-31 04:14:00 -07:00
|
|
|
import { Guild, GuildAd, GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
|
2025-01-03 22:17:34 +01:00
|
|
|
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
2025-03-06 21:24:25 -08:00
|
|
|
import {
|
2025-03-14 07:09:28 -07:00
|
|
|
GuildPermission,
|
2025-03-06 21:24:25 -08:00
|
|
|
IDojoClient,
|
|
|
|
IDojoComponentClient,
|
2025-03-14 02:07:08 -07:00
|
|
|
IDojoComponentDatabase,
|
2025-03-06 21:24:25 -08:00
|
|
|
IDojoContributable,
|
|
|
|
IDojoDecoClient,
|
2025-03-10 16:40:40 -07:00
|
|
|
IGuildClient,
|
|
|
|
IGuildMemberClient,
|
2025-03-14 07:09:28 -07:00
|
|
|
IGuildMemberDatabase,
|
2025-03-30 09:58:51 -07:00
|
|
|
IGuildVault,
|
|
|
|
ITechProjectDatabase
|
2025-03-06 21:24:25 -08:00
|
|
|
} from "@/src/types/guildTypes";
|
2025-02-11 20:11:31 -08:00
|
|
|
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
|
2025-03-03 12:48:39 -08:00
|
|
|
import { Types } from "mongoose";
|
2025-03-30 09:58:51 -07:00
|
|
|
import { ExportDojoRecipes, IDojoBuild, IDojoResearch } from "warframe-public-export-plus";
|
2025-03-06 07:19:01 -08:00
|
|
|
import { logger } from "../utils/logger";
|
2025-03-10 16:40:40 -07:00
|
|
|
import { config } from "./configService";
|
|
|
|
import { Account } from "../models/loginModel";
|
2025-03-11 10:31:56 -07:00
|
|
|
import { getRandomInt } from "./rngService";
|
2025-03-30 04:40:00 -07:00
|
|
|
import { Inbox } from "../models/inboxModel";
|
2025-03-30 09:58:51 -07:00
|
|
|
import { ITypeCount } from "../types/inventoryTypes/inventoryTypes";
|
2024-06-06 16:55:37 +02:00
|
|
|
|
2025-02-11 20:11:31 -08:00
|
|
|
export const getGuildForRequest = async (req: Request): Promise<TGuildDatabaseDocument> => {
|
2024-06-06 16:55:37 +02:00
|
|
|
const accountId = await getAccountIdForRequest(req);
|
|
|
|
const inventory = await getInventory(accountId);
|
2025-01-03 09:06:50 +01:00
|
|
|
return await getGuildForRequestEx(req, inventory);
|
|
|
|
};
|
|
|
|
|
2025-02-11 20:11:31 -08:00
|
|
|
export const getGuildForRequestEx = async (
|
|
|
|
req: Request,
|
|
|
|
inventory: TInventoryDatabaseDocument
|
|
|
|
): Promise<TGuildDatabaseDocument> => {
|
2024-06-06 16:55:37 +02:00
|
|
|
const guildId = req.query.guildId as string;
|
|
|
|
if (!inventory.GuildId || inventory.GuildId.toString() != guildId) {
|
|
|
|
throw new Error("Account is not in the guild that it has sent a request for");
|
|
|
|
}
|
2025-03-24 11:32:08 -07:00
|
|
|
const guild = await Guild.findById(guildId);
|
2024-06-06 16:55:37 +02:00
|
|
|
if (!guild) {
|
2025-01-03 09:06:50 +01:00
|
|
|
throw new Error("Account thinks it is in a guild that doesn't exist");
|
2024-06-06 16:55:37 +02:00
|
|
|
}
|
|
|
|
return guild;
|
|
|
|
};
|
2025-02-11 20:11:31 -08:00
|
|
|
|
2025-03-10 16:40:40 -07:00
|
|
|
export const getGuildClient = async (guild: TGuildDatabaseDocument, accountId: string): Promise<IGuildClient> => {
|
|
|
|
const guildMembers = await GuildMember.find({ guildId: guild._id });
|
|
|
|
|
|
|
|
const members: IGuildMemberClient[] = [];
|
|
|
|
let missingEntry = true;
|
|
|
|
for (const guildMember of guildMembers) {
|
|
|
|
const member: IGuildMemberClient = {
|
|
|
|
_id: toOid(guildMember.accountId),
|
|
|
|
Rank: guildMember.rank,
|
|
|
|
Status: guildMember.status
|
|
|
|
};
|
|
|
|
if (guildMember.accountId.equals(accountId)) {
|
|
|
|
missingEntry = false;
|
|
|
|
} else {
|
2025-03-27 03:33:08 -07:00
|
|
|
member.DisplayName = (await Account.findById(guildMember.accountId, "DisplayName"))!.DisplayName;
|
2025-03-10 16:40:40 -07:00
|
|
|
await fillInInventoryDataForGuildMember(member);
|
|
|
|
}
|
|
|
|
members.push(member);
|
|
|
|
}
|
|
|
|
if (missingEntry) {
|
|
|
|
// Handle clans created prior to creation of the GuildMember model.
|
|
|
|
await GuildMember.insertOne({
|
|
|
|
accountId: accountId,
|
|
|
|
guildId: guild._id,
|
|
|
|
status: 0,
|
|
|
|
rank: 0
|
|
|
|
});
|
|
|
|
members.push({
|
|
|
|
_id: { $oid: accountId },
|
|
|
|
Status: 0,
|
|
|
|
Rank: 0
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
_id: toOid(guild._id),
|
|
|
|
Name: guild.Name,
|
|
|
|
MOTD: guild.MOTD,
|
|
|
|
LongMOTD: guild.LongMOTD,
|
|
|
|
Members: members,
|
2025-03-13 02:14:29 -07:00
|
|
|
Ranks: guild.Ranks,
|
2025-03-15 03:21:40 -07:00
|
|
|
TradeTax: guild.TradeTax,
|
2025-03-21 05:19:53 -07:00
|
|
|
Tier: guild.Tier,
|
2025-03-10 16:40:40 -07:00
|
|
|
Vault: getGuildVault(guild),
|
2025-03-16 04:32:11 -07:00
|
|
|
ActiveDojoColorResearch: guild.ActiveDojoColorResearch,
|
2025-03-10 16:40:40 -07:00
|
|
|
Class: guild.Class,
|
|
|
|
XP: guild.XP,
|
|
|
|
IsContributor: !!guild.CeremonyContributors?.find(x => x.equals(accountId)),
|
|
|
|
NumContributors: guild.CeremonyContributors?.length ?? 0,
|
|
|
|
CeremonyResetDate: guild.CeremonyResetDate ? toMongoDate(guild.CeremonyResetDate) : undefined
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2025-03-06 21:24:25 -08:00
|
|
|
export const getGuildVault = (guild: TGuildDatabaseDocument): IGuildVault => {
|
|
|
|
return {
|
|
|
|
DojoRefundRegularCredits: guild.VaultRegularCredits,
|
|
|
|
DojoRefundMiscItems: guild.VaultMiscItems,
|
|
|
|
DojoRefundPremiumCredits: guild.VaultPremiumCredits,
|
|
|
|
ShipDecorations: guild.VaultShipDecorations,
|
|
|
|
FusionTreasures: guild.VaultFusionTreasures
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2025-03-06 07:19:01 -08:00
|
|
|
export const getDojoClient = async (
|
2025-03-03 12:48:39 -08:00
|
|
|
guild: TGuildDatabaseDocument,
|
|
|
|
status: number,
|
2025-03-05 23:54:47 -08:00
|
|
|
componentId: Types.ObjectId | string | undefined = undefined
|
2025-03-06 07:19:01 -08:00
|
|
|
): Promise<IDojoClient> => {
|
2025-02-11 20:11:31 -08:00
|
|
|
const dojo: IDojoClient = {
|
|
|
|
_id: { $oid: guild._id.toString() },
|
|
|
|
Name: guild.Name,
|
|
|
|
Tier: 1,
|
|
|
|
FixedContributions: true,
|
|
|
|
DojoRevision: 1,
|
2025-03-06 21:24:25 -08:00
|
|
|
Vault: getGuildVault(guild),
|
2025-02-11 20:11:31 -08:00
|
|
|
RevisionTime: Math.round(Date.now() / 1000),
|
|
|
|
Energy: guild.DojoEnergy,
|
|
|
|
Capacity: guild.DojoCapacity,
|
|
|
|
DojoRequestStatus: status,
|
|
|
|
DojoComponents: []
|
|
|
|
};
|
2025-03-06 07:19:01 -08:00
|
|
|
const roomsToRemove: Types.ObjectId[] = [];
|
2025-03-14 02:07:08 -07:00
|
|
|
let needSave = false;
|
|
|
|
for (const dojoComponent of guild.DojoComponents) {
|
2025-03-05 23:54:47 -08:00
|
|
|
if (!componentId || dojoComponent._id.equals(componentId)) {
|
2025-03-03 12:48:39 -08:00
|
|
|
const clientComponent: IDojoComponentClient = {
|
|
|
|
id: toOid(dojoComponent._id),
|
|
|
|
pf: dojoComponent.pf,
|
|
|
|
ppf: dojoComponent.ppf,
|
|
|
|
Name: dojoComponent.Name,
|
|
|
|
Message: dojoComponent.Message,
|
2025-03-05 23:54:47 -08:00
|
|
|
DecoCapacity: dojoComponent.DecoCapacity ?? 600
|
2025-03-03 12:48:39 -08:00
|
|
|
};
|
|
|
|
if (dojoComponent.pi) {
|
|
|
|
clientComponent.pi = toOid(dojoComponent.pi);
|
|
|
|
clientComponent.op = dojoComponent.op!;
|
|
|
|
clientComponent.pp = dojoComponent.pp!;
|
|
|
|
}
|
|
|
|
if (dojoComponent.CompletionTime) {
|
|
|
|
clientComponent.CompletionTime = toMongoDate(dojoComponent.CompletionTime);
|
2025-03-14 02:07:08 -07:00
|
|
|
if (dojoComponent.CompletionLogPending && Date.now() >= dojoComponent.CompletionTime.getTime()) {
|
|
|
|
const entry = guild.RoomChanges?.find(x => x.componentId.equals(dojoComponent._id));
|
|
|
|
if (entry) {
|
|
|
|
dojoComponent.CompletionLogPending = undefined;
|
|
|
|
entry.entryType = 1;
|
|
|
|
needSave = true;
|
|
|
|
}
|
2025-03-30 09:58:51 -07:00
|
|
|
|
|
|
|
let newTier: number | undefined;
|
|
|
|
switch (dojoComponent.pf) {
|
|
|
|
case "/Lotus/Levels/ClanDojo/ClanDojoBarracksShadow.level":
|
|
|
|
newTier = 2;
|
|
|
|
break;
|
|
|
|
case "/Lotus/Levels/ClanDojo/ClanDojoBarracksStorm.level":
|
|
|
|
newTier = 3;
|
|
|
|
break;
|
|
|
|
case "/Lotus/Levels/ClanDojo/ClanDojoBarracksMountain.level":
|
|
|
|
newTier = 4;
|
|
|
|
break;
|
|
|
|
case "/Lotus/Levels/ClanDojo/ClanDojoBarracksMoon.level":
|
|
|
|
newTier = 5;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (newTier) {
|
|
|
|
logger.debug(`clan finished building barracks, updating to tier ${newTier}`);
|
|
|
|
await setGuildTier(guild, newTier);
|
|
|
|
needSave = true;
|
|
|
|
}
|
2025-03-14 02:07:08 -07:00
|
|
|
}
|
2025-03-06 07:19:01 -08:00
|
|
|
if (dojoComponent.DestructionTime) {
|
|
|
|
if (Date.now() >= dojoComponent.DestructionTime.getTime()) {
|
|
|
|
roomsToRemove.push(dojoComponent._id);
|
2025-03-14 02:07:08 -07:00
|
|
|
continue;
|
2025-03-06 07:19:01 -08:00
|
|
|
}
|
|
|
|
clientComponent.DestructionTime = toMongoDate(dojoComponent.DestructionTime);
|
|
|
|
}
|
2025-03-03 12:48:39 -08:00
|
|
|
} else {
|
|
|
|
clientComponent.RegularCredits = dojoComponent.RegularCredits;
|
|
|
|
clientComponent.MiscItems = dojoComponent.MiscItems;
|
|
|
|
}
|
2025-03-05 23:54:47 -08:00
|
|
|
if (dojoComponent.Decos) {
|
|
|
|
clientComponent.Decos = [];
|
|
|
|
for (const deco of dojoComponent.Decos) {
|
2025-03-06 21:24:25 -08:00
|
|
|
const clientDeco: IDojoDecoClient = {
|
2025-03-05 23:54:47 -08:00
|
|
|
id: toOid(deco._id),
|
|
|
|
Type: deco.Type,
|
|
|
|
Pos: deco.Pos,
|
|
|
|
Rot: deco.Rot,
|
2025-03-06 21:24:25 -08:00
|
|
|
Name: deco.Name
|
|
|
|
};
|
|
|
|
if (deco.CompletionTime) {
|
|
|
|
clientDeco.CompletionTime = toMongoDate(deco.CompletionTime);
|
|
|
|
} else {
|
|
|
|
clientDeco.RegularCredits = deco.RegularCredits;
|
|
|
|
clientDeco.MiscItems = deco.MiscItems;
|
|
|
|
}
|
|
|
|
clientComponent.Decos.push(clientDeco);
|
2025-03-05 23:54:47 -08:00
|
|
|
}
|
|
|
|
}
|
2025-03-03 12:48:39 -08:00
|
|
|
dojo.DojoComponents.push(clientComponent);
|
2025-02-11 20:11:31 -08:00
|
|
|
}
|
2025-03-14 02:07:08 -07:00
|
|
|
}
|
2025-03-06 07:19:01 -08:00
|
|
|
if (roomsToRemove.length) {
|
|
|
|
logger.debug(`removing now-destroyed rooms`, roomsToRemove);
|
|
|
|
for (const id of roomsToRemove) {
|
2025-03-30 09:58:51 -07:00
|
|
|
await removeDojoRoom(guild, id);
|
2025-03-06 07:19:01 -08:00
|
|
|
}
|
2025-03-14 02:07:08 -07:00
|
|
|
needSave = true;
|
|
|
|
}
|
|
|
|
if (needSave) {
|
2025-03-06 07:19:01 -08:00
|
|
|
await guild.save();
|
|
|
|
}
|
2025-03-30 09:58:51 -07:00
|
|
|
dojo.Tier = guild.Tier;
|
2025-02-11 20:11:31 -08:00
|
|
|
return dojo;
|
|
|
|
};
|
2025-03-03 12:48:39 -08:00
|
|
|
|
2025-03-30 09:58:51 -07:00
|
|
|
const guildTierScalingFactors = [0.01, 0.03, 0.1, 0.3, 1];
|
|
|
|
export const scaleRequiredCount = (tier: number, count: number): number => {
|
|
|
|
return Math.max(1, Math.trunc(count * guildTierScalingFactors[tier - 1]));
|
2025-03-03 12:48:39 -08:00
|
|
|
};
|
2025-03-05 23:54:47 -08:00
|
|
|
|
2025-03-30 09:58:51 -07:00
|
|
|
export const removeDojoRoom = async (
|
|
|
|
guild: TGuildDatabaseDocument,
|
|
|
|
componentId: Types.ObjectId | string
|
|
|
|
): Promise<void> => {
|
2025-03-05 23:54:47 -08:00
|
|
|
const component = guild.DojoComponents.splice(
|
|
|
|
guild.DojoComponents.findIndex(x => x._id.equals(componentId)),
|
|
|
|
1
|
|
|
|
)[0];
|
|
|
|
const meta = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == component.pf);
|
|
|
|
if (meta) {
|
|
|
|
guild.DojoCapacity -= meta.capacity;
|
|
|
|
guild.DojoEnergy -= meta.energy;
|
|
|
|
}
|
2025-03-06 21:24:25 -08:00
|
|
|
moveResourcesToVault(guild, component);
|
|
|
|
component.Decos?.forEach(deco => moveResourcesToVault(guild, deco));
|
2025-03-14 02:07:08 -07:00
|
|
|
|
|
|
|
if (guild.RoomChanges) {
|
|
|
|
const index = guild.RoomChanges.findIndex(x => x.componentId.equals(component._id));
|
|
|
|
if (index != -1) {
|
|
|
|
guild.RoomChanges.splice(index, 1);
|
|
|
|
}
|
|
|
|
}
|
2025-03-30 09:58:51 -07:00
|
|
|
|
|
|
|
switch (component.pf) {
|
|
|
|
case "/Lotus/Levels/ClanDojo/ClanDojoBarracksShadow.level":
|
|
|
|
await setGuildTier(guild, 1);
|
|
|
|
break;
|
|
|
|
case "/Lotus/Levels/ClanDojo/ClanDojoBarracksStorm.level":
|
|
|
|
await setGuildTier(guild, 2);
|
|
|
|
break;
|
|
|
|
case "/Lotus/Levels/ClanDojo/ClanDojoBarracksMountain.level":
|
|
|
|
await setGuildTier(guild, 3);
|
|
|
|
break;
|
|
|
|
case "/Lotus/Levels/ClanDojo/ClanDojoBarracksMoon.level":
|
|
|
|
await setGuildTier(guild, 4);
|
|
|
|
break;
|
|
|
|
}
|
2025-03-05 23:54:47 -08:00
|
|
|
};
|
|
|
|
|
2025-03-06 07:19:01 -08:00
|
|
|
export const removeDojoDeco = (
|
|
|
|
guild: TGuildDatabaseDocument,
|
|
|
|
componentId: Types.ObjectId | string,
|
|
|
|
decoId: Types.ObjectId | string
|
|
|
|
): void => {
|
2025-03-05 23:54:47 -08:00
|
|
|
const component = guild.DojoComponents.id(componentId)!;
|
|
|
|
const deco = component.Decos!.splice(
|
|
|
|
component.Decos!.findIndex(x => x._id.equals(decoId)),
|
|
|
|
1
|
|
|
|
)[0];
|
|
|
|
const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == deco.Type);
|
|
|
|
if (meta && meta.capacityCost) {
|
|
|
|
component.DecoCapacity! += meta.capacityCost;
|
|
|
|
}
|
2025-03-06 21:24:25 -08:00
|
|
|
moveResourcesToVault(guild, deco);
|
|
|
|
};
|
|
|
|
|
|
|
|
const moveResourcesToVault = (guild: TGuildDatabaseDocument, component: IDojoContributable): void => {
|
|
|
|
if (component.RegularCredits) {
|
|
|
|
guild.VaultRegularCredits ??= 0;
|
|
|
|
guild.VaultRegularCredits += component.RegularCredits;
|
|
|
|
}
|
|
|
|
if (component.MiscItems) {
|
2025-03-30 09:58:51 -07:00
|
|
|
addVaultMiscItems(guild, component.MiscItems);
|
2025-03-06 21:24:25 -08:00
|
|
|
}
|
|
|
|
if (component.RushPlatinum) {
|
|
|
|
guild.VaultPremiumCredits ??= 0;
|
|
|
|
guild.VaultPremiumCredits += component.RushPlatinum;
|
|
|
|
}
|
2025-03-05 23:54:47 -08:00
|
|
|
};
|
2025-03-08 01:44:30 -08:00
|
|
|
|
2025-03-31 04:14:00 -07:00
|
|
|
export const getVaultMiscItemCount = (guild: TGuildDatabaseDocument, itemType: string): number => {
|
|
|
|
return guild.VaultMiscItems?.find(x => x.ItemType == itemType)?.ItemCount ?? 0;
|
|
|
|
};
|
|
|
|
|
2025-03-30 09:58:51 -07:00
|
|
|
export const addVaultMiscItems = (guild: TGuildDatabaseDocument, miscItems: ITypeCount[]): void => {
|
|
|
|
guild.VaultMiscItems ??= [];
|
|
|
|
for (const miscItem of miscItems) {
|
|
|
|
const vaultMiscItem = guild.VaultMiscItems.find(x => x.ItemType == miscItem.ItemType);
|
|
|
|
if (vaultMiscItem) {
|
|
|
|
vaultMiscItem.ItemCount += miscItem.ItemCount;
|
|
|
|
} else {
|
|
|
|
guild.VaultMiscItems.push(miscItem);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2025-03-31 04:14:00 -07:00
|
|
|
export const addGuildMemberMiscItemContribution = (guildMember: IGuildMemberDatabase, item: ITypeCount): void => {
|
|
|
|
guildMember.MiscItemsContributed ??= [];
|
|
|
|
const miscItemContribution = guildMember.MiscItemsContributed.find(x => x.ItemType == item.ItemType);
|
|
|
|
if (miscItemContribution) {
|
|
|
|
miscItemContribution.ItemCount += item.ItemCount;
|
|
|
|
} else {
|
|
|
|
guildMember.MiscItemsContributed.push(item);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2025-03-08 01:44:30 -08:00
|
|
|
export const processDojoBuildMaterialsGathered = (guild: TGuildDatabaseDocument, build: IDojoBuild): void => {
|
|
|
|
if (build.guildXpValue) {
|
|
|
|
guild.ClaimedXP ??= [];
|
|
|
|
if (!guild.ClaimedXP.find(x => x == build.resultType)) {
|
|
|
|
guild.ClaimedXP.push(build.resultType);
|
|
|
|
guild.XP += build.guildXpValue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2025-03-10 16:40:40 -07:00
|
|
|
|
2025-03-14 02:07:08 -07:00
|
|
|
// guild.save(); is expected some time after this function is called
|
|
|
|
export const setDojoRoomLogFunded = (guild: TGuildDatabaseDocument, component: IDojoComponentDatabase): void => {
|
|
|
|
const entry = guild.RoomChanges?.find(x => x.componentId.equals(component._id));
|
|
|
|
if (entry && entry.entryType == 2) {
|
|
|
|
entry.entryType = 0;
|
|
|
|
entry.dateTime = component.CompletionTime!;
|
|
|
|
component.CompletionLogPending = true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2025-03-10 16:40:40 -07:00
|
|
|
export const fillInInventoryDataForGuildMember = async (member: IGuildMemberClient): Promise<void> => {
|
|
|
|
const inventory = await getInventory(member._id.$oid, "PlayerLevel ActiveAvatarImageType");
|
|
|
|
member.PlayerLevel = config.spoofMasteryRank == -1 ? inventory.PlayerLevel : config.spoofMasteryRank;
|
|
|
|
member.ActiveAvatarImageType = inventory.ActiveAvatarImageType;
|
|
|
|
};
|
|
|
|
|
|
|
|
export const updateInventoryForConfirmedGuildJoin = async (
|
|
|
|
accountId: string,
|
|
|
|
guildId: Types.ObjectId
|
|
|
|
): Promise<void> => {
|
2025-03-29 18:01:15 +01:00
|
|
|
const inventory = await getInventory(accountId, "GuildId Recipes");
|
2025-03-10 16:40:40 -07:00
|
|
|
|
|
|
|
// Set GuildId
|
|
|
|
inventory.GuildId = guildId;
|
|
|
|
|
|
|
|
// Give clan key blueprint
|
|
|
|
addRecipes(inventory, [
|
|
|
|
{
|
|
|
|
ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint",
|
|
|
|
ItemCount: 1
|
|
|
|
}
|
|
|
|
]);
|
|
|
|
|
|
|
|
await inventory.save();
|
|
|
|
};
|
2025-03-11 10:31:56 -07:00
|
|
|
|
|
|
|
export const createUniqueClanName = async (name: string): Promise<string> => {
|
|
|
|
const initialDiscriminator = getRandomInt(0, 999);
|
|
|
|
let discriminator = initialDiscriminator;
|
|
|
|
do {
|
|
|
|
const fullName = name + "#" + discriminator.toString().padStart(3, "0");
|
|
|
|
if (!(await Guild.exists({ Name: fullName }))) {
|
|
|
|
return fullName;
|
|
|
|
}
|
|
|
|
discriminator = (discriminator + 1) % 1000;
|
|
|
|
} while (discriminator != initialDiscriminator);
|
|
|
|
throw new Error(`clan name is so unoriginal it's already been done 1000 times: ${name}`);
|
|
|
|
};
|
2025-03-14 07:09:28 -07:00
|
|
|
|
|
|
|
export const hasAccessToDojo = (inventory: TInventoryDatabaseDocument): boolean => {
|
|
|
|
return inventory.LevelKeys.find(x => x.ItemType == "/Lotus/Types/Keys/DojoKey") !== undefined;
|
|
|
|
};
|
|
|
|
|
|
|
|
export const hasGuildPermission = async (
|
|
|
|
guild: TGuildDatabaseDocument,
|
|
|
|
accountId: string | Types.ObjectId,
|
|
|
|
perm: GuildPermission
|
|
|
|
): Promise<boolean> => {
|
|
|
|
const member = await GuildMember.findOne({ accountId: accountId, guildId: guild._id });
|
|
|
|
if (member) {
|
|
|
|
return hasGuildPermissionEx(guild, member, perm);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
|
|
|
export const hasGuildPermissionEx = (
|
|
|
|
guild: TGuildDatabaseDocument,
|
|
|
|
member: IGuildMemberDatabase,
|
|
|
|
perm: GuildPermission
|
|
|
|
): boolean => {
|
|
|
|
const rank = guild.Ranks[member.rank];
|
|
|
|
return (rank.Permissions & perm) != 0;
|
|
|
|
};
|
2025-03-16 04:32:11 -07:00
|
|
|
|
|
|
|
export const removePigmentsFromGuildMembers = async (guildId: string | Types.ObjectId): Promise<void> => {
|
|
|
|
const members = await GuildMember.find({ guildId, status: 0 }, "accountId");
|
|
|
|
for (const member of members) {
|
|
|
|
const inventory = await getInventory(member.accountId.toString(), "MiscItems");
|
|
|
|
const index = inventory.MiscItems.findIndex(
|
|
|
|
x => x.ItemType == "/Lotus/Types/Items/Research/DojoColors/GenericDojoColorPigment"
|
|
|
|
);
|
|
|
|
if (index != -1) {
|
|
|
|
inventory.MiscItems.splice(index, 1);
|
|
|
|
await inventory.save();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2025-03-30 04:40:00 -07:00
|
|
|
|
2025-03-30 09:58:51 -07:00
|
|
|
export const processGuildTechProjectContributionsUpdate = async (
|
|
|
|
guild: TGuildDatabaseDocument,
|
|
|
|
techProject: ITechProjectDatabase
|
|
|
|
): Promise<void> => {
|
|
|
|
if (techProject.ReqCredits == 0 && !techProject.ReqItems.find(x => x.ItemCount > 0)) {
|
|
|
|
// This research is now fully funded.
|
|
|
|
|
|
|
|
if (
|
|
|
|
techProject.State == 0 &&
|
|
|
|
techProject.ItemType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/"
|
|
|
|
) {
|
|
|
|
guild.ActiveDojoColorResearch = "";
|
|
|
|
await removePigmentsFromGuildMembers(guild._id);
|
|
|
|
}
|
|
|
|
|
|
|
|
const recipe = ExportDojoRecipes.research[techProject.ItemType];
|
|
|
|
processFundedGuildTechProject(guild, techProject, recipe);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
export const processFundedGuildTechProject = (
|
|
|
|
guild: TGuildDatabaseDocument,
|
|
|
|
techProject: ITechProjectDatabase,
|
|
|
|
recipe: IDojoResearch
|
|
|
|
): void => {
|
|
|
|
techProject.State = 1;
|
|
|
|
techProject.CompletionDate = new Date(Date.now() + (config.noDojoResearchTime ? 0 : recipe.time) * 1000);
|
|
|
|
if (recipe.guildXpValue) {
|
|
|
|
guild.XP += recipe.guildXpValue;
|
|
|
|
}
|
|
|
|
setGuildTechLogState(guild, techProject.ItemType, config.noDojoResearchTime ? 4 : 3, techProject.CompletionDate);
|
|
|
|
};
|
|
|
|
|
|
|
|
export const setGuildTechLogState = (
|
|
|
|
guild: TGuildDatabaseDocument,
|
|
|
|
type: string,
|
|
|
|
state: number,
|
|
|
|
dateTime: Date | undefined = undefined
|
|
|
|
): boolean => {
|
|
|
|
guild.TechChanges ??= [];
|
|
|
|
const entry = guild.TechChanges.find(x => x.details == type);
|
|
|
|
if (entry) {
|
|
|
|
if (entry.entryType == state) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
entry.dateTime = dateTime;
|
|
|
|
entry.entryType = state;
|
|
|
|
} else {
|
|
|
|
guild.TechChanges.push({
|
|
|
|
dateTime: dateTime,
|
|
|
|
entryType: state,
|
|
|
|
details: type
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
|
|
|
const setGuildTier = async (guild: TGuildDatabaseDocument, newTier: number): Promise<void> => {
|
|
|
|
const oldTier = guild.Tier;
|
|
|
|
guild.Tier = newTier;
|
|
|
|
if (guild.TechProjects) {
|
|
|
|
for (const project of guild.TechProjects) {
|
|
|
|
if (project.State == 1) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const meta = ExportDojoRecipes.research[project.ItemType];
|
|
|
|
|
|
|
|
{
|
|
|
|
const numContributed = scaleRequiredCount(oldTier, meta.price) - project.ReqCredits;
|
|
|
|
project.ReqCredits = scaleRequiredCount(newTier, meta.price) - numContributed;
|
|
|
|
if (project.ReqCredits < 0) {
|
|
|
|
guild.VaultRegularCredits ??= 0;
|
|
|
|
guild.VaultRegularCredits += project.ReqCredits * -1;
|
|
|
|
project.ReqCredits = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let i = 0; i != project.ReqItems.length; ++i) {
|
|
|
|
const numContributed =
|
|
|
|
scaleRequiredCount(oldTier, meta.ingredients[i].ItemCount) - project.ReqItems[i].ItemCount;
|
|
|
|
project.ReqItems[i].ItemCount =
|
|
|
|
scaleRequiredCount(newTier, meta.ingredients[i].ItemCount) - numContributed;
|
|
|
|
if (project.ReqItems[i].ItemCount < 0) {
|
|
|
|
project.ReqItems[i].ItemCount *= -1;
|
|
|
|
addVaultMiscItems(guild, [project.ReqItems[i]]);
|
|
|
|
project.ReqItems[i].ItemCount = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if research is fully funded now due to lowered requirements.
|
|
|
|
await processGuildTechProjectContributionsUpdate(guild, project);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2025-03-30 04:40:00 -07:00
|
|
|
export const deleteGuild = async (guildId: Types.ObjectId): Promise<void> => {
|
|
|
|
await Guild.deleteOne({ _id: guildId });
|
|
|
|
await GuildMember.deleteMany({ guildId });
|
|
|
|
|
|
|
|
// If guild sent any invites, delete those inbox messages as well.
|
|
|
|
await Inbox.deleteMany({
|
|
|
|
contextInfo: guildId.toString(),
|
|
|
|
acceptAction: "GUILD_INVITE"
|
|
|
|
});
|
2025-03-31 04:14:00 -07:00
|
|
|
|
|
|
|
await GuildAd.deleteOne({ GuildId: guildId });
|
2025-03-30 04:40:00 -07:00
|
|
|
};
|