mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-16 22:38:34 +02:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87c63cde59 | ||
|
|
383b02fdbb | ||
|
|
41de3102c7 | ||
|
|
783750051e | ||
|
|
0aa26df9d7 | ||
|
|
4355420349 | ||
|
|
f4ecbd9b48 | ||
|
|
528a8f30db | ||
|
|
500063e87e | ||
|
|
8d7f0488f8 | ||
|
|
62bdfb1875 | ||
|
|
8e5c10cc2f | ||
|
|
15a03dd532 | ||
|
|
8e58482db0 | ||
|
|
da746a63c3 | ||
|
|
2f677c7ec8 | ||
|
|
c5e29dafc4 | ||
|
|
880654c222 | ||
|
|
4fc6d393e4 | ||
|
|
7304e5379f | ||
|
|
793d9b34ce |
23
css/staneksgift.scss
Normal file
23
css/staneksgift.scss
Normal file
@@ -0,0 +1,23 @@
|
||||
.staneksgift_row {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.staneksgift_cell {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
background-color: #808080;
|
||||
font-color: white;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
border: 1px solid black;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.staneksgift_cell:first-child {
|
||||
clear: left;
|
||||
}
|
||||
|
||||
.staneksgift_container {
|
||||
position: fixed;
|
||||
}
|
||||
64
dist/vendor.bundle.js
vendored
64
dist/vendor.bundle.js
vendored
File diff suppressed because one or more lines are too long
@@ -30,5 +30,6 @@ to reach out to the developer!
|
||||
Gang API <netscript/netscriptgangapi>
|
||||
Coding Contract API <netscript/netscriptcodingcontractapi>
|
||||
Sleeve API <netscript/netscriptsleeveapi>
|
||||
Stanek API <netscript/netscriptstanekapi>
|
||||
Formulas API <netscript/netscriptformulasapi>
|
||||
Miscellaneous <netscript/netscriptmisc>
|
||||
|
||||
20
doc/source/netscript/netscriptstanekapi.rst
Normal file
20
doc/source/netscript/netscriptstanekapi.rst
Normal file
@@ -0,0 +1,20 @@
|
||||
.. _netscriptstanek:
|
||||
|
||||
Netscript Stanek Functions
|
||||
============================
|
||||
|
||||
.. warning:: This page contains spoilers for the game.
|
||||
|
||||
The Stanek API allow you to control Stanek's Gift.
|
||||
|
||||
All these function require Source-File 13-1 or to be in BitNode 13.
|
||||
|
||||
.. toctree::
|
||||
charge() <stanekapi/charge>
|
||||
fragmentDefinitions() <stanekapi/fragmentDefinitions>
|
||||
placedFragments() <stanekapi/placedFragments>
|
||||
clear() <stanekapi/clear>
|
||||
canPlace() <stanekapi/canPlace>
|
||||
place() <stanekapi/place>
|
||||
fragmentAt() <stanekapi/fragmentAt>
|
||||
deleteAt() <stanekapi/deleteAt>
|
||||
15
doc/source/netscript/stanekapi/canPlace.rst
Normal file
15
doc/source/netscript/stanekapi/canPlace.rst
Normal file
@@ -0,0 +1,15 @@
|
||||
canPlace() Netscript Function
|
||||
=======================================
|
||||
|
||||
.. js:function:: canPlace(worldX, worldY, fragmentId)
|
||||
|
||||
:RAM cost: 0.5 GB
|
||||
:param int worldX: World X against which to align the top left of the fragment.
|
||||
:param int worldY: World Y against which to align the top left of the fragment.
|
||||
:param int fragmentId: ID of the fragment to place.
|
||||
:returns: `true` if the fragment can be placed at that position. `false` otherwise.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: javascript
|
||||
canPlace(0, 4, 17); // returns true
|
||||
21
doc/source/netscript/stanekapi/charge.rst
Normal file
21
doc/source/netscript/stanekapi/charge.rst
Normal file
@@ -0,0 +1,21 @@
|
||||
charge() Netscript Function
|
||||
=======================================
|
||||
|
||||
.. js:function:: charge(worldX, worldY)
|
||||
|
||||
:RAM cost: 0.4 GB
|
||||
:param int worldX: World X of the fragment to charge.
|
||||
:param int worldY: World Y of the fragment to charge.
|
||||
|
||||
Charge a fragment, increasing it's power but also it's heat. The
|
||||
effectiveness of the charge depends on the amount of ram the running script
|
||||
consumes as well as the fragments current heat. This operation takes time to
|
||||
complete.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: javascript
|
||||
charge(0, 4); // Finishes 5 seconds later.
|
||||
.. warning::
|
||||
|
||||
Netscript JS users: This function is `async`
|
||||
13
doc/source/netscript/stanekapi/clear.rst
Normal file
13
doc/source/netscript/stanekapi/clear.rst
Normal file
@@ -0,0 +1,13 @@
|
||||
clear() Netscript Function
|
||||
=======================================
|
||||
|
||||
.. js:function:: clear()
|
||||
|
||||
:RAM cost: 0 GB
|
||||
|
||||
Completely clear Stanek's Gift.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: javascript
|
||||
clear(); // No more fragments.
|
||||
16
doc/source/netscript/stanekapi/deleteAt.rst
Normal file
16
doc/source/netscript/stanekapi/deleteAt.rst
Normal file
@@ -0,0 +1,16 @@
|
||||
deleteAt() Netscript Function
|
||||
=======================================
|
||||
|
||||
.. js:function:: deleteAt(worldX, worldY)
|
||||
|
||||
:RAM cost: 0.15 GB
|
||||
:param int worldX: World X coordinate of the fragment to delete.
|
||||
:param int worldY: World Y coordinate of the fragment to delete.
|
||||
:returns: `true` if the fragment was deleted. `false` otherwise.
|
||||
|
||||
Delete the fragment located at `[worldX, worldY]`.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: javascript
|
||||
deleteAt(0, 4); // returns true
|
||||
28
doc/source/netscript/stanekapi/fragmentAt.rst
Normal file
28
doc/source/netscript/stanekapi/fragmentAt.rst
Normal file
@@ -0,0 +1,28 @@
|
||||
fragmentAt() Netscript Function
|
||||
=======================================
|
||||
|
||||
.. js:function:: fragmentAt(worldX, worldY)
|
||||
|
||||
:RAM cost: 2 GB
|
||||
:param int worldX: World X coordinate of the fragment.
|
||||
:param int worldY: World Y coordinate of the fragment.
|
||||
:returns: The fragment located at `[worldX, worldY]` in Stanek's Gift, or null.
|
||||
|
||||
.. code-block:: typescript
|
||||
{
|
||||
// In world coordinates
|
||||
x: number;
|
||||
y: number;
|
||||
heat: number;
|
||||
charge: number;
|
||||
id: number;
|
||||
shape: boolean[][];
|
||||
type: string;
|
||||
magnitude: number;
|
||||
limit: number;
|
||||
}
|
||||
Example:
|
||||
|
||||
.. code-block:: javascript
|
||||
var fragment = fragmentAt(0, 4);
|
||||
print(fragment); // {'heat': 50, 'charge': 98}
|
||||
23
doc/source/netscript/stanekapi/fragmentDefinitions.rst
Normal file
23
doc/source/netscript/stanekapi/fragmentDefinitions.rst
Normal file
@@ -0,0 +1,23 @@
|
||||
fragmentDefinitions() Netscript Function
|
||||
=======================================
|
||||
|
||||
.. js:function:: fragmentDefinitions()
|
||||
|
||||
:RAM cost: 0 GB
|
||||
:returns: The list of all fragment that can be embedded in Stanek's Gift.
|
||||
|
||||
.. code-block:: typescript
|
||||
[
|
||||
{
|
||||
id: number;
|
||||
shape: boolean[][];
|
||||
type: string;
|
||||
magnitude: number;
|
||||
limit: number;
|
||||
}
|
||||
]
|
||||
Example:
|
||||
|
||||
.. code-block:: javascript
|
||||
var fragments = fragmentDefinitions();
|
||||
print(fragment); // prints all possible fragments
|
||||
15
doc/source/netscript/stanekapi/place.rst
Normal file
15
doc/source/netscript/stanekapi/place.rst
Normal file
@@ -0,0 +1,15 @@
|
||||
place() Netscript Function
|
||||
=======================================
|
||||
|
||||
.. js:function:: place(worldX, worldY, fragmentId)
|
||||
|
||||
:RAM cost: 5 GB
|
||||
:param int worldX: World X against which to align the top left of the fragment.
|
||||
:param int worldY: World Y against which to align the top left of the fragment.
|
||||
:param int fragmentId: ID of the fragment to place.
|
||||
:returns: `true` if the fragment has been placed at that position. `false` otherwise.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: javascript
|
||||
place(0, 4, 17); // returns true
|
||||
27
doc/source/netscript/stanekapi/placedFragments.rst
Normal file
27
doc/source/netscript/stanekapi/placedFragments.rst
Normal file
@@ -0,0 +1,27 @@
|
||||
placedFragments() Netscript Function
|
||||
=======================================
|
||||
|
||||
.. js:function:: placedFragments()
|
||||
|
||||
:RAM cost: 5 GB
|
||||
:returns: The list of all fragment that are embedded in Stanek's Gift.
|
||||
|
||||
.. code-block:: typescript
|
||||
[
|
||||
{
|
||||
// In world coordinates
|
||||
x: number;
|
||||
y: number;
|
||||
heat: number;
|
||||
charge: number;
|
||||
id: number;
|
||||
shape: boolean[][];
|
||||
type: string;
|
||||
magnitude: number;
|
||||
limit: number;
|
||||
}
|
||||
]
|
||||
Example:
|
||||
|
||||
.. code-block:: javascript
|
||||
var myFragments = placedFragments();
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -2366,6 +2366,145 @@ function initAugmentations(): void {
|
||||
resetAugmentation(BladesSimulacrum);
|
||||
}
|
||||
|
||||
// Special CotMG Augmentations
|
||||
const ChurchOfTheMachineGodFactionName = "Church of the Machine God";
|
||||
if (factionExists(ChurchOfTheMachineGodFactionName)) {
|
||||
const StaneksGift1 = new Augmentation({
|
||||
name: AugmentationNames.StaneksGift1,
|
||||
repCost: 0,
|
||||
moneyCost: 0,
|
||||
info:
|
||||
'Allison "Mother" Stanek imparts you with her gift. An ' +
|
||||
"experimental Augmentation implanted at the base of the neck. " +
|
||||
"It allows you to overclock your entire system by carefully " +
|
||||
"changing the configuration.",
|
||||
isSpecial: true,
|
||||
hacking_chance_mult: 0.9,
|
||||
hacking_speed_mult: 0.9,
|
||||
hacking_money_mult: 0.9,
|
||||
hacking_grow_mult: 0.9,
|
||||
hacking_mult: 0.9,
|
||||
strength_mult: 0.9,
|
||||
defense_mult: 0.9,
|
||||
dexterity_mult: 0.9,
|
||||
agility_mult: 0.9,
|
||||
charisma_mult: 0.9,
|
||||
hacking_exp_mult: 0.9,
|
||||
strength_exp_mult: 0.9,
|
||||
defense_exp_mult: 0.9,
|
||||
dexterity_exp_mult: 0.9,
|
||||
agility_exp_mult: 0.9,
|
||||
charisma_exp_mult: 0.9,
|
||||
company_rep_mult: 0.9,
|
||||
faction_rep_mult: 0.9,
|
||||
crime_money_mult: 0.9,
|
||||
crime_success_mult: 0.9,
|
||||
hacknet_node_money_mult: 0.9,
|
||||
hacknet_node_purchase_cost_mult: 1.1,
|
||||
hacknet_node_ram_cost_mult: 1.1,
|
||||
hacknet_node_core_cost_mult: 1.1,
|
||||
hacknet_node_level_cost_mult: 1.1,
|
||||
work_money_mult: 0.9,
|
||||
stats: <>Its unstable nature decreases all your stats by 10%</>,
|
||||
});
|
||||
StaneksGift1.addToFactions([ChurchOfTheMachineGodFactionName]);
|
||||
resetAugmentation(StaneksGift1);
|
||||
|
||||
const StaneksGift2 = new Augmentation({
|
||||
name: AugmentationNames.StaneksGift2,
|
||||
repCost: 1000,
|
||||
moneyCost: 0,
|
||||
info:
|
||||
'TODO, something about Mother being bullshit and you get more control over her "gift"<br><br>' +
|
||||
"The penalty for the gift is only 5%",
|
||||
prereqs: [AugmentationNames.StaneksGift1],
|
||||
isSpecial: true,
|
||||
hacking_chance_mult: 0.95 / 0.9,
|
||||
hacking_speed_mult: 0.95 / 0.9,
|
||||
hacking_money_mult: 0.95 / 0.9,
|
||||
hacking_grow_mult: 0.95 / 0.9,
|
||||
hacking_mult: 0.95 / 0.9,
|
||||
strength_mult: 0.95 / 0.9,
|
||||
defense_mult: 0.95 / 0.9,
|
||||
dexterity_mult: 0.95 / 0.9,
|
||||
agility_mult: 0.95 / 0.9,
|
||||
charisma_mult: 0.95 / 0.9,
|
||||
hacking_exp_mult: 0.95 / 0.9,
|
||||
strength_exp_mult: 0.95 / 0.9,
|
||||
defense_exp_mult: 0.95 / 0.9,
|
||||
dexterity_exp_mult: 0.95 / 0.9,
|
||||
agility_exp_mult: 0.95 / 0.9,
|
||||
charisma_exp_mult: 0.95 / 0.9,
|
||||
company_rep_mult: 0.95 / 0.9,
|
||||
faction_rep_mult: 0.95 / 0.9,
|
||||
crime_money_mult: 0.95 / 0.9,
|
||||
crime_success_mult: 0.95 / 0.9,
|
||||
hacknet_node_money_mult: 0.95 / 0.9,
|
||||
hacknet_node_purchase_cost_mult: 1.05 / 1.1,
|
||||
hacknet_node_ram_cost_mult: 1.05 / 1.1,
|
||||
hacknet_node_core_cost_mult: 1.05 / 1.1,
|
||||
hacknet_node_level_cost_mult: 1.05 / 1.1,
|
||||
work_money_mult: 0.95 / 0.9,
|
||||
stats: null,
|
||||
});
|
||||
StaneksGift2.addToFactions([ChurchOfTheMachineGodFactionName]);
|
||||
resetAugmentation(StaneksGift2);
|
||||
|
||||
const StaneksGift3 = new Augmentation({
|
||||
name: AugmentationNames.StaneksGift3,
|
||||
repCost: 10000,
|
||||
moneyCost: 0,
|
||||
info:
|
||||
"TODO, learn more about Allisons scheme, gain full control over the gift.<br><br>" +
|
||||
"Finally freed from the penalty of the gift.",
|
||||
prereqs: [AugmentationNames.StaneksGift2],
|
||||
isSpecial: true,
|
||||
hacking_chance_mult: 1 / 0.95,
|
||||
hacking_speed_mult: 1 / 0.95,
|
||||
hacking_money_mult: 1 / 0.95,
|
||||
hacking_grow_mult: 1 / 0.95,
|
||||
hacking_mult: 1 / 0.95,
|
||||
strength_mult: 1 / 0.95,
|
||||
defense_mult: 1 / 0.95,
|
||||
dexterity_mult: 1 / 0.95,
|
||||
agility_mult: 1 / 0.95,
|
||||
charisma_mult: 1 / 0.95,
|
||||
hacking_exp_mult: 1 / 0.95,
|
||||
strength_exp_mult: 1 / 0.95,
|
||||
defense_exp_mult: 1 / 0.95,
|
||||
dexterity_exp_mult: 1 / 0.95,
|
||||
agility_exp_mult: 1 / 0.95,
|
||||
charisma_exp_mult: 1 / 0.95,
|
||||
company_rep_mult: 1 / 0.95,
|
||||
faction_rep_mult: 1 / 0.95,
|
||||
crime_money_mult: 1 / 0.95,
|
||||
crime_success_mult: 1 / 0.95,
|
||||
hacknet_node_money_mult: 1 / 0.95,
|
||||
hacknet_node_purchase_cost_mult: 1 / 1.05,
|
||||
hacknet_node_ram_cost_mult: 1 / 1.05,
|
||||
hacknet_node_core_cost_mult: 1 / 1.05,
|
||||
hacknet_node_level_cost_mult: 1 / 1.05,
|
||||
work_money_mult: 1 / 0.95,
|
||||
stats: null,
|
||||
});
|
||||
StaneksGift3.addToFactions([ChurchOfTheMachineGodFactionName]);
|
||||
resetAugmentation(StaneksGift3);
|
||||
|
||||
const StaneksGiftAscension4 = new Augmentation({
|
||||
name: AugmentationNames.StaneksGift4,
|
||||
repCost: 500000000,
|
||||
moneyCost: 0,
|
||||
info:
|
||||
"Allow Allison to install an Ascension port in her Gift. Allowing you to connect with the Machine God.<br><br>" +
|
||||
"(hydro notes: Finishes the BN, eventually)",
|
||||
prereqs: [AugmentationNames.StaneksGift3],
|
||||
isSpecial: true,
|
||||
stats: null,
|
||||
});
|
||||
StaneksGiftAscension4.addToFactions([ChurchOfTheMachineGodFactionName]);
|
||||
resetAugmentation(StaneksGiftAscension4);
|
||||
}
|
||||
|
||||
// Update costs based on how many have been purchased
|
||||
mult = Math.pow(
|
||||
CONSTANTS.MultipleAugMultiplier * [1, 0.96, 0.94, 0.93][SourceFileFlags[11]],
|
||||
|
||||
@@ -108,6 +108,10 @@ export const AugmentationNames: {
|
||||
BladeArmorOmnibeam: string;
|
||||
BladeArmorIPU: string;
|
||||
BladesSimulacrum: string;
|
||||
StaneksGift1: string;
|
||||
StaneksGift2: string;
|
||||
StaneksGift3: string;
|
||||
StaneksGift4: string;
|
||||
} = {
|
||||
Targeting1: "Augmented Targeting I",
|
||||
Targeting2: "Augmented Targeting II",
|
||||
@@ -219,6 +223,11 @@ export const AugmentationNames: {
|
||||
BladeArmorIPU: "BLADE-51b Tesla Armor: IPU Upgrade",
|
||||
BladesSimulacrum: "The Blade's Simulacrum",
|
||||
|
||||
StaneksGift1: "Stanek's Gift - Genesis",
|
||||
StaneksGift2: "Stanek's Gift - Awakening",
|
||||
StaneksGift3: "Stanek's Gift - Serenity",
|
||||
StaneksGift4: "Stanek's Gift - Ascension",
|
||||
|
||||
//Wasteland Augs
|
||||
//PepBoy: "P.E.P-Boy", Plasma Energy Projection System
|
||||
//PepBoyForceField Generates plasma force fields
|
||||
|
||||
@@ -530,8 +530,38 @@ BitNodes["BitNode12"] = new BitNode(
|
||||
</>
|
||||
),
|
||||
);
|
||||
BitNodes["BitNode13"] = new BitNode(
|
||||
13,
|
||||
2,
|
||||
"They're lunatics",
|
||||
"1 step back, 2 steps forward",
|
||||
(
|
||||
<>
|
||||
With the invention of Augmentations in the 2040s a religious group known as the Church of the Machine God has
|
||||
rallied far more support than anyone would have hoped.
|
||||
<br />
|
||||
<br />
|
||||
Their leader, Allison "Mother" Stanek is said to have created her own Augmentation whose power goes beyond any
|
||||
other. Find her in Chongquing and gain her trust.
|
||||
<br />
|
||||
<br />
|
||||
In this BitNode:
|
||||
<br />
|
||||
<br />
|
||||
Every is significantly reduced
|
||||
<br />
|
||||
Stanek's Gift power is significantly increased.
|
||||
<br />
|
||||
<br />
|
||||
Destroying this BitNode will give you Source-File 13, or if you already have this Source-File it will upgrade its
|
||||
level up to a maximum of 3. This Source-File lets the Church of the Machine God appear in other BitNodes.
|
||||
<br />
|
||||
<br />
|
||||
Each level of this Source-File increases the size of Stanek's Gift.
|
||||
</>
|
||||
),
|
||||
);
|
||||
// Books: Frontera, Shiner
|
||||
BitNodes["BitNode13"] = new BitNode(13, 2, "fOS", "COMING SOON"); //Unlocks the new game mode and the rest of the BitNodes
|
||||
BitNodes["BitNode14"] = new BitNode(14, 2, "", "COMING SOON");
|
||||
BitNodes["BitNode15"] = new BitNode(15, 2, "", "COMING SOON");
|
||||
BitNodes["BitNode16"] = new BitNode(16, 2, "", "COMING SOON");
|
||||
@@ -553,6 +583,8 @@ export function initBitNodeMultipliers(p: IPlayer): void {
|
||||
BitNodeMultipliers[mult] = 1;
|
||||
}
|
||||
}
|
||||
// Special case.
|
||||
BitNodeMultipliers.StaneksGiftExtraSize = 0;
|
||||
|
||||
switch (p.bitNodeN) {
|
||||
case 1: // Source Genesis (every multiplier is 1)
|
||||
@@ -793,6 +825,47 @@ export function initBitNodeMultipliers(p: IPlayer): void {
|
||||
BitNodeMultipliers.BladeburnerSkillCost = inc;
|
||||
break;
|
||||
}
|
||||
case 13: {
|
||||
BitNodeMultipliers.DaedalusAugsRequirement = 100;
|
||||
|
||||
BitNodeMultipliers.HackingLevelMultiplier = 0.2;
|
||||
BitNodeMultipliers.StrengthLevelMultiplier = 0.2;
|
||||
BitNodeMultipliers.DefenseLevelMultiplier = 0.2;
|
||||
BitNodeMultipliers.DexterityLevelMultiplier = 0.2;
|
||||
BitNodeMultipliers.AgilityLevelMultiplier = 0.2;
|
||||
BitNodeMultipliers.CharismaLevelMultiplier = 0.2;
|
||||
|
||||
BitNodeMultipliers.ServerMaxMoney = 0.15;
|
||||
BitNodeMultipliers.ServerStartingMoney = 0.75;
|
||||
|
||||
BitNodeMultipliers.ServerStartingSecurity = 2;
|
||||
|
||||
BitNodeMultipliers.ScriptHackMoney = 0.2;
|
||||
BitNodeMultipliers.CompanyWorkMoney = 0.2;
|
||||
BitNodeMultipliers.CrimeMoney = 0.2;
|
||||
BitNodeMultipliers.HacknetNodeMoney = 0.2;
|
||||
BitNodeMultipliers.CodingContractMoney = 0.2;
|
||||
|
||||
BitNodeMultipliers.CompanyWorkExpGain = 0.1;
|
||||
BitNodeMultipliers.ClassGymExpGain = 0.1;
|
||||
BitNodeMultipliers.FactionWorkExpGain = 0.1;
|
||||
BitNodeMultipliers.HackExpGain = 0.1;
|
||||
BitNodeMultipliers.CrimeExpGain = 0.1;
|
||||
|
||||
BitNodeMultipliers.FactionWorkRepGain = 0.4;
|
||||
|
||||
BitNodeMultipliers.FourSigmaMarketDataCost = 10;
|
||||
BitNodeMultipliers.FourSigmaMarketDataApiCost = 10;
|
||||
|
||||
BitNodeMultipliers.CorporationValuation = 0.001;
|
||||
|
||||
BitNodeMultipliers.BladeburnerRank = 0.1;
|
||||
BitNodeMultipliers.BladeburnerSkillCost = 5;
|
||||
BitNodeMultipliers.GangKarmaRequirement = 20;
|
||||
BitNodeMultipliers.StaneksGiftPowerMultiplier = 2;
|
||||
BitNodeMultipliers.StaneksGiftExtraSize = 1;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.warn("Player.bitNodeN invalid");
|
||||
break;
|
||||
|
||||
@@ -212,6 +212,16 @@ interface IBitNodeMultipliers {
|
||||
*/
|
||||
StrengthLevelMultiplier: number;
|
||||
|
||||
/**
|
||||
* Influences the power of the gift.
|
||||
*/
|
||||
StaneksGiftPowerMultiplier: number;
|
||||
|
||||
/**
|
||||
* Influences the size of the gift.
|
||||
*/
|
||||
StaneksGiftExtraSize: number;
|
||||
|
||||
// Index signature
|
||||
[key: string]: number;
|
||||
}
|
||||
@@ -274,4 +284,7 @@ export const BitNodeMultipliers: IBitNodeMultipliers = {
|
||||
|
||||
DaedalusAugsRequirement: 1,
|
||||
GangKarmaRequirement: 1,
|
||||
|
||||
StaneksGiftPowerMultiplier: 1,
|
||||
StaneksGiftExtraSize: 0,
|
||||
};
|
||||
|
||||
@@ -160,7 +160,7 @@ export function BitverseRoot(props: IProps): React.ReactElement {
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>O | | | \| | O / _/ | / O | |/ | | | O</Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>| | | |O / | | O / | O O | | \ O| | | |</Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>| | |/ \/ / __| | |/ \ | \ | |__ \ \/ \| | |</Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| O | |_/ |\| \ O \__| \_| | O |/ </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| O | |_/ |\| \ <BitNodePortal n={13} level={nextSourceFileFlags[13]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> \__| \_| | O |/ </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | | |_/ | | \| / | \_| | | </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> \| / \| | / / \ |/ </Typography>
|
||||
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> | <BitNodePortal n={10} level={nextSourceFileFlags[10]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | | / | <BitNodePortal n={11} level={nextSourceFileFlags[11]} enter={enter} flume={props.flume} destroyedBitNode={destroyed} /> | </Typography>
|
||||
|
||||
90
src/CotMG/ActiveFragment.ts
Normal file
90
src/CotMG/ActiveFragment.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { Fragment, FragmentById } from "./Fragment";
|
||||
import { FragmentType } from "./FragmentType";
|
||||
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
|
||||
|
||||
const noCharge = [FragmentType.None, FragmentType.Delete, FragmentType.Booster];
|
||||
|
||||
export interface IActiveFragmentParams {
|
||||
x: number;
|
||||
y: number;
|
||||
fragment: Fragment;
|
||||
}
|
||||
|
||||
export class ActiveFragment {
|
||||
id: number;
|
||||
charge: number;
|
||||
x: number;
|
||||
y: number;
|
||||
|
||||
constructor(params?: IActiveFragmentParams) {
|
||||
if (params) {
|
||||
this.id = params.fragment.id;
|
||||
this.x = params.x;
|
||||
this.y = params.y;
|
||||
this.charge = 1;
|
||||
if (noCharge.includes(params.fragment.type)) this.charge = 0;
|
||||
} else {
|
||||
this.id = -1;
|
||||
this.x = -1;
|
||||
this.y = -1;
|
||||
this.charge = -1;
|
||||
}
|
||||
}
|
||||
|
||||
collide(other: ActiveFragment): boolean {
|
||||
const thisFragment = this.fragment();
|
||||
const otherFragment = other.fragment();
|
||||
// These 2 variables converts 'this' local coordinates to world to other local.
|
||||
const dx: number = other.x - this.x;
|
||||
const dy: number = other.y - this.y;
|
||||
for (let j = 0; j < thisFragment.shape.length; j++) {
|
||||
for (let i = 0; i < thisFragment.shape[j].length; i++) {
|
||||
if (thisFragment.fullAt(i, j) && otherFragment.fullAt(i - dx, j - dy)) return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
fragment(): Fragment {
|
||||
const fragment = FragmentById(this.id);
|
||||
if (fragment === null) throw new Error("ActiveFragment id refers to unknown Fragment.");
|
||||
return fragment;
|
||||
}
|
||||
|
||||
fullAt(worldX: number, worldY: number): boolean {
|
||||
return this.fragment().fullAt(worldX - this.x, worldY - this.y);
|
||||
}
|
||||
|
||||
neighboors(): number[][] {
|
||||
return this.fragment()
|
||||
.neighboors()
|
||||
.map((cell) => [this.x + cell[0], this.y + cell[1]]);
|
||||
}
|
||||
|
||||
copy(): ActiveFragment {
|
||||
// We have to do a round trip because the constructor.
|
||||
const fragment = FragmentById(this.id);
|
||||
if (fragment === null) throw new Error("ActiveFragment id refers to unknown Fragment.");
|
||||
const c = new ActiveFragment({ x: this.x, y: this.y, fragment: fragment });
|
||||
c.charge = this.charge;
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize an active fragment to a JSON save state.
|
||||
*/
|
||||
toJSON(): any {
|
||||
return Generic_toJSON("ActiveFragment", this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes an acive fragment from a JSON save state
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
static fromJSON(value: any): ActiveFragment {
|
||||
return Generic_fromJSON(ActiveFragment, value.data);
|
||||
}
|
||||
}
|
||||
|
||||
Reviver.constructors.ActiveFragment = ActiveFragment;
|
||||
329
src/CotMG/Fragment.ts
Normal file
329
src/CotMG/Fragment.ts
Normal file
@@ -0,0 +1,329 @@
|
||||
import { FragmentType } from "./FragmentType";
|
||||
|
||||
export const Fragments: Fragment[] = [];
|
||||
|
||||
export class Fragment {
|
||||
id: number;
|
||||
shape: boolean[][];
|
||||
type: FragmentType;
|
||||
power: number;
|
||||
limit: number;
|
||||
|
||||
constructor(id: number, shape: boolean[][], type: FragmentType, power: number, limit: number) {
|
||||
this.id = id;
|
||||
this.shape = shape;
|
||||
this.type = type;
|
||||
this.power = power;
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
fullAt(x: number, y: number): boolean {
|
||||
if (y < 0) return false;
|
||||
if (y >= this.shape.length) return false;
|
||||
if (x < 0) return false;
|
||||
if (x >= this.shape[y].length) return false;
|
||||
// Yes it's ordered y first.
|
||||
return this.shape[y][x];
|
||||
}
|
||||
|
||||
width(): number {
|
||||
// check every line for robustness.
|
||||
return Math.max(...this.shape.map((line) => line.length));
|
||||
}
|
||||
|
||||
height(): number {
|
||||
return this.shape.length;
|
||||
}
|
||||
|
||||
// List of direct neighboors of this fragment.
|
||||
neighboors(): number[][] {
|
||||
const candidates: number[][] = [];
|
||||
|
||||
const add = (x: number, y: number): void => {
|
||||
if (this.fullAt(x, y)) return;
|
||||
if (candidates.some((coord) => coord[0] === x && coord[1] === y)) return;
|
||||
candidates.push([x, y]);
|
||||
};
|
||||
for (let y = 0; y < this.shape.length; y++) {
|
||||
for (let x = 0; x < this.shape[y].length; x++) {
|
||||
// This cell is full, add all it's neighboors.
|
||||
if (!this.shape[y][x]) continue;
|
||||
add(x - 1, y);
|
||||
add(x + 1, y);
|
||||
add(x, y - 1);
|
||||
add(x, y + 1);
|
||||
}
|
||||
}
|
||||
const cells: number[][] = [];
|
||||
for (const candidate of candidates) {
|
||||
if (cells.some((cell) => cell[0] === candidate[0] && cell[1] === candidate[1])) continue;
|
||||
cells.push(candidate);
|
||||
}
|
||||
|
||||
return cells;
|
||||
}
|
||||
|
||||
copy(): Fragment {
|
||||
return new Fragment(
|
||||
this.id,
|
||||
this.shape.map((a) => a.slice()),
|
||||
this.type,
|
||||
this.power,
|
||||
this.limit,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function FragmentById(id: number): Fragment | null {
|
||||
for (const fragment of Fragments) {
|
||||
if (fragment.id === id) return fragment;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
(function () {
|
||||
const _ = false;
|
||||
const X = true;
|
||||
Fragments.push(
|
||||
new Fragment(
|
||||
0, // id
|
||||
[
|
||||
// shape
|
||||
[X, X, X],
|
||||
[_, _, X],
|
||||
[_, _, X],
|
||||
],
|
||||
FragmentType.Hacking, // type
|
||||
1,
|
||||
1, // limit
|
||||
),
|
||||
);
|
||||
Fragments.push(
|
||||
new Fragment(
|
||||
1, // id
|
||||
[
|
||||
// shape
|
||||
[_, X, _],
|
||||
[X, X, X],
|
||||
[_, X, _],
|
||||
],
|
||||
FragmentType.Hacking, // type
|
||||
1,
|
||||
1, // limit
|
||||
),
|
||||
);
|
||||
Fragments.push(
|
||||
new Fragment(
|
||||
5, // id
|
||||
[
|
||||
// shape
|
||||
[X, X],
|
||||
],
|
||||
FragmentType.HackingSpeed, // type
|
||||
1.3,
|
||||
1, // limit
|
||||
),
|
||||
);
|
||||
|
||||
Fragments.push(
|
||||
new Fragment(
|
||||
6, // id
|
||||
[
|
||||
[X, _],
|
||||
[X, X],
|
||||
], // shape
|
||||
FragmentType.HackingMoney, // type
|
||||
2, // power
|
||||
1, // limit
|
||||
),
|
||||
);
|
||||
Fragments.push(
|
||||
new Fragment(
|
||||
7, // id
|
||||
[
|
||||
[X, X],
|
||||
[X, X],
|
||||
], // shape
|
||||
FragmentType.HackingGrow, // type
|
||||
0.5, // power
|
||||
1, // limit
|
||||
),
|
||||
);
|
||||
Fragments.push(
|
||||
new Fragment(
|
||||
8, // id
|
||||
[
|
||||
[X, X, X],
|
||||
[_, X, _],
|
||||
[X, X, X],
|
||||
], // shape
|
||||
FragmentType.Hacking, // type
|
||||
1, // power
|
||||
1, // limit
|
||||
),
|
||||
);
|
||||
Fragments.push(
|
||||
new Fragment(
|
||||
10, // id
|
||||
[
|
||||
[X, X],
|
||||
[_, X],
|
||||
], // shape
|
||||
FragmentType.Strength, // type
|
||||
2, // power
|
||||
1, // limit
|
||||
),
|
||||
);
|
||||
Fragments.push(
|
||||
new Fragment(
|
||||
12, // id
|
||||
[
|
||||
[_, X],
|
||||
[X, X],
|
||||
], // shape
|
||||
FragmentType.Defense, // type
|
||||
2, // power
|
||||
1, // limit
|
||||
),
|
||||
);
|
||||
Fragments.push(
|
||||
new Fragment(
|
||||
14, // id
|
||||
[
|
||||
[X, X],
|
||||
[X, _],
|
||||
], // shape
|
||||
FragmentType.Dexterity, // type
|
||||
2, // power
|
||||
1, // limit
|
||||
),
|
||||
);
|
||||
Fragments.push(
|
||||
new Fragment(
|
||||
16, // id
|
||||
[
|
||||
[X, _],
|
||||
[X, X],
|
||||
], // shape
|
||||
FragmentType.Agility, // type
|
||||
2, // power
|
||||
1, // limit
|
||||
),
|
||||
);
|
||||
Fragments.push(
|
||||
new Fragment(
|
||||
18, // id
|
||||
[
|
||||
[X, X],
|
||||
[X, _],
|
||||
], // shape
|
||||
FragmentType.Charisma, // type
|
||||
3, // power
|
||||
1, // limit
|
||||
),
|
||||
);
|
||||
Fragments.push(
|
||||
new Fragment(
|
||||
20, // id
|
||||
[
|
||||
[X, _, _],
|
||||
[X, X, _],
|
||||
[X, X, X],
|
||||
], // shape
|
||||
FragmentType.HacknetMoney, // type
|
||||
1, // power
|
||||
1, // limit
|
||||
),
|
||||
);
|
||||
Fragments.push(
|
||||
new Fragment(
|
||||
21, // id
|
||||
[
|
||||
[X, X],
|
||||
[_, X],
|
||||
[_, X],
|
||||
], // shape
|
||||
FragmentType.HacknetCost, // type
|
||||
-1, // power
|
||||
1, // limit
|
||||
),
|
||||
);
|
||||
Fragments.push(
|
||||
new Fragment(
|
||||
25, // id
|
||||
[
|
||||
[X, X, X],
|
||||
[_, X, _],
|
||||
], // shape
|
||||
FragmentType.Rep, // type
|
||||
0.5, // power
|
||||
1, // limit
|
||||
),
|
||||
);
|
||||
Fragments.push(
|
||||
new Fragment(
|
||||
27, // id
|
||||
[
|
||||
[X, _],
|
||||
[_, X],
|
||||
], // shape
|
||||
FragmentType.WorkMoney, // type
|
||||
10, // power
|
||||
1, // limit
|
||||
),
|
||||
);
|
||||
Fragments.push(
|
||||
new Fragment(
|
||||
28, // id
|
||||
[[X, X]], // shape
|
||||
FragmentType.Crime, // type
|
||||
2, // power
|
||||
1, // limit
|
||||
),
|
||||
);
|
||||
Fragments.push(
|
||||
new Fragment(
|
||||
30, // id
|
||||
[
|
||||
[X, X, X],
|
||||
[X, X, X],
|
||||
[X, X, X],
|
||||
], // shape
|
||||
FragmentType.Bladeburner, // type
|
||||
1.3, // power
|
||||
1, // limit
|
||||
),
|
||||
);
|
||||
Fragments.push(
|
||||
new Fragment(
|
||||
2, // id
|
||||
[
|
||||
// shape
|
||||
[X, X, X],
|
||||
[X, _, X],
|
||||
[X, X, X],
|
||||
],
|
||||
FragmentType.Booster, // type
|
||||
1.1, // power
|
||||
3, // limit
|
||||
),
|
||||
);
|
||||
Fragments.push(
|
||||
new Fragment(
|
||||
31, // id
|
||||
[
|
||||
// shape
|
||||
[X],
|
||||
[X],
|
||||
[X],
|
||||
[X],
|
||||
],
|
||||
FragmentType.Booster, // type
|
||||
1.1, // power
|
||||
3, // limit
|
||||
),
|
||||
);
|
||||
})();
|
||||
|
||||
export const NoneFragment = new Fragment(-2, [], FragmentType.None, 0, Infinity);
|
||||
export const DeleteFragment = new Fragment(-2, [], FragmentType.Delete, 0, Infinity);
|
||||
96
src/CotMG/FragmentType.ts
Normal file
96
src/CotMG/FragmentType.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
export enum FragmentType {
|
||||
// Special fragments for the UI
|
||||
None,
|
||||
Delete,
|
||||
|
||||
// Stats boosting fragments
|
||||
HackingChance,
|
||||
HackingSpeed,
|
||||
HackingMoney,
|
||||
HackingGrow,
|
||||
Hacking,
|
||||
Strength,
|
||||
Defense,
|
||||
Dexterity,
|
||||
Agility,
|
||||
Charisma,
|
||||
HacknetMoney,
|
||||
HacknetCost,
|
||||
Rep,
|
||||
WorkMoney,
|
||||
Crime,
|
||||
Bladeburner,
|
||||
|
||||
// utility fragments.
|
||||
Booster,
|
||||
}
|
||||
|
||||
export function Effect(tpe: FragmentType): string {
|
||||
switch (tpe) {
|
||||
case FragmentType.HackingChance: {
|
||||
return "+x% hack() success chance";
|
||||
break;
|
||||
}
|
||||
case FragmentType.HackingSpeed: {
|
||||
return "+x% faster hack(), grow(), and weaken()";
|
||||
break;
|
||||
}
|
||||
case FragmentType.HackingMoney: {
|
||||
return "+x% hack() power";
|
||||
break;
|
||||
}
|
||||
case FragmentType.HackingGrow: {
|
||||
return "+x% grow() power";
|
||||
break;
|
||||
}
|
||||
case FragmentType.Hacking: {
|
||||
return "+x% hacking skill";
|
||||
break;
|
||||
}
|
||||
case FragmentType.Strength: {
|
||||
return "+x% strength skill";
|
||||
break;
|
||||
}
|
||||
case FragmentType.Defense: {
|
||||
return "+x% defense skill";
|
||||
break;
|
||||
}
|
||||
case FragmentType.Dexterity: {
|
||||
return "+x% dexterity skill";
|
||||
break;
|
||||
}
|
||||
case FragmentType.Agility: {
|
||||
return "+x% agility skill";
|
||||
break;
|
||||
}
|
||||
case FragmentType.Charisma: {
|
||||
return "+x% charisma skill";
|
||||
break;
|
||||
}
|
||||
case FragmentType.HacknetMoney: {
|
||||
return "+x% hacknet production";
|
||||
break;
|
||||
}
|
||||
case FragmentType.HacknetCost: {
|
||||
return "-x% all hacknet cost";
|
||||
break;
|
||||
}
|
||||
case FragmentType.Rep: {
|
||||
return "+x% reputation from factions and companies";
|
||||
break;
|
||||
}
|
||||
case FragmentType.WorkMoney: {
|
||||
return "+x% work money";
|
||||
break;
|
||||
}
|
||||
case FragmentType.Crime: {
|
||||
return "+x% crime money";
|
||||
break;
|
||||
}
|
||||
case FragmentType.Bladeburner: {
|
||||
return "+x% all bladeburner stats";
|
||||
break;
|
||||
}
|
||||
}
|
||||
throw new Error("Calling effect for fragment type that doesn't have an effect " + tpe);
|
||||
}
|
||||
14
src/CotMG/Helper.tsx
Normal file
14
src/CotMG/Helper.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Reviver } from "../utils/JSONReviver";
|
||||
|
||||
import { IStaneksGift } from "./IStaneksGift";
|
||||
import { StaneksGift } from "./StaneksGift";
|
||||
|
||||
export let staneksGift: IStaneksGift = new StaneksGift();
|
||||
|
||||
export function loadStaneksGift(saveString: string): void {
|
||||
if (saveString) {
|
||||
staneksGift = JSON.parse(saveString, Reviver);
|
||||
} else {
|
||||
staneksGift = new StaneksGift();
|
||||
}
|
||||
}
|
||||
22
src/CotMG/IStaneksGift.ts
Normal file
22
src/CotMG/IStaneksGift.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ActiveFragment } from "./ActiveFragment";
|
||||
import { Fragment } from "./Fragment";
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
|
||||
export interface IStaneksGift {
|
||||
storedCycles: number;
|
||||
fragments: ActiveFragment[];
|
||||
width(): number;
|
||||
height(): number;
|
||||
charge(worldX: number, worldY: number, ram: number): number;
|
||||
process(p: IPlayer, n: number): void;
|
||||
effect(fragment: ActiveFragment): number;
|
||||
canPlace(x: number, y: number, fragment: Fragment): boolean;
|
||||
place(x: number, y: number, fragment: Fragment): boolean;
|
||||
fragmentAt(worldX: number, worldY: number): ActiveFragment | null;
|
||||
deleteAt(worldX: number, worldY: number): boolean;
|
||||
clear(): void;
|
||||
count(fragment: Fragment): number;
|
||||
inBonus(): boolean;
|
||||
prestigeAugmentation(): void;
|
||||
prestigeSourceFile(): void;
|
||||
}
|
||||
222
src/CotMG/StaneksGift.ts
Normal file
222
src/CotMG/StaneksGift.ts
Normal file
@@ -0,0 +1,222 @@
|
||||
import { Fragment } from "./Fragment";
|
||||
import { ActiveFragment } from "./ActiveFragment";
|
||||
import { FragmentType } from "./FragmentType";
|
||||
import { IStaneksGift } from "./IStaneksGift";
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
import { Factions } from "../Faction/Factions";
|
||||
import { CalculateEffect } from "./formulas/effect";
|
||||
import { CalculateCharge } from "./formulas/charge";
|
||||
import { StaneksGiftEvents } from "./StaneksGiftEvents";
|
||||
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
|
||||
import { CONSTANTS } from "../Constants";
|
||||
import { StanekConstants } from "./data/Constants";
|
||||
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
|
||||
import { Player } from "../Player";
|
||||
|
||||
export class StaneksGift implements IStaneksGift {
|
||||
storedCycles = 0;
|
||||
fragments: ActiveFragment[] = [];
|
||||
|
||||
baseSize(): number {
|
||||
return StanekConstants.BaseSize + BitNodeMultipliers.StaneksGiftExtraSize + Player.sourceFileLvl(13);
|
||||
}
|
||||
|
||||
width(): number {
|
||||
return Math.floor(this.baseSize() / 2 + 1);
|
||||
}
|
||||
height(): number {
|
||||
return Math.floor(this.baseSize() / 2 + 0.6);
|
||||
}
|
||||
|
||||
charge(worldX: number, worldY: number, ram: number): number {
|
||||
const af = this.fragmentAt(worldX, worldY);
|
||||
if (af === null) return 0;
|
||||
|
||||
const charge = CalculateCharge(ram);
|
||||
af.charge += charge;
|
||||
|
||||
Factions["Church of the Machine God"].playerReputation += Math.log(ram) / Math.log(2);
|
||||
|
||||
return ram;
|
||||
}
|
||||
|
||||
inBonus(): boolean {
|
||||
return (this.storedCycles * CONSTANTS._idleSpeed) / 1000 > 1;
|
||||
}
|
||||
|
||||
process(p: IPlayer, numCycles = 1): void {
|
||||
this.storedCycles += numCycles;
|
||||
this.storedCycles -= 5;
|
||||
this.storedCycles = Math.max(0, this.storedCycles);
|
||||
this.updateMults(p);
|
||||
StaneksGiftEvents.emit();
|
||||
}
|
||||
|
||||
effect(fragment: ActiveFragment): number {
|
||||
// Find all the neighbooring cells
|
||||
const cells = fragment.neighboors();
|
||||
// find the neighbooring active fragments.
|
||||
const maybeFragments = cells.map((n) => this.fragmentAt(n[0], n[1]));
|
||||
|
||||
// Filter out nulls with typescript "Type guard". Whatever
|
||||
let neighboors = maybeFragments.filter((v: ActiveFragment | null): v is ActiveFragment => !!v);
|
||||
|
||||
neighboors = neighboors.filter((fragment) => fragment.fragment().type === FragmentType.Booster);
|
||||
let boost = 1;
|
||||
for (const neighboor of neighboors) {
|
||||
boost *= neighboor.fragment().power;
|
||||
}
|
||||
|
||||
return CalculateEffect(fragment.charge, fragment.fragment().power, boost);
|
||||
}
|
||||
|
||||
canPlace(x: number, y: number, fragment: Fragment): boolean {
|
||||
if (x + fragment.width() > this.width()) return false;
|
||||
if (y + fragment.height() > this.height()) return false;
|
||||
if (this.count(fragment) >= fragment.limit) return false;
|
||||
const newFrag = new ActiveFragment({ x: x, y: y, fragment: fragment });
|
||||
for (const aFrag of this.fragments) {
|
||||
if (aFrag.collide(newFrag)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
place(x: number, y: number, fragment: Fragment): boolean {
|
||||
if (!this.canPlace(x, y, fragment)) return false;
|
||||
this.fragments.push(new ActiveFragment({ x: x, y: y, fragment: fragment }));
|
||||
return true;
|
||||
}
|
||||
|
||||
fragmentAt(worldX: number, worldY: number): ActiveFragment | null {
|
||||
for (const aFrag of this.fragments) {
|
||||
if (aFrag.fullAt(worldX, worldY)) {
|
||||
return aFrag;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
count(fragment: Fragment): number {
|
||||
let amt = 0;
|
||||
for (const aFrag of this.fragments) {
|
||||
if (aFrag.fragment().id === fragment.id) amt++;
|
||||
}
|
||||
return amt;
|
||||
}
|
||||
|
||||
deleteAt(worldX: number, worldY: number): boolean {
|
||||
for (let i = 0; i < this.fragments.length; i++) {
|
||||
if (this.fragments[i].fullAt(worldX, worldY)) {
|
||||
this.fragments.splice(i, 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.fragments = [];
|
||||
}
|
||||
|
||||
updateMults(p: IPlayer): void {
|
||||
p.reapplyAllAugmentations(true);
|
||||
p.reapplyAllSourceFiles();
|
||||
|
||||
for (const aFrag of this.fragments) {
|
||||
const fragment = aFrag.fragment();
|
||||
|
||||
const power = this.effect(aFrag);
|
||||
switch (fragment.type) {
|
||||
case FragmentType.HackingChance:
|
||||
p.hacking_chance_mult *= power;
|
||||
break;
|
||||
case FragmentType.HackingSpeed:
|
||||
p.hacking_speed_mult *= power;
|
||||
break;
|
||||
case FragmentType.HackingMoney:
|
||||
p.hacking_money_mult *= power;
|
||||
break;
|
||||
case FragmentType.HackingGrow:
|
||||
p.hacking_grow_mult *= power;
|
||||
break;
|
||||
case FragmentType.Hacking:
|
||||
p.hacking_mult *= power;
|
||||
p.hacking_exp_mult *= power;
|
||||
break;
|
||||
case FragmentType.Strength:
|
||||
p.strength_mult *= power;
|
||||
p.strength_exp_mult *= power;
|
||||
break;
|
||||
case FragmentType.Defense:
|
||||
p.defense_mult *= power;
|
||||
p.defense_exp_mult *= power;
|
||||
break;
|
||||
case FragmentType.Dexterity:
|
||||
p.dexterity_mult *= power;
|
||||
p.dexterity_exp_mult *= power;
|
||||
break;
|
||||
case FragmentType.Agility:
|
||||
p.agility_mult *= power;
|
||||
p.agility_exp_mult *= power;
|
||||
break;
|
||||
case FragmentType.Charisma:
|
||||
p.charisma_mult *= power;
|
||||
p.charisma_exp_mult *= power;
|
||||
break;
|
||||
case FragmentType.HacknetMoney:
|
||||
p.hacknet_node_money_mult *= power;
|
||||
break;
|
||||
case FragmentType.HacknetCost:
|
||||
p.hacknet_node_purchase_cost_mult *= power;
|
||||
p.hacknet_node_ram_cost_mult *= power;
|
||||
p.hacknet_node_core_cost_mult *= power;
|
||||
p.hacknet_node_level_cost_mult *= power;
|
||||
break;
|
||||
case FragmentType.Rep:
|
||||
p.company_rep_mult *= power;
|
||||
p.faction_rep_mult *= power;
|
||||
break;
|
||||
case FragmentType.WorkMoney:
|
||||
p.work_money_mult *= power;
|
||||
break;
|
||||
case FragmentType.Crime:
|
||||
p.crime_success_mult *= power;
|
||||
p.crime_money_mult *= power;
|
||||
break;
|
||||
case FragmentType.Bladeburner:
|
||||
p.bladeburner_max_stamina_mult *= power;
|
||||
p.bladeburner_stamina_gain_mult *= power;
|
||||
p.bladeburner_analysis_mult *= power;
|
||||
p.bladeburner_success_chance_mult *= power;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prestigeAugmentation(): void {
|
||||
this.clear();
|
||||
}
|
||||
|
||||
prestigeSourceFile(): void {
|
||||
this.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize Staneks Gift to a JSON save state.
|
||||
*/
|
||||
toJSON(): any {
|
||||
return Generic_toJSON("StaneksGift", this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes Staneks Gift from a JSON save state
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
static fromJSON(value: any): StaneksGift {
|
||||
return Generic_fromJSON(StaneksGift, value.data);
|
||||
}
|
||||
}
|
||||
|
||||
Reviver.constructors.StaneksGift = StaneksGift;
|
||||
2
src/CotMG/StaneksGiftEvents.ts
Normal file
2
src/CotMG/StaneksGiftEvents.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import { EventEmitter } from "../utils/EventEmitter";
|
||||
export const StaneksGiftEvents = new EventEmitter<[]>();
|
||||
7
src/CotMG/data/Constants.ts
Normal file
7
src/CotMG/data/Constants.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export const StanekConstants: {
|
||||
RAMBonus: number;
|
||||
BaseSize: number;
|
||||
} = {
|
||||
RAMBonus: 0.1,
|
||||
BaseSize: 12,
|
||||
};
|
||||
5
src/CotMG/formulas/charge.ts
Normal file
5
src/CotMG/formulas/charge.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { StanekConstants } from "../data/Constants";
|
||||
|
||||
export function CalculateCharge(ram: number): number {
|
||||
return ram * Math.pow(1 + Math.log2(ram) * StanekConstants.RAMBonus, 0.7);
|
||||
}
|
||||
3
src/CotMG/formulas/effect.ts
Normal file
3
src/CotMG/formulas/effect.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export function CalculateEffect(charge: number, power: number, boost: number): number {
|
||||
return 1 + (Math.log(charge + 1) / (Math.log(3) * 100)) * power * boost;
|
||||
}
|
||||
4
src/CotMG/notes
Normal file
4
src/CotMG/notes
Normal file
@@ -0,0 +1,4 @@
|
||||
incentive for more threads
|
||||
boosters just multiply output, eg 20% * 1.5 = 30%
|
||||
|
||||
git remote add danielyxie git@github.com:danielyxie/bitburner.git
|
||||
40
src/CotMG/ui/Cell.tsx
Normal file
40
src/CotMG/ui/Cell.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import * as React from "react";
|
||||
|
||||
import makeStyles from "@mui/styles/makeStyles";
|
||||
import { TableCell as MuiTableCell, TableCellProps } from "@mui/material";
|
||||
|
||||
const useStyles = makeStyles({
|
||||
root: {
|
||||
border: "1px solid white",
|
||||
width: "5px",
|
||||
height: "5px",
|
||||
},
|
||||
});
|
||||
|
||||
export const TableCell: React.FC<TableCellProps> = (props: TableCellProps) => {
|
||||
return (
|
||||
<MuiTableCell
|
||||
{...props}
|
||||
classes={{
|
||||
root: useStyles().root,
|
||||
...props.classes,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type IProps = {
|
||||
onMouseEnter?: () => void;
|
||||
onClick?: () => void;
|
||||
color: string;
|
||||
};
|
||||
|
||||
export function Cell(cellProps: IProps): React.ReactElement {
|
||||
return (
|
||||
<TableCell
|
||||
style={{ backgroundColor: cellProps.color }}
|
||||
onMouseEnter={cellProps.onMouseEnter}
|
||||
onClick={cellProps.onClick}
|
||||
></TableCell>
|
||||
);
|
||||
}
|
||||
79
src/CotMG/ui/FragmentInspector.tsx
Normal file
79
src/CotMG/ui/FragmentInspector.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { ActiveFragment } from "../ActiveFragment";
|
||||
import { IStaneksGift } from "../IStaneksGift";
|
||||
import { FragmentType, Effect } from "../FragmentType";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
|
||||
import Paper from "@mui/material/Paper";
|
||||
import Typography from "@mui/material/Typography";
|
||||
|
||||
type IProps = {
|
||||
gift: IStaneksGift;
|
||||
fragment: ActiveFragment | null;
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
|
||||
export function FragmentInspector(props: IProps): React.ReactElement {
|
||||
const [, setC] = useState(new Date());
|
||||
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => setC(new Date()), 250);
|
||||
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
|
||||
if (props.fragment === null) {
|
||||
return (
|
||||
<Paper>
|
||||
<Typography>
|
||||
ID: N/A
|
||||
<br />
|
||||
Effect: N/A
|
||||
<br />
|
||||
Magnitude: N/A
|
||||
<br />
|
||||
Charge: N/A
|
||||
<br />
|
||||
Heat: N/A
|
||||
<br />
|
||||
Effect: N/A
|
||||
<br />
|
||||
[X, Y] N/A
|
||||
<br />
|
||||
[X, Y] {props.x}, {props.y}
|
||||
</Typography>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
const f = props.fragment.fragment();
|
||||
|
||||
let charge = numeralWrapper.formatStaneksGiftCharge(props.fragment.charge);
|
||||
let effect = "N/A";
|
||||
// Boosters and cooling don't deal with heat.
|
||||
if ([FragmentType.Booster, FragmentType.None, FragmentType.Delete].includes(f.type)) {
|
||||
charge = "N/A";
|
||||
effect = `${f.power}x adjacent fragment power`;
|
||||
} else {
|
||||
effect = Effect(f.type).replace("x%", numeralWrapper.formatPercentage(props.gift.effect(props.fragment) - 1));
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper>
|
||||
<Typography>
|
||||
ID: {props.fragment.id}
|
||||
<br />
|
||||
Effect: {effect}
|
||||
<br />
|
||||
Power: {numeralWrapper.formatStaneksGiftPower(f.power)}
|
||||
<br />
|
||||
Charge: {charge}
|
||||
<br />
|
||||
<br />
|
||||
root [X, Y] {props.fragment.x}, {props.fragment.y}
|
||||
<br />
|
||||
[X, Y] {props.x}, {props.y}
|
||||
</Typography>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
31
src/CotMG/ui/FragmentPreview.tsx
Normal file
31
src/CotMG/ui/FragmentPreview.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import * as React from "react";
|
||||
import { Cell } from "./Cell";
|
||||
|
||||
import TableRow from "@mui/material/TableRow";
|
||||
|
||||
import TableBody from "@mui/material/TableBody";
|
||||
import { Table } from "../../ui/React/Table";
|
||||
|
||||
type IProps = {
|
||||
width: number;
|
||||
height: number;
|
||||
colorAt: (x: number, y: number) => string;
|
||||
};
|
||||
|
||||
export function FragmentPreview(props: IProps): React.ReactElement {
|
||||
// switch the width/length to make axis consistent.
|
||||
const elems = [];
|
||||
for (let j = 0; j < props.height; j++) {
|
||||
const cells = [];
|
||||
for (let i = 0; i < props.width; i++) {
|
||||
cells.push(<Cell key={i} color={props.colorAt(i, j)} />);
|
||||
}
|
||||
elems.push(<TableRow key={j}>{cells}</TableRow>);
|
||||
}
|
||||
|
||||
return (
|
||||
<Table>
|
||||
<TableBody>{elems}</TableBody>
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
91
src/CotMG/ui/FragmentSelector.tsx
Normal file
91
src/CotMG/ui/FragmentSelector.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
import React, { useState } from "react";
|
||||
import { Fragments, Fragment, NoneFragment, DeleteFragment } from "../Fragment";
|
||||
import { FragmentType, Effect } from "../FragmentType";
|
||||
import { IStaneksGift } from "../IStaneksGift";
|
||||
import { FragmentPreview } from "./FragmentPreview";
|
||||
import { numeralWrapper } from "../../ui/numeralFormat";
|
||||
|
||||
import Select, { SelectChangeEvent } from "@mui/material/Select";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Box from "@mui/material/Box";
|
||||
|
||||
type IOptionProps = {
|
||||
gift: IStaneksGift;
|
||||
fragment: Fragment;
|
||||
selectFragment: (fragment: Fragment) => void;
|
||||
};
|
||||
|
||||
function FragmentOption(props: IOptionProps): React.ReactElement {
|
||||
const remaining =
|
||||
props.fragment.limit !== Infinity ? (
|
||||
<>{props.fragment.limit - props.gift.count(props.fragment)} remaining</>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
return (
|
||||
<Box display="flex">
|
||||
<Box sx={{ mx: 2 }}>
|
||||
<FragmentPreview
|
||||
width={props.fragment.width()}
|
||||
height={props.fragment.height()}
|
||||
colorAt={(x, y) => {
|
||||
return !props.fragment.fullAt(x, y) ? "" : props.fragment.type === FragmentType.Booster ? "blue" : "green";
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Typography>
|
||||
{props.fragment.type === FragmentType.Booster
|
||||
? `${props.fragment.power}x adjacent fragment power`
|
||||
: Effect(props.fragment.type)}
|
||||
<br />
|
||||
power: {numeralWrapper.formatStaneksGiftPower(props.fragment.power)}
|
||||
<br />
|
||||
{remaining}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
type IProps = {
|
||||
gift: IStaneksGift;
|
||||
selectFragment: (fragment: Fragment) => void;
|
||||
};
|
||||
|
||||
export function FragmentSelector(props: IProps): React.ReactElement {
|
||||
const [value, setValue] = useState<string | number>("None");
|
||||
function onChange(event: SelectChangeEvent<string | number>): void {
|
||||
const v = event.target.value;
|
||||
setValue(v);
|
||||
if (v === "None") {
|
||||
props.selectFragment(NoneFragment);
|
||||
return;
|
||||
} else if (v === "Delete") {
|
||||
props.selectFragment(DeleteFragment);
|
||||
return;
|
||||
}
|
||||
const fragment = Fragments.find((f) => f.id === v);
|
||||
if (fragment === undefined) throw new Error("Fragment selector selected an undefined fragment with id " + v);
|
||||
if (typeof v === "number") props.selectFragment(fragment);
|
||||
}
|
||||
return (
|
||||
<Select sx={{ width: "100%" }} onChange={onChange} value={value}>
|
||||
<MenuItem value="None">
|
||||
<Typography>None</Typography>
|
||||
</MenuItem>
|
||||
<MenuItem value="Delete">
|
||||
<Typography>Delete</Typography>
|
||||
</MenuItem>
|
||||
{Fragments.map((fragment) => (
|
||||
<MenuItem key={fragment.id} value={fragment.id}>
|
||||
<FragmentOption
|
||||
key={fragment.id}
|
||||
gift={props.gift}
|
||||
selectFragment={props.selectFragment}
|
||||
fragment={fragment}
|
||||
/>
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
145
src/CotMG/ui/Grid.tsx
Normal file
145
src/CotMG/ui/Grid.tsx
Normal file
@@ -0,0 +1,145 @@
|
||||
import * as React from "react";
|
||||
import { Fragment, NoneFragment } from "../Fragment";
|
||||
import { ActiveFragment } from "../ActiveFragment";
|
||||
import { FragmentType } from "../FragmentType";
|
||||
import { IStaneksGift } from "../IStaneksGift";
|
||||
import { Cell } from "./Cell";
|
||||
import { FragmentInspector } from "./FragmentInspector";
|
||||
import { FragmentSelector } from "./FragmentSelector";
|
||||
import Box from "@mui/material/Box";
|
||||
import Button from "@mui/material/Button";
|
||||
import TableRow from "@mui/material/TableRow";
|
||||
import TableBody from "@mui/material/TableBody";
|
||||
import { Table } from "../../ui/React/Table";
|
||||
|
||||
function zeros(dimensions: number[]): any {
|
||||
const array = [];
|
||||
|
||||
for (let i = 0; i < dimensions[0]; ++i) {
|
||||
array.push(dimensions.length == 1 ? 0 : zeros(dimensions.slice(1)));
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
function randomColor(fragment: ActiveFragment): string {
|
||||
// Can't set Math.random seed so copy casino. TODO refactor both RNG later.
|
||||
let s1 = Math.pow((fragment.x + 1) * (fragment.y + 1), 10);
|
||||
let s2 = s1;
|
||||
let s3 = s1;
|
||||
|
||||
const colors = [];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
s1 = (171 * s1) % 30269;
|
||||
s2 = (172 * s2) % 30307;
|
||||
s3 = (170 * s3) % 30323;
|
||||
colors.push((s1 / 30269.0 + s2 / 30307.0 + s3 / 30323.0) % 1.0);
|
||||
}
|
||||
|
||||
return `rgb(${colors[0] * 256}, ${colors[1] * 256}, ${colors[2] * 256})`;
|
||||
}
|
||||
|
||||
type GridProps = {
|
||||
gift: IStaneksGift;
|
||||
};
|
||||
|
||||
export function Grid(props: GridProps): React.ReactElement {
|
||||
function calculateGrid(gift: IStaneksGift): any {
|
||||
const newgrid = zeros([gift.width(), gift.height()]);
|
||||
for (let i = 0; i < gift.width(); i++) {
|
||||
for (let j = 0; j < gift.height(); j++) {
|
||||
const fragment = gift.fragmentAt(i, j);
|
||||
if (fragment === null) continue;
|
||||
newgrid[i][j] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return newgrid;
|
||||
}
|
||||
|
||||
const [grid, setGrid] = React.useState(calculateGrid(props.gift));
|
||||
const [ghostGrid, setGhostGrid] = React.useState(zeros([props.gift.width(), props.gift.height()]));
|
||||
const [pos, setPos] = React.useState([0, 0]);
|
||||
const [selectedFragment, setSelectedFragment] = React.useState(NoneFragment);
|
||||
|
||||
function moveGhost(worldX: number, worldY: number): void {
|
||||
const newgrid = zeros([props.gift.width(), props.gift.height()]);
|
||||
for (let i = 0; i < selectedFragment.shape.length; i++) {
|
||||
for (let j = 0; j < selectedFragment.shape[i].length; j++) {
|
||||
if (!selectedFragment.shape[i][j]) continue;
|
||||
if (worldX + j > newgrid.length - 1) continue;
|
||||
if (worldY + i > newgrid[worldX + j].length - 1) continue;
|
||||
newgrid[worldX + j][worldY + i] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
setGhostGrid(newgrid);
|
||||
setPos([worldX, worldY]);
|
||||
}
|
||||
|
||||
function deleteAt(worldX: number, worldY: number): boolean {
|
||||
return props.gift.deleteAt(worldX, worldY);
|
||||
}
|
||||
|
||||
function clickAt(worldX: number, worldY: number): void {
|
||||
if (selectedFragment.type == FragmentType.None) return;
|
||||
if (selectedFragment.type == FragmentType.Delete) {
|
||||
deleteAt(worldX, worldY);
|
||||
} else {
|
||||
if (!props.gift.canPlace(worldX, worldY, selectedFragment)) return;
|
||||
props.gift.place(worldX, worldY, selectedFragment);
|
||||
}
|
||||
setGrid(calculateGrid(props.gift));
|
||||
}
|
||||
|
||||
function color(worldX: number, worldY: number): string {
|
||||
if (ghostGrid[worldX][worldY] && grid[worldX][worldY]) return "red";
|
||||
if (ghostGrid[worldX][worldY]) return "white";
|
||||
if (grid[worldX][worldY]) {
|
||||
const fragment = props.gift.fragmentAt(worldX, worldY);
|
||||
if (fragment === null) throw new Error("ActiveFragment should not be null");
|
||||
return randomColor(fragment);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function clear(): void {
|
||||
props.gift.clear();
|
||||
setGrid(zeros([props.gift.width(), props.gift.height()]));
|
||||
}
|
||||
|
||||
// switch the width/length to make axis consistent.
|
||||
const elems = [];
|
||||
for (let j = 0; j < props.gift.height(); j++) {
|
||||
const cells = [];
|
||||
for (let i = 0; i < props.gift.width(); i++) {
|
||||
cells.push(
|
||||
<Cell key={i} onMouseEnter={() => moveGhost(i, j)} onClick={() => clickAt(i, j)} color={color(i, j)} />,
|
||||
);
|
||||
}
|
||||
elems.push(
|
||||
<TableRow key={j} className="staneksgift_row">
|
||||
{cells}
|
||||
</TableRow>,
|
||||
);
|
||||
}
|
||||
|
||||
function updateSelectedFragment(fragment: Fragment): void {
|
||||
setSelectedFragment(fragment);
|
||||
const newgrid = zeros([props.gift.width(), props.gift.height()]);
|
||||
setGhostGrid(newgrid);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button onClick={clear}>Clear</Button>
|
||||
<Box display="flex">
|
||||
<Table>
|
||||
<TableBody>{elems}</TableBody>
|
||||
</Table>
|
||||
<FragmentInspector gift={props.gift} x={pos[0]} y={pos[1]} fragment={props.gift.fragmentAt(pos[0], pos[1])} />
|
||||
</Box>
|
||||
<FragmentSelector gift={props.gift} selectFragment={updateSelectedFragment} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
36
src/CotMG/ui/StaneksGiftRoot.tsx
Normal file
36
src/CotMG/ui/StaneksGiftRoot.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
|
||||
import { CONSTANTS } from "../../Constants";
|
||||
import { StaneksGiftEvents } from "../StaneksGiftEvents";
|
||||
import { Grid } from "./Grid";
|
||||
import { IStaneksGift } from "../IStaneksGift";
|
||||
import Typography from "@mui/material/Typography";
|
||||
|
||||
type IProps = {
|
||||
staneksGift: IStaneksGift;
|
||||
};
|
||||
|
||||
export function StaneksGiftRoot({ staneksGift }: IProps): React.ReactElement {
|
||||
const setRerender = useState(true)[1];
|
||||
function rerender(): void {
|
||||
setRerender((o) => !o);
|
||||
}
|
||||
useEffect(() => StaneksGiftEvents.subscribe(rerender), []);
|
||||
return (
|
||||
<>
|
||||
<Typography variant="h4">Stanek's Gift</Typography>
|
||||
<Typography>
|
||||
The gift is a grid on which you can place upgrades called fragments. The main type of fragment increases a stat,
|
||||
like your hacking skill or agility exp. Once a stat fragment is placed it then needs to be charged via scripts
|
||||
in order to become useful. The other kind of fragment is called booster fragments. They increase the efficiency
|
||||
of the charged happening on fragments neighboring them (no diagonal)
|
||||
</Typography>
|
||||
{staneksGift.storedCycles > 5 && (
|
||||
<Typography>
|
||||
Bonus time: {convertTimeMsToTimeElapsedString(CONSTANTS._idleSpeed * staneksGift.storedCycles)}
|
||||
</Typography>
|
||||
)}
|
||||
<Grid gift={staneksGift} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { IPlayer } from "./PersonObjects/IPlayer";
|
||||
import { Bladeburner } from "./Bladeburner/Bladeburner";
|
||||
import { IEngine } from "./IEngine";
|
||||
import { IRouter } from "./ui/Router";
|
||||
import { AugmentationNames } from "./Augmentation/data/AugmentationNames";
|
||||
|
||||
import React from "react";
|
||||
|
||||
@@ -19,6 +20,7 @@ import { Corporation } from "./DevMenu/ui/Corporation";
|
||||
import { CodingContracts } from "./DevMenu/ui/CodingContracts";
|
||||
import { StockMarket } from "./DevMenu/ui/StockMarket";
|
||||
import { Sleeves } from "./DevMenu/ui/Sleeves";
|
||||
import { Stanek } from "./DevMenu/ui/Stanek";
|
||||
import { TimeSkip } from "./DevMenu/ui/TimeSkip";
|
||||
import Typography from "@mui/material/Typography";
|
||||
|
||||
@@ -52,6 +54,7 @@ export function DevMenuRoot(props: IProps): React.ReactElement {
|
||||
{props.player.hasWseAccount && <StockMarket />}
|
||||
|
||||
{props.player.sleeves.length > 0 && <Sleeves player={props.player} />}
|
||||
{props.player.augmentations.some((aug) => aug.name === AugmentationNames.StaneksGift1) && <Stanek />}
|
||||
|
||||
<TimeSkip player={props.player} engine={props.engine} />
|
||||
</>
|
||||
|
||||
79
src/DevMenu/ui/Stanek.tsx
Normal file
79
src/DevMenu/ui/Stanek.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import React from "react";
|
||||
|
||||
import { staneksGift } from "../../CotMG/Helper";
|
||||
|
||||
import Accordion from "@mui/material/Accordion";
|
||||
import AccordionSummary from "@mui/material/AccordionSummary";
|
||||
import AccordionDetails from "@mui/material/AccordionDetails";
|
||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { Adjuster } from "./Adjuster";
|
||||
|
||||
export function Stanek(): React.ReactElement {
|
||||
function addCycles(): void {
|
||||
staneksGift.storedCycles = 1e6;
|
||||
}
|
||||
|
||||
function modCycles(modify: number): (x: number) => void {
|
||||
return function (cycles: number): void {
|
||||
staneksGift.storedCycles += cycles * modify;
|
||||
};
|
||||
}
|
||||
|
||||
function resetCycles(): void {
|
||||
staneksGift.storedCycles = 0;
|
||||
}
|
||||
|
||||
function addCharge(): void {
|
||||
staneksGift.fragments.forEach((f) => (f.charge = 1e21));
|
||||
}
|
||||
|
||||
function modCharge(modify: number): (x: number) => void {
|
||||
return function (cycles: number): void {
|
||||
staneksGift.fragments.forEach((f) => (f.charge += cycles * modify));
|
||||
};
|
||||
}
|
||||
|
||||
function resetCharge(): void {
|
||||
staneksGift.fragments.forEach((f) => (f.charge = 0));
|
||||
}
|
||||
|
||||
return (
|
||||
<Accordion TransitionProps={{ unmountOnExit: true }}>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<Typography>Stanek's Gift</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<Adjuster
|
||||
label="cycles"
|
||||
placeholder="amt"
|
||||
tons={addCycles}
|
||||
add={modCycles(1)}
|
||||
subtract={modCycles(-1)}
|
||||
reset={resetCycles}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Adjuster
|
||||
label="all charge"
|
||||
placeholder="amt"
|
||||
tons={addCharge}
|
||||
add={modCharge(1)}
|
||||
subtract={modCharge(-1)}
|
||||
reset={resetCharge}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
||||
@@ -526,4 +526,47 @@ export const FactionInfos: IMap<FactionInfo> = {
|
||||
true,
|
||||
false,
|
||||
),
|
||||
|
||||
// prettier-ignore
|
||||
"Church of the Machine God": new FactionInfo(<>
|
||||
{" `` "}<br />
|
||||
{" -odmmNmds: "}<br />
|
||||
{" `hNmo:..-omNh. "}<br />
|
||||
{" yMd` `hNh "}<br />
|
||||
{" mMd oNm "}<br />
|
||||
{" oMNo .mM/ "}<br />
|
||||
{" `dMN+ -mM+ "}<br />
|
||||
{" -mMNo -mN+ "}<br />
|
||||
{" .+- :mMNo/mN/ "}<br />
|
||||
{":yNMd. :NMNNN/ "}<br />
|
||||
{"-mMMMh. /NMMh` "}<br />
|
||||
{" .dMMMd. /NMMMy` "}<br />
|
||||
{" `yMMMd. /NNyNMMh` "}<br />
|
||||
{" `sMMMd. +Nm: +NMMh. "}<br />
|
||||
{" oMMMm- oNm: /NMMd. "}<br />
|
||||
{" +NMMmsMm- :mMMd. "}<br />
|
||||
{" /NMMMm- -mMMd. "}<br />
|
||||
{" /MMMm- -mMMd. "}<br />
|
||||
{" `sMNMMm- .mMmo "}<br />
|
||||
{" `sMd:hMMm. ./. "}<br />
|
||||
{" `yMy` `yNMd` "}<br />
|
||||
{" `hMs` oMMy "}<br />
|
||||
{" `hMh sMN- "}<br />
|
||||
{" /MM- .NMo "}<br />
|
||||
{" +MM: :MM+ "}<br />
|
||||
{" sNNo-.`.-omNy` "}<br />
|
||||
{" -smNNNNmdo- "}<br />
|
||||
{" `..` "}<br /><br />
|
||||
Many cultures predict an end to humanity in the near future, a final
|
||||
Armageddon that will end the world; but we disagree.
|
||||
<br /><br />Note that for this faction, reputation can
|
||||
only be gained by charging Stanek's gift.</>,
|
||||
[],
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
),
|
||||
};
|
||||
|
||||
@@ -85,7 +85,7 @@ Cities[CityName.Chongqing].asciiArt = `
|
||||
\\ o 78 [kuaigong international]
|
||||
\\ /
|
||||
38 o----x--x------x------A------G--
|
||||
/ 39 | 41 [church]
|
||||
/ 39 | 41 [church]
|
||||
37 o + 79 o--x--x-C-0
|
||||
/ | /
|
||||
/ x-----+-----x-----0 [hospital]
|
||||
|
||||
@@ -17,6 +17,9 @@ import Button from "@mui/material/Button";
|
||||
import { Location } from "../Location";
|
||||
import { CreateCorporationModal } from "../../Corporation/ui/CreateCorporationModal";
|
||||
import { LocationName } from "../data/LocationNames";
|
||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||
import { Factions } from "../../Faction/Factions";
|
||||
import { joinFaction } from "../../Faction/FactionHelpers";
|
||||
|
||||
import { use } from "../../ui/Context";
|
||||
|
||||
@@ -109,6 +112,21 @@ export function SpecialLocation(props: IProps): React.ReactElement {
|
||||
return <Button onClick={handleResleeving}>Re-Sleeve</Button>;
|
||||
}
|
||||
|
||||
function handleCotMG(): void {
|
||||
const faction = Factions["Church of the Machine God"];
|
||||
if (!player.factions.includes("Church of the Machine God")) {
|
||||
joinFaction(faction);
|
||||
}
|
||||
if (
|
||||
!player.augmentations.some((a) => a.name === AugmentationNames.StaneksGift1) &&
|
||||
!player.queuedAugmentations.some((a) => a.name === AugmentationNames.StaneksGift1)
|
||||
) {
|
||||
player.queueAugmentation(AugmentationNames.StaneksGift1);
|
||||
}
|
||||
|
||||
router.toFaction(faction);
|
||||
}
|
||||
|
||||
function renderCotMG(): React.ReactElement {
|
||||
// prettier-ignore
|
||||
const symbol = <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>
|
||||
@@ -140,15 +158,56 @@ export function SpecialLocation(props: IProps): React.ReactElement {
|
||||
{" sNNo-.`.-omNy` "}<br />
|
||||
{" -smNNNNmdo- "}<br />
|
||||
{" `..` "}</Typography>
|
||||
if (player.factions.includes("Church of the Machine God")) {
|
||||
return (
|
||||
<>
|
||||
<Typography>
|
||||
<i>Allison "Mother" Stanek: Welcome back my child!</i>
|
||||
</Typography>
|
||||
{symbol}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (!player.canAccessCotMG()) {
|
||||
return (
|
||||
<>
|
||||
<Typography>
|
||||
A decrepit altar stands in the middle of a dilapidated church.
|
||||
<br />
|
||||
<br />A symbol is carved in the altar.
|
||||
</Typography>
|
||||
<br />
|
||||
{symbol}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
player.augmentations.filter((a) => a.name !== AugmentationNames.NeuroFluxGovernor).length > 0 ||
|
||||
player.queuedAugmentations.filter((a) => a.name !== AugmentationNames.NeuroFluxGovernor).length > 0
|
||||
) {
|
||||
return (
|
||||
<>
|
||||
<Typography>
|
||||
<i>
|
||||
Allison "Mother" Stanek: Begone you filth! My gift must be the first modification that your body should
|
||||
have!
|
||||
</i>
|
||||
</Typography>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography>
|
||||
A decrepit altar stands in the middle of a dilapidated church.
|
||||
<br />
|
||||
<br />A symbol is carved in the altar.
|
||||
<i>
|
||||
Allison "Mother" Stanek: Welcome child, I see your body is pure. Are you ready to ascend beyond our human
|
||||
form? If you are, accept my gift.
|
||||
</i>
|
||||
</Typography>
|
||||
<br />
|
||||
<Button onClick={handleCotMG}>Accept Stanek's Gift</Button>
|
||||
{symbol}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -54,6 +54,15 @@ export const RamCostConstants: IMap<number> = {
|
||||
ScriptGangApiBaseRamCost: 4,
|
||||
|
||||
ScriptBladeburnerApiBaseRamCost: 4,
|
||||
|
||||
ScriptStanekCharge: 0.4,
|
||||
ScriptStanekFragmentDefinitions: 0,
|
||||
ScriptStanekPlacedFragments: 5,
|
||||
ScriptStanekClear: 0,
|
||||
ScriptStanekCanPlace: 0.5,
|
||||
ScriptStanekPlace: 5,
|
||||
ScriptStanekFragmentAt: 2,
|
||||
ScriptStanekDeleteAt: 0.15,
|
||||
};
|
||||
|
||||
export const RamCosts: IMap<any> = {
|
||||
@@ -313,6 +322,17 @@ export const RamCosts: IMap<any> = {
|
||||
purchaseSleeveAug: () => RamCostConstants.ScriptSleeveBaseRamCost,
|
||||
},
|
||||
|
||||
stanek: {
|
||||
charge: () => RamCostConstants.ScriptStanekCharge,
|
||||
fragmentDefinitions: () => RamCostConstants.ScriptStanekFragmentDefinitions,
|
||||
placedFragments: () => RamCostConstants.ScriptStanekPlacedFragments,
|
||||
clear: () => RamCostConstants.ScriptStanekClear,
|
||||
canPlace: () => RamCostConstants.ScriptStanekCanPlace,
|
||||
place: () => RamCostConstants.ScriptStanekPlace,
|
||||
fragmentAt: () => RamCostConstants.ScriptStanekFragmentAt,
|
||||
deleteAt: () => RamCostConstants.ScriptStanekDeleteAt,
|
||||
},
|
||||
|
||||
heart: {
|
||||
// Easter egg function
|
||||
break: () => 0,
|
||||
|
||||
@@ -146,6 +146,7 @@ import { INetscriptGang, NetscriptGang } from "./NetscriptFunctions/Gang";
|
||||
import { INetscriptSleeve, NetscriptSleeve } from "./NetscriptFunctions/Sleeve";
|
||||
import { INetscriptExtra, NetscriptExtra } from "./NetscriptFunctions/Extra";
|
||||
import { INetscriptHacknet, NetscriptHacknet } from "./NetscriptFunctions/Hacknet";
|
||||
import { INetscriptStanek, NetscriptStanek } from "./NetscriptFunctions/Stanek";
|
||||
|
||||
const defaultInterpreter = new Interpreter("", () => undefined);
|
||||
|
||||
@@ -187,6 +188,7 @@ interface NS extends INetscriptExtra {
|
||||
hacknet: INetscriptHacknet;
|
||||
gang: INetscriptGang;
|
||||
sleeve: INetscriptSleeve;
|
||||
stanek: INetscriptStanek;
|
||||
}
|
||||
|
||||
function NetscriptFunctions(workerScript: WorkerScript): NS {
|
||||
@@ -477,6 +479,15 @@ function NetscriptFunctions(workerScript: WorkerScript): NS {
|
||||
return contract;
|
||||
};
|
||||
|
||||
const checkStanekAPIAccess = function (func: string): void {
|
||||
if (Player.bitNodeN !== 13 && !SourceFileFlags[13]) {
|
||||
throw makeRuntimeErrorMsg(
|
||||
`stanek.${func}`,
|
||||
"You do not currently have access to the Stanek API. This is either because you are not in BitNode-13 or because you do not have Source-File 13",
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const getBladeburnerActionObject = function (func: any, type: any, name: any): any {
|
||||
const bladeburner = Player.bladeburner;
|
||||
if (bladeburner === null) throw new Error("Must have joined bladeburner");
|
||||
@@ -717,6 +728,7 @@ function NetscriptFunctions(workerScript: WorkerScript): NS {
|
||||
const sleeve = NetscriptSleeve(Player, workerScript, helper);
|
||||
const extra = NetscriptExtra(Player, workerScript);
|
||||
const hacknet = NetscriptHacknet(Player, workerScript, helper);
|
||||
const stanek = NetscriptStanek(Player, workerScript, helper);
|
||||
|
||||
const functions = {
|
||||
hacknet: hacknet,
|
||||
@@ -4469,7 +4481,11 @@ function NetscriptFunctions(workerScript: WorkerScript): NS {
|
||||
},
|
||||
}, // End coding contracts
|
||||
|
||||
// Duplicate Sleeve API
|
||||
sleeve: sleeve,
|
||||
|
||||
stanek: stanek,
|
||||
|
||||
formulas: {
|
||||
basic: {
|
||||
calculateSkill: function (exp: any, mult: any = 1): any {
|
||||
|
||||
85
src/NetscriptFunctions/Stanek.ts
Normal file
85
src/NetscriptFunctions/Stanek.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { INetscriptHelper } from "./INetscriptHelper";
|
||||
import { IPlayer } from "../PersonObjects/IPlayer";
|
||||
import { WorkerScript } from "../Netscript/WorkerScript";
|
||||
import { netscriptDelay } from "../NetscriptEvaluator";
|
||||
import { getRamCost } from "../Netscript/RamCostGenerator";
|
||||
|
||||
import { staneksGift } from "../CotMG/Helper";
|
||||
import { Fragments, FragmentById } from "../CotMG/Fragment";
|
||||
|
||||
export interface INetscriptStanek {
|
||||
charge(worldX: number, worldY: number): any;
|
||||
fragmentDefinitions(): any;
|
||||
placedFragments(): any;
|
||||
clear(): void;
|
||||
canPlace(worldX: number, worldY: number, fragmentId: number): boolean;
|
||||
place(worldX: number, worldY: number, fragmentId: number): boolean;
|
||||
fragmentAt(worldX: number, worldY: number): any;
|
||||
deleteAt(worldX: number, worldY: number): boolean;
|
||||
}
|
||||
|
||||
export function NetscriptStanek(
|
||||
player: IPlayer,
|
||||
workerScript: WorkerScript,
|
||||
helper: INetscriptHelper,
|
||||
): INetscriptStanek {
|
||||
return {
|
||||
charge: function (worldX: any, worldY: any): any {
|
||||
helper.updateDynamicRam("charge", getRamCost("stanek", "charge"));
|
||||
//checkStanekAPIAccess("charge");
|
||||
const fragment = staneksGift.fragmentAt(worldX, worldY);
|
||||
if (!fragment) throw helper.makeRuntimeErrorMsg("stanek.charge", `No fragment at (${worldX}, ${worldY})`);
|
||||
const time = staneksGift.inBonus() ? 200 : 1000;
|
||||
return netscriptDelay(time, workerScript).then(function () {
|
||||
if (workerScript.env.stopFlag) {
|
||||
return Promise.reject(workerScript);
|
||||
}
|
||||
const ram = workerScript.scriptRef.ramUsage * workerScript.scriptRef.threads;
|
||||
return Promise.resolve(staneksGift.charge(worldX, worldY, ram));
|
||||
});
|
||||
},
|
||||
fragmentDefinitions: function () {
|
||||
helper.updateDynamicRam("fragmentDefinitions", getRamCost("stanek", "fragmentDefinitions"));
|
||||
//checkStanekAPIAccess("fragmentDefinitions");
|
||||
return Fragments.map((f) => f.copy());
|
||||
},
|
||||
placedFragments: function () {
|
||||
helper.updateDynamicRam("placedFragments", getRamCost("stanek", "placedFragments"));
|
||||
//checkStanekAPIAccess("placedFragments");
|
||||
return staneksGift.fragments.map((af) => {
|
||||
return { ...af.copy(), ...af.fragment().copy() };
|
||||
});
|
||||
},
|
||||
clear: function () {
|
||||
helper.updateDynamicRam("clear", getRamCost("stanek", "clear"));
|
||||
//checkStanekAPIAccess("clear");
|
||||
staneksGift.clear();
|
||||
},
|
||||
canPlace: function (worldX: any, worldY: any, fragmentId: any): any {
|
||||
helper.updateDynamicRam("canPlace", getRamCost("stanek", "canPlace"));
|
||||
//checkStanekAPIAccess("canPlace");
|
||||
const fragment = FragmentById(fragmentId);
|
||||
if (!fragment) throw helper.makeRuntimeErrorMsg("stanek.canPlace", `Invalid fragment id: ${fragmentId}`);
|
||||
return staneksGift.canPlace(worldX, worldY, fragment);
|
||||
},
|
||||
place: function (worldX: any, worldY: any, fragmentId: any): any {
|
||||
helper.updateDynamicRam("place", getRamCost("stanek", "place"));
|
||||
//checkStanekAPIAccess("place");
|
||||
const fragment = FragmentById(fragmentId);
|
||||
if (!fragment) throw helper.makeRuntimeErrorMsg("stanek.place", `Invalid fragment id: ${fragmentId}`);
|
||||
return staneksGift.place(worldX, worldY, fragment);
|
||||
},
|
||||
fragmentAt: function (worldX: any, worldY: any): any {
|
||||
helper.updateDynamicRam("fragmentAt", getRamCost("stanek", "fragmentAt"));
|
||||
//checkStanekAPIAccess("fragmentAt");
|
||||
const fragment = staneksGift.fragmentAt(worldX, worldY);
|
||||
if (fragment !== null) return fragment.copy();
|
||||
return null;
|
||||
},
|
||||
deleteAt: function (worldX: any, worldY: any): any {
|
||||
helper.updateDynamicRam("deleteAt", getRamCost("stanek", "deleteAt"));
|
||||
//checkStanekAPIAccess("deleteAt");
|
||||
return staneksGift.deleteAt(worldX, worldY);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -113,6 +113,7 @@ function startNetscript2Script(workerScript: WorkerScript): Promise<WorkerScript
|
||||
if (typeof workerScript.env.vars[prop] !== "function") continue;
|
||||
workerScript.env.vars[prop] = wrap(prop, workerScript.env.vars[prop]);
|
||||
}
|
||||
workerScript.env.vars.stanek.charge = wrap("stanek.prop", workerScript.env.vars.stanek.charge);
|
||||
|
||||
// Note: the environment that we pass to the JS script only needs to contain the functions visible
|
||||
// to that script, which env.vars does at this point.
|
||||
|
||||
@@ -275,4 +275,7 @@ export interface IPlayer {
|
||||
setBitNodeNumber(n: number): void;
|
||||
getMult(name: string): number;
|
||||
setMult(name: string, mult: number): void;
|
||||
|
||||
canAccessCotMG(): boolean;
|
||||
sourceFileLvl(n: number): number;
|
||||
}
|
||||
|
||||
@@ -282,6 +282,8 @@ export class PlayerObject implements IPlayer {
|
||||
setBitNodeNumber: (n: number) => void;
|
||||
getMult: (name: string) => number;
|
||||
setMult: (name: string, mult: number) => void;
|
||||
canAccessCotMG: () => boolean;
|
||||
sourceFileLvl: (n: number) => number;
|
||||
|
||||
constructor() {
|
||||
//Skills and stats
|
||||
@@ -574,6 +576,9 @@ export class PlayerObject implements IPlayer {
|
||||
|
||||
this.getMult = generalMethods.getMult;
|
||||
this.setMult = generalMethods.setMult;
|
||||
|
||||
this.canAccessCotMG = generalMethods.canAccessCotMG;
|
||||
this.sourceFileLvl = generalMethods.sourceFileLvl;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2655,3 +2655,13 @@ export function setMult(this: IPlayer, name: string, mult: number): void {
|
||||
if (!this.hasOwnProperty(name)) return;
|
||||
(this as any)[name] = mult;
|
||||
}
|
||||
|
||||
export function canAccessCotMG(this: IPlayer): boolean {
|
||||
return this.bitNodeN === 13 || SourceFileFlags[13] > 0;
|
||||
}
|
||||
|
||||
export function sourceFileLvl(this: IPlayer, n: number): number {
|
||||
const sf = this.sourceFiles.find((sf) => sf.n === n);
|
||||
if (!sf) return 0;
|
||||
return sf.lvl;
|
||||
}
|
||||
|
||||
@@ -137,6 +137,12 @@ function prestigeAugmentation(): void {
|
||||
}
|
||||
}
|
||||
|
||||
if (augmentationExists(AugmentationNames.StaneksGift1) && Augmentations[AugmentationNames.StaneksGift1].owned) {
|
||||
// TODO(hydroflame): refactor faction names so we don't have to hard
|
||||
// code strings.
|
||||
joinFaction(Factions["Church of the Machine God"]);
|
||||
}
|
||||
|
||||
resetPidCounter();
|
||||
}
|
||||
|
||||
@@ -235,6 +241,10 @@ function prestigeSourceFile(flume: boolean): void {
|
||||
dialogBoxCreate("Visit VitaLife in New Tokyo if you'd like to purchase a new sleeve!");
|
||||
}
|
||||
|
||||
if (Player.bitNodeN === 13) {
|
||||
dialogBoxCreate("Trouble is brewing in Chongqing");
|
||||
}
|
||||
|
||||
// Reset Stock market, gang, and corporation
|
||||
if (Player.hasWseAccount) {
|
||||
initStockMarket();
|
||||
|
||||
@@ -9,6 +9,7 @@ import { saveAllServers, loadAllServers } from "./Server/AllServers";
|
||||
import { Settings } from "./Settings/Settings";
|
||||
import { SourceFileFlags } from "./SourceFile/SourceFileFlags";
|
||||
import { loadStockMarket, StockMarket } from "./StockMarket/StockMarket";
|
||||
import { staneksGift, loadStaneksGift } from "./CotMG/Helper";
|
||||
|
||||
import { GameSavedEvents } from "./ui/React/Snackbar";
|
||||
|
||||
@@ -35,6 +36,7 @@ class BitburnerSaveObject {
|
||||
VersionSave = "";
|
||||
AllGangsSave = "";
|
||||
LastExportBonus = "";
|
||||
StaneksGiftSave = "";
|
||||
|
||||
getSaveString(): string {
|
||||
this.PlayerSave = JSON.stringify(Player);
|
||||
@@ -49,6 +51,7 @@ class BitburnerSaveObject {
|
||||
this.SettingsSave = JSON.stringify(Settings);
|
||||
this.VersionSave = JSON.stringify(CONSTANTS.Version);
|
||||
this.LastExportBonus = JSON.stringify(ExportBonus.LastExportBonus);
|
||||
this.StaneksGiftSave = JSON.stringify(staneksGift);
|
||||
if (Player.inGang()) {
|
||||
this.AllGangsSave = JSON.stringify(AllGangs);
|
||||
}
|
||||
@@ -166,6 +169,12 @@ function loadGame(saveString: string): boolean {
|
||||
loadCompanies(saveObj.CompaniesSave);
|
||||
loadFactions(saveObj.FactionsSave);
|
||||
|
||||
if (saveObj.hasOwnProperty("StaneksGiftSave")) {
|
||||
loadStaneksGift(saveObj.StaneksGiftSave);
|
||||
} else {
|
||||
console.warn(`Could not load Staneks Gift from save`);
|
||||
loadStaneksGift("");
|
||||
}
|
||||
if (saveObj.hasOwnProperty("AliasesSave")) {
|
||||
try {
|
||||
loadAliases(saveObj.AliasesSave);
|
||||
|
||||
@@ -197,6 +197,8 @@ async function parseOnlyRamCalculate(
|
||||
func = workerScript.env.vars.bladeburner[ref];
|
||||
} else if (ref in workerScript.env.vars.codingcontract) {
|
||||
func = workerScript.env.vars.codingcontract[ref];
|
||||
} else if (ref in workerScript.env.vars.stanek) {
|
||||
func = workerScript.env.vars.stanek[ref];
|
||||
} else if (ref in workerScript.env.vars.gang) {
|
||||
func = workerScript.env.vars.gang[ref];
|
||||
} else if (ref in workerScript.env.vars.sleeve) {
|
||||
|
||||
@@ -49,6 +49,7 @@ import { iTutorialSteps, iTutorialNextStep, ITutorial } from "../../InteractiveT
|
||||
import { getAvailableCreatePrograms } from "../../Programs/ProgramHelpers";
|
||||
import { Settings } from "../../Settings/Settings";
|
||||
import { redPillFlag } from "../../RedPill";
|
||||
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
|
||||
|
||||
import { KEY } from "../../utils/helpers/keyCodes";
|
||||
|
||||
@@ -155,13 +156,12 @@ export function SidebarRoot(props: IProps): React.ReactElement {
|
||||
|
||||
const canOpenSleeves = props.player.sleeves.length > 0;
|
||||
|
||||
// TODO(hydroflame): these should not as any but right now the def is that it
|
||||
// can only be defined;
|
||||
const canCorporation = !!(props.player.corporation as any);
|
||||
const canGang = !!(props.player.gang as any);
|
||||
const canJob = props.player.companyName !== "";
|
||||
const canStockMarket = props.player.hasWseAccount;
|
||||
const canBladeburner = !!(props.player.bladeburner as any);
|
||||
const canStaneksGift = props.player.augmentations.some((aug) => aug.name === AugmentationNames.StaneksGift1);
|
||||
|
||||
function clickTerminal(): void {
|
||||
props.router.toTerminal();
|
||||
@@ -186,6 +186,10 @@ export function SidebarRoot(props: IProps): React.ReactElement {
|
||||
props.router.toCreateProgram();
|
||||
}
|
||||
|
||||
function clickStaneksGift(): void {
|
||||
props.router.toStaneksGift();
|
||||
}
|
||||
|
||||
function clickFactions(): void {
|
||||
props.router.toFactions();
|
||||
}
|
||||
@@ -425,6 +429,25 @@ export function SidebarRoot(props: IProps): React.ReactElement {
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
)}
|
||||
{canStaneksGift && (
|
||||
<ListItem
|
||||
button
|
||||
key={"Staneks Gift"}
|
||||
className={clsx({
|
||||
[classes.active]: props.page === Page.StaneksGift,
|
||||
})}
|
||||
onClick={clickStaneksGift}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<DeveloperBoardIcon color={props.page !== Page.StaneksGift ? "secondary" : "primary"} />
|
||||
</ListItemIcon>
|
||||
<ListItemText>
|
||||
<Typography color={props.page !== Page.StaneksGift ? "secondary" : "primary"}>
|
||||
Stanek's Gift
|
||||
</Typography>
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
)}
|
||||
</List>
|
||||
</Collapse>
|
||||
|
||||
|
||||
@@ -204,3 +204,7 @@ SourceFiles["SourceFile12"] = new SourceFile(
|
||||
12,
|
||||
<>This Source-File lets the player start with Neuroflux Governor equal to the level of this Source-File.</>,
|
||||
);
|
||||
SourceFiles["SourceFile13"] = new SourceFile(
|
||||
13,
|
||||
<>Each level of this Source-File increases the size of Stanek's Gift.</>,
|
||||
);
|
||||
|
||||
@@ -163,7 +163,10 @@ export function applySourceFile(srcFile: PlayerOwnedSourceFile): void {
|
||||
break;
|
||||
}
|
||||
case 12: // The Recursion
|
||||
// No effects, grants neuroflux.
|
||||
// Grants neuroflux.
|
||||
break;
|
||||
case 13: // They're Lunatics
|
||||
// Grants more space on Stanek's Gift.
|
||||
break;
|
||||
default:
|
||||
console.error(`Invalid source file number: ${srcFile.n}`);
|
||||
|
||||
@@ -12,6 +12,7 @@ import { initCompanies } from "./Company/Companies";
|
||||
import { Corporation } from "./Corporation/Corporation";
|
||||
import { CONSTANTS } from "./Constants";
|
||||
import { Factions, initFactions } from "./Faction/Factions";
|
||||
import { staneksGift } from "./CotMG/Helper";
|
||||
import { processPassiveFactionRepGain, inviteToFaction } from "./Faction/FactionHelpers";
|
||||
import { Router } from "./ui/GameRoot";
|
||||
import { SetupTextEditor } from "./ScriptEditor/ui/ScriptEditorRoot";
|
||||
@@ -101,6 +102,9 @@ const Engine: {
|
||||
Player.gang.process(numCycles, Player);
|
||||
}
|
||||
|
||||
// Staneks gift
|
||||
staneksGift.process(Player, numCycles);
|
||||
|
||||
// Corporation
|
||||
if (Player.corporation instanceof Corporation) {
|
||||
// Stores cycles in a "buffer". Processed separately using Engine Counters
|
||||
@@ -329,6 +333,8 @@ const Engine: {
|
||||
Player.bladeburner.storeCycles(numCyclesOffline);
|
||||
}
|
||||
|
||||
staneksGift.process(Player, numCyclesOffline);
|
||||
|
||||
// Sleeves offline progress
|
||||
for (let i = 0; i < Player.sleeves.length; ++i) {
|
||||
if (Player.sleeves[i] instanceof Sleeve) {
|
||||
|
||||
33
src/engineStyle.ts
Normal file
33
src/engineStyle.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
// These should really be imported with the module that is presenting that UI, but because they very much depend on the
|
||||
// cascade order, we'll pull them all in here.
|
||||
import "normalize.css";
|
||||
import "../css/styles.scss";
|
||||
import "../css/tooltips.scss";
|
||||
import "../css/buttons.scss";
|
||||
import "../css/mainmenu.scss";
|
||||
import "../css/characteroverview.scss";
|
||||
|
||||
import "../css/scripteditor.scss";
|
||||
import "../css/hacknetnodes.scss";
|
||||
import "../css/menupages.scss";
|
||||
import "../css/augmentations.scss";
|
||||
import "../css/redpill.scss";
|
||||
import "../css/stockmarket.scss";
|
||||
import "../css/workinprogress.scss";
|
||||
import "../css/popupboxes.scss";
|
||||
import "../css/gameoptions.scss";
|
||||
import "../css/interactivetutorial.scss";
|
||||
import "../css/loader.scss";
|
||||
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";
|
||||
import "../css/grid.min.css";
|
||||
import "../css/dev-menu.css";
|
||||
import "../css/casino.scss";
|
||||
import "../css/milestones.scss";
|
||||
import "../css/infiltration.scss";
|
||||
import "../css/staneksgift.scss";
|
||||
@@ -61,6 +61,8 @@ import { CharacterStats } from "./CharacterStats";
|
||||
import { TravelAgencyRoot } from "../Locations/ui/TravelAgencyRoot";
|
||||
import { StockMarketRoot } from "../StockMarket/ui/StockMarketRoot";
|
||||
import { BitverseRoot } from "../BitNode/ui/BitverseRoot";
|
||||
import { StaneksGiftRoot } from "../CotMG/ui/StaneksGiftRoot";
|
||||
import { staneksGift } from "../CotMG/Helper";
|
||||
import { CharacterOverview } from "./React/CharacterOverview";
|
||||
import { BladeburnerCinematic } from "../Bladeburner/ui/BladeburnerCinematic";
|
||||
import { workerScripts } from "../Netscript/WorkerScripts";
|
||||
@@ -178,6 +180,9 @@ export let Router: IRouter = {
|
||||
toLocation: () => {
|
||||
throw new Error("Router called before initialization");
|
||||
},
|
||||
toStaneksGift: () => {
|
||||
throw new Error("Router called before initialization");
|
||||
},
|
||||
};
|
||||
|
||||
function determineStartPage(player: IPlayer): Page {
|
||||
@@ -267,6 +272,9 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
|
||||
setLocation(location);
|
||||
setPage(Page.Location);
|
||||
},
|
||||
toStaneksGift: () => {
|
||||
setPage(Page.StaneksGift);
|
||||
},
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -303,6 +311,8 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
|
||||
<SleeveRoot />
|
||||
) : page === Page.Stats ? (
|
||||
<CharacterStats />
|
||||
) : page === Page.StaneksGift ? (
|
||||
<StaneksGiftRoot staneksGift={staneksGift} />
|
||||
) : page === Page.ScriptEditor ? (
|
||||
<ScriptEditorRoot filename={filename} code={code} player={player} router={Router} />
|
||||
) : page === Page.ActiveScripts ? (
|
||||
|
||||
@@ -638,6 +638,9 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
|
||||
<Link href="https://www.reddit.com/r/bitburner" target="_blank">
|
||||
<Typography>Reddit</Typography>
|
||||
</Link>
|
||||
<Link href="https://plaza.dsolver.ca/games/bitburner" target="_blank">
|
||||
<Typography>Incremental game plaza</Typography>
|
||||
</Link>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -34,6 +34,7 @@ export enum Page {
|
||||
BladeburnerCinematic,
|
||||
Location,
|
||||
Loading,
|
||||
StaneksGift,
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,4 +74,5 @@ export interface IRouter {
|
||||
toWork(): void;
|
||||
toBladeburnerCinematic(): void;
|
||||
toLocation(location: Location): void;
|
||||
toStaneksGift(): void;
|
||||
}
|
||||
|
||||
@@ -169,6 +169,18 @@ class NumeralFormatter {
|
||||
return this.format(n, "0,0");
|
||||
}
|
||||
|
||||
formatStaneksGiftHeat(n: number): string {
|
||||
return this.format(n, "0.000a");
|
||||
}
|
||||
|
||||
formatStaneksGiftCharge(n: number): string {
|
||||
return this.format(n, "0.000a");
|
||||
}
|
||||
|
||||
formatStaneksGiftPower(n: number): string {
|
||||
return this.format(n, "0.00");
|
||||
}
|
||||
|
||||
parseMoney(s: string): number {
|
||||
// numeral library does not handle formats like 1e10 well (returns 110),
|
||||
// so if both return a valid number, return the biggest one
|
||||
|
||||
Reference in New Issue
Block a user