Added growthAnalyze() NS function. Fixed GH issue #492. In Gang mechanic, added new hacking augs/upgrades. Also rebalanced defense upgrades. Added new 'compact' main menu configuration

This commit is contained in:
danielyxie
2018-11-19 21:54:03 -08:00
parent 1a47e81001
commit 66d50a7ae4
13 changed files with 222 additions and 52 deletions

View File

@@ -39,6 +39,19 @@
padding: 16px;
}
.mainmenu.compact > li a,
.mainmenu.compact > li button {
display: block;
color: #e6e6e6;
background-color: #555;
text-decoration: none;
cursor: pointer;
width: 100%;
text-align: left;
padding: 4px;
}
/* Hovering makes them lighter */
.mainmenu > li a:hover,
.mainmenu > li a:hover:not(.active),
@@ -73,7 +86,8 @@
}
/* Accordion Outline */
.mainmenu-accordion-header {
.mainmenu-accordion-header,
.mainmenu-accordion-header-compact {
outline: 2px solid #fff !important;
}
@@ -83,7 +97,8 @@
}
/* Plus and minus signs */
.mainmenu-accordion-header:after {
.mainmenu-accordion-header:after,
.mainmenu-accordion-header-compact:after {
content: '\02795';
float: right;
font-size: $defaultFontSize * 0.8125;
@@ -103,8 +118,9 @@
}
.mainmenu-accordion-header.opened,
.mainmenu-accordion-header-classic.opened {
background-color: #222;
.mainmenu-accordion-header-classic.opened,
.mainmenu-accordion-header-compact.opened {
background-color: #222 !important;
&:after {
content: "\2796";

View File

@@ -56,7 +56,7 @@ weaken
.. js:function:: weaken(hostname/ip)
:param string hostname.ip: IP or hostname of the target server to weaken
:param string hostname/ip: IP or hostname of the target server to weaken
:returns: The amount by which the target server's security level was decreased. This is equivalent to 0.05 multiplied
by the number of script threads
:RAM cost: 0.15 GB
@@ -72,6 +72,31 @@ weaken
weaken("foodnstuff");
growthAnalyze
^^^^^^^^^^^^^
.. js:function:: growthAnalyze(hostname/ip, growthAmount)
:param string hostname/ip: IP or hostname of server to analyze
:param number growthAmount: Multiplicative factor by which the server is grown. Decimal form.
:returns: The amount of grow() calls needed to grow the specified server by the specified amount
:RAM cost: 1 GB
This function returns the number of "growths" needed in order to increase the amount
of money available on the specified server by the specified amount.
The specified amount is multiplicative and is in decimal form, not percentage.
For example, if you want to determine how many `grow()` calls you need
to double the amount of money on `foodnstuff`, you would use::
growthAnalyze("foodnstuff", 2);
If this returns 100, then this means you need to call `grow()` 100 times
in order to double the money (or once with 100 threads).
**Warning**: The value returned by this function isn't necessarily a whole number.
sleep
^^^^^

View File

@@ -187,6 +187,23 @@ getEquipmentCost
:returns: Cost to purchase the specified Equipment/Augmentation (number). Infinity
for invalid arguments
getEquipmentType
----------------
.. js:function:: getEquipmentType(equipName)
:param string equipName: Name of equipment
Get the specified equipment type, which can be one of the following:
* Weapon
* Armor
* Vehicle
* Rootkit
* Augmentation
:returns: A string stating the type of the equipment
purchaseEquipment
-----------------

View File

@@ -59,7 +59,8 @@ var TextHighlightRules = acequire("./text_highlight_rules").TextHighlightRules;
var identifierRe = "[a-zA-Z\\$_\u00a1-\uffff][a-zA-Z\\d\\$_\u00a1-\uffff]*";
let NetscriptFunctions =
"hack|sleep|grow|weaken|print|tprint|scan|nuke|brutessh|ftpcrack|" + //Netscript functions
"hack|sleep|grow|weaken|growthAnalyze|print|tprint|scan|nuke|brutessh|" +
"ftpcrack|" +
"clearLog|disableLog|enableLog|isLogEnabled|getScriptLogs|" +
"relaysmtp|httpworm|sqlinject|run|exec|spawn|kill|killall|exit|" +
"scp|ls|ps|hasRootAccess|" +
@@ -106,7 +107,8 @@ let NetscriptFunctions =
"gang|" +
"getMemberNames|getGangInformation|getMemberInformation|canRecruitMember|" +
"recruitMember|getTaskNames|setMemberTask|getEquipmentNames|" +
"getEquipmentCost|purchaseEquipment|ascendMember|setTerritoryWarfare|" +
"getEquipmentCost|getEquipmentType|purchaseEquipment|ascendMember|" +
"setTerritoryWarfare|" +
"getChanceToWinClash|getBonusTime|" +
// Bladeburner API

View File

@@ -34,7 +34,7 @@ CharacterOverview.prototype.update = function() {
let changed = false;
changed = replaceAndChanged(this.hp, Player.hp + " / " + Player.max_hp) || changed;
changed = replaceAndChanged(this.money, numeralWrapper.format(Player.money.toNumber(), '($0.000a)')) || changed;
changed = replaceAndChanged(this.money, numeralWrapper.format(Player.money.toNumber(), '$0.000a')) || changed;
changed = replaceAndChanged(this.hack, (Player.hacking_skill).toLocaleString()) || changed;
changed = replaceAndChanged(this.str, (Player.strength).toLocaleString()) || changed;
changed = replaceAndChanged(this.def, (Player.defense).toLocaleString()) || changed;

View File

@@ -56,6 +56,7 @@ export let CONSTANTS: IMap<any> = {
ScriptIfRamCost: 0,
ScriptHackRamCost: 0.1,
ScriptGrowRamCost: 0.15,
ScriptGrowthAnalyzeRamCost: 1,
ScriptWeakenRamCost: 0.15,
ScriptScanRamCost: 0.2,
ScriptPortProgramRamCost: 0.05,
@@ -507,17 +508,23 @@ export let CONSTANTS: IMap<any> = {
v0.41.2
* IMPORTANT - Netscript Changes:
** rm() now takes an optional parameter that lets you specify on which server to delete the file
** Added growthAnalyze() Netscript function
* Gang Changes:
** UI now displays your chance to win a clash with other gangs
** Added getChanceToWinClash() function to the Gang API
** Added several new hacking-based equipment and Augmentations
** Rebalanced several equipment/upgrades to give less defense
* Added new Main Menu configuration in .fconf: "compact"
* Added the terminal command 'expr', which can be used to evaluate simple mathematical expressions
* Bug Fix: scp() should no longer throw errors when used with 2-arguments and an array of files
* Bug Fix: Coding Contracts no longer give money in BitNode-8
* Bug Fix: In Bladeburner, you can no longer start a BlackOp through the Netscript API if it has already been completed
* Bug Fix: In Bladeburner, fixed a bug which caused the configured 'automate' actions to occasionally be switched to other actions
* Bug Fix: 'Return to World' button at locations no longer accumulates event listeners
* Bug Fix: Working & taking classes now continuously add/subtract money during the action, instead of doing it only at completion
* Bug Fix: Top-right overview panel now displays negative money using '-' instead of '()'
`
}

View File

@@ -385,14 +385,17 @@ function displayFactionContent(factionName) {
gangDiv.appendChild(gangDivWrapper);
gangDivWrapper.appendChild(createElement("a", {
class:"a-link-button", innerText:"Manage Gang",
clickListener:()=>{
clickListener: () => {
if (!Player.inGang()) {
// Determine whether this is a hacking gang
let hacking = false;
if (factionName === "NiteSec" || factionName === "The Black Hand") { hacking = true; }
// Configure Yes/No buttons for the pop-up
var yesBtn = yesNoBoxGetYesButton(), noBtn = yesNoBoxGetNoButton();
yesBtn.innerHTML = "Create Gang";
noBtn.innerHTML = "Cancel";
yesBtn.addEventListener("click", () => {
var hacking = false;
if (factionName === "NiteSec" || factionName === "The Black Hand") {hacking = true;}
Player.startGang(factionName, hacking);
document.getElementById("world-menu-header").click();
document.getElementById("world-menu-header").click();
@@ -402,10 +405,24 @@ function displayFactionContent(factionName) {
noBtn.addEventListener("click", () => {
yesNoBoxClose();
});
yesNoBoxCreate("Would you like to create a new Gang with " + factionName + "?<br><br>" +
// Pop-up text
let gangTypeText = "";
if (hacking) {
gangTypeText = "This is a HACKING gang. Members in this gang will have different tasks than COMBAT gangs. " +
"Compared to combat gangs, progression with hacking gangs is more straightforward as territory warfare " +
"is not as important.<br><br>";
} else {
gangTypeText = "This is a COMBAT gang. Members in this gang will have different tasks than HACKING gangs. " +
"Compared to hacking gangs, progression with combat gangs can be more difficult as territory management " +
"is more important. However, well-managed combat gangs can progress faster than hacking ones.<br><br>";
}
yesNoBoxCreate(`Would you like to create a new Gang with ${factionName}?<br><br>` +
"Note that this will prevent you from creating a Gang with any other Faction until " +
"this BitNode is destroyed. There are NO differences between the Factions you can " +
"create a Gang with and each of these Factions have all Augmentations available");
"this BitNode is destroyed.<br><br>" +
gangTypeText +
"Other than hacking vs combat, there are NO differences between the Factions you can " +
"create a Gang with, and each of these Factions have all Augmentations available.");
} else {
Engine.loadGangContent();
}

View File

@@ -23,7 +23,7 @@ var FconfComments = {
ENABLE_TIMESTAMPS: "Terminal commands and log entries will be timestamped. The timestamp\n" +
"will have the format: M/D h:m",
MAIN_MENU_STYLE: "Customize the main navigation menu on the left-hand side. Current options:\n\n" +
"default, classic",
"default, classic, compact",
THEME_BACKGROUND_COLOR: "Sets the background color for not only the Terminal, but also for\n" +
"most of the game's UI.\n\n" +
"The color must be specified as a pound sign (#) followed by a \n" +
@@ -46,7 +46,7 @@ var FconfComments = {
"before its effect takes place.",
}
const MainMenuStyleOptions = ["default", "classic"];
const MainMenuStyleOptions = ["default", "classic", "compact"];
//Parse Fconf settings from the config text
//Throws an exception if parsing fails
@@ -231,12 +231,18 @@ function setMainMenuStyle() {
if (FconfSettings["MAIN_MENU_STYLE"] === "default") {
removeAllAccordionHeaderClasses();
mainMenu.classList.remove("classic");
mainMenu.classList.remove("compact");
addClassToAllAccordionHeaders("mainmenu-accordion-header");
} else if (FconfSettings["MAIN_MENU_STYLE"] === "classic") {
removeAllAccordionHeaderClasses();
mainMenu.classList.remove("compact");
mainMenu.classList.add("classic");
addClassToAllAccordionHeaders("mainmenu-accordion-header-classic");
} else if (FconfSettings["MAIN_MENU_STYLE"] === "compact") {
removeAllAccordionHeaderClasses();
mainMenu.classList.remove("classic");
mainMenu.classList.add("compact");
addClassToAllAccordionHeaders("mainmenu-accordion-header-compact");
} else {
return;
}

View File

@@ -533,6 +533,27 @@ Gang.prototype.getUpgradeCost = function(upgName) {
return GangMemberUpgrades[upgName].getCost(this);
}
// Returns a player-friendly string stating the type of the specified upgrade
Gang.prototype.getUpgradeType = function(upgName) {
const upg = GangMemberUpgrades[upgName];
if (upg == null) { return ""; }
switch (upg.type) {
case "w":
return "Weapon";
case "a":
return "Armor";
case "v":
return "Vehicle";
case "r":
return "Rootkit";
case "g":
return "Augmentation";
default:
return "";
}
}
Gang.prototype.toJSON = function() {
return Generic_toJSON("Gang", this);
}
@@ -1858,6 +1879,25 @@ Gang.prototype.updateGangMemberDisplayElement = function(memberObj) {
`Wanted Level: ${formatNumber(5*memberObj.calculateWantedLevelGain(this), 6)} / sec`,
`Total Respect Earned: ${formatNumber(memberObj.earnedRespect, 6)}`].join("<br>");
}
// Update selector to have the correct task
const taskSelector = document.getElementById(name + "gang-member-task-selector");
if (taskSelector) {
let tasks = this.getAllTaskNames();
tasks.unshift("---");
if (GangMemberTasks.hasOwnProperty(memberObj.task)) {
const taskName = memberObj.task;
let taskIndex = 0;
for (let i = 0; i < tasks.length; ++i) {
if (taskName === tasks[i]) {
taskIndex = i;
break;
}
}
taskSelector.selectedIndex = taskIndex;
}
}
}
Gang.prototype.setGangMemberTaskDescription = function(memberObj, taskName) {

View File

@@ -33,7 +33,7 @@ import {Script, findRunningScript, RunningScript,
isScriptFilename} from "./Script";
import {Server, getServer, AddToAllServers,
AllServers, processSingleServerGrowth,
GetServerByHostname} from "./Server";
GetServerByHostname, numCycleForGrowth} from "./Server";
import {Settings} from "./Settings";
import {SpecialServerIps} from "./SpecialServerIps";
import {Stock} from "./Stock";
@@ -463,7 +463,21 @@ function NetscriptFunctions(workerScript) {
return Promise.resolve(moneyAfter/moneyBefore);
});
},
weaken : function(ip){
growthAnalyze : function(ip, growth) {
if (workerScript.checkingRam) {
return updateStaticRam("growthAnalyze", CONSTANTS.ScriptGrowthAnalyzeRamCost);
}
updateDynamicRam("growthAnalyze", CONSTANTS.ScriptGrowthAnalyzeRamCost);
// Check argument validity
const server = safeGetServer(ip, 'growthAnalyze');
if (isNaN(growth)) {
throw makeRuntimeRejectMsg(workerScript, `Invalid growth argument passed into growthAnalyze: ${growth}. Must be numeric`);
}
return numCycleForGrowth(server, Number(growth));
},
weaken : function(ip) {
if (workerScript.checkingRam) {
return updateStaticRam("weaken", CONSTANTS.ScriptWeakenRamCost);
}
@@ -3869,6 +3883,19 @@ function NetscriptFunctions(workerScript) {
throw makeRuntimeRejectMsg(workerScript, nsGang.unknownGangApiExceptionMessage("getEquipmentCost", e));
}
},
getEquipmentType : function(equipName) {
if (workerScript.checkingRam) {
return updateStaticRam("getEquipmentType", CONSTANTS.ScriptGangApiBaseRamCost / 2);
}
updateDynamicRam("getEquipmentType", CONSTANTS.ScriptGangApiBaseRamCost / 2);
nsGang.checkGangApiAccess(workerScript, "getEquipmentType");
try {
return Player.gang.getUpgradeType(equipName);
} catch(e) {
throw makeRuntimeRejectMsg(workerScript, nsGang.unknownGangApiExceptionMessage("getEquipmentType", e));
}
},
purchaseEquipment : function(memberName, equipName) {
if (workerScript.checkingRam) {
return updateStaticRam("purchaseEquipment", CONSTANTS.ScriptGangApiBaseRamCost);

View File

@@ -639,12 +639,13 @@ PlayerObject.prototype.resetWorkStatus = function() {
}
PlayerObject.prototype.processWorkEarnings = function(numCycles=1) {
var hackExpGain = this.workHackExpGainRate * numCycles;
var strExpGain = this.workStrExpGainRate * numCycles;
var defExpGain = this.workDefExpGainRate * numCycles;
var dexExpGain = this.workDexExpGainRate * numCycles;
var agiExpGain = this.workAgiExpGainRate * numCycles;
var chaExpGain = this.workChaExpGainRate * numCycles;
const hackExpGain = this.workHackExpGainRate * numCycles;
const strExpGain = this.workStrExpGainRate * numCycles;
const defExpGain = this.workDefExpGainRate * numCycles;
const dexExpGain = this.workDexExpGainRate * numCycles;
const agiExpGain = this.workAgiExpGainRate * numCycles;
const chaExpGain = this.workChaExpGainRate * numCycles;
const moneyGain = (this.workMoneyGainRate - this.workMoneyLossRate) * numCycles;
this.gainHackingExp(hackExpGain);
this.gainStrengthExp(strExpGain);
@@ -652,6 +653,7 @@ PlayerObject.prototype.processWorkEarnings = function(numCycles=1) {
this.gainDexterityExp(dexExpGain);
this.gainAgilityExp(agiExpGain);
this.gainCharismaExp(chaExpGain);
this.gainMoney(moneyGain);
this.workHackExpGained += hackExpGain;
this.workStrExpGained += strExpGain;
this.workDefExpGained += defExpGain;
@@ -744,8 +746,6 @@ PlayerObject.prototype.finishWork = function(cancelled, sing=false) {
var company = Companies[this.companyName];
company.playerReputation += (this.workRepGained);
this.gainMoney(this.workMoneyGained);
this.updateSkillLevels();
var txt = "You earned a total of: <br>" +
@@ -862,8 +862,6 @@ PlayerObject.prototype.finishWorkPartTime = function(sing=false) {
var company = Companies[this.companyName];
company.playerReputation += (this.workRepGained);
this.gainMoney(this.workMoneyGained);
this.updateSkillLevels();
var txt = "You earned a total of: <br>" +
@@ -1031,8 +1029,6 @@ PlayerObject.prototype.finishFactionWork = function(cancelled, sing=false) {
var faction = Factions[this.currentWorkFactionName];
faction.playerReputation += (this.workRepGained);
this.gainMoney(this.workMoneyGained);
this.updateSkillLevels();
var txt = "You worked for your faction " + faction.name + " for a total of " + convertTimeMsToTimeElapsedString(this.timeWorked) + " <br><br> " +
@@ -1424,7 +1420,6 @@ PlayerObject.prototype.finishClass = function(sing=false) {
if (this.workMoneyGained > 0) {
throw new Error("ERR: Somehow gained money while taking class");
}
this.loseMoney(this.workMoneyGained * -1);
this.updateSkillLevels();
var txt = "After " + this.className + " for " + convertTimeMsToTimeElapsedString(this.timeWorked) + ", <br>" +

View File

@@ -206,7 +206,7 @@ Server.fromJSON = function(value) {
Reviver.constructors.Server = Server;
function initForeignServers() {
export function initForeignServers() {
/* Create a randomized network for all the foreign servers */
//Groupings for creating a randomized network
const networkLayers = [];
@@ -287,7 +287,9 @@ function initForeignServers() {
}
}
function numCycleForGrowth(server, growth) {
// Returns the number of cycles needed to grow the specified server by the
// specified amount. 'growth' parameter is in decimal form, not percentage
export function numCycleForGrowth(server, growth) {
let ajdGrowthRate = 1 + (CONSTANTS.ServerBaseGrowthRate - 1) / server.hackDifficulty;
if(ajdGrowthRate > CONSTANTS.ServerMaxGrowthRate) {
ajdGrowthRate = CONSTANTS.ServerMaxGrowthRate;
@@ -300,7 +302,7 @@ function numCycleForGrowth(server, growth) {
}
//Applied server growth for a single server. Returns the percentage growth
function processSingleServerGrowth(server, numCycles) {
export function processSingleServerGrowth(server, numCycles) {
//Server growth processed once every 450 game cycles
const numServerGrowthCycles = Math.max(Math.floor(numCycles / 450), 0);
@@ -334,7 +336,7 @@ function processSingleServerGrowth(server, numCycles) {
}
// if there was any growth at all, increase security
if(oldMoneyAvailable !== server.moneyAvailable) {
if (oldMoneyAvailable !== server.moneyAvailable) {
//Growing increases server security twice as much as hacking
let usedCycles = numCycleForGrowth(server, server.moneyAvailable / oldMoneyAvailable);
usedCycles = Math.max(0, usedCycles);
@@ -343,7 +345,7 @@ function processSingleServerGrowth(server, numCycles) {
return server.moneyAvailable / oldMoneyAvailable;
}
function prestigeHomeComputer(homeComp) {
export function prestigeHomeComputer(homeComp) {
const hasBitflume = homeComp.programs.includes(Programs.BitFlume.name);
homeComp.programs.length = 0; //Remove programs
@@ -366,14 +368,14 @@ function prestigeHomeComputer(homeComp) {
//List of all servers that exist in the game, indexed by their ip
let AllServers = {};
function prestigeAllServers() {
export function prestigeAllServers() {
for (var member in AllServers) {
delete AllServers[member];
}
AllServers = {};
}
function loadAllServers(saveString) {
export function loadAllServers(saveString) {
AllServers = JSON.parse(saveString, Reviver);
}
@@ -386,7 +388,7 @@ function SizeOfAllServers() {
}
//Add a server onto the map of all servers in the game
function AddToAllServers(server) {
export function AddToAllServers(server) {
var serverIp = server.ip;
if (ipExists(serverIp)) {
console.log("IP of server that's being added: " + serverIp);
@@ -400,7 +402,7 @@ function AddToAllServers(server) {
//Returns server object with corresponding hostname
// Relatively slow, would rather not use this a lot
function GetServerByHostname(hostname) {
export function GetServerByHostname(hostname) {
for (var ip in AllServers) {
if (AllServers.hasOwnProperty(ip)) {
if (AllServers[ip].hostname == hostname) {
@@ -412,7 +414,7 @@ function GetServerByHostname(hostname) {
}
//Get server by IP or hostname. Returns null if invalid
function getServer(s) {
export function getServer(s) {
if (!isValidIPAddress(s)) {
return GetServerByHostname(s);
}
@@ -459,6 +461,4 @@ Directory.prototype.getPath = function(name) {
return res.join("");
}
export {Server, AllServers, getServer, GetServerByHostname, loadAllServers,
AddToAllServers, processSingleServerGrowth, initForeignServers,
prestigeAllServers, prestigeHomeComputer};
export {Server, AllServers};

View File

@@ -34,31 +34,31 @@ export const gangMemberUpgradesMetadata: IGangMemberUpgradeMetadata[] = [
},
{
cost: 50e6,
mults: {str: 1.12, def: 1.12, agi: 1.1},
mults: {str: 1.12, def: 1.1, agi: 1.1},
name: "P90C",
upgType: "w",
},
{
cost: 60e6,
mults: {str: 1.2, def: 1.2},
mults: {str: 1.2, def: 1.15},
name: "Steyr AUG",
upgType: "w",
},
{
cost: 100e6,
mults: {str: 1.25, def: 1.25},
mults: {str: 1.25, def: 1.2},
name: "AK-47",
upgType: "w",
},
{
cost: 150e6,
mults: {str: 1.3, def: 1.3},
mults: {str: 1.3, def: 1.25},
name: "M15A10 Assault Rifle",
upgType: "w",
},
{
cost: 225e6,
mults: {str: 1.3, dex: 1.3, agi: 1.3},
mults: {str: 1.3, dex: 1.25, agi: 1.3},
name: "AWM Sniper Rifle",
upgType: "w",
},
@@ -117,17 +117,29 @@ export const gangMemberUpgradesMetadata: IGangMemberUpgradeMetadata[] = [
upgType: "r",
},
{
cost: 15e6,
cost: 25e6,
mults: {hack: 1.1},
name: "Soulstealer Rootkit",
upgType: "r",
},
{
cost: 50e6,
cost: 75e6,
mults: {hack: 1.15},
name: "Demon Rootkit",
upgType: "r",
},
{
cost: 40e6,
mults: {hack: 1.12},
name: "Hmap Node",
upgType: "r",
},
{
cost: 75e6,
mults: {hack: 1.15},
name: "Jack the Ripper",
upgType: "r",
},
{
cost: 10e9,
mults: {str: 1.3, dex: 1.3},
@@ -182,6 +194,12 @@ export const gangMemberUpgradesMetadata: IGangMemberUpgradeMetadata[] = [
name: "Neuralstimulator",
upgType: "g",
},
{
cost: 7.5e9,
mults: {hack: 1.1},
name: "DataJack",
upgType: "g",
},
{
cost: 50e9,
mults: {str: 1.7, def: 1.7},