forked from OpenWF/SpaceNinjaServer
		
	feat: dojo component "collecting materials" stage (#1071)
Closes #1051 Reviewed-on: OpenWF/SpaceNinjaServer#1071 Co-authored-by: Sainan <sainan@calamity.inc> Co-committed-by: Sainan <sainan@calamity.inc>
This commit is contained in:
		
							parent
							
								
									77cadc732c
								
							
						
					
					
						commit
						67a275a009
					
				@ -32,6 +32,7 @@
 | 
			
		||||
  "unlockArcanesEverywhere": true,
 | 
			
		||||
  "noDailyStandingLimits": true,
 | 
			
		||||
  "instantResourceExtractorDrones": false,
 | 
			
		||||
  "noDojoRoomBuildStage": true,
 | 
			
		||||
  "noDojoResearchCosts": true,
 | 
			
		||||
  "noDojoResearchTime": true,
 | 
			
		||||
  "spoofMasteryRank": -1
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										19
									
								
								src/controllers/api/abortDojoComponentController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/controllers/api/abortDojoComponentController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
			
		||||
import { getDojoClient, getGuildForRequestEx } from "@/src/services/guildService";
 | 
			
		||||
import { getInventory } from "@/src/services/inventoryService";
 | 
			
		||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
			
		||||
import { RequestHandler } from "express";
 | 
			
		||||
 | 
			
		||||
export const abortDojoComponentController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const inventory = await getInventory(accountId);
 | 
			
		||||
    const guild = await getGuildForRequestEx(req, inventory);
 | 
			
		||||
    const request = JSON.parse(String(req.body)) as IAbortDojoComponentRequest;
 | 
			
		||||
    // TODO: Move already-contributed credits & items to the clan vault
 | 
			
		||||
    guild.DojoComponents.pull({ _id: request.ComponentId });
 | 
			
		||||
    await guild.save();
 | 
			
		||||
    res.json(getDojoClient(guild, 0));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export interface IAbortDojoComponentRequest {
 | 
			
		||||
    ComponentId: string;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										82
									
								
								src/controllers/api/contributeToDojoComponentController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								src/controllers/api/contributeToDojoComponentController.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,82 @@
 | 
			
		||||
import { getDojoClient, getGuildForRequestEx, scaleRequiredCount } from "@/src/services/guildService";
 | 
			
		||||
import { addMiscItems, getInventory, updateCurrency } from "@/src/services/inventoryService";
 | 
			
		||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
			
		||||
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
 | 
			
		||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
 | 
			
		||||
import { RequestHandler } from "express";
 | 
			
		||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
 | 
			
		||||
 | 
			
		||||
export const contributeToDojoComponentController: RequestHandler = async (req, res) => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
    const inventory = await getInventory(accountId);
 | 
			
		||||
    const guild = await getGuildForRequestEx(req, inventory);
 | 
			
		||||
    const request = JSON.parse(String(req.body)) as IContributeToDojoComponentRequest;
 | 
			
		||||
    const component = guild.DojoComponents.id(request.ComponentId)!;
 | 
			
		||||
    const componentMeta = Object.values(ExportDojoRecipes.rooms).find(x => x.resultType == component.pf)!;
 | 
			
		||||
 | 
			
		||||
    component.RegularCredits ??= 0;
 | 
			
		||||
    if (component.RegularCredits + request.RegularCredits > scaleRequiredCount(componentMeta.price)) {
 | 
			
		||||
        request.RegularCredits = scaleRequiredCount(componentMeta.price) - component.RegularCredits;
 | 
			
		||||
    }
 | 
			
		||||
    component.RegularCredits += request.RegularCredits;
 | 
			
		||||
    const inventoryChanges: IInventoryChanges = updateCurrency(inventory, request.RegularCredits, false);
 | 
			
		||||
 | 
			
		||||
    component.MiscItems ??= [];
 | 
			
		||||
    const miscItemChanges: IMiscItem[] = [];
 | 
			
		||||
    for (const ingredientContribution of request.IngredientContributions) {
 | 
			
		||||
        const componentMiscItem = component.MiscItems.find(x => x.ItemType == ingredientContribution.ItemType);
 | 
			
		||||
        if (componentMiscItem) {
 | 
			
		||||
            const ingredientMeta = componentMeta.ingredients.find(x => x.ItemType == ingredientContribution.ItemType)!;
 | 
			
		||||
            if (
 | 
			
		||||
                componentMiscItem.ItemCount + ingredientContribution.ItemCount >
 | 
			
		||||
                scaleRequiredCount(ingredientMeta.ItemCount)
 | 
			
		||||
            ) {
 | 
			
		||||
                ingredientContribution.ItemCount =
 | 
			
		||||
                    scaleRequiredCount(ingredientMeta.ItemCount) - componentMiscItem.ItemCount;
 | 
			
		||||
            }
 | 
			
		||||
            componentMiscItem.ItemCount += ingredientContribution.ItemCount;
 | 
			
		||||
        } else {
 | 
			
		||||
            component.MiscItems.push(ingredientContribution);
 | 
			
		||||
        }
 | 
			
		||||
        miscItemChanges.push({
 | 
			
		||||
            ItemType: ingredientContribution.ItemType,
 | 
			
		||||
            ItemCount: ingredientContribution.ItemCount * -1
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    addMiscItems(inventory, miscItemChanges);
 | 
			
		||||
    inventoryChanges.MiscItems = miscItemChanges;
 | 
			
		||||
 | 
			
		||||
    if (component.RegularCredits >= scaleRequiredCount(componentMeta.price)) {
 | 
			
		||||
        let fullyFunded = true;
 | 
			
		||||
        for (const ingredient of componentMeta.ingredients) {
 | 
			
		||||
            const componentMiscItem = component.MiscItems.find(x => x.ItemType == ingredient.ItemType);
 | 
			
		||||
            if (!componentMiscItem || componentMiscItem.ItemCount < scaleRequiredCount(ingredient.ItemCount)) {
 | 
			
		||||
                fullyFunded = false;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (fullyFunded) {
 | 
			
		||||
            component.RegularCredits = undefined;
 | 
			
		||||
            component.MiscItems = undefined;
 | 
			
		||||
            component.CompletionTime = new Date(Date.now() + componentMeta.time * 1000);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await guild.save();
 | 
			
		||||
    await inventory.save();
 | 
			
		||||
    res.json({
 | 
			
		||||
        ...getDojoClient(guild, 0, component._id),
 | 
			
		||||
        InventoryChanges: inventoryChanges
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export interface IContributeToDojoComponentRequest {
 | 
			
		||||
    ComponentId: string;
 | 
			
		||||
    IngredientContributions: {
 | 
			
		||||
        ItemType: string;
 | 
			
		||||
        ItemCount: number;
 | 
			
		||||
    }[];
 | 
			
		||||
    RegularCredits: number;
 | 
			
		||||
    VaultIngredientContributions: [];
 | 
			
		||||
    VaultCredits: number;
 | 
			
		||||
}
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
import { RequestHandler } from "express";
 | 
			
		||||
import { getGuildForRequestEx } from "@/src/services/guildService";
 | 
			
		||||
import { getGuildForRequestEx, scaleRequiredCount } from "@/src/services/guildService";
 | 
			
		||||
import { ExportDojoRecipes, IDojoResearch } from "warframe-public-export-plus";
 | 
			
		||||
import { getAccountIdForRequest } from "@/src/services/loginService";
 | 
			
		||||
import { addMiscItems, addRecipes, getInventory, updateCurrency } from "@/src/services/inventoryService";
 | 
			
		||||
@ -130,8 +130,3 @@ interface IGuildTechContributeFields {
 | 
			
		||||
    VaultCredits: number;
 | 
			
		||||
    VaultMiscItems: IMiscItem[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const scaleRequiredCount = (count: number): number => {
 | 
			
		||||
    // The recipes in the export are for Moon clans. For now we'll just assume we only have Ghost clans.
 | 
			
		||||
    return Math.max(1, Math.trunc(count / 100));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,7 @@ export const setDojoComponentMessageController: RequestHandler = async (req, res
 | 
			
		||||
        component.Message = payload.Message;
 | 
			
		||||
    }
 | 
			
		||||
    await guild.save();
 | 
			
		||||
    res.json(getDojoClient(guild, 1));
 | 
			
		||||
    res.json(getDojoClient(guild, 0, component._id));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type SetDojoComponentMessageRequest = { Name: string } | { Message: string };
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,7 @@ import { IDojoComponentClient } from "@/src/types/guildTypes";
 | 
			
		||||
import { getDojoClient, getGuildForRequest } from "@/src/services/guildService";
 | 
			
		||||
import { Types } from "mongoose";
 | 
			
		||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
 | 
			
		||||
import { config } from "@/src/services/configService";
 | 
			
		||||
 | 
			
		||||
interface IStartDojoRecipeRequest {
 | 
			
		||||
    PlacedComponent: IDojoComponentClient;
 | 
			
		||||
@ -20,15 +21,20 @@ export const startDojoRecipeController: RequestHandler = async (req, res) => {
 | 
			
		||||
        guild.DojoEnergy += room.energy;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const component =
 | 
			
		||||
        guild.DojoComponents[
 | 
			
		||||
            guild.DojoComponents.push({
 | 
			
		||||
                _id: new Types.ObjectId(),
 | 
			
		||||
                pf: request.PlacedComponent.pf,
 | 
			
		||||
                ppf: request.PlacedComponent.ppf,
 | 
			
		||||
                pi: new Types.ObjectId(request.PlacedComponent.pi!.$oid),
 | 
			
		||||
                op: request.PlacedComponent.op,
 | 
			
		||||
        pp: request.PlacedComponent.pp,
 | 
			
		||||
        CompletionTime: new Date(Date.now()) // TOOD: Omit this field & handle the "Collecting Materials" state.
 | 
			
		||||
    });
 | 
			
		||||
                pp: request.PlacedComponent.pp
 | 
			
		||||
            }) - 1
 | 
			
		||||
        ];
 | 
			
		||||
    if (config.noDojoRoomBuildStage) {
 | 
			
		||||
        component.CompletionTime = new Date(Date.now());
 | 
			
		||||
    }
 | 
			
		||||
    await guild.save();
 | 
			
		||||
    res.json(getDojoClient(guild, 0));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,8 @@ const dojoComponentSchema = new Schema<IDojoComponentDatabase>({
 | 
			
		||||
    pp: String,
 | 
			
		||||
    Name: String,
 | 
			
		||||
    Message: String,
 | 
			
		||||
    RegularCredits: Number,
 | 
			
		||||
    MiscItems: { type: [typeCountSchema], default: undefined },
 | 
			
		||||
    CompletionTime: Date
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
import express from "express";
 | 
			
		||||
import { abandonLibraryDailyTaskController } from "@/src/controllers/api/abandonLibraryDailyTaskController";
 | 
			
		||||
import { abortDojoComponentController } from "@/src/controllers/api/abortDojoComponentController";
 | 
			
		||||
import { activateRandomModController } from "@/src/controllers/api/activateRandomModController";
 | 
			
		||||
import { addFriendImageController } from "@/src/controllers/api/addFriendImageController";
 | 
			
		||||
import { arcaneCommonController } from "@/src/controllers/api/arcaneCommonController";
 | 
			
		||||
@ -11,6 +12,7 @@ import { claimCompletedRecipeController } from "@/src/controllers/api/claimCompl
 | 
			
		||||
import { claimLibraryDailyTaskRewardController } from "@/src/controllers/api/claimLibraryDailyTaskRewardController";
 | 
			
		||||
import { clearDialogueHistoryController } from "@/src/controllers/api/clearDialogueHistoryController";
 | 
			
		||||
import { completeRandomModChallengeController } from "@/src/controllers/api/completeRandomModChallengeController";
 | 
			
		||||
import { contributeToDojoComponentController } from "@/src/controllers/api/contributeToDojoComponentController";
 | 
			
		||||
import { createGuildController } from "@/src/controllers/api/createGuildController";
 | 
			
		||||
import { creditsController } from "@/src/controllers/api/creditsController";
 | 
			
		||||
import { deleteSessionController } from "@/src/controllers/api/deleteSessionController";
 | 
			
		||||
@ -135,6 +137,7 @@ apiRouter.get("/surveys.php", surveysController);
 | 
			
		||||
apiRouter.get("/updateSession.php", updateSessionGetController);
 | 
			
		||||
 | 
			
		||||
// post
 | 
			
		||||
apiRouter.post("/abortDojoComponent.php", abortDojoComponentController);
 | 
			
		||||
apiRouter.post("/activateRandomMod.php", activateRandomModController);
 | 
			
		||||
apiRouter.post("/addFriendImage.php", addFriendImageController);
 | 
			
		||||
apiRouter.post("/arcaneCommon.php", arcaneCommonController);
 | 
			
		||||
@ -144,6 +147,7 @@ apiRouter.post("/changeDojoRoot.php", changeDojoRootController);
 | 
			
		||||
apiRouter.post("/claimCompletedRecipe.php", claimCompletedRecipeController);
 | 
			
		||||
apiRouter.post("/clearDialogueHistory.php", clearDialogueHistoryController);
 | 
			
		||||
apiRouter.post("/completeRandomModChallenge.php", completeRandomModChallengeController);
 | 
			
		||||
apiRouter.post("/contributeToDojoComponent.php", contributeToDojoComponentController);
 | 
			
		||||
apiRouter.post("/createGuild.php", createGuildController);
 | 
			
		||||
apiRouter.post("/drones.php", dronesController);
 | 
			
		||||
apiRouter.post("/endlessXp.php", endlessXpController);
 | 
			
		||||
 | 
			
		||||
@ -58,6 +58,7 @@ interface IConfig {
 | 
			
		||||
    unlockArcanesEverywhere?: boolean;
 | 
			
		||||
    noDailyStandingLimits?: boolean;
 | 
			
		||||
    instantResourceExtractorDrones?: boolean;
 | 
			
		||||
    noDojoRoomBuildStage?: boolean;
 | 
			
		||||
    noDojoResearchCosts?: boolean;
 | 
			
		||||
    noDojoResearchTime?: boolean;
 | 
			
		||||
    spoofMasteryRank?: number;
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,7 @@ import { Guild, TGuildDatabaseDocument } from "@/src/models/guildModel";
 | 
			
		||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
 | 
			
		||||
import { IDojoClient, IDojoComponentClient } from "@/src/types/guildTypes";
 | 
			
		||||
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
 | 
			
		||||
import { Types } from "mongoose";
 | 
			
		||||
 | 
			
		||||
export const getGuildForRequest = async (req: Request): Promise<TGuildDatabaseDocument> => {
 | 
			
		||||
    const accountId = await getAccountIdForRequest(req);
 | 
			
		||||
@ -27,7 +28,11 @@ export const getGuildForRequestEx = async (
 | 
			
		||||
    return guild;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getDojoClient = (guild: TGuildDatabaseDocument, status: number): IDojoClient => {
 | 
			
		||||
export const getDojoClient = (
 | 
			
		||||
    guild: TGuildDatabaseDocument,
 | 
			
		||||
    status: number,
 | 
			
		||||
    componentId: Types.ObjectId | undefined = undefined
 | 
			
		||||
): IDojoClient => {
 | 
			
		||||
    const dojo: IDojoClient = {
 | 
			
		||||
        _id: { $oid: guild._id.toString() },
 | 
			
		||||
        Name: guild.Name,
 | 
			
		||||
@ -41,6 +46,7 @@ export const getDojoClient = (guild: TGuildDatabaseDocument, status: number): ID
 | 
			
		||||
        DojoComponents: []
 | 
			
		||||
    };
 | 
			
		||||
    guild.DojoComponents.forEach(dojoComponent => {
 | 
			
		||||
        if (!componentId || componentId == dojoComponent._id) {
 | 
			
		||||
            const clientComponent: IDojoComponentClient = {
 | 
			
		||||
                id: toOid(dojoComponent._id),
 | 
			
		||||
                pf: dojoComponent.pf,
 | 
			
		||||
@ -56,8 +62,17 @@ export const getDojoClient = (guild: TGuildDatabaseDocument, status: number): ID
 | 
			
		||||
            }
 | 
			
		||||
            if (dojoComponent.CompletionTime) {
 | 
			
		||||
                clientComponent.CompletionTime = toMongoDate(dojoComponent.CompletionTime);
 | 
			
		||||
            } else {
 | 
			
		||||
                clientComponent.RegularCredits = dojoComponent.RegularCredits;
 | 
			
		||||
                clientComponent.MiscItems = dojoComponent.MiscItems;
 | 
			
		||||
            }
 | 
			
		||||
            dojo.DojoComponents.push(clientComponent);
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    return dojo;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const scaleRequiredCount = (count: number): number => {
 | 
			
		||||
    // The recipes in the export are for Moon clans. For now we'll just assume we only have Ghost clans.
 | 
			
		||||
    return Math.max(1, Math.trunc(count / 100));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -521,6 +521,10 @@
 | 
			
		||||
                                        <input class="form-check-input" type="checkbox" id="instantResourceExtractorDrones" />
 | 
			
		||||
                                        <label class="form-check-label" for="instantResourceExtractorDrones" data-loc="cheats_instantResourceExtractorDrones"></label>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    <div class="form-check">
 | 
			
		||||
                                        <input class="form-check-input" type="checkbox" id="noDojoRoomBuildStage" />
 | 
			
		||||
                                        <label class="form-check-label" for="noDojoRoomBuildStage" data-loc="cheats_noDojoRoomBuildStage"></label>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    <div class="form-check">
 | 
			
		||||
                                        <input class="form-check-input" type="checkbox" id="noDojoResearchCosts" />
 | 
			
		||||
                                        <label class="form-check-label" for="noDojoResearchCosts" data-loc="cheats_noDojoResearchCosts"></label>
 | 
			
		||||
 | 
			
		||||
@ -112,6 +112,7 @@ dict = {
 | 
			
		||||
    cheats_unlockArcanesEverywhere: `Arcane Adapters Everywhere`,
 | 
			
		||||
    cheats_noDailyStandingLimits: `No Daily Standing Limits`,
 | 
			
		||||
    cheats_instantResourceExtractorDrones: `Instant Resource Extractor Drones`,
 | 
			
		||||
    cheats_noDojoRoomBuildStage: `No Dojo Room Build Stage`,
 | 
			
		||||
    cheats_noDojoResearchCosts: `No Dojo Research Costs`,
 | 
			
		||||
    cheats_noDojoResearchTime: `No Dojo Research Time`,
 | 
			
		||||
    cheats_spoofMasteryRank: `Spoofed Mastery Rank (-1 to disable)`,
 | 
			
		||||
 | 
			
		||||
@ -113,6 +113,7 @@ dict = {
 | 
			
		||||
    cheats_unlockArcanesEverywhere: `Адаптеры для мистификаторов везде`,
 | 
			
		||||
    cheats_noDailyStandingLimits: `Без ежедневных ограничений репутации`,
 | 
			
		||||
    cheats_instantResourceExtractorDrones: `[UNTRANSLATED] Instant Resource Extractor Drones`,
 | 
			
		||||
    cheats_noDojoRoomBuildStage: `[UNTRANSLATED] No Dojo Room Build Stage`,
 | 
			
		||||
    cheats_noDojoResearchCosts: `[UNTRANSLATED] No Dojo Research Costs`,
 | 
			
		||||
    cheats_noDojoResearchTime: `[UNTRANSLATED] No Dojo Research Time`,
 | 
			
		||||
    cheats_spoofMasteryRank: `Подделанный ранг мастерства (-1 для отключения)`,
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user