feat: belly of the beast
All checks were successful
Build / build (pull_request) Successful in 56s

Re #1103
This commit is contained in:
AMelonInsideLemon 2025-08-14 01:43:11 +02:00
parent 7bc5065251
commit 60801b3043
20 changed files with 221 additions and 20 deletions

View File

@ -84,6 +84,8 @@
"starDaysOverride": null,
"dogDaysOverride": null,
"dogDaysRewardsOverride": null,
"bellyOfTheBeast": false,
"bellyOfTheBeastProgressOverride": 0,
"eidolonOverride": "",
"vallisOverride": "",
"duviriOverride": "",

View File

@ -0,0 +1,25 @@
import { RequestHandler } from "express";
import { getAccountForRequest } from "@/src/services/loginService";
import { getInventory } from "@/src/services/inventoryService";
import { Guild } from "@/src/models/guildModel";
export const getGuildEventScoreController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
const inventory = await getInventory(account._id.toString(), "GuildId");
const guild = await Guild.findById(inventory.GuildId);
const goalId = req.query.goalId as string;
if (guild && guild.GoalProgress && goalId) {
const goal = guild.GoalProgress.find(x => x.goalId.toString() == goalId);
if (goal) {
return res.json({
Tier: guild.Tier,
GoalProgress: {
Count: goal.Count,
Tag: goal.Tag,
_id: { $oid: goal.goalId }
}
});
}
}
return res.json({});
};

View File

@ -19,6 +19,8 @@ import {
import { Document, Model, model, Schema, Types } from "mongoose";
import { fusionTreasuresSchema, typeCountSchema } from "@/src/models/inventoryModels/inventoryModel";
import { pictureFrameInfoSchema } from "@/src/models/personalRoomsModel";
import { IGoalProgressClient, IGoalProgressDatabase } from "@/src/types/inventoryTypes/inventoryTypes";
import { toOid } from "@/src/helpers/inventoryHelpers";
const dojoDecoSchema = new Schema<IDojoDecoDatabase>({
Type: String,
@ -174,6 +176,28 @@ const guildLogEntryNumberSchema = new Schema<IGuildLogEntryNumber>(
{ _id: false }
);
const goalProgressSchema = new Schema<IGoalProgressDatabase>(
{
Count: Number,
Tag: String,
goalId: Types.ObjectId
},
{ _id: false }
);
goalProgressSchema.set("toJSON", {
virtuals: true,
transform(_doc, obj: Record<string, any>) {
const db = obj as IGoalProgressDatabase;
const client = obj as IGoalProgressClient;
client._id = toOid(db.goalId);
delete obj.goalId;
delete obj.__v;
}
});
const guildSchema = new Schema<IGuildDatabase>(
{
Name: { type: String, required: true, unique: true },
@ -206,7 +230,8 @@ const guildSchema = new Schema<IGuildDatabase>(
RoomChanges: { type: [guildLogRoomChangeSchema], default: undefined },
TechChanges: { type: [guildLogEntryContributableSchema], default: undefined },
RosterActivity: { type: [guildLogEntryRosterSchema], default: undefined },
ClassChanges: { type: [guildLogEntryNumberSchema], default: undefined }
ClassChanges: { type: [guildLogEntryNumberSchema], default: undefined },
GoalProgress: { type: [goalProgressSchema], default: undefined }
},
{ id: false }
);

View File

@ -85,8 +85,8 @@ import {
IAccolades,
IHubNpcCustomization,
IEndlessXpReward,
IPersonalGoalProgressDatabase,
IPersonalGoalProgressClient,
IGoalProgressDatabase,
IGoalProgressClient,
IKubrowPetPrintClient,
IKubrowPetPrintDatabase
} from "@/src/types/inventoryTypes/inventoryTypes";
@ -445,7 +445,7 @@ const discoveredMarkerSchema = new Schema<IDiscoveredMarker>(
{ _id: false }
);
const personalGoalProgressSchema = new Schema<IPersonalGoalProgressDatabase>(
const personalGoalProgressSchema = new Schema<IGoalProgressDatabase>(
{
Best: Number,
Count: Number,
@ -458,8 +458,8 @@ const personalGoalProgressSchema = new Schema<IPersonalGoalProgressDatabase>(
personalGoalProgressSchema.set("toJSON", {
virtuals: true,
transform(_doc, obj: Record<string, any>) {
const db = obj as IPersonalGoalProgressDatabase;
const client = obj as IPersonalGoalProgressClient;
const db = obj as IGoalProgressDatabase;
const client = obj as IGoalProgressClient;
client._id = toOid(db.goalId);

View File

@ -62,6 +62,7 @@ import { getFriendsController } from "@/src/controllers/api/getFriendsController
import { getGuildContributionsController } from "@/src/controllers/api/getGuildContributionsController";
import { getGuildController } from "@/src/controllers/api/getGuildController";
import { getGuildDojoController } from "@/src/controllers/api/getGuildDojoController";
import { getGuildEventScoreController } from "@/src/controllers/api/getGuildEventScore";
import { getGuildLogController } from "@/src/controllers/api/getGuildLogController";
import { getIgnoredUsersController } from "@/src/controllers/api/getIgnoredUsersController";
import { getNewRewardSeedController } from "@/src/controllers/api/getNewRewardSeedController";
@ -192,6 +193,7 @@ apiRouter.get("/getFriends.php", getFriendsController);
apiRouter.get("/getGuild.php", getGuildController);
apiRouter.get("/getGuildContributions.php", getGuildContributionsController);
apiRouter.get("/getGuildDojo.php", getGuildDojoController);
apiRouter.get("/getGuildEventScore.php", getGuildEventScoreController);
apiRouter.get("/getGuildLog.php", getGuildLogController);
apiRouter.get("/getIgnoredUsers.php", getIgnoredUsersController);
apiRouter.get("/getMessages.php", inboxController); // unsure if this is correct, but needed for U17

View File

@ -96,6 +96,8 @@ export interface IConfig {
starDaysOverride?: boolean;
dogDaysOverride?: boolean;
dogDaysRewardsOverride?: number;
bellyOfTheBeast?: boolean;
bellyOfTheBeastProgressOverride?: number;
eidolonOverride?: string;
vallisOverride?: string;
duviriOverride?: string;

View File

@ -115,7 +115,14 @@ export const getGuildClient = async (
NumContributors: guild.CeremonyContributors?.length ?? 0,
CeremonyResetDate: guild.CeremonyResetDate ? toMongoDate(guild.CeremonyResetDate) : undefined,
AutoContributeFromVault: guild.AutoContributeFromVault,
AllianceId: guild.AllianceId ? toOid2(guild.AllianceId, account.BuildLabel) : undefined
AllianceId: guild.AllianceId ? toOid2(guild.AllianceId, account.BuildLabel) : undefined,
GoalProgress: guild.GoalProgress
? guild.GoalProgress.map(gp => ({
Count: gp.Count,
Tag: gp.Tag,
_id: { $oid: gp.goalId.toString() }
}))
: undefined
};
};

View File

@ -81,6 +81,7 @@ import { fromOid } from "@/src/helpers/inventoryHelpers";
import { TAccountDocument } from "@/src/services/loginService";
import { ITypeCount } from "@/src/types/commonTypes";
import { IEquipmentClient } from "@/src/types/equipmentTypes";
import { Guild } from "@/src/models/guildModel";
const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[] => {
// Disruption missions just tell us (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2599)
@ -712,10 +713,56 @@ export const addMissionInventoryUpdates = async (
}
if (goalProgress) {
goalProgress.Best = Math.max(goalProgress.Best, uploadProgress.Best);
goalProgress.Best = Math.max(goalProgress.Best!, uploadProgress.Best);
goalProgress.Count += uploadProgress.Count;
}
}
if (goal && goal.ClanGoal && inventory.GuildId) {
const guild = await Guild.findById(inventory.GuildId, "GoalProgress Tier VaultDecoRecipes");
if (guild) {
guild.GoalProgress ??= [];
const goalProgress = guild.GoalProgress.find(x => x.goalId.equals(goal._id.$oid));
if (!goalProgress) {
guild.GoalProgress.push({
Count: uploadProgress.Count,
Tag: goal.Tag,
goalId: new Types.ObjectId(goal._id.$oid)
});
}
const totalCount = (goalProgress?.Count ?? 0) + uploadProgress.Count;
const guildRewards = goalGuildRewardByTag[goal.Tag].rewards;
const tierGoals = goalGuildRewardByTag[goal.Tag].guildGoals[guild.Tier - 1];
const rewards = [];
if (tierGoals.length && guildRewards.length) {
for (let i = 0; i < tierGoals.length; i++) {
if (
tierGoals[i] &&
tierGoals[i] <= totalCount &&
(!goalProgress || goalProgress.Count < tierGoals[i]) &&
guildRewards[i]
) {
rewards.push(guildRewards[i]);
}
}
if (rewards.length) {
logger.debug(`guild goal rewards`, rewards);
guild.VaultDecoRecipes ??= [];
rewards.forEach(type => {
guild.VaultDecoRecipes!.push({
ItemType: type,
ItemCount: 1
});
});
}
}
if (goalProgress) {
goalProgress.Count += uploadProgress.Count;
}
await guild.save();
}
}
}
break;
}
@ -2387,3 +2434,22 @@ const goalMessagesByKey: Record<string, { sndr: string; msg: string; sub: string
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png"
}
};
const goalGuildRewardByTag: Record<string, { guildGoals: number[][]; rewards: string[] }> = {
JadeShadowsEvent: {
guildGoals: [
// I don't know what ClanGoal means
[15, 30, 45, 60],
[45, 90, 135, 180],
[150, 300, 450, 600],
[450, 900, 1350, 1800],
[1500, 3000, 4500, 6000]
],
rewards: [
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/JadeShadowsEventPewterTrophyRecipe",
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/JadeShadowsEventBronzeTrophyRecipe",
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/JadeShadowsEventSilverTrophyRecipe",
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/JadeShadowsEventGoldTrophyRecipe"
]
}
};

View File

@ -2265,6 +2265,28 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
BonusReward: { items: ["/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem"] }
});
}
if (config.worldState?.bellyOfTheBeast) {
worldState.Goals.push({
_id: { $oid: "67a5035c2a198564d62e165e" },
Activation: { $date: { $numberLong: "1738868400000" } },
Expiry: { $date: { $numberLong: "2000000000000" } },
Count: config.worldState.bellyOfTheBeastProgressOverride ?? 0,
HealthPct: (config.worldState.bellyOfTheBeastProgressOverride ?? 0) / 100,
Goal: 0,
Personal: true,
Community: true,
ClanGoal: [72, 216, 648, 1944, 5832],
Tag: "JadeShadowsEvent",
Faction: "FC_MITW",
Desc: "/Lotus/Language/JadeShadows/JadeShadowsEventName",
ToolTip: "/Lotus/Language/JadeShadows/JadeShadowsShortEventDesc",
Icon: "/Lotus/Interface/Icons/WorldStatePanel/JadeShadowsEventBadge.png",
ScoreLocTag: "/Lotus/Language/JadeShadows/JadeShadowsEventScore",
Node: "SolNode723",
MissionKeyName: "/Lotus/Types/Keys/JadeShadowsEventMission",
ItemType: "/Lotus/Types/Gameplay/JadeShadows/Resources/AscensionEventResourceItem"
});
}
// Nightwave Challenges
const nightwaveSyndicateTag = getNightwaveSyndicateTag(buildLabel);

View File

@ -1,6 +1,11 @@
import { Types } from "mongoose";
import { IOid, IMongoDate, IOidWithLegacySupport, ITypeCount } from "@/src/types/commonTypes";
import { IFusionTreasure, IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
import {
IFusionTreasure,
IMiscItem,
IGoalProgressDatabase,
IGoalProgressClient
} from "@/src/types/inventoryTypes/inventoryTypes";
import { IPictureFrameInfo } from "@/src/types/personalRoomsTypes";
import { IFriendInfo } from "@/src/types/friendTypes";
@ -23,6 +28,8 @@ export interface IGuildClient {
CrossPlatformEnabled?: boolean;
AutoContributeFromVault?: boolean;
AllianceId?: IOidWithLegacySupport;
GoalProgress?: IGoalProgressClient[];
}
export interface IGuildDatabase {
@ -63,6 +70,8 @@ export interface IGuildDatabase {
TechChanges?: IGuildLogEntryContributable[];
RosterActivity?: IGuildLogEntryRoster[];
ClassChanges?: IGuildLogEntryNumber[];
GoalProgress?: IGoalProgressDatabase[];
}
export interface ILongMOTD {

View File

@ -99,7 +99,7 @@ export interface IInventoryDatabase
QualifyingInvasions: IInvasionProgressDatabase[];
LastInventorySync?: Types.ObjectId;
EndlessXP?: IEndlessXpProgressDatabase[];
PersonalGoalProgress?: IPersonalGoalProgressDatabase[];
PersonalGoalProgress?: IGoalProgressDatabase[];
}
export interface IQuestKeyDatabase {
@ -308,7 +308,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
HWIDProtectEnabled?: boolean;
KubrowPetPrints: IKubrowPetPrintClient[];
AlignmentReplay?: IAlignment;
PersonalGoalProgress?: IPersonalGoalProgressClient[];
PersonalGoalProgress?: IGoalProgressClient[];
ThemeStyle: string;
ThemeBackground: string;
ThemeSounds: string;
@ -885,8 +885,8 @@ export interface IPeriodicMissionCompletionResponse extends Omit<IPeriodicMissio
date: IMongoDate;
}
export interface IPersonalGoalProgressClient {
Best: number;
export interface IGoalProgressClient {
Best?: number;
Count: number;
Tag: string;
_id: IOid;
@ -894,7 +894,7 @@ export interface IPersonalGoalProgressClient {
//ReceivedClanReward1?: boolean;
}
export interface IPersonalGoalProgressDatabase extends Omit<IPersonalGoalProgressClient, "_id"> {
export interface IGoalProgressDatabase extends Omit<IGoalProgressClient, "_id"> {
goalId: Types.ObjectId;
}

View File

@ -39,32 +39,44 @@ export interface IGoal {
_id: IOid;
Activation: IMongoDate;
Expiry: IMongoDate;
Count?: number;
HealthPct?: number;
Icon: string;
Desc: string;
ToolTip?: string;
Faction?: string;
Goal?: number;
InterimGoals?: number[];
BonusGoal?: number;
HealthPct?: number;
ClanGoal?: number[];
Success?: number;
Personal?: boolean;
Best?: boolean;
Community?: boolean;
Best?: boolean; // Fist one on Event Tab
Bounty?: boolean; // Tactical Alert
Faction?: string;
ClampNodeScores?: boolean;
Desc: string;
ToolTip?: string;
Transmission?: string;
InstructionalItem?: string;
Icon: string;
ItemType?: string;
Tag: string;
PrereqGoalTags?: string[];
Node?: string;
VictimNode?: string;
ConcurrentMissionKeyNames?: string[];
ConcurrentNodeReqs?: number[];
ConcurrentNodes?: string[];
RegionIdx?: number;
Regions?: number[];
MissionKeyName?: string;
Reward?: IMissionReward;
InterimRewards?: IMissionReward[];
BonusReward?: IMissionReward;
@ -77,6 +89,8 @@ export interface IGoal {
ScoreVar?: string;
ScoreMaxTag?: string;
ScoreLocTag?: string;
NightLevel?: string;
}

View File

@ -1040,6 +1040,19 @@
</select>
</div>
</div>
<div class="form-group mt-2 d-flex gap-2">
<div class="flex-fill">
<label class="form-label" for="worldState.bellyOfTheBeast" data-loc="worldState_bellyOfTheBeast"></label>
<select class="form-control" id="worldState.bellyOfTheBeast" data-default="false">
<option value="true" data-loc="enabled"></option>
<option value="false" data-loc="disabled"></option>
</select>
</div>
<div class="flex-fill">
<label class="form-label" for="worldState.bellyOfTheBeastProgressOverride" data-loc="worldState_bellyOfTheBeastProgressOverride"></label>
<input id="worldState.bellyOfTheBeastProgressOverride" class="form-control" type="number" min="0" max="100" data-default="0" />
</div>
</div>
<div class="form-group mt-2">
<label class="form-label" for="worldState.eidolonOverride" data-loc="worldState_eidolonOverride"></label>
<select class="form-control" id="worldState.eidolonOverride" data-default="">

View File

@ -257,6 +257,8 @@ dict = {
worldState_hallowedNightmaresRewards: `[UNTRANSLATED] Hallowed Nightmares Rewards`,
worldState_proxyRebellion: `Proxy-Rebellion`,
worldState_proxyRebellionRewards: `[UNTRANSLATED] Proxy Rebellion Rewards`,
worldState_bellyOfTheBeast: `Das Innere der Bestie`,
worldState_bellyOfTheBeastProgressOverride: `[UNTRANSLATED] Belly of the Beast Progress`,
worldState_from_year: `[UNTRANSLATED] from |YEAR|`,
worldState_pre_year: `[UNTRANSLATED] pre |YEAR|`,
worldState_incompatibleWith: `[UNTRANSLATED] Incompatible with:`,

View File

@ -256,6 +256,8 @@ dict = {
worldState_hallowedNightmaresRewards: `Hallowed Nightmares Rewards`,
worldState_proxyRebellion: `Proxy Rebellion`,
worldState_proxyRebellionRewards: `Proxy Rebellion Rewards`,
worldState_bellyOfTheBeast: `Belly of the Beast`,
worldState_bellyOfTheBeastProgressOverride: `Belly of the Beast Progress`,
worldState_from_year: `from |YEAR|`,
worldState_pre_year: `pre |YEAR|`,
worldState_incompatibleWith: `Incompatible with:`,

View File

@ -257,6 +257,8 @@ dict = {
worldState_hallowedNightmaresRewards: `[UNTRANSLATED] Hallowed Nightmares Rewards`,
worldState_proxyRebellion: `Rebelión Proxy`,
worldState_proxyRebellionRewards: `[UNTRANSLATED] Proxy Rebellion Rewards`,
worldState_bellyOfTheBeast: `Vientre de la Bestia`,
worldState_bellyOfTheBeastProgressOverride: `[UNTRANSLATED] Belly of the Beast Progress`,
worldState_from_year: `[UNTRANSLATED] from |YEAR|`,
worldState_pre_year: `[UNTRANSLATED] pre |YEAR|`,
worldState_incompatibleWith: `[UNTRANSLATED] Incompatible with:`,

View File

@ -257,6 +257,8 @@ dict = {
worldState_hallowedNightmaresRewards: `[UNTRANSLATED] Hallowed Nightmares Rewards`,
worldState_proxyRebellion: `Rébellion Proxy`,
worldState_proxyRebellionRewards: `[UNTRANSLATED] Proxy Rebellion Rewards`,
worldState_bellyOfTheBeast: `Ventre de la Bête`,
worldState_bellyOfTheBeastProgressOverride: `[UNTRANSLATED] Belly of the Beast Progress`,
worldState_from_year: `[UNTRANSLATED] from |YEAR|`,
worldState_pre_year: `[UNTRANSLATED] pre |YEAR|`,
worldState_incompatibleWith: `[UNTRANSLATED] Incompatible with:`,

View File

@ -257,6 +257,8 @@ dict = {
worldState_hallowedNightmaresRewards: `Награды Священных Кошмаров`,
worldState_proxyRebellion: `Восстание Роботов`,
worldState_proxyRebellionRewards: `Награды Восстания Роботов`,
worldState_bellyOfTheBeast: `Чрево зверя`,
worldState_bellyOfTheBeastProgressOverride: `[UNTRANSLATED] Belly of the Beast Progress`,
worldState_from_year: `из |YEAR|`,
worldState_pre_year: `до |YEAR|`,
worldState_incompatibleWith: `Несовместимо с:`,

View File

@ -257,6 +257,8 @@ dict = {
worldState_hallowedNightmaresRewards: `Нагороди Священних Жахіть`,
worldState_proxyRebellion: `Повстання роботів`,
worldState_proxyRebellionRewards: `Нагороди Повстання роботів`,
worldState_bellyOfTheBeast: `У лігві звіра`,
worldState_bellyOfTheBeastProgressOverride: `[UNTRANSLATED] Belly of the Beast Progress`,
worldState_from_year: `з |YEAR|`,
worldState_pre_year: `до |YEAR|`,
worldState_incompatibleWith: `Несумісне з:`,

View File

@ -257,6 +257,8 @@ dict = {
worldState_hallowedNightmaresRewards: `[UNTRANSLATED] Hallowed Nightmares Rewards`,
worldState_proxyRebellion: `机械叛乱`,
worldState_proxyRebellionRewards: `[UNTRANSLATED] Proxy Rebellion Rewards`,
worldState_bellyOfTheBeast: `兽之腹`,
worldState_bellyOfTheBeastProgressOverride: `[UNTRANSLATED] Belly of the Beast Progress`,
worldState_from_year: `[UNTRANSLATED] from |YEAR|`,
worldState_pre_year: `[UNTRANSLATED] pre |YEAR|`,
worldState_incompatibleWith: `[UNTRANSLATED] Incompatible with:`,