Fixed bugs with Sleeve mechanics. Updated documentation to use RTD theme

This commit is contained in:
danielyxie 2019-01-20 14:57:38 -08:00
parent 17bfbfeb80
commit 5573e778bb
43 changed files with 4064 additions and 2491 deletions

@ -63,7 +63,7 @@
z-index: 10;
width: 50%;
height: auto;
max-height: 40%;
max-height: 50%;
top: 40%;
left: 50%;
margin: -10% 0 0 -25%;

22
css/redpill.scss Normal file

@ -0,0 +1,22 @@
@import "theme";
/**
* Styling for the Red Pill screen (the BitNode selection UI)
*/
#red-pill-container {
position: fixed;
}
.bitnode {
color: #00f;
}
.bitnode-destroyed {
color: #f00;
}
.bitnode:hover,
.bitnode-destroyed:hover {
color: #fff;
}

28
css/resleeving.scss Normal file

@ -0,0 +1,28 @@
/**
* Styling for the Re-Sleeving Page
*/
@import "theme";
.resleeve-container {
border: 1px solid white;
margin: 4px;
width: 75%;
p {
font-size: $defaultFontSize * 0.8125;
}
}
.resleeve-panel {
display: inline-block;
margin: 0px;
padding: 2px;
}
.resleeve-aug-selector {
font-size: $defaultFontSize * 0.8125;
option {
font-size: $defaultFontSize * 0.8125;
}
}

@ -6,4 +6,24 @@
.sleeve-container {
border: 1px solid white;
margin: 4px;
width: 75%;
p {
font-size: $defaultFontSize * 0.875;
}
}
.sleeves-page-info {
display: "block";
width: 75%;
}
.sleeve-panel {
display: inline-block;
margin: 0px;
padding: 2px;
select {
display: block;
}
}

@ -46,18 +46,6 @@
cursor: pointer;
}
#red-pill-container,
#cinematic-text-container {
position: fixed;
}
.bitnode {
color: #00f;
}
.bitnode-destroyed {
color: #f00;
}
.bitnode:hover,
.bitnode-destroyed:hover {
color: #fff;
}

4287
dist/engine.bundle.js vendored

File diff suppressed because it is too large Load Diff

76
dist/engine.css vendored

@ -1222,6 +1222,24 @@ button {
display: inline;
width: 25%; }
/* COLORS */
/* Attributes */
/**
* Styling for the Red Pill screen (the BitNode selection UI)
*/
#red-pill-container {
position: fixed; }
.bitnode {
color: #00f; }
.bitnode-destroyed {
color: #f00; }
.bitnode:hover,
.bitnode-destroyed:hover {
color: #fff; }
/* COLORS */
/* Attributes */
#stock-market-container {
@ -1318,20 +1336,9 @@ button {
text-decoration: none;
cursor: pointer; }
#red-pill-container,
#cinematic-text-container {
position: fixed; }
.bitnode {
color: #00f; }
.bitnode-destroyed {
color: #f00; }
.bitnode:hover,
.bitnode-destroyed:hover {
color: #fff; }
/* COLORS */
/* Attributes */
/* Pop-up boxes */
@ -1393,7 +1400,7 @@ button {
z-index: 10;
width: 50%;
height: auto;
max-height: 40%;
max-height: 50%;
top: 40%;
left: 50%;
margin: -10% 0 0 -25%;
@ -2136,6 +2143,51 @@ button {
margin: 1px;
padding: 1px; }
/**
* Styling for the Sleeves Management page
*/
/* COLORS */
/* Attributes */
.sleeve-container {
border: 1px solid white;
margin: 4px;
width: 75%; }
.sleeve-container p {
font-size: 14px; }
.sleeves-page-info {
display: "block";
width: 75%; }
.sleeve-panel {
display: inline-block;
margin: 0px;
padding: 2px; }
.sleeve-panel select {
display: block; }
/**
* Styling for the Re-Sleeving Page
*/
/* COLORS */
/* Attributes */
.resleeve-container {
border: 1px solid white;
margin: 4px;
width: 75%; }
.resleeve-container p {
font-size: 13px; }
.resleeve-panel {
display: inline-block;
margin: 0px;
padding: 2px; }
.resleeve-aug-selector {
font-size: 13px; }
.resleeve-aug-selector option {
font-size: 13px; }
/* required LIB STYLES */
/* .Treant se automatski dodaje na svaki chart conatiner */
.Treant {

1164
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

@ -0,0 +1,14 @@
Advanced Gameplay
=================
This section documents Bitburner gameplay elements that are **not** immediately
available and/or accessible to the player. These gameplay mechanics
must be unlocked.
.. toctree::
:maxdepth: 5
:caption: Elements:
BitNodes <advancedgameplay/bitnodes>
Source-Files <advancedgameplay/sourcefiles>
Intelligence <advancedgameplay/intelligence>
Sleeves <advancedgameplay/sleeves>

@ -0,0 +1,63 @@
.. _gameplay_bitnodes:
.. warning:: This page contains spoilers regarding the game's story/plot-line.
BitNodes
========
A BitNode is an important part of the game's storyline. In the game, you discover
what BitNodes are by following the trail of clues left by the mysterious jump3r
(essentially a minimal questline).
What is a BitNode
^^^^^^^^^^^^^^^^^
A BitNode is the complex simulated reality in which you reside. By following the messages
from jump3r, you discover that humanity was enslaved by an advanced alien race, called
the Enders, using virtual simulations that trapped the minds of humans.
However, the Enders didn't just create a single virtual reality to enslave humans, but many
different simulations. In other words, there are many different BitNodes that exist.
These BitNode are very different from each other.
jump3r tells you that the only hope for humanity is to destroy all of these BitNodes.
Therefore, the end goal for the player is to enter and then destroy each BitNode at least once.
Destroying a BitNode resets most of the player's progress but grants the player a
powerful second-tier persistent upgrade called a :ref:`Source-File <gameplay_sourcefiles>`.
Different BitNodes grant different Source-Files.
Each BitNode has unique characteristics that are related to varying backstories. For example,
in one BitNode the world is in the middle of a financial catastrophe with a collapsing
market. In this BitNode, most forms of income such as working at a company or Hacknet
Nodes are significantly less profitable. Servers have less money on them and lowered
growth rates, but it is easier to lower their security level using the weaken() Netscript function.
Furthermore, some BitNodes introduce new content and mechanics. For example there is one
BitNode that grants access to the :ref:`Netscript Singularity Functions <netscript_singularityfunctions>`.
There is another BitNode in which you can manage a gang to earn money and reputation.
How to destroy a BitNode
^^^^^^^^^^^^^^^^^^^^^^^^
Initially, the only way to destroy a BitNode is to join the Daedalus :ref:`Daedalus <gameplay_factions>`.
From Daedalus, the player can obtain an Augmentation called 'The Red Pill', which doesn't cost any money
but does require a good amount of faction reputation.
After installing 'The Red Pill', the player must search for and then manually hack a
server called 'w0r1d_d43m0n'. This server requires a hacking level of 3000 in order
to successfully hack it. This will destroy the player's current BitNode.
There is a second method of destroying a BitNode, but it must be unlocked by first
destroying BitNode-6 or BitNode-7 (Bladeburners).
.. todo:: Link to Bladeburner documentation page here
When the player destroys a BitNode, most of his/her progress will be reset. This includes things
such as Augmentations and RAM upgrades on the home computer. The only things that will persist
through destroying BitNodes is:
* Source-Files
* Scripts on the home computer
BitNode Details
^^^^^^^^^^^^^^^
TODO

@ -0,0 +1,21 @@
.. _gameplay_intelligence:
Intelligence
============
Intelligence is a :ref:`stat <gameplay_stats>` that is unlocked by having
:ref:`Source-File 5 <gameplay_sourcefiles>` (i.e. Destroying BitNode-5).
Intelligence is unique because it is permanent and persistent. It never gets reset
back to 1. However, gaining Intelligence experience is extremely slow. The methods
of gaining Intelligence exp is also hidden. You won't know when you gain
experience and how much. It is a stat that gradually builds up as you continue
to play the game.
Intelligence will boost your production for many actions in the game, including:
* Hacking
* Infiltration
* Hacking Missions
* Crime success rate
* Bladeburner
* Reputation gain for companies & factions

@ -0,0 +1,54 @@
.. _gameplay_sleeves:
Sleeves
=======
When VitaLife unveiled their Persona Core technology that allowed people to digitize
and transfer their consciousness into other vessels, human bodies became nothing more
than 'sleeves' for the human consciousness. This technology thus became known as
"Sleeve technology".
Sleeve technology unlocks two different gameplay features:
* Duplicate Sleeves
* Re-sleeving
Sleeve technology is unlocked in :ref:`BitNode-10 <gameplay_bitnodes>`.
Duplicate Sleeves
^^^^^^^^^^^^^^^^^
Duplicate Sleeves are MK-V Synthoids (synthetic androids) into which your consciuosness
has been copied. In other words, these Synthoids contain a perfect duplicate of your mind.
Duplicate Sleeves are essentially clones which you can use to perform work-type actions,
such as working for a company/faction or committing a crime. When sleeves perform these tasks,
they will earn money, experience, and reputation.
Sleeves are their own individuals, which means they each have their own experience and stats.
When a sleeve earns experience, it earns experience for itself, the player's
original consciousness, as well as all of the player's other sleeves.
Synchronization
~~~~~~~~~~~~~~~
Synchronization is a measure of how aligned your consciousness is with that of your
Duplicate Sleeves. It is a numeral value between 1 and 100, and it affects how much experience
is earned when the sleeve is performing a task.
Let N be the sleeve's synchronization. When the sleeve earns experience by performing
a task, both the sleeve and the player's original host consciousness of N% of the
amount of experience normally earned by the task. All of the player's other sleeves
earn ((N/100)^2 * 100)% of the experience.
Synchronization can be increased by assigning sleeves to the 'Synchronize' task.
Sleeve Shock
~~~~~~~~~~~~
Sleeve shock is a measure of how much trauma the sleeve has due to being placed in a new
body. It is a numeral value between 0 and 99, where 99 indicates full shock and 0 indicates
no shock. Shock affects the amount of experience earned by the sleeve.
Sleeve shock slowly decreases over time. You can further increase the rate at which
it decreases by assigning sleeves to the 'Shock Recovery' task.
Re-sleeving
^^^^^^^^^^^

@ -0,0 +1,86 @@
.. _gameplay_sourcefiles:
.. warning:: This page contains spoilers regarding the game's story/plot-line.
Source-Files
============
Source-Files are a type of persistent upgrade that are more powerful than Augmentations.
Source-Files are received by destroying a BitNode. There are many different BitNodes
in the game and each BitNode will grant a different Source-File when it is destroyed.
A Source-File can be upgraded by destroying its corresponding BitNode a second or
third time (AKA playing through that BitNode again). It can be upgraded to a maximum
of level 3.
List of all Source-Files
^^^^^^^^^^^^^^^^^^^^^^^^
+------------------------------------+-------------------------------------------------------------------------------------+
| BitNode-1: Source Genesis | * Lets the player start with 32 GB of RAM on home computer |
| | * Increases all of the player's multipliers by 16%/24%/28% |
+------------------------------------+-------------------------------------------------------------------------------------+
| BitNode-2: Rise of the Underworld | * Increases the player's crime success rate, crime money, and |
| | charisma multipliers by 24%/36%/42% |
+------------------------------------+-------------------------------------------------------------------------------------+
| BitNode-3: Corporatocracy | * Lets the player create Corporations in other BitNodes (although some |
| | BitNodes will disable this mechanic) |
| | * Increases the player's charisma and company salary multipliers by 8%/12%/14% |
+------------------------------------+-------------------------------------------------------------------------------------+
| BitNode-4: The Singularity | * Lets the player access and use Netscript Singularity Functions in other BitNodes. |
| | Each level of this Source-File opens up more of the Singularity Functions to use |
+------------------------------------+-------------------------------------------------------------------------------------+
| BitNode-5: Artificial Intelligence | * Unlocks :ref:`gameplay_intelligence` |
| | * Unlocks getBitNodeMultipliers() Netscript function |
| | * Increases all of the player's hacking-related multipliers by 8%/12%/14% |
+------------------------------------+-------------------------------------------------------------------------------------+
| BitNode-6: Bladeburners | * Unlocks the Bladeburner feature in other BitNodes |
| | * Increases all of the player's level and experience gain rate multipliers for |
| | combat stats by 8%/12%/14% |
+------------------------------------+-------------------------------------------------------------------------------------+
| BitNode-7: Bladeburners 2079 | * Allows the player to access the :ref:`netscript_bladeburnerapi` in other BitNodes |
| | * Increases all of the player's Bladeburner multipliers by 8%/12%/14% |
+------------------------------------+-------------------------------------------------------------------------------------+
| BitNode-8: Ghost of Wall Street | * Increases the player's hacking growth multiplier by 12%/18%/21% |
| | * Level 1 grants permanent access to :ref:`WSE <gameplay_stock_market>` and |
| | :ref:`TIX API <netscript_tixapi>` |
| | * Level 2 grants permanent access to shorting stocks |
| | * Level 3 grants permanent access to use limit/stop orders |
+------------------------------------+-------------------------------------------------------------------------------------+
| BitNode-9: Coming Soon | |
+------------------------------------+-------------------------------------------------------------------------------------+
| BitNode-10: Digital Carbon | * Each level of this grants a Duplicate Sleeve |
+------------------------------------+-------------------------------------------------------------------------------------+
| BitNode-11: The Big Crash | * Company favor increases both the player's salary and reputation gain at that |
| | company by 1% per favor (rather than just the reputation gain) |
| | * Increases the player's company salary and reputation gain multipliers by |
| | 24%/36%/42% |
+------------------------------------+-------------------------------------------------------------------------------------+
| BitNode-12: The Recursion | * There is no maximum level for this Source-File |
| | * Each level of this Source-File increases all of the player's multipliers by 1%. |
| | This affect is multiplicative with itself. This means that level N of this |
| | Source-File will result in a multiplier of 1.01^N (or 0.99^N for multipliers |
| | that decrease) |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+

@ -2,7 +2,7 @@
Companies
=========
When exploring the :ref:`world <World>`, you can visit various companies. At
When exploring the :ref:`world <gameplay_world>`, you can visit various companies. At
these companies, you can apply for jobs.
Working a job lets you earn money, experience, and reputation with that company.

@ -4,7 +4,7 @@ Crimes
======
Commiting crimes is an active gameplay mechanic that allows the player to train
their stats and potentially earn money. The player can attempt to commit crimes
by visiting 'The Slums' through the 'City' tab (:ref:`Keyboard shortcut <_shortcuts>` Alt + w).
by visiting 'The Slums' through the 'City' tab (:ref:`Keyboard shortcut <shortcuts>` Alt + w).
'The Slums' is available in every city.

@ -110,7 +110,7 @@ List of Factions and their Requirements
| | Clarke | * Have 200k reputation with | |
| | Incorporated | the Corporation | |
+ +----------------+-----------------------------------------+-------------------------------+
| | Fulcrum Secret | * Have 200k reputation with | |
| | Fulcrum Secret | * Have 250k reputation with | |
| | Technologies | the Corporation | |
| | | * Hack fulcrumassets manually | |
+---------------------+----------------+-----------------------------------------+-------------------------------+

@ -92,7 +92,8 @@ todo_include_todos = True
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'agogo'
#html_theme = 'agogo'
html_theme = "sphinx_rtd_theme"
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the

@ -21,6 +21,7 @@ secrets that you've been searching for.
Netscript <netscript>
Basic Gameplay <basicgameplay>
Advanced Gameplay <advancedgameplay>
Keyboard Shortcuts <shortcuts>
Game Frozen or Stuck? <gamefrozen>
Changelog <changelog>

@ -1,3 +1,5 @@
.. _netscript_bladeburnerapi:
Netscript Bladeburner API
=========================
Netscript provides the following API for interacting with the game's Bladeburner mechanic.

@ -1,3 +1,5 @@
.. _netscript_tixapi:
Netscript Trade Information eXchange (TIX) API
==============================================

@ -1,3 +1,5 @@
.. _netscript_singularityfunctions:
Netscript Singularity Functions
===============================

@ -18,7 +18,7 @@ These shortcuts are almost always available. Exceptions include:
========== ===========================================================================
Shortcut Action
========== ===========================================================================
Alt + t Switch to :doc:`terminal`
Alt + t Switch to :ref:`terminal`
Alt + c Switch to 'Stats' page
Alt + e Switch to Script Editor. Will open up the last-edited file or a new file
Alt + s Switch to 'Active Scripts' page
@ -40,7 +40,7 @@ These shortcuts are available only in the Script Editor
============= ===========================================================================
Shortcut Action
============= ===========================================================================
Ctrl + b Save script and return to :doc:`terminal`
Ctrl + b Save script and return to :ref:`terminal`
Ctrl + space Function autocompletion
============= ===========================================================================
@ -52,7 +52,7 @@ In the Script Editor you can configure your key binding mode to three preset opt
Terminal Shortcuts
------------------
These shortcuts are available only in the :doc:`terminal`
These shortcuts are available only in the :ref:`terminal`
============= ===========================================================================
Shortcut Action
@ -66,7 +66,7 @@ Tab Autocomplete command
Terminal Bash Shortcuts
-----------------------
These shortcuts were implemented to better emulate a bash shell. They must be enabled
in your :doc:`terminal`'s *.fconf* file. This can be done be entering the Terminal command::
in your :ref:`terminal`'s *.fconf* file. This can be done be entering the Terminal command::
nano .fconf

@ -65,6 +65,9 @@
<li id="hacknet-nodes-tab" class="mainmenu-accordion-panel">
<button id="hacknet-nodes-menu-link"> Hacknet Nodes </button>
</li>
<li id="sleeves-tab" class="mainmenu-accordion-panel">
<button id="sleeves-menu-link"> Sleeves </button>
</li>
<!-- World dropdown -->
<li id="world-menu-header-li">

@ -158,7 +158,7 @@ function initBitNodes() {
"<br>Level 1: 12%<br>Level 2: 18%<br>Level 3: 21%");
BitNodes["BitNode9"] = new BitNode(9, "Do Androids Dream?", "COMING SOON");
BitNodes["BitNode10"] = new BitNode(10, "Digital Carbon", "Your body is not who you are",
"In 2084, VitaLife unveiled to the world the Persona Core, an Augmentation that allowed people " +
"In 2084, VitaLife unveiled to the world the Persona Core, a technology that allowed people " +
"to digitize their consciousness. Their consciousness could then be transferred into Synthoids " +
"or other bodies by trasmitting the digitized data. Human bodies became nothing more than 'sleeves' for the " +
"human consciousness. Mankind had finally achieved immortality - at least for those that could afford it.<br><br>" +
@ -170,7 +170,8 @@ function initBitNodes() {
"All methods of gaining money are half as profitable (except Stock Market)<br>" +
"Augmentations are 5x as expensive and require twice as much reputation<br><br>" +
"Destroying this BitNode will give you Source-File 10, or if you already have this Source-File it will " +
"upgrade its level up to a maximum of 3. Each level of this Source-File grants you a Duplicate Sleeve.");
"upgrade its level up to a maximum of 3. This Source-File unlocks Sleeve technology in other BitNodes. " +
"Each level of this Source-File also grants you a Duplicate Sleeve");
BitNodes["BitNode11"] = new BitNode(11, "The Big Crash", "Okay. Sell it all.",
"The 2050s was defined by the massive amounts of violent civil unrest and anarchic rebellion that rose all around the world. It was this period " +
"of disorder that eventually lead to the governmental reformation of many global superpowers, most notably " +

@ -511,6 +511,8 @@ export let CONSTANTS: IMap<any> = {
LatestUpdate:
`
v0.43.0
* Added BitNode-10: Digital Carbon
* 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
@ -518,7 +520,9 @@ export let CONSTANTS: IMap<any> = {
* Job Changes:
** You can now hold multiple jobs at once. This means you no longer lose reputation when leaving a company
** Because of this change, the getCharacterInformation() Netscript function returns a slightly different value
* Home Computer RAM is now capped at 2 ^ 30 GB (1073741824 GB)
* Pop-up dialog boxes are a little bit bigger
`
}

@ -1,5 +1,7 @@
import { Crimes } from "./Crimes";
import { Player } from "../Player";
import { dialogBoxCreate } from "../../utils/DialogBox";
export function determineCrimeSuccess(type, moneyGained) {

@ -47,6 +47,7 @@ export interface IPlayer {
crime_success_mult: number;
// Methods
canAfford(cost: number): boolean;
gainHackingExp(exp: number): void;
gainStrengthExp(exp: number): void;
gainDefenseExp(exp: number): void;
@ -59,6 +60,7 @@ export interface IPlayer {
inGang(): boolean;
loseMoney(money: number): void;
reapplyAllAugmentations(resetMultipliers: boolean): void;
reapplyAllSourceFiles(): void;
startCrime(crimeType: string,
hackExp: number,
strExp: number,

@ -85,6 +85,17 @@ export abstract class Person {
work_money_mult: number = 1;
hacknet_node_money_mult: number = 1;
hacknet_node_purchase_cost_mult: number = 1;
hacknet_node_ram_cost_mult: number = 1;
hacknet_node_core_cost_mult: number = 1;
hacknet_node_level_cost_mult: number = 1;
bladeburner_max_stamina_mult: number = 1;
bladeburner_stamina_gain_mult: number = 1;
bladeburner_analysis_mult: number = 1;
bladeburner_success_chance_mult : number = 1;
/**
* Augmentations
*/
@ -101,7 +112,7 @@ export abstract class Person {
/**
* Updates this object's multipliers for the given augmentation
*/
applyAugmentation(aug: Augmentation, reapply=false) {
applyAugmentation(aug: Augmentation) {
for (const mult in aug.mults) {
if ((<any>this)[mult] == null) {
console.warn(`Augmentation has unrecognized multiplier property: ${mult}`);
@ -188,11 +199,11 @@ export abstract class Person {
*/
updateStatLevels(): void {
this.hacking_skill = Math.max(1, Math.floor(this.calculateStat(this.hacking_exp, this.hacking_mult * BitNodeMultipliers.HackingLevelMultiplier)));
this.strength = Math.max(1, Math.floor(this.calculateStat(this.strength_exp, this.strength_mult)));
this.defense = Math.max(1, Math.floor(this.calculateStat(this.defense_exp, this.defense_mult)));
this.dexterity = Math.max(1, Math.floor(this.calculateStat(this.dexterity_exp, this.dexterity_mult)));
this.agility = Math.max(1, Math.floor(this.calculateStat(this.agility_exp, this.agility_mult)));
this.charisma = Math.max(1, Math.floor(this.calculateStat(this.charisma_exp, this.charisma_mult)));
this.strength = Math.max(1, Math.floor(this.calculateStat(this.strength_exp, this.strength_mult * BitNodeMultipliers.StrengthLevelMultiplier)));
this.defense = Math.max(1, Math.floor(this.calculateStat(this.defense_exp, this.defense_mult * BitNodeMultipliers.DefenseLevelMultiplier)));
this.dexterity = Math.max(1, Math.floor(this.calculateStat(this.dexterity_exp, this.dexterity_mult * BitNodeMultipliers.DexterityLevelMultiplier)));
this.agility = Math.max(1, Math.floor(this.calculateStat(this.agility_exp, this.agility_mult * BitNodeMultipliers.AgilityLevelMultiplier)));
this.charisma = Math.max(1, Math.floor(this.calculateStat(this.charisma_exp, this.charisma_mult * BitNodeMultipliers.CharismaLevelMultiplier)));
const ratio: number = this.hp / this.max_hp;
this.max_hp = Math.floor(10 + this.defense / 10);

@ -24,7 +24,7 @@ export class Resleeve extends Person {
getCost(): number {
// Each experience point adds this to the cost
const CostPerExp: number = 5;
const CostPerExp: number = 4;
// Final cost is multiplied by # Augs ^ this constant
const NumAugsExponent: number = 1.05;

@ -23,7 +23,13 @@ import { getRandomInt } from "../../../utils/helpers/getRandomInt";
// Executes the actual re-sleeve when one is purchased
export function purchaseResleeve(r: Resleeve, p: IPlayer):void {
export function purchaseResleeve(r: Resleeve, p: IPlayer): boolean {
const cost: number = r.getCost();
if (!p.canAfford(cost)) {
return false;
}
p.loseMoney(cost);
// Set the player's exp
p.hacking_exp = r.hacking_exp;
p.strength_exp = r.strength_exp;
@ -32,16 +38,25 @@ export function purchaseResleeve(r: Resleeve, p: IPlayer):void {
p.agility_exp = r.agility_exp;
p.charisma_exp = r.charisma_exp;
// Reset Augmentation "owned" data
for (const augKey in Augmentations) {
Augmentations[augKey].owned = false;
}
// 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);
} else {
// NeuroFlux Governor
Augmentations[AugmentationNames.NeuroFluxGovernor].owned = true;
}
}
for (let i = 0; i < r.augmentations.length; ++i) {
p.augmentations.push(new PlayerOwnedAugmentation(r.augmentations[i].name));
Augmentations[r.augmentations[i].name].owned = true;
}
// The player's purchased Augmentations should remain the same, but any purchased
@ -55,6 +70,8 @@ export function purchaseResleeve(r: Resleeve, p: IPlayer):void {
}
p.reapplyAllAugmentations(true);
p.reapplyAllSourceFiles(); //Multipliers get reset, so have to re-process source files too
return true;
}
// Creates all of the Re-sleeves that will be available for purchase at VitaLife
@ -67,23 +84,33 @@ export function generateResleeves(): Resleeve[] {
let r: Resleeve = new Resleeve();
// Generate experience
const expMult: number = i + 1;
r.hacking_exp = expMult * getRandomInt(500, 1500);
r.strength_exp = expMult * getRandomInt(500, 1500);
r.defense_exp = expMult * getRandomInt(500, 1500);
r.dexterity_exp = expMult * getRandomInt(500, 1500);
r.agility_exp = expMult * getRandomInt(500, 1500);
r.charisma_exp = expMult * getRandomInt(500, 1500);
const expMult: number = (5 * i) + 1;
r.hacking_exp = expMult * getRandomInt(1000, 5000);
r.strength_exp = expMult * getRandomInt(1000, 5000);
r.defense_exp = expMult * getRandomInt(1000, 5000);
r.dexterity_exp = expMult * getRandomInt(1000, 5000);
r.agility_exp = expMult * getRandomInt(1000, 5000);
r.charisma_exp = expMult * getRandomInt(1000, 5000);
// Generate Augs
const baseNumAugs: number = Math.ceil((i + 1) / 2);
// Augmentation prequisites will be ignored for this
const baseNumAugs: number = Math.max(2, Math.ceil((i + 3) / 2));
const numAugs: number = getRandomInt(baseNumAugs, baseNumAugs + 2);
const augKeys: string[] = Object.keys(Augmentations);
for (let a = 0; a < numAugs; ++a) {
// We'll ignore Aug prerequisites for this
const augKeys: string[] = Object.keys(Augmentations);
const randKey: string = augKeys[getRandomInt(0, augKeys.length - 1)];
// Get a random aug
const randIndex: number = getRandomInt(0, augKeys.length - 1)
const randKey: string = augKeys[randIndex];
if (randKey === AugmentationNames.TheRedPill) {
continue; // A sleeve can't have The Red Pill
}
const randAug: Augmentation | null = Augmentations[randKey];
r.augmentations.push({name: randAug!.name, level: 1});
r.applyAugmentation(Augmentations[randKey]);
r.updateStatLevels();
// Remove Augmentation so that there are no duplicates
augKeys.splice(randIndex, 1);
}
ret.push(r);

@ -7,8 +7,6 @@ import { generateResleeves,
import { IPlayer } from "../IPlayer";
import { IMap } from "../../types";
import { Augmentation } from "../../Augmentation/Augmentation";
import { Augmentations } from "../../Augmentation/Augmentations";
@ -23,9 +21,7 @@ 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;
@ -70,7 +66,19 @@ export function createResleevesPage(p: IPlayer) {
UIElems.info = createElement("p", {
display: "inline-block",
innerText: "TOODOOO",
innerHTML: "Re-sleeving is the process of digitizing and transferring your consciousness " +
"into a new human body, or 'sleeve'. Here at VitaLife, you can purchase new " +
"specially-engineered bodies for the re-sleeve process. Many of these bodies " +
"even come with genetic and cybernetic Augmentations!<br><br>" +
"Re-sleeving will chance your experience for every stat. It will also REMOVE " +
"all of your currently-installed Augmentations, and replace " +
"them with the ones provided by the purchased sleeve. However, Augmentations that you have " +
"purchased but not installed will NOT be removed. If you have purchased an " +
"Augmentation and then re-sleeve into a body which already has that Augmentation, " +
"it will be removed (since you cannot have duplicate Augmentations).<br><br>" +
"NOTE: The stats and multipliers displayed on this page do NOT include your bonuses from " +
"Source-File.",
width: "75%",
});
UIElems.resleeveList = createElement("ul");
@ -96,7 +104,10 @@ export function createResleevesPage(p: IPlayer) {
}
export function clearResleevesPage() {
removeElement(UIElems.container);
if (UIElems.container instanceof HTMLElement) {
removeElement(UIElems.container);
}
for (const prop in UIElems) {
(<any>UIElems)[prop] = null;
}
@ -125,8 +136,17 @@ function createResleeveUi(resleeve: Resleeve): IResleeveUIElems {
display: "block",
});
elems.statsPanel = createElement("div", { class: "resleeve-panel" });
elems.stats = createElement("p", { class: "resleeve-stats-text" });
elems.statsPanel = createElement("div", { class: "resleeve-panel", width: "30%" });
elems.stats = createElement("p", {
class: "resleeve-stats-text",
innerHTML:
`Hacking: ${numeralWrapper.format(resleeve.hacking_skill, "0,0")} (${numeralWrapper.formatBigNumber(resleeve.hacking_exp)} exp)<br>` +
`Strength: ${numeralWrapper.format(resleeve.strength, "0,0")} (${numeralWrapper.formatBigNumber(resleeve.strength_exp)} exp)<br>` +
`Defense: ${numeralWrapper.format(resleeve.defense, "0,0")} (${numeralWrapper.formatBigNumber(resleeve.defense_exp)} exp)<br>` +
`Dexterity: ${numeralWrapper.format(resleeve.dexterity, "0,0")} (${numeralWrapper.formatBigNumber(resleeve.dexterity_exp)} exp)<br>` +
`Agility: ${numeralWrapper.format(resleeve.agility, "0,0")} (${numeralWrapper.formatBigNumber(resleeve.agility_exp)} exp)<br>` +
`Charisma: ${numeralWrapper.format(resleeve.charisma, "0,0")} (${numeralWrapper.formatBigNumber(resleeve.charisma_exp)} exp)`,
});
elems.multipliersButton = createElement("button", {
class: "std-button",
innerText: "Multipliers",
@ -155,33 +175,58 @@ function createResleeveUi(resleeve: Resleeve): IResleeveUIElems {
`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)}`,
`Hacknet Income multiplier: ${numeralWrapper.formatPercentage(resleeve.hacknet_node_money_mult)}`,
`Hacknet Purchase Cost multiplier: ${numeralWrapper.formatPercentage(resleeve.hacknet_node_purchase_cost_mult)}`,
`Hacknet Level Upgrade Cost multiplier: ${numeralWrapper.formatPercentage(resleeve.hacknet_node_level_cost_mult)}`,
`Hacknet Ram Upgrade Cost multiplier: ${numeralWrapper.formatPercentage(resleeve.hacknet_node_ram_cost_mult)}`,
`Hacknet Core Upgrade Cost multiplier: ${numeralWrapper.formatPercentage(resleeve.hacknet_node_core_cost_mult)}`,
`Bladeburner Max Stamina multiplier: ${numeralWrapper.formatPercentage(resleeve.bladeburner_max_stamina_mult)}`,
`Bladeburner Stamina Gain multiplier: ${numeralWrapper.formatPercentage(resleeve.bladeburner_stamina_gain_mult)}`,
`Bladeburner Field Analysis multiplier: ${numeralWrapper.formatPercentage(resleeve.bladeburner_analysis_mult)}`,
`Bladeburner Success Chance multiplier: ${numeralWrapper.formatPercentage(resleeve.bladeburner_success_chance_mult)}`
].join("<br>"), false
)
}
});
elems.statsPanel.appendChild(elems.stats);
elems.statsPanel.appendChild(elems.multipliersButton);
elems.augPanel = createElement("div", { class: "resleeve-panel" });
elems.augSelector = createElement("select") as HTMLSelectElement;
elems.augPanel = createElement("div", { class: "resleeve-panel", width: "50%" });
elems.augSelector = createElement("select", { class: "resleeve-aug-selector" }) as HTMLSelectElement;
elems.augDescription = createElement("p");
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.augSelector.dispatchEvent(new Event('change')); // Set inital description by manually triggering change event
elems.augPanel.appendChild(elems.augSelector);
elems.augPanel.appendChild(elems.augDescription);
elems.costPanel = createElement("div", { class: "resleeve-panel" });
const cost: number = resleeve.getCost();
elems.costPanel = createElement("div", { class: "resleeve-panel", width: "20%" });
elems.costText = createElement("p", {
innerText: `It costs ${numeralWrapper.formatMoney(resleeve.getCost())} ` +
innerText: `It costs ${numeralWrapper.formatMoney(cost)} ` +
`to purchase this Sleeve.`,
});
elems.buyButton = createElement("button", {
class: "std-button",
innerText: "Purchase",
clickListener: () => {
purchaseResleeve(resleeve, playerRef!);
if (purchaseResleeve(resleeve, playerRef!)) {
dialogBoxCreate(`You re-sleeved for ${numeralWrapper.formatMoney(cost)}!`, false);
} else {
dialogBoxCreate(`You cannot afford to re-sleeve into this body`, false);
}
}
});
elems.costPanel.appendChild(elems.costText);
elems.costPanel.appendChild(elems.buyButton);
elems.container.appendChild(elems.statsPanel);
elems.container.appendChild(elems.augPanel);
elems.container.appendChild(elems.costPanel);
return elems;
}

@ -47,17 +47,13 @@ export class Sleeve extends Person {
*/
currentTask: SleeveTaskType = SleeveTaskType.Idle;
/**
* Description of current task. Used only for logging purposes
*/
currentTaskDescription: string = "";
/**
* Contains details about the sleeve's current task. The info stored
* in this depends on the task type
*
* Faction/Company Work: Name of Faction/Company
* Crime: Success rate of current crime, in decimal form
* Crime: Money earned if successful
* Class/Gym: Name of university/gym
*/
currentTaskLocation: string = "";
@ -135,7 +131,8 @@ export class Sleeve extends Person {
/**
* Commit crimes
*/
commitCrime(p: IPlayer, crime: Crime): void {
commitCrime(p: IPlayer, crime: Crime): boolean {
if (!(crime instanceof Crime)) { return false; }
if (this.currentTask !== SleeveTaskType.Idle) {
this.finishTask(p);
} else {
@ -150,6 +147,8 @@ export class Sleeve extends Person {
this.gainRatesForTask.cha = crime.charisma_exp * this.charisma_exp_mult * BitNodeMultipliers.CrimeExpGain;
this.gainRatesForTask.money = crime.money * this.crime_money_mult * BitNodeMultipliers.CrimeMoney;
this.currentTaskLocation = String(this.gainRatesForTask.money);
// We'll determine success now and adjust the earnings accordingly
if (Math.random() < crime.successRate(p)) {
this.gainRatesForTask.hack *= 2;
@ -162,27 +161,35 @@ export class Sleeve extends Person {
this.gainRatesForTask.money = 0;
}
this.currentTaskMaxTime = crime.time;
this.currentTask = SleeveTaskType.Crime;
return true;
}
/**
* Called to stop the current task
*/
finishTask(p: IPlayer): void {
finishTask(p: IPlayer): ITaskTracker {
let retValue: ITaskTracker = createTaskTracker(); // Amount of exp to be gained by other sleeves
if (this.currentTask === SleeveTaskType.Crime) {
// For crimes, all experience and money is gained at the end
if (this.currentTaskTime >= this.currentTaskMaxTime) {
let retValue: ITaskTracker = createTaskTracker(); // Amount of exp to be gained by other sleeves
retValue = this.gainExperience(p, this.gainRatesForTask);
this.gainMoney(p, this.gainRatesForTask);
// Do not reset task to IDLE
this.currentTaskTime = 0;
return retValue;
}
} else {
// For other crimes... I dont think anything else needs to be done
}
this.resetTaskStatus();
return retValue;
}
/**
@ -267,15 +274,17 @@ 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);
const gain: number = (task.money * numCycles);
this.earningsForTask.money += gain;
this.earningsForPlayer.money += gain;
p.gainMoney(gain);
}
/**
* Gets reputation gain for the current task
* Only applicable when working for company or faction
*/
getRepGain(): number {
getRepGain(p: IPlayer): number {
if (this.currentTask === SleeveTaskType.Faction) {
switch (this.factionWorkType) {
case FactionWorkType.Hacking:
@ -289,7 +298,25 @@ export class Sleeve extends Person {
return 0;
}
} else if (this.currentTask === SleeveTaskType.Company) {
return 0; // TODO
const companyName: string = this.currentTaskLocation;
const company: Company | null = Companies[companyName];
if (company == null) {
console.error(`Invalid company found when trying to calculate rep gain: ${companyName}`);
return 0;
}
const companyPosition: CompanyPosition | null = CompanyPositions[p.jobs[companyName]];
if (companyPosition == null) {
console.error(`Invalid company position name found when trying to calculate rep gain: ${p.jobs[companyName]}`);
return 0;
}
const jobPerformance: number = companyPosition!.calculateJobPerformance(this.hacking_skill, this.strength,
this.defense, this.dexterity,
this.agility, this.charisma);
const favorMult = 1 + (company!.favor / 100);
return jobPerformance * this.company_rep_mult * favorMult;
} else {
console.warn(`Sleeve.getRepGain() called for invalid task type: ${this.currentTask}`);
return 0;
@ -315,14 +342,9 @@ export class Sleeve extends Person {
this.storedCycles += numCycles;
if (this.storedCycles < CyclesPerSecond) { return null; }
// Shock gradually goes towards 100
this.shock = Math.max(100, this.shock + (0.0001 * this.storedCycles));
if (this.currentTask === SleeveTaskType.Idle) { return null; }
let time = this.storedCycles * CONSTANTS.MilliPerCycle;
let cyclesUsed = this.storedCycles;
if (this.currentTaskTime + time > this.currentTaskMaxTime) {
if (this.currentTaskMaxTime !== 0 && this.currentTaskTime + time > this.currentTaskMaxTime) {
time = this.currentTaskMaxTime - this.currentTaskTime;
cyclesUsed = Math.floor(time / CONSTANTS.MilliPerCycle);
@ -334,9 +356,15 @@ export class Sleeve extends Person {
}
this.currentTaskTime += time;
// Shock gradually goes towards 100
this.shock = Math.min(100, this.shock + (0.0001 * this.storedCycles));
let retValue: ITaskTracker = createTaskTracker();
switch (this.currentTask) {
case SleeveTaskType.Idle:
break;
case SleeveTaskType.Class:
case SleeveTaskType.Gym:
retValue = this.gainExperience(p, this.gainRatesForTask, cyclesUsed);
this.gainMoney(p, this.gainRatesForTask, cyclesUsed);
break;
@ -351,7 +379,7 @@ export class Sleeve extends Person {
break;
}
fac.playerReputation += (this.getRepGain() * cyclesUsed);
fac.playerReputation += (this.getRepGain(p) * cyclesUsed);
break;
case SleeveTaskType.Company:
retValue = this.gainExperience(p, this.gainRatesForTask, cyclesUsed);
@ -363,27 +391,31 @@ export class Sleeve extends Person {
break;
}
company.playerReputation *= (this.getRepGain() * cyclesUsed);
company!.playerReputation += (this.getRepGain(p) * cyclesUsed);
break;
case SleeveTaskType.Recovery:
this.shock = Math.max(100, this.shock + (0.001 * this.storedCycles));
this.shock = Math.min(100, this.shock + (0.0001 * cyclesUsed));
break;
case SleeveTaskType.Sync:
this.sync = Math.max(100, this.sync + (0.001 * this.storedCycles));
this.sync = Math.min(100, this.sync + (0.0001 * cyclesUsed));
break;
default:
break;
}
if (this.currentTaskMaxTime !== 0 && this.currentTaskTime >= this.currentTaskMaxTime) {
this.finishTask(p);
if (this.currentTask === SleeveTaskType.Crime) {
retValue = this.finishTask(p);
} else {
this.finishTask(p);
}
}
this.updateStatLevels();
this.storedCycles -= cyclesUsed;
// TODO Finish this
return retValue;
}
@ -416,16 +448,19 @@ export class Sleeve extends Person {
switch (universityName.toLowerCase()) {
case Locations.AevumSummitUniversity.toLowerCase():
if (this.city !== Cities.Aevum) { return false; }
this.currentTaskLocation = Locations.AevumSummitUniversity;
costMult = 4;
expMult = 3;
break;
case Locations.Sector12RothmanUniversity.toLowerCase():
if (this.city !== Cities.Sector12) { return false; }
this.currentTaskLocation = Locations.Sector12RothmanUniversity;
costMult = 3;
expMult = 2;
break;
case Locations.VolhavenZBInstituteOfTechnology.toLowerCase():
if (this.city !== Cities.Volhaven) { return false; }
this.currentTaskLocation = Locations.VolhavenZBInstituteOfTechnology;
costMult = 5;
expMult = 4;
break;
@ -433,9 +468,6 @@ export class Sleeve extends Person {
return false;
}
// Number of game cycles in a second
const cps: number = 1000 / CONSTANTS.MilliPerCycle;
// Set experience/money gains based on class
// TODO Refactor University Courses into its own class or something
const baseStudyComputerScienceExp: number = 0.5;
@ -511,7 +543,10 @@ export class Sleeve extends Person {
const companyPosition: CompanyPosition | null = CompanyPositions[p.jobs[companyName]];
if (company == null) { throw new Error(`Invalid company name specified in Sleeve.workForCompany(): ${companyName}`); }
if (companyPosition == null) { throw new Error(`Invalid CompanyPosition data in Sleeve.workForCompany(): ${companyName}`); }
this.gainRatesForTask.money = companyPosition.baseSalary *
company.salaryMultiplier *
this.work_money_mult *
BitNodeMultipliers.CompanyWorkMoney;
this.gainRatesForTask.hack = companyPosition.hackingExpGain *
company.expMultiplier *
this.hacking_exp_mult *
@ -539,6 +574,7 @@ export class Sleeve extends Person {
this.currentTaskLocation = companyName;
this.currentTask = SleeveTaskType.Company;
this.currentTaskMaxTime = CONSTANTS.MillisecondsPer8Hours;
return true;
}
@ -585,6 +621,7 @@ export class Sleeve extends Person {
this.currentTaskLocation = factionName;
this.currentTask = SleeveTaskType.Faction;
this.currentTaskMaxTime = CONSTANTS.MillisecondsPer20Hours;
return true;
}
@ -606,26 +643,31 @@ export class Sleeve extends Person {
switch (gymName.toLowerCase()) {
case Locations.AevumCrushFitnessGym.toLowerCase():
if (this.city != Cities.Aevum) { return false; }
this.currentTaskLocation = Locations.AevumCrushFitnessGym;
costMult = 3;
expMult = 2;
break;
case Locations.AevumSnapFitnessGym.toLowerCase():
if (this.city != Cities.Aevum) { return false; }
this.currentTaskLocation = Locations.AevumSnapFitnessGym;
costMult = 10;
expMult = 5;
break;
case Locations.Sector12IronGym.toLowerCase():
if (this.city != Cities.Sector12) { return false; }
this.currentTaskLocation = Locations.Sector12IronGym;
costMult = 1;
expMult = 1;
break;
case Locations.Sector12PowerhouseGym.toLowerCase():
if (this.city != Cities.Sector12) { return false; }
this.currentTaskLocation = Locations.Sector12PowerhouseGym;
costMult = 20;
expMult = 10;
break;
case Locations.VolhavenMilleniumFitnessGym:
if (this.city != Cities.Volhaven) { return false; }
this.currentTaskLocation = Locations.VolhavenMilleniumFitnessGym;
costMult = 7;
expMult = 4;
break;
@ -633,9 +675,6 @@ export class Sleeve extends Person {
return false;
}
// Number of game cycles in a second
const cps = 1000 / CONSTANTS.MilliPerCycle;
// Set experience/money gains based on class
// TODO Refactor University Courses into its own class or something
const baseGymExp: number = 1;
@ -657,7 +696,7 @@ export class Sleeve extends Person {
return false;
}
this.currentTask = SleeveTaskType.Class;
this.currentTask = SleeveTaskType.Gym;
return true;
}

@ -2,11 +2,13 @@
* Enum for different types of tasks that a Sleeve can perform
*/
export enum SleeveTaskType {
Class,
Company,
Crime,
Faction,
// Same Order as selectable order in UI
Idle,
Company,
Faction,
Crime,
Class,
Gym,
Recovery,
Sync,
}

@ -3,6 +3,7 @@
*/
import { Sleeve } from "./Sleeve";
import { SleeveTaskType } from "./SleeveTaskTypesEnum";
import { SleeveFaq } from "./data/SleeveFaq";
import { IPlayer } from "../IPlayer";
@ -11,14 +12,13 @@ import { Locations } from "../../Locations";
import { Cities } from "../../Locations/Cities";
import { Crimes } from "../../Crime/Crimes";
import { IMap } from "../../types";
import { numeralWrapper } from "../../ui/numeralFormat";
import { Page,
routing } from "../../ui/navigationTracking";
import { dialogBoxCreate } from "../../../utils/DialogBox";
import { createProgressBarText } from "../../../utils/helpers/createProgressBarText";
import { exceptionAlert } from "../../../utils/helpers/exceptionAlert";
import { createElement } from "../../../utils/uiHelpers/createElement";
@ -26,35 +26,39 @@ import { createOptionElement } from "../../../utils/uiHelpers/createOptionElemen
import { getSelectValue } from "../../../utils/uiHelpers/getSelectData";
import { removeChildrenFromElement } from "../../../utils/uiHelpers/removeChildrenFromElement";
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: HTMLElement | null,
statsPanel: HTMLElement | null,
stats: HTMLElement | null,
moreStatsButton: HTMLElement | null,
taskPanel: HTMLElement | null,
taskSelector: HTMLSelectElement | null,
taskDetailsSelector: HTMLSelectElement | null,
taskDetailsSelector2: HTMLSelectElement | null,
taskDescription: HTMLElement | null,
taskSetButton: HTMLElement | null,
earningsPanel: HTMLElement | null,
currentEarningsInfo: HTMLElement | null,
totalEarningsButton: HTMLElement | null,
container: HTMLElement | null;
statsPanel: HTMLElement | null;
stats: HTMLElement | null;
moreStatsButton: HTMLElement | null;
taskPanel: HTMLElement | null;
taskSelector: HTMLSelectElement | null;
taskDetailsSelector: HTMLSelectElement | null;
taskDetailsSelector2: HTMLSelectElement | null;
taskDescription: HTMLElement | null;
taskSetButton: HTMLElement | null;
taskProgressBar: HTMLElement | null;
earningsPanel: HTMLElement | null;
currentEarningsInfo: HTMLElement | null;
totalEarningsButton: HTMLElement | null;
}
// Object that keeps track of all DOM elements for the entire Sleeve UI
interface IPageUIElems {
container: HTMLElement | null;
info: HTMLElement | null,
sleeveList: HTMLElement | null,
sleeves: ISleeveUIElems[] | null,
docButton: HTMLElement | null;
faqButton: HTMLElement | null;
info: HTMLElement | null;
sleeveList: HTMLElement | null;
sleeves: ISleeveUIElems[] | null;
}
const UIElems: IPageUIElems = {
container: null,
docButton: null,
faqButton: null,
info: null,
sleeveList: null,
sleeves: null,
@ -75,11 +79,26 @@ export function createSleevesPage(p: IPlayer) {
});
UIElems.info = createElement("p", {
display: "inline-block",
innerText: "Duplicate Sleeves are MK-V Synthoids (synthetic androids) into which your " +
"consciousness has copied. In other words, these Synthoids contain " +
class: "sleeves-page-info",
innerHTML: "Duplicate Sleeves are MK-V Synthoids (synthetic androids) into which your " +
"consciousness has been copied. In other words, these Synthoids contain " +
"a perfect duplicate of your mind.<br><br>" +
"Sleeves can be used to perform different tasks synchronously.",
"Sleeves can be used to perform different tasks synchronously.<br><br>",
});
UIElems.faqButton = createElement("button", {
class: "std-button",
display: "inline-block",
innerText: "FAQ",
clickListener: () => {
dialogBoxCreate(SleeveFaq, false);
}
});
UIElems.docButton = createElement("button", {
class: "std-button",
display: "inline-block",
innerText: "Documentation",
});
UIElems.sleeveList = createElement("ul");
@ -93,6 +112,7 @@ export function createSleevesPage(p: IPlayer) {
}
UIElems.container.appendChild(UIElems.info);
UIElems.container.appendChild(UIElems.faqButton);
UIElems.container.appendChild(UIElems.sleeveList);
document.getElementById("entire-game-container")!.appendChild(UIElems.container);
@ -104,10 +124,23 @@ export function createSleevesPage(p: IPlayer) {
// Updates the UI for the entire Sleeves page
export function updateSleevesPage() {
if (!routing.isOn(Page.Sleeves)) { return; }
try {
for (let i = 0; i < playerRef!.sleeves.length; ++i) {
const sleeve: Sleeve = playerRef!.sleeves[i];
const elems: ISleeveUIElems = UIElems.sleeves![i];
updateSleeveUi(sleeve!, elems!);
}
} catch(e) {
exceptionAlert(e);
}
}
export function clearSleevesPage() {
removeElement(UIElems.container);
if (UIElems.container instanceof HTMLElement) {
removeElement(UIElems.container);
}
for (const prop in UIElems) {
(<any>UIElems)[prop] = null;
}
@ -129,6 +162,7 @@ function createSleeveUi(sleeve: Sleeve, allSleeves: Sleeve[]): ISleeveUIElems {
taskDetailsSelector2: null,
taskDescription: null,
taskSetButton: null,
taskProgressBar: null,
earningsPanel: null,
currentEarningsInfo: null,
totalEarningsButton: null,
@ -141,7 +175,7 @@ function createSleeveUi(sleeve: Sleeve, allSleeves: Sleeve[]): ISleeveUIElems {
display: "block",
});
elems.statsPanel = createElement("div", { class: "sleeve-panel" });
elems.statsPanel = createElement("div", { class: "sleeve-panel", width: "25%" });
elems.stats = createElement("p", { class: "sleeve-stats-text" });
elems.moreStatsButton = createElement("button", {
class: "std-button",
@ -181,7 +215,7 @@ function createSleeveUi(sleeve: Sleeve, allSleeves: Sleeve[]): ISleeveUIElems {
elems.statsPanel.appendChild(elems.stats);
elems.statsPanel.appendChild(elems.moreStatsButton);
elems.taskPanel = createElement("div", { class: "sleeve-panel" });
elems.taskPanel = createElement("div", { class: "sleeve-panel", width: "40%" });
elems.taskSelector = createElement("select") as HTMLSelectElement;
elems.taskSelector.add(createOptionElement("------"));
elems.taskSelector.add(createOptionElement("Work for Company"));
@ -194,10 +228,13 @@ function createSleeveUi(sleeve: Sleeve, allSleeves: Sleeve[]): ISleeveUIElems {
elems.taskSelector.addEventListener("change", () => {
updateSleeveTaskSelector(sleeve, elems, allSleeves);
});
// TODO Set initial value for task selector
elems.taskDetailsSelector = createElement("select") as HTMLSelectElement;
elems.taskDetailsSelector2 = createElement("select") as HTMLSelectElement;
elems.taskDescription = createElement("p");
elems.taskProgressBar = createElement("p");
elems.taskSelector.selectedIndex = sleeve.currentTask; // Set initial value for Task Selector
elems.taskSelector.dispatchEvent(new Event('change'));
updateSleeveTaskDescription(sleeve, elems);
elems.taskSetButton = createElement("button", {
class: "std-button",
innerText: "Set Task",
@ -207,10 +244,12 @@ function createSleeveUi(sleeve: Sleeve, allSleeves: Sleeve[]): ISleeveUIElems {
});
elems.taskPanel.appendChild(elems.taskSelector);
elems.taskPanel.appendChild(elems.taskDetailsSelector);
elems.taskPanel.appendChild(elems.taskDetailsSelector2);
elems.taskPanel.appendChild(elems.taskSetButton);
elems.taskPanel.appendChild(elems.taskDescription);
elems.taskPanel.appendChild(elems.taskProgressBar);
elems.earningsPanel = createElement("div", { class: "sleeve-panel" });
elems.earningsPanel = createElement("div", { class: "sleeve-panel", width: "35%" });
elems.currentEarningsInfo = createElement("p");
elems.totalEarningsButton = createElement("button", {
class: "std-button",
@ -218,23 +257,23 @@ function createSleeveUi(sleeve: Sleeve, allSleeves: Sleeve[]): ISleeveUIElems {
clickListener: () => {
dialogBoxCreate(
[
"<h2><u>Total Earnings for Current Task:</u></h2>",
"<h2><u>Earnings for Current Task:</u></h2>",
`Money: ${numeralWrapper.formatMoney(sleeve.earningsForTask.money)}`,
`Hacking Exp: ${numeralWrapper.formatBigNumber(sleeve.earningsForTask.hack)}`,
`Strength Exp: ${numeralWrapper.formatBigNumber(sleeve.earningsForTask.str)}`,
`Defense Exp: ${numeralWrapper.formatBigNumber(sleeve.earningsForTask.def)}`,
`Dexterity Exp: ${numeralWrapper.formatBigNumber(sleeve.earningsForTask.dex)}`,
`Agility Exp: ${numeralWrapper.formatBigNumber(sleeve.earningsForTask.agi)}`,
`Charisma Exp: ${numeralWrapper.formatBigNumber(sleeve.earningsForTask.cha)}`,
"<h2><u>Earnings for Host Consciousness:</u></h2>",
`Charisma Exp: ${numeralWrapper.formatBigNumber(sleeve.earningsForTask.cha)}<br>`,
"<h2><u>Total Earnings for Host Consciousness:</u></h2>",
`Money: ${numeralWrapper.formatMoney(sleeve.earningsForPlayer.money)}`,
`Hacking Exp: ${numeralWrapper.formatBigNumber(sleeve.earningsForPlayer.hack)}`,
`Strength Exp: ${numeralWrapper.formatBigNumber(sleeve.earningsForPlayer.str)}`,
`Defense Exp: ${numeralWrapper.formatBigNumber(sleeve.earningsForPlayer.def)}`,
`Dexterity Exp: ${numeralWrapper.formatBigNumber(sleeve.earningsForPlayer.dex)}`,
`Agility Exp: ${numeralWrapper.formatBigNumber(sleeve.earningsForPlayer.agi)}`,
`Charisma Exp: ${numeralWrapper.formatBigNumber(sleeve.earningsForPlayer.cha)}`,
"<h2><u>Earnings for Other Sleeves:</u></h2>",
`Charisma Exp: ${numeralWrapper.formatBigNumber(sleeve.earningsForPlayer.cha)}<br>`,
"<h2><u>Total Earnings for Other Sleeves:</u></h2>",
`Money: ${numeralWrapper.formatMoney(sleeve.earningsForSleeves.money)}`,
`Hacking Exp: ${numeralWrapper.formatBigNumber(sleeve.earningsForSleeves.hack)}`,
`Strength Exp: ${numeralWrapper.formatBigNumber(sleeve.earningsForSleeves.str)}`,
@ -247,6 +286,15 @@ function createSleeveUi(sleeve: Sleeve, allSleeves: Sleeve[]): ISleeveUIElems {
}
});
elems.earningsPanel.appendChild(elems.currentEarningsInfo);
elems.earningsPanel.appendChild(elems.totalEarningsButton);
updateSleeveUi(sleeve, elems);
elems.container.appendChild(elems.statsPanel);
elems.container.appendChild(elems.taskPanel);
elems.container.appendChild(elems.earningsPanel);
return elems;
}
@ -261,12 +309,19 @@ function updateSleeveUi(sleeve: Sleeve, elems: ISleeveUIElems) {
`Agility: ${numeralWrapper.format(sleeve.agility, "0,0")}`,
`Charisma: ${numeralWrapper.format(sleeve.charisma, "0,0")}`,
`HP: ${numeralWrapper.format(sleeve.hp, "0,0")} / ${numeralWrapper.format(sleeve.max_hp, "0,0")}<br>`,
`Shock: ${numeralWrapper.format(100 - sleeve.shock, "0,0")}`,
`Synchronization: ${numeralWrapper.format(sleeve.sync, "0,0")}`].join("<br>");
`Shock: ${numeralWrapper.format(100 - sleeve.shock, "0,0.000")}`,
`Sync: ${numeralWrapper.format(sleeve.sync, "0,0.000")}`].join("<br>");
let repGainText: string = "";
if (sleeve.currentTask === SleeveTaskType.Company || sleeve.currentTask === SleeveTaskType.Faction) {
const repGain: number = sleeve.getRepGain(playerRef!);
repGainText = `Reputation: ${numeralWrapper.format(5 * repGain, "0.00")} / s`
}
if (sleeve.currentTask === SleeveTaskType.Crime) {
elems.currentEarningsInfo!.innerHTML = [
`Money: ${numeralWrapper.formatMoney(sleeve.gainRatesForTask.money)} if successful`,
`Earnings (Pre-Synchronization):`,
`Money: ${numeralWrapper.formatMoney(parseFloat(sleeve.currentTaskLocation))} if successful`,
`Hacking Exp: ${numeralWrapper.format(sleeve.gainRatesForTask.hack, "0.00")} (2x if successful)`,
`Strength Exp: ${numeralWrapper.format(sleeve.gainRatesForTask.str, "0.00")} (2x if successful)`,
`Defense Exp: ${numeralWrapper.format(sleeve.gainRatesForTask.def, "0.00")} (2x if successful)`,
@ -274,16 +329,26 @@ function updateSleeveUi(sleeve: Sleeve, elems: ISleeveUIElems) {
`Agility Exp: ${numeralWrapper.format(sleeve.gainRatesForTask.agi, "0.00")} (2x if successful)`,
`Charisma Exp: ${numeralWrapper.format(sleeve.gainRatesForTask.cha, "0.00")} (2x if successful)`
].join("<br>");
elems.taskProgressBar!.innerText = createProgressBarText({
progress: sleeve.currentTaskTime / sleeve.currentTaskMaxTime,
totalTicks: 25,
});
} else {
elems.currentEarningsInfo!.innerHTML = [
`Money: ${numeralWrapper.formatMoney(sleeve.gainRatesForTask.money)} / s`,
`Hacking Exp: ${numeralWrapper.format(sleeve.gainRatesForTask.hack, "0.00")} / s`,
`Strength Exp: ${numeralWrapper.format(sleeve.gainRatesForTask.str, "0.00")} / s`,
`Defense Exp: ${numeralWrapper.format(sleeve.gainRatesForTask.def, "0.00")} / s`,
`Dexterity Exp: ${numeralWrapper.format(sleeve.gainRatesForTask.dex, "0.00")} / s`,
`Agility Exp: ${numeralWrapper.format(sleeve.gainRatesForTask.agi, "0.00")} / s`,
`Charisma Exp: ${numeralWrapper.format(sleeve.gainRatesForTask.cha, "0.00")} / s`
].join("<br>");
const lines = [
`Earnings (Pre-Synchronization):`,
`Money: ${numeralWrapper.formatMoney(5 * sleeve.gainRatesForTask.money)} / s`,
`Hacking Exp: ${numeralWrapper.format(5 * sleeve.gainRatesForTask.hack, "0.00")} / s`,
`Strength Exp: ${numeralWrapper.format(5 * sleeve.gainRatesForTask.str, "0.00")} / s`,
`Defense Exp: ${numeralWrapper.format(5 * sleeve.gainRatesForTask.def, "0.00")} / s`,
`Dexterity Exp: ${numeralWrapper.format(5 * sleeve.gainRatesForTask.dex, "0.00")} / s`,
`Agility Exp: ${numeralWrapper.format(5 * sleeve.gainRatesForTask.agi, "0.00")} / s`,
`Charisma Exp: ${numeralWrapper.format(5 * sleeve.gainRatesForTask.cha, "0.00")} / s`
];
if (repGainText !== "") { lines.push(repGainText); }
elems.currentEarningsInfo!.innerHTML = lines.join("<br>");
elems.taskProgressBar!.innerText = "";
}
}
@ -333,7 +398,9 @@ function updateSleeveTaskSelector(sleeve: Sleeve, elems: ISleeveUIElems, allSlee
}
}
// Reset Selectors
removeChildrenFromElement(elems.taskDetailsSelector);
removeChildrenFromElement(elems.taskDetailsSelector2);
const value: string = getSelectValue(elems.taskSelector);
switch(value) {
@ -342,7 +409,14 @@ function updateSleeveTaskSelector(sleeve: Sleeve, elems: ISleeveUIElems, allSlee
for (let i = 0; i < allJobs.length; ++i) {
if (!forbiddenCompanies.includes(allJobs[i])) {
elems.taskDetailsSelector!.add(createOptionElement(allJobs[i]));
// Set initial value of the 'Details' selector
if (sleeve.currentTaskLocation === allJobs[i]) {
elems.taskDetailsSelector!.selectedIndex = i;
}
}
elems.taskDetailsSelector2!.add(createOptionElement("------"));
}
break;
case "Work for Faction":
@ -350,6 +424,11 @@ function updateSleeveTaskSelector(sleeve: Sleeve, elems: ISleeveUIElems, allSlee
const fac: string = playerRef!.factions[i]!;
if (!forbiddenFactions.includes(fac)) {
elems.taskDetailsSelector!.add(createOptionElement(fac));
// Set initial value of the 'Details' Selector
if (sleeve.currentTaskLocation === fac) {
elems.taskDetailsSelector!.selectedIndex = i;
}
}
}
for (let i = 0; i < factionWorkTypeSelectorOptions.length; ++i) {
@ -361,6 +440,8 @@ function updateSleeveTaskSelector(sleeve: Sleeve, elems: ISleeveUIElems, allSlee
const name: string = Crimes[crimeLabel].name;
elems.taskDetailsSelector!.add(createOptionElement(name, crimeLabel));
}
elems.taskDetailsSelector2!.add(createOptionElement("------"));
break;
case "Take University Course":
// First selector has class type
@ -410,17 +491,18 @@ function updateSleeveTaskSelector(sleeve: Sleeve, elems: ISleeveUIElems, allSlee
break;
case "Shock Recovery":
// No options in "Details" selector
return;
case "Synchronize":
case "------":
// No options in "Details" selector
elems.taskDetailsSelector!.add(createOptionElement("------"));
elems.taskDetailsSelector2!.add(createOptionElement("------"));
return;
default:
break;
}
}
function setSleeveTask(sleeve: Sleeve, elems: ISleeveUIElems): void {
function setSleeveTask(sleeve: Sleeve, elems: ISleeveUIElems): boolean {
try {
if (playerRef == null) {
throw new Error("playerRef is null in Sleeve UI's setSleeveTask()");
@ -428,32 +510,21 @@ function setSleeveTask(sleeve: Sleeve, elems: ISleeveUIElems): void {
const taskValue: string = getSelectValue(elems.taskSelector);
const detailValue: string = getSelectValue(elems.taskDetailsSelector);
const detailValue2: string = getSelectValue(elems.taskDetailsSelector);
const detailValue2: string = getSelectValue(elems.taskDetailsSelector2);
let res: boolean = false;
switch(taskValue) {
case "------":
elems.taskDescription!.innerText = "This sleeve is currently idle";
break;
case "Work for Company":
res = sleeve.workForCompany(playerRef!, detailValue);
if (res) {
elems.taskDescription!.innerText = `This sleeve is currently working your ` +
`job at ${sleeve.currentTaskLocation}.`;
} else {
elems.taskDescription!.innerText = "Failed to assign sleeve to task. Invalid choice(s).";
}
break;
case "Work for Faction":
res = sleeve.workForFaction(playerRef!, detailValue, detailValue2);
if (res) {
elems.taskDescription!.innerText = `This sleeve is currently doing ${detailValue2} for ` +
`${sleeve.currentTaskLocation}.`;
} else {
elems.taskDescription!.innerText = "Failed to assign sleeve to task. Invalid choice(s).";
}
break;
case "Commit Crime":
sleeve.commitCrime(playerRef!, Crimes[detailValue]);
elems.taskDescription!.innerText = `This sleeve is currently attempting to ` +
`${Crimes[detailValue]}.`;
res = sleeve.commitCrime(playerRef!, Crimes[detailValue]);
break;
case "Take University Course":
res = sleeve.takeUniversityCourse(playerRef!, detailValue2, detailValue);
@ -463,22 +534,79 @@ function setSleeveTask(sleeve: Sleeve, elems: ISleeveUIElems): void {
break;
case "Shock Recovery":
sleeve.currentTask = SleeveTaskType.Recovery;
elems.taskDescription!.innerText = "This sleeve is currently set to focus on shock recovery. This causes " +
"the Sleeve's shock to decrease at a faster rate.";
res = true;
break;
case "Synchronize":
sleeve.currentTask = SleeveTaskType.Sync;
elems.taskDescription!.innerText = "This sleeve is currently set to synchronize with the original consciousness. " +
"This causes the Sleeve's synchronization to increase."
res = true;
break;
default:
console.error(`Invalid/Unrecognized taskValue in setSleeveTask(): ${taskValue}`);
}
if (res) {
updateSleeveTaskDescription(sleeve, elems);
} else {
elems.taskDescription!.innerText = "Failed to assign sleeve to task. Invalid choice(s).";
}
if (routing.isOn(Page.Sleeves)) {
updateSleevesPage();
}
return res;
} catch(e) {
console.error(`Exception caught in setSleeveTask(): ${e}`);
exceptionAlert(e);
return false;
}
}
function updateSleeveTaskDescription(sleeve: Sleeve, elems: ISleeveUIElems): void {
try {
if (playerRef == null) {
throw new Error("playerRef is null in Sleeve UI's setSleeveTask()");
}
const taskValue: string = getSelectValue(elems.taskSelector);
const detailValue: string = getSelectValue(elems.taskDetailsSelector);
const detailValue2: string = getSelectValue(elems.taskDetailsSelector2);
switch(taskValue) {
case "------":
elems.taskDescription!.innerText = "This sleeve is currently idle";
break;
case "Work for Company":
elems.taskDescription!.innerText = `This sleeve is currently working your ` +
`job at ${sleeve.currentTaskLocation}.`;
break;
case "Work for Faction":
elems.taskDescription!.innerText = `This sleeve is currently doing ${detailValue2} for ` +
`${sleeve.currentTaskLocation}.`;
break;
case "Commit Crime":
elems.taskDescription!.innerText = `This sleeve is currently attempting to ` +
`${Crimes[detailValue].type}.`;
break;
case "Take University Course":
elems.taskDescription!.innerText = `This sleeve is currently studying/taking a course at ${sleeve.currentTaskLocation}.`;
break;
case "Workout at Gym":
elems.taskDescription!.innerText = `This sleeve is currently working out at ${sleeve.currentTaskLocation}.`;
break;
case "Shock Recovery":
elems.taskDescription!.innerText = "This sleeve is currently set to focus on shock recovery. This causes " +
"the Sleeve's shock to decrease at a faster rate.";
break;
case "Synchronize":
elems.taskDescription!.innerText = "This sleeve is currently set to synchronize with the original consciousness. " +
"This causes the Sleeve's synchronization to increase."
break;
default:
console.error(`Invalid/Unrecognized taskValue in updateSleeveTaskDescription(): ${taskValue}`);
}
} catch(e) {
console.error(`Exception caught in updateSleeveTaskDescription(): ${e}`);
exceptionAlert(e);
}
}

@ -0,0 +1,36 @@
export const SleeveFaq: string =
[
"<strong><u>How do sleeves work?</strong></u><br>",
"Sleeves are essentially clones. You can use them to perform any work type",
"action, such as working for a company/faction or committing a crime.",
"Having sleeves perform these tasks earns you money, experience, and reputation.<br><br>",
"Sleeves are their own individuals, which means they each have their own",
"experience and stats.<br><br>",
"When a sleeve earns experience, it earns experience for itself, the player's",
"original 'consciousness', as well as all of the player's other sleeves.<br><br>",
"<strong><u>What is Synchronization (Sync)?</strong></u><br>",
"Synchronization is a measure of how aligned your consciousness is with",
"that of your Duplicate Sleeves. It is a numerical value between 1 and 100, and",
"it affects how much experience is earned when the sleeve is performing a task.<br><br>",
"Let N be the sleeve's synchronization. When the sleeve earns experience by performing a",
"task, both the sleeve and the player's original host consciousness earn N%",
"of the amount of experience normally earned by the task. All of the player's",
"other sleeves earn ((N/100)^2 * 100)% of the experience.<br><br>",
"Synchronization can be increased by assigning sleeves to the 'Synchronize' task.<br><br>",
"<strong><u>What is Shock?</u></strong><br>",
"Sleeve shock is a measure of how much trauma the sleeve has due to being placed in a new",
"body. It is a numerical value between 0 and 99, where 99 indicates full shock and 0 indicates",
"no shock. Shock affects the amount of experience earned by the sleeve.<br><br>",
"Sleeve shock slowly decreases over time. You can further increase the rate at which",
"it decreases by assigning sleeves to the 'Shock Recovery' task.<br><br>",
"<strong><u>Why can't I work for this company or faction?</u></strong><br>",
"Only one of your sleeves can work for a given company/faction a time.",
"To clarify further, if you have two sleeves they can work for two different",
"companies, but they cannot both work for the same company.<br><br>",
"<strong><u>Do sleeves get reset when installing Augmentations or switching BitNodes?</u></strong><br>",
"Sleeves are reset when switching BitNodes, but not when installing Augmentations."
].join(" ");

@ -548,6 +548,14 @@ PlayerObject.prototype.loseMoney = function(money) {
this.money = this.money.minus(money);
}
PlayerObject.prototype.canAfford = function(cost) {
if (isNaN(cost)) {
console.error(`NaN passed into Player.canAfford()`);
return false;
}
return this.money.gte(cost);
}
PlayerObject.prototype.gainHackingExp = function(exp) {
if (isNaN(exp)) {
console.log("ERR: NaN passed into Player.gainHackingExp()"); return;

@ -63,7 +63,8 @@ function initSourceFiles() {
"This Source-File also increases your hacking growth multipliers by: " +
"<br>Level 1: 12%<br>Level 2: 18%<br>Level 3: 21%");
SourceFiles["SourceFile9"] = new SourceFile(9);
SourceFiles["SourceFile10"] = new SourceFile(10);
SourceFiles["SourceFile10"] = new SourceFile(10, "This Source-File unlocks Sleeve technology in other BitNodes. Each level of this " +
"Source-File also grants you a Duplicate Sleeve");
SourceFiles["SourceFile11"] = new SourceFile(11, "This Source-File makes it so that company favor increases BOTH the player's salary and reputation gain rate " +
"at that company by 1% per favor (rather than just the reputation gain). This Source-File also " +
" increases the player's company salary and reputation gain multipliers by:<br><br>" +
@ -187,6 +188,9 @@ function applySourceFile(srcFile) {
var incMult = 1 + (mult / 100);
Player.hacking_grow_mult *= incMult;
break;
case 10: // Digital Carbon
// No effects, just grants sleeves
break;
case 11: //The Big Crash
var mult = 0;
for (var i = 0; i < srcFile.lvl; ++i) {

@ -230,35 +230,35 @@ function initStockMarket() {
const randInt = getRandomInt;
var ecorp = Locations.AevumECorp;
var ecorpStk = new Stock(ecorp, StockSymbols[ecorp], randInt(40, 50) / 100, true, 19, randInt(17e3, 28e3), 3e12);
var ecorpStk = new Stock(ecorp, StockSymbols[ecorp], randInt(40, 50) / 100, true, 19, randInt(17e3, 28e3), 2.4e12);
StockMarket[ecorp] = ecorpStk;
var megacorp = Locations.Sector12MegaCorp;
var megacorpStk = new Stock(megacorp, StockSymbols[megacorp], randInt(40,50)/100, true, 19, randInt(24e3, 34e3), 3e12);
var megacorpStk = new Stock(megacorp, StockSymbols[megacorp], randInt(40,50)/100, true, 19, randInt(24e3, 34e3), 2.4e12);
StockMarket[megacorp] = megacorpStk;
var blade = Locations.Sector12BladeIndustries;
var bladeStk = new Stock(blade, StockSymbols[blade], randInt(70, 80)/100, true, 13, randInt(12e3, 25e3), 1.9e12);
var bladeStk = new Stock(blade, StockSymbols[blade], randInt(70, 80)/100, true, 13, randInt(12e3, 25e3), 1.6e12);
StockMarket[blade] = bladeStk;
var clarke = Locations.AevumClarkeIncorporated;
var clarkeStk = new Stock(clarke, StockSymbols[clarke], randInt(65, 75)/100, true, 12, randInt(10e3, 25e3), 1.8e12);
var clarkeStk = new Stock(clarke, StockSymbols[clarke], randInt(65, 75)/100, true, 12, randInt(10e3, 25e3), 1.5e12);
StockMarket[clarke] = clarkeStk;
var omnitek = Locations.VolhavenOmniTekIncorporated;
var omnitekStk = new Stock(omnitek, StockSymbols[omnitek], randInt(60, 70)/100, true, 12, randInt(32e3, 43e3), 2.1e12);
var omnitekStk = new Stock(omnitek, StockSymbols[omnitek], randInt(60, 70)/100, true, 12, randInt(32e3, 43e3), 1.8e12);
StockMarket[omnitek] = omnitekStk;
var foursigma = Locations.Sector12FourSigma;
var foursigmaStk = new Stock(foursigma, StockSymbols[foursigma], randInt(100, 110)/100, true, 17, randInt(50e3, 80e3), 2.4e12);
var foursigmaStk = new Stock(foursigma, StockSymbols[foursigma], randInt(100, 110)/100, true, 17, randInt(50e3, 80e3), 2e12);
StockMarket[foursigma] = foursigmaStk;
var kuaigong = Locations.ChongqingKuaiGongInternational;
var kuaigongStk = new Stock(kuaigong, StockSymbols[kuaigong], randInt(75, 85)/100, true, 10, randInt(16e3, 28e3), 2.3e12);
var kuaigongStk = new Stock(kuaigong, StockSymbols[kuaigong], randInt(75, 85)/100, true, 10, randInt(16e3, 28e3), 1.9e12);
StockMarket[kuaigong] = kuaigongStk;
var fulcrum = Locations.AevumFulcrumTechnologies;
var fulcrumStk = new Stock(fulcrum, StockSymbols[fulcrum], randInt(120, 130)/100, true, 16, randInt(29e3, 36e3), 2.4e12);
var fulcrumStk = new Stock(fulcrum, StockSymbols[fulcrum], randInt(120, 130)/100, true, 16, randInt(29e3, 36e3), 2e12);
StockMarket[fulcrum] = fulcrumStk;
var storm = Locations.IshimaStormTechnologies;

@ -66,6 +66,10 @@ import {StockMarket, StockSymbols,
displayStockMarketContent} from "./StockMarket/StockMarket";
import {Terminal, postNetburnerText} from "./Terminal";
import { Sleeve } from "./PersonObjects/Sleeve/Sleeve";
import { clearSleevesPage,
createSleevesPage,
updateSleevesPage } from "./PersonObjects/Sleeve/SleeveUI";
import { clearResleevesPage,
createResleevesPage } from "./PersonObjects/Resleeving/ResleevingUI";
@ -111,6 +115,8 @@ import "../css/missions.scss";
import "../css/companymanagement.scss";
import "../css/bladeburner.scss";
import "../css/gang.scss";
import "../css/sleeves.scss";
import "../css/resleeving.scss";
import "../css/treant.css";
@ -472,6 +478,13 @@ const Engine = {
loadSleevesContent: function() {
// This is for Duplicate Sleeves page, not Re-sleeving @ Vita Life
try {
Engine.hideAllContent();
routing.navigateTo(Page.Sleeves);
createSleevesPage(Player);
} catch(e) {
exceptionAlert(e);
}
},
loadResleevingContent: function() {
@ -520,6 +533,9 @@ const Engine = {
Player.bladeburner.clearContent();
}
clearResleevesPage();
clearSleevesPage();
//Location lists
Engine.aevumLocationsList.style.display = "none";
Engine.chongqingLocationsList.style.display = "none";
@ -944,6 +960,20 @@ const Engine = {
Player.bladeburner.storeCycles(numCycles);
}
// Sleeves
for (let i = 0; i < Player.sleeves.length; ++i) {
if (Player.sleeves[i] instanceof Sleeve) {
const expForOtherSleeves = Player.sleeves[i].process(Player, numCycles);
// This sleeve earns experience for other sleeves
if (expForOtherSleeves == null) { continue; }
for (let j = 0; j < Player.sleeves.length; ++j) {
if (j === i) { continue; }
Player.sleeves[j].gainExperience(Player, expForOtherSleeves, numCycles);
}
}
}
//Counters
Engine.decrementAllCounters(numCycles);
Engine.checkCounters();
@ -1024,6 +1054,8 @@ const Engine = {
updateHacknetNodesContent();
} else if (routing.isOn(Page.CreateProgram)) {
displayCreateProgramContent();
} else if (routing.isOn(Page.Sleeves)) {
updateSleevesPage();
}
if (logBoxOpened) {
@ -1295,6 +1327,20 @@ const Engine = {
Player.bladeburner.storeCycles(numCyclesOffline);
}
// Sleeves offline progress
for (let i = 0; i < Player.sleeves.length; ++i) {
if (Player.sleeves[i] instanceof Sleeve) {
const expForOtherSleeves = Player.sleeves[i].process(Player, numCyclesOffline);
// This sleeve earns experience for other sleeves
if (expForOtherSleeves == null) { continue; }
for (let j = 0; j < Player.sleeves.length; ++j) {
if (j === i) { continue; }
Player.sleeves[j].gainExperience(Player, expForOtherSleeves, numCyclesOffline);
}
}
}
//Update total playtime
var time = numCyclesOffline * Engine._idleSpeed;
if (Player.totalPlaytime == null) {Player.totalPlaytime = 0;}
@ -1582,6 +1628,11 @@ const Engine = {
return false;
});
MainMenuLinks.Sleeves.addEventListener("click", function() {
Engine.loadSleevesContent();
return false;
});
MainMenuLinks.City.addEventListener("click", function() {
Engine.loadWorldContent();
return false;

@ -67,6 +67,9 @@ if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
<li id="hacknet-nodes-tab" class="mainmenu-accordion-panel">
<button id="hacknet-nodes-menu-link"> Hacknet Nodes </button>
</li>
<li id="sleeves-tab" class="mainmenu-accordion-panel">
<button id="sleeves-menu-link"> Sleeves </button>
</li>
<!-- World dropdown -->
<li id="world-menu-header-li">

@ -84,11 +84,14 @@ export function initializeMainMenuHeaders(p: IPlayer, dev: boolean=false): boole
const factions: HTMLElement = safeGetElement("factions-tab");
const augmentations: HTMLElement = safeGetElement("augmentations-tab");
const hacknetnodes: HTMLElement = safeGetElement("hacknet-nodes-tab");
const sleeves: HTMLElement = safeGetElement("sleeves-tab");
sleeves.style.display = p.sleeves.length > 0 ? "list-item" : "none";
this.classList.toggle("opened");
const elems: HTMLElement[] = [stats, factions, augmentations, hacknetnodes];
const links: HTMLElement[] = [MainMenuLinks.Stats!, MainMenuLinks.Factions!, MainMenuLinks.Augmentations!, MainMenuLinks.HacknetNodes!];
const elems: HTMLElement[] = [stats, factions, augmentations, hacknetnodes, sleeves];
const links: HTMLElement[] = [MainMenuLinks.Stats!, MainMenuLinks.Factions!, MainMenuLinks.Augmentations!, MainMenuLinks.HacknetNodes!, MainMenuLinks.Sleeves!];
if (stats.style.maxHeight) {
toggleHeader(false, elems, links);
} else {

@ -11,6 +11,7 @@ interface IMainMenuLinks {
Factions: HTMLElement | null;
Augmentations: HTMLElement | null;
HacknetNodes: HTMLElement | null;
Sleeves: HTMLElement | null;
City: HTMLElement | null;
Travel: HTMLElement | null;
Job: HTMLElement | null;
@ -32,6 +33,7 @@ export const MainMenuLinks: IMainMenuLinks = {
Factions: null,
Augmentations: null,
HacknetNodes: null,
Sleeves: null,
City: null,
Travel: null,
Job: null,
@ -63,6 +65,7 @@ export function initializeMainMenuLinks(): boolean {
MainMenuLinks.Factions = safeGetLink("factions-menu-link");
MainMenuLinks.Augmentations = safeGetLink("augmentations-menu-link");
MainMenuLinks.HacknetNodes = safeGetLink("hacknet-nodes-menu-link");
MainMenuLinks.Sleeves = safeGetLink("sleeves-menu-link");
MainMenuLinks.City = safeGetLink("city-menu-link");
MainMenuLinks.Travel = safeGetLink("travel-menu-link");
MainMenuLinks.Job = safeGetLink("job-menu-link");

@ -1,9 +1,11 @@
export function getSelectValue(selector: HTMLSelectElement | null): string {
if (selector == null) { return ""; }
return selector[selector.selectedIndex].value;
if (selector.options.length <= 0) { return ""; }
return selector.options[selector.selectedIndex].value;
}
export function getSelectText(selector: HTMLSelectElement | null): string {
if (selector == null) { return ""; }
return selector[selector.selectedIndex].text;
if (selector.options.length <= 0) { return ""; }
return selector.options[selector.selectedIndex].text;
}