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

View File

@ -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
View 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
View 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;
}
}

View File

@ -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;
}
}

View File

@ -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
View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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
^^^^^^^^^^^

View File

@ -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) |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+
| | |
+------------------------------------+-------------------------------------------------------------------------------------+

View File

@ -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.

View File

@ -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.

View File

@ -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 | |
+---------------------+----------------+-----------------------------------------+-------------------------------+

View File

@ -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

View File

@ -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>

View File

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

View File

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

View File

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

View File

@ -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

View File

@ -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">

View File

@ -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 " +

View File

@ -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
`
}

View File

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

View File

@ -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,

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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,
}

View File

@ -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);
}
}

View File

@ -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(" ");

View File

@ -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;

View File

@ -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) {

View File

@ -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;

View File

@ -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;

View File

@ -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">

View File

@ -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 {

View File

@ -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");

View File

@ -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;
}