feat: personal quarters loadout, stencil, vignette, & fish customisation (#1619)

Closes #1618

Reviewed-on: OpenWF/SpaceNinjaServer#1619
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
This commit is contained in:
Sainan 2025-04-14 07:16:25 -07:00 committed by Sainan
parent c64d466ce1
commit 827ea47468
7 changed files with 122 additions and 38 deletions

View File

@ -26,7 +26,10 @@ export const getShipController: RequestHandler = async (req, res) => {
Colors: personalRooms.ShipInteriorColors,
ShipAttachments: ship.ShipAttachments,
SkinFlavourItem: ship.SkinFlavourItem
}
},
FavouriteLoadoutId: personalRooms.Ship.FavouriteLoadoutId
? toOid(personalRooms.Ship.FavouriteLoadoutId)
: undefined
},
Apartment: personalRooms.Apartment,
TailorShop: personalRooms.TailorShop

View File

@ -3,29 +3,40 @@ import { RequestHandler } from "express";
import { getPersonalRooms } from "@/src/services/personalRoomsService";
import { IOid } from "@/src/types/commonTypes";
import { Types } from "mongoose";
import { IFavouriteLoadoutDatabase, TBootLocation } from "@/src/types/shipTypes";
export const setShipFavouriteLoadoutController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const personalRooms = await getPersonalRooms(accountId);
const body = JSON.parse(String(req.body)) as ISetShipFavouriteLoadoutRequest;
if (body.BootLocation != "SHOP") {
throw new Error(`unexpected BootLocation: ${body.BootLocation}`);
}
const display = personalRooms.TailorShop.FavouriteLoadouts.find(x => x.Tag == body.TagName);
if (display) {
display.LoadoutId = new Types.ObjectId(body.FavouriteLoadoutId.$oid);
if (body.BootLocation == "LISET") {
personalRooms.Ship.FavouriteLoadoutId = new Types.ObjectId(body.FavouriteLoadoutId.$oid);
} else if (body.BootLocation == "APARTMENT") {
updateTaggedDisplay(personalRooms.Apartment.FavouriteLoadouts, body);
} else if (body.BootLocation == "SHOP") {
updateTaggedDisplay(personalRooms.TailorShop.FavouriteLoadouts, body);
} else {
personalRooms.TailorShop.FavouriteLoadouts.push({
Tag: body.TagName,
LoadoutId: new Types.ObjectId(body.FavouriteLoadoutId.$oid)
});
console.log(body);
throw new Error(`unexpected BootLocation: ${body.BootLocation}`);
}
await personalRooms.save();
res.json({});
};
interface ISetShipFavouriteLoadoutRequest {
BootLocation: string;
BootLocation: TBootLocation;
FavouriteLoadoutId: IOid;
TagName: string;
TagName?: string;
}
const updateTaggedDisplay = (arr: IFavouriteLoadoutDatabase[], body: ISetShipFavouriteLoadoutRequest): void => {
const display = arr.find(x => x.Tag == body.TagName!);
if (display) {
display.LoadoutId = new Types.ObjectId(body.FavouriteLoadoutId.$oid);
} else {
arr.push({
Tag: body.TagName!,
LoadoutId: new Types.ObjectId(body.FavouriteLoadoutId.$oid)
});
}
};

View File

@ -0,0 +1,48 @@
import { addMiscItems, combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getPersonalRooms } from "@/src/services/personalRoomsService";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express";
export const setShipVignetteController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "MiscItems");
const personalRooms = await getPersonalRooms(accountId);
const body = JSON.parse(String(req.body)) as ISetShipVignetteRequest;
personalRooms.Ship.Wallpaper = body.Wallpaper;
personalRooms.Ship.Vignette = body.Vignette;
personalRooms.Ship.VignetteFish ??= [];
const inventoryChanges: IInventoryChanges = {};
for (let i = 0; i != body.Fish.length; ++i) {
if (body.Fish[i] && !personalRooms.Ship.VignetteFish[i]) {
logger.debug(`moving ${body.Fish[i]} from inventory to vignette slot ${i}`);
const miscItemsDelta = [{ ItemType: body.Fish[i], ItemCount: -1 }];
addMiscItems(inventory, miscItemsDelta);
combineInventoryChanges(inventoryChanges, { MiscItems: miscItemsDelta });
} else if (personalRooms.Ship.VignetteFish[i] && !body.Fish[i]) {
logger.debug(`moving ${personalRooms.Ship.VignetteFish[i]} from vignette slot ${i} to inventory`);
const miscItemsDelta = [{ ItemType: personalRooms.Ship.VignetteFish[i], ItemCount: +1 }];
addMiscItems(inventory, miscItemsDelta);
combineInventoryChanges(inventoryChanges, { MiscItems: miscItemsDelta });
}
}
personalRooms.Ship.VignetteFish = body.Fish;
if (body.VignetteDecos.length) {
logger.error(`setShipVignette request not fully handled:`, body);
}
await Promise.all([inventory.save(), personalRooms.save()]);
res.json({
Wallpaper: body.Wallpaper,
Vignette: body.Vignette,
VignetteFish: body.Fish,
InventoryChanges: inventoryChanges
});
};
interface ISetShipVignetteRequest {
Wallpaper: string;
Vignette: string;
Fish: string[];
VignetteDecos: unknown[];
}

View File

@ -2,13 +2,13 @@ import { toOid } from "@/src/helpers/inventoryHelpers";
import { colorSchema } from "@/src/models/inventoryModels/inventoryModel";
import { IOrbiter, IPersonalRoomsDatabase, PersonalRoomsModelType } from "@/src/types/personalRoomsTypes";
import {
IApartment,
IFavouriteLoadoutDatabase,
IGardening,
IPlacedDecosDatabase,
IPictureFrameInfo,
IRoom,
ITailorShopDatabase
ITailorShopDatabase,
IApartmentDatabase
} from "@/src/types/shipTypes";
import { Schema, model } from "mongoose";
@ -62,19 +62,34 @@ const roomSchema = new Schema<IRoom>(
{ _id: false }
);
const favouriteLoadoutSchema = new Schema<IFavouriteLoadoutDatabase>(
{
Tag: String,
LoadoutId: Schema.Types.ObjectId
},
{ _id: false }
);
favouriteLoadoutSchema.set("toJSON", {
virtuals: true,
transform(_document, returnedObject) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
returnedObject.LoadoutId = toOid(returnedObject.LoadoutId);
}
});
const gardeningSchema = new Schema<IGardening>({
Planters: [Schema.Types.Mixed] //TODO: add when implementing gardening
});
const apartmentSchema = new Schema<IApartment>(
const apartmentSchema = new Schema<IApartmentDatabase>(
{
Rooms: [roomSchema],
FavouriteLoadouts: [Schema.Types.Mixed],
FavouriteLoadouts: [favouriteLoadoutSchema],
Gardening: gardeningSchema // TODO: ensure this is correct
},
{ _id: false }
);
const apartmentDefault: IApartment = {
const apartmentDefault: IApartmentDatabase = {
Rooms: [
{ Name: "ElevatorLanding", MaxCapacity: 1600 },
{ Name: "ApartmentRoomA", MaxCapacity: 1000 },
@ -90,6 +105,10 @@ const orbiterSchema = new Schema<IOrbiter>(
{
Features: [String],
Rooms: [roomSchema],
VignetteFish: { type: [String], default: undefined },
FavouriteLoadoutId: Schema.Types.ObjectId,
Wallpaper: String,
Vignette: String,
ContentUrlSignature: { type: String, required: false },
BootLocation: String
},
@ -107,21 +126,6 @@ const orbiterDefault: IOrbiter = {
]
};
const favouriteLoadoutSchema = new Schema<IFavouriteLoadoutDatabase>(
{
Tag: String,
LoadoutId: Schema.Types.ObjectId
},
{ _id: false }
);
favouriteLoadoutSchema.set("toJSON", {
virtuals: true,
transform(_document, returnedObject) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
returnedObject.LoadoutId = toOid(returnedObject.LoadoutId);
}
});
const tailorShopSchema = new Schema<ITailorShopDatabase>(
{
FavouriteLoadouts: [favouriteLoadoutSchema],

View File

@ -115,6 +115,7 @@ import { setGuildMotdController } from "@/src/controllers/api/setGuildMotdContro
import { setPlacedDecoInfoController } from "@/src/controllers/api/setPlacedDecoInfoController";
import { setShipCustomizationsController } from "@/src/controllers/api/setShipCustomizationsController";
import { setShipFavouriteLoadoutController } from "@/src/controllers/api/setShipFavouriteLoadoutController";
import { setShipVignetteController } from "@/src/controllers/api/setShipVignetteController";
import { setSupportedSyndicateController } from "@/src/controllers/api/setSupportedSyndicateController";
import { setWeaponSkillTreeController } from "@/src/controllers/api/setWeaponSkillTreeController";
import { shipDecorationsController } from "@/src/controllers/api/shipDecorationsController";
@ -277,6 +278,7 @@ apiRouter.post("/setGuildMotd.php", setGuildMotdController);
apiRouter.post("/setPlacedDecoInfo.php", setPlacedDecoInfoController);
apiRouter.post("/setShipCustomizations.php", setShipCustomizationsController);
apiRouter.post("/setShipFavouriteLoadout.php", setShipFavouriteLoadoutController);
apiRouter.post("/setShipVignette.php", setShipVignetteController);
apiRouter.post("/setWeaponSkillTree.php", setWeaponSkillTreeController);
apiRouter.post("/shipDecorations.php", shipDecorationsController);
apiRouter.post("/startCollectibleEntry.php", startCollectibleEntryController);

View File

@ -5,13 +5,18 @@ import {
IPlacedDecosDatabase,
ITailorShop,
ITailorShopDatabase,
TBootLocation
TBootLocation,
IApartmentDatabase
} from "@/src/types/shipTypes";
import { Document, Model, Types } from "mongoose";
export interface IOrbiter {
Features: string[];
Rooms: IRoom[];
VignetteFish?: string[];
FavouriteLoadoutId?: Types.ObjectId;
Wallpaper?: string;
Vignette?: string;
ContentUrlSignature?: string;
BootLocation?: TBootLocation;
}
@ -28,7 +33,7 @@ export interface IPersonalRoomsDatabase {
personalRoomsOwnerId: Types.ObjectId;
activeShipId: Types.ObjectId;
Ship: IOrbiter;
Apartment: IApartment;
Apartment: IApartmentDatabase;
TailorShop: ITailorShopDatabase;
}
@ -38,7 +43,7 @@ export type PersonalRoomsDocumentProps = {
Ship: Omit<IOrbiter, "Rooms"> & {
Rooms: RoomsType[];
};
Apartment: Omit<IApartment, "Rooms"> & {
Apartment: Omit<IApartmentDatabase, "Rooms"> & {
Rooms: RoomsType[];
};
TailorShop: Omit<ITailorShopDatabase, "Rooms"> & {

View File

@ -28,8 +28,12 @@ export interface IShip {
ShipId: IOid;
ShipInterior: IShipInterior;
Rooms: IRoom[];
ContentUrlSignature?: string;
VignetteFish?: string[];
FavouriteLoadoutId?: IOid;
Wallpaper?: string;
Vignette?: string;
BootLocation?: TBootLocation;
ContentUrlSignature?: string;
}
export interface IShipDatabase {
@ -60,10 +64,17 @@ export interface IPlanters {
export interface IGardening {
Planters?: IPlanters[];
}
export interface IApartment {
Gardening: IGardening;
Rooms: IRoom[];
FavouriteLoadouts: string[];
FavouriteLoadouts: IFavouriteLoadout[];
}
export interface IApartmentDatabase {
Gardening: IGardening;
Rooms: IRoom[];
FavouriteLoadouts: IFavouriteLoadoutDatabase[];
}
export interface IPlacedDecosDatabase {