diff --git a/css/sleeves.scss b/css/sleeves.scss new file mode 100644 index 000000000..f395f5fab --- /dev/null +++ b/css/sleeves.scss @@ -0,0 +1,9 @@ +/** + * Styling for the Sleeves Management page + */ +@import "theme"; + +.sleeve-container { + border: 1px solid white; + margin: 4px; +} diff --git a/src/Constants.ts b/src/Constants.ts index 355fc3b04..293a2cda0 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -508,6 +508,10 @@ export let CONSTANTS: IMap = { LatestUpdate: ` v0.43.0 + * Stock Market Changes: + ** Each stock now has a maximum number of shares you can purchase (both Long and Short positions combined) + ** Added getStockMaxShares() Netscript function to the TIX API + * Home Computer RAM is now capped at 2 ^ 30 GB (1073741824 GB) ` diff --git a/src/PersonObjects/Person.ts b/src/PersonObjects/Person.ts index e63328e10..527ade7be 100644 --- a/src/PersonObjects/Person.ts +++ b/src/PersonObjects/Person.ts @@ -51,14 +51,6 @@ export interface ICrime { kills: number; } -// Interface for Faction object -// Used because at the time of implementation, the Faction object has not been -// converted to TypeScript -export interface IFaction { - name: string; - playerReputation: number; -} - // Interface that defines a generic object used to track experience/money // earnings for tasks export interface ITaskTracker { @@ -129,6 +121,12 @@ export abstract class Person { work_money_mult: number; + /** + * Augmentations + */ + this.augmentations = []; + this.queuedAugmentations = []; + /** * City that the person is in */ diff --git a/src/PersonObjects/Sleeve/Sleeve.ts b/src/PersonObjects/Sleeve/Sleeve.ts index 6a65aba19..9d2daf79c 100644 --- a/src/PersonObjects/Sleeve/Sleeve.ts +++ b/src/PersonObjects/Sleeve/Sleeve.ts @@ -1,6 +1,6 @@ /** - * Sleeves are clones of the player that can be used to perform - * different tasks synchronously. + * Sleeves are bodies that contain the player's cloned consciousness. + * The player can use these bodies to perform different tasks synchronously. * * Each sleeve is its own individual, meaning it has its own stats/exp * @@ -11,7 +11,6 @@ import { SleeveTaskType } from "./SleeveTaskTypesEnum"; import { Person, IPlayer, ICrime, - IFaction, ITaskTracker, createTaskTracker } from "../Person"; @@ -101,6 +100,8 @@ export class Sleeve extends Person { * Sleeve shock. Number between 1 and 100 * Trauma/shock that comes with being in a sleeve. Experience earned * is multipled by shock%. This gets applied before synchronization + * + * Reputation earned is also multiplied by shock% */ shock: number = 1; @@ -251,6 +252,7 @@ export class Sleeve extends Person { * Earn money for player */ gainMoney(p: IPlayer, task: ITaskTracker, numCycles: number=1): void { + this.earningsForPlayer.money += (task.money * numCycles); p.gainMoney(task.money * numCycles); } @@ -262,17 +264,17 @@ export class Sleeve extends Person { if (this.currentTask === SleeveTaskType.Faction) { switch (this.factionWorkType) { case FactionWorkType.Hacking: - return this.getFactionHackingWorkRepGain(); + return this.getFactionHackingWorkRepGain() * (this.shock / 100); case FactionWorkType.Field: - return this.getFactionFieldWorkRepGain(); + return this.getFactionFieldWorkRepGain() * (this.shock / 100); case FactionWorkType.Security: - return this.getFactionSecurityWorkRepGain(); + return this.getFactionSecurityWorkRepGain() * (this.shock / 100); default: console.warn(`Invalid Sleeve.factionWorkType property in Sleeve.getRepGain(): ${this.factionWorkType}`); return 0; } } else if (this.currentTask === SleeveTaskType.Company) { - return 0; + return 0; // TODO } else { console.warn(`Sleeve.getRepGain() called for invalid task type: ${this.currentTask}`); return 0; @@ -327,16 +329,21 @@ export class Sleeve extends Person { retValue = this.gainExperience(p, this.gainRatesForTask, cyclesUsed); this.gainMoney(p, this.gainRatesForTask, cyclesUsed); - // TODO REP for both this and company - const fac = Factions[this.currentTaskLocation]; + // Gain faction reputation + const fac: Faction = Factions[this.currentTaskLocation]; if (!(fac instanceof Faction)) { console.error(`Invalid faction for Sleeve task: ${this.currentTaskLocation}`); break; } + + const repGainPerCycle: number = this.getRepGain(); + fac.playerReputation += (repGainPerCycle * cyclesUsed); break; case SleeveTaskType.Company: retValue = this.gainExperience(p, this.gainRatesForTask, cyclesUsed); this.gainMoney(p, this.gainRatesForTask, cyclesUsed); + + // TODO Rep gain for this break; case SleeveTaskType.Recovery: this.shock = Math.max(100, this.shock + (0.001 * this.storedCycles)); diff --git a/src/PersonObjects/Sleeve/SleeveUI.ts b/src/PersonObjects/Sleeve/SleeveUI.ts new file mode 100644 index 000000000..e048e01b3 --- /dev/null +++ b/src/PersonObjects/Sleeve/SleeveUI.ts @@ -0,0 +1,166 @@ +/** + * Module for handling the Sleeve UI + */ +import { Sleeve } from "./Sleeve"; +import { SleeveTaskType } from "./SleeveTaskTypesEnum"; + +import { IMap } from "../../types"; + +import { Page, + routing } from "../../ui/navigationTracking"; + +import { exceptionAlert } from "../../../utils/helpers/exceptionAlert"; + +import { createElement } from "../../../utils/uiHelpers/createElement"; +import { createOptionElement } from "../../../utils/uiHelpers/createOptionElement"; +import { removeElement } from "../../../utils/uiHelpers/removeElement"; +import { removeElementById } from "../../../utils/uiHelpers/removeElementById"; + +// Object that keeps track of all DOM elements for the UI for a single Sleeve +interface ISleeveUIElems { + container: Element | null, + statsPanel: Element | null, + stats: Element | null, + statsTooltip: Element | null, + taskPanel: Element | null, + taskSelector: Element | null, + taskDetailsSelector: Element | null, + taskDescription: Element | null, + earningsPanel: Element | null, + currentEarningsInfo: Element | null, + totalEarningsInfo: Element | null, +} + +// Object that keeps track of all DOM elements for the entire Sleeve UI +interface IPageUIElems { + container: Element | null; + info: Element | null, + sleeveList: Element | null, + sleeves: ISleeveUIElems[] | null, +} + +const UIElems: IPageUIElems = { + container: null, + info: null, + sleeveList: null, + sleeves: null, +} + +// Interface for Player object +interface IPlayer { + sleeves: Sleeve[]; +} + +// Creates the UI for the entire Sleeves page +export function createSleevesPage(p: IPlayer) { + if (!routing.isOn(Page.Sleeves)) { return; } + + try { + UIElems.container = createElement("div", { + class: "generic-menupage-container", + id: "sleeves-container", + position: "fixed", + }); + + UIElems.info = createElement("p", { + display: "inline-block", + innerText: "Sleeves are MK-V Synthoids (synthetic androids) into which your " + + "consciousness has copied. In other words, these Synthoids contain " + + "a perfect duplicate of your mind.

" + + "Sleeves can be used to perform different tasks synchronously.", + }); + + UIElems.sleeveList = createElement("ul"); + UIElems.sleeves = []; + + for (const sleeve of p.sleeves) { + UIElems.sleeves.push(this.createSleeveUi(sleeve, p.sleeves)); + } + + UIElems.container.appendChild(UIElems.info); + UIElems.container.appendChild(UIElems.sleeveList); + + document.getElementById("entire-game-container")!.appendChild(UIElems.container); + } catch(e) { + exceptionAlert(e); + } +} + +// Updates the UI for the entire Sleeves page +export function updateSleevesPage() { + if (!routing.isOn(Page.Sleeves)) { return; } +} + +export function clearSleevesPage() { + removeElement(UIElems.container); + for (const prop in UIElems) { + UIElems[prop] = null; + } +} + +// Creates the UI for a single Sleeve +// Returns an object containing the DOM elements in the UI (ISleeveUIElems) +function createSleeveUi(sleeve: Sleeve, allSleeves: Sleeve[]) { + if (!routing.isOn(Page.Sleeves)) { return; } + + const elems: ISleeveUIElems = { + container: null, + statsPanel: null, + stats: null, + statsTooltip: null, + taskPanel: null, + taskSelector: null, + taskDetailsSelector: null, + taskDescription: null, + earningsPanel: null, + currentEarningsInfo: null, + totalEarningsButton: null, + } + + elems.container = createElement("div", { + class: "sleeve-container", + display: "block", + }); + + elems.statsPanel = createElement("div", { class: "sleeve-panel" }); + elems.stats = createElement("p", { class: "sleeve-stats-text tooltip" }); + elems.statsTooltip = createElement("span", { class: "tooltiptext" }); + elems.stats.appendChild(elems.statsTooltip); + elems.statsPanel.appendChild(elems.stats); + + elems.taskPanel = createElement("div", { class: "sleeve-panel" }); + elems.taskSelector = createElement("select"); + elems.taskSelector.add(createOptionElement("------")); + elems.taskSelector.add(createOptionElement("Work for Company")); + elems.taskSelector.add(createOptionElement("Work for Faction")); + elems.taskSelector.add(createOptionElement("Commit Crime")); + elems.taskSelector.add(createOptionElement("Take University Course")); + elems.taskSelector.add(createOptionElement("Workout at Gym")); + elems.taskSelector.add(createOptionElement("Shock Recovery")); + elems.taskSelector.add(createOptionElement("Synchronize")); + elems.taskSelector.addEventListener("change", () => { + updateSleeveTaskSelector(sleeve, elems, allSleeves); + }); + // TODO Set initial value for task selector + elems.taskDetailsSelector = createElement("select"); + elems.taskDescription = createElement("p"); + elems.taskPanel.appendChild(elems.taskSelector); + elems.taskPanel.appendChild(elems.taskDetailsSelector); + elems.taskPanel.appendChild(elems.taskDescription); + + elems.earningsPanel = createElement("div", { class: "sleeve-panel" }); + elems.currentEarningsInfo = createElement("p"); + elems.totalEarningsButton = createElement("button", { class: "std-button" }); + + return elems; +} + +// Updates the UI for a single Sleeve +function updateSleeveUi() { + if (!routing.isOn(Page.Sleeves)) { return; } +} + +// Whenever a new task is selected, the "details" selector must update accordingly +function updateSleeveTaskSelector(sleeve: Sleeve, elems: ISleeveUIElems, allSleeves: Sleeve[]) { + const value: string = +} diff --git a/src/Player.js b/src/Player.js index ee71e1087..32da31c88 100644 --- a/src/Player.js +++ b/src/Player.js @@ -190,6 +190,9 @@ function PlayerObject() { this.bladeburner_analysis_mult = 1; //Field Analysis Only this.bladeburner_success_chance_mult = 1; + // Sleeves + this.sleeves = []; + //bitnode this.bitNodeN = 1; diff --git a/src/ui/navigationTracking.ts b/src/ui/navigationTracking.ts index 62c256319..c55801d9d 100644 --- a/src/ui/navigationTracking.ts +++ b/src/ui/navigationTracking.ts @@ -112,6 +112,11 @@ export enum Page { * Manage special Bladeburner activities. */ Bladeburner = "Bladeburner", + + /** + * Manage your Sleeves + */ + Sleeves = "Sleeves", } /** diff --git a/utils/helpers/exceptionAlert.js b/utils/helpers/exceptionAlert.ts similarity index 76% rename from utils/helpers/exceptionAlert.js rename to utils/helpers/exceptionAlert.ts index e652cd8e9..c5bc571e3 100644 --- a/utils/helpers/exceptionAlert.js +++ b/utils/helpers/exceptionAlert.ts @@ -1,16 +1,16 @@ import { dialogBoxCreate } from "../DialogBox"; -function exceptionAlert(e) { +interface IError { + fileName?: string, + lineNumber?: number, +} + +export function exceptionAlert(e: IError): void { dialogBoxCreate("Caught an exception: " + e + "

" + "Filename: " + (e.fileName || "UNKNOWN FILE NAME") + "

" + "Line Number: " + (e.lineNumber || "UNKNOWN LINE NUMBER") + "

" + "This is a bug, please report to game developer with this " + "message as well as details about how to reproduce the bug.

" + "If you want to be safe, I suggest refreshing the game WITHOUT saving so that your " + - "safe doesn't get corrupted"); + "safe doesn't get corrupted", false); } - - -export { - exceptionAlert -} \ No newline at end of file diff --git a/utils/uiHelpers/createOptionElement.ts b/utils/uiHelpers/createOptionElement.ts new file mode 100644 index 000000000..c1c56347f --- /dev/null +++ b/utils/uiHelpers/createOptionElement.ts @@ -0,0 +1,11 @@ +import { createElement } from "./createElement"; + +export function createOptionElement(text: string, value: string="") { + const sanitizedValue: string = value; + if (sanitizedValue === "") { sanitizedValue = text; } + + return createElement("option", { + text: text, + value: sanitizedValue, + }); +}