diff --git a/.github/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE index 96f22093b..bf3cf78d5 100644 --- a/.github/PULL_REQUEST_TEMPLATE +++ b/.github/PULL_REQUEST_TEMPLATE @@ -1,5 +1,14 @@ # DELETE THIS AFTER READING +# PR title + +Formatted as such: +SECTION: FIX #xzyw PLAYER DESCRIPTION + +SECTION is something like "API", "UI", "MISC", "STANEK", "CORPORATION" +FIX #xyzw is the issue number, if any +PLAYER DESCRIPTION is what you'd tell a non-contributor to convey what is changed. + # Documentation - DO NOT CHANGE any markdown/\*.md, these files are autogenerated from NetscriptDefinitions.d.ts and will be overwritten @@ -10,5 +19,4 @@ - Include how it was tested - Include screenshot / gif (if possible) - Make sure you run `npm run format` and `npm run lint` before pushing. diff --git a/README.md b/README.md index 0401f18f6..8a9ee34c0 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Bitburner +[![Join Discord](https://img.shields.io/discord/415207508303544321)](https://discord.gg/TFc3hKD) + [![Build Status](https://github.com/danielyxie/bitburner/actions/workflows/ci.yml/badge.svg?branch=dev)](https://github.com/danielyxie/bitburner/actions/workflows/ci.yml) Bitburner is a programming-based [incremental game](https://en.wikipedia.org/wiki/Incremental_game) diff --git a/lore/bitnodes-general.txt b/lore/bitnodes-general.txt new file mode 100644 index 000000000..0068f7646 --- /dev/null +++ b/lore/bitnodes-general.txt @@ -0,0 +1,11 @@ +BitNodes are advanced simulations used to contain humanity by the Enders. It is +unknown how or why they operate, but what is clear is that the World Daemon is +extremely important to the operation of a BitNode. It is possible for the daemon +to be hacked, which results in the entire simulation going offline and the +failure of automatic attempts to reboot the node. The Daemon has a physical +presence, as indicated by the Bladeburners' ability to destroy it via force. +Also, hydroflame (irl) has stated that the glitch in Ishima is the physical +location of the World Daemon in a node. When the player destroys a BitNode, it is +currently unknown what becomes of it, or the people trapped within. However, based +on the way jump3r and Deadalus help the player to destroy it, doing so is somehow +aligned with their goals. \ No newline at end of file diff --git a/lore/enders.txt b/lore/enders.txt new file mode 100644 index 000000000..c00015843 --- /dev/null +++ b/lore/enders.txt @@ -0,0 +1,5 @@ +The "Enders", as dubbed by the humans who know of them, are a humanoid alien race +with extremely advanced technology. "Many decades ago", they invaded Earth, leading +to war between the humans and enders, but the enders were far too powerful for the +humans to win against. When the enders had won, they, for reasons unknown, kept some +number of humans alive, and in some way contained the humans within BitNodes. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a7fa8f271..77b1d9de3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5046,9 +5046,9 @@ } }, "node_modules/async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "dev": true, "dependencies": { "lodash": "^4.17.14" @@ -10199,9 +10199,9 @@ } }, "node_modules/getos/node_modules/async": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.1.tgz", - "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", + "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==", "dev": true }, "node_modules/getpass": { @@ -26245,9 +26245,9 @@ "dev": true }, "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "dev": true, "requires": { "lodash": "^4.17.14" @@ -30405,9 +30405,9 @@ }, "dependencies": { "async": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.1.tgz", - "integrity": "sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", + "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==", "dev": true } } diff --git a/package.json b/package.json index 60be198f8..a5778a54b 100644 --- a/package.json +++ b/package.json @@ -134,6 +134,7 @@ "allbuild": "npm run build && npm run electron && git add --all && git commit -m \"allbuild commit $(git rev-parse --short HEAD)\" && git push -f -u origin dev", "preversion": "npm install && npm run test", "version": "sh ./tools/build-release.sh && git add --all", - "postversion": "git push -u origin dev && git push --tags" + "postversion": "git push -u origin dev && git push --tags", + "changelog": "node tools/fetch-changelog/index.js --from=$(cat last_changelog_hash) > changelog.md" } } diff --git a/src/Augmentation/data/AugmentationCreator.tsx b/src/Augmentation/data/AugmentationCreator.tsx index 298811e0f..17af8248e 100644 --- a/src/Augmentation/data/AugmentationCreator.tsx +++ b/src/Augmentation/data/AugmentationCreator.tsx @@ -6,6 +6,7 @@ import { WHRNG } from "../../Casino/RNG"; import React from "react"; import { FactionNames } from "../../Faction/data/FactionNames"; import { CityName } from "../../Locations/data/CityNames"; +import { CONSTANTS } from "../../Constants"; function getRandomBonus(): any { const bonuses = [ @@ -2049,6 +2050,7 @@ export const initChurchOfTheMachineGodAugmentations = (): Augmentation[] => [ ]; export function initNeuroFluxGovernor(): Augmentation { + const donationBonus = CONSTANTS.Donations / 1e6 / 100; // 1 millionth of a percent per donation return new Augmentation({ name: AugmentationNames.NeuroFluxGovernor, repCost: 500, @@ -2061,35 +2063,35 @@ export function initNeuroFluxGovernor(): Augmentation { stats: ( <> This special augmentation can be leveled up infinitely. Each level of this augmentation increases MOST - multipliers by 1%, stacking multiplicatively. + multipliers by 1% (+{donationBonus * 100}% boosted by real life blood donations), stacking multiplicatively. ), - hacking_chance_mult: 1.01, - hacking_speed_mult: 1.01, - hacking_money_mult: 1.01, - hacking_grow_mult: 1.01, - hacking_mult: 1.01, - strength_mult: 1.01, - defense_mult: 1.01, - dexterity_mult: 1.01, - agility_mult: 1.01, - charisma_mult: 1.01, - hacking_exp_mult: 1.01, - strength_exp_mult: 1.01, - defense_exp_mult: 1.01, - dexterity_exp_mult: 1.01, - agility_exp_mult: 1.01, - charisma_exp_mult: 1.01, - company_rep_mult: 1.01, - faction_rep_mult: 1.01, - crime_money_mult: 1.01, - crime_success_mult: 1.01, - hacknet_node_money_mult: 1.01, - hacknet_node_purchase_cost_mult: 0.99, - hacknet_node_ram_cost_mult: 0.99, - hacknet_node_core_cost_mult: 0.99, - hacknet_node_level_cost_mult: 0.99, - work_money_mult: 1.01, + hacking_chance_mult: 1.01 + donationBonus, + hacking_speed_mult: 1.01 + donationBonus, + hacking_money_mult: 1.01 + donationBonus, + hacking_grow_mult: 1.01 + donationBonus, + hacking_mult: 1.01 + donationBonus, + strength_mult: 1.01 + donationBonus, + defense_mult: 1.01 + donationBonus, + dexterity_mult: 1.01 + donationBonus, + agility_mult: 1.01 + donationBonus, + charisma_mult: 1.01 + donationBonus, + hacking_exp_mult: 1.01 + donationBonus, + strength_exp_mult: 1.01 + donationBonus, + defense_exp_mult: 1.01 + donationBonus, + dexterity_exp_mult: 1.01 + donationBonus, + agility_exp_mult: 1.01 + donationBonus, + charisma_exp_mult: 1.01 + donationBonus, + company_rep_mult: 1.01 + donationBonus, + faction_rep_mult: 1.01 + donationBonus, + crime_money_mult: 1.01 + donationBonus, + crime_success_mult: 1.01 + donationBonus, + hacknet_node_money_mult: 1.01 + donationBonus, + hacknet_node_purchase_cost_mult: 1 / (1.01 + donationBonus), + hacknet_node_ram_cost_mult: 1 / (1.01 + donationBonus), + hacknet_node_core_cost_mult: 1 / (1.01 + donationBonus), + hacknet_node_level_cost_mult: 1 / (1.01 + donationBonus), + work_money_mult: 1.01 + donationBonus, factions: Object.values(FactionNames).filter( (factionName) => ![FactionNames.Infiltrators, FactionNames.Bladeburners, FactionNames.ChurchOfTheMachineGod].includes( diff --git a/src/Constants.ts b/src/Constants.ts index 46bf33f2d..e145b95b1 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -114,6 +114,7 @@ export const CONSTANTS: { AugmentationGraftingTimeBase: number; EntropyEffect: number; TotalNumBitNodes: number; + Donations: number; // number of blood/plasma/palette donation the dev have verified., boosts NFG LatestUpdate: string; } = { VersionString: "1.6.4", @@ -286,6 +287,8 @@ export const CONSTANTS: { // BitNode/Source-File related stuff TotalNumBitNodes: 24, + Donations: 2, + LatestUpdate: ` v1.6.3 - 2022-04-01 Few stanek fixes ---------------------------- diff --git a/src/CotMG/ActiveFragment.ts b/src/CotMG/ActiveFragment.ts index ac46d40e3..0a7d7e44b 100644 --- a/src/CotMG/ActiveFragment.ts +++ b/src/CotMG/ActiveFragment.ts @@ -40,8 +40,9 @@ export class ActiveFragment { // 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++) { + const fragSize = Math.max(thisFragment.shape.length, thisFragment.shape[0].length); + for (let j = 0; j < fragSize; j++) { + for (let i = 0; i < fragSize; i++) { if (thisFragment.fullAt(i, j, this.rotation) && otherFragment.fullAt(i - dx, j - dy, other.rotation)) return true; } diff --git a/src/Message/MessageHelpers.ts b/src/Message/MessageHelpers.ts index 9236b8664..cf36f15ad 100644 --- a/src/Message/MessageHelpers.ts +++ b/src/Message/MessageHelpers.ts @@ -183,7 +183,7 @@ const Messages: Record = { "like us. Because they can't hide from us. Because they can't fight shadows " + "and ideas with bullets.

" + "Join us, and people will fear you, too.

" + - "Find and install the backdoor on our server. Then, we will contact you again." + + "Find and install the backdoor on our server, avmnite-02h. Then, we will contact you again." + `

-${FactionNames.NiteSec}`, ), diff --git a/src/NetscriptFunctions/Extra.ts b/src/NetscriptFunctions/Extra.ts index deeb65c04..4fc5a888a 100644 --- a/src/NetscriptFunctions/Extra.ts +++ b/src/NetscriptFunctions/Extra.ts @@ -59,17 +59,20 @@ export function NetscriptExtra(player: IPlayer, workerScript: WorkerScript, help player.giveExploit(Exploit.RealityAlteration); } }, - rainbow: function (guess: unknown): void { - async function tryGuess(): Promise { - const verified = await bcrypt.compare( + rainbow: function (guess: unknown): boolean { + function tryGuess(): boolean { + // eslint-disable-next-line no-sync + const verified = bcrypt.compareSync( helper.string("rainbow", "guess", guess), "$2a$10$aertxDEkgor8baVtQDZsLuMwwGYmkRM/ohcA6FjmmzIHQeTCsrCcO", ); if (verified) { player.giveExploit(Exploit.INeedARainbow); + return true; } + return false; } - tryGuess(); + return tryGuess(); }, }; } diff --git a/src/NetscriptFunctions/Formulas.ts b/src/NetscriptFunctions/Formulas.ts index e0d70a200..fb6c4cd56 100644 --- a/src/NetscriptFunctions/Formulas.ts +++ b/src/NetscriptFunctions/Formulas.ts @@ -38,6 +38,7 @@ import { calculateAscensionPointsGain, } from "../Gang/formulas/formulas"; import { favorToRep as calculateFavorToRep, repToFavor as calculateRepToFavor } from "../Faction/formulas/favor"; +import { repFromDonation } from "../Faction/formulas/donation"; export function NetscriptFormulas(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): IFormulas { const checkFormulasAccess = function (func: string): void { @@ -57,6 +58,11 @@ export function NetscriptFormulas(player: IPlayer, workerScript: WorkerScript, h checkFormulasAccess("reputation.calculateRepToFavor"); return calculateRepToFavor(rep); }, + repFromDonation: function (_amount: unknown, player: any): number { + const amount = helper.number("repFromDonation", "amount", _amount); + checkFormulasAccess("reputation.repFromDonation"); + return repFromDonation(amount, player); + }, }, skills: { calculateSkill: function (_exp: unknown, _mult: unknown = 1): number { diff --git a/src/NetscriptFunctions/Singularity.ts b/src/NetscriptFunctions/Singularity.ts index 0124e1e90..5b6df98a3 100644 --- a/src/NetscriptFunctions/Singularity.ts +++ b/src/NetscriptFunctions/Singularity.ts @@ -23,7 +23,6 @@ import { findCrime } from "../Crime/CrimeHelpers"; import { CompanyPosition } from "../Company/CompanyPosition"; import { CompanyPositions } from "../Company/CompanyPositions"; import { DarkWebItems } from "../DarkWeb/DarkWebItems"; -import { AllGangs } from "../Gang/AllGangs"; import { CityName } from "../Locations/data/CityNames"; import { LocationName } from "../Locations/data/LocationNames"; import { Router } from "../ui/GameRoot"; @@ -49,6 +48,7 @@ import { FactionInfos } from "../Faction/FactionInfo"; import { InternalAPI, NetscriptContext } from "src/Netscript/APIWrapper"; import { BlackOperationNames } from "../Bladeburner/data/BlackOperationNames"; import { enterBitNode } from "../RedPill"; +import { FactionNames } from "../Faction/data/FactionNames"; export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript): InternalAPI { const getAugmentation = function (_ctx: NetscriptContext, name: string): Augmentation { @@ -1047,11 +1047,14 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript const facName = _ctx.helper.string("facName", _facName); const type = _ctx.helper.string("type", _type); const focus = _ctx.helper.boolean(_focus); - getFaction(_ctx, facName); + const faction = getFaction(_ctx, facName); // if the player is in a gang and the target faction is any of the gang faction, fail - if (player.inGang() && AllGangs[facName] !== undefined) { - workerScript.log("workForFaction", () => `Faction '${facName}' does not offer work at the moment.`); + if (player.inGang() && faction.name === player.getGangFaction().name) { + workerScript.log( + "workForFaction", + () => `You can't work for '${facName}' because youre managing a gang for it`, + ); return false; } @@ -1066,21 +1069,18 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript workerScript.log("workForFaction", () => txt); } - const fac = Factions[facName]; - // Arrays listing factions that allow each time of work - switch (type.toLowerCase()) { case "hacking": case "hacking contracts": case "hackingcontracts": - if (!FactionInfos[fac.name].offerHackingWork) { + if (!FactionInfos[faction.name].offerHackingWork) { workerScript.log( "workForFaction", - () => `Faction '${fac.name}' do not need help with hacking contracts.`, + () => `Faction '${faction.name}' do not need help with hacking contracts.`, ); return false; } - player.startFactionHackWork(fac); + player.startFactionHackWork(faction); if (focus) { player.startFocusing(); Router.toWork(); @@ -1088,16 +1088,19 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript player.stopFocusing(); Router.toTerminal(); } - workerScript.log("workForFaction", () => `Started carrying out hacking contracts for '${fac.name}'`); + workerScript.log("workForFaction", () => `Started carrying out hacking contracts for '${faction.name}'`); return true; case "field": case "fieldwork": case "field work": - if (!FactionInfos[fac.name].offerFieldWork) { - workerScript.log("workForFaction", () => `Faction '${fac.name}' do not need help with field missions.`); + if (!FactionInfos[faction.name].offerFieldWork) { + workerScript.log( + "workForFaction", + () => `Faction '${faction.name}' do not need help with field missions.`, + ); return false; } - player.startFactionFieldWork(fac); + player.startFactionFieldWork(faction); if (focus) { player.startFocusing(); Router.toWork(); @@ -1105,16 +1108,19 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript player.stopFocusing(); Router.toTerminal(); } - workerScript.log("workForFaction", () => `Started carrying out field missions for '${fac.name}'`); + workerScript.log("workForFaction", () => `Started carrying out field missions for '${faction.name}'`); return true; case "security": case "securitywork": case "security work": - if (!FactionInfos[fac.name].offerSecurityWork) { - workerScript.log("workForFaction", () => `Faction '${fac.name}' do not need help with security work.`); + if (!FactionInfos[faction.name].offerSecurityWork) { + workerScript.log( + "workForFaction", + () => `Faction '${faction.name}' do not need help with security work.`, + ); return false; } - player.startFactionSecurityWork(fac); + player.startFactionSecurityWork(faction); if (focus) { player.startFocusing(); Router.toWork(); @@ -1122,7 +1128,7 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript player.stopFocusing(); Router.toTerminal(); } - workerScript.log("workForFaction", () => `Started carrying out security work for '${fac.name}'`); + workerScript.log("workForFaction", () => `Started carrying out security work for '${faction.name}'`); return true; default: workerScript.log("workForFaction", () => `Invalid work type: '${type}`); @@ -1168,6 +1174,13 @@ export function NetscriptSingularity(player: IPlayer, workerScript: WorkerScript ); return false; } + if (faction.name === FactionNames.ChurchOfTheMachineGod || faction.name === FactionNames.Bladeburners) { + workerScript.log( + "donateToFaction", + () => `You can't donate to '${facName}' because they do not accept donations`, + ); + return false; + } if (typeof amt !== "number" || amt <= 0 || isNaN(amt)) { workerScript.log("donateToFaction", () => `Invalid donation amount: '${amt}'.`); return false; diff --git a/src/NetscriptFunctions/Sleeve.ts b/src/NetscriptFunctions/Sleeve.ts index 6b7751001..ed58bbfdc 100644 --- a/src/NetscriptFunctions/Sleeve.ts +++ b/src/NetscriptFunctions/Sleeve.ts @@ -16,6 +16,7 @@ import { SleeveSkills, SleeveTask, } from "../ScriptEditor/NetscriptDefinitions"; +import { checkEnum } from "../utils/helpers/checkEnum"; export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, helper: INetscriptHelper): ISleeve { const checkSleeveAPIAccess = function (func: string): void { @@ -99,7 +100,11 @@ export function NetscriptSleeve(player: IPlayer, workerScript: WorkerScript, hel const cityName = helper.string("travel", "cityName", _cityName); checkSleeveAPIAccess("travel"); checkSleeveNumber("travel", sleeveNumber); - return player.sleeves[sleeveNumber].travel(player, cityName as CityName); + if (checkEnum(CityName, cityName)) { + return player.sleeves[sleeveNumber].travel(player, cityName); + } else { + throw helper.makeRuntimeErrorMsg("sleeve.setToCompanyWork", `Invalid city name: '${cityName}'.`); + } }, setToCompanyWork: function (_sleeveNumber: unknown, acompanyName: unknown): boolean { updateRam("setToCompanyWork"); diff --git a/src/PersonObjects/Player/PlayerObject.ts b/src/PersonObjects/Player/PlayerObject.ts index e2c66b9eb..eebf6c2ee 100644 --- a/src/PersonObjects/Player/PlayerObject.ts +++ b/src/PersonObjects/Player/PlayerObject.ts @@ -37,6 +37,7 @@ import { ISkillProgress } from "../formulas/skill"; import { PlayerAchievement } from "../../Achievements/Achievements"; import { cyrb53 } from "../../utils/StringHelperFunctions"; import { getRandomInt } from "../../utils/helpers/getRandomInt"; +import { CONSTANTS } from "../../Constants"; export class PlayerObject implements IPlayer { // Class members @@ -361,7 +362,7 @@ export class PlayerObject implements IPlayer { this.faction_rep_mult = 1; //Money - this.money = 1000; + this.money = 1000 + CONSTANTS.Donations; //Location information this.city = CityName.Sector12; diff --git a/src/PersonObjects/Player/PlayerObjectGeneralMethods.tsx b/src/PersonObjects/Player/PlayerObjectGeneralMethods.tsx index a45df0b32..2646a20f2 100644 --- a/src/PersonObjects/Player/PlayerObjectGeneralMethods.tsx +++ b/src/PersonObjects/Player/PlayerObjectGeneralMethods.tsx @@ -105,7 +105,7 @@ export function prestigeAugmentation(this: PlayerObject): void { this.agility_exp = 0; this.charisma_exp = 0; - this.money = 1000; + this.money = 1000 + CONSTANTS.Donations; this.city = CityName.Sector12; this.location = LocationName.TravelAgency; diff --git a/src/ScriptEditor/NetscriptDefinitions.d.ts b/src/ScriptEditor/NetscriptDefinitions.d.ts index 68a7220a7..dd7e8dd10 100644 --- a/src/ScriptEditor/NetscriptDefinitions.d.ts +++ b/src/ScriptEditor/NetscriptDefinitions.d.ts @@ -3873,6 +3873,13 @@ interface ReputationFormulas { * @returns The calculated faction favor. */ calculateRepToFavor(rep: number): number; + + /** + * Calculate how much rep would be gained. + * @param amount - Amount of money donated + * @param player - Player info from {@link NS.getPlayer | getPlayer} + */ + repFromDonation(amount: number, player: Player): number; } /** diff --git a/src/Terminal/commands/ls.tsx b/src/Terminal/commands/ls.tsx index f784f217f..1fddc05e8 100644 --- a/src/Terminal/commands/ls.tsx +++ b/src/Terminal/commands/ls.tsx @@ -9,6 +9,8 @@ import { evaluateDirectoryPath, getFirstParentDirectory, isValidDirectoryPath } import { IRouter } from "../../ui/Router"; import { ITerminal } from "../ITerminal"; import * as libarg from "arg"; +import { showLiterature } from "../../Literature/LiteratureHelpers"; +import { MessageFilenames, showMessage } from "../../Message/MessageHelpers"; export function ls( terminal: ITerminal, @@ -121,13 +123,13 @@ export function ls( allMessages.sort(); folders.sort(); - interface ClickableScriptRowProps { + interface ClickableRowProps { row: string; prefix: string; hostname: string; } - function ClickableScriptRow({ row, prefix, hostname }: ClickableScriptRowProps): React.ReactElement { + function ClickableScriptRow({ row, prefix, hostname }: ClickableRowProps): React.ReactElement { const classes = makeStyles((theme: Theme) => createStyles({ scriptLinksWrap: { @@ -160,18 +162,82 @@ export function ls( return ( {rowSplitArray.map((rowItem) => ( - - onScriptLinkClick(rowItem[0])}> + + onScriptLinkClick(rowItem[0])}> {rowItem[0]} - {rowItem[1]} + {rowItem[1]} ))} ); } - function postSegments(segments: string[], flags: any, style?: any, linked?: boolean): void { + function ClickableMessageRow({ row, prefix, hostname }: ClickableRowProps): React.ReactElement { + const classes = makeStyles((theme: Theme) => + createStyles({ + linksWrap: { + display: "inline-flex", + color: theme.palette.primary.main, + }, + link: { + cursor: "pointer", + textDecorationLine: "underline", + paddingRight: "1.15em", + "&:last-child": { padding: 0 }, + }, + }), + )(); + + const rowSplit = row.split("~"); + let rowSplitArray = rowSplit.map((x) => [x.trim(), x.replace(x.trim(), "")]); + rowSplitArray = rowSplitArray.filter((x) => !!x[0]); + + function onMessageLinkClick(filename: string): void { + if (player.getCurrentServer().hostname !== hostname) { + return terminal.error(`File is not on this server, connect to ${hostname} and try again`); + } + if (filename.startsWith("/")) filename = filename.slice(1); + const filepath = terminal.getFilepath(`${prefix}${filename}`); + + if (filepath.endsWith(".lit")) { + showLiterature(filepath); + } else if (filepath.endsWith(".msg")) { + showMessage(filepath as MessageFilenames); + } + } + + return ( + + {rowSplitArray.map((rowItem) => ( + + onMessageLinkClick(rowItem[0])}> + {rowItem[0]} + + {rowItem[1]} + + ))} + + ); + } + + enum FileType { + Folder, + Message, + TextFile, + Program, + Contract, + Script, + } + + interface FileGroup { + type: FileType; + segments: string[]; + } + + function postSegments(group: FileGroup, flags: any): void { + const segments = group.segments; + const linked = group.type === FileType.Script || group.type === FileType.Message; const maxLength = Math.max(...segments.map((s) => s.length)) + 1; const filesPerRow = flags["-l"] === true ? 1 : Math.ceil(80 / maxLength); for (let i = 0; i < segments.length; i++) { @@ -186,25 +252,32 @@ export function ls( i++; } i--; - if (!style) { - terminal.print(row); - } else if (linked) { - terminal.printRaw(); - } else { - terminal.printRaw({row}); + + switch (group.type) { + case FileType.Folder: + terminal.printRaw({row}); + break; + case FileType.Script: + terminal.printRaw(); + break; + case FileType.Message: + terminal.printRaw(); + break; + default: + terminal.print(row); } } } - const groups = [ - { segments: folders, style: { color: "cyan" } }, - { segments: allMessages }, - { segments: allTextFiles }, - { segments: allPrograms }, - { segments: allContracts }, - { segments: allScripts, style: { color: "yellow", fontStyle: "bold" }, linked: true }, + const groups: FileGroup[] = [ + { type: FileType.Folder, segments: folders }, + { type: FileType.Message, segments: allMessages }, + { type: FileType.TextFile, segments: allTextFiles }, + { type: FileType.Program, segments: allPrograms }, + { type: FileType.Contract, segments: allContracts }, + { type: FileType.Script, segments: allScripts }, ].filter((g) => g.segments.length > 0); - for (let i = 0; i < groups.length; i++) { - postSegments(groups[i].segments, flags, groups[i].style, groups[i].linked); + for (const group of groups) { + postSegments(group, flags); } } diff --git a/test/jest/Netscript/DynamicRamCalculation.test.js b/test/jest/Netscript/DynamicRamCalculation.test.js index 4170f9ebd..b08e09ba8 100644 --- a/test/jest/Netscript/DynamicRamCalculation.test.js +++ b/test/jest/Netscript/DynamicRamCalculation.test.js @@ -6,7 +6,6 @@ import { getRamCost, RamCostConstants } from "../../../src/Netscript/RamCostGene import { Environment } from "../../../src/Netscript/Environment"; import { RunningScript } from "../../../src/Script/RunningScript"; import { Script } from "../../../src/Script/Script"; -import { SourceFileFlags } from "../../../src/SourceFile/SourceFileFlags"; jest.mock(`!!raw-loader!../NetscriptDefinitions.d.ts`, () => "", { virtual: true, @@ -169,12 +168,6 @@ describe("Netscript Dynamic RAM Calculation/Generation Tests", function () { testEquality(workerScript.dynamicRamUsage, runningScript.ramUsage); } - beforeEach(function () { - for (let i = 0; i < SourceFileFlags.length; ++i) { - SourceFileFlags[i] = 3; - } - }); - describe("Basic Functions", function () { it("hack()", async function () { const f = ["hack"];