Merge pull request #1382 from danielyxie/sg

[SPOILERS] BN13
This commit is contained in:
hydroflame 2021-12-03 15:45:48 -05:00 committed by GitHub
commit 564bc4502b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 2479 additions and 64 deletions

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

36
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

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

@ -0,0 +1,16 @@
canPlace() Netscript Function
=======================================
.. js:function:: canPlace(worldX, worldY, rotation, 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 rotation: A number from 0 to 3, the mount of 90 degree turn to take.
: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

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

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

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

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

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

@ -0,0 +1,16 @@
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 rotation: A number from 0 to 3, the mount of 90 degree turn to take.
: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

@ -0,0 +1,26 @@
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;
charge: number;
id: number;
shape: boolean[][];
type: string;
power: 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,134 @@ function initAugmentations(): void {
resetAugmentation(BladesSimulacrum); 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: 1e6,
moneyCost: 0,
info:
"The next evolution is near, A coming together of man and machine. A synthesis greater than the birth of the human " +
"organism. Time spent with the gift has allowed for acclimitaztion of the invavise augment and the toll it takes upon " +
"your frame granting lesser penalty of 5% to all stats.",
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: <>The penalty for the gift is reduced to 5%</>,
});
StaneksGift2.addToFactions([ChurchOfTheMachineGodFactionName]);
resetAugmentation(StaneksGift2);
const StaneksGift3 = new Augmentation({
name: AugmentationNames.StaneksGift3,
repCost: 1e8,
moneyCost: 0,
info:
"The synthesis of human and machine is nothing to fear. It is our destiny. " +
"You will become greater than the sum of our parts. As One. Enbrace your gift " +
"fully and wholly free of it's accursed toll. Serenity brings tranquility the form " +
"of no longer suffering a stat penalty. ",
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: <>Staneks Gift has no penalty.</>,
});
StaneksGift3.addToFactions([ChurchOfTheMachineGodFactionName]);
resetAugmentation(StaneksGift3);
}
// Update costs based on how many have been purchased // Update costs based on how many have been purchased
mult = Math.pow( mult = Math.pow(
CONSTANTS.MultipleAugMultiplier * [1, 0.96, 0.94, 0.93][SourceFileFlags[11]], CONSTANTS.MultipleAugMultiplier * [1, 0.96, 0.94, 0.93][SourceFileFlags[11]],

@ -108,6 +108,9 @@ export const AugmentationNames: {
BladeArmorOmnibeam: string; BladeArmorOmnibeam: string;
BladeArmorIPU: string; BladeArmorIPU: string;
BladesSimulacrum: string; BladesSimulacrum: string;
StaneksGift1: string;
StaneksGift2: string;
StaneksGift3: string;
} = { } = {
Targeting1: "Augmented Targeting I", Targeting1: "Augmented Targeting I",
Targeting2: "Augmented Targeting II", Targeting2: "Augmented Targeting II",
@ -219,6 +222,10 @@ export const AugmentationNames: {
BladeArmorIPU: "BLADE-51b Tesla Armor: IPU Upgrade", BladeArmorIPU: "BLADE-51b Tesla Armor: IPU Upgrade",
BladesSimulacrum: "The Blade's Simulacrum", BladesSimulacrum: "The Blade's Simulacrum",
StaneksGift1: "Stanek's Gift - Genesis",
StaneksGift2: "Stanek's Gift - Awakening",
StaneksGift3: "Stanek's Gift - Serenity",
//Wasteland Augs //Wasteland Augs
//PepBoy: "P.E.P-Boy", Plasma Energy Projection System //PepBoy: "P.E.P-Boy", Plasma Energy Projection System
//PepBoyForceField Generates plasma force fields //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 stat 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 // 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["BitNode14"] = new BitNode(14, 2, "", "COMING SOON");
BitNodes["BitNode15"] = new BitNode(15, 2, "", "COMING SOON"); BitNodes["BitNode15"] = new BitNode(15, 2, "", "COMING SOON");
BitNodes["BitNode16"] = new BitNode(16, 2, "", "COMING SOON"); BitNodes["BitNode16"] = new BitNode(16, 2, "", "COMING SOON");
@ -553,6 +583,8 @@ export function initBitNodeMultipliers(p: IPlayer): void {
BitNodeMultipliers[mult] = 1; BitNodeMultipliers[mult] = 1;
} }
} }
// Special case.
BitNodeMultipliers.StaneksGiftExtraSize = 0;
switch (p.bitNodeN) { switch (p.bitNodeN) {
case 1: // Source Genesis (every multiplier is 1) case 1: // Source Genesis (every multiplier is 1)
@ -566,6 +598,8 @@ export function initBitNodeMultipliers(p: IPlayer): void {
BitNodeMultipliers.InfiltrationMoney = 3; BitNodeMultipliers.InfiltrationMoney = 3;
BitNodeMultipliers.FactionWorkRepGain = 0.5; BitNodeMultipliers.FactionWorkRepGain = 0.5;
BitNodeMultipliers.FactionPassiveRepGain = 0; BitNodeMultipliers.FactionPassiveRepGain = 0;
BitNodeMultipliers.StaneksGiftPowerMultiplier = 2;
BitNodeMultipliers.StaneksGiftExtraSize = -6;
BitNodeMultipliers.PurchasedServerSoftcap = 1.3; BitNodeMultipliers.PurchasedServerSoftcap = 1.3;
BitNodeMultipliers.CorporationSoftCap = 0.9; BitNodeMultipliers.CorporationSoftCap = 0.9;
break; break;
@ -583,6 +617,8 @@ export function initBitNodeMultipliers(p: IPlayer): void {
BitNodeMultipliers.HacknetNodeMoney = 0.25; BitNodeMultipliers.HacknetNodeMoney = 0.25;
BitNodeMultipliers.HomeComputerRamCost = 1.5; BitNodeMultipliers.HomeComputerRamCost = 1.5;
BitNodeMultipliers.PurchasedServerCost = 2; BitNodeMultipliers.PurchasedServerCost = 2;
BitNodeMultipliers.StaneksGiftPowerMultiplier = 0.75;
BitNodeMultipliers.StaneksGiftExtraSize = -2;
BitNodeMultipliers.PurchasedServerSoftcap = 1.3; BitNodeMultipliers.PurchasedServerSoftcap = 1.3;
BitNodeMultipliers.GangSoftcap = 0.9; BitNodeMultipliers.GangSoftcap = 0.9;
break; break;
@ -599,6 +635,8 @@ export function initBitNodeMultipliers(p: IPlayer): void {
BitNodeMultipliers.HackExpGain = 0.4; BitNodeMultipliers.HackExpGain = 0.4;
BitNodeMultipliers.CrimeExpGain = 0.5; BitNodeMultipliers.CrimeExpGain = 0.5;
BitNodeMultipliers.FactionWorkRepGain = 0.75; BitNodeMultipliers.FactionWorkRepGain = 0.75;
BitNodeMultipliers.StaneksGiftPowerMultiplier = 1.5;
BitNodeMultipliers.StaneksGiftExtraSize = 0;
BitNodeMultipliers.PurchasedServerSoftcap = 1.2; BitNodeMultipliers.PurchasedServerSoftcap = 1.2;
break; break;
case 5: // Artificial intelligence case 5: // Artificial intelligence
@ -613,6 +651,8 @@ export function initBitNodeMultipliers(p: IPlayer): void {
BitNodeMultipliers.AugmentationMoneyCost = 2; BitNodeMultipliers.AugmentationMoneyCost = 2;
BitNodeMultipliers.HackExpGain = 0.5; BitNodeMultipliers.HackExpGain = 0.5;
BitNodeMultipliers.CorporationValuation = 0.5; BitNodeMultipliers.CorporationValuation = 0.5;
BitNodeMultipliers.StaneksGiftPowerMultiplier = 1.3;
BitNodeMultipliers.StaneksGiftExtraSize = 0;
BitNodeMultipliers.PurchasedServerSoftcap = 1.2; BitNodeMultipliers.PurchasedServerSoftcap = 1.2;
break; break;
case 6: // Bladeburner case 6: // Bladeburner
@ -630,6 +670,8 @@ export function initBitNodeMultipliers(p: IPlayer): void {
BitNodeMultipliers.HackExpGain = 0.25; BitNodeMultipliers.HackExpGain = 0.25;
BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed
BitNodeMultipliers.PurchasedServerSoftcap = 2; BitNodeMultipliers.PurchasedServerSoftcap = 2;
BitNodeMultipliers.StaneksGiftPowerMultiplier = 0.5;
BitNodeMultipliers.StaneksGiftExtraSize = 2;
BitNodeMultipliers.GangSoftcap = 0.7; BitNodeMultipliers.GangSoftcap = 0.7;
BitNodeMultipliers.CorporationSoftCap = 0.9; BitNodeMultipliers.CorporationSoftCap = 0.9;
break; break;
@ -653,6 +695,8 @@ export function initBitNodeMultipliers(p: IPlayer): void {
BitNodeMultipliers.FourSigmaMarketDataApiCost = 2; BitNodeMultipliers.FourSigmaMarketDataApiCost = 2;
BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed BitNodeMultipliers.DaedalusAugsRequirement = 1.166; // Results in 35 Augs needed
BitNodeMultipliers.PurchasedServerSoftcap = 2; BitNodeMultipliers.PurchasedServerSoftcap = 2;
BitNodeMultipliers.StaneksGiftPowerMultiplier = 0.9;
BitNodeMultipliers.StaneksGiftExtraSize = -1;
BitNodeMultipliers.GangSoftcap = 0.7; BitNodeMultipliers.GangSoftcap = 0.7;
BitNodeMultipliers.CorporationSoftCap = 0.9; BitNodeMultipliers.CorporationSoftCap = 0.9;
break; break;
@ -667,6 +711,7 @@ export function initBitNodeMultipliers(p: IPlayer): void {
BitNodeMultipliers.RepToDonateToFaction = 0; BitNodeMultipliers.RepToDonateToFaction = 0;
BitNodeMultipliers.CorporationValuation = 0; BitNodeMultipliers.CorporationValuation = 0;
BitNodeMultipliers.CodingContractMoney = 0; BitNodeMultipliers.CodingContractMoney = 0;
BitNodeMultipliers.StaneksGiftExtraSize = -7;
BitNodeMultipliers.PurchasedServerSoftcap = 4; BitNodeMultipliers.PurchasedServerSoftcap = 4;
BitNodeMultipliers.GangSoftcap = 0; BitNodeMultipliers.GangSoftcap = 0;
BitNodeMultipliers.CorporationSoftCap = 0; BitNodeMultipliers.CorporationSoftCap = 0;
@ -691,6 +736,8 @@ export function initBitNodeMultipliers(p: IPlayer): void {
BitNodeMultipliers.FourSigmaMarketDataApiCost = 4; BitNodeMultipliers.FourSigmaMarketDataApiCost = 4;
BitNodeMultipliers.BladeburnerRank = 0.9; BitNodeMultipliers.BladeburnerRank = 0.9;
BitNodeMultipliers.BladeburnerSkillCost = 1.2; BitNodeMultipliers.BladeburnerSkillCost = 1.2;
BitNodeMultipliers.StaneksGiftPowerMultiplier = 0.5;
BitNodeMultipliers.StaneksGiftExtraSize = 2;
BitNodeMultipliers.GangSoftcap = 0.8; BitNodeMultipliers.GangSoftcap = 0.8;
BitNodeMultipliers.CorporationSoftCap = 0.9; BitNodeMultipliers.CorporationSoftCap = 0.9;
break; break;
@ -716,6 +763,8 @@ export function initBitNodeMultipliers(p: IPlayer): void {
BitNodeMultipliers.PurchasedServerLimit = 0.6; BitNodeMultipliers.PurchasedServerLimit = 0.6;
BitNodeMultipliers.PurchasedServerMaxRam = 0.5; BitNodeMultipliers.PurchasedServerMaxRam = 0.5;
BitNodeMultipliers.BladeburnerRank = 0.8; BitNodeMultipliers.BladeburnerRank = 0.8;
BitNodeMultipliers.StaneksGiftPowerMultiplier = 0.75;
BitNodeMultipliers.StaneksGiftExtraSize = -3;
BitNodeMultipliers.PurchasedServerSoftcap = 1.1; BitNodeMultipliers.PurchasedServerSoftcap = 1.1;
BitNodeMultipliers.GangSoftcap = 0.9; BitNodeMultipliers.GangSoftcap = 0.9;
BitNodeMultipliers.CorporationSoftCap = 0.9; BitNodeMultipliers.CorporationSoftCap = 0.9;
@ -807,10 +856,56 @@ export function initBitNodeMultipliers(p: IPlayer): void {
BitNodeMultipliers.BladeburnerRank = dec; BitNodeMultipliers.BladeburnerRank = dec;
BitNodeMultipliers.BladeburnerSkillCost = inc; BitNodeMultipliers.BladeburnerSkillCost = inc;
BitNodeMultipliers.StaneksGiftPowerMultiplier = inc;
BitNodeMultipliers.StaneksGiftExtraSize = inc;
BitNodeMultipliers.GangSoftcap = 0.8; BitNodeMultipliers.GangSoftcap = 0.8;
BitNodeMultipliers.CorporationSoftCap = 0.8; BitNodeMultipliers.CorporationSoftCap = 0.8;
break; break;
} }
case 13: {
BitNodeMultipliers.PurchasedServerSoftcap = 1.5;
BitNodeMultipliers.HackingLevelMultiplier = 0.3;
BitNodeMultipliers.StrengthLevelMultiplier = 0.3;
BitNodeMultipliers.DefenseLevelMultiplier = 0.3;
BitNodeMultipliers.DexterityLevelMultiplier = 0.3;
BitNodeMultipliers.AgilityLevelMultiplier = 0.3;
BitNodeMultipliers.CharismaLevelMultiplier = 0.3;
BitNodeMultipliers.ServerMaxMoney = 0.45;
BitNodeMultipliers.ServerStartingMoney = 0.75;
BitNodeMultipliers.ServerStartingSecurity = 2;
BitNodeMultipliers.ScriptHackMoney = 0.4;
BitNodeMultipliers.CompanyWorkMoney = 0.4;
BitNodeMultipliers.CrimeMoney = 0.4;
BitNodeMultipliers.HacknetNodeMoney = 0.4;
BitNodeMultipliers.CodingContractMoney = 0.4;
BitNodeMultipliers.CompanyWorkExpGain = 0.5;
BitNodeMultipliers.ClassGymExpGain = 0.5;
BitNodeMultipliers.FactionWorkExpGain = 0.5;
BitNodeMultipliers.HackExpGain = 0.5;
BitNodeMultipliers.CrimeExpGain = 0.5;
BitNodeMultipliers.FactionWorkRepGain = 0.6;
BitNodeMultipliers.FourSigmaMarketDataCost = 10;
BitNodeMultipliers.FourSigmaMarketDataApiCost = 10;
BitNodeMultipliers.CorporationValuation = 0.001;
BitNodeMultipliers.BladeburnerRank = 0.45;
BitNodeMultipliers.BladeburnerSkillCost = 2;
BitNodeMultipliers.StaneksGiftPowerMultiplier = 2;
BitNodeMultipliers.StaneksGiftExtraSize = 1;
BitNodeMultipliers.GangSoftcap = 0.3;
BitNodeMultipliers.CorporationSoftCap = 0.3;
BitNodeMultipliers.WorldDaemonDifficulty = 2.5;
break;
}
default: default:
console.warn("Player.bitNodeN invalid"); console.warn("Player.bitNodeN invalid");
break; break;

@ -217,6 +217,21 @@ interface IBitNodeMultipliers {
*/ */
StrengthLevelMultiplier: number; StrengthLevelMultiplier: number;
/**
* Influences the power of the gift.
*/
StaneksGiftPowerMultiplier: number;
/**
* Influences the size of the gift.
*/
StaneksGiftExtraSize: number;
/**
* Influences the hacking skill required to backdoor the world daemon.
*/
WorldDaemonDifficulty: number;
// Index signature // Index signature
[key: string]: number; [key: string]: number;
} }
@ -282,4 +297,9 @@ export const BitNodeMultipliers: IBitNodeMultipliers = {
GangSoftcap: 1, GangSoftcap: 1,
DaedalusAugsRequirement: 1, DaedalusAugsRequirement: 1,
StaneksGiftPowerMultiplier: 1,
StaneksGiftExtraSize: 0,
WorldDaemonDifficulty: 1,
}; };

@ -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</Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>| | | |O / | | 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'}}>| | |/ \/ / __| | |/ \ | \ | |__ \ \/ \| | |</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'}}> \| / \| | / / \ |/ </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> <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>

@ -0,0 +1,95 @@
import { Fragment, FragmentById } from "./Fragment";
import { Generic_fromJSON, Generic_toJSON, Reviver } from "../utils/JSONReviver";
export interface IActiveFragmentParams {
x: number;
y: number;
rotation: number;
fragment: Fragment;
}
export class ActiveFragment {
id: number;
avgCharge: number;
numCharge: number;
rotation: number;
x: number;
y: number;
constructor(params?: IActiveFragmentParams) {
if (params) {
this.id = params.fragment.id;
this.x = params.x;
this.y = params.y;
this.avgCharge = 0;
this.numCharge = 0;
this.rotation = params.rotation;
} else {
this.id = -1;
this.x = -1;
this.y = -1;
this.avgCharge = -1;
this.numCharge = -1;
this.rotation = -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, this.rotation) && otherFragment.fullAt(i - dx, j - dy, other.rotation))
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, this.rotation);
}
neighboors(): number[][] {
return this.fragment()
.neighboors(this.rotation)
.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, rotation: this.rotation, fragment: fragment });
c.avgCharge = this.avgCharge;
c.numCharge = this.numCharge;
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;

356
src/CotMG/Fragment.ts Normal file

@ -0,0 +1,356 @@
import { FragmentType } from "./FragmentType";
import { Shapes } from "./data/Shapes";
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, rotation: number): boolean {
if (y < 0) return false;
if (y >= this.height(rotation)) return false;
if (x < 0) return false;
if (x >= this.width(rotation)) return false;
// start xy, modifier xy
let [sx, sy, mx, my] = [0, 0, 1, 1];
if (rotation === 1) {
[sx, sy, mx, my] = [this.width(rotation) - 1, 0, -1, 1];
} else if (rotation === 2) {
[sx, sy, mx, my] = [this.width(rotation) - 1, this.height(rotation) - 1, -1, -1];
} else if (rotation === 3) {
[sx, sy, mx, my] = [0, this.height(rotation) - 1, 1, -1];
}
let [qx, qy] = [sx + mx * x, sy + my * y];
if (rotation % 2 === 1) [qx, qy] = [qy, qx];
return this.shape[qy][qx];
}
width(rotation: number): number {
if (rotation % 2 === 0) return this.shape[0].length;
return this.shape.length;
}
height(rotation: number): number {
if (rotation % 2 === 0) return this.shape.length;
return this.shape[0].length;
}
// List of direct neighboors of this fragment.
neighboors(rotation: number): number[][] {
const candidates: number[][] = [];
const add = (x: number, y: number): void => {
if (this.fullAt(x, y, rotation)) return;
if (candidates.some((coord) => coord[0] === x && coord[1] === y)) return;
candidates.push([x, y]);
};
for (let y = 0; y < this.height(rotation); y++) {
for (let x = 0; x < this.width(rotation); x++) {
// This cell is full, add all it's neighboors.
if (!this.fullAt(x, y, rotation)) 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
Shapes.S,
FragmentType.Hacking, // type
1,
1, // limit
),
);
Fragments.push(
new Fragment(
1, // id
Shapes.Z,
FragmentType.Hacking, // type
1,
1, // limit
),
);
Fragments.push(
new Fragment(
5, // id
Shapes.T,
FragmentType.HackingSpeed, // type
1.3,
1, // limit
),
);
Fragments.push(
new Fragment(
6, // id
Shapes.I,
FragmentType.HackingMoney, // type
2, // power
1, // limit
),
);
Fragments.push(
new Fragment(
7, // id
Shapes.J,
FragmentType.HackingGrow, // type
0.5, // power
1, // limit
),
);
Fragments.push(
new Fragment(
10, // id
Shapes.T,
FragmentType.Strength, // type
2, // power
1, // limit
),
);
Fragments.push(
new Fragment(
12, // id
Shapes.L,
FragmentType.Defense, // type
2, // power
1, // limit
),
);
Fragments.push(
new Fragment(
14, // id
Shapes.L,
FragmentType.Dexterity, // type
2, // power
1, // limit
),
);
Fragments.push(
new Fragment(
16, // id
Shapes.S,
FragmentType.Agility, // type
2, // power
1, // limit
),
);
Fragments.push(
new Fragment(
18, // id
Shapes.S,
FragmentType.Charisma, // type
3, // power
1, // limit
),
);
Fragments.push(
new Fragment(
20, // id
Shapes.I,
FragmentType.HacknetMoney, // type
1, // power
1, // limit
),
);
Fragments.push(
new Fragment(
21, // id
Shapes.O,
FragmentType.HacknetCost, // type
-1, // power
1, // limit
),
);
Fragments.push(
new Fragment(
25, // id
Shapes.J,
FragmentType.Rep, // type
0.5, // power
1, // limit
),
);
Fragments.push(
new Fragment(
27, // id
Shapes.J,
FragmentType.WorkMoney, // type
10, // power
1, // limit
),
);
Fragments.push(
new Fragment(
28, // id
Shapes.L,
FragmentType.Crime, // type
2, // power
1, // limit
),
);
Fragments.push(
new Fragment(
30, // id
Shapes.S,
FragmentType.Bladeburner, // type
0.4, // power
1, // limit
),
);
Fragments.push(
new Fragment(
100, // id
[
// shape
[_, X, X],
[X, X, _],
[_, X, _],
],
FragmentType.Booster, // type
1.1, // power
99, // limit
),
);
Fragments.push(
new Fragment(
101, // id
[
// shape
[X, X, X, X],
[X, _, _, _],
],
FragmentType.Booster, // type
1.1, // power
99, // limit
),
);
Fragments.push(
new Fragment(
102, // id
[
// shape
[_, X, X, X],
[X, X, _, _],
],
FragmentType.Booster, // type
1.1, // power
99, // limit
),
);
Fragments.push(
new Fragment(
103, // id
[
// shape
[X, X, X, _],
[_, _, X, X],
],
FragmentType.Booster, // type
1.1, // power
99, // limit
),
);
Fragments.push(
new Fragment(
104, // id
[
// shape
[_, X, X],
[_, X, _],
[X, X, _],
],
FragmentType.Booster, // type
1.1, // power
99, // limit
),
);
Fragments.push(
new Fragment(
105, // id
[
// shape
[_, _, X],
[_, X, X],
[X, X, _],
],
FragmentType.Booster, // type
1.1, // power
99, // limit
),
);
Fragments.push(
new Fragment(
106, // id
[
// shape
[X, _, _],
[X, X, X],
[X, _, _],
],
FragmentType.Booster, // type
1.1, // power
99, // limit
),
);
Fragments.push(
new Fragment(
107, // id
[
// shape
[_, X, _],
[X, X, X],
[_, X, _],
],
FragmentType.Booster, // type
1.1, // power
99, // 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

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

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

23
src/CotMG/IStaneksGift.ts Normal file

@ -0,0 +1,23 @@
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(player: IPlayer, fragment: ActiveFragment, threads: number): void;
process(p: IPlayer, n: number): void;
effect(fragment: ActiveFragment): number;
canPlace(x: number, y: number, rotation: number, fragment: Fragment): boolean;
place(x: number, y: number, rotation: number, fragment: Fragment): boolean;
findFragment(rootX: number, rootY: number): ActiveFragment | undefined;
fragmentAt(rootX: number, rootY: number): ActiveFragment | undefined;
delete(rootX: number, rootY: number): boolean;
clear(): void;
count(fragment: Fragment): number;
inBonus(): boolean;
prestigeAugmentation(): void;
prestigeSourceFile(): void;
}

234
src/CotMG/StaneksGift.ts Normal file

@ -0,0 +1,234 @@
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 { 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";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
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(player: IPlayer, af: ActiveFragment, threads: number): void {
af.avgCharge = (af.numCharge * af.avgCharge + threads) / (af.numCharge + 1);
af.numCharge++;
const cotmg = Factions["Church of the Machine God"];
cotmg.playerReputation += (player.faction_rep_mult * (Math.pow(threads, 0.95) * (cotmg.favor + 100))) / 1000;
}
inBonus(): boolean {
return (this.storedCycles * CONSTANTS._idleSpeed) / 1000 > 1;
}
process(p: IPlayer, numCycles = 1): void {
if (!p.hasAugmentation(AugmentationNames.StaneksGift1)) return;
this.storedCycles += numCycles;
this.storedCycles -= 10;
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 undefined with typescript "Type guard". Whatever
let neighboors = maybeFragments.filter((v: ActiveFragment | undefined): v is ActiveFragment => !!v);
neighboors = neighboors.filter((fragment) => fragment.fragment().type === FragmentType.Booster);
let boost = 1;
neighboors = neighboors.filter((v, i, s) => s.indexOf(v) === i);
for (const neighboor of neighboors) {
boost *= neighboor.fragment().power;
}
return CalculateEffect(fragment.avgCharge, fragment.numCharge, fragment.fragment().power, boost);
}
canPlace(rootX: number, rootY: number, rotation: number, fragment: Fragment): boolean {
if (rootX < 0 || rootY < 0) return false;
if (rootX + fragment.width(rotation) > this.width()) return false;
if (rootY + fragment.height(rotation) > this.height()) return false;
if (this.count(fragment) >= fragment.limit) return false;
const newFrag = new ActiveFragment({ x: rootX, y: rootY, rotation: rotation, fragment: fragment });
for (const aFrag of this.fragments) {
if (aFrag.collide(newFrag)) return false;
}
return true;
}
place(rootX: number, rootY: number, rotation: number, fragment: Fragment): boolean {
if (!this.canPlace(rootX, rootY, rotation, fragment)) return false;
this.fragments.push(new ActiveFragment({ x: rootX, y: rootY, rotation: rotation, fragment: fragment }));
return true;
}
findFragment(rootX: number, rootY: number): ActiveFragment | undefined {
return this.fragments.find((f) => f.x === rootX && f.y === rootY);
}
fragmentAt(worldX: number, worldY: number): ActiveFragment | undefined {
for (const aFrag of this.fragments) {
if (aFrag.fullAt(worldX, worldY)) {
return aFrag;
}
}
return undefined;
}
count(fragment: Fragment): number {
let amt = 0;
for (const aFrag of this.fragments) {
if (aFrag.fragment().id === fragment.id) amt++;
}
return amt;
}
delete(rootX: number, rootY: number): boolean {
for (let i = 0; i < this.fragments.length; i++) {
if (this.fragments[i].x === rootX && this.fragments[i].y === rootY) {
this.fragments.splice(i, 1);
return true;
}
}
return false;
}
clear(): void {
this.fragments = [];
}
clearCharge(): void {
this.fragments.forEach((f) => {
f.avgCharge = 0;
f.numCharge = 0;
});
}
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;
}
}
p.updateSkillLevels();
}
prestigeAugmentation(): void {
this.clearCharge();
}
prestigeSourceFile(): void {
this.clear();
this.storedCycles = 0;
}
/**
* 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;

@ -0,0 +1,2 @@
import { EventEmitter } from "../utils/EventEmitter";
export const StaneksGiftEvents = new EventEmitter<[]>();

@ -0,0 +1,7 @@
export const StanekConstants: {
RAMBonus: number;
BaseSize: number;
} = {
RAMBonus: 0.1,
BaseSize: 9,
};

37
src/CotMG/data/Shapes.ts Normal file

@ -0,0 +1,37 @@
const _ = false;
const X = true;
export const Shapes: {
O: boolean[][];
I: boolean[][];
L: boolean[][];
J: boolean[][];
S: boolean[][];
Z: boolean[][];
T: boolean[][];
} = {
O: [
[X, X],
[X, X],
],
I: [[X, X, X, X]],
L: [
[_, _, X],
[X, X, X],
],
J: [
[X, _, _],
[X, X, X],
],
S: [
[_, X, X],
[X, X, _],
],
Z: [
[X, X, _],
[_, X, X],
],
T: [
[X, X, X],
[_, X, _],
],
};

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

@ -0,0 +1,12 @@
import { BitNodeMultipliers } from "../../BitNode/BitNodeMultipliers";
export function CalculateEffect(avgCharge: number, numCharge: number, power: number, boost: number): number {
return (
1 +
(Math.log(avgCharge + 1) / (Math.log(1.8) * 100)) *
Math.pow((numCharge + 1) / 5, 0.07) *
power *
boost *
BitNodeMultipliers.StaneksGiftPowerMultiplier
);
}

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

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

@ -0,0 +1,81 @@
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 | undefined;
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 === undefined) {
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.avgCharge)} avg. * ${
props.fragment.numCharge
} times`;
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 />
Base 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>
);
}

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

@ -0,0 +1,89 @@
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 left = props.fragment.limit - props.gift.count(props.fragment);
const remaining = props.fragment.limit !== Infinity ? <>{left} remaining</> : <></>;
return (
<Box display="flex">
<Box sx={{ mx: 2 }}>
<FragmentPreview
width={props.fragment.width(0)}
height={props.fragment.height(0)}
colorAt={(x, y) => {
if (!props.fragment.fullAt(x, y, 0)) return "";
if (left === 0) return "grey";
return 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>
);
}

172
src/CotMG/ui/MainBoard.tsx Normal file

@ -0,0 +1,172 @@
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})`;
}
interface IProps {
gift: IStaneksGift;
}
export function MainBoard(props: IProps): 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) 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 [rotation, setRotation] = React.useState(0);
const [selectedFragment, setSelectedFragment] = React.useState(NoneFragment);
function moveGhost(worldX: number, worldY: number, rotation: number): void {
setPos([worldX, worldY]);
if (selectedFragment.type === FragmentType.None || selectedFragment.type === FragmentType.Delete) return;
const newgrid = zeros([props.gift.width(), props.gift.height()]);
for (let y = 0; y < selectedFragment.height(rotation); y++) {
for (let x = 0; x < selectedFragment.width(rotation); x++) {
if (!selectedFragment.fullAt(x, y, rotation)) continue;
if (worldX + x > newgrid.length - 1) continue;
if (worldY + y > newgrid[worldX + x].length - 1) continue;
newgrid[worldX + x][worldY + y] = 1;
}
}
setGhostGrid(newgrid);
}
function deleteAt(worldX: number, worldY: number): boolean {
const f = props.gift.fragmentAt(worldX, worldY);
if (f === undefined) return false;
return props.gift.delete(f.x, f.y);
}
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, rotation, selectedFragment)) return;
props.gift.place(worldX, worldY, rotation, 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) 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, rotation)}
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);
}
React.useEffect(() => {
function doRotate(this: Document, event: KeyboardEvent): void {
if (event.key === "q") {
const r = (rotation - 1 + 4) % 4;
setRotation(r);
moveGhost(pos[0], pos[1], r);
}
if (event.key === "e") {
const r = (rotation + 1) % 4;
setRotation(r);
moveGhost(pos[0], pos[1], r);
}
}
document.addEventListener("keydown", doRotate);
return () => document.removeEventListener("keydown", doRotate);
});
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} />
</>
);
}

@ -0,0 +1,36 @@
import React, { useState, useEffect } from "react";
import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
import { CONSTANTS } from "../../Constants";
import { StaneksGiftEvents } from "../StaneksGiftEvents";
import { MainBoard } from "./MainBoard";
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). Q/E to rotate fragments.
</Typography>
{staneksGift.storedCycles > 5 && (
<Typography>
Bonus time: {convertTimeMsToTimeElapsedString(CONSTANTS._idleSpeed * staneksGift.storedCycles)}
</Typography>
)}
<MainBoard gift={staneksGift} />
</>
);
}

@ -2,6 +2,7 @@ import { IPlayer } from "./PersonObjects/IPlayer";
import { Bladeburner } from "./Bladeburner/Bladeburner"; import { Bladeburner } from "./Bladeburner/Bladeburner";
import { IEngine } from "./IEngine"; import { IEngine } from "./IEngine";
import { IRouter } from "./ui/Router"; import { IRouter } from "./ui/Router";
import { AugmentationNames } from "./Augmentation/data/AugmentationNames";
import React from "react"; import React from "react";
@ -19,6 +20,7 @@ import { Corporation } from "./DevMenu/ui/Corporation";
import { CodingContracts } from "./DevMenu/ui/CodingContracts"; import { CodingContracts } from "./DevMenu/ui/CodingContracts";
import { StockMarket } from "./DevMenu/ui/StockMarket"; import { StockMarket } from "./DevMenu/ui/StockMarket";
import { Sleeves } from "./DevMenu/ui/Sleeves"; import { Sleeves } from "./DevMenu/ui/Sleeves";
import { Stanek } from "./DevMenu/ui/Stanek";
import { TimeSkip } from "./DevMenu/ui/TimeSkip"; import { TimeSkip } from "./DevMenu/ui/TimeSkip";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
@ -52,6 +54,7 @@ export function DevMenuRoot(props: IProps): React.ReactElement {
{props.player.hasWseAccount && <StockMarket />} {props.player.hasWseAccount && <StockMarket />}
{props.player.sleeves.length > 0 && <Sleeves player={props.player} />} {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} /> <TimeSkip player={props.player} engine={props.engine} />
</> </>

85
src/DevMenu/ui/Stanek.tsx Normal file

@ -0,0 +1,85 @@
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.avgCharge = 1e21;
f.numCharge = 1e21;
});
}
function modCharge(modify: number): (x: number) => void {
return function (cycles: number): void {
staneksGift.fragments.forEach((f) => (f.avgCharge += cycles * modify));
};
}
function resetCharge(): void {
staneksGift.fragments.forEach((f) => {
f.avgCharge = 0;
f.numCharge = 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, true,
false, 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,
),
}; };

@ -17,6 +17,9 @@ import Button from "@mui/material/Button";
import { Location } from "../Location"; import { Location } from "../Location";
import { CreateCorporationModal } from "../../Corporation/ui/CreateCorporationModal"; import { CreateCorporationModal } from "../../Corporation/ui/CreateCorporationModal";
import { LocationName } from "../data/LocationNames"; 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"; import { use } from "../../ui/Context";
@ -24,6 +27,7 @@ import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { SnackbarEvents } from "../../ui/React/Snackbar"; import { SnackbarEvents } from "../../ui/React/Snackbar";
import { N00dles } from "../../utils/helpers/N00dles"; import { N00dles } from "../../utils/helpers/N00dles";
import { Exploit } from "../../Exploits/Exploit"; import { Exploit } from "../../Exploits/Exploit";
import { applyAugmentation } from "../../Augmentation/AugmentationHelpers";
type IProps = { type IProps = {
loc: Location; loc: Location;
@ -119,46 +123,130 @@ export function SpecialLocation(props: IProps): React.ReactElement {
return <Button onClick={handleResleeving}>Re-Sleeve</Button>; 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)
) {
applyAugmentation({ name: AugmentationNames.StaneksGift1, level: 1 });
}
router.toFaction(faction);
}
function renderCotMG(): React.ReactElement { function renderCotMG(): React.ReactElement {
// prettier-ignore // prettier-ignore
const symbol = <Typography sx={{ lineHeight: '1em', whiteSpace: 'pre' }}> const symbol = <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}>
{" `` "}<br /> {" `` "}<br />
{" -odmmNmds: "}<br /> {" -odmmNmds: "}<br />
{" `hNmo:..-omNh. "}<br /> {" `hNmo:..-omNh. "}<br />
{" yMd` `hNh "}<br /> {" yMd` `hNh "}<br />
{" mMd oNm "}<br /> {" mMd oNm "}<br />
{" oMNo .mM/ "}<br /> {" oMNo .mM/ "}<br />
{" `dMN+ -mM+ "}<br /> {" `dMN+ -mM+ "}<br />
{" -mMNo -mN+ "}<br /> {" -mMNo -mN+ "}<br />
{" .+- :mMNo/mN/ "}<br /> {" .+- :mMNo/mN/ "}<br />
{":yNMd. :NMNNN/ "}<br /> {":yNMd. :NMNNN/ "}<br />
{"-mMMMh. /NMMh` "}<br /> {"-mMMMh. /NMMh` "}<br />
{" .dMMMd. /NMMMy` "}<br /> {" .dMMMd. /NMMMy` "}<br />
{" `yMMMd. /NNyNMMh` "}<br /> {" `yMMMd. /NNyNMMh` "}<br />
{" `sMMMd. +Nm: +NMMh. "}<br /> {" `sMMMd. +Nm: +NMMh. "}<br />
{" oMMMm- oNm: /NMMd. "}<br /> {" oMMMm- oNm: /NMMd. "}<br />
{" +NMMmsMm- :mMMd. "}<br /> {" +NMMmsMm- :mMMd. "}<br />
{" /NMMMm- -mMMd. "}<br /> {" /NMMMm- -mMMd. "}<br />
{" /MMMm- -mMMd. "}<br /> {" /MMMm- -mMMd. "}<br />
{" `sMNMMm- .mMmo "}<br /> {" `sMNMMm- .mMmo "}<br />
{" `sMd:hMMm. ./. "}<br /> {" `sMd:hMMm. ./. "}<br />
{" `yMy` `yNMd` "}<br /> {" `yMy` `yNMd` "}<br />
{" `hMs` oMMy "}<br /> {" `hMs` oMMy "}<br />
{" `hMh sMN- "}<br /> {" `hMh sMN- "}<br />
{" /MM- .NMo "}<br /> {" /MM- .NMo "}<br />
{" +MM: :MM+ "}<br /> {" +MM: :MM+ "}<br />
{" sNNo-.`.-omNy` "}<br /> {" sNNo-.`.-omNy` "}<br />
{" -smNNNNmdo- "}<br /> {" -smNNNNmdo- "}<br />
{" `..` "}</Typography> {" `..` "}</Typography>
if (player.hasAugmentation(AugmentationNames.StaneksGift3, true)) {
return (
<>
<Typography>
<i>
Allison "Mother" Stanek: ..can ...you hear them too ...? Come now, don't be shy and let me get a closer
look at you. Yes wonderful, I see my creation has taken root without consquence or much ill effect it
seems. Curious, Just how much of a machine's soul do you house in that body?
</i>
</Typography>
{symbol}
</>
);
}
if (player.hasAugmentation(AugmentationNames.StaneksGift2, true)) {
return (
<>
<Typography>
<i>
Allison "Mother" Stanek: I see you've taken to my creation. So much so it could hardly be recoginized as
one of my own after your tinkering with it. I see you follow the ways of the MachineGod as I do, and your
mastery of the gift thus for clearly demonstrates that. My hopes are climbing by the day for you.
</i>
</Typography>
{symbol}
</>
);
}
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 ( return (
<> <>
<Typography> <Typography>
A decrepit altar stands in the middle of a dilapidated church. <i>
<br /> Allison "Mother" Stanek: Welcome child, I see your body is pure. Are you ready to ascend beyond our human
<br />A symbol is carved in the altar. form? If you are, accept my gift.
</i>
</Typography> </Typography>
<br /> <Button onClick={handleCotMG}>Accept Stanek's Gift</Button>
{symbol} {symbol}
</> </>
); );

@ -55,6 +55,15 @@ export const RamCostConstants: IMap<number> = {
ScriptGangApiBaseRamCost: 4, ScriptGangApiBaseRamCost: 4,
ScriptBladeburnerApiBaseRamCost: 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> = { export const RamCosts: IMap<any> = {
@ -326,6 +335,17 @@ export const RamCosts: IMap<any> = {
purchaseSleeveAug: RamCostConstants.ScriptSleeveBaseRamCost, purchaseSleeveAug: RamCostConstants.ScriptSleeveBaseRamCost,
}, },
stanek: {
charge: RamCostConstants.ScriptStanekCharge,
fragmentDefinitions: RamCostConstants.ScriptStanekFragmentDefinitions,
activeFragments: RamCostConstants.ScriptStanekPlacedFragments,
clear: RamCostConstants.ScriptStanekClear,
canPlace: RamCostConstants.ScriptStanekCanPlace,
place: RamCostConstants.ScriptStanekPlace,
get: RamCostConstants.ScriptStanekFragmentAt,
remove: RamCostConstants.ScriptStanekDeleteAt,
},
heart: { heart: {
// Easter egg function // Easter egg function
break: 0, break: 0,

@ -63,14 +63,24 @@ import { NetscriptGang } from "./NetscriptFunctions/Gang";
import { NetscriptSleeve } from "./NetscriptFunctions/Sleeve"; import { NetscriptSleeve } from "./NetscriptFunctions/Sleeve";
import { NetscriptExtra } from "./NetscriptFunctions/Extra"; import { NetscriptExtra } from "./NetscriptFunctions/Extra";
import { NetscriptHacknet } from "./NetscriptFunctions/Hacknet"; import { NetscriptHacknet } from "./NetscriptFunctions/Hacknet";
import { NS as INS, Player as INetscriptPlayer, SourceFileLvl } from "./ScriptEditor/NetscriptDefinitions"; import { NetscriptStanek } from "./NetscriptFunctions/Stanek";
import { NetscriptBladeburner } from "./NetscriptFunctions/Bladeburner"; import { NetscriptBladeburner } from "./NetscriptFunctions/Bladeburner";
import { NetscriptCodingContract } from "./NetscriptFunctions/CodingContract"; import { NetscriptCodingContract } from "./NetscriptFunctions/CodingContract";
import { NetscriptCorporation } from "./NetscriptFunctions/Corporation"; import { NetscriptCorporation } from "./NetscriptFunctions/Corporation";
import { NetscriptFormulas } from "./NetscriptFunctions/Formulas"; import { NetscriptFormulas } from "./NetscriptFunctions/Formulas";
import { NetscriptSingularity } from "./NetscriptFunctions/Singularity";
import { NetscriptStockMarket } from "./NetscriptFunctions/StockMarket"; import { NetscriptStockMarket } from "./NetscriptFunctions/StockMarket";
import {
NS as INS,
Player as INetscriptPlayer,
Gang as IGang,
Bladeburner as IBladeburner,
Stanek as IStanek,
SourceFileLvl,
} from "./ScriptEditor/NetscriptDefinitions";
import { NetscriptSingularity } from "./NetscriptFunctions/Singularity";
import { toNative } from "./NetscriptFunctions/toNative"; import { toNative } from "./NetscriptFunctions/toNative";
import { dialogBoxCreate } from "./ui/React/DialogBox"; import { dialogBoxCreate } from "./ui/React/DialogBox";
@ -80,6 +90,9 @@ import { Flags } from "./NetscriptFunctions/Flags";
interface NS extends INS { interface NS extends INS {
[key: string]: any; [key: string]: any;
gang: IGang;
bladeburner: IBladeburner;
stanek: IStanek;
} }
export function NetscriptFunctions(workerScript: WorkerScript): NS { export function NetscriptFunctions(workerScript: WorkerScript): NS {
@ -431,6 +444,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
const sleeve = NetscriptSleeve(Player, workerScript, helper); const sleeve = NetscriptSleeve(Player, workerScript, helper);
const extra = NetscriptExtra(Player, workerScript); const extra = NetscriptExtra(Player, workerScript);
const hacknet = NetscriptHacknet(Player, workerScript, helper); const hacknet = NetscriptHacknet(Player, workerScript, helper);
const stanek = NetscriptStanek(Player, workerScript, helper);
const bladeburner = NetscriptBladeburner(Player, workerScript, helper); const bladeburner = NetscriptBladeburner(Player, workerScript, helper);
const codingcontract = NetscriptCodingContract(Player, workerScript, helper); const codingcontract = NetscriptCodingContract(Player, workerScript, helper);
const corporation = NetscriptCorporation(Player); const corporation = NetscriptCorporation(Player);
@ -445,6 +459,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
bladeburner: bladeburner, bladeburner: bladeburner,
codingcontract: codingcontract, codingcontract: codingcontract,
sleeve: sleeve, sleeve: sleeve,
stanek: stanek,
formulas: formulas, formulas: formulas,
stock: stockmarket, stock: stockmarket,
@ -2255,7 +2270,6 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
Object.assign(data.jobs, Player.jobs); Object.assign(data.jobs, Player.jobs);
return data; return data;
}, },
atExit: function (f: any): void { atExit: function (f: any): void {
if (typeof f !== "function") { if (typeof f !== "function") {
throw makeRuntimeErrorMsg("atExit", "argument should be function"); throw makeRuntimeErrorMsg("atExit", "argument should be function");

@ -0,0 +1,109 @@
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";
import {
Stanek as IStanek,
Fragment as IFragment,
ActiveFragment as IActiveFragment,
} from "../ScriptEditor/NetscriptDefinitions";
import { AugmentationNames } from "../Augmentation/data/AugmentationNames";
export function NetscriptStanek(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): IStanek {
function checkStanekAPIAccess(func: string): void {
if (!player.hasAugmentation(AugmentationNames.StaneksGift1, true)) {
helper.makeRuntimeErrorMsg(func, "Requires Stanek's Gift installed.");
}
}
return {
width: function (): number {
return staneksGift.width();
},
height: function (): number {
return staneksGift.height();
},
charge: function (arootX: any, arootY: any): Promise<void> {
const rootX = helper.number("stanek.charge", "rootX", arootX);
const rootY = helper.number("stanek.charge", "rootY", arootY);
helper.updateDynamicRam("charge", getRamCost("stanek", "charge"));
checkStanekAPIAccess("charge");
const fragment = staneksGift.findFragment(rootX, rootY);
if (!fragment) throw helper.makeRuntimeErrorMsg("stanek.charge", `No fragment with root (${rootX}, ${rootY}).`);
const time = staneksGift.inBonus() ? 200 : 1000;
return netscriptDelay(time, workerScript).then(function () {
if (workerScript.env.stopFlag) {
return Promise.reject(workerScript);
}
const charge = staneksGift.charge(player, fragment, workerScript.scriptRef.threads);
workerScript.log("stanek.charge", () => `Charged fragment for ${charge} charge.`);
return Promise.resolve();
});
},
fragmentDefinitions: function (): IFragment[] {
helper.updateDynamicRam("fragmentDefinitions", getRamCost("stanek", "fragmentDefinitions"));
checkStanekAPIAccess("fragmentDefinitions");
workerScript.log("stanek.fragmentDefinitions", () => `Returned ${Fragments.length} fragments`);
return Fragments.map((f) => f.copy());
},
activeFragments: function (): IActiveFragment[] {
helper.updateDynamicRam("activeFragments", getRamCost("stanek", "activeFragments"));
checkStanekAPIAccess("activeFragments");
workerScript.log("stanek.activeFragments", () => `Returned ${staneksGift.fragments.length} fragments`);
return staneksGift.fragments.map((af) => {
return { ...af.copy(), ...af.fragment().copy() };
});
},
clear: function (): void {
helper.updateDynamicRam("clear", getRamCost("stanek", "clear"));
checkStanekAPIAccess("clear");
workerScript.log("stanek.clear", () => `Cleared Stanek's Gift.`);
staneksGift.clear();
},
canPlace: function (arootX: any, arootY: any, arotation: any, afragmentId: any): boolean {
const rootX = helper.number("stanek.canPlace", "rootX", arootX);
const rootY = helper.number("stanek.canPlace", "rootY", arootY);
const rotation = helper.number("stanek.canPlace", "rotation", arotation);
const fragmentId = helper.number("stanek.canPlace", "fragmentId", afragmentId);
helper.updateDynamicRam("canPlace", getRamCost("stanek", "canPlace"));
checkStanekAPIAccess("canPlace");
const fragment = FragmentById(fragmentId);
if (!fragment) throw helper.makeRuntimeErrorMsg("stanek.canPlace", `Invalid fragment id: ${fragmentId}`);
const can = staneksGift.canPlace(rootX, rootY, rotation, fragment);
return can;
},
place: function (arootX: any, arootY: any, arotation: any, afragmentId: any): boolean {
const rootX = helper.number("stanek.place", "rootX", arootX);
const rootY = helper.number("stanek.place", "rootY", arootY);
const rotation = helper.number("stanek.place", "rotation", arotation);
const fragmentId = helper.number("stanek.place", "fragmentId", afragmentId);
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(rootX, rootY, rotation, fragment);
},
get: function (arootX: any, arootY: any): IActiveFragment | undefined {
const rootX = helper.number("stanek.get", "rootX", arootX);
const rootY = helper.number("stanek.get", "rootY", arootY);
helper.updateDynamicRam("get", getRamCost("stanek", "get"));
checkStanekAPIAccess("get");
const fragment = staneksGift.findFragment(rootX, rootY);
if (fragment !== undefined) return fragment.copy();
return undefined;
},
remove: function (arootX: any, arootY: any): boolean {
const rootX = helper.number("stanek.remove", "rootX", arootX);
const rootY = helper.number("stanek.remove", "rootY", arootY);
helper.updateDynamicRam("remove", getRamCost("stanek", "remove"));
checkStanekAPIAccess("remove");
return staneksGift.delete(rootX, rootY);
},
};
}

@ -115,6 +115,7 @@ function startNetscript2Script(workerScript: WorkerScript): Promise<WorkerScript
if (typeof workerScript.env.vars[prop] !== "function") continue; if (typeof workerScript.env.vars[prop] !== "function") continue;
workerScript.env.vars[prop] = wrap(prop, workerScript.env.vars[prop]); 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 // 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. // to that script, which env.vars does at this point.

@ -277,5 +277,6 @@ export interface IPlayer {
setBitNodeNumber(n: number): void; setBitNodeNumber(n: number): void;
getMult(name: string): number; getMult(name: string): number;
setMult(name: string, mult: number): void; setMult(name: string, mult: number): void;
canAccessCotMG(): boolean;
sourceFileLvl(n: number): number; sourceFileLvl(n: number): number;
} }

@ -283,6 +283,7 @@ export class PlayerObject implements IPlayer {
setBitNodeNumber: (n: number) => void; setBitNodeNumber: (n: number) => void;
getMult: (name: string) => number; getMult: (name: string) => number;
setMult: (name: string, mult: number) => void; setMult: (name: string, mult: number) => void;
canAccessCotMG: () => boolean;
sourceFileLvl: (n: number) => number; sourceFileLvl: (n: number) => number;
constructor() { constructor() {
@ -579,6 +580,8 @@ export class PlayerObject implements IPlayer {
this.getMult = generalMethods.getMult; this.getMult = generalMethods.getMult;
this.setMult = generalMethods.setMult; this.setMult = generalMethods.setMult;
this.canAccessCotMG = generalMethods.canAccessCotMG;
this.sourceFileLvl = generalMethods.sourceFileLvl; this.sourceFileLvl = generalMethods.sourceFileLvl;
} }

@ -105,7 +105,14 @@ export function generateResleeves(): Resleeve[] {
const randKey: string = augKeys[randIndex]; const randKey: string = augKeys[randIndex];
// Forbidden augmentations // Forbidden augmentations
if (randKey === AugmentationNames.TheRedPill || randKey === AugmentationNames.NeuroFluxGovernor) { const forbidden = [
AugmentationNames.TheRedPill,
AugmentationNames.NeuroFluxGovernor,
AugmentationNames.StaneksGift1,
AugmentationNames.StaneksGift2,
AugmentationNames.StaneksGift3,
];
if (forbidden.includes(randKey)) {
continue; continue;
} }

@ -26,8 +26,10 @@ import { Terminal } from "./Terminal";
import { dialogBoxCreate } from "./ui/React/DialogBox"; import { dialogBoxCreate } from "./ui/React/DialogBox";
import { staneksGift } from "./CotMG/Helper";
import { ProgramsSeen } from "./Programs/ui/ProgramsRoot"; import { ProgramsSeen } from "./Programs/ui/ProgramsRoot";
import { InvitationsSeen } from "./Faction/ui/FactionsRoot"; import { InvitationsSeen } from "./Faction/ui/FactionsRoot";
import { CONSTANTS } from "./Constants";
import { LogBoxClearEvents } from "./ui/React/LogBoxManager"; import { LogBoxClearEvents } from "./ui/React/LogBoxManager";
const BitNode8StartingMoney = 250e6; const BitNode8StartingMoney = 250e6;
@ -143,6 +145,14 @@ export 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"]);
}
staneksGift.prestigeAugmentation();
resetPidCounter(); resetPidCounter();
ProgramsSeen.splice(0, ProgramsSeen.length); ProgramsSeen.splice(0, ProgramsSeen.length);
InvitationsSeen.splice(0, InvitationsSeen.length); InvitationsSeen.splice(0, InvitationsSeen.length);
@ -247,6 +257,10 @@ export function prestigeSourceFile(flume: boolean): void {
dialogBoxCreate("Visit VitaLife in New Tokyo if you'd like to purchase a new sleeve!"); 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 // Reset Stock market, gang, and corporation
if (Player.hasWseAccount) { if (Player.hasWseAccount) {
initStockMarket(); initStockMarket();
@ -272,6 +286,11 @@ export function prestigeSourceFile(flume: boolean): void {
updateHashManagerCapacity(Player); updateHashManagerCapacity(Player);
} }
if (Player.bitNodeN === 13) {
Player.money = CONSTANTS.TravelCost;
}
staneksGift.prestigeSourceFile();
// Gain int exp // Gain int exp
if (SourceFileFlags[5] !== 0 && !flume) Player.gainIntelligenceExp(300); if (SourceFileFlags[5] !== 0 && !flume) Player.gainIntelligenceExp(300);

@ -9,6 +9,7 @@ import { saveAllServers, loadAllServers, GetAllServers } from "./Server/AllServe
import { Settings } from "./Settings/Settings"; import { Settings } from "./Settings/Settings";
import { SourceFileFlags } from "./SourceFile/SourceFileFlags"; import { SourceFileFlags } from "./SourceFile/SourceFileFlags";
import { loadStockMarket, StockMarket } from "./StockMarket/StockMarket"; import { loadStockMarket, StockMarket } from "./StockMarket/StockMarket";
import { staneksGift, loadStaneksGift } from "./CotMG/Helper";
import { SnackbarEvents } from "./ui/React/Snackbar"; import { SnackbarEvents } from "./ui/React/Snackbar";
@ -38,6 +39,7 @@ class BitburnerSaveObject {
VersionSave = ""; VersionSave = "";
AllGangsSave = ""; AllGangsSave = "";
LastExportBonus = ""; LastExportBonus = "";
StaneksGiftSave = "";
getSaveString(): string { getSaveString(): string {
this.PlayerSave = JSON.stringify(Player); this.PlayerSave = JSON.stringify(Player);
@ -52,6 +54,7 @@ class BitburnerSaveObject {
this.SettingsSave = JSON.stringify(Settings); this.SettingsSave = JSON.stringify(Settings);
this.VersionSave = JSON.stringify(CONSTANTS.VersionNumber); this.VersionSave = JSON.stringify(CONSTANTS.VersionNumber);
this.LastExportBonus = JSON.stringify(ExportBonus.LastExportBonus); this.LastExportBonus = JSON.stringify(ExportBonus.LastExportBonus);
this.StaneksGiftSave = JSON.stringify(staneksGift);
if (Player.inGang()) { if (Player.inGang()) {
this.AllGangsSave = JSON.stringify(AllGangs); this.AllGangsSave = JSON.stringify(AllGangs);
} }
@ -240,6 +243,12 @@ function loadGame(saveString: string): boolean {
loadCompanies(saveObj.CompaniesSave); loadCompanies(saveObj.CompaniesSave);
loadFactions(saveObj.FactionsSave); 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")) { if (saveObj.hasOwnProperty("AliasesSave")) {
try { try {
loadAliases(saveObj.AliasesSave); loadAliases(saveObj.AliasesSave);

@ -183,6 +183,8 @@ async function parseOnlyRamCalculate(
func = workerScript.env.vars.bladeburner[ref]; func = workerScript.env.vars.bladeburner[ref];
} else if (ref in workerScript.env.vars.codingcontract) { } else if (ref in workerScript.env.vars.codingcontract) {
func = workerScript.env.vars.codingcontract[ref]; 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) { } else if (ref in workerScript.env.vars.gang) {
func = workerScript.env.vars.gang[ref]; func = workerScript.env.vars.gang[ref];
} else if (ref in workerScript.env.vars.sleeve) { } else if (ref in workerScript.env.vars.sleeve) {

@ -3325,6 +3325,125 @@ export interface Formulas {
hacknetServers: HacknetServersFormulas; hacknetServers: HacknetServersFormulas;
} }
export interface Fragment {
id: number;
shape: boolean[][];
type: number;
power: number;
limit: number;
}
export interface ActiveFragment {
id: number;
avgCharge: number;
numCharge: number;
rotation: number;
x: number;
y: number;
}
/**
* Stanek's Gift API.
* @public
*/
interface Stanek {
/**
* Stanek's Gift width.
* @remarks
* RAM cost: 0.4 GB
* @returns The width of the gift.
*/
width(): number;
/**
* Stanek's Gift height.
* @remarks
* RAM cost: 0.4 GB
* @returns The height of the gift.
*/
height(): number;
/**
* Charge a fragment, increasing it's power.
* @remarks
* RAM cost: 0.4 GB
* @param rootX - rootX Root X against which to align the top left of the fragment.
* @param rootY - rootY Root Y against which to align the top left of the fragment.
* @returns Promise that lasts until the charge action is over.
*/
charge(rootX: number, rootY: number): Promise<void>;
/**
* List possible fragments.
* @remarks
* RAM cost: cost: 0 GB
*
* @returns List of possible fragments.
*/
fragmentDefinitions(): Fragment[];
/**
* List of fragments in Stanek's Gift.
* @remarks
* RAM cost: cost: 5 GB
*
* @returns List of active fragments placed on Stanek's Gift.
*/
activeFragments(): ActiveFragment[];
/**
* Clear the board of all fragments.
* @remarks
* RAM cost: cost: 0 GB
*/
clear(): void;
/**
* Check if fragment can be placed at specified location.
* @remarks
* RAM cost: cost: 0.5 GB
*
* @param rootX - rootX Root X against which to align the top left of the fragment.
* @param rootY - rootY Root Y against which to align the top left of the fragment.
* @param rotation - rotation A number from 0 to 3, the mount of 90 degree turn to take.
* @param fragmentId - fragmentId ID of the fragment to place.
* @returns true if the fragment can be placed at that position. false otherwise.
*/
canPlace(rootX: number, rootY: number, rotation: number, fragmentId: number): boolean;
/**
* Place fragment on Stanek's Gift.
* @remarks
* RAM cost: cost: 5 GB
*
* @param rootX - X against which to align the top left of the fragment.
* @param rootY - Y against which to align the top left of the fragment.
* @param rotation - A number from 0 to 3, the mount of 90 degree turn to take.
* @param fragmentId - ID of the fragment to place.
* @returns true if the fragment can be placed at that position. false otherwise.
*/
place(rootX: number, rootY: number, rotation: number, fragmentId: number): boolean;
/**
* Get placed fragment at location.
* @remarks
* RAM cost: cost: 5 GB
*
* @param rootX - X against which to align the top left of the fragment.
* @param rootY - Y against which to align the top left of the fragment.
* @returns The fragment at [rootX, rootY], if any.
*/
get(rootX: number, rootY: number): ActiveFragment | undefined;
/**
* Remove fragment at location.
* @remarks
* RAM cost: cost: 0.15 GB
*
* @param rootX - X against which to align the top left of the fragment.
* @param rootY - Y against which to align the top left of the fragment.
* @returns The fragment at [rootX, rootY], if any.
*/
remove(rootX: number, rootY: number): boolean;
}
/** /**
* Collection of all functions passed to scripts * Collection of all functions passed to scripts
* @public * @public
@ -3374,6 +3493,11 @@ export interface NS extends Singularity {
* RAM cost: 0 GB * RAM cost: 0 GB
*/ */
readonly formulas: Formulas; readonly formulas: Formulas;
/**
* Namespace for stanek functions.
* @ramCost 0 GB
*/
readonly stanek: Stanek;
/** /**
* Arguments passed into the script. * Arguments passed into the script.

@ -9,6 +9,8 @@ import { createRandomIp } from "../utils/IPAddress";
import { getRandomInt } from "../utils/helpers/getRandomInt"; import { getRandomInt } from "../utils/helpers/getRandomInt";
import { Reviver } from "../utils/JSONReviver"; import { Reviver } from "../utils/JSONReviver";
import { isValidIPAddress } from "../utils/helpers/isValidIPAddress"; import { isValidIPAddress } from "../utils/helpers/isValidIPAddress";
import { SpecialServers } from "./data/SpecialServers";
import { BitNodeMultipliers } from "../BitNode/BitNodeMultipliers";
/** /**
* Map of all Servers that exist in the game * Map of all Servers that exist in the game
@ -161,6 +163,9 @@ export function initForeignServers(homeComputer: Server): void {
server.messages.push(filename); server.messages.push(filename);
} }
if (server.hostname === SpecialServers.WorldDaemon) {
server.requiredHackingSkill *= BitNodeMultipliers.WorldDaemonDifficulty;
}
AddToAllServers(server); AddToAllServers(server);
if (metadata.networkLayer !== undefined) { if (metadata.networkLayer !== undefined) {
networkLayers[toNumber(metadata.networkLayer) - 1].push(server); networkLayers[toNumber(metadata.networkLayer) - 1].push(server);

@ -49,6 +49,7 @@ import { iTutorialSteps, iTutorialNextStep, ITutorial } from "../../InteractiveT
import { getAvailableCreatePrograms } from "../../Programs/ProgramHelpers"; import { getAvailableCreatePrograms } from "../../Programs/ProgramHelpers";
import { Settings } from "../../Settings/Settings"; import { Settings } from "../../Settings/Settings";
import { redPillFlag } from "../../RedPill"; import { redPillFlag } from "../../RedPill";
import { AugmentationNames } from "../../Augmentation/data/AugmentationNames";
import { KEY } from "../../utils/helpers/keyCodes"; import { KEY } from "../../utils/helpers/keyCodes";
import { ProgramsSeen } from "../../Programs/ui/ProgramsRoot"; import { ProgramsSeen } from "../../Programs/ui/ProgramsRoot";
@ -157,13 +158,12 @@ export function SidebarRoot(props: IProps): React.ReactElement {
const canOpenSleeves = props.player.sleeves.length > 0; 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 canCorporation = !!(props.player.corporation as any);
const canGang = !!(props.player.gang as any); const canGang = !!(props.player.gang as any);
const canJob = props.player.companyName !== ""; const canJob = props.player.companyName !== "";
const canStockMarket = props.player.hasWseAccount; const canStockMarket = props.player.hasWseAccount;
const canBladeburner = !!(props.player.bladeburner as any); const canBladeburner = !!(props.player.bladeburner as any);
const canStaneksGift = props.player.augmentations.some((aug) => aug.name === AugmentationNames.StaneksGift1);
function clickTerminal(): void { function clickTerminal(): void {
props.router.toTerminal(); props.router.toTerminal();
@ -188,6 +188,10 @@ export function SidebarRoot(props: IProps): React.ReactElement {
props.router.toCreateProgram(); props.router.toCreateProgram();
} }
function clickStaneksGift(): void {
props.router.toStaneksGift();
}
function clickFactions(): void { function clickFactions(): void {
props.router.toFactions(); props.router.toFactions();
} }
@ -427,6 +431,25 @@ export function SidebarRoot(props: IProps): React.ReactElement {
</ListItemText> </ListItemText>
</ListItem> </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> </List>
</Collapse> </Collapse>

@ -204,3 +204,7 @@ SourceFiles["SourceFile12"] = new SourceFile(
12, 12,
<>This Source-File lets the player start with Neuroflux Governor equal to the level of this Source-File.</>, <>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; break;
} }
case 12: // The Recursion case 12: // The Recursion
// No effects, grants neuroflux. // Grants neuroflux.
break;
case 13: // They're Lunatics
// Grants more space on Stanek's Gift.
break; break;
default: default:
console.error(`Invalid source file number: ${srcFile.n}`); console.error(`Invalid source file number: ${srcFile.n}`);

@ -12,6 +12,7 @@ import { initCompanies } from "./Company/Companies";
import { Corporation } from "./Corporation/Corporation"; import { Corporation } from "./Corporation/Corporation";
import { CONSTANTS } from "./Constants"; import { CONSTANTS } from "./Constants";
import { Factions, initFactions } from "./Faction/Factions"; import { Factions, initFactions } from "./Faction/Factions";
import { staneksGift } from "./CotMG/Helper";
import { processPassiveFactionRepGain, inviteToFaction } from "./Faction/FactionHelpers"; import { processPassiveFactionRepGain, inviteToFaction } from "./Faction/FactionHelpers";
import { Router } from "./ui/GameRoot"; import { Router } from "./ui/GameRoot";
import { SetupTextEditor } from "./ScriptEditor/ui/ScriptEditorRoot"; import { SetupTextEditor } from "./ScriptEditor/ui/ScriptEditorRoot";
@ -101,6 +102,9 @@ const Engine: {
Player.gang.process(numCycles, Player); Player.gang.process(numCycles, Player);
} }
// Staneks gift
staneksGift.process(Player, numCycles);
// Corporation // Corporation
if (Player.corporation instanceof Corporation) { if (Player.corporation instanceof Corporation) {
// Stores cycles in a "buffer". Processed separately using Engine Counters // Stores cycles in a "buffer". Processed separately using Engine Counters
@ -345,6 +349,8 @@ const Engine: {
Player.bladeburner.storeCycles(numCyclesOffline); Player.bladeburner.storeCycles(numCyclesOffline);
} }
staneksGift.process(Player, numCyclesOffline);
// Sleeves offline progress // Sleeves offline progress
for (let i = 0; i < Player.sleeves.length; ++i) { for (let i = 0; i < Player.sleeves.length; ++i) {
if (Player.sleeves[i] instanceof Sleeve) { if (Player.sleeves[i] instanceof Sleeve) {

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 { TravelAgencyRoot } from "../Locations/ui/TravelAgencyRoot";
import { StockMarketRoot } from "../StockMarket/ui/StockMarketRoot"; import { StockMarketRoot } from "../StockMarket/ui/StockMarketRoot";
import { BitverseRoot } from "../BitNode/ui/BitverseRoot"; import { BitverseRoot } from "../BitNode/ui/BitverseRoot";
import { StaneksGiftRoot } from "../CotMG/ui/StaneksGiftRoot";
import { staneksGift } from "../CotMG/Helper";
import { CharacterOverview } from "./React/CharacterOverview"; import { CharacterOverview } from "./React/CharacterOverview";
import { BladeburnerCinematic } from "../Bladeburner/ui/BladeburnerCinematic"; import { BladeburnerCinematic } from "../Bladeburner/ui/BladeburnerCinematic";
import { workerScripts } from "../Netscript/WorkerScripts"; import { workerScripts } from "../Netscript/WorkerScripts";
@ -181,6 +183,9 @@ export let Router: IRouter = {
toLocation: () => { toLocation: () => {
throw new Error("Router called before initialization"); throw new Error("Router called before initialization");
}, },
toStaneksGift: () => {
throw new Error("Router called before initialization");
},
}; };
function determineStartPage(player: IPlayer): Page { function determineStartPage(player: IPlayer): Page {
@ -279,6 +284,9 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
setLocation(location); setLocation(location);
setPage(Page.Location); setPage(Page.Location);
}, },
toStaneksGift: () => {
setPage(Page.StaneksGift);
},
}; };
useEffect(() => { useEffect(() => {
@ -316,6 +324,8 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
<TerminalRoot terminal={terminal} router={Router} player={player} /> <TerminalRoot terminal={terminal} router={Router} player={player} />
) : page === Page.Sleeves ? ( ) : page === Page.Sleeves ? (
<SleeveRoot /> <SleeveRoot />
) : page === Page.StaneksGift ? (
<StaneksGiftRoot staneksGift={staneksGift} />
) : page === Page.Stats ? ( ) : page === Page.Stats ? (
<CharacterStats /> <CharacterStats />
) : page === Page.ScriptEditor ? ( ) : page === Page.ScriptEditor ? (

@ -641,6 +641,9 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
<Link href="https://www.reddit.com/r/bitburner" target="_blank"> <Link href="https://www.reddit.com/r/bitburner" target="_blank">
<Typography>Reddit</Typography> <Typography>Reddit</Typography>
</Link> </Link>
<Link href="https://plaza.dsolver.ca/games/bitburner" target="_blank">
<Typography>Incremental game plaza</Typography>
</Link>
</Box> </Box>
</Grid> </Grid>
</Grid> </Grid>

@ -34,6 +34,7 @@ export enum Page {
BladeburnerCinematic, BladeburnerCinematic,
Location, Location,
Loading, Loading,
StaneksGift,
Recovery, Recovery,
} }
@ -74,4 +75,5 @@ export interface IRouter {
toWork(): void; toWork(): void;
toBladeburnerCinematic(): void; toBladeburnerCinematic(): void;
toLocation(location: Location): void; toLocation(location: Location): void;
toStaneksGift(): void;
} }

@ -169,6 +169,18 @@ class NumeralFormatter {
return this.format(n, "0,0"); 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 { parseMoney(s: string): number {
// numeral library does not handle formats like 1e10 well (returns 110), // numeral library does not handle formats like 1e10 well (returns 110),
// so if both return a valid number, return the biggest one // so if both return a valid number, return the biggest one