feat: classic lich guess history (#2129)
Some checks failed
Build / build (push) Has been cancelled
Build Docker image / docker (push) Has been cancelled

Closes #2123

Reviewed-on: #2129
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-06-07 16:46:18 -07:00 committed by OrdisPrime
parent b7c47b91ff
commit 135b1e54fe
3 changed files with 103 additions and 42 deletions

View File

@ -1,12 +1,17 @@
import { version_compare } from "@/src/helpers/inventoryHelpers"; import { version_compare } from "@/src/helpers/inventoryHelpers";
import { import {
consumeModCharge, consumeModCharge,
decodeNemesisGuess,
encodeNemesisGuess, encodeNemesisGuess,
getInfNodes, getInfNodes,
getKnifeUpgrade, getKnifeUpgrade,
getNemesisManifest, getNemesisManifest,
getNemesisPasscode, getNemesisPasscode,
getNemesisPasscodeModTypes, getNemesisPasscodeModTypes,
GUESS_CORRECT,
GUESS_INCORRECT,
GUESS_NEUTRAL,
GUESS_NONE,
GUESS_WILDCARD, GUESS_WILDCARD,
IKnifeResponse IKnifeResponse
} from "@/src/helpers/nemesisHelpers"; } from "@/src/helpers/nemesisHelpers";
@ -98,18 +103,29 @@ export const nemesisController: RequestHandler = async (req, res) => {
if (inventory.Nemesis!.Faction == "FC_INFESTATION") { if (inventory.Nemesis!.Faction == "FC_INFESTATION") {
const guess: number[] = [body.guess & 0xf, (body.guess >> 4) & 0xf, (body.guess >> 8) & 0xf]; const guess: number[] = [body.guess & 0xf, (body.guess >> 4) & 0xf, (body.guess >> 8) & 0xf];
const passcode = getNemesisPasscode(inventory.Nemesis!)[0]; const passcode = getNemesisPasscode(inventory.Nemesis!)[0];
const result1 = passcode == guess[0] ? GUESS_CORRECT : GUESS_INCORRECT;
// Add to GuessHistory const result2 = passcode == guess[1] ? GUESS_CORRECT : GUESS_INCORRECT;
const result1 = passcode == guess[0] ? 0 : 1; const result3 = passcode == guess[2] ? GUESS_CORRECT : GUESS_INCORRECT;
const result2 = passcode == guess[1] ? 0 : 1;
const result3 = passcode == guess[2] ? 0 : 1;
inventory.Nemesis!.GuessHistory.push( inventory.Nemesis!.GuessHistory.push(
encodeNemesisGuess(guess[0], result1, guess[1], result2, guess[2], result3) encodeNemesisGuess([
{
symbol: guess[0],
result: result1
},
{
symbol: guess[1],
result: result2
},
{
symbol: guess[2],
result: result3
}
])
); );
// Increase antivirus if correct antivirus mod is installed // Increase antivirus if correct antivirus mod is installed
const response: IKnifeResponse = {}; const response: IKnifeResponse = {};
if (result1 == 0 || result2 == 0 || result3 == 0) { if (result1 == GUESS_CORRECT || result2 == GUESS_CORRECT || result3 == GUESS_CORRECT) {
let antivirusGain = 5; let antivirusGain = 5;
const loadout = (await Loadout.findById(inventory.LoadOutPresets, "DATAKNIFE"))!; const loadout = (await Loadout.findById(inventory.LoadOutPresets, "DATAKNIFE"))!;
const dataknifeLoadout = loadout.DATAKNIFE.id(inventory.CurrentLoadOutIds[LoadoutIndex.DATAKNIFE].$oid); const dataknifeLoadout = loadout.DATAKNIFE.id(inventory.CurrentLoadOutIds[LoadoutIndex.DATAKNIFE].$oid);
@ -150,19 +166,47 @@ export const nemesisController: RequestHandler = async (req, res) => {
await inventory.save(); await inventory.save();
res.json(response); res.json(response);
} else { } else {
let RankIncrease: number | undefined; // For first guess, create a new entry.
if (body.guess != GUESS_WILDCARD) { if (body.position == 0) {
const passcode = getNemesisPasscode(inventory.Nemesis!); inventory.Nemesis!.GuessHistory.push(
if (passcode[body.position] != body.guess) { encodeNemesisGuess([
const manifest = getNemesisManifest(inventory.Nemesis!.manifest); {
if (inventory.Nemesis!.Rank + 1 < manifest.systemIndexes.length) { symbol: GUESS_NONE,
inventory.Nemesis!.Rank += 1; result: GUESS_NEUTRAL
RankIncrease = 1; },
} {
inventory.Nemesis!.InfNodes = getInfNodes(manifest, inventory.Nemesis!.Rank); symbol: GUESS_NONE,
await inventory.save(); result: GUESS_NEUTRAL
} },
{
symbol: GUESS_NONE,
result: GUESS_NEUTRAL
}
])
);
} }
// Evaluate guess
const correct =
body.guess == GUESS_WILDCARD || getNemesisPasscode(inventory.Nemesis!)[body.position] == body.guess;
// Update entry
const guess = decodeNemesisGuess(
inventory.Nemesis!.GuessHistory[inventory.Nemesis!.GuessHistory.length - 1]
);
guess[body.position].symbol = body.guess;
guess[body.position].result = correct ? GUESS_CORRECT : GUESS_INCORRECT;
inventory.Nemesis!.GuessHistory[inventory.Nemesis!.GuessHistory.length - 1] = encodeNemesisGuess(guess);
// Increase rank if incorrect
let RankIncrease: number | undefined;
if (!correct) {
RankIncrease = 1;
const manifest = getNemesisManifest(inventory.Nemesis!.manifest);
inventory.Nemesis!.Rank = Math.min(inventory.Nemesis!.Rank + 1, manifest.systemIndexes.length - 1);
inventory.Nemesis!.InfNodes = getInfNodes(manifest, inventory.Nemesis!.Rank);
}
await inventory.save();
res.json({ RankIncrease }); res.json({ RankIncrease });
} }
} else if ((req.query.mode as string) == "rs") { } else if ((req.query.mode as string) == "rs") {

View File

@ -237,7 +237,7 @@ export const getNemesisPasscode = (nemesis: { fp: bigint; Faction: TNemesisFacti
return passcode; return passcode;
}; };
const reqiuemMods: readonly string[] = [ const requiemMods: readonly string[] = [
"/Lotus/Upgrades/Mods/Immortal/ImmortalOneMod", "/Lotus/Upgrades/Mods/Immortal/ImmortalOneMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalTwoMod", "/Lotus/Upgrades/Mods/Immortal/ImmortalTwoMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalThreeMod", "/Lotus/Upgrades/Mods/Immortal/ImmortalThreeMod",
@ -263,32 +263,51 @@ export const getNemesisPasscodeModTypes = (nemesis: { fp: bigint; Faction: TNeme
const passcode = getNemesisPasscode(nemesis); const passcode = getNemesisPasscode(nemesis);
return nemesis.Faction == "FC_INFESTATION" return nemesis.Faction == "FC_INFESTATION"
? passcode.map(i => antivirusMods[i]) ? passcode.map(i => antivirusMods[i])
: passcode.map(i => reqiuemMods[i]); : passcode.map(i => requiemMods[i]);
}; };
// Symbols; 0-7 are the normal requiem mods.
export const GUESS_NONE = 8; export const GUESS_NONE = 8;
export const GUESS_WILDCARD = 9; export const GUESS_WILDCARD = 9;
export const encodeNemesisGuess = ( // Results; there are 3, 4, 5 as well which are more muted versions but unused afaik.
symbol1: number, export const GUESS_NEUTRAL = 0;
result1: number, export const GUESS_INCORRECT = 1;
symbol2: number, export const GUESS_CORRECT = 2;
result2: number,
symbol3: number, interface NemesisPositionGuess {
result3: number symbol: number;
): number => { result: number;
}
export type NemesisGuess = [NemesisPositionGuess, NemesisPositionGuess, NemesisPositionGuess];
export const encodeNemesisGuess = (guess: NemesisGuess): number => {
return ( return (
(symbol1 & 0xf) | (guess[0].symbol & 0xf) |
((result1 & 3) << 12) | ((guess[0].result & 3) << 12) |
((symbol2 << 4) & 0xff) | ((guess[1].symbol << 4) & 0xff) |
((result2 << 14) & 0xffff) | ((guess[1].result << 14) & 0xffff) |
((symbol3 & 0xf) << 8) | ((guess[2].symbol & 0xf) << 8) |
((result3 & 3) << 16) ((guess[2].result & 3) << 16)
); );
}; };
export const decodeNemesisGuess = (val: number): number[] => { export const decodeNemesisGuess = (val: number): NemesisGuess => {
return [val & 0xf, (val >> 12) & 3, (val & 0xff) >> 4, (val & 0xffff) >> 14, (val >> 8) & 0xf, (val >> 16) & 3]; return [
{
symbol: val & 0xf,
result: (val >> 12) & 3
},
{
symbol: (val & 0xff) >> 4,
result: (val & 0xffff) >> 14
},
{
symbol: (val >> 8) & 0xf,
result: (val >> 16) & 3
}
];
}; };
export interface IKnifeResponse { export interface IKnifeResponse {

View File

@ -1182,14 +1182,12 @@ export const addMissionRewards = async (
if (nodeIndex !== -1) inventory.Nemesis.InfNodes.splice(nodeIndex, 1); if (nodeIndex !== -1) inventory.Nemesis.InfNodes.splice(nodeIndex, 1);
if (inventory.Nemesis.InfNodes.length <= 0) { if (inventory.Nemesis.InfNodes.length <= 0) {
const manifest = getNemesisManifest(inventory.Nemesis.manifest);
if (inventory.Nemesis.Faction != "FC_INFESTATION") { if (inventory.Nemesis.Faction != "FC_INFESTATION") {
inventory.Nemesis.Rank = Math.min(inventory.Nemesis.Rank + 1, 4); inventory.Nemesis.Rank = Math.min(inventory.Nemesis.Rank + 1, manifest.systemIndexes.length - 1);
inventoryChanges.Nemesis.Rank = inventory.Nemesis.Rank; inventoryChanges.Nemesis.Rank = inventory.Nemesis.Rank;
} }
inventory.Nemesis.InfNodes = getInfNodes( inventory.Nemesis.InfNodes = getInfNodes(manifest, inventory.Nemesis.Rank);
getNemesisManifest(inventory.Nemesis.manifest),
inventory.Nemesis.Rank
);
} }
if (inventory.Nemesis.Faction == "FC_INFESTATION") { if (inventory.Nemesis.Faction == "FC_INFESTATION") {