XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

SpaceNinjaServer

A simple server for a small space ninja game

公开
关注 0 Fork 1 Star 0
返回提交历史

XFEstudio/SpaceNinjaServer

feat: guild ads (#1390)

Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/1390 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>

48598c14
Sainan <63328889+Sainan@users.noreply.github.com>
提交于

代码差异

11 个文件 +198 -14
Added src/controllers/api/cancelGuildAdvertisementController.ts +20 -0
@@ -0,0 +1,20 @@
1 import { GuildAd } from "@/src/models/guildModel";
2 import { getGuildForRequestEx, hasGuildPermission } from "@/src/services/guildService";
3 import { getInventory } from "@/src/services/inventoryService";
4 import { getAccountIdForRequest } from "@/src/services/loginService";
5 import { GuildPermission } from "@/src/types/guildTypes";
6 import { RequestHandler } from "express";
7
8 export const cancelGuildAdvertisementController: RequestHandler = async (req, res) => {
9 const accountId = await getAccountIdForRequest(req);
10 const inventory = await getInventory(accountId, "GuildId");
11 const guild = await getGuildForRequestEx(req, inventory);
12 if (!(await hasGuildPermission(guild, accountId, GuildPermission.Advertiser))) {
13 res.status(400).end();
14 return;
15 }
16
17 await GuildAd.deleteOne({ GuildId: guild._id });
18
19 res.end();
20 };
Modified src/controllers/api/contributeToDojoComponentController.ts +2 -2
@@ -1,6 +1,7 @@
1 1 import { GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
2 2 import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
3 3 import {
4 addGuildMemberMiscItemContribution,
4 5 getDojoClient,
5 6 getGuildForRequestEx,
6 7 hasAccessToDojo,
@@ -143,8 +144,7 @@ const processContribution = (
143 144 ItemCount: ingredientContribution.ItemCount * -1
144 145 });
145 146
146 guildMember.MiscItemsContributed ??= [];
147 guildMember.MiscItemsContributed.push(ingredientContribution);
147 addGuildMemberMiscItemContribution(guildMember, ingredientContribution);
148 148 }
149 149 addMiscItems(inventory, miscItemChanges);
150 150 inventoryChanges.MiscItems = miscItemChanges;
Modified src/controllers/api/contributeToVaultController.ts +6 -8
@@ -1,5 +1,9 @@
1 1 import { GuildMember } from "@/src/models/guildModel";
2 import { addVaultMiscItems, getGuildForRequestEx } from "@/src/services/guildService";
2 import {
3 addGuildMemberMiscItemContribution,
4 addVaultMiscItems,
5 getGuildForRequestEx
6 } from "@/src/services/guildService";
3 7 import { addFusionTreasures, addMiscItems, addShipDecorations, getInventory } from "@/src/services/inventoryService";
4 8 import { getAccountIdForRequest } from "@/src/services/loginService";
5 9 import { IFusionTreasure, IMiscItem, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
@@ -25,14 +29,8 @@ export const contributeToVaultController: RequestHandler = async (req, res) => {
25 29 if (request.MiscItems.length) {
26 30 addVaultMiscItems(guild, request.MiscItems);
27 31
28 guildMember.MiscItemsContributed ??= [];
29 32 for (const item of request.MiscItems) {
30 const miscItemContribution = guildMember.MiscItemsContributed.find(x => x.ItemType == item.ItemType);
31 if (miscItemContribution) {
32 miscItemContribution.ItemCount += item.ItemCount;
33 } else {
34 guildMember.MiscItemsContributed.push(item);
35 }
33 addGuildMemberMiscItemContribution(guildMember, item);
36 34
37 35 addMiscItems(inventory, [{ ...item, ItemCount: item.ItemCount * -1 }]);
38 36 }
Modified src/controllers/api/guildTechController.ts +2 -2
@@ -1,5 +1,6 @@
1 1 import { RequestHandler } from "express";
2 2 import {
3 addGuildMemberMiscItemContribution,
3 4 getGuildForRequestEx,
4 5 getGuildVault,
5 6 hasAccessToDojo,
@@ -146,8 +147,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
146 147 ItemCount: miscItem.ItemCount * -1
147 148 });
148 149
149 guildMember.MiscItemsContributed ??= [];
150 guildMember.MiscItemsContributed.push(miscItem);
150 addGuildMemberMiscItemContribution(guildMember, miscItem);
151 151 }
152 152 }
153 153 addMiscItems(inventory, miscItemChanges);
Added src/controllers/api/postGuildAdvertisementController.ts +75 -0
@@ -0,0 +1,75 @@
1 import { getJSONfromString } from "@/src/helpers/stringHelpers";
2 import { GuildAd, GuildMember } from "@/src/models/guildModel";
3 import {
4 addGuildMemberMiscItemContribution,
5 addVaultMiscItems,
6 getGuildForRequestEx,
7 getVaultMiscItemCount,
8 hasGuildPermissionEx
9 } from "@/src/services/guildService";
10 import { getInventory } from "@/src/services/inventoryService";
11 import { getAccountIdForRequest } from "@/src/services/loginService";
12 import { getVendorManifestByTypeName, preprocessVendorManifest } from "@/src/services/serversideVendorsService";
13 import { GuildPermission } from "@/src/types/guildTypes";
14 import { IPurchaseParams } from "@/src/types/purchaseTypes";
15 import { RequestHandler } from "express";
16
17 export const postGuildAdvertisementController: RequestHandler = async (req, res) => {
18 const accountId = await getAccountIdForRequest(req);
19 const inventory = await getInventory(accountId, "GuildId MiscItems");
20 const guild = await getGuildForRequestEx(req, inventory);
21 const guildMember = (await GuildMember.findOne({ accountId, guildId: guild._id }))!;
22 if (!hasGuildPermissionEx(guild, guildMember, GuildPermission.Advertiser)) {
23 res.status(400).end();
24 return;
25 }
26 const payload = getJSONfromString<IPostGuildAdvertisementRequest>(String(req.body));
27
28 // Handle resource cost
29 const vendor = preprocessVendorManifest(
30 getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest")!
31 );
32 const offer = vendor.VendorInfo.ItemManifest.find(x => x.StoreItem == payload.PurchaseParams.StoreItem)!;
33 if (getVaultMiscItemCount(guild, offer.ItemPrices![0].ItemType) >= offer.ItemPrices![0].ItemCount) {
34 addVaultMiscItems(guild, [
35 {
36 ItemType: offer.ItemPrices![0].ItemType,
37 ItemCount: offer.ItemPrices![0].ItemCount * -1
38 }
39 ]);
40 } else {
41 const miscItem = inventory.MiscItems.find(x => x.ItemType == offer.ItemPrices![0].ItemType);
42 if (!miscItem || miscItem.ItemCount < offer.ItemPrices![0].ItemCount) {
43 res.status(400).json("Insufficient funds");
44 return;
45 }
46 miscItem.ItemCount -= offer.ItemPrices![0].ItemCount;
47 addGuildMemberMiscItemContribution(guildMember, offer.ItemPrices![0]);
48 await guildMember.save();
49 await inventory.save();
50 }
51
52 // Create or update ad
53 await GuildAd.findOneAndUpdate(
54 { GuildId: guild._id },
55 {
56 Emblem: guild.Emblem,
57 Expiry: new Date(Date.now() + 12 * 3600 * 1000),
58 Features: payload.Features,
59 GuildName: guild.Name,
60 MemberCount: await GuildMember.countDocuments({ guildId: guild._id, status: 0 }),
61 RecruitMsg: payload.RecruitMsg,
62 Tier: guild.Tier
63 },
64 { upsert: true }
65 );
66
67 res.end();
68 };
69
70 interface IPostGuildAdvertisementRequest {
71 Features: number;
72 RecruitMsg: string;
73 Languages: string[];
74 PurchaseParams: IPurchaseParams;
75 }
Added src/controllers/dynamic/getGuildAdsController.ts +26 -0
@@ -0,0 +1,26 @@
1 import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
2 import { GuildAd } from "@/src/models/guildModel";
3 import { IGuildAdInfoClient } from "@/src/types/guildTypes";
4 import { RequestHandler } from "express";
5
6 export const getGuildAdsController: RequestHandler = async (req, res) => {
7 const ads = await GuildAd.find(req.query.tier ? { Tier: req.query.tier } : {});
8 const guildAdInfos: IGuildAdInfoClient[] = [];
9 for (const ad of ads) {
10 guildAdInfos.push({
11 _id: toOid(ad.GuildId),
12 CrossPlatformEnabled: true,
13 Emblem: ad.Emblem,
14 Expiry: toMongoDate(ad.Expiry),
15 Features: ad.Features,
16 GuildName: ad.GuildName,
17 MemberCount: ad.MemberCount,
18 OriginalPlatform: 0,
19 RecruitMsg: ad.RecruitMsg,
20 Tier: ad.Tier
21 });
22 }
23 res.json({
24 GuildAdInfos: guildAdInfos
25 });
26 };
Modified src/models/guildModel.ts +19 -1
@@ -10,7 +10,8 @@ import {
10 10 IGuildLogRoomChange,
11 11 IGuildLogEntryRoster,
12 12 IGuildLogEntryContributable,
13 IDojoLeaderboardEntry
13 IDojoLeaderboardEntry,
14 IGuildAdDatabase
14 15 } from "@/src/types/guildTypes";
15 16 import { Document, Model, model, Schema, Types } from "mongoose";
16 17 import { fusionTreasuresSchema, typeCountSchema } from "./inventoryModels/inventoryModel";
@@ -165,6 +166,7 @@ const guildSchema = new Schema<IGuildDatabase>(
165 166 Ranks: { type: [guildRankSchema], default: defaultRanks },
166 167 TradeTax: { type: Number, default: 0 },
167 168 Tier: { type: Number, default: 1 },
169 Emblem: { type: Boolean },
168 170 DojoComponents: { type: [dojoComponentSchema], default: [] },
169 171 DojoCapacity: { type: Number, default: 100 },
170 172 DojoEnergy: { type: Number, default: 5 },
@@ -225,3 +227,19 @@ const guildMemberSchema = new Schema<IGuildMemberDatabase>({
225 227 guildMemberSchema.index({ accountId: 1, guildId: 1 }, { unique: true });
226 228
227 229 export const GuildMember = model<IGuildMemberDatabase>("GuildMember", guildMemberSchema);
230
231 const guildAdSchema = new Schema<IGuildAdDatabase>({
232 GuildId: { type: Schema.Types.ObjectId, required: true },
233 Emblem: Boolean,
234 Expiry: { type: Date, required: true },
235 Features: { type: Number, required: true },
236 GuildName: { type: String, required: true },
237 MemberCount: { type: Number, required: true },
238 RecruitMsg: { type: String, required: true },
239 Tier: { type: Number, required: true }
240 });
241
242 guildAdSchema.index({ GuildId: 1 }, { unique: true });
243 guildAdSchema.index({ Expiry: 1 }, { expireAfterSeconds: 0 });
244
245 export const GuildAd = model<IGuildAdDatabase>("GuildAd", guildAdSchema);
Modified src/routes/api.ts +4 -0
@@ -9,6 +9,7 @@ import { arcaneCommonController } from "@/src/controllers/api/arcaneCommonContro
9 9 import { archonFusionController } from "@/src/controllers/api/archonFusionController";
10 10 import { artifactsController } from "@/src/controllers/api/artifactsController";
11 11 import { artifactTransmutationController } from "@/src/controllers/api/artifactTransmutationController";
12 import { cancelGuildAdvertisementController } from "@/src/controllers/api/cancelGuildAdvertisementController";
12 13 import { changeDojoRootController } from "@/src/controllers/api/changeDojoRootController";
13 14 import { changeGuildRankController } from "@/src/controllers/api/changeGuildRankController";
14 15 import { checkDailyMissionBonusController } from "@/src/controllers/api/checkDailyMissionBonusController";
@@ -80,6 +81,7 @@ import { nameWeaponController } from "@/src/controllers/api/nameWeaponController
80 81 import { nemesisController } from "@/src/controllers/api/nemesisController";
81 82 import { placeDecoInComponentController } from "@/src/controllers/api/placeDecoInComponentController";
82 83 import { playerSkillsController } from "@/src/controllers/api/playerSkillsController";
84 import { postGuildAdvertisementController } from "@/src/controllers/api/postGuildAdvertisementController";
83 85 import { projectionManagerController } from "@/src/controllers/api/projectionManagerController";
84 86 import { purchaseController } from "@/src/controllers/api/purchaseController";
85 87 import { queueDojoComponentDestructionController } from "@/src/controllers/api/queueDojoComponentDestructionController";
@@ -131,6 +133,7 @@ const apiRouter = express.Router();
131 133 // get
132 134 apiRouter.get("/abandonLibraryDailyTask.php", abandonLibraryDailyTaskController);
133 135 apiRouter.get("/abortDojoComponentDestruction.php", abortDojoComponentDestructionController);
136 apiRouter.get("/cancelGuildAdvertisement.php", cancelGuildAdvertisementController);
134 137 apiRouter.get("/changeGuildRank.php", changeGuildRankController);
135 138 apiRouter.get("/checkDailyMissionBonus.php", checkDailyMissionBonusController);
136 139 apiRouter.get("/claimLibraryDailyTaskReward.php", claimLibraryDailyTaskRewardController);
@@ -228,6 +231,7 @@ apiRouter.post("/nameWeapon.php", nameWeaponController);
228 231 apiRouter.post("/nemesis.php", nemesisController);
229 232 apiRouter.post("/placeDecoInComponent.php", placeDecoInComponentController);
230 233 apiRouter.post("/playerSkills.php", playerSkillsController);
234 apiRouter.post("/postGuildAdvertisement.php", postGuildAdvertisementController);
231 235 apiRouter.post("/projectionManager.php", projectionManagerController);
232 236 apiRouter.post("/purchase.php", purchaseController);
233 237 apiRouter.post("/redeemPromoCode.php", redeemPromoCodeController);
Modified src/routes/dynamic.ts +2 -0
@@ -1,11 +1,13 @@
1 1 import express from "express";
2 2 import { aggregateSessionsController } from "@/src/controllers/dynamic/aggregateSessionsController";
3 import { getGuildAdsController } from "@/src/controllers/dynamic/getGuildAdsController";
3 4 import { getProfileViewingDataController } from "@/src/controllers/dynamic/getProfileViewingDataController";
4 5 import { worldStateController } from "@/src/controllers/dynamic/worldStateController";
5 6
6 7 const dynamicController = express.Router();
7 8
8 9 dynamicController.get("/aggregateSessions.php", aggregateSessionsController);
10 dynamicController.get("/getGuildAds.php", getGuildAdsController);
9 11 dynamicController.get("/getProfileViewingData.php", getProfileViewingDataController);
10 12 dynamicController.get("/worldState.php", worldStateController);
11 13
Modified src/services/guildService.ts +17 -1
@@ -1,7 +1,7 @@
1 1 import { Request } from "express";
2 2 import { getAccountIdForRequest } from "@/src/services/loginService";
3 3 import { addRecipes, getInventory } from "@/src/services/inventoryService";
4 import { Guild, GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
4 import { Guild, GuildAd, GuildMember, TGuildDatabaseDocument } from "@/src/models/guildModel";
5 5 import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
6 6 import {
7 7 GuildPermission,
@@ -298,6 +298,10 @@ const moveResourcesToVault = (guild: TGuildDatabaseDocument, component: IDojoCon
298 298 }
299 299 };
300 300
301 export const getVaultMiscItemCount = (guild: TGuildDatabaseDocument, itemType: string): number => {
302 return guild.VaultMiscItems?.find(x => x.ItemType == itemType)?.ItemCount ?? 0;
303 };
304
301 305 export const addVaultMiscItems = (guild: TGuildDatabaseDocument, miscItems: ITypeCount[]): void => {
302 306 guild.VaultMiscItems ??= [];
303 307 for (const miscItem of miscItems) {
@@ -310,6 +314,16 @@ export const addVaultMiscItems = (guild: TGuildDatabaseDocument, miscItems: ITyp
310 314 }
311 315 };
312 316
317 export const addGuildMemberMiscItemContribution = (guildMember: IGuildMemberDatabase, item: ITypeCount): void => {
318 guildMember.MiscItemsContributed ??= [];
319 const miscItemContribution = guildMember.MiscItemsContributed.find(x => x.ItemType == item.ItemType);
320 if (miscItemContribution) {
321 miscItemContribution.ItemCount += item.ItemCount;
322 } else {
323 guildMember.MiscItemsContributed.push(item);
324 }
325 };
326
313 327 export const processDojoBuildMaterialsGathered = (guild: TGuildDatabaseDocument, build: IDojoBuild): void => {
314 328 if (build.guildXpValue) {
315 329 guild.ClaimedXP ??= [];
@@ -513,4 +527,6 @@ export const deleteGuild = async (guildId: Types.ObjectId): Promise<void> => {
513 527 contextInfo: guildId.toString(),
514 528 acceptAction: "GUILD_INVITE"
515 529 });
530
531 await GuildAd.deleteOne({ GuildId: guildId });
516 532 };
Modified src/types/guildTypes.ts +25 -0
@@ -28,6 +28,7 @@ export interface IGuildDatabase {
28 28 Ranks: IGuildRank[];
29 29 TradeTax: number;
30 30 Tier: number;
31 Emblem?: boolean;
31 32
32 33 DojoComponents: IDojoComponentDatabase[];
33 34 DojoCapacity: number;
@@ -223,3 +224,27 @@ export interface IDojoLeaderboardEntry {
223 224 r: number; // rank
224 225 n: string; // displayName
225 226 }
227
228 export interface IGuildAdInfoClient {
229 _id: IOid; // Guild ID
230 CrossPlatformEnabled: boolean;
231 Emblem?: boolean;
232 Expiry: IMongoDate;
233 Features: number;
234 GuildName: string;
235 MemberCount: number;
236 OriginalPlatform: number;
237 RecruitMsg: string;
238 Tier: number;
239 }
240
241 export interface IGuildAdDatabase {
242 GuildId: Types.ObjectId;
243 Emblem?: boolean;
244 Expiry: Date;
245 Features: number;
246 GuildName: string;
247 MemberCount: number;
248 RecruitMsg: string;
249 Tier: number;
250 }