From 901df3a4d45eed2246722568ca3fdc57d4e99f35 Mon Sep 17 00:00:00 2001 From: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com> Date: Sat, 6 Sep 2025 01:28:00 +0200 Subject: [PATCH] feat(webui): guild view Also moves guild-specific cheats to a switch for each guild Closes #1403 --- config-vanilla.json | 7 - .../api/confirmGuildInvitationController.ts | 12 +- src/controllers/api/createGuildController.ts | 4 +- src/controllers/api/guildTechController.ts | 7 +- .../api/placeDecoInComponentController.ts | 3 +- ...queueDojoComponentDestructionController.ts | 3 +- .../api/startDojoRecipeController.ts | 3 +- .../custom/addCurrencyController.ts | 25 +- .../custom/addVaultDecoRecipeController.ts | 36 + .../custom/getAllianceController.ts | 16 + src/controllers/custom/getGuildController.ts | 39 + .../custom/getItemListsController.ts | 79 +- .../custom/setGuildCheatController.ts | 29 + .../custom/techProjectController.ts | 128 +++ src/models/guildModel.ts | 9 + src/routes/custom.ts | 18 + src/routes/webui.ts | 3 + src/services/configService.ts | 18 +- src/services/guildService.ts | 24 +- src/services/itemDataService.ts | 4 + src/types/guildTypes.ts | 12 +- static/fixed_responses/allDecoRecipes.json | 104 --- static/webui/index.html | 174 ++++- static/webui/script.js | 727 +++++++++++++++++- static/webui/translations/de.js | 34 +- static/webui/translations/en.js | 34 +- static/webui/translations/es.js | 34 +- static/webui/translations/fr.js | 34 +- static/webui/translations/ru.js | 34 +- static/webui/translations/uk.js | 34 +- static/webui/translations/zh.js | 34 +- 31 files changed, 1510 insertions(+), 212 deletions(-) create mode 100644 src/controllers/custom/addVaultDecoRecipeController.ts create mode 100644 src/controllers/custom/getAllianceController.ts create mode 100644 src/controllers/custom/getGuildController.ts create mode 100644 src/controllers/custom/setGuildCheatController.ts create mode 100644 src/controllers/custom/techProjectController.ts delete mode 100644 static/fixed_responses/allDecoRecipes.json diff --git a/config-vanilla.json b/config-vanilla.json index 8ac9c564..62ad4275 100644 --- a/config-vanilla.json +++ b/config-vanilla.json @@ -14,13 +14,6 @@ "unlockAllFlavourItems": false, "unlockAllSkins": false, "fullyStockedVendors": false, - "skipClanKeyCrafting": false, - "noDojoRoomBuildStage": false, - "noDojoDecoBuildStage": false, - "fastDojoRoomDestruction": false, - "noDojoResearchCosts": false, - "noDojoResearchTime": false, - "fastClanAscension": false, "spoofMasteryRank": -1, "relicRewardItemCountMultiplier": 1, "nightwaveStandingMultiplier": 1, diff --git a/src/controllers/api/confirmGuildInvitationController.ts b/src/controllers/api/confirmGuildInvitationController.ts index bfe30fb2..4c8d073c 100644 --- a/src/controllers/api/confirmGuildInvitationController.ts +++ b/src/controllers/api/confirmGuildInvitationController.ts @@ -47,10 +47,9 @@ export const confirmGuildInvitationGetController: RequestHandler = async (req, r // Update inventory of new member const inventory = await getInventory(account._id.toString(), "GuildId LevelKeys Recipes"); inventory.GuildId = new Types.ObjectId(req.query.clanId as string); - giveClanKey(inventory, inventoryChanges); - await inventory.save(); - const guild = (await Guild.findById(req.query.clanId as string))!; + giveClanKey(inventory, guild, inventoryChanges); + await inventory.save(); // Add join to clan log guild.RosterActivity ??= []; @@ -95,12 +94,9 @@ export const confirmGuildInvitationPostController: RequestHandler = async (req, await GuildMember.deleteMany({ accountId: guildMember.accountId, status: 1 }); // Update inventory of new member - const inventory = await getInventory( - guildMember.accountId.toString(), - "GuildId LevelKeys Recipes skipClanKeyCrafting" - ); + const inventory = await getInventory(guildMember.accountId.toString(), "GuildId LevelKeys Recipes"); inventory.GuildId = new Types.ObjectId(req.query.clanId as string); - giveClanKey(inventory); + giveClanKey(inventory, guild); await inventory.save(); // Add join to clan log diff --git a/src/controllers/api/createGuildController.ts b/src/controllers/api/createGuildController.ts index 239b3318..53a39e0a 100644 --- a/src/controllers/api/createGuildController.ts +++ b/src/controllers/api/createGuildController.ts @@ -27,10 +27,10 @@ export const createGuildController: RequestHandler = async (req, res) => { rank: 0 }); - const inventory = await getInventory(account._id.toString(), "GuildId LevelKeys Recipes skipClanKeyCrafting"); + const inventory = await getInventory(account._id.toString(), "GuildId LevelKeys Recipes"); inventory.GuildId = guild._id; const inventoryChanges: IInventoryChanges = {}; - giveClanKey(inventory, inventoryChanges); + giveClanKey(inventory, guild, inventoryChanges); await inventory.save(); res.json({ diff --git a/src/controllers/api/guildTechController.ts b/src/controllers/api/guildTechController.ts index 84bce3b2..eaebe702 100644 --- a/src/controllers/api/guildTechController.ts +++ b/src/controllers/api/guildTechController.ts @@ -28,7 +28,6 @@ import { import type { IMiscItem } from "../../types/inventoryTypes/inventoryTypes.ts"; import { InventorySlot } from "../../types/inventoryTypes/inventoryTypes.ts"; import type { IInventoryChanges } from "../../types/purchaseTypes.ts"; -import { config } from "../../services/configService.ts"; import type { ITechProjectClient } from "../../types/guildTypes.ts"; import { GuildPermission } from "../../types/guildTypes.ts"; import { GuildMember } from "../../models/guildModel.ts"; @@ -83,16 +82,16 @@ export const guildTechController: RequestHandler = async (req, res) => { guild.TechProjects[ guild.TechProjects.push({ ItemType: data.RecipeType, - ReqCredits: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, recipe.price), + ReqCredits: guild.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, recipe.price), ReqItems: recipe.ingredients.map(x => ({ ItemType: x.ItemType, - ItemCount: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, x.ItemCount) + ItemCount: guild.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, x.ItemCount) })), State: 0 }) - 1 ]; setGuildTechLogState(guild, techProject.ItemType, 5); - if (config.noDojoResearchCosts) { + if (guild.noDojoResearchCosts) { processFundedGuildTechProject(guild, techProject, recipe); } else { if (data.RecipeType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/") { diff --git a/src/controllers/api/placeDecoInComponentController.ts b/src/controllers/api/placeDecoInComponentController.ts index a56aae67..86167826 100644 --- a/src/controllers/api/placeDecoInComponentController.ts +++ b/src/controllers/api/placeDecoInComponentController.ts @@ -13,7 +13,6 @@ import { GuildPermission } from "../../types/guildTypes.ts"; import type { RequestHandler } from "express"; import { Types } from "mongoose"; import { ExportDojoRecipes, ExportResources } from "warframe-public-export-plus"; -import { config } from "../../services/configService.ts"; export const placeDecoInComponentController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); @@ -74,7 +73,7 @@ export const placeDecoInComponentController: RequestHandler = async (req, res) = } } if (deco.Type != "/Lotus/Objects/Tenno/Props/TnoPaintBotDojoDeco") { - if (!meta || (meta.price == 0 && meta.ingredients.length == 0) || config.noDojoDecoBuildStage) { + if (!meta || (meta.price == 0 && meta.ingredients.length == 0) || guild.noDojoDecoBuildStage) { deco.CompletionTime = new Date(); if (meta) { processDojoBuildMaterialsGathered(guild, meta); diff --git a/src/controllers/api/queueDojoComponentDestructionController.ts b/src/controllers/api/queueDojoComponentDestructionController.ts index 98cddd70..413612c5 100644 --- a/src/controllers/api/queueDojoComponentDestructionController.ts +++ b/src/controllers/api/queueDojoComponentDestructionController.ts @@ -1,4 +1,3 @@ -import { config } from "../../services/configService.ts"; import { getDojoClient, getGuildForRequestEx, @@ -21,7 +20,7 @@ export const queueDojoComponentDestructionController: RequestHandler = async (re const componentId = req.query.componentId as string; guild.DojoComponents.id(componentId)!.DestructionTime = new Date( - (Math.trunc(Date.now() / 1000) + (config.fastDojoRoomDestruction ? 5 : 2 * 3600)) * 1000 + (Math.trunc(Date.now() / 1000) + (guild.fastDojoRoomDestruction ? 5 : 2 * 3600)) * 1000 ); await guild.save(); diff --git a/src/controllers/api/startDojoRecipeController.ts b/src/controllers/api/startDojoRecipeController.ts index 69b9e58e..bfca8b08 100644 --- a/src/controllers/api/startDojoRecipeController.ts +++ b/src/controllers/api/startDojoRecipeController.ts @@ -11,7 +11,6 @@ import { } from "../../services/guildService.ts"; import { Types } from "mongoose"; import { ExportDojoRecipes } from "warframe-public-export-plus"; -import { config } from "../../services/configService.ts"; import { getAccountForRequest } from "../../services/loginService.ts"; import { getInventory } from "../../services/inventoryService.ts"; @@ -57,7 +56,7 @@ export const startDojoRecipeController: RequestHandler = async (req, res) => { DecoCapacity: room?.decoCapacity }) - 1 ]; - if (config.noDojoRoomBuildStage) { + if (guild.noDojoRoomBuildStage) { component.CompletionTime = new Date(Date.now()); if (room) { processDojoBuildMaterialsGathered(guild, room); diff --git a/src/controllers/custom/addCurrencyController.ts b/src/controllers/custom/addCurrencyController.ts index dd7f472b..3df52a64 100644 --- a/src/controllers/custom/addCurrencyController.ts +++ b/src/controllers/custom/addCurrencyController.ts @@ -1,21 +1,40 @@ import type { RequestHandler } from "express"; import { getAccountIdForRequest } from "../../services/loginService.ts"; import { addFusionPoints, getInventory } from "../../services/inventoryService.ts"; +import { getGuildForRequestEx, hasGuildPermission } from "../../services/guildService.ts"; +import { GuildPermission } from "../../types/guildTypes.ts"; export const addCurrencyController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); const request = req.body as IAddCurrencyRequest; - const inventory = await getInventory(accountId, request.currency); + let projection = request.currency as string; + if (request.currency.startsWith("Vault")) projection = "GuildId"; + const inventory = await getInventory(accountId, projection); if (request.currency == "FusionPoints") { addFusionPoints(inventory, request.delta); + } else if (request.currency == "VaultRegularCredits" || request.currency == "VaultPremiumCredits") { + const guild = await getGuildForRequestEx(req, inventory); + if (await hasGuildPermission(guild, accountId, GuildPermission.Treasurer)) { + guild[request.currency] ??= 0; + guild[request.currency]! += request.delta; + await guild.save(); + } } else { inventory[request.currency] += request.delta; } - await inventory.save(); + if (!request.currency.startsWith("Vault")) { + await inventory.save(); + } res.end(); }; interface IAddCurrencyRequest { - currency: "RegularCredits" | "PremiumCredits" | "FusionPoints" | "PrimeTokens"; + currency: + | "RegularCredits" + | "PremiumCredits" + | "FusionPoints" + | "PrimeTokens" + | "VaultRegularCredits" + | "VaultPremiumCredits"; delta: number; } diff --git a/src/controllers/custom/addVaultDecoRecipeController.ts b/src/controllers/custom/addVaultDecoRecipeController.ts new file mode 100644 index 00000000..437e555e --- /dev/null +++ b/src/controllers/custom/addVaultDecoRecipeController.ts @@ -0,0 +1,36 @@ +import { getAccountIdForRequest } from "../../services/loginService.ts"; +import { getInventory } from "../../services/inventoryService.ts"; +import type { RequestHandler } from "express"; +import { hasAccessToDojo, getGuildForRequestEx, hasGuildPermission } from "../../services/guildService.ts"; +import { GuildPermission } from "../../types/guildTypes.ts"; +import type { ITypeCount } from "../../types/commonTypes.ts"; + +export const addVaultDecoRecipeController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const requests = req.body as ITypeCount[]; + const inventory = await getInventory(accountId, "LevelKeys GuildId"); + const guild = await getGuildForRequestEx(req, inventory); + if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Architect))) { + res.status(400).send("-1").end(); + return; + } + guild.VaultDecoRecipes ??= []; + for (const request of requests) { + const index = guild.VaultDecoRecipes.findIndex(x => x.ItemType === request.ItemType); + + if (index == -1) { + guild.VaultDecoRecipes.push({ + ItemType: request.ItemType, + ItemCount: request.ItemCount + }); + } else { + guild.VaultDecoRecipes[index].ItemCount += request.ItemCount; + + if (guild.VaultDecoRecipes[index].ItemCount < 1) { + guild.VaultDecoRecipes.splice(index, 1); + } + } + } + await guild.save(); + res.end(); +}; diff --git a/src/controllers/custom/getAllianceController.ts b/src/controllers/custom/getAllianceController.ts new file mode 100644 index 00000000..5ea0e38b --- /dev/null +++ b/src/controllers/custom/getAllianceController.ts @@ -0,0 +1,16 @@ +import { Alliance, Guild } from "../../models/guildModel.ts"; +import { getAllianceClient } from "../../services/guildService.ts"; +import type { RequestHandler } from "express"; + +export const getAllianceController: RequestHandler = async (req, res) => { + const guildId = req.query.guildId; + if (guildId) { + const guild = (await Guild.findById(guildId, "Name Tier AllianceId"))!; + if (guild.AllianceId) { + const alliance = (await Alliance.findById(guild.AllianceId))!; + res.json(await getAllianceClient(alliance, guild)); + return; + } + } + res.end(); +}; diff --git a/src/controllers/custom/getGuildController.ts b/src/controllers/custom/getGuildController.ts new file mode 100644 index 00000000..18df338f --- /dev/null +++ b/src/controllers/custom/getGuildController.ts @@ -0,0 +1,39 @@ +import type { RequestHandler } from "express"; +import { Guild, GuildMember } from "../../models/guildModel.ts"; +import { toMongoDate, toOid2 } from "../../helpers/inventoryHelpers.ts"; +import { addAccountDataToFriendInfo, addInventoryDataToFriendInfo } from "../../services/friendService.ts"; +import type { IGuildMemberClient } from "../../types/guildTypes.ts"; + +export const getGuildController: RequestHandler = async (req, res) => { + const guildId = req.query.guildId; + if (guildId) { + const guild = await Guild.findById(guildId); + if (guild) { + const guildMembers = await GuildMember.find({ guildId: guild._id }); + + const members: IGuildMemberClient[] = []; + const dataFillInPromises: Promise[] = []; + for (const guildMember of guildMembers) { + const member: IGuildMemberClient = { + _id: toOid2(guildMember.accountId, undefined), + Rank: guildMember.rank, + Status: guildMember.status, + Note: guildMember.RequestMsg, + RequestExpiry: guildMember.RequestExpiry ? toMongoDate(guildMember.RequestExpiry) : undefined + }; + dataFillInPromises.push(addAccountDataToFriendInfo(member)); + dataFillInPromises.push(addInventoryDataToFriendInfo(member)); + + members.push(member); + } + + await Promise.all(dataFillInPromises); + + res.json({ + ...guild.toObject(), + Members: members + }); + } + } + res.end(); +}; diff --git a/src/controllers/custom/getItemListsController.ts b/src/controllers/custom/getItemListsController.ts index dc2d6c4f..bfe46310 100644 --- a/src/controllers/custom/getItemListsController.ts +++ b/src/controllers/custom/getItemListsController.ts @@ -7,6 +7,7 @@ import { ExportAvionics, ExportBoosters, ExportCustoms, + ExportDojoRecipes, ExportDrones, ExportGear, ExportKeys, @@ -59,6 +60,8 @@ interface ItemLists { Boosters: ListedItem[]; VarziaOffers: ListedItem[]; Abilities: ListedItem[]; + TechProjects: ListedItem[]; + VaultDecoRecipes: ListedItem[]; //circuitGameModes: ListedItem[]; } @@ -97,7 +100,9 @@ const getItemListsController: RequestHandler = (req, response) => { mods: [], Boosters: [], VarziaOffers: [], - Abilities: [] + Abilities: [], + TechProjects: [], + VaultDecoRecipes: [] /*circuitGameModes: [ { uniqueName: "Survival", @@ -367,6 +372,78 @@ const getItemListsController: RequestHandler = (req, response) => { }); } + for (const uniqueName of Object.keys(ExportDojoRecipes.research)) { + if ( + !["Zekti", "Vidar", "Lavan"].some(house => uniqueName.includes(house)) && + !uniqueName.startsWith("/Lotus/Types/Items/ShipFeatureItems/Railjack/") + ) { + let resultType; + if (uniqueName.startsWith("/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/")) { + resultType = ExportDojoRecipes.decos[uniqueName].resultType; + } else if (uniqueName.startsWith("/Lotus/Types/Game/")) { + resultType = uniqueName.replace("Blueprint", ""); + } else if (uniqueName.startsWith("/Lotus/Types/Items/Research/Dojo")) { + resultType = uniqueName; + } else if (uniqueName.startsWith("/Lotus/Types/Recipes/Railjack/")) { + resultType = ExportDojoRecipes.fabrications[uniqueName].resultType; + } else { + resultType = ExportRecipes[uniqueName].resultType; + } + + let name = getString(getItemName(resultType) || resultType, lang); + + if ( + ![ + "/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/", + "/Lotus/Types/Game/", + "/Lotus/Types/Items/Research/Dojo", + "/Lotus/Types/Recipes/Railjack/" + ].some(p => uniqueName.startsWith(p)) + ) { + const recipeNum = ExportRecipes[uniqueName].num; + if (recipeNum > 1) { + name = `${name} X ${recipeNum}`; + } + } + + res.TechProjects.push({ + uniqueName, + name + }); + } + } + + for (const uniqueName of [ + ...Object.keys(ExportDojoRecipes.decos).filter( + name => name.includes("Trophy") || name.includes("NpcPlaceables") + ), + "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ThumperTrophyBronzeRecipe", + "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ThumperTrophyCrystalRecipe", + "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ThumperTrophyGoldRecipe", + "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ThumperTrophySilverRecipe", + "/Lotus/Levels/ClanDojo/ComponentPropRecipes/CorpusPlaceables/GasTurbineConeRecipe", + "/Lotus/Levels/ClanDojo/ComponentPropRecipes/NaturalPlaceables/CoralChunkARecipe", + "/Lotus/Levels/ClanDojo/ComponentPropRecipes/TennoPlaceables/TnoBeaconEmitterRecipe", + "/Lotus/Levels/ClanDojo/ComponentPropRecipes/OrokinMusicBoxRecipe" + ]) { + let resultType; + + if (uniqueName.startsWith("/Lotus/Levels/ClanDojo/ComponentPropRecipes/ThumperTrophy")) { + resultType = uniqueName + .replace("/Lotus/Levels/ClanDojo/ComponentPropRecipes/", "/Lotus/Objects/Festivities/") + .slice(0, -6); + } else if (uniqueName === "/Lotus/Levels/ClanDojo/ComponentPropRecipes/NaturalPlaceables/CoralChunkARecipe") { + resultType = "/Lotus/Objects/Tenno/Props/NaturalPlaceables/CoralChunkA"; + } else { + resultType = ExportDojoRecipes.decos[uniqueName].resultType; + } + + res.VaultDecoRecipes.push({ + uniqueName, + name: getString(getItemName(resultType) || resultType, lang) + }); + } + response.json(res); }; diff --git a/src/controllers/custom/setGuildCheatController.ts b/src/controllers/custom/setGuildCheatController.ts new file mode 100644 index 00000000..6a5d8631 --- /dev/null +++ b/src/controllers/custom/setGuildCheatController.ts @@ -0,0 +1,29 @@ +import { GuildMember } from "../../models/guildModel.ts"; +import { getGuildForRequestEx, hasAccessToDojo } from "../../services/guildService.ts"; +import { getInventory } from "../../services/inventoryService.ts"; +import { getAccountIdForRequest } from "../../services/loginService.ts"; +import type { IGuildCheats } from "../../types/guildTypes.ts"; +import type { RequestHandler } from "express"; + +export const setGuildCheatController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const payload = req.body as ISetGuildCheatRequest; + const inventory = await getInventory(accountId, `${payload.key} GuildId LevelKeys`); + const guild = await getGuildForRequestEx(req, inventory); + const member = await GuildMember.findOne({ accountId: accountId, guildId: guild._id }); + + if (member) { + if (!hasAccessToDojo(inventory) || member.rank > 1) { + res.end(); + return; + } + guild[payload.key] = payload.value; + await guild.save(); + } + res.end(); +}; + +interface ISetGuildCheatRequest { + key: keyof IGuildCheats; + value: boolean; +} diff --git a/src/controllers/custom/techProjectController.ts b/src/controllers/custom/techProjectController.ts new file mode 100644 index 00000000..38965fd0 --- /dev/null +++ b/src/controllers/custom/techProjectController.ts @@ -0,0 +1,128 @@ +import { getAccountIdForRequest } from "../../services/loginService.ts"; +import { getInventory } from "../../services/inventoryService.ts"; +import type { RequestHandler } from "express"; +import { + hasAccessToDojo, + getGuildForRequestEx, + setGuildTechLogState, + processFundedGuildTechProject, + scaleRequiredCount, + hasGuildPermission, + addGuildMemberMiscItemContribution, + processGuildTechProjectContributionsUpdate, + processCompletedGuildTechProject +} from "../../services/guildService.ts"; +import { ExportDojoRecipes } from "warframe-public-export-plus"; +import { GuildPermission } from "../../types/guildTypes.ts"; +import { GuildMember } from "../../models/guildModel.ts"; + +export const addTechProjectController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const requests = req.body as ITechProjectRequest[]; + const inventory = await getInventory(accountId, "LevelKeys GuildId"); + const guild = await getGuildForRequestEx(req, inventory); + if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) { + res.status(400).send("-1").end(); + return; + } + guild.TechProjects ??= []; + for (const request of requests) { + const recipe = ExportDojoRecipes.research[request.ItemType]; + if (!guild.TechProjects.find(x => x.ItemType == request.ItemType)) { + const techProject = + guild.TechProjects[ + guild.TechProjects.push({ + ItemType: request.ItemType, + ReqCredits: guild.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, recipe.price), + ReqItems: recipe.ingredients.map(x => ({ + ItemType: x.ItemType, + ItemCount: guild.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, x.ItemCount) + })), + State: 0 + }) - 1 + ]; + setGuildTechLogState(guild, techProject.ItemType, 5); + if (guild.noDojoResearchCosts) { + processFundedGuildTechProject(guild, techProject, recipe); + } + } + } + await guild.save(); + res.end(); +}; + +export const removeTechProjectController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const requests = req.body as ITechProjectRequest[]; + const inventory = await getInventory(accountId, "LevelKeys GuildId"); + const guild = await getGuildForRequestEx(req, inventory); + if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) { + res.status(400).send("-1").end(); + return; + } + guild.TechProjects ??= []; + for (const request of requests) { + const index = guild.TechProjects.findIndex(x => x.ItemType === request.ItemType); + if (index !== -1) { + guild.TechProjects.splice(index, 1); + } + } + await guild.save(); + res.end(); +}; + +export const fundTechProjectController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const requests = req.body as ITechProjectRequest[]; + const inventory = await getInventory(accountId, "LevelKeys GuildId"); + const guild = await getGuildForRequestEx(req, inventory); + const guildMember = (await GuildMember.findOne( + { accountId, guildId: guild._id }, + "RegularCreditsContributed MiscItemsContributed" + ))!; + if (!hasAccessToDojo(inventory)) { + res.status(400).send("-1").end(); + return; + } + for (const request of requests) { + const techProject = guild.TechProjects!.find(x => x.ItemType == request.ItemType)!; + + guildMember.RegularCreditsContributed ??= 0; + guildMember.RegularCreditsContributed += techProject.ReqCredits; + techProject.ReqCredits = 0; + + for (const reqItem of techProject.ReqItems) { + addGuildMemberMiscItemContribution(guildMember, reqItem); + reqItem.ItemCount = 0; + } + + await processGuildTechProjectContributionsUpdate(guild, techProject); + } + await Promise.all([guild.save(), guildMember.save()]); + res.end(); +}; + +export const completeTechProjectsController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const requests = req.body as ITechProjectRequest[]; + const inventory = await getInventory(accountId, "LevelKeys GuildId"); + const guild = await getGuildForRequestEx(req, inventory); + if (!hasAccessToDojo(inventory)) { + res.status(400).send("-1").end(); + return; + } + for (const request of requests) { + const techProject = guild.TechProjects!.find(x => x.ItemType == request.ItemType)!; + techProject.CompletionDate = new Date(); + + if (setGuildTechLogState(guild, techProject.ItemType, 4, techProject.CompletionDate)) { + processCompletedGuildTechProject(guild, techProject.ItemType); + } + } + await guild.save(); + res.end(); +}; + +interface ITechProjectRequest { + ItemType: string; +} diff --git a/src/models/guildModel.ts b/src/models/guildModel.ts index e7d53297..743b61a5 100644 --- a/src/models/guildModel.ts +++ b/src/models/guildModel.ts @@ -201,6 +201,15 @@ goalProgressSchema.set("toJSON", { const guildSchema = new Schema( { + // SNS guild cheats + skipClanKeyCrafting: Boolean, + noDojoRoomBuildStage: Boolean, + noDojoDecoBuildStage: Boolean, + fastDojoRoomDestruction: Boolean, + noDojoResearchCosts: Boolean, + noDojoResearchTime: Boolean, + fastClanAscension: Boolean, + Name: { type: String, required: true, unique: true }, MOTD: { type: String, default: "" }, LongMOTD: { type: longMOTDSchema, default: undefined }, diff --git a/src/routes/custom.ts b/src/routes/custom.ts index 525fbe93..ce0082db 100644 --- a/src/routes/custom.ts +++ b/src/routes/custom.ts @@ -7,6 +7,8 @@ import { popArchonCrystalUpgradeController } from "../controllers/custom/popArch import { deleteAccountController } from "../controllers/custom/deleteAccountController.ts"; import { getNameController } from "../controllers/custom/getNameController.ts"; import { getAccountInfoController } from "../controllers/custom/getAccountInfoController.ts"; +import { getGuildController } from "../controllers/custom/getGuildController.ts"; +import { getAllianceController } from "../controllers/custom/getAllianceController.ts"; import { renameAccountController } from "../controllers/custom/renameAccountController.ts"; import { ircDroppedController } from "../controllers/custom/ircDroppedController.ts"; import { unlockAllIntrinsicsController } from "../controllers/custom/unlockAllIntrinsicsController.ts"; @@ -25,6 +27,13 @@ import { createAccountController } from "../controllers/custom/createAccountCont import { createMessageController } from "../controllers/custom/createMessageController.ts"; import { addCurrencyController } from "../controllers/custom/addCurrencyController.ts"; import { addItemsController } from "../controllers/custom/addItemsController.ts"; +import { + addTechProjectController, + completeTechProjectsController, + fundTechProjectController, + removeTechProjectController +} from "../controllers/custom/techProjectController.ts"; +import { addVaultDecoRecipeController } from "../controllers/custom/addVaultDecoRecipeController.ts"; import { addXpController } from "../controllers/custom/addXpController.ts"; import { importController } from "../controllers/custom/importController.ts"; import { manageQuestsController } from "../controllers/custom/manageQuestsController.ts"; @@ -34,6 +43,7 @@ import { updateFingerprintController } from "../controllers/custom/updateFingerp import { changeModularPartsController } from "../controllers/custom/changeModularPartsController.ts"; import { editSuitInvigorationUpgradeController } from "../controllers/custom/editSuitInvigorationUpgradeController.ts"; import { setAccountCheatController } from "../controllers/custom/setAccountCheatController.ts"; +import { setGuildCheatController } from "../controllers/custom/setGuildCheatController.ts"; import { getConfigController, setConfigController } from "../controllers/custom/configController.ts"; @@ -46,6 +56,8 @@ customRouter.get("/popArchonCrystalUpgrade", popArchonCrystalUpgradeController); customRouter.get("/deleteAccount", deleteAccountController); customRouter.get("/getName", getNameController); customRouter.get("/getAccountInfo", getAccountInfoController); +customRouter.get("/getGuild", getGuildController); +customRouter.get("/getAlliance", getAllianceController); customRouter.get("/renameAccount", renameAccountController); customRouter.get("/ircDropped", ircDroppedController); customRouter.get("/unlockAllIntrinsics", unlockAllIntrinsicsController); @@ -64,6 +76,11 @@ customRouter.post("/createAccount", createAccountController); customRouter.post("/createMessage", createMessageController); customRouter.post("/addCurrency", addCurrencyController); customRouter.post("/addItems", addItemsController); +customRouter.post("/addTechProject", addTechProjectController); +customRouter.post("/removeTechProject", removeTechProjectController); +customRouter.post("/addVaultDecoRecipe", addVaultDecoRecipeController); +customRouter.post("/fundTechProject", fundTechProjectController); +customRouter.post("/completeTechProject", completeTechProjectsController); customRouter.post("/addXp", addXpController); customRouter.post("/import", importController); customRouter.post("/manageQuests", manageQuestsController); @@ -73,6 +90,7 @@ customRouter.post("/updateFingerprint", updateFingerprintController); customRouter.post("/changeModularParts", changeModularPartsController); customRouter.post("/editSuitInvigorationUpgrade", editSuitInvigorationUpgradeController); customRouter.post("/setAccountCheat", setAccountCheatController); +customRouter.post("/setGuildCheat", setGuildCheatController); customRouter.post("/getConfig", getConfigController); customRouter.post("/setConfig", setConfigController); diff --git a/src/routes/webui.ts b/src/routes/webui.ts index b31b2746..6b6386ed 100644 --- a/src/routes/webui.ts +++ b/src/routes/webui.ts @@ -42,6 +42,9 @@ webuiRouter.get("/webui/cheats", (_req, res) => { webuiRouter.get("/webui/import", (_req, res) => { res.sendFile(path.join(baseDir, "static/webui/index.html")); }); +webuiRouter.get("/webui/guildView", (_req, res) => { + res.sendFile(path.join(baseDir, "static/webui/index.html")); +}); // Serve static files webuiRouter.use("/webui", express.static(path.join(baseDir, "static/webui"))); diff --git a/src/services/configService.ts b/src/services/configService.ts index 2c7c9e01..8c68df43 100644 --- a/src/services/configService.ts +++ b/src/services/configService.ts @@ -23,15 +23,7 @@ export interface IConfig { unlockAllShipDecorations?: boolean; unlockAllFlavourItems?: boolean; unlockAllSkins?: boolean; - unlockAllDecoRecipes?: boolean; fullyStockedVendors?: boolean; - skipClanKeyCrafting?: boolean; - noDojoRoomBuildStage?: boolean; - noDojoDecoBuildStage?: boolean; - fastDojoRoomDestruction?: boolean; - noDojoResearchCosts?: boolean; - noDojoResearchTime?: boolean; - fastClanAscension?: boolean; spoofMasteryRank?: number; relicRewardItemCountMultiplier?: number; nightwaveStandingMultiplier?: number; @@ -127,7 +119,15 @@ export const configRemovedOptionsKeys = [ "exceptionalRelicsAlwaysGiveBronzeReward", "flawlessRelicsAlwaysGiveSilverReward", "radiantRelicsAlwaysGiveGoldReward", - "disableDailyTribute" + "disableDailyTribute", + "skipClanKeyCrafting", + "noDojoRoomBuildStage", + "noDojoDecoBuildStage", + "fastDojoRoomDestruction", + "noDojoResearchCosts", + "noDojoResearchTime", + "fastClanAscension", + "unlockAllDecoRecipes" ]; export const configPath = path.join(repoDir, args.configPath ?? "config.json"); diff --git a/src/services/guildService.ts b/src/services/guildService.ts index 630f675a..8b676aa9 100644 --- a/src/services/guildService.ts +++ b/src/services/guildService.ts @@ -27,13 +27,11 @@ import type { Types } from "mongoose"; import type { IDojoBuild, IDojoResearch } from "warframe-public-export-plus"; import { ExportDojoRecipes, ExportResources } from "warframe-public-export-plus"; import { logger } from "../utils/logger.ts"; -import { config } from "./configService.ts"; import { getRandomInt } from "./rngService.ts"; import { Inbox } from "../models/inboxModel.ts"; import type { IFusionTreasure } from "../types/inventoryTypes/inventoryTypes.ts"; import type { IInventoryChanges } from "../types/purchaseTypes.ts"; import { parallelForeach } from "../utils/async-utils.ts"; -import allDecoRecipes from "../../static/fixed_responses/allDecoRecipes.json" with { type: "json" }; import { createMessage } from "./inboxService.ts"; import { addAccountDataToFriendInfo, addInventoryDataToFriendInfo } from "./friendService.ts"; import type { ITypeCount } from "../types/commonTypes.ts"; @@ -136,9 +134,7 @@ export const getGuildVault = (guild: TGuildDatabaseDocument): IGuildVault => { DojoRefundPremiumCredits: guild.VaultPremiumCredits, ShipDecorations: guild.VaultShipDecorations, FusionTreasures: guild.VaultFusionTreasures, - DecoRecipes: config.unlockAllDecoRecipes - ? allDecoRecipes.map(recipe => ({ ItemType: recipe, ItemCount: 1 })) - : guild.VaultDecoRecipes + DecoRecipes: guild.VaultDecoRecipes }; }; @@ -565,12 +561,12 @@ export const processFundedGuildTechProject = ( recipe: IDojoResearch ): void => { techProject.State = 1; - techProject.CompletionDate = new Date(Date.now() + (config.noDojoResearchTime ? 0 : recipe.time) * 1000); + techProject.CompletionDate = new Date(Date.now() + (guild.noDojoResearchTime ? 0 : recipe.time) * 1000); if (recipe.guildXpValue) { guild.XP += recipe.guildXpValue; } - setGuildTechLogState(guild, techProject.ItemType, config.noDojoResearchTime ? 4 : 3, techProject.CompletionDate); - if (config.noDojoResearchTime) { + setGuildTechLogState(guild, techProject.ItemType, guild.noDojoResearchTime ? 4 : 3, techProject.CompletionDate); + if (guild.noDojoResearchTime) { processCompletedGuildTechProject(guild, techProject.ItemType); } }; @@ -657,8 +653,8 @@ export const checkClanAscensionHasRequiredContributors = async (guild: TGuildDat if (guild.CeremonyContributors!.length >= requiredContributors) { guild.Class = guild.CeremonyClass!; guild.CeremonyClass = undefined; - guild.CeremonyResetDate = new Date(Date.now() + (config.fastClanAscension ? 5_000 : 72 * 3600_000)); - if (!config.fastClanAscension) { + guild.CeremonyResetDate = new Date(Date.now() + (guild.fastClanAscension ? 5_000 : 72 * 3600_000)); + if (!guild.fastClanAscension) { // Send message to all active guild members const members = await GuildMember.find({ guildId: guild._id, status: 0 }, "accountId"); await parallelForeach(members, async member => { @@ -692,8 +688,12 @@ export const checkClanAscensionHasRequiredContributors = async (guild: TGuildDat } }; -export const giveClanKey = (inventory: TInventoryDatabaseDocument, inventoryChanges?: IInventoryChanges): void => { - if (config.skipClanKeyCrafting) { +export const giveClanKey = ( + inventory: TInventoryDatabaseDocument, + guild: TGuildDatabaseDocument, + inventoryChanges?: IInventoryChanges +): void => { + if (guild.skipClanKeyCrafting) { const levelKeyChanges = [ { ItemType: "/Lotus/Types/Keys/DojoKey", diff --git a/src/services/itemDataService.ts b/src/services/itemDataService.ts index bb11b611..cc8b6bcb 100644 --- a/src/services/itemDataService.ts +++ b/src/services/itemDataService.ts @@ -30,6 +30,7 @@ import { ExportDrones, ExportGear, ExportKeys, + ExportRailjackWeapons, ExportRecipes, ExportResources, ExportSentinels, @@ -149,6 +150,9 @@ export const getItemName = (uniqueName: string): string | undefined => { if (uniqueName in ExportWeapons) { return ExportWeapons[uniqueName].name; } + if (uniqueName in ExportRailjackWeapons) { + return ExportRailjackWeapons[uniqueName].name; + } return undefined; }; diff --git a/src/types/guildTypes.ts b/src/types/guildTypes.ts index 366e661c..d9746e33 100644 --- a/src/types/guildTypes.ts +++ b/src/types/guildTypes.ts @@ -31,8 +31,18 @@ export interface IGuildClient { GoalProgress?: IGoalProgressClient[]; } +// Fields specific to SNS +export interface IGuildCheats { + skipClanKeyCrafting?: boolean; + noDojoRoomBuildStage?: boolean; + noDojoDecoBuildStage?: boolean; + fastDojoRoomDestruction?: boolean; + noDojoResearchCosts?: boolean; + noDojoResearchTime?: boolean; + fastClanAscension?: boolean; +} -export interface IGuildDatabase { +export interface IGuildDatabase extends IGuildCheats { _id: Types.ObjectId; Name: string; MOTD: string; diff --git a/static/fixed_responses/allDecoRecipes.json b/static/fixed_responses/allDecoRecipes.json deleted file mode 100644 index 471f7ae3..00000000 --- a/static/fixed_responses/allDecoRecipes.json +++ /dev/null @@ -1,104 +0,0 @@ -[ - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/AmbulasEventBronzeTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/AmbulasEventGoldTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/AmbulasEventSilverTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/AmbulasEventTerracottaTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/AridFearBronzeTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/AridFearGoldTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/AridFearSilverTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/BreedingGroundsBronzeTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/BreedingGroundsGoldTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/BreedingGroundsSilverTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/CiceroCrisisBronzeTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/CiceroCrisisGoldTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/CiceroCrisisSilverTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/DisruptionEventBronzeTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/DisruptionEventGoldTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/DisruptionEventSilverTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/DisruptionEventTerracottaTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/DojoRemasterTrophyBronzeARecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/DojoRemasterTrophyGoldARecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/DojoRemasterTrophyPlatinumARecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/DojoRemasterTrophySilverARecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/DuviriMurmurEventBronzeTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/DuviriMurmurEventClayTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/DuviriMurmurEventGoldTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/DuviriMurmurEventSilverTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/EntratiEventBaseTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/EntratiEventBronzeTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/EntratiEventGoldTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/EntratiEventSilverTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/EntratiEventTerracottaTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/EvacuationEventBronzeTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/EvacuationEventGoldTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/EvacuationEventSilverTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/EvacuationEventTerracottaTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/EyesOfBlightTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/FalseProfitBronzeTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/FalseProfitClayTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/FalseProfitGoldTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/FalseProfitSilverTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/FusionMoaTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/GradivusDilemmaCorpusBronzeTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/GradivusDilemmaCorpusGoldTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/GradivusDilemmaCorpusSilverTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/GradivusDilemmaGrineerBronzeTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/GradivusDilemmaGrineerGoldTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/GradivusDilemmaGrineerSilverTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/IcePlanetTrophyBronzeRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/IcePlanetTrophyGoldRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/IcePlanetTrophySilverRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/JadeShadowsEventBaseTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/JadeShadowsEventBronzeTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/JadeShadowsEventGoldTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/JadeShadowsEventPewterTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/JadeShadowsEventSilverTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/MechEventTrophyBronzeRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/MechEventTrophyGoldRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/MechEventTrophySilverRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/MechEventTrophyTerracottaRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/MutalistIncursionBronzeTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/MutalistIncursionGoldTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/MutalistIncursionSilverTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/OrokinMusicBoxRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/OrokinSabotageTrophyBronzeRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/OrokinSabotageTrophyGoldRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/OrokinSabotageTrophySilverRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ProjectSinisterBronzeTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ProjectSinisterClayTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ProjectSinisterGoldTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ProjectSinisterSilverTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/RailjackResearchTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/RathuumBronzeTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/RathuumClayTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/RathuumGoldTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/RathuumSilverTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ShipyardsEventBronzeTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ShipyardsEventGoldTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ShipyardsEventSilverTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/SlingStoneTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/SpyDroneTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/SurvivalEventBronzeTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/SurvivalEventGoldTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/SurvivalEventSilverTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/TennoConDojoGhostTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/TennoConDojoMoonTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/TennoConDojoMountainTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/TennoConDojoShadowTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/TennoConDojoStormTrophyRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ThumperTrophyBronzeRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ThumperTrophyCrystalRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ThumperTrophyGoldRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/ThumperTrophySilverRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/CorpusPlaceables/GasTurbineConeRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/NaturalPlaceables/CoralChunkARecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/TennoPlaceables/TnoBeaconEmitterRecipe", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/OstronFemaleSitting", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/OstronFemaleStanding", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/OstronMaleStanding", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/OstronMaleStandingTwo", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/SolarisForeman", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/SolarisHazard", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/SolarisStrikerOne", - "/Lotus/Levels/ClanDojo/ComponentPropRecipes/NpcPlaceables/SolarisStrikerThree" -] diff --git a/static/webui/index.html b/static/webui/index.html index 0c78f010..9375ecd4 100644 --- a/static/webui/index.html +++ b/static/webui/index.html @@ -59,6 +59,9 @@ + @@ -480,6 +483,143 @@ +
+

+

+

+

+

+
+
+
+
+
+

+
+ + +
+
+
+
+
+
+
+
+

+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + +
+ + +
+
+
+
+
+
+
+
+
+ + +
+ + +
+
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ + +
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+

@@ -855,42 +995,10 @@
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
@@ -1277,6 +1385,8 @@ + + diff --git a/static/webui/script.js b/static/webui/script.js index b4b7f32e..b262a56e 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -151,7 +151,7 @@ function doLogout() { function renameAccount(taken_name) { const newname = window.prompt( (taken_name ? loc("code_changeNameRetry").split("|NAME|").join(taken_name) + " " : "") + - loc("code_changeNameConfirm") + loc("code_changeNameConfirm") ); if (newname) { revalidateAuthz().then(() => { @@ -256,6 +256,9 @@ function setLanguage(lang) { // Not in prelogin state? fetchItemList(); updateInventory(); + if (single.getCurrentPath().startsWith("/webui/guildView")) { + updateInventory(); + } } } @@ -490,6 +493,33 @@ function fetchItemList() { name: data.ModularParts.find( i => i.uniqueName === "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadC" ).name + }, + "/Lotus/Language/Game/Rank_Creator": { + name: loc("guildView_rank_creator") + }, + "/Lotus/Language/Game/Rank_Warlord": { + name: loc("guildView_rank_warlord") + }, + "/Lotus/Language/Game/Rank_General": { + name: loc("guildView_rank_general") + }, + "/Lotus/Language/Game/Rank_Officer": { + name: loc("guildView_rank_officer") + }, + "/Lotus/Language/Game/Rank_Leader": { + name: loc("guildView_rank_leader") + }, + "/Lotus/Language/Game/Rank_Sage": { + name: loc("guildView_rank_sage") + }, + "/Lotus/Language/Game/Rank_Soldier": { + name: loc("guildView_rank_soldier") + }, + "/Lotus/Language/Game/Rank_Initiate": { + name: loc("guildView_rank_initiate") + }, + "/Lotus/Language/Game/Rank_Utility": { + name: loc("guildView_rank_utility") } }; for (const [type, items] of Object.entries(data)) { @@ -623,6 +653,7 @@ function updateInventory() { req.done(data => { window.itemListPromise.then(itemMap => { window.didInitialInventoryUpdate = true; + if (data.GuildId.$oid) window.guildId = data.GuildId.$oid; const modularWeapons = [ "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary", @@ -935,7 +966,7 @@ function updateInventory() { if (!data.QuestKeys.some(x => x.ItemType == questKey.uniqueName)) { const datalist = document.getElementById("datalist-QuestKeys"); if (!datalist.querySelector(`option[data-key="${questKey.uniqueName}"]`)) { - readdQuestKey(itemMap, questKey.uniqueName); + reAddToItemList(itemMap, "QuestKeys", questKey.uniqueName); } } }); @@ -1032,7 +1063,7 @@ function updateInventory() { a.href = "#"; a.onclick = function (event) { event.preventDefault(); - readdQuestKey(itemMap, item.ItemType); + reAddToItemList(itemMap, "QuestKeys", item.ItemType); doQuestUpdate("deleteKey", item.ItemType); }; a.title = loc("code_remove"); @@ -1502,6 +1533,307 @@ function updateInventory() { document.getElementById("Boosters-list").appendChild(tr); }); + if (single.getCurrentPath().startsWith("/webui/guildView")) { + const guildReq = $.get("/custom/getGuild?guildId=" + window.guildId); + guildReq.done(guildData => { + window.itemListPromise.then(itemMap => { + document.getElementById("guildView-loading").classList.add("d-none"); + + document.getElementById("guildView-title").textContent = guildData.Name; + document.getElementById("guildView-tier").textContent = loc("guildView_tierDisplay") + .split("|TIER|") + .join(loc(`guildView_tier${guildData.Tier}`)); + document.getElementById("guildView-class").textContent = loc("guildView_classDisplay") + .split("|CLASS|") + .join(guildData.Class); + + ["VaultRegularCredits", "VaultPremiumCredits"].forEach(currency => { + document.getElementById(currency + "-owned").textContent = loc("guildView_currency_owned") + .split("|COUNT|") + .join((guildData[currency] ?? 0).toLocaleString()); + }); + + const userGuildMember = guildData.Members.find(m => m._id.$oid === window.accountId); + let userGuildPermissions; + if (userGuildMember) { + userGuildPermissions = guildData.Ranks[userGuildMember.Rank].Permissions; + // Ruler = 1, // Clan: Change hierarchy. Alliance (Creator only): Kick clans. + // Advertiser = 8192, + // Recruiter = 2, // Send invites (Clans & Alliances) + // Regulator = 4, // Kick members + // Promoter = 8, // Clan: Promote and demote members. Alliance (Creator only): Change clan permissions. + // Architect = 16, // Create and destroy rooms + // Host = 32, // No longer used in modern versions + // Decorator = 1024, // Create and destroy decos + // Treasurer = 64, // Clan: Contribute from vault and edit tax rate. Alliance: Divvy vault. + // Tech = 128, // Queue research + // ChatModerator = 512, // (Clans & Alliances) + // Herald = 2048, // Change MOTD + // Fabricator = 4096 // Replicate research + if (userGuildPermissions & 128) { + document.getElementById("techProjects-form").classList.remove("d-none"); + } + if (userGuildPermissions & 16) { + document.getElementById("vaultDecoRecipes-form").classList.remove("d-none"); + } + if (userGuildPermissions & 64) { + document.getElementById("vaultRegularCredits-form").classList.remove("d-none"); + document.getElementById("vaultPremiumCredits-form").classList.remove("d-none"); + } + if (userGuildMember.Rank <= 1) { + document.querySelectorAll("#guild-actions button").forEach(btn => { + btn.disabled = false; + }); + } + } + + const guildCheats = document.querySelectorAll("#guild-cheats input[id]"); + for (const elm of guildCheats) { + elm.checked = !!guildData[elm.id]; + if (!userGuildMember || userGuildMember.Rank > 1) { + elm.disabled = true; + } else { + elm.disabled = false; + } + } + + document.getElementById("TechProjects-list").innerHTML = ""; + guildData.TechProjects ??= []; + guildData.TechProjects.forEach(item => { + const datalist = document.getElementById("datalist-TechProjects"); + const optionToRemove = datalist.querySelector(`option[data-key="${item.ItemType}"]`); + if (optionToRemove) { + datalist.removeChild(optionToRemove); + } + const tr = document.createElement("tr"); + tr.setAttribute("data-item-type", item.ItemType); + { + const td = document.createElement("td"); + td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType; + if (new Date(item.CompletionDate) < new Date()) { + td.textContent += " | " + loc("code_completed"); + } else if (item.State == 1) { + td.textContent += " | " + loc("code_funded"); + } + tr.appendChild(td); + } + { + const td = document.createElement("td"); + td.classList = "text-end text-nowrap"; + + if (userGuildPermissions && userGuildPermissions & 128 && item.State != 1) { + const a = document.createElement("a"); + a.href = "#"; + a.onclick = function (event) { + event.preventDefault(); + fundGuildTechProject(item.ItemType); + }; + a.title = loc("code_fund"); + a.innerHTML = ``; + td.appendChild(a); + } + + if ( + userGuildPermissions && + userGuildPermissions & 128 && + item.State == 1 && + new Date(item.CompletionDate) > new Date() + ) { + const a = document.createElement("a"); + a.href = "#"; + a.onclick = function (event) { + event.preventDefault(); + completeGuildTechProject(item.ItemType); + }; + a.title = loc("code_complete"); + a.innerHTML = ``; + td.appendChild(a); + } + + if (userGuildMember && userGuildMember.Rank <= 1) { + const a = document.createElement("a"); + a.href = "#"; + a.onclick = function (event) { + event.preventDefault(); + reAddToItemList(itemMap, "TechProjects", item.ItemType); + removeGuildTechProject(item.ItemType); + }; + a.title = loc("code_remove"); + a.innerHTML = ``; + td.appendChild(a); + } + + tr.appendChild(td); + } + + document.getElementById("TechProjects-list").appendChild(tr); + }); + + document.getElementById("VaultDecoRecipes-list").innerHTML = ""; + guildData.VaultDecoRecipes ??= []; + guildData.VaultDecoRecipes.forEach(item => { + const datalist = document.getElementById("datalist-VaultDecoRecipes"); + const optionToRemove = datalist.querySelector(`option[data-key="${item.ItemType}"]`); + if (optionToRemove) { + datalist.removeChild(optionToRemove); + } + const tr = document.createElement("tr"); + tr.setAttribute("data-item-type", item.ItemType); + { + const td = document.createElement("td"); + td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType; + tr.appendChild(td); + } + { + const td = document.createElement("td"); + td.classList = "text-end text-nowrap"; + + if (userGuildMember && userGuildMember.Rank <= 1) { + const a = document.createElement("a"); + a.href = "#"; + a.onclick = function (event) { + event.preventDefault(); + reAddToItemList(itemMap, "VaultDecoRecipes", item.ItemType); + removeVaultDecoRecipe(item.ItemType); + }; + a.title = loc("code_remove"); + a.innerHTML = ``; + td.appendChild(a); + } + + tr.appendChild(td); + } + + document.getElementById("VaultDecoRecipes-list").appendChild(tr); + }); + + document.getElementById("Members-list").innerHTML = ""; + guildData.Members.forEach(member => { + const tr = document.createElement("tr"); + { + const td = document.createElement("td"); + const memberRank = guildData.Ranks[member.Rank]; + td.textContent = member.DisplayName; + td.textContent += " | " + itemMap[memberRank.Name]?.name ?? memberRank.Name; + if (member.Status != 0) { + td.textContent += " | " + loc("guildView_pending"); + } + tr.appendChild(td); + } + { + const td = document.createElement("td"); + td.classList = "text-end text-nowrap"; + + if ( + userGuildMember && + member.Rank < 8 && + member.Rank > userGuildMember.Rank && + userGuildPermissions && + userGuildPermissions & 8 + ) { + const a = document.createElement("a"); + a.href = "#"; + a.onclick = function (event) { + event.preventDefault(); + changeGuildRank(guildId, member._id.$oid, member.Rank + 1); + }; + a.title = loc("code_rankDown"); + a.innerHTML = ``; + td.appendChild(a); + } + if ( + userGuildMember && + member.Rank > userGuildMember.Rank && + userGuildPermissions && + userGuildPermissions & 8 + ) { + const a = document.createElement("a"); + a.href = "#"; + a.onclick = function (event) { + event.preventDefault(); + changeGuildRank(guildId, member._id.$oid, member.Rank - 1); + }; + a.title = loc("code_rankUp"); + a.innerHTML = ``; + td.appendChild(a); + } + + if ( + (userGuildMember && + member.Rank > userGuildMember.Rank && + userGuildPermissions && + userGuildPermissions & 4) || + (userGuildMember && userGuildMember.Rank != 0 && userGuildMember._id == member._id) + ) { + const a = document.createElement("a"); + a.href = "#"; + a.onclick = function (event) { + event.preventDefault(); + kickFromGuild(member._id.$oid); + }; + a.title = loc("code_remove"); + a.innerHTML = ``; + td.appendChild(a); + } + + tr.appendChild(td); + } + + document.getElementById("Members-list").appendChild(tr); + }); + + if (guildData.AllianceId) { + const allianceReq = $.get("/custom/getAlliance?guildId=" + guildId); + allianceReq.done(allianceData => { + document.getElementById("guildView-alliance").textContent = + loc("guildView_alliance") + ": " + allianceData.Name; + + let userAlliancePermisssions; + if (userGuildMember && userGuildMember.Rank <= 1) { + userAlliancePermisssions = allianceData.Clans.find(c => c._id.$oid === guildId).Permissions; + } + document.getElementById("Alliance-list").innerHTML = ""; + allianceData.Clans.forEach(clan => { + const tr = document.createElement("tr"); + { + const td = document.createElement("td"); + td.textContent = clan.Name; + if (clan.Pending) { + td.textContent += " | " + loc("guildView_pending"); + } + tr.appendChild(td); + } + { + const td = document.createElement("td"); + td.classList = "text-end text-nowrap"; + + if (!(clan.Permissions & 1) && userAlliancePermisssions && userAlliancePermisssions & 1) { + const a = document.createElement("a"); + a.href = "#"; + a.onclick = function (event) { + event.preventDefault(); + kickFromAlliance(clan._id.$oid); + }; + a.title = loc("code_remove"); + a.innerHTML = ``; + td.appendChild(a); + } + + tr.appendChild(td); + } + + document.getElementById("Alliance-list").appendChild(tr); + }); + }); + } + }); + }); + + guildReq.fail(() => { + single.loadRoute("/webui/inventory"); + }); + } + for (const elm of accountCheats) { elm.checked = !!data[elm.id]; } @@ -1509,6 +1841,74 @@ function updateInventory() { }); } +function addVaultDecoRecipe() { + const uniqueName = getKey(document.getElementById("acquire-type-VaultDecoRecipes")); + if (!guildId) { + return; + } + if (!uniqueName) { + $("acquire-type-VaultDecoRecipes").addClass("is-invalid").focus(); + return; + } + revalidateAuthz().then(() => { + const req = $.post({ + url: "/custom/addVaultDecoRecipe?" + window.authz + "&guildId=" + window.guildId, + contentType: "application/json", + data: JSON.stringify([ + { + ItemType: uniqueName, + ItemCount: 1 + } + ]) + }); + req.done(() => { + updateInventory(); + }); + }); +} + +function changeGuildRank(guildId, targetId, rankChange) { + revalidateAuthz().then(() => { + const req = $.get( + "/api/changeGuildRank.php?" + + window.authz + + "&guildId=" + + guildId + + "&targetId=" + + targetId + + "&rankChange=" + + rankChange + ); + req.done(() => { + updateInventory(); + }); + }); +} + +function kickFromGuild(accountId) { + revalidateAuthz().then(() => { + const req = $.post({ + url: "/api/removeFromGuild.php?" + window.authz + "&guildId=" + window.guildId, + contentType: "application/octet-stream", + data: JSON.stringify({ + userId: accountId + }) + }); + req.done(() => { + updateInventory(); + }); + }); +} + +function kickFromAlliance(guildId) { + revalidateAuthz().then(() => { + const req = $.get("/api/removeFromAlliance.php?" + window.authz + "&guildId=" + guildId); + req.done(() => { + updateInventory(); + }); + }); +} + function getKey(input) { return document .getElementById(input.getAttribute("list")) @@ -1733,6 +2133,262 @@ function addMissingEquipment(categories) { } } +function addVaultDecoRecipe() { + const uniqueName = getKey(document.getElementById("acquire-type-VaultDecoRecipes")); + if (!uniqueName) { + $("#acquire-type-VaultDecoRecipes").addClass("is-invalid").focus(); + return; + } + revalidateAuthz().then(() => { + const req = $.post({ + url: "/custom/addVaultDecoRecipe?" + window.authz + "&guildId=" + window.guildId, + contentType: "application/json", + data: JSON.stringify([ + { + ItemType: uniqueName, + ItemCount: 1 + } + ]) + }); + req.done(() => { + document.getElementById("acquire-type-VaultDecoRecipes").value = ""; + updateInventory(); + }); + }); +} + +function removeVaultDecoRecipe(uniqueName) { + revalidateAuthz().then(() => { + const req = $.post({ + url: "/custom/addVaultDecoRecipe?" + window.authz + "&guildId=" + window.guildId, + contentType: "application/json", + data: JSON.stringify([ + { + ItemType: uniqueName, + ItemCount: -1 + } + ]) + }); + req.done(() => { + updateInventory(); + }); + }); +} + +function addGuildTechProject() { + const uniqueName = getKey(document.getElementById("acquire-type-TechProjects")); + if (!uniqueName) { + $("#acquire-type-TechProjects").addClass("is-invalid").focus(); + return; + } + revalidateAuthz().then(() => { + const req = $.post({ + url: "/custom/addTechProject?" + window.authz + "&guildId=" + window.guildId, + contentType: "application/json", + data: JSON.stringify([ + { + ItemType: uniqueName + } + ]) + }); + req.done(() => { + document.getElementById("acquire-type-TechProjects").value = ""; + updateInventory(); + }); + }); +} + +function removeGuildTechProject(uniqueName) { + revalidateAuthz().then(() => { + const req = $.post({ + url: "/custom/removeTechProject?" + window.authz + "&guildId=" + window.guildId, + contentType: "application/json", + data: JSON.stringify([ + { + ItemType: uniqueName + } + ]) + }); + req.done(() => { + updateInventory(); + }); + }); +} + +function completeGuildTechProject(uniqueName) { + revalidateAuthz().then(() => { + const req = $.post({ + url: "/custom/completeTechProject?" + window.authz + "&guildId=" + window.guildId, + contentType: "application/json", + data: JSON.stringify([ + { + ItemType: uniqueName + } + ]) + }); + req.done(() => { + updateInventory(); + }); + }); +} + +function fundGuildTechProject(uniqueName) { + revalidateAuthz().then(() => { + const req = $.post({ + url: "/custom/fundTechProject?" + window.authz + "&guildId=" + window.guildId, + contentType: "application/json", + data: JSON.stringify([ + { + ItemType: uniqueName + } + ]) + }); + req.done(() => { + updateInventory(); + }); + }); +} + +function dispatchAddVaultDecoRecipesBatch(requests) { + return new Promise(resolve => { + revalidateAuthz().then(() => { + const req = $.post({ + url: "/custom/addVaultDecoRecipe?" + window.authz + "&guildId=" + window.guildId, + contentType: "application/json", + data: JSON.stringify(requests) + }); + req.done(() => { + updateInventory(); + resolve(); + }); + }); + }); +} + +function addMissingVaultDecoRecipes() { + const requests = []; + + document.querySelectorAll("#datalist-VaultDecoRecipes" + " option").forEach(elm => { + if (!document.querySelector("#VaultDecoRecipes-list [data-item-type='" + elm.getAttribute("data-key") + "']")) { + requests.push({ ItemType: elm.getAttribute("data-key"), ItemCount: 1 }); + } + }); + + if ( + requests.length != 0 && + window.confirm(loc("code_addDecoRecipesConfirm").split("|COUNT|").join(requests.length)) + ) { + return dispatchAddVaultDecoRecipesBatch(requests); + } +} + +function dispatchAddTechProjectsBatch(requests) { + return new Promise(resolve => { + revalidateAuthz().then(() => { + const req = $.post({ + url: "/custom/addTechProject?" + window.authz + "&guildId=" + window.guildId, + contentType: "application/json", + data: JSON.stringify(requests) + }); + req.done(() => { + updateInventory(); + resolve(); + }); + }); + }); +} + +function addMissingTechProjects() { + const requests = []; + + document.querySelectorAll("#datalist-TechProjects option").forEach(elm => { + if (!document.querySelector("#TechProjects-list [data-item-type='" + elm.getAttribute("data-key") + "']")) { + requests.push({ ItemType: elm.getAttribute("data-key") }); + } + }); + + if ( + requests.length != 0 && + window.confirm(loc("code_addTechProjectsConfirm").split("|COUNT|").join(requests.length)) + ) { + return dispatchAddTechProjectsBatch(requests); + } +} + +function dispatchFundTechProjectsBatch(requests) { + return new Promise(resolve => { + revalidateAuthz().then(() => { + const req = $.post({ + url: "/custom/fundTechProject?" + window.authz + "&guildId=" + window.guildId, + contentType: "application/json", + data: JSON.stringify(requests) + }); + req.done(() => { + updateInventory(); + resolve(); + }); + }); + }); +} + +function fundAllTechProjects() { + revalidateAuthz().then(() => { + const req = $.get("/custom/getGuild?guildId=" + window.guildId); + req.done(data => { + const requests = []; + data.TechProjects ??= []; + data.TechProjects.forEach(techProject => { + if (techProject.State != 1) { + requests.push({ + ItemType: techProject.ItemType + }); + } + }); + + if (Object.keys(requests).length > 0) { + return dispatchFundTechProjectsBatch(requests); + } + }); + }); +} + +function dispatchCompleteTechProjectsBatch(requests) { + return new Promise(resolve => { + revalidateAuthz().then(() => { + const req = $.post({ + url: "/custom/completeTechProject?" + window.authz + "&guildId=" + window.guildId, + contentType: "application/json", + data: JSON.stringify(requests) + }); + req.done(() => { + updateInventory(); + resolve(); + }); + }); + }); +} + +function completeAllTechProjects() { + revalidateAuthz().then(() => { + const req = $.get("/custom/getGuild?guildId=" + window.guildId); + req.done(data => { + const requests = []; + data.TechProjects ??= []; + data.TechProjects.forEach(techProject => { + if (techProject.State == 1 && new Date(techProject.CompletionDate) > new Date()) { + requests.push({ + ItemType: techProject.ItemType + }); + } + }); + + if (Object.keys(requests).length > 0) { + return dispatchCompleteTechProjectsBatch(requests); + } + }); + }); +} + async function addMissingHelminthRecipes() { await revalidateAuthz(); await fetch("/custom/addMissingHelminthBlueprints?" + window.authz); @@ -1784,9 +2440,9 @@ function maxRankAllEquipment(categories) { data[category].forEach(item => { const maxXP = category === "Suits" || - category === "SpaceSuits" || - category === "Sentinels" || - category === "Hoverboards" + category === "SpaceSuits" || + category === "Sentinels" || + category === "Hoverboards" ? 1_600_000 : 800_000; @@ -2382,6 +3038,21 @@ document.querySelectorAll("#account-cheats input[type=checkbox]").forEach(elm => }; }); +document.querySelectorAll("#guild-cheats input[type=checkbox]").forEach(elm => { + elm.onchange = function () { + revalidateAuthz().then(() => { + $.post({ + url: "/custom/setGuildCheat?" + window.authz + "&guildId=" + window.guildId, + contentType: "application/json", + data: JSON.stringify({ + key: elm.id, + value: elm.checked + }) + }); + }); + }; +}); + // Mods route function doAddAllMods() { @@ -2472,6 +3143,30 @@ single.getRoute("#detailedView-route").on("beforeload", function () { } }); +single.getRoute("#guild-route").on("beforeload", function () { + document.getElementById("guildView-loading").classList.remove("d-none"); + document.getElementById("guildView-title").textContent = ""; + document.getElementById("guildView-tier").textContent = ""; + document.getElementById("guildView-class").textContent = ""; + document.getElementById("vaultRegularCredits-form").classList.add("d-none"); + document.getElementById("vaultPremiumCredits-form").classList.add("d-none"); + document.getElementById("TechProjects-list").innerHTML = ""; + document.getElementById("techProjects-form").classList.add("d-none"); + document.getElementById("acquire-type-TechProjects").value = ""; + document.getElementById("VaultDecoRecipes-list").innerHTML = ""; + document.getElementById("vaultDecoRecipes-form").classList.add("d-none"); + document.getElementById("acquire-type-VaultDecoRecipes").value = ""; + document.getElementById("Alliance-list").innerHTML = ""; + document.getElementById("guildView-alliance").textContent = ""; + document.getElementById("Members-list").innerHTML = ""; + document.querySelectorAll("#guild-actions button").forEach(btn => { + btn.disabled = true; + }); + if (window.didInitialInventoryUpdate) { + updateInventory(); + } +}); + function doPushArchonCrystalUpgrade() { const urlParams = new URLSearchParams(window.location.search); const uniqueName = getKey(document.querySelector("[list='datalist-archonCrystalUpgrades']")); @@ -2482,13 +3177,13 @@ function doPushArchonCrystalUpgrade() { revalidateAuthz().then(() => { $.get( "/custom/pushArchonCrystalUpgrade?" + - window.authz + - "&oid=" + - urlParams.get("itemId") + - "&type=" + - uniqueName + - "&count=" + - $("#archon-crystal-add-count").val() + window.authz + + "&oid=" + + urlParams.get("itemId") + + "&type=" + + uniqueName + + "&count=" + + $("#archon-crystal-add-count").val() ).done(function () { $("[list='datalist-archonCrystalUpgrades']").val(""); updateInventory(); @@ -2535,7 +3230,7 @@ function doChangeSupportedSyndicate() { function doAddCurrency(currency) { revalidateAuthz().then(() => { $.post({ - url: "/custom/addCurrency?" + window.authz, + url: "/custom/addCurrency?" + window.authz + "&guildId=" + window.guildId, contentType: "application/json", data: JSON.stringify({ currency, @@ -2547,11 +3242,11 @@ function doAddCurrency(currency) { }); } -function readdQuestKey(itemMap, itemType) { +function reAddToItemList(itemMap, datalist, itemType) { const option = document.createElement("option"); option.setAttribute("data-key", itemType); option.value = itemMap[itemType]?.name ?? itemType; - document.getElementById("datalist-QuestKeys").appendChild(option); + document.getElementById("datalist-" + datalist).appendChild(option); } function doQuestUpdate(operation, itemType) { diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index 0952b5be..2b893f3a 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -31,6 +31,8 @@ dict = { code_renamePrompt: `Neuen benutzerdefinierten Namen eingeben:`, code_remove: `Entfernen`, code_addItemsConfirm: `Bist du sicher, dass du |COUNT| Gegenstände zu deinem Account hinzufügen möchtest?`, + code_addTechProjectsConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| research to your clan?`, + code_addDecoRecipesConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| deco recipes to your clan?`, code_succRankUp: `Erfolgreich aufgestiegen.`, code_noEquipmentToRankUp: `Keine Ausstattung zum Rangaufstieg verfügbar.`, code_succAdded: `Erfolgreich hinzugefügt.`, @@ -63,6 +65,8 @@ dict = { code_pigment: `Pigment`, code_mature: `Für den Kampf auswachsen lassen`, code_unmature: `Genetisches Altern zurücksetzen`, + code_fund: `[UNTRANSLATED] Fund`, + code_funded: `[UNTRANSLATED] Funded`, code_succChange: `Erfolgreich geändert.`, code_requiredInvigorationUpgrade: `Du musst sowohl ein offensives & defensives Upgrade auswählen.`, login_description: `Melde dich mit deinem OpenWF-Account an (denselben Angaben wie im Spiel, wenn du dich mit diesem Server verbindest).`, @@ -74,6 +78,7 @@ dict = { navbar_renameAccount: `Account umbenennen`, navbar_deleteAccount: `Account löschen`, navbar_inventory: `Inventar`, + navbar_guildView: `Clan`, navbar_mods: `Mods`, navbar_quests: `Quests`, navbar_cheats: `Cheats`, @@ -196,7 +201,6 @@ dict = { cheats_unlockAllFlavourItems: `Alle Sammlerstücke freischalten`, cheats_unlockAllSkins: `Alle Skins freischalten`, cheats_unlockAllCapturaScenes: `Alle Photora-Szenen freischalten`, - cheats_unlockAllDecoRecipes: `Alle Dojo-Deko-Baupläne freischalten`, cheats_universalPolarityEverywhere: `Universelle Polarität überall`, cheats_unlockDoubleCapacityPotatoesEverywhere: `Orokin Reaktor & Beschleuniger überall`, cheats_unlockExilusEverywhere: `Exilus-Adapter überall`, @@ -387,5 +391,33 @@ dict = { theme_dark: `Dunkles Design`, theme_light: `Helles Design`, + guildView_cheats: `[UNTRANSLATED] Clan Cheats`, + guildView_techProjects: `Forschung`, + guildView_vaultDecoRecipes: `[UNTRANSLATED] Dojo Deco Recipes`, + guildView_alliance: `Allianz`, + guildView_members: `Mitglieder`, + guildView_pending: `Ausstehend`, + guildView_classDisplay: `Rang |CLASS|`, + guildView_tierDisplay: `|TIER| Clan`, + guildView_tier1: `Geist`, + guildView_tier2: `Schatten`, + guildView_tier3: `Sturm`, + guildView_tier4: `Berg`, + guildView_tier5: `Mond`, + guildView_rank_creator: `Gründer Kriegsherr`, + guildView_rank_general: `General`, + guildView_rank_initiate: `Initiant`, + guildView_rank_leader: `Anführer`, + guildView_rank_officer: `Offizier`, + guildView_rank_sage: `Weiser`, + guildView_rank_soldier: `Soldat`, + guildView_rank_utility: `Versorger`, + guildView_rank_warlord: `Kriegsherr`, + guildView_currency_owned: `[UNTRANSLATED] |COUNT| in Vault.`, + guildView_bulkAddTechProjects: `[UNTRANSLATED] Add Missing Research`, + guildView_bulkAddVaultDecoRecipes: `[UNTRANSLATED] Add Missing Dojo Deco Recipes`, + guildView_bulkFundTechProjects: `[UNTRANSLATED] Fund All Research`, + guildView_bulkCompleteTechProjects: `[UNTRANSLATED] Complete All Research`, + prettier_sucks_ass: `` }; diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index d82f523a..06bcf146 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -30,6 +30,8 @@ dict = { code_renamePrompt: `Enter new custom name:`, code_remove: `Remove`, code_addItemsConfirm: `Are you sure you want to add |COUNT| items to your account?`, + code_addTechProjectsConfirm: `Are you sure you want to add |COUNT| research to your clan?`, + code_addDecoRecipesConfirm: `Are you sure you want to add |COUNT| deco recipes to your clan?`, code_succRankUp: `Successfully ranked up.`, code_noEquipmentToRankUp: `No equipment to rank up.`, code_succAdded: `Successfully added.`, @@ -62,6 +64,8 @@ dict = { code_pigment: `Pigment`, code_mature: `Mature for combat`, code_unmature: `Regress genetic aging`, + code_fund: `Fund`, + code_funded: `Funded`, code_succChange: `Successfully changed.`, code_requiredInvigorationUpgrade: `You must select both an offensive & defensive upgrade.`, login_description: `Login using your OpenWF account credentials (same as in-game when connecting to this server).`, @@ -73,6 +77,7 @@ dict = { navbar_renameAccount: `Rename Account`, navbar_deleteAccount: `Delete Account`, navbar_inventory: `Inventory`, + navbar_guildView: `Clan`, navbar_mods: `Mods`, navbar_quests: `Quests`, navbar_cheats: `Cheats`, @@ -195,7 +200,6 @@ dict = { cheats_unlockAllFlavourItems: `Unlock All Flavor Items`, cheats_unlockAllSkins: `Unlock All Skins`, cheats_unlockAllCapturaScenes: `Unlock All Captura Scenes`, - cheats_unlockAllDecoRecipes: `Unlock All Dojo Deco Recipes`, cheats_universalPolarityEverywhere: `Universal Polarity Everywhere`, cheats_unlockDoubleCapacityPotatoesEverywhere: `Potatoes Everywhere`, cheats_unlockExilusEverywhere: `Exilus Adapters Everywhere`, @@ -386,5 +390,33 @@ dict = { theme_dark: `Dark Theme`, theme_light: `Light Theme`, + guildView_cheats: `Clan Cheats`, + guildView_techProjects: `Research`, + guildView_vaultDecoRecipes: `Dojo Deco Recipes`, + guildView_alliance: `Alliance`, + guildView_members: `Members`, + guildView_pending: `Pending`, + guildView_classDisplay: `Rank |CLASS|`, + guildView_tierDisplay: `|TIER| Clan`, + guildView_tier1: `Ghost`, + guildView_tier2: `Shadow`, + guildView_tier3: `Storm`, + guildView_tier4: `Mountain`, + guildView_tier5: `Moon`, + guildView_rank_creator: `Founding Warlord`, + guildView_rank_general: `General`, + guildView_rank_initiate: `Initiate`, + guildView_rank_leader: `Leader`, + guildView_rank_officer: `Officer`, + guildView_rank_sage: `Sage`, + guildView_rank_soldier: `Soldier`, + guildView_rank_utility: `Utility`, + guildView_rank_warlord: `Warlord`, + guildView_currency_owned: `|COUNT| in Vault.`, + guildView_bulkAddTechProjects: `Add Missing Research`, + guildView_bulkAddVaultDecoRecipes: `Add Missing Dojo Deco Recipes`, + guildView_bulkFundTechProjects: `Fund All Research`, + guildView_bulkCompleteTechProjects: `Complete All Research`, + prettier_sucks_ass: `` }; diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index e5aee5ce..e04a4bd4 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -31,6 +31,8 @@ dict = { code_renamePrompt: `Escribe tu nuevo nombre personalizado:`, code_remove: `Quitar`, code_addItemsConfirm: `¿Estás seguro de que deseas agregar |COUNT| objetos a tu cuenta?`, + code_addTechProjectsConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| research to your clan?`, + code_addDecoRecipesConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| deco recipes to your clan?`, code_succRankUp: `Ascenso exitoso.`, code_noEquipmentToRankUp: `No hay equipo para ascender.`, code_succAdded: `Agregado exitosamente.`, @@ -63,6 +65,8 @@ dict = { code_pigment: `Pigmento`, code_mature: `Listo para el combate`, code_unmature: `Regresar el envejecimiento genético`, + code_fund: `[UNTRANSLATED] Fund`, + code_funded: `[UNTRANSLATED] Funded`, code_succChange: `Cambiado correctamente`, code_requiredInvigorationUpgrade: `Debes seleccionar una mejora ofensiva y una defensiva.`, login_description: `Inicia sesión con las credenciales de tu cuenta OpenWF (las mismas que usas en el juego al conectarte a este servidor).`, @@ -74,6 +78,7 @@ dict = { navbar_renameAccount: `Renombrar cuenta`, navbar_deleteAccount: `Eliminar cuenta`, navbar_inventory: `Inventario`, + navbar_guildView: `Clan`, navbar_mods: `Mods`, navbar_quests: `Misiones`, navbar_cheats: `Trucos`, @@ -196,7 +201,6 @@ dict = { cheats_unlockAllFlavourItems: `Desbloquear todos los ítems estéticos`, cheats_unlockAllSkins: `Desbloquear todas las skins`, cheats_unlockAllCapturaScenes: `Desbloquear todas las escenas de Captura`, - cheats_unlockAllDecoRecipes: `Desbloquear todas las recetas decorativas del dojo`, cheats_universalPolarityEverywhere: `Polaridad universal en todas partes`, cheats_unlockDoubleCapacityPotatoesEverywhere: `Patatas en todas partes`, cheats_unlockExilusEverywhere: `Adaptadores Exilus en todas partes`, @@ -387,5 +391,33 @@ dict = { theme_dark: `Tema Oscuro`, theme_light: `Tema Claro`, + guildView_cheats: `[UNTRANSLATED] Clan Cheats`, + guildView_techProjects: `Investigación`, + guildView_vaultDecoRecipes: `[UNTRANSLATED] Dojo Deco Recipes`, + guildView_alliance: `Alianza`, + guildView_members: `Miembros`, + guildView_pending: `Pendiente`, + guildView_classDisplay: `Rango |CLASS|`, + guildView_tierDisplay: `Clan |TIER|`, + guildView_tier1: `Fantasma`, + guildView_tier2: `Sombra`, + guildView_tier3: `Tormenta`, + guildView_tier4: `Montaña`, + guildView_tier5: `Luna`, + guildView_rank_creator: `Señor de la guerra fundador`, + guildView_rank_general: `General`, + guildView_rank_initiate: `Iniciado`, + guildView_rank_leader: `Líder`, + guildView_rank_officer: `Oficial`, + guildView_rank_sage: `Sabio`, + guildView_rank_soldier: `Soldado`, + guildView_rank_utility: `Utilitario`, + guildView_rank_warlord: `Señor de la guerra`, + guildView_currency_owned: `[UNTRANSLATED] |COUNT| in Vault.`, + guildView_bulkAddTechProjects: `[UNTRANSLATED] Add Missing Research`, + guildView_bulkAddVaultDecoRecipes: `[UNTRANSLATED] Add Missing Dojo Deco Recipes`, + guildView_bulkFundTechProjects: `[UNTRANSLATED] Fund All Research`, + guildView_bulkCompleteTechProjects: `[UNTRANSLATED] Complete All Research`, + prettier_sucks_ass: `` }; diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index 1b915cba..a5f14150 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -31,6 +31,8 @@ dict = { code_renamePrompt: `Nouveau nom :`, code_remove: `Retirer`, code_addItemsConfirm: `Ajouter |COUNT| items à l'inventaire ?`, + code_addTechProjectsConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| research to your clan?`, + code_addDecoRecipesConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| deco recipes to your clan?`, code_succRankUp: `Montée de niveau effectuée.`, code_noEquipmentToRankUp: `Aucun équipement à monter de niveau.`, code_succAdded: `Ajouté.`, @@ -63,6 +65,8 @@ dict = { code_pigment: `Pigment`, code_mature: `Maturer pour le combat`, code_unmature: `Régrésser l'âge génétique`, + code_fund: `[UNTRANSLATED] Fund`, + code_funded: `[UNTRANSLATED] Funded`, code_succChange: `Changement effectué.`, code_requiredInvigorationUpgrade: `Augmentation offensive et défensive requises.`, login_description: `Connexion avec les informations de connexion OpenWF.`, @@ -74,6 +78,7 @@ dict = { navbar_renameAccount: `Renommer le compte`, navbar_deleteAccount: `Supprimer le compte`, navbar_inventory: `Inventaire`, + navbar_guildView: `Clan`, navbar_mods: `Mods`, navbar_quests: `Quêtes`, navbar_cheats: `Cheats`, @@ -196,7 +201,6 @@ dict = { cheats_unlockAllFlavourItems: `Débloquer tous les Flavor Items`, cheats_unlockAllSkins: `Débloquer tous les skins`, cheats_unlockAllCapturaScenes: `Débloquer toutes les scènes captura`, - cheats_unlockAllDecoRecipes: `Débloquer toutes les recherches dojo`, cheats_universalPolarityEverywhere: `Polarités universelles partout`, cheats_unlockDoubleCapacityPotatoesEverywhere: `Réacteurs et Catalyseurs partout`, cheats_unlockExilusEverywhere: `Adaptateurs Exilus partout`, @@ -387,5 +391,33 @@ dict = { theme_dark: `Thème sombre`, theme_light: `Thème clair`, + guildView_cheats: `[UNTRANSLATED] Clan Cheats`, + guildView_techProjects: `Recherche`, + guildView_vaultDecoRecipes: `[UNTRANSLATED] Dojo Deco Recipes`, + guildView_alliance: `Alliance`, + guildView_members: `Members`, + guildView_pending: `En Attente`, + guildView_classDisplay: `Rang |CLASS|`, + guildView_tierDisplay: `Clan |TIER|`, + guildView_tier1: `Fantôme`, + guildView_tier2: `Ombre`, + guildView_tier3: `Tempête`, + guildView_tier4: `Montagne`, + guildView_tier5: `Lune`, + guildView_rank_creator: `Seigneur de Guerre Fondateur`, + guildView_rank_general: `Général`, + guildView_rank_initiate: `Initié`, + guildView_rank_leader: `Chef`, + guildView_rank_officer: `Officier`, + guildView_rank_sage: `Sage`, + guildView_rank_soldier: `Soldat`, + guildView_rank_utility: `Utilitaire`, + guildView_rank_warlord: `Seigneur de guerre`, + guildView_currency_owned: `[UNTRANSLATED] |COUNT| in Vault.`, + guildView_bulkAddTechProjects: `[UNTRANSLATED] Add Missing Research`, + guildView_bulkAddVaultDecoRecipes: `[UNTRANSLATED] Add Missing Dojo Deco Recipes`, + guildView_bulkFundTechProjects: `[UNTRANSLATED] Fund All Research`, + guildView_bulkCompleteTechProjects: `[UNTRANSLATED] Complete All Research`, + prettier_sucks_ass: `` }; diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index 4657ff3c..ba7b9349 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -31,6 +31,8 @@ dict = { code_renamePrompt: `Введите новое имя:`, code_remove: `Удалить`, code_addItemsConfirm: `Вы уверены, что хотите добавить |COUNT| предметов на ваш аккаунт?`, + code_addTechProjectsConfirm: `Вы уверены, что хотите добавить |COUNT| исследований в свой клан?`, + code_addDecoRecipesConfirm: `Вы уверены, что хотите добавить |COUNT| рецептов декораций в свой клан?`, code_succRankUp: `Ранг успешно повышен.`, code_noEquipmentToRankUp: `Нет снаряжения для повышения ранга.`, code_succAdded: `Успешно добавлено.`, @@ -63,6 +65,8 @@ dict = { code_pigment: `Пигмент`, code_mature: `Подготовить к сражениям`, code_unmature: `Регрессия генетического старения`, + code_fund: `Профинансировать`, + code_funded: `Профинансировано`, code_succChange: `Успешно изменено.`, code_requiredInvigorationUpgrade: `Вы должны выбрать как атакующее, так и вспомогательное улучшение.`, login_description: `Войдите, используя учетные данные OpenWF (те же, что и в игре при подключении к этому серверу).`, @@ -74,6 +78,7 @@ dict = { navbar_renameAccount: `Переименовать аккаунт`, navbar_deleteAccount: `Удалить аккаунт`, navbar_inventory: `Инвентарь`, + navbar_guildView: `Клан`, navbar_mods: `Моды`, navbar_quests: `Квесты`, navbar_cheats: `Читы`, @@ -196,7 +201,6 @@ dict = { cheats_unlockAllFlavourItems: `Разблокировать все уникальные предметы`, cheats_unlockAllSkins: `Разблокировать все скины`, cheats_unlockAllCapturaScenes: `Разблокировать все сцены Каптуры`, - cheats_unlockAllDecoRecipes: `Разблокировать все рецепты декораций Дoдзё`, cheats_universalPolarityEverywhere: `Универсальная полярность везде`, cheats_unlockDoubleCapacityPotatoesEverywhere: `Реакторы/Катализаторы орокин везде`, cheats_unlockExilusEverywhere: `Адаптеры Эксилус везде`, @@ -387,5 +391,33 @@ dict = { theme_dark: `Темная тема`, theme_light: `Светлая тема`, + guildView_cheats: `Читы Клана`, + guildView_techProjects: `Иследовения`, + guildView_vaultDecoRecipes: `Рецепты декораций Додзё`, + guildView_alliance: `Альянс`, + guildView_members: `Товарищи`, + guildView_pending: `Ожидание`, + guildView_classDisplay: `Ранг |CLASS|`, + guildView_tierDisplay: `|TIER| Клан`, + guildView_tier1: `Призрачный`, + guildView_tier2: `Теневой`, + guildView_tier3: `Штормовой`, + guildView_tier4: `Горный`, + guildView_tier5: `Лунный`, + guildView_rank_creator: `Основатель`, + guildView_rank_general: `Генерал`, + guildView_rank_initiate: `Неофит`, + guildView_rank_leader: `Лидер`, + guildView_rank_officer: `Офицер`, + guildView_rank_sage: `Мудрец`, + guildView_rank_soldier: `Солдат`, + guildView_rank_utility: `Инженер`, + guildView_rank_warlord: `Военачальник`, + guildView_currency_owned: `В хранилище |COUNT|.`, + guildView_bulkAddTechProjects: `Добавить отсутствующие Иследования`, + guildView_bulkAddVaultDecoRecipes: `Добавить отсутствующие рецепты декораций Дoдзё`, + guildView_bulkFundTechProjects: `Профинансировать все Иследования`, + guildView_bulkCompleteTechProjects: `Завершить все Иследования`, + prettier_sucks_ass: `` }; diff --git a/static/webui/translations/uk.js b/static/webui/translations/uk.js index d4818dad..94873431 100644 --- a/static/webui/translations/uk.js +++ b/static/webui/translations/uk.js @@ -31,6 +31,8 @@ dict = { code_renamePrompt: `Введіть нове ім'я:`, code_remove: `Видалити`, code_addItemsConfirm: `Ви впевнені, що хочете додати |COUNT| предметів на ваш обліковий запис?`, + code_addTechProjectsConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| research to your clan?`, + code_addDecoRecipesConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| deco recipes to your clan?`, code_succRankUp: `Рівень успішно підвищено`, code_noEquipmentToRankUp: `Немає спорядження для підвищення рівня.`, code_succAdded: `Успішно додано.`, @@ -63,6 +65,8 @@ dict = { code_pigment: `Барвник`, code_mature: `Виростити для бою`, code_unmature: `Обернути старіння`, + code_fund: `[UNTRANSLATED] Fund`, + code_funded: `[UNTRANSLATED] Funded`, code_succChange: `Успішно змінено.`, code_requiredInvigorationUpgrade: `Ви повинні вибрати як атакуюче, так і допоміжне вдосконалення.`, login_description: `Увійдіть, використовуючи облікові дані OpenWF (ті ж, що й у грі при підключенні до цього серверу).`, @@ -74,6 +78,7 @@ dict = { navbar_renameAccount: `Перейменувати обліковий запис`, navbar_deleteAccount: `Видалити обліковий запис`, navbar_inventory: `Спорядження`, + navbar_guildView: `Клан`, navbar_mods: `Модифікатори`, navbar_quests: `Пригоди`, navbar_cheats: `Чити`, @@ -196,7 +201,6 @@ dict = { cheats_unlockAllFlavourItems: `Розблокувати всі унікальні предмети`, cheats_unlockAllSkins: `Розблокувати всі скіни`, cheats_unlockAllCapturaScenes: `Розблокувати всі сцени Світлописця`, - cheats_unlockAllDecoRecipes: `Розблокувати всі рецепти декорацій Доджьо`, cheats_universalPolarityEverywhere: `Будь-яка полярність скрізь`, cheats_unlockDoubleCapacityPotatoesEverywhere: `Орокінські Реактори/Каталізатори скрізь`, cheats_unlockExilusEverywhere: `Ексилотримач скрізь`, @@ -387,5 +391,33 @@ dict = { theme_dark: `Темна тема`, theme_light: `Світла тема`, + guildView_cheats: `[UNTRANSLATED] Clan Cheats`, + guildView_techProjects: `Дослідження`, + guildView_vaultDecoRecipes: `[UNTRANSLATED] Dojo Deco Recipes`, + guildView_alliance: `Альянс`, + guildView_members: `Учасники`, + guildView_pending: `Очікування`, + guildView_classDisplay: `Ранг: |CLASS|`, + guildView_tierDisplay: `|TIER| Клан`, + guildView_tier1: `Примарний`, + guildView_tier2: `Тіньовий`, + guildView_tier3: `Грозовий`, + guildView_tier4: `Гірський`, + guildView_tier5: `Місячний`, + guildView_rank_creator: `Воєвода-засновник`, + guildView_rank_general: `Генерал`, + guildView_rank_initiate: `Рекрут`, + guildView_rank_leader: `Лідер`, + guildView_rank_officer: `Офіцер`, + guildView_rank_sage: `Ветеран`, + guildView_rank_soldier: `Солдат`, + guildView_rank_utility: `Наймит`, + guildView_rank_warlord: `Воєвода`, + guildView_currency_owned: `[UNTRANSLATED] |COUNT| in Vault.`, + guildView_bulkAddTechProjects: `[UNTRANSLATED] Add Missing Research`, + guildView_bulkAddVaultDecoRecipes: `[UNTRANSLATED] Add Missing Dojo Deco Recipes`, + guildView_bulkFundTechProjects: `[UNTRANSLATED] Fund All Research`, + guildView_bulkCompleteTechProjects: `[UNTRANSLATED] Complete All Research`, + prettier_sucks_ass: `` }; diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index 2d987e33..52daa35a 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -31,6 +31,8 @@ dict = { code_renamePrompt: `输入新的自定义名称:`, code_remove: `移除`, code_addItemsConfirm: `确定要向您的账户添加 |COUNT| 件物品吗?`, + code_addTechProjectsConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| research to your clan?`, + code_addDecoRecipesConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| deco recipes to your clan?`, code_succRankUp: `等级已提升`, code_noEquipmentToRankUp: `没有可升级的装备`, code_succAdded: `添加成功`, @@ -63,6 +65,8 @@ dict = { code_pigment: `颜料`, code_mature: `成长并战备`, code_unmature: `逆转衰老基因`, + code_fund: `[UNTRANSLATED] Fund`, + code_funded: `[UNTRANSLATED] Funded`, code_succChange: `更改成功`, code_requiredInvigorationUpgrade: `您必须同时选择一个进攻型和一个功能型活化属性.`, login_description: `使用您的 OpenWF 账户凭证登录(与游戏内连接本服务器时使用的昵称相同)`, @@ -74,6 +78,7 @@ dict = { navbar_renameAccount: `重命名账户`, navbar_deleteAccount: `删除账户`, navbar_inventory: `仓库`, + navbar_guildView: `氏族`, navbar_mods: `Mods`, navbar_quests: `系列任务`, navbar_cheats: `作弊选项`, @@ -196,7 +201,6 @@ dict = { cheats_unlockAllFlavourItems: `解锁所有装饰物品`, cheats_unlockAllSkins: `解锁所有外观`, cheats_unlockAllCapturaScenes: `解锁所有Captura场景`, - cheats_unlockAllDecoRecipes: `解锁所有道场配方`, cheats_universalPolarityEverywhere: `全局万用极性`, cheats_unlockDoubleCapacityPotatoesEverywhere: `全物品自带Orokin反应堆`, cheats_unlockExilusEverywhere: `全物品自带适配器`, @@ -387,5 +391,33 @@ dict = { theme_dark: `暗色主题`, theme_light: `亮色主题`, + guildView_cheats: `[UNTRANSLATED] Clan Cheats`, + guildView_techProjects: `研究`, + guildView_vaultDecoRecipes: `[UNTRANSLATED] Dojo Deco Recipes`, + guildView_alliance: `联盟`, + guildView_members: `成员`, + guildView_pending: `待处理`, + guildView_classDisplay: `等級 |CLASS|`, + guildView_tierDisplay: `|TIER| 氏族`, + guildView_tier1: `幽灵`, + guildView_tier2: `暗影`, + guildView_tier3: `风暴`, + guildView_tier4: `山脉`, + guildView_tier5: `月亮`, + guildView_rank_creator: `创始军阀`, + guildView_rank_general: `将军`, + guildView_rank_initiate: `新兵`, + guildView_rank_leader: `首领`, + guildView_rank_officer: `智者`, + guildView_rank_sage: `贤者`, + guildView_rank_soldier: `战士`, + guildView_rank_utility: `实管`, + guildView_rank_warlord: `军阀`, + guildView_currency_owned: `[UNTRANSLATED] |COUNT| in Vault.`, + guildView_bulkAddTechProjects: `[UNTRANSLATED] Add Missing Research`, + guildView_bulkAddVaultDecoRecipes: `[UNTRANSLATED] Add Missing Dojo Deco Recipes`, + guildView_bulkFundTechProjects: `[UNTRANSLATED] Fund All Research`, + guildView_bulkCompleteTechProjects: `[UNTRANSLATED] Complete All Research`, + prettier_sucks_ass: `` };