Compare commits

...

9 Commits

Author SHA1 Message Date
b1cf1012f5 fix: use correct items for Hallowed Nightmares 2025-10-09 18:13:52 +02:00
610a432e46 fixup for 2ca895a5f88be3ab43943142e5d559823cac7387 2025-10-09 11:01:49 +02:00
2ca895a5f8 feat: Void Corruption 2025 (#2865)
Reviewed-on: OpenWF/SpaceNinjaServer#2865
Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com>
Co-authored-by: Slayer55555 <slayer55555@noreply.localhost>
Co-committed-by: Slayer55555 <slayer55555@noreply.localhost>
2025-10-09 00:28:34 -07:00
fd2286c253 fix: send back entire dojo when a room build has been cancelled (#2879)
Fixes #2877

Reviewed-on: OpenWF/SpaceNinjaServer#2879
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-09 00:28:16 -07:00
5a582daa1a chore(webui): debounce guild tech actions (#2882)
Closes #2880

Reviewed-on: OpenWF/SpaceNinjaServer#2882
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-09 00:28:09 -07:00
6a571e5e78 chore: explicitly declare body-parser dependency for pnpm (#2873)
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Reviewed-on: OpenWF/SpaceNinjaServer#2873
Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com>
Co-authored-by: Mind <1634300602@qq.com>
Co-committed-by: Mind <1634300602@qq.com>
2025-10-07 23:21:17 -07:00
0349c4a32c feat: increase BountyScore for additional stratos emblems earned (#2872)
Closes #2871

Reviewed-on: OpenWF/SpaceNinjaServer#2872
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-07 23:20:48 -07:00
e1563bf298 fix(webui): allow digits in itemtype for add items(raw) (#2870)
For items like `/Lotus/Types/Keys/TacAlertKeyAnniversary2023k`

Reviewed-on: OpenWF/SpaceNinjaServer#2870
Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com>
Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
2025-10-07 23:18:03 -07:00
af6f422fec feat: nemesis mode t / LastNemesisAllySpawnTime (#2869)
Also some import stuff. Closes #2867

Reviewed-on: OpenWF/SpaceNinjaServer#2869
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-07 23:17:50 -07:00
23 changed files with 254 additions and 23 deletions

View File

@ -41,6 +41,10 @@
"naberusNightsOverride": null,
"proxyRebellion": false,
"proxyRebellionRewardsOverride": 0,
"voidCorruption2025Week1": false,
"voidCorruption2025Week2": false,
"voidCorruption2025Week3": false,
"voidCorruption2025Week4": false,
"galleonOfGhouls": 0,
"ghoulEmergenceOverride": null,
"plagueStarOverride": null,

2
package-lock.json generated
View File

@ -9,6 +9,7 @@
"version": "0.1.0",
"license": "GNU",
"dependencies": {
"body-parser": "^2.2.0",
"chokidar": "^4.0.3",
"crc-32": "^1.2.2",
"express": "^5",
@ -37,6 +38,7 @@
"node": ">=20.18.1"
},
"optionalDependencies": {
"@types/body-parser": "^1.19.6",
"@types/express": "^5",
"@types/morgan": "^1.9.9",
"@types/websocket": "^1.0.10",

View File

@ -27,6 +27,7 @@
"license": "GNU",
"type": "module",
"dependencies": {
"body-parser": "^2.2.0",
"chokidar": "^4.0.3",
"crc-32": "^1.2.2",
"express": "^5",
@ -42,6 +43,7 @@
"ws": "^8.18.2"
},
"optionalDependencies": {
"@types/body-parser": "^1.19.6",
"@types/express": "^5",
"@types/morgan": "^1.9.9",
"@types/websocket": "^1.0.10",

View File

@ -31,12 +31,13 @@ export const abortDojoComponentController: RequestHandler = async (req, res) =>
if (request.DecoId) {
removeDojoDeco(guild, request.ComponentId, request.DecoId);
await guild.save();
res.json(await getDojoClient(guild, 0, request.ComponentId));
} else {
await removeDojoRoom(guild, request.ComponentId);
await guild.save();
res.json(await getDojoClient(guild, 0));
}
await guild.save();
res.json(await getDojoClient(guild, 0, request.ComponentId));
};
interface IAbortDojoComponentRequest {

View File

@ -1,4 +1,4 @@
import { fromDbOid, version_compare } from "../../helpers/inventoryHelpers.ts";
import { fromDbOid, toMongoDate, version_compare } from "../../helpers/inventoryHelpers.ts";
import type { IKnifeResponse } from "../../helpers/nemesisHelpers.ts";
import {
antivirusMods,
@ -310,6 +310,15 @@ export const nemesisController: RequestHandler = async (req, res) => {
res.json({
target: inventory.toJSON().Nemesis
});
} else if ((req.query.mode as string) == "t") {
const inventory = await getInventory(account._id.toString(), "LastNemesisAllySpawnTime");
//const body = getJSONfromString<IUpdateAllySpawnTimeRequest>(String(req.body));
const now = new Date(Math.trunc(Date.now() / 1000) * 1000);
inventory.LastNemesisAllySpawnTime = now;
await inventory.save();
res.json({
NewTime: toMongoDate(now)
} satisfies IUpdateAllySpawnTimeResponse);
} else if ((req.query.mode as string) == "d") {
const inventory = await getInventory(account._id.toString(), "NemesisHistory");
const body = getJSONfromString<IRelinquishAdversariesRequest>(String(req.body));
@ -462,3 +471,11 @@ const consumeModCharge = (
interface IRelinquishAdversariesRequest {
nemesisFingerprints: (bigint | number)[];
}
// interface IUpdateAllySpawnTimeRequest {
// LastSpawnTime: IMongoDate;
// }
interface IUpdateAllySpawnTimeResponse {
NewTime: IMongoDate;
}

View File

@ -1743,7 +1743,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
NemesisAbandonedRewards: { type: [String], default: [] },
Nemesis: nemesisSchema,
NemesisHistory: { type: [nemesisSchema], default: undefined },
//LastNemesisAllySpawnTime: Schema.Types.Mixed,
LastNemesisAllySpawnTime: { type: Date, default: undefined },
//TradingRulesConfirmed,ShowFriendInvNotifications(Option->Social)
Settings: settingsSchema,
@ -1864,6 +1864,9 @@ inventorySchema.set("toJSON", {
if (inventoryDatabase.BlessingCooldown) {
inventoryResponse.BlessingCooldown = toMongoDate(inventoryDatabase.BlessingCooldown);
}
if (inventoryDatabase.LastNemesisAllySpawnTime) {
inventoryResponse.LastNemesisAllySpawnTime = toMongoDate(inventoryDatabase.LastNemesisAllySpawnTime);
}
if (inventoryDatabase.NextRefill) {
inventoryResponse.NextRefill = toMongoDate(inventoryDatabase.NextRefill);
}

View File

@ -51,6 +51,10 @@ export interface IConfig {
naberusNightsOverride?: boolean;
proxyRebellion?: boolean;
proxyRebellionRewardsOverride?: number;
voidCorruption2025Week1?: boolean;
voidCorruption2025Week2?: boolean;
voidCorruption2025Week3?: boolean;
voidCorruption2025Week4?: boolean;
galleonOfGhouls?: number;
ghoulEmergenceOverride?: boolean;
plagueStarOverride?: boolean;

View File

@ -73,6 +73,7 @@ import type {
ITailorShop,
ITailorShopDatabase
} from "../types/personalRoomsTypes.ts";
import { fromMongoDate } from "../helpers/inventoryHelpers.ts";
const convertDate = (value: IMongoDate): Date => {
return new Date(parseInt(value.$date.$numberLong));
@ -326,7 +327,15 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
"GiftsRemaining",
"ChallengesFixVersion",
"Founder",
"Guide"
"Guide",
"BountyScore",
"EntratiVaultCountLastPeriod",
"EntratiLabConquestUnlocked",
"EntratiLabConquestHardModeStatus",
"EntratiLabConquestCacheScoreMission",
"EchoesHexConquestUnlocked",
"EchoesHexConquestHardModeStatus",
"EchoesHexConquestCacheScoreMission"
] as const) {
if (client[key] !== undefined) {
db[key] = client[key];
@ -354,12 +363,28 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
"NodeIntrosCompleted",
"DeathMarks",
"Wishlist",
"NemesisAbandonedRewards"
"NemesisAbandonedRewards",
"EntratiLabConquestActiveFrameVariants",
"EchoesHexConquestActiveFrameVariants",
"EchoesHexConquestActiveStickers"
] as const) {
if (client[key] !== undefined) {
db[key] = client[key];
}
}
// IMongoDate
for (const key of [
"Created",
"TrainingDate",
"BlessingCooldown",
"LastNemesisAllySpawnTime",
"NextRefill",
"EntratiVaultCountResetDate"
] as const) {
if (client[key] !== undefined) {
db[key] = fromMongoDate(client[key]);
}
}
// IRewardAtten[]
for (const key of ["SortieRewardAttenuation", "SpecialItemRewardAttenuation"] as const) {
if (client[key] !== undefined) {

View File

@ -1489,7 +1489,13 @@ export const addSkin = (
inventoryChanges: IInventoryChanges = {}
): IInventoryChanges => {
if (inventory.WeaponSkins.some(x => x.ItemType == typeName)) {
logger.debug(`refusing to add WeaponSkin ${typeName} because account already owns it`);
if (typeName == "/Lotus/Upgrades/Skins/Clan/BountyHunterBadgeItem") {
logger.debug(`account already owns stratos emblem, increasing bounty score instead`);
inventory.BountyScore ??= 0;
inventory.BountyScore += 1;
} else {
logger.debug(`refusing to add WeaponSkin ${typeName} because account already owns it`);
}
} else {
const index =
inventory.WeaponSkins.push({

View File

@ -1151,6 +1151,27 @@ export const addMissionRewards = async (
let ConquestCompletedMissionsCount;
let missionCompletionCredits = 0;
if (rewardInfo.alertId) {
const alert = getWorldState().Alerts.find(x => x._id.$oid == rewardInfo.alertId);
if (!alert) {
logger.warn(`mission completed unknown alert`, { alertId: rewardInfo.alertId });
} else {
if (inventory.CompletedAlerts.includes(alert._id.$oid)) {
logger.debug(`alert ${alert._id.$oid} already completed, skipping alert reward`);
} else {
inventory.CompletedAlerts.push(alert._id.$oid);
if (alert.MissionInfo.missionReward) {
missionCompletionCredits += addFixedLevelRewards(
alert.MissionInfo.missionReward,
MissionRewards,
rewardInfo
);
}
}
}
}
//inventory change is what the client has not rewarded itself, also the client needs to know the credit changes for display
if (rewardInfo.goalId) {

View File

@ -19,6 +19,7 @@ import type {
ICalendarDay,
ICalendarEvent,
ICalendarSeason,
IAlert,
IGoal,
IInvasion,
ILiteSortie,
@ -112,6 +113,89 @@ const sortieBossNode: Record<Exclude<TSortieBoss, "SORTIE_BOSS_CORRUPTED_VOR">,
SORTIE_BOSS_VOR: "SolNode108"
};
const configAlerts: Record<string, IAlert> = {
voidCorruption2025Week1: {
_id: { $oid: "677d452e2f324ee7b90f8ccf" },
Activation: { $date: { $numberLong: "1736524800000" } },
Expiry: { $date: { $numberLong: "2000000000000" } },
MissionInfo: {
location: "SolNode61",
missionType: "MT_SABOTAGE",
faction: "FC_CORPUS",
difficulty: 1,
missionReward: {
credits: 30000,
items: ["/Lotus/StoreItems/Upgrades/Mods/Pistol/DualStat/CorruptedFireRateDamagePistol"]
},
levelOverride: "/Lotus/Levels/Proc/Corpus/CorpusShipCoreSabotage",
enemySpec: "/Lotus/Types/Game/EnemySpecs/CorpusShipEnemySpecs/CorpusShipSquadA",
extraEnemySpec: "/Lotus/Types/Game/EnemySpecs/GamemodeExtraEnemySpecs/CorpusSabotageTiersA",
minEnemyLevel: 10,
maxEnemyLevel: 15
}
},
voidCorruption2025Week2: {
_id: { $oid: "677d45811daeae9de40e8c0f" },
Activation: { $date: { $numberLong: "1737129600000" } },
Expiry: { $date: { $numberLong: "2000000000000" } },
MissionInfo: {
location: "SettlementNode11",
missionType: "MT_DEFENSE",
faction: "FC_CORPUS",
difficulty: 1,
missionReward: {
credits: 30000,
items: ["/Lotus/StoreItems/Upgrades/Mods/Pistol/DualStat/CorruptedCritChanceFireRatePistol"]
},
levelOverride: "/Lotus/Levels/Proc/Corpus/CorpusShipDefense",
enemySpec: "/Lotus/Types/Game/EnemySpecs/CorpusShipEnemySpecs/CorpusShipSquadDefenseB",
minEnemyLevel: 20,
maxEnemyLevel: 25,
maxWaveNum: 6
}
},
voidCorruption2025Week3: {
_id: { $oid: "677d45a494ad716c90006b9a" },
Activation: { $date: { $numberLong: "1737734400000" } },
Expiry: { $date: { $numberLong: "2000000000000" } },
MissionInfo: {
location: "SolNode118",
missionType: "MT_ARTIFACT",
faction: "FC_CORPUS",
difficulty: 1,
missionReward: {
credits: 30000,
items: ["/Lotus/StoreItems/Upgrades/Mods/Pistol/DualStat/CorruptedCritDamagePistol"]
},
levelOverride: "/Lotus/Levels/Proc/Corpus/CorpusShipDisruption",
enemySpec: "/Lotus/Types/Game/EnemySpecs/CorpusShipEnemySpecs/CorpusShipSurvivalA",
extraEnemySpec: "/Lotus/Types/Game/EnemySpecs/SpecialMissionSpecs/DisruptionCorpusShip",
customAdvancedSpawners: ["/Lotus/Types/Enemies/AdvancedSpawners/ErrantSpecterInvasion"],
minEnemyLevel: 30,
maxEnemyLevel: 35
}
},
voidCorruption2025Week4: {
_id: { $oid: "677d4700682d173abb0e19fe" },
Activation: { $date: { $numberLong: "1738339200000" } },
Expiry: { $date: { $numberLong: "2000000000000" } },
MissionInfo: {
location: "SolNode4",
missionType: "MT_EXTERMINATION",
faction: "FC_CORPUS",
difficulty: 1,
missionReward: {
credits: 30000,
items: ["/Lotus/StoreItems/Upgrades/Mods/Pistol/DualStat/CorruptedDamageRecoilPistol"]
},
levelOverride: "/Lotus/Levels/Proc/Corpus/CorpusShipExterminate",
enemySpec: "/Lotus/Types/Game/EnemySpecs/CorpusShipEnemySpecs/CorpusShipExterminateMixed",
minEnemyLevel: 40,
maxEnemyLevel: 45
}
}
};
const eidolonJobs: readonly string[] = [
"/Lotus/Types/Gameplay/Eidolon/Jobs/AssassinateBountyAss",
"/Lotus/Types/Gameplay/Eidolon/Jobs/AssassinateBountyCap",
@ -1481,6 +1565,15 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
worldState.VoidTraders.push(vt);
fullyStockBaro(vt);
}
if (config.worldState) {
for (const [key, alert] of Object.entries(configAlerts)) {
if (config.worldState[key as keyof typeof config.worldState]) {
worldState.Alerts.push(alert);
}
}
}
const isFebruary = date.getUTCMonth() == 1;
if (config.worldState?.starDaysOverride ?? isFebruary) {
worldState.Goals.push({
@ -2316,22 +2409,22 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
// 2016
[
{
items: ["/Lotus/StoreItems/Upgrades/Skins/Sigils/OrokinCatalyst"]
},
{
items: ["/Lotus/StoreItems/Upgrades/Skins/Sigils/DotD2016Sigil"]
items: ["/Lotus/StoreItems/Types/Items/MiscItems/OrokinCatalyst"]
},
{
items: [
"/Lotus/StoreItems/Types/Items/MiscItems/OrokinReactor",
"/Lotus/StoreItems/Upgrades/Skins/Sigils/DotD2016Sigil",
"/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem"
]
},
{
items: ["/Lotus/StoreItems/Types/Items/MiscItems/OrokinReactor"]
}
],
// 2015
[
{
items: ["/Lotus/StoreItems/Upgrades/Skins/Sigils/OrokinCatalyst"]
items: ["/Lotus/StoreItems/Types/Items/MiscItems/OrokinCatalyst"]
},
{
items: ["/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem"]

View File

@ -92,6 +92,7 @@ export interface IInventoryDatabase
| "NextRefill"
| "Nemesis"
| "NemesisHistory"
| "LastNemesisAllySpawnTime"
| "EntratiVaultCountResetDate"
| "BrandedSuits"
| "LockedWeaponGroup"
@ -136,6 +137,7 @@ export interface IInventoryDatabase
NextRefill?: Date;
Nemesis?: INemesisDatabase;
NemesisHistory?: INemesisBaseDatabase[];
LastNemesisAllySpawnTime?: Date;
EntratiVaultCountResetDate?: Date;
BrandedSuits?: Types.ObjectId[];
LockedWeaponGroup?: ILockedWeaponGroupDatabase;
@ -360,7 +362,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
ThemeStyle: string;
ThemeBackground: string;
ThemeSounds: string;
BountyScore: number;
BountyScore?: number;
//ChallengeInstanceStates: IChallengeInstanceState[];
LoginMilestoneRewards: string[];
RecentVendorPurchases?: IRecentVendorPurchaseClient[];
@ -372,7 +374,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
//InvasionChainProgress: IInvasionChainProgress[];
Nemesis?: INemesisClient;
NemesisHistory?: INemesisBaseClient[];
//LastNemesisAllySpawnTime?: IMongoDate;
LastNemesisAllySpawnTime?: IMongoDate;
Settings?: ISettings;
PersonalTechProjects: IPersonalTechProjectClient[];
PlayerSkills: IPlayerSkills;

View File

@ -178,6 +178,7 @@ export interface IRewardInfo {
goalManifest?: string;
invasionId?: string;
invasionAllyFaction?: "FC_GRINEER" | "FC_CORPUS";
alertId?: string;
sortieId?: string;
sortieTag?: "Mission1" | "Mission2" | "Final";
sortiePrereqs?: string[];

View File

@ -1,4 +1,4 @@
import type { IMissionReward } from "warframe-public-export-plus";
import type { IMissionReward, TFaction } from "warframe-public-export-plus";
import type { IMongoDate, IOid } from "./commonTypes.ts";
export interface IWorldState {
@ -7,7 +7,7 @@ export interface IWorldState {
Time: number;
InGameMarket: IInGameMarket;
Goals: IGoal[];
Alerts: [];
Alerts: IAlert[];
Sorties: ISortie[];
LiteSorties: ILiteSortie[];
SyndicateMissions: ISyndicateMissionInfo[];
@ -35,6 +35,28 @@ export interface IWorldState {
Tmp?: string;
}
export interface IAlert {
_id: IOid;
Activation: IMongoDate;
Expiry: IMongoDate;
MissionInfo: IAlertMissionInfo;
}
export interface IAlertMissionInfo {
location: string;
missionType: string;
faction: TFaction;
difficulty: number;
missionReward?: IMissionReward;
levelOverride?: string;
enemySpec?: string;
extraEnemySpec?: string;
customAdvancedSpawners?: string[];
minEnemyLevel?: number;
maxEnemyLevel?: number;
maxWaveNum?: number;
}
export interface IGoal {
_id: IOid;
Activation: IMongoDate;

View File

@ -1232,6 +1232,27 @@
</select>
</div>
</div>
<div class="mt-2">
<label class="form-label" data-loc="worldState_voidCorruption" data-loc-replace="2025"></label>
<div class="d-flex flex-wrap gap-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.voidCorruption2025Week1" />
<label class="form-check-label" for="worldState.voidCorruption2025Week1" data-loc="worldState_week" data-loc-replace="1"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.voidCorruption2025Week2" />
<label class="form-check-label" for="worldState.voidCorruption2025Week2" data-loc="worldState_week" data-loc-replace="2"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.voidCorruption2025Week3" />
<label class="form-check-label" for="worldState.voidCorruption2025Week3" data-loc="worldState_week" data-loc-replace="3"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.voidCorruption2025Week4" />
<label class="form-check-label" for="worldState.voidCorruption2025Week4" data-loc="worldState_week" data-loc-replace="4"></label>
</div>
</div>
</div>
<div class="form-group mt-2">
<label class="form-label" for="worldState.galleonOfGhouls" data-loc="worldState_galleonOfGhouls"></label>
<abbr data-loc-inc="worldState_wolfHunt|worldState_anniversary|worldState_orphixVenom"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M320 576C461.4 576 576 461.4 576 320C576 178.6 461.4 64 320 64C178.6 64 64 178.6 64 320C64 461.4 178.6 576 320 576zM320 200C333.3 200 344 210.7 344 224L344 336C344 349.3 333.3 360 320 360C306.7 360 296 349.3 296 336L296 224C296 210.7 306.7 200 320 200zM293.3 416C292.7 406.1 297.6 396.7 306.1 391.5C314.6 386.4 325.3 386.4 333.8 391.5C342.3 396.7 347.2 406.1 346.6 416C347.2 425.9 342.3 435.3 333.8 440.5C325.3 445.6 314.6 445.6 306.1 440.5C297.6 435.3 292.7 425.9 293.3 416z"/></svg></abbr>

View File

@ -1771,7 +1771,7 @@ function updateInventory() {
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
fundGuildTechProject(item.ItemType);
debounce(fundGuildTechProject, item.ItemType);
};
a.title = loc("code_fund");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free v7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M214.6 17.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 117.3 160 488c0 17.7 14.3 32 32 32s32-14.3 32-32l0-370.7 105.4 105.4c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z"/></svg>`;
@ -1788,7 +1788,7 @@ function updateInventory() {
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
completeGuildTechProject(item.ItemType);
debounce(completeGuildTechProject, item.ItemType);
};
a.title = loc("code_complete");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--!Font Awesome Free v7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M371.7 43.1C360.1 32 343 28.9 328.3 35.2S304 56 304 72l0 136.3-172.3-165.1C120.1 32 103 28.9 88.3 35.2S64 56 64 72l0 368c0 16 9.6 30.5 24.3 36.8s31.8 3.2 43.4-7.9L304 303.7 304 440c0 16 9.6 30.5 24.3 36.8s31.8 3.2 43.4-7.9l192-184c7.9-7.5 12.3-18 12.3-28.9s-4.5-21.3-12.3-28.9l-192-184z"/></svg>`;
@ -1801,7 +1801,7 @@ function updateInventory() {
a.onclick = function (event) {
event.preventDefault();
reAddToItemList(itemMap, "TechProjects", item.ItemType);
removeGuildTechProject(item.ItemType);
debounce(removeGuildTechProject, item.ItemType);
};
a.title = loc("code_remove");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"/></svg>`;
@ -2844,8 +2844,8 @@ function removeCountItems(uniqueName, count) {
function addItemByItemType() {
const ItemType = document.getElementById("typeName-type").value;
// Must start with "/Lotus/", contain only AZ letters, no "//", and not end with "/"
if (!ItemType || !/^\/Lotus\/(?:[A-Za-z]+(?:\/[A-Za-z]+)*)$/.test(ItemType)) {
// Must start with "/Lotus/", contain only letters AZ, digits 09, no "//", and not end with "/"
if (!ItemType || !/^\/Lotus\/(?:[A-Za-z0-9]+(?:\/[A-Za-z0-9]+)*)$/.test(ItemType)) {
$("#typeName-type").addClass("is-invalid").focus();
return;
}

View File

@ -279,6 +279,7 @@ dict = {
worldState_dogDays: `Hitzefrei`,
worldState_dogDaysRewards: `[UNTRANSLATED] Dog Days Rewards`,
worldState_wolfHunt: `Wolfsjagd (2025)`,
worldState_voidCorruption: `[UNTRANSLATED] Void Corruption (|VAL|)`,
worldState_orphixVenom: `Orphix Gift`,
worldState_longShadow: `Lange Schatten`,
worldState_hallowedFlame: `Geweihte Flamme`,

View File

@ -278,6 +278,7 @@ dict = {
worldState_dogDays: `Dog Days`,
worldState_dogDaysRewards: `Dog Days Rewards`,
worldState_wolfHunt: `Wolf Hunt (2025)`,
worldState_voidCorruption: `Void Corruption (|VAL|)`,
worldState_orphixVenom: `Orphix Venom`,
worldState_longShadow: `Long Shadow`,
worldState_hallowedFlame: `Hallowed Flame`,

View File

@ -279,6 +279,7 @@ dict = {
worldState_dogDays: `Canícula`,
worldState_dogDaysRewards: `Recompensas de Canícula`,
worldState_wolfHunt: `Cacería del Lobo (2025)`,
worldState_voidCorruption: `Corrupción del Vacío (|VAL|)`,
worldState_orphixVenom: `Veneno de Orphix`,
worldState_longShadow: `Sombra Prolongada`,
worldState_hallowedFlame: `Llama Sagrada`,

View File

@ -279,6 +279,7 @@ dict = {
worldState_dogDays: `Bataille d'Eau`,
worldState_dogDaysRewards: `Récompenses de la Bataille d'Eau`,
worldState_wolfHunt: `Chasse au Loup (2025)`,
worldState_voidCorruption: `[UNTRANSLATED] Void Corruption (|VAL|)`,
worldState_orphixVenom: `Venin Orphix`,
worldState_longShadow: `La Propagation des Ombres`,
worldState_hallowedFlame: `Flamme Hantée`,

View File

@ -279,6 +279,7 @@ dict = {
worldState_dogDays: `Знойные дни`,
worldState_dogDaysRewards: `Награды Знойных дней`,
worldState_wolfHunt: `Волчья Охота (2025)`,
worldState_voidCorruption: `[UNTRANSLATED] Void Corruption (|VAL|)`,
worldState_orphixVenom: `Яд Орфикса`,
worldState_longShadow: `Длинная Тень`,
worldState_hallowedFlame: `Священное пламя`,

View File

@ -279,6 +279,7 @@ dict = {
worldState_dogDays: `Спекотні дні`,
worldState_dogDaysRewards: `Нагороди Спекотних днів`,
worldState_wolfHunt: `Полювання на Вовка (2025)`,
worldState_voidCorruption: `[UNTRANSLATED] Void Corruption (|VAL|)`,
worldState_orphixVenom: `Орфіксова отрута`,
worldState_longShadow: `Довга тінь`,
worldState_hallowedFlame: `Священне полум'я`,

View File

@ -279,6 +279,7 @@ dict = {
worldState_dogDays: `三伏天`,
worldState_dogDaysRewards: `三伏天奖励设置`,
worldState_wolfHunt: `恶狼狩猎 (2025)`,
worldState_voidCorruption: `[UNTRANSLATED] Void Corruption (|VAL|)`,
worldState_orphixVenom: `奥影之毒`,
worldState_longShadow: `暗夜长影`,
worldState_hallowedFlame: `万圣之焰`,