Finished ResleeveUI implementation (untested)

This commit is contained in:
danielyxie 2019-01-17 07:40:43 -08:00
parent 19f65de555
commit 10231b6c66
11 changed files with 320 additions and 73 deletions

@ -0,0 +1,59 @@
// Interface for an object that represents the player (PlayerObject)
// Used because at the time of implementation, the PlayerObject
// cant be converted to TypeScript.
//
// Only contains the needed properties for Sleeve implementation
import { Resleeve } from "./Resleeving/Resleeve";
import { Sleeve } from "./Sleeve/Sleeve";
import { IMap } from "../types";
import { IPlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation";
export interface IPlayer {
augmentations: IPlayerOwnedAugmentation[];
companyName: string;
factions: string[];
jobs: IMap<string>;
money: any;
queuedAugmentations: IPlayerOwnedAugmentation[];
resleeves: Resleeve[];
sleeves: Sleeve[];
hacking_skill: number;
strength: number;
defense: number;
dexterity: number;
agility: number;
charisma: number;
intelligence: number;
hacking_exp: number;
strength_exp: number;
defense_exp: number;
dexterity_exp: number;
agility_exp: number;
charisma_exp: number;
crime_success_mult: number;
gainHackingExp(exp: number): void;
gainStrengthExp(exp: number): void;
gainDefenseExp(exp: number): void;
gainDexterityExp(exp: number): void;
gainAgilityExp(exp: number): void;
gainCharismaExp(exp: number): void;
gainMoney(money: number): void;
loseMoney(money: number): void;
reapplyAllAugmentations(resetMultipliers: boolean): void;
startCrime(crimeType: string,
hackExp: number,
strExp: number,
defExp: number,
dexExp: number,
agiExp: number,
chaExp: number,
money: number,
time: number,
singParams: any): void;
}

@ -1,61 +1,12 @@
// Base class representing a person-like object
import { Augmentation } from "../Augmentation/Augmentation";
import { Augmentations } from "../Augmentation/Augmentations";
import { IPlayerOwnedAugmentation } from "../Augmentation/PlayerOwnedAugmentation";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
import { Cities } from "../Locations/Cities";
import { CONSTANTS } from "../Constants";
import { IMap } from "../types";
// Interface for an object that represents the player (PlayerObject)
// Used because at the time of implementation, the PlayerObject
// cant be converted to TypeScript.
//
// Only contains the needed properties for Sleeve implementation
export interface IPlayer {
augmentations: IPlayerOwnedAugmentation[];
companyName: string;
factions: string[];
jobs: IMap<string>;
money: any;
queuedAugmentations: IPlayerOwnedAugmentation[];
hacking_skill: number;
strength: number;
defense: number;
dexterity: number;
agility: number;
charisma: number;
intelligence: number;
hacking_exp: number;
strength_exp: number;
defense_exp: number;
dexterity_exp: number;
agility_exp: number;
charisma_exp: number;
crime_success_mult: number;
gainHackingExp(exp: number): void;
gainStrengthExp(exp: number): void;
gainDefenseExp(exp: number): void;
gainDexterityExp(exp: number): void;
gainAgilityExp(exp: number): void;
gainCharismaExp(exp: number): void;
gainMoney(money: number): void;
loseMoney(money: number): void;
reapplyAllAugmentations(resetMultipliers: boolean): void;
startCrime(crimeType: string,
hackExp: number,
strExp: number,
defExp: number,
dexExp: number,
agiExp: number,
chaExp: number,
money: number,
time: number,
singParams: any): void;
}
// Interface that defines a generic object used to track experience/money
// earnings for tasks
export interface ITaskTracker {
@ -147,6 +98,19 @@ export abstract class Person {
constructor() {}
/**
* Updates this object's multipliers for the given augmentation
*/
applyAugmentation(aug: Augmentation, reapply=false) {
for (const mult in aug.mults) {
if ((<any>this)[mult] == null) {
console.warn(`Augmentation has unrecognized multiplier property: ${mult}`);
} else {
(<any>this)[mult] *= aug.mults[mult];
}
}
}
/**
* Given an experience amount and stat multiplier, calculates the
* stat level. Stat-agnostic (same formula for every stat)

@ -7,4 +7,4 @@ the user to use it in other BitNodes (provided that they purchase the required
cortical stack Augmentation)
While they are based on the same concept, this feature is different than the
"Sleeve" mechanic.
"Duplicate Sleeve" mechanic (which is referred to as just "Sleeve" in the source code).

@ -8,7 +8,16 @@ import { Augmentation } from "../../Augmentation/Augmentation";
import { Augmentations } from "../../Augmentation/Augmentations";
import { CONSTANTS } from "../../Constants";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../../../utils/JSONReviver";
export class Resleeve extends Person {
/**
* Initiatizes a Resleeve object from a JSON save state.
*/
static fromJSON(value: any): Resleeve {
return Generic_fromJSON(Resleeve, value.data);
}
constructor() {
super();
}
@ -42,4 +51,12 @@ export class Resleeve extends Person {
return (totalExp * CostPerExp) + (totalAugmentationCost * Math.pow(this.augmentations.length, NumAugsExponent));
}
/**
* Serialize the current object to a JSON save state.
*/
toJSON(): any {
return Generic_toJSON("Resleeve", this);
}
}
Reviver.constructors.Resleeve = Resleeve;

@ -11,17 +11,19 @@
* As of right now, this feature is only available in BitNode 10
*/
import { Resleeve } from "./Resleeve";
import { IPlayer } from "../Person";
import { IPlayer } from "../IPlayer";
import { Augmentation } from "../../Augmentation/Augmentation";
import { Augmentations } from "../../Augmentation/Augmentations";
import { PlayerOwnedAugmentation } from "../../Augmentation/PlayerOwnedAugmentation";
import { IPlayerOwnedAugmentation,
PlayerOwnedAugmentation } from "../../Augmentation/PlayerOwnedAugmentation";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { getRandomInt } from "../../../utils/helpers/getRandomInt";
// Executes the actual re-sleeve when one is purchased
export function resleeve(r: Resleeve, p: IPlayer):void {
export function purchaseResleeve(r: Resleeve, p: IPlayer):void {
// Set the player's exp
p.hacking_exp = r.hacking_exp;
p.strength_exp = r.strength_exp;
@ -30,15 +32,28 @@ export function resleeve(r: Resleeve, p: IPlayer):void {
p.agility_exp = r.agility_exp;
p.charisma_exp = r.charisma_exp;
// Clear all of the player's augmentations, including those that are
// purchased but not installed
p.queuedAugmentations.length = 0;
p.augmentations.length = 0;
// Clear all of the player's augmentations, except the NeuroFlux Governor
// which is kept
for (let i = p.augmentations.length - 1; i >= 0; --i) {
if (p.augmentations[i].name !== AugmentationNames.NeuroFluxGovernor) {
p.augmentations.splice(i, 1);
}
}
for (let i = 0; i < r.augmentations.length; ++i) {
p.augmentations.push(new PlayerOwnedAugmentation(r.augmentations[i].name));
}
// The player's purchased Augmentations should remain the same, but any purchased
// Augmentations that are given by the resleeve should be removed so there are no duplicates
for (let i = p.queuedAugmentations.length - 1; i >= 0; --i) {
const name: string = p.queuedAugmentations[i].name;
if (p.augmentations.filter((e: IPlayerOwnedAugmentation) => {e.name !== AugmentationNames.NeuroFluxGovernor && e.name === name}).length >= 1) {
p.queuedAugmentations.splice(i, 1);
}
}
p.reapplyAllAugmentations(true);
}

@ -3,17 +3,196 @@
*/
import { Resleeve } from "./Resleeve";
import { generateResleeves,
resleeve } from "./Resleeving";
purchaseResleeve } from "./Resleeving";
import { IPlayer } from "../IPlayer";
import { IMap } from "../../types";
import { Augmentation } from "../../Augmentation/Augmentation";
import { Augmentations } from "../../Augmentation/Augmentations";
import { numeralWrapper } from "../../ui/numeralFormat";
import { Page,
routing } from "../../ui/navigationTracking";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { exceptionAlert } from "../../../utils/helpers/exceptionAlert";
import { createElement } from "../../../utils/uiHelpers/createElement";
import { createOptionElement } from "../../../utils/uiHelpers/createOptionElement";
import { getSelectValue } from "../../../utils/uiHelpers/getSelectData";
import { removeChildrenFromElement } from "../../../utils/uiHelpers/removeChildrenFromElement";
import { removeElement } from "../../../utils/uiHelpers/removeElement";
import { removeElementById } from "../../../utils/uiHelpers/removeElementById";
interface IResleeveUIElems {
container: HTMLElement | null;
statsPanel: HTMLElement | null;
stats: HTMLElement | null;
multipliersButton: HTMLElement | null;
augPanel: HTMLElement | null;
augSelector: HTMLSelectElement | null;
augDescription: HTMLElement | null;
costPanel: HTMLElement | null;
costText: HTMLElement | null;
buyButton: HTMLElement | null;
}
interface IPageUIElems {
container: HTMLElement | null;
info: HTMLElement | null;
resleeveList: HTMLElement | null;
resleeves: IResleeveUIElems[] | null;
}
const UIElems: IPageUIElems = {
container: null,
info: null,
resleeveList: null,
resleeves: null,
}
let playerRef: IPlayer | null;
export function createResleevesPage(p: IPlayer) {
if (!routing.isOn(Page.Resleeves)) { return; }
try {
playerRef = p;
UIElems.container = createElement("div", {
class: "generic-menupage-container",
id: "resleeves-container",
position: "fixed",
});
UIElems.info = createElement("p", {
display: "inline-block",
innerText: "TOODOOO",
});
UIElems.resleeveList = createElement("ul");
UIElems.resleeves = [];
if (p.resleeves.length === 0) {
p.resleeves = generateResleeves();
}
for (const resleeve of p.resleeves) {
const resleeveUi = createResleeveUi(resleeve);
UIElems.resleeveList.appendChild(resleeveUi.container!);
UIElems.resleeves.push(resleeveUi);
}
UIElems.container.appendChild(UIElems.info);
UIElems.container.appendChild(UIElems.resleeveList);
document.getElementById("entire-game-container")!.appendChild(UIElems.container);
} catch(e) {
exceptionAlert(e);
}
}
export function clearResleevesPage() {
removeElement(UIElems.container);
for (const prop in UIElems) {
(<any>UIElems)[prop] = null;
}
playerRef = null;
}
function createResleeveUi(resleeve: Resleeve): IResleeveUIElems {
const elems: IResleeveUIElems = {
container: null,
statsPanel: null,
stats: null,
multipliersButton: null,
augPanel: null,
augSelector: null,
augDescription: null,
costPanel: null,
costText: null,
buyButton: null,
};
if (!routing.isOn(Page.Resleeves)) { return elems; }
elems.container = createElement("div", {
class: "resleeve-container",
display: "block",
});
elems.statsPanel = createElement("div", { class: "resleeve-panel" });
elems.stats = createElement("p", { class: "resleeve-stats-text" });
elems.multipliersButton = createElement("button", {
class: "std-button",
innerText: "Multipliers",
clickListener: () => {
dialogBoxCreate(
[
"<h2><u>Total Multipliers:</u></h2>",
`Hacking Level multiplier: ${numeralWrapper.formatPercentage(resleeve.hacking_mult)}`,
`Hacking Experience multiplier: ${numeralWrapper.formatPercentage(resleeve.hacking_exp_mult)}`,
`Strength Level multiplier: ${numeralWrapper.formatPercentage(resleeve.strength_mult)}`,
`Strength Experience multiplier: ${numeralWrapper.formatPercentage(resleeve.strength_exp_mult)}`,
`Defense Level multiplier: ${numeralWrapper.formatPercentage(resleeve.defense_mult)}`,
`Defense Experience multiplier: ${numeralWrapper.formatPercentage(resleeve.defense_exp_mult)}`,
`Dexterity Level multiplier: ${numeralWrapper.formatPercentage(resleeve.dexterity_mult)}`,
`Dexterity Experience multiplier: ${numeralWrapper.formatPercentage(resleeve.dexterity_exp_mult)}`,
`Agility Level multiplier: ${numeralWrapper.formatPercentage(resleeve.agility_mult)}`,
`Agility Experience multiplier: ${numeralWrapper.formatPercentage(resleeve.agility_exp_mult)}`,
`Charisma Level multiplier: ${numeralWrapper.formatPercentage(resleeve.charisma_mult)}`,
`Charisma Experience multiplier: ${numeralWrapper.formatPercentage(resleeve.charisma_exp_mult)}`,
`Hacking Chance multiplier: ${numeralWrapper.formatPercentage(resleeve.hacking_chance_mult)}`,
`Hacking Speed multiplier: ${numeralWrapper.formatPercentage(resleeve.hacking_speed_mult)}`,
`Hacking Money multiplier: ${numeralWrapper.formatPercentage(resleeve.hacking_money_mult)}`,
`Hacking Growth multiplier: ${numeralWrapper.formatPercentage(resleeve.hacking_grow_mult)}`,
`Salary multiplier: ${numeralWrapper.formatPercentage(resleeve.work_money_mult)}`,
`Company Reputation Gain multiplier: ${numeralWrapper.formatPercentage(resleeve.company_rep_mult)}`,
`Faction Reputation Gain multiplier: ${numeralWrapper.formatPercentage(resleeve.faction_rep_mult)}`,
`Crime Money multiplier: ${numeralWrapper.formatPercentage(resleeve.crime_money_mult)}`,
`Crime Success multiplier: ${numeralWrapper.formatPercentage(resleeve.crime_success_mult)}`,
].join("<br>"), false
)
}
});
elems.augPanel = createElement("div", { class: "resleeve-panel" });
elems.augSelector = createElement("select") as HTMLSelectElement;
for (let i = 0; i < resleeve.augmentations.length; ++i) {
elems.augSelector.add(createOptionElement(resleeve.augmentations[i].name));
};
elems.augSelector.addEventListener("change", () => {
updateAugDescription(elems);
});
elems.augDescription = createElement("p");
elems.costPanel = createElement("div", { class: "resleeve-panel" });
elems.costText = createElement("p", {
innerText: `It costs ${numeralWrapper.formatMoney(resleeve.getCost())} ` +
`to purchase this Sleeve.`,
});
elems.buyButton = createElement("button", {
class: "std-button",
innerText: "Purchase",
clickListener: () => {
purchaseResleeve(resleeve, playerRef!);
}
});
return elems;
}
function updateAugDescription(elems: IResleeveUIElems) {
const augName: string = getSelectValue(elems.augSelector);
const aug: Augmentation | null = Augmentations[augName];
if (aug == null) {
console.warn(`Could not find Augmentation with name ${augName}`);
return;
}
elems.augDescription.innerHTML = aug!.info;
}

@ -0,0 +1,9 @@
Implements the "Duplicate Sleeves" feature, which allows the player to purchase
new duplicate sleeves. These are synthetic bodies that contain the player's
cloned consciousness. The player can use these sleeves to perform
different tasks synchronously.
This feature is introduced and unlocked in BitNode-10.
Note that while they are based on the same concept, this feature is different
than the "Re-sleeving" mechanic (which is referred to as "Resleeve" in the source code).

@ -8,8 +8,8 @@
*/
import { SleeveTaskType } from "./SleeveTaskTypesEnum";
import { IPlayer } from "../IPlayer";
import { Person,
IPlayer,
ITaskTracker,
createTaskTracker } from "../Person";

@ -4,7 +4,7 @@
import { Sleeve } from "./Sleeve";
import { SleeveTaskType } from "./SleeveTaskTypesEnum";
import { IPlayer } from "../Person";
import { IPlayer } from "../IPlayer";
import { Locations } from "../../Locations";
@ -47,9 +47,9 @@ interface ISleeveUIElems {
// Object that keeps track of all DOM elements for the entire Sleeve UI
interface IPageUIElems {
container: Element | null;
info: Element | null,
sleeveList: Element | null,
container: HTMLElement | null;
info: HTMLElement | null,
sleeveList: HTMLElement | null,
sleeves: ISleeveUIElems[] | null,
}
@ -60,14 +60,9 @@ const UIElems: IPageUIElems = {
sleeves: null,
}
// Interface for Player object
interface ISleeveUiPlayer extends IPlayer {
sleeves: Sleeve[];
}
// Creates the UI for the entire Sleeves page
let playerRef: ISleeveUiPlayer | null;
export function createSleevesPage(p: ISleeveUiPlayer) {
let playerRef: IPlayer | null;
export function createSleevesPage(p: IPlayer) {
if (!routing.isOn(Page.Sleeves)) { return; }
try {
@ -147,7 +142,7 @@ function createSleeveUi(sleeve: Sleeve, allSleeves: Sleeve[]): ISleeveUIElems {
});
elems.statsPanel = createElement("div", { class: "sleeve-panel" });
elems.stats = createElement("p", { class: "sleeve-stats-text tooltip" });
elems.stats = createElement("p", { class: "sleeve-stats-text" });
elems.moreStatsButton = createElement("button", {
class: "std-button",
innerText: "More Stats",

@ -477,6 +477,10 @@ const Engine = {
}
},
loadSleevesContent: function() {
},
//Helper function that hides all content
hideAllContent: function() {
Engine.Display.terminalContent.style.display = "none";

@ -117,6 +117,11 @@ export enum Page {
* Manage your Sleeves
*/
Sleeves = "Sleeves",
/**
* Purchase Resleeves
*/
Resleeves = "Re-sleeving",
}
/**