diff --git a/src/Augmentation/AugmentationHelpers.d.ts b/src/Augmentation/AugmentationHelpers.d.ts
index c20c69364..80a1be9cc 100644
--- a/src/Augmentation/AugmentationHelpers.d.ts
+++ b/src/Augmentation/AugmentationHelpers.d.ts
@@ -1,2 +1,3 @@
 export declare function isRepeatableAug(aug: Augmentation): boolean;
 export declare function installAugmentations(): void;
+export declare function applyAugmentation(aug: Augmentation, reapply?: boolean): void;
diff --git a/src/Bladeburner/ui/BladeburnerRoot.tsx b/src/Bladeburner/ui/BladeburnerRoot.tsx
index 35af06174..e0c62747c 100644
--- a/src/Bladeburner/ui/BladeburnerRoot.tsx
+++ b/src/Bladeburner/ui/BladeburnerRoot.tsx
@@ -6,13 +6,11 @@ import { AllPages } from "./AllPages";
 import { use } from "../../ui/Context";
 import { IBladeburner } from "../IBladeburner";
 
-interface IProps {
-  bladeburner: IBladeburner;
-}
-
-export function BladeburnerRoot(props: IProps): React.ReactElement {
+export function BladeburnerRoot(): React.ReactElement {
   const player = use.Player();
   const router = use.Router();
+  const bladeburner = player.bladeburner;
+  if (bladeburner === null) return <></>;
   return (
     <div className="bladeburner-container">
       <div style={{ height: "60%", display: "block", position: "relative" }}>
@@ -24,9 +22,9 @@ export function BladeburnerRoot(props: IProps): React.ReactElement {
             border: "1px solid white",
           }}
         >
-          <Stats bladeburner={props.bladeburner} player={player} router={router} />
+          <Stats bladeburner={bladeburner} player={player} router={router} />
         </div>
-        <Console bladeburner={props.bladeburner} player={player} />
+        <Console bladeburner={bladeburner} player={player} />
       </div>
       <div
         style={{
@@ -38,7 +36,7 @@ export function BladeburnerRoot(props: IProps): React.ReactElement {
           position: "relative",
         }}
       >
-        <AllPages bladeburner={props.bladeburner} player={player} />
+        <AllPages bladeburner={bladeburner} player={player} />
       </div>
     </div>
   );
diff --git a/src/Corporation/ui/CorporationRoot.tsx b/src/Corporation/ui/CorporationRoot.tsx
index 8784a35a7..0ad51fd23 100644
--- a/src/Corporation/ui/CorporationRoot.tsx
+++ b/src/Corporation/ui/CorporationRoot.tsx
@@ -10,6 +10,7 @@ import { ICorporation } from "../ICorporation";
 import { IPlayer } from "../../PersonObjects/IPlayer";
 import { MainPanel } from "./MainPanel";
 import { Industries } from "../IndustryData";
+import { use } from "../../ui/Context";
 
 interface IExpandButtonProps {
   corp: ICorporation;
@@ -38,12 +39,10 @@ function ExpandButton(props: IExpandButtonProps): React.ReactElement {
   return <HeaderTab current={false} onClick={openNewIndustryPopup} text={"Expand into new Industry"} />;
 }
 
-interface IProps {
-  corp: ICorporation;
-  player: IPlayer;
-}
-
-export function CorporationRoot(props: IProps): React.ReactElement {
+export function CorporationRoot(): React.ReactElement {
+  const player = use.Player();
+  const corporation = player.corporation;
+  if (corporation === null) return <></>;
   const setRerender = useState(false)[1];
   function rerender(): void {
     setRerender((old) => !old);
@@ -62,9 +61,9 @@ export function CorporationRoot(props: IProps): React.ReactElement {
           current={divisionName === "Overview"}
           key={"overview"}
           onClick={() => setDivisionName("Overview")}
-          text={props.corp.name}
+          text={corporation.name}
         />
-        {props.corp.divisions.map((division: IIndustry) => (
+        {corporation.divisions.map((division: IIndustry) => (
           <HeaderTab
             current={division.name === divisionName}
             key={division.name}
@@ -72,9 +71,9 @@ export function CorporationRoot(props: IProps): React.ReactElement {
             text={division.name}
           />
         ))}
-        <ExpandButton corp={props.corp} setDivisionName={setDivisionName} />
+        <ExpandButton corp={corporation} setDivisionName={setDivisionName} />
       </div>
-      <MainPanel rerender={rerender} corp={props.corp} divisionName={divisionName} player={props.player} />
+      <MainPanel rerender={rerender} corp={corporation} divisionName={divisionName} player={player} />
     </div>
   );
 }
diff --git a/src/Crime/Crime.ts b/src/Crime/Crime.ts
index 798b8e262..75db4bb42 100644
--- a/src/Crime/Crime.ts
+++ b/src/Crime/Crime.ts
@@ -2,6 +2,7 @@ import { CONSTANTS } from "../Constants";
 import { IPlayer } from "../PersonObjects/IPlayer";
 import { IPlayerOrSleeve } from "../PersonObjects/IPlayerOrSleeve";
 import { IRouter } from "../ui/Router";
+import { WorkerScript } from "../Netscript/WorkerScript";
 
 export interface IConstructorParams {
   hacking_success_weight?: number;
@@ -86,7 +87,7 @@ export class Crime {
     this.kills = params.kills ? params.kills : 0;
   }
 
-  commit(router: IRouter, p: IPlayer, div = 1, singParams: any = null): number {
+  commit(router: IRouter, p: IPlayer, div = 1, workerScript: WorkerScript | null = null): number {
     if (div <= 0) {
       div = 1;
     }
@@ -101,7 +102,7 @@ export class Crime {
       this.charisma_exp / div,
       this.money / div,
       this.time,
-      singParams,
+      workerScript,
     );
 
     return this.time;
diff --git a/src/DarkWeb/DarkWeb.tsx b/src/DarkWeb/DarkWeb.tsx
index 5473bf036..2e339946b 100644
--- a/src/DarkWeb/DarkWeb.tsx
+++ b/src/DarkWeb/DarkWeb.tsx
@@ -14,7 +14,8 @@ export function checkIfConnectedToDarkweb(): void {
     if (!isValidIPAddress(darkwebIp)) {
       return;
     }
-    if (darkwebIp == Player.getCurrentServer().ip) {
+    const server = Player.getCurrentServer();
+    if (server !== null && darkwebIp == server.ip) {
       Terminal.print(
         "You are now connected to the dark web. From the dark web you can purchase illegal items. " +
           "Use the 'buy -l' command to display a list of all the items you can buy. Use 'buy [item-name] " +
diff --git a/src/DevMenu/ui/Bladeburner.tsx b/src/DevMenu/ui/Bladeburner.tsx
index 0a435dfa5..81082a8b9 100644
--- a/src/DevMenu/ui/Bladeburner.tsx
+++ b/src/DevMenu/ui/Bladeburner.tsx
@@ -15,43 +15,42 @@ interface IProps {
 }
 
 export function Bladeburner(props: IProps): React.ReactElement {
+  const bladeburner = props.player.bladeburner;
+  if (bladeburner === null) return <></>;
   function modifyBladeburnerRank(modify: number): (x: number) => void {
     return function (rank: number): void {
-      if (props.player.bladeburner) {
-        props.player.bladeburner.changeRank(props.player, rank * modify);
-      }
+      if (!bladeburner) return;
+      bladeburner.changeRank(props.player, rank * modify);
     };
   }
 
   function resetBladeburnerRank(): void {
-    props.player.bladeburner.rank = 0;
-    props.player.bladeburner.maxRank = 0;
+    if (!bladeburner) return;
+    bladeburner.rank = 0;
+    bladeburner.maxRank = 0;
   }
 
   function addTonsBladeburnerRank(): void {
-    if (props.player.bladeburner) {
-      props.player.bladeburner.changeRank(props.player, bigNumber);
-    }
+    if (!bladeburner) return;
+
+    bladeburner.changeRank(props.player, bigNumber);
   }
 
   function modifyBladeburnerCycles(modify: number): (x: number) => void {
     return function (cycles: number): void {
-      if (props.player.bladeburner) {
-        props.player.bladeburner.storedCycles += cycles * modify;
-      }
+      if (!bladeburner) return;
+      bladeburner.storedCycles += cycles * modify;
     };
   }
 
   function resetBladeburnerCycles(): void {
-    if (props.player.bladeburner) {
-      props.player.bladeburner.storedCycles = 0;
-    }
+    if (!bladeburner) return;
+    bladeburner.storedCycles = 0;
   }
 
   function addTonsBladeburnerCycles(): void {
-    if (props.player.bladeburner) {
-      props.player.bladeburner.storedCycles += bigNumber;
-    }
+    if (!bladeburner) return;
+    bladeburner.storedCycles += bigNumber;
   }
 
   return (
diff --git a/src/Exploits/Exploit.ts b/src/Exploits/Exploit.ts
index 77da933c7..1d5f23482 100644
--- a/src/Exploits/Exploit.ts
+++ b/src/Exploits/Exploit.ts
@@ -35,6 +35,6 @@ export function ExploitName(exploit: string): string {
   return names[exploit];
 }
 
-export function sanitizeExploits(exploits: string[]): string[] {
-  return exploits.filter((e: string) => Object.keys(Exploit).includes(e));
+export function sanitizeExploits(exploits: Exploit[]): Exploit[] {
+  return exploits.filter((e: Exploit) => Object.keys(Exploit).includes(e));
 }
diff --git a/src/Fconf/Fconf.d.ts b/src/Fconf/Fconf.d.ts
deleted file mode 100644
index fcacfe3f8..000000000
--- a/src/Fconf/Fconf.d.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export declare function parseFconfSettings(config: string): void;
-export declare function createFconf(): string;
diff --git a/src/Fconf/Fconf.js b/src/Fconf/Fconf.js
deleted file mode 100644
index a31645858..000000000
--- a/src/Fconf/Fconf.js
+++ /dev/null
@@ -1,268 +0,0 @@
-import { FconfSettings } from "./FconfSettings";
-
-import { parse, Node } from "acorn";
-import { dialogBoxCreate } from "../../utils/DialogBox";
-
-var FconfComments = {
-  ENABLE_BASH_HOTKEYS:
-    "Improved Bash emulation mode. Setting this to 1 enables several\n" +
-    "new Terminal shortcuts and features that more closely resemble\n" +
-    "a real Bash-style shell. Note that when this mode is enabled,\n" +
-    "the default browser shortcuts are overriden by the new Bash\n" +
-    "shortcuts.\n\n" +
-    "To see a full list of the Terminal shortcuts that this enables, see:\n" +
-    "http://bitburner.readthedocs.io/en/latest/shortcuts.html",
-  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, 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" +
-    "3-digit or 6-digit hex color code (e.g. #123456). Default color: #000000",
-  THEME_FONT_COLOR:
-    "Sets the font 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" +
-    "3-digit or 6-digit hex color code (e.g. #123456). Default color: #66ff33",
-  THEME_HIGHLIGHT_COLOR:
-    "Sets the highlight 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" +
-    "3-digit or 6-digit hex color code (e.g. #123456). Default color: #ffffff",
-  THEME_PROMPT_COLOR:
-    "Sets the prompt color in the Terminal\n\n" +
-    "The color must be specified as a pound sign (#) followed by a \n" +
-    "3-digit or 6-digit hex color code (e.g. #123456). Default color: #f92672",
-  WRAP_INPUT:
-    "Wrap Terminal Input. If this is enabled, then when a Terminal command is\n" +
-    "too long and overflows, then it will wrap to the next line instead of\n" +
-    "side-scrolling\n\n" +
-    "Note that after you enable/disable this, you'll have to run a command\n" +
-    "before its effect takes place.",
-};
-
-const MainMenuStyleOptions = ["default", "classic", "compact"];
-
-//Parse Fconf settings from the config text
-//Throws an exception if parsing fails
-function parseFconfSettings(config) {
-  var ast = parse(config, { sourceType: "module" });
-  var queue = [];
-  queue.push(ast);
-  while (queue.length != 0) {
-    var exp = queue.shift();
-    switch (exp.type) {
-      case "BlockStatement":
-      case "Program":
-        for (var i = 0; i < exp.body.length; ++i) {
-          if (exp.body[i] instanceof Node) {
-            queue.push(exp.body[i]);
-          }
-        }
-        break;
-      case "AssignmentExpression":
-        var setting, value;
-        if (exp.left != null && exp.left.name != null) {
-          setting = exp.left.name;
-        } else {
-          break;
-        }
-        if (exp.right != null && exp.right.raw != null) {
-          value = exp.right.raw;
-        } else {
-          break;
-        }
-        parseFconfSetting(setting, value);
-        break;
-      default:
-        break;
-    }
-
-    for (var prop in exp) {
-      if (exp.hasOwnProperty(prop)) {
-        if (exp[prop] instanceof Node) {
-          queue.push(exp[prop]);
-        }
-      }
-    }
-  }
-
-  setTheme();
-  setMainMenuStyle();
-}
-
-function parseFconfSetting(setting, value) {
-  setting = String(setting);
-  value = String(value);
-  if (setting == null || value == null || FconfSettings[setting] == null) {
-    console.warn(`Invalid .fconf setting: ${setting}`);
-    return;
-  }
-
-  function sanitizeString(value) {
-    value = value.toLowerCase();
-    if (value.startsWith('"')) {
-      value = value.slice(1);
-    }
-    if (value.endsWith('"')) {
-      value = value.slice(0, -1);
-    }
-    return value;
-  }
-
-  switch (setting) {
-    case "ENABLE_BASH_HOTKEYS":
-    case "ENABLE_TIMESTAMPS":
-    case "WRAP_INPUT":
-      // Need to convert entered value to boolean/strings accordingly
-      var value = value.toLowerCase();
-      if (value === "1" || value === "true" || value === "y") {
-        value = true;
-      } else {
-        value = false;
-      }
-      FconfSettings[setting] = value;
-      break;
-    case "MAIN_MENU_STYLE":
-      var value = sanitizeString(value);
-      if (MainMenuStyleOptions.includes(value)) {
-        FconfSettings[setting] = value;
-      } else {
-        dialogBoxCreate(`Invalid option specified for ${setting}. Options: ${MainMenuStyleOptions.toString()}`);
-      }
-      break;
-    case "THEME_BACKGROUND_COLOR":
-    case "THEME_FONT_COLOR":
-    case "THEME_HIGHLIGHT_COLOR":
-    case "THEME_PROMPT_COLOR":
-      var value = sanitizeString(value);
-      if (/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(value)) {
-        FconfSettings[setting] = value;
-      } else {
-        dialogBoxCreate(`Invalid color specified for ${setting}. Must be a hex color code preceded by a pound (#)`);
-      }
-      break;
-    default:
-      break;
-  }
-}
-
-//Create the .fconf file text from the settings
-function createFconf() {
-  var res = "";
-  for (var setting in FconfSettings) {
-    if (FconfSettings.hasOwnProperty(setting)) {
-      //Setting comments (description)
-      var comment = FconfComments[setting];
-      if (comment == null) {
-        continue;
-      }
-      var comment = comment.split("\n");
-      for (var i = 0; i < comment.length; ++i) {
-        res += "//" + comment[i] + "\n";
-      }
-
-      var value = 0;
-      if (FconfSettings[setting] === true) {
-        value = "1";
-      } else if (FconfSettings[setting] === false) {
-        value = "0";
-      } else {
-        value = '"' + String(FconfSettings[setting]) + '"';
-      }
-      res += `${setting} = ${value}\n\n`;
-    }
-  }
-  return res;
-}
-
-function loadFconf(saveString) {
-  let tempFconfSettings = JSON.parse(saveString);
-  for (var setting in tempFconfSettings) {
-    if (tempFconfSettings.hasOwnProperty(setting)) {
-      FconfSettings[setting] = tempFconfSettings[setting];
-    }
-  }
-
-  // Initialize themes/styles after loading
-  setTheme();
-  setMainMenuStyle();
-}
-
-function setTheme() {
-  if (
-    FconfSettings.THEME_HIGHLIGHT_COLOR == null ||
-    FconfSettings.THEME_FONT_COLOR == null ||
-    FconfSettings.THEME_BACKGROUND_COLOR == null ||
-    FconfSettings.THEME_PROMPT_COLOR == null
-  ) {
-    console.error("Cannot find Theme Settings");
-    return;
-  }
-  if (
-    /^#[0-9a-f]{3}(?:[0-9a-f]{3})?$/i.test(FconfSettings.THEME_HIGHLIGHT_COLOR) &&
-    /^#[0-9a-f]{3}(?:[0-9a-f]{3})?$/i.test(FconfSettings.THEME_FONT_COLOR) &&
-    /^#[0-9a-f]{3}(?:[0-9a-f]{3})?$/i.test(FconfSettings.THEME_BACKGROUND_COLOR) &&
-    /^#[0-9a-f]{3}(?:[0-9a-f]{3})?$/i.test(FconfSettings.THEME_PROMPT_COLOR)
-  ) {
-    // document.body.style.setProperty("--my-highlight-color", FconfSettings.THEME_HIGHLIGHT_COLOR);
-    // document.body.style.setProperty("--my-font-color", FconfSettings.THEME_FONT_COLOR);
-    // document.body.style.setProperty("--my-background-color", FconfSettings.THEME_BACKGROUND_COLOR);
-    // document.body.style.setProperty("--my-prompt-color", FconfSettings.THEME_PROMPT_COLOR);
-  }
-}
-
-function setMainMenuStyle() {
-  const mainMenu = document.getElementById("mainmenu");
-  const hackingMenuHdr = document.getElementById("hacking-menu-header");
-  const characterMenuHdr = document.getElementById("character-menu-header");
-  const worldMenuHdr = document.getElementById("world-menu-header");
-  const helpMenuHdr = document.getElementById("help-menu-header");
-
-  function removeAllAccordionHeaderClasses() {
-    hackingMenuHdr.classList.remove("mainmenu-accordion-header", "mainmenu-accordion-header-classic");
-    characterMenuHdr.classList.remove("mainmenu-accordion-header", "mainmenu-accordion-header-classic");
-    worldMenuHdr.classList.remove("mainmenu-accordion-header", "mainmenu-accordion-header-classic");
-    helpMenuHdr.classList.remove("mainmenu-accordion-header", "mainmenu-accordion-header-classic");
-  }
-
-  function addClassToAllAccordionHeaders(clsName) {
-    hackingMenuHdr.classList.add(clsName);
-    characterMenuHdr.classList.add(clsName);
-    worldMenuHdr.classList.add(clsName);
-    helpMenuHdr.classList.add(clsName);
-  }
-
-  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;
-  }
-
-  // Click each header twice to reset lol
-  hackingMenuHdr.click();
-  hackingMenuHdr.click();
-  characterMenuHdr.click();
-  characterMenuHdr.click();
-  worldMenuHdr.click();
-  worldMenuHdr.click();
-  helpMenuHdr.click();
-  helpMenuHdr.click();
-}
-
-export { FconfSettings, createFconf, parseFconfSettings, loadFconf };
diff --git a/src/Fconf/FconfSettings.ts b/src/Fconf/FconfSettings.ts
deleted file mode 100644
index 12c5e8a0e..000000000
--- a/src/Fconf/FconfSettings.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-export const FconfSettings = {
-  ENABLE_BASH_HOTKEYS: false,
-  ENABLE_TIMESTAMPS: false,
-  MAIN_MENU_STYLE: "default",
-  THEME_BACKGROUND_COLOR: "#000000",
-  THEME_FONT_COLOR: "#66ff33",
-  THEME_HIGHLIGHT_COLOR: "#ffffff",
-  THEME_PROMPT_COLOR: "#f92672",
-  WRAP_INPUT: false,
-};
diff --git a/src/Gang/ui/GangRoot.tsx b/src/Gang/ui/GangRoot.tsx
index d746d7626..5ee45804f 100644
--- a/src/Gang/ui/GangRoot.tsx
+++ b/src/Gang/ui/GangRoot.tsx
@@ -8,13 +8,13 @@ import { use } from "../../ui/Context";
 import { Factions } from "../../Faction/Factions";
 import { Gang } from "../Gang";
 
-interface IProps {
-  gang: Gang;
-}
-
-export function GangRoot(props: IProps): React.ReactElement {
+export function GangRoot(): React.ReactElement {
   const player = use.Player();
   const router = use.Router();
+  const gang = (function () {
+    if (player.gang === null) throw new Error("Gang should not be null");
+    return player.gang;
+  })();
   const [management, setManagement] = useState(true);
   const setRerender = useState(false)[1];
 
@@ -24,7 +24,7 @@ export function GangRoot(props: IProps): React.ReactElement {
   }, []);
 
   function back(): void {
-    router.toFaction(Factions[props.gang.facName]);
+    router.toFaction(Factions[gang.facName]);
   }
 
   return (
@@ -46,7 +46,7 @@ export function GangRoot(props: IProps): React.ReactElement {
       >
         Gang Territory
       </a>
-      {management ? <ManagementSubpage gang={props.gang} player={player} /> : <TerritorySubpage gang={props.gang} />}
+      {management ? <ManagementSubpage gang={gang} player={player} /> : <TerritorySubpage gang={gang} />}
     </div>
   );
 }
diff --git a/src/Hacknet/HacknetHelpers.tsx b/src/Hacknet/HacknetHelpers.tsx
index 38411fdba..d5fd1345a 100644
--- a/src/Hacknet/HacknetHelpers.tsx
+++ b/src/Hacknet/HacknetHelpers.tsx
@@ -479,13 +479,12 @@ export function purchaseHashUpgrade(player: IPlayer, upgName: string, upgTarget:
         break;
       }
       case "Sell for Corporation Funds": {
-        // This will throw if player doesn't have a corporation
-        try {
-          player.corporation.funds = player.corporation.funds.plus(upg.value);
-        } catch (e) {
+        const corp = player.corporation;
+        if (corp === null) {
           player.hashManager.refundUpgrade(upgName);
           return false;
         }
+        corp.funds = corp.funds.plus(upg.value);
         break;
       }
       case "Reduce Minimum Security": {
@@ -530,36 +529,35 @@ export function purchaseHashUpgrade(player: IPlayer, upgName: string, upgTarget:
       }
       case "Exchange for Corporation Research": {
         // This will throw if player doesn't have a corporation
-        try {
-          for (const division of player.corporation.divisions) {
-            division.sciResearch.qty += upg.value;
-          }
-        } catch (e) {
+        const corp = player.corporation;
+        if (corp === null) {
           player.hashManager.refundUpgrade(upgName);
           return false;
         }
+        for (const division of corp.divisions) {
+          division.sciResearch.qty += upg.value;
+        }
         break;
       }
       case "Exchange for Bladeburner Rank": {
         // This will throw if player isnt in Bladeburner
-        try {
-          player.bladeburner.changeRank(player, upg.value);
-        } catch (e) {
+        const bladeburner = player.bladeburner;
+        if (bladeburner === null) {
           player.hashManager.refundUpgrade(upgName);
           return false;
         }
+        bladeburner.changeRank(player, upg.value);
         break;
       }
       case "Exchange for Bladeburner SP": {
-        // This will throw if player isn't in Bladeburner
-        try {
-          // As long as we don't change `Bladeburner.totalSkillPoints`, this
-          // shouldn't affect anything else
-          player.bladeburner.skillPoints += upg.value;
-        } catch (e) {
+        // This will throw if player isnt in Bladeburner
+        const bladeburner = player.bladeburner;
+        if (bladeburner === null) {
           player.hashManager.refundUpgrade(upgName);
           return false;
         }
+
+        bladeburner.skillPoints += upg.value;
         break;
       }
       case "Generate Coding Contract": {
diff --git a/src/NetscriptFunctions.js b/src/NetscriptFunctions.js
index 787e52c30..86627e160 100644
--- a/src/NetscriptFunctions.js
+++ b/src/NetscriptFunctions.js
@@ -3808,7 +3808,7 @@ function NetscriptFunctions(workerScript) {
         throw makeRuntimeErrorMsg("commitCrime", `Invalid crime: '${crimeRoughName}'`);
       }
       workerScript.log("commitCrime", `Attempting to commit ${crime.name}...`);
-      return crime.commit(Router, Player, 1, { workerscript: workerScript });
+      return crime.commit(Router, Player, 1, workerScript);
     },
     getCrimeChance: function (crimeRoughName) {
       updateDynamicRam("getCrimeChance", getRamCost("getCrimeChance"));
diff --git a/src/PersonObjects/IPlayer.ts b/src/PersonObjects/IPlayer.ts
index 993d8a064..393a8995b 100644
--- a/src/PersonObjects/IPlayer.ts
+++ b/src/PersonObjects/IPlayer.ts
@@ -26,6 +26,8 @@ import { IGang } from "../Gang/IGang";
 import { IBladeburner } from "../Bladeburner/IBladeburner";
 import { ICodingContractReward } from "../CodingContracts";
 import { IRouter } from "../ui/Router";
+import { WorkerScript } from "../Netscript/WorkerScript";
+import { HacknetServer } from "../Hacknet/HacknetServer";
 
 export interface IPlayer {
   // Class members
@@ -33,14 +35,12 @@ export interface IPlayer {
   bitNodeN: number;
   city: CityName;
   companyName: string;
-  corporation: ICorporation;
-  gang: IGang;
-  bladeburner: IBladeburner;
+  corporation: ICorporation | null;
+  gang: IGang | null;
+  bladeburner: IBladeburner | null;
   currentServer: string;
   factions: string[];
   factionInvitations: string[];
-  firstProgramAvailable: boolean;
-  firstTimeTraveled: boolean;
   hacknetNodes: (HacknetNode | string)[]; // HacknetNode object or IP of Hacknet Server
   has4SData: boolean;
   has4SDataTixApi: boolean;
@@ -122,9 +122,13 @@ export interface IPlayer {
   bladeburner_analysis_mult: number;
   bladeburner_success_chance_mult: number;
 
+  createProgramReqLvl: number;
+  factionWorkType: string;
   createProgramName: string;
   timeWorkedCreateProgram: number;
   crimeType: string;
+  committingCrimeThruSingFn: boolean;
+  singFnCrimeWorkerScript: WorkerScript | null;
   timeNeededToCompleteWork: number;
   focus: boolean;
   className: string;
@@ -151,20 +155,23 @@ export interface IPlayer {
   workMoneyLossRate: number;
 
   // Methods
-  applyForAgentJob(sing?: boolean): boolean | void;
-  applyForBusinessConsultantJob(sing?: boolean): boolean | void;
-  applyForBusinessJob(sing?: boolean): boolean | void;
-  applyForEmployeeJob(sing?: boolean): boolean | void;
-  applyForItJob(sing?: boolean): boolean | void;
-  applyForJob(entryPosType: CompanyPosition, sing?: boolean): boolean | void;
-  applyForNetworkEngineerJob(sing?: boolean): boolean | void;
-  applyForPartTimeEmployeeJob(sing?: boolean): boolean | void;
-  applyForPartTimeWaiterJob(sing?: boolean): boolean | void;
-  applyForSecurityEngineerJob(sing?: boolean): boolean | void;
-  applyForSecurityJob(sing?: boolean): boolean | void;
-  applyForSoftwareConsultantJob(sing?: boolean): boolean | void;
-  applyForSoftwareJob(sing?: boolean): boolean | void;
-  applyForWaiterJob(sing?: boolean): boolean | void;
+  work(numCycles: number): boolean;
+  workPartTime(numCycles: number): boolean;
+  workForFaction(numCycles: number): boolean;
+  applyForAgentJob(sing?: boolean): boolean;
+  applyForBusinessConsultantJob(sing?: boolean): boolean;
+  applyForBusinessJob(sing?: boolean): boolean;
+  applyForEmployeeJob(sing?: boolean): boolean;
+  applyForItJob(sing?: boolean): boolean;
+  applyForJob(entryPosType: CompanyPosition, sing?: boolean): boolean;
+  applyForNetworkEngineerJob(sing?: boolean): boolean;
+  applyForPartTimeEmployeeJob(sing?: boolean): boolean;
+  applyForPartTimeWaiterJob(sing?: boolean): boolean;
+  applyForSecurityEngineerJob(sing?: boolean): boolean;
+  applyForSecurityJob(sing?: boolean): boolean;
+  applyForSoftwareConsultantJob(sing?: boolean): boolean;
+  applyForSoftwareJob(sing?: boolean): boolean;
+  applyForWaiterJob(sing?: boolean): boolean;
   canAccessBladeburner(): boolean;
   canAccessCorporation(): boolean;
   canAccessGang(): boolean;
@@ -178,11 +185,11 @@ export interface IPlayer {
   gainCharismaExp(exp: number): void;
   gainIntelligenceExp(exp: number): void;
   gainMoney(money: number): void;
-  getCurrentServer(): Server;
+  getCurrentServer(): Server | HacknetServer;
   getGangFaction(): Faction;
   getGangName(): string;
   getHomeComputer(): Server;
-  getNextCompanyPosition(company: Company, entryPosType: CompanyPosition): CompanyPosition;
+  getNextCompanyPosition(company: Company, entryPosType: CompanyPosition): CompanyPosition | null;
   getUpgradeHomeRamCost(): number;
   gotoLocation(to: LocationName): boolean;
   hasAugmentation(aug: Augmentation): boolean;
@@ -201,6 +208,7 @@ export interface IPlayer {
   setMoney(amt: number): void;
   singularityStopWork(): void;
   startBladeburner(p: any): void;
+  startFactionWork(router: IRouter, faction: Faction): void;
   startClass(router: IRouter, costMult: number, expMult: number, className: string): void;
   startCorporation(corpName: string, additionalShares?: number): void;
   startCrime(
@@ -237,11 +245,31 @@ export interface IPlayer {
   updateSkillLevels(): void;
   gainCodingContractReward(reward: ICodingContractReward, difficulty?: number): string;
   stopFocusing(): void;
-  finishFactionWork(cancelled: boolean, sing?: boolean): void;
-  finishClass(sing?: boolean): void;
-  finishWork(cancelled: boolean, sing?: boolean): void;
+  finishFactionWork(cancelled: boolean, sing?: boolean): string;
+  finishClass(sing?: boolean): string;
+  finishWork(cancelled: boolean, sing?: boolean): string;
   cancelationPenalty(): number;
-  finishWorkPartTime(sing?: boolean): void;
-  finishCrime(cancelled: boolean): void;
-  finishCreateProgramWork(cancelled: boolean): void;
+  finishWorkPartTime(sing?: boolean): string;
+  finishCrime(cancelled: boolean): string;
+  finishCreateProgramWork(cancelled: boolean): string;
+  resetMultipliers(): void;
+  prestigeAugmentation(): void;
+  prestigeSourceFile(): void;
+  calculateSkill(exp: number, mult?: number): number;
+  resetWorkStatus(generalType?: string, group?: string, workType?: string): void;
+  getWorkHackExpGain(): number;
+  getWorkStrExpGain(): number;
+  getWorkDefExpGain(): number;
+  getWorkDexExpGain(): number;
+  getWorkAgiExpGain(): number;
+  getWorkChaExpGain(): number;
+  getWorkRepGain(): number;
+  getWorkMoneyGain(): number;
+  processWorkEarnings(cycles: number): void;
+  hospitalize(): void;
+  createProgramWork(numCycles: number): boolean;
+  takeClass(numCycles: number): boolean;
+  commitCrime(numCycles: number): boolean;
+  checkForFactionInvitations(): void;
+  setBitNodeNumber(n: number): void;
 }
diff --git a/src/PersonObjects/Player/PlayerObject.js b/src/PersonObjects/Player/PlayerObject.js
deleted file mode 100644
index 32022fa3c..000000000
--- a/src/PersonObjects/Player/PlayerObject.js
+++ /dev/null
@@ -1,224 +0,0 @@
-import * as augmentationMethods from "./PlayerObjectAugmentationMethods";
-import * as bladeburnerMethods from "./PlayerObjectBladeburnerMethods";
-import * as corporationMethods from "./PlayerObjectCorporationMethods";
-import * as gangMethods from "./PlayerObjectGangMethods";
-import * as generalMethods from "./PlayerObjectGeneralMethods";
-import * as serverMethods from "./PlayerObjectServerMethods";
-
-import { HashManager } from "../../Hacknet/HashManager";
-import { CityName } from "../../Locations/data/CityNames";
-
-import { MoneySourceTracker } from "../../utils/MoneySourceTracker";
-import { Reviver, Generic_toJSON, Generic_fromJSON } from "../../../utils/JSONReviver";
-
-import Decimal from "decimal.js";
-
-export function PlayerObject() {
-  //Skills and stats
-  this.hacking_skill = 1;
-
-  //Combat stats
-  this.hp = 10;
-  this.max_hp = 10;
-  this.strength = 1;
-  this.defense = 1;
-  this.dexterity = 1;
-  this.agility = 1;
-
-  //Labor stats
-  this.charisma = 1;
-
-  //Special stats
-  this.intelligence = 0;
-
-  //Hacking multipliers
-  this.hacking_chance_mult = 1;
-  this.hacking_speed_mult = 1;
-  this.hacking_money_mult = 1;
-  this.hacking_grow_mult = 1;
-
-  //Experience and multipliers
-  this.hacking_exp = 0;
-  this.strength_exp = 0;
-  this.defense_exp = 0;
-  this.dexterity_exp = 0;
-  this.agility_exp = 0;
-  this.charisma_exp = 0;
-  this.intelligence_exp = 0;
-
-  this.hacking_mult = 1;
-  this.strength_mult = 1;
-  this.defense_mult = 1;
-  this.dexterity_mult = 1;
-  this.agility_mult = 1;
-  this.charisma_mult = 1;
-
-  this.hacking_exp_mult = 1;
-  this.strength_exp_mult = 1;
-  this.defense_exp_mult = 1;
-  this.dexterity_exp_mult = 1;
-  this.agility_exp_mult = 1;
-  this.charisma_exp_mult = 1;
-
-  this.company_rep_mult = 1;
-  this.faction_rep_mult = 1;
-
-  //Money
-  this.money = new Decimal(1000);
-
-  //IP Address of Starting (home) computer
-  this.homeComputer = "";
-
-  //Location information
-  this.city = CityName.Sector12;
-  this.location = "";
-
-  // Jobs that the player holds
-  // Map of company name (key) -> name of company position (value. Just the name, not the CompanyPosition object)
-  // The CompanyPosition name must match a key value in CompanyPositions
-  this.jobs = {};
-
-  // Company at which player is CURRENTLY working (only valid when the player is actively working)
-  this.companyName = ""; // Name of Company. Must match a key value in Companies map
-
-  // Servers
-  this.currentServer = ""; //IP address of Server currently being accessed through terminal
-  this.purchasedServers = []; //IP Addresses of purchased servers
-
-  // Hacknet Nodes/Servers
-  this.hacknetNodes = []; // Note: For Hacknet Servers, this array holds the IP addresses of the servers
-  this.hashManager = new HashManager();
-
-  //Factions
-  this.factions = []; //Names of all factions player has joined
-  this.factionInvitations = []; //Outstanding faction invitations
-
-  //Augmentations
-  this.queuedAugmentations = [];
-  this.augmentations = [];
-
-  this.sourceFiles = [];
-
-  //Crime statistics
-  this.numPeopleKilled = 0;
-  this.karma = 0;
-
-  this.crime_money_mult = 1;
-  this.crime_success_mult = 1;
-
-  //Flags/variables for working (Company, Faction, Creating Program, Taking Class)
-  this.isWorking = false;
-  this.focus = false;
-  this.workType = "";
-
-  this.currentWorkFactionName = "";
-  this.currentWorkFactionDescription = "";
-
-  this.workHackExpGainRate = 0;
-  this.workStrExpGainRate = 0;
-  this.workDefExpGainRate = 0;
-  this.workDexExpGainRate = 0;
-  this.workAgiExpGainRate = 0;
-  this.workChaExpGainRate = 0;
-  this.workRepGainRate = 0;
-  this.workMoneyGainRate = 0;
-  this.workMoneyLossRate = 0;
-
-  this.workHackExpGained = 0;
-  this.workStrExpGained = 0;
-  this.workDefExpGained = 0;
-  this.workDexExpGained = 0;
-  this.workAgiExpGained = 0;
-  this.workChaExpGained = 0;
-  this.workRepGained = 0;
-  this.workMoneyGained = 0;
-
-  this.createProgramName = "";
-  this.createProgramReqLvl = 0;
-
-  this.className = "";
-
-  this.crimeType = "";
-
-  this.timeWorked = 0; //in ms
-  this.timeWorkedCreateProgram = 0;
-  this.timeNeededToCompleteWork = 0;
-
-  this.work_money_mult = 1;
-
-  //Hacknet Node multipliers
-  this.hacknet_node_money_mult = 1;
-  this.hacknet_node_purchase_cost_mult = 1;
-  this.hacknet_node_ram_cost_mult = 1;
-  this.hacknet_node_core_cost_mult = 1;
-  this.hacknet_node_level_cost_mult = 1;
-
-  //Stock Market
-  this.hasWseAccount = false;
-  this.hasTixApiAccess = false;
-  this.has4SData = false;
-  this.has4SDataTixApi = false;
-
-  //Gang
-  this.gang = null;
-
-  //Corporation
-  this.corporation = null;
-
-  //Bladeburner
-  this.bladeburner = null;
-  this.bladeburner_max_stamina_mult = 1;
-  this.bladeburner_stamina_gain_mult = 1;
-  this.bladeburner_analysis_mult = 1; //Field Analysis Only
-  this.bladeburner_success_chance_mult = 1;
-
-  // Sleeves & Re-sleeving
-  this.sleeves = [];
-  this.resleeves = [];
-  this.sleevesFromCovenant = 0; // # of Duplicate sleeves purchased from the covenant
-
-  //bitnode
-  this.bitNodeN = 1;
-
-  //Flags for determining whether certain "thresholds" have been achieved
-  this.firstFacInvRecvd = false;
-  this.firstAugPurchased = false;
-  this.firstTimeTraveled = false;
-  this.firstProgramAvailable = false;
-
-  //Used to store the last update time.
-  this.lastUpdate = 0;
-  this.totalPlaytime = 0;
-  this.playtimeSinceLastAug = 0;
-  this.playtimeSinceLastBitnode = 0;
-
-  // Keep track of where money comes from
-  this.moneySourceA = new MoneySourceTracker(); // Where money comes from since last-installed Augmentation
-  this.moneySourceB = new MoneySourceTracker(); // Where money comes from for this entire BitNode run
-
-  // Production since last Augmentation installation
-  this.scriptProdSinceLastAug = 0;
-
-  this.exploits = [];
-}
-
-// Apply player methods to the prototype using Object.assign()
-Object.assign(
-  PlayerObject.prototype,
-  generalMethods,
-  serverMethods,
-  bladeburnerMethods,
-  corporationMethods,
-  gangMethods,
-  augmentationMethods,
-);
-
-PlayerObject.prototype.toJSON = function () {
-  return Generic_toJSON("PlayerObject", this);
-};
-
-PlayerObject.fromJSON = function (value) {
-  return Generic_fromJSON(PlayerObject, value.data);
-};
-
-Reviver.constructors.PlayerObject = PlayerObject;
diff --git a/src/PersonObjects/Player/PlayerObject.ts b/src/PersonObjects/Player/PlayerObject.ts
new file mode 100644
index 000000000..ad7da4a70
--- /dev/null
+++ b/src/PersonObjects/Player/PlayerObject.ts
@@ -0,0 +1,591 @@
+import * as augmentationMethods from "./PlayerObjectAugmentationMethods";
+import * as bladeburnerMethods from "./PlayerObjectBladeburnerMethods";
+import * as corporationMethods from "./PlayerObjectCorporationMethods";
+import * as gangMethods from "./PlayerObjectGangMethods";
+import * as generalMethods from "./PlayerObjectGeneralMethods";
+import * as serverMethods from "./PlayerObjectServerMethods";
+
+import { IMap } from "../../types";
+import { Resleeve } from "../Resleeving/Resleeve";
+import { Sleeve } from "../Sleeve/Sleeve";
+import { IPlayerOwnedSourceFile } from "../../SourceFile/PlayerOwnedSourceFile";
+import { Exploit } from "../../Exploits/Exploit";
+import { WorkerScript } from "../../Netscript/WorkerScript";
+import { CompanyPosition } from "../../Company/CompanyPosition";
+import { Server } from "../../Server/Server";
+import { HacknetServer } from "../../Hacknet/HacknetServer";
+import { Faction } from "../../Faction/Faction";
+import { Company } from "../../Company/Company";
+import { Augmentation } from "../../Augmentation/Augmentation";
+import { IRouter } from "../../ui/Router";
+import { ICodingContractReward } from "../../CodingContracts";
+
+import { IPlayer } from "../IPlayer";
+import { LocationName } from "../../Locations/data/LocationNames";
+import { IPlayerOwnedAugmentation } from "../../Augmentation/PlayerOwnedAugmentation";
+import { ICorporation } from "../../Corporation/ICorporation";
+import { IGang } from "../../Gang/IGang";
+import { IBladeburner } from "../../Bladeburner/IBladeburner";
+import { HacknetNode } from "../../Hacknet/HacknetNode";
+
+import { HashManager } from "../../Hacknet/HashManager";
+import { CityName } from "../../Locations/data/CityNames";
+
+import { MoneySourceTracker } from "../../utils/MoneySourceTracker";
+import { Reviver, Generic_toJSON, Generic_fromJSON } from "../../../utils/JSONReviver";
+
+import Decimal from "decimal.js";
+
+export class PlayerObject implements IPlayer {
+  // Class members
+  augmentations: IPlayerOwnedAugmentation[];
+  bitNodeN: number;
+  city: CityName;
+  companyName: string;
+  corporation: ICorporation | null;
+  gang: IGang | null;
+  bladeburner: IBladeburner | null;
+  currentServer: string;
+  factions: string[];
+  factionInvitations: string[];
+  hacknetNodes: (HacknetNode | string)[]; // HacknetNode object or IP of Hacknet Server
+  has4SData: boolean;
+  has4SDataTixApi: boolean;
+  hashManager: HashManager;
+  hasTixApiAccess: boolean;
+  hasWseAccount: boolean;
+  homeComputer: string;
+  hp: number;
+  jobs: IMap<string>;
+  init: () => void;
+  isWorking: boolean;
+  karma: number;
+  numPeopleKilled: number;
+  location: LocationName;
+  max_hp: number;
+  money: any;
+  moneySourceA: MoneySourceTracker;
+  moneySourceB: MoneySourceTracker;
+  playtimeSinceLastAug: number;
+  playtimeSinceLastBitnode: number;
+  purchasedServers: any[];
+  queuedAugmentations: IPlayerOwnedAugmentation[];
+  resleeves: Resleeve[];
+  scriptProdSinceLastAug: number;
+  sleeves: Sleeve[];
+  sleevesFromCovenant: number;
+  sourceFiles: IPlayerOwnedSourceFile[];
+  exploits: Exploit[];
+  lastUpdate: number;
+  totalPlaytime: number;
+
+  // Stats
+  hacking_skill: number;
+  strength: number;
+  defense: number;
+  dexterity: number;
+  agility: number;
+  charisma: number;
+  intelligence: number;
+
+  // Experience
+  hacking_exp: number;
+  strength_exp: number;
+  defense_exp: number;
+  dexterity_exp: number;
+  agility_exp: number;
+  charisma_exp: number;
+  intelligence_exp: number;
+
+  // Multipliers
+  hacking_chance_mult: number;
+  hacking_speed_mult: number;
+  hacking_money_mult: number;
+  hacking_grow_mult: number;
+  hacking_mult: number;
+  hacking_exp_mult: number;
+  strength_mult: number;
+  strength_exp_mult: number;
+  defense_mult: number;
+  defense_exp_mult: number;
+  dexterity_mult: number;
+  dexterity_exp_mult: number;
+  agility_mult: number;
+  agility_exp_mult: number;
+  charisma_mult: number;
+  charisma_exp_mult: number;
+  hacknet_node_money_mult: number;
+  hacknet_node_purchase_cost_mult: number;
+  hacknet_node_ram_cost_mult: number;
+  hacknet_node_core_cost_mult: number;
+  hacknet_node_level_cost_mult: number;
+  company_rep_mult: number;
+  faction_rep_mult: number;
+  work_money_mult: number;
+  crime_success_mult: number;
+  crime_money_mult: number;
+  bladeburner_max_stamina_mult: number;
+  bladeburner_stamina_gain_mult: number;
+  bladeburner_analysis_mult: number;
+  bladeburner_success_chance_mult: number;
+
+  createProgramReqLvl: number;
+  factionWorkType: string;
+  createProgramName: string;
+  timeWorkedCreateProgram: number;
+  crimeType: string;
+  committingCrimeThruSingFn: boolean;
+  singFnCrimeWorkerScript: WorkerScript | null;
+  timeNeededToCompleteWork: number;
+  focus: boolean;
+  className: string;
+  currentWorkFactionName: string;
+  workType: string;
+  currentWorkFactionDescription: string;
+  timeWorked: number;
+  workMoneyGained: number;
+  workMoneyGainRate: number;
+  workRepGained: number;
+  workRepGainRate: number;
+  workHackExpGained: number;
+  workHackExpGainRate: number;
+  workStrExpGained: number;
+  workStrExpGainRate: number;
+  workDefExpGained: number;
+  workDefExpGainRate: number;
+  workDexExpGained: number;
+  workDexExpGainRate: number;
+  workAgiExpGained: number;
+  workAgiExpGainRate: number;
+  workChaExpGained: number;
+  workChaExpGainRate: number;
+  workMoneyLossRate: number;
+
+  // Methods
+  work: (numCycles: number) => boolean;
+  workPartTime: (numCycles: number) => boolean;
+  workForFaction: (numCycles: number) => boolean;
+  applyForAgentJob: (sing?: boolean) => boolean;
+  applyForBusinessConsultantJob: (sing?: boolean) => boolean;
+  applyForBusinessJob: (sing?: boolean) => boolean;
+  applyForEmployeeJob: (sing?: boolean) => boolean;
+  applyForItJob: (sing?: boolean) => boolean;
+  applyForJob: (entryPosType: CompanyPosition, sing?: boolean) => boolean;
+  applyForNetworkEngineerJob: (sing?: boolean) => boolean;
+  applyForPartTimeEmployeeJob: (sing?: boolean) => boolean;
+  applyForPartTimeWaiterJob: (sing?: boolean) => boolean;
+  applyForSecurityEngineerJob: (sing?: boolean) => boolean;
+  applyForSecurityJob: (sing?: boolean) => boolean;
+  applyForSoftwareConsultantJob: (sing?: boolean) => boolean;
+  applyForSoftwareJob: (sing?: boolean) => boolean;
+  applyForWaiterJob: (sing?: boolean) => boolean;
+  canAccessBladeburner: () => boolean;
+  canAccessCorporation: () => boolean;
+  canAccessGang: () => boolean;
+  canAccessResleeving: () => boolean;
+  canAfford: (cost: number) => boolean;
+  gainHackingExp: (exp: number) => void;
+  gainStrengthExp: (exp: number) => void;
+  gainDefenseExp: (exp: number) => void;
+  gainDexterityExp: (exp: number) => void;
+  gainAgilityExp: (exp: number) => void;
+  gainCharismaExp: (exp: number) => void;
+  gainIntelligenceExp: (exp: number) => void;
+  gainMoney: (money: number) => void;
+  getCurrentServer: () => Server | HacknetServer;
+  getGangFaction: () => Faction;
+  getGangName: () => string;
+  getHomeComputer: () => Server;
+  getNextCompanyPosition: (company: Company, entryPosType: CompanyPosition) => CompanyPosition | null;
+  getUpgradeHomeRamCost: () => number;
+  gotoLocation: (to: LocationName) => boolean;
+  hasAugmentation: (aug: Augmentation) => boolean;
+  hasCorporation: () => boolean;
+  hasGangWith: (facName: string) => boolean;
+  hasTorRouter: () => boolean;
+  hasProgram: (program: string) => boolean;
+  inBladeburner: () => boolean;
+  inGang: () => boolean;
+  isQualified: (company: Company, position: CompanyPosition) => boolean;
+  loseMoney: (money: number) => void;
+  reapplyAllAugmentations: (resetMultipliers: boolean) => void;
+  reapplyAllSourceFiles: () => void;
+  regenerateHp: (amt: number) => void;
+  recordMoneySource: (amt: number, source: string) => void;
+  setMoney: (amt: number) => void;
+  singularityStopWork: () => void;
+  startBladeburner: (p: any) => void;
+  startFactionWork: (router: IRouter, faction: Faction) => void;
+  startClass: (router: IRouter, costMult: number, expMult: number, className: string) => void;
+  startCorporation: (corpName: string, additionalShares?: number) => void;
+  startCrime: (
+    router: IRouter,
+    crimeType: string,
+    hackExp: number,
+    strExp: number,
+    defExp: number,
+    dexExp: number,
+    agiExp: number,
+    chaExp: number,
+    money: number,
+    time: number,
+    singParams: any,
+  ) => void;
+  startFactionFieldWork: (router: IRouter, faction: Faction) => void;
+  startFactionHackWork: (router: IRouter, faction: Faction) => void;
+  startFactionSecurityWork: (router: IRouter, faction: Faction) => void;
+  startFocusing: () => void;
+  startGang: (facName: string, isHacking: boolean) => void;
+  startWork: (router: IRouter, companyName: string) => void;
+  startWorkPartTime: (router: IRouter, companyName: string) => void;
+  takeDamage: (amt: number) => boolean;
+  travel: (to: CityName) => boolean;
+  giveExploit: (exploit: Exploit) => void;
+  queryStatFromString: (str: string) => number;
+  getIntelligenceBonus: (weight: number) => number;
+  getCasinoWinnings: () => number;
+  quitJob: (company: string) => void;
+  createHacknetServer: () => void;
+  startCreateProgramWork: (router: IRouter, programName: string, time: number, reqLevel: number) => void;
+  queueAugmentation: (augmentationName: string) => void;
+  receiveInvite: (factionName: string) => void;
+  updateSkillLevels: () => void;
+  gainCodingContractReward: (reward: ICodingContractReward, difficulty?: number) => string;
+  stopFocusing: () => void;
+  finishFactionWork: (cancelled: boolean, sing?: boolean) => string;
+  finishClass: (sing?: boolean) => string;
+  finishWork: (cancelled: boolean, sing?: boolean) => string;
+  cancelationPenalty: () => number;
+  finishWorkPartTime: (sing?: boolean) => string;
+  finishCrime: (cancelled: boolean) => string;
+  finishCreateProgramWork: (cancelled: boolean) => string;
+  resetMultipliers: () => void;
+  prestigeAugmentation: () => void;
+  prestigeSourceFile: () => void;
+  calculateSkill: (exp: number, mult?: number) => number;
+  resetWorkStatus: (generalType?: string, group?: string, workType?: string) => void;
+  getWorkHackExpGain: () => number;
+  getWorkStrExpGain: () => number;
+  getWorkDefExpGain: () => number;
+  getWorkDexExpGain: () => number;
+  getWorkAgiExpGain: () => number;
+  getWorkChaExpGain: () => number;
+  getWorkRepGain: () => number;
+  getWorkMoneyGain: () => number;
+  processWorkEarnings: (cycles: number) => void;
+  hospitalize: () => void;
+  createProgramWork: (numCycles: number) => boolean;
+  takeClass: (numCycles: number) => boolean;
+  commitCrime: (numCycles: number) => boolean;
+  checkForFactionInvitations: () => void;
+  setBitNodeNumber: (n: number) => void;
+
+  constructor() {
+    //Skills and stats
+    this.hacking_skill = 1;
+
+    //Combat stats
+    this.hp = 10;
+    this.max_hp = 10;
+    this.strength = 1;
+    this.defense = 1;
+    this.dexterity = 1;
+    this.agility = 1;
+
+    //Labor stats
+    this.charisma = 1;
+
+    //Special stats
+    this.intelligence = 0;
+
+    //Hacking multipliers
+    this.hacking_chance_mult = 1;
+    this.hacking_speed_mult = 1;
+    this.hacking_money_mult = 1;
+    this.hacking_grow_mult = 1;
+
+    //Experience and multipliers
+    this.hacking_exp = 0;
+    this.strength_exp = 0;
+    this.defense_exp = 0;
+    this.dexterity_exp = 0;
+    this.agility_exp = 0;
+    this.charisma_exp = 0;
+    this.intelligence_exp = 0;
+
+    this.hacking_mult = 1;
+    this.strength_mult = 1;
+    this.defense_mult = 1;
+    this.dexterity_mult = 1;
+    this.agility_mult = 1;
+    this.charisma_mult = 1;
+
+    this.hacking_exp_mult = 1;
+    this.strength_exp_mult = 1;
+    this.defense_exp_mult = 1;
+    this.dexterity_exp_mult = 1;
+    this.agility_exp_mult = 1;
+    this.charisma_exp_mult = 1;
+
+    this.company_rep_mult = 1;
+    this.faction_rep_mult = 1;
+
+    //Money
+    this.money = new Decimal(1000);
+
+    //IP Address of Starting (home) computer
+    this.homeComputer = "";
+
+    //Location information
+    this.city = CityName.Sector12;
+    this.location = LocationName.TravelAgency;
+
+    // Jobs that the player holds
+    // Map of company name (key) -> name of company position (value. Just the name, not the CompanyPosition object)
+    // The CompanyPosition name must match a key value in CompanyPositions
+    this.jobs = {};
+
+    // Company at which player is CURRENTLY working (only valid when the player is actively working)
+    this.companyName = ""; // Name of Company. Must match a key value in Companies ma;
+
+    // Servers
+    this.currentServer = ""; //IP address of Server currently being accessed through termina;
+    this.purchasedServers = []; //IP Addresses of purchased server;
+
+    // Hacknet Nodes/Servers
+    this.hacknetNodes = []; // Note= For Hacknet Servers, this array holds the IP addresses of the server;
+    this.hashManager = new HashManager();
+
+    //Factions
+    this.factions = []; //Names of all factions player has joine;
+    this.factionInvitations = []; //Outstanding faction invitation;
+
+    //Augmentations
+    this.queuedAugmentations = [];
+    this.augmentations = [];
+
+    this.sourceFiles = [];
+
+    //Crime statistics
+    this.numPeopleKilled = 0;
+    this.karma = 0;
+
+    this.crime_money_mult = 1;
+    this.crime_success_mult = 1;
+
+    //Flags/variables for working (Company, Faction, Creating Program, Taking Class)
+    this.isWorking = false;
+    this.focus = false;
+    this.workType = "";
+
+    this.currentWorkFactionName = "";
+    this.currentWorkFactionDescription = "";
+
+    this.workHackExpGainRate = 0;
+    this.workStrExpGainRate = 0;
+    this.workDefExpGainRate = 0;
+    this.workDexExpGainRate = 0;
+    this.workAgiExpGainRate = 0;
+    this.workChaExpGainRate = 0;
+    this.workRepGainRate = 0;
+    this.workMoneyGainRate = 0;
+    this.workMoneyLossRate = 0;
+
+    this.workHackExpGained = 0;
+    this.workStrExpGained = 0;
+    this.workDefExpGained = 0;
+    this.workDexExpGained = 0;
+    this.workAgiExpGained = 0;
+    this.workChaExpGained = 0;
+    this.workRepGained = 0;
+    this.workMoneyGained = 0;
+
+    this.createProgramName = "";
+    this.createProgramReqLvl = 0;
+
+    this.className = "";
+
+    this.crimeType = "";
+
+    (this.timeWorked = 0), //in m;
+      (this.timeWorkedCreateProgram = 0);
+    this.timeNeededToCompleteWork = 0;
+
+    this.work_money_mult = 1;
+
+    //Hacknet Node multipliers
+    this.hacknet_node_money_mult = 1;
+    this.hacknet_node_purchase_cost_mult = 1;
+    this.hacknet_node_ram_cost_mult = 1;
+    this.hacknet_node_core_cost_mult = 1;
+    this.hacknet_node_level_cost_mult = 1;
+
+    //Stock Market
+    this.hasWseAccount = false;
+    this.hasTixApiAccess = false;
+    this.has4SData = false;
+    this.has4SDataTixApi = false;
+
+    //Gang
+    this.gang = null;
+
+    //Corporation
+    this.corporation = null;
+
+    //Bladeburner
+    this.bladeburner = null;
+    this.bladeburner_max_stamina_mult = 1;
+    this.bladeburner_stamina_gain_mult = 1;
+    (this.bladeburner_analysis_mult = 1), //Field Analysis Onl;
+      (this.bladeburner_success_chance_mult = 1);
+
+    // Sleeves & Re-sleeving
+    this.sleeves = [];
+    this.resleeves = [];
+    (this.sleevesFromCovenant = 0), // # of Duplicate sleeves purchased from the covenan;
+      //bitnode
+      (this.bitNodeN = 1);
+
+    //Used to store the last update time.
+    this.lastUpdate = 0;
+    this.totalPlaytime = 0;
+    this.playtimeSinceLastAug = 0;
+    this.playtimeSinceLastBitnode = 0;
+
+    // Keep track of where money comes from
+    (this.moneySourceA = new MoneySourceTracker()), // Where money comes from since last-installed Augmentatio;
+      (this.moneySourceB = new MoneySourceTracker()), // Where money comes from for this entire BitNode ru;
+      // Production since last Augmentation installation
+      (this.scriptProdSinceLastAug = 0);
+
+    this.exploits = [];
+
+    this.init = generalMethods.init;
+    this.prestigeAugmentation = generalMethods.prestigeAugmentation;
+    this.prestigeSourceFile = generalMethods.prestigeSourceFile;
+    this.receiveInvite = generalMethods.receiveInvite;
+    this.calculateSkill = generalMethods.calculateSkill;
+    this.updateSkillLevels = generalMethods.updateSkillLevels;
+    this.resetMultipliers = generalMethods.resetMultipliers;
+    this.hasProgram = generalMethods.hasProgram;
+    this.setMoney = generalMethods.setMoney;
+    this.gainMoney = generalMethods.gainMoney;
+    this.loseMoney = generalMethods.loseMoney;
+    this.canAfford = generalMethods.canAfford;
+    this.recordMoneySource = generalMethods.recordMoneySource;
+    this.gainHackingExp = generalMethods.gainHackingExp;
+    this.gainStrengthExp = generalMethods.gainStrengthExp;
+    this.gainDefenseExp = generalMethods.gainDefenseExp;
+    this.gainDexterityExp = generalMethods.gainDexterityExp;
+    this.gainAgilityExp = generalMethods.gainAgilityExp;
+    this.gainCharismaExp = generalMethods.gainCharismaExp;
+    this.gainIntelligenceExp = generalMethods.gainIntelligenceExp;
+    this.queryStatFromString = generalMethods.queryStatFromString;
+    this.resetWorkStatus = generalMethods.resetWorkStatus;
+    this.processWorkEarnings = generalMethods.processWorkEarnings;
+    this.startWork = generalMethods.startWork;
+    this.cancelationPenalty = generalMethods.cancelationPenalty;
+    this.work = generalMethods.work;
+    this.finishWork = generalMethods.finishWork;
+    this.startWorkPartTime = generalMethods.startWorkPartTime;
+    this.workPartTime = generalMethods.workPartTime;
+    this.finishWorkPartTime = generalMethods.finishWorkPartTime;
+    this.startFocusing = generalMethods.startFocusing;
+    this.stopFocusing = generalMethods.stopFocusing;
+    this.startFactionWork = generalMethods.startFactionWork;
+    this.startFactionHackWork = generalMethods.startFactionHackWork;
+    this.startFactionFieldWork = generalMethods.startFactionFieldWork;
+    this.startFactionSecurityWork = generalMethods.startFactionSecurityWork;
+    this.workForFaction = generalMethods.workForFaction;
+    this.finishFactionWork = generalMethods.finishFactionWork;
+    this.getWorkMoneyGain = generalMethods.getWorkMoneyGain;
+    this.getWorkHackExpGain = generalMethods.getWorkHackExpGain;
+    this.getWorkStrExpGain = generalMethods.getWorkStrExpGain;
+    this.getWorkDefExpGain = generalMethods.getWorkDefExpGain;
+    this.getWorkDexExpGain = generalMethods.getWorkDexExpGain;
+    this.getWorkAgiExpGain = generalMethods.getWorkAgiExpGain;
+    this.getWorkChaExpGain = generalMethods.getWorkChaExpGain;
+    this.getWorkRepGain = generalMethods.getWorkRepGain;
+    this.startCreateProgramWork = generalMethods.startCreateProgramWork;
+    this.createProgramWork = generalMethods.createProgramWork;
+    this.finishCreateProgramWork = generalMethods.finishCreateProgramWork;
+    this.startClass = generalMethods.startClass;
+    this.takeClass = generalMethods.takeClass;
+    this.finishClass = generalMethods.finishClass;
+    this.startCrime = generalMethods.startCrime;
+    this.commitCrime = generalMethods.commitCrime;
+    this.finishCrime = generalMethods.finishCrime;
+    this.singularityStopWork = generalMethods.singularityStopWork;
+    this.takeDamage = generalMethods.takeDamage;
+    this.regenerateHp = generalMethods.regenerateHp;
+    this.hospitalize = generalMethods.hospitalize;
+    this.applyForJob = generalMethods.applyForJob;
+    this.getNextCompanyPosition = generalMethods.getNextCompanyPosition;
+    this.quitJob = generalMethods.quitJob;
+    this.applyForSoftwareJob = generalMethods.applyForSoftwareJob;
+    this.applyForSoftwareConsultantJob = generalMethods.applyForSoftwareConsultantJob;
+    this.applyForItJob = generalMethods.applyForItJob;
+    this.applyForSecurityEngineerJob = generalMethods.applyForSecurityEngineerJob;
+    this.applyForNetworkEngineerJob = generalMethods.applyForNetworkEngineerJob;
+    this.applyForBusinessJob = generalMethods.applyForBusinessJob;
+    this.applyForBusinessConsultantJob = generalMethods.applyForBusinessConsultantJob;
+    this.applyForSecurityJob = generalMethods.applyForSecurityJob;
+    this.applyForAgentJob = generalMethods.applyForAgentJob;
+    this.applyForEmployeeJob = generalMethods.applyForEmployeeJob;
+    this.applyForPartTimeEmployeeJob = generalMethods.applyForPartTimeEmployeeJob;
+    this.applyForWaiterJob = generalMethods.applyForWaiterJob;
+    this.applyForPartTimeWaiterJob = generalMethods.applyForPartTimeWaiterJob;
+    this.isQualified = generalMethods.isQualified;
+    this.reapplyAllAugmentations = generalMethods.reapplyAllAugmentations;
+    this.reapplyAllSourceFiles = generalMethods.reapplyAllSourceFiles;
+    this.checkForFactionInvitations = generalMethods.checkForFactionInvitations;
+    this.setBitNodeNumber = generalMethods.setBitNodeNumber;
+    this.queueAugmentation = generalMethods.queueAugmentation;
+    this.gainCodingContractReward = generalMethods.gainCodingContractReward;
+    this.travel = generalMethods.travel;
+    this.gotoLocation = generalMethods.gotoLocation;
+    this.canAccessResleeving = generalMethods.canAccessResleeving;
+    this.giveExploit = generalMethods.giveExploit;
+    this.getIntelligenceBonus = generalMethods.getIntelligenceBonus;
+    this.getCasinoWinnings = generalMethods.getCasinoWinnings;
+    this.hasAugmentation = augmentationMethods.hasAugmentation;
+    this.canAccessBladeburner = bladeburnerMethods.canAccessBladeburner;
+    this.inBladeburner = bladeburnerMethods.inBladeburner;
+    this.startBladeburner = bladeburnerMethods.startBladeburner;
+    this.canAccessCorporation = corporationMethods.canAccessCorporation;
+    this.hasCorporation = corporationMethods.hasCorporation;
+    this.startCorporation = corporationMethods.startCorporation;
+    this.canAccessGang = gangMethods.canAccessGang;
+    this.getGangFaction = gangMethods.getGangFaction;
+    this.getGangName = gangMethods.getGangName;
+    this.hasGangWith = gangMethods.hasGangWith;
+    this.inGang = gangMethods.inGang;
+    this.startGang = gangMethods.startGang;
+
+    this.hasTorRouter = serverMethods.hasTorRouter;
+    this.getCurrentServer = serverMethods.getCurrentServer;
+    this.getHomeComputer = serverMethods.getHomeComputer;
+    this.getUpgradeHomeRamCost = serverMethods.getUpgradeHomeRamCost;
+    this.createHacknetServer = serverMethods.createHacknetServer;
+    this.factionWorkType = "";
+    this.committingCrimeThruSingFn = false;
+    this.singFnCrimeWorkerScript = null;
+  }
+
+  /**
+   * Serialize the current object to a JSON save state.
+   */
+  toJSON(): any {
+    return Generic_toJSON("PlayerObject", this);
+  }
+
+  /**
+   * Initiatizes a PlayerObject object from a JSON save state.
+   */
+  // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+  static fromJSON(value: any): PlayerObject {
+    return Generic_fromJSON(PlayerObject, value.data);
+  }
+}
+
+Reviver.constructors.PlayerObject = PlayerObject;
diff --git a/src/PersonObjects/Player/PlayerObjectGangMethods.ts b/src/PersonObjects/Player/PlayerObjectGangMethods.ts
index b8aea304f..c24783512 100644
--- a/src/PersonObjects/Player/PlayerObjectGangMethods.ts
+++ b/src/PersonObjects/Player/PlayerObjectGangMethods.ts
@@ -20,20 +20,34 @@ export function canAccessGang(this: IPlayer): boolean {
 }
 
 export function getGangFaction(this: IPlayer): Faction {
-  const fac = Factions[this.gang.facName];
+  const gang = this.gang;
+  if (gang === null) {
+    throw new Error("Cannot get gang faction because player is not in a gang.");
+  }
+  const fac = Factions[gang.facName];
   if (fac == null) {
-    throw new Error(`Gang has invalid faction name: ${this.gang.facName}`);
+    throw new Error(`Gang has invalid faction name: ${gang.facName}`);
   }
 
   return fac;
 }
 
 export function getGangName(this: IPlayer): string {
-  return this.inGang() ? this.gang.facName : "";
+  if (!this.inGang()) return "";
+  const gang = this.gang;
+  if (gang === null) {
+    throw new Error("Cannot get gang faction because player is not in a gang.");
+  }
+  return gang.facName;
 }
 
 export function hasGangWith(this: IPlayer, facName: string): boolean {
-  return this.inGang() && this.gang.facName === facName;
+  if (!this.inGang()) return false;
+  const gang = this.gang;
+  if (gang === null) {
+    throw new Error("Cannot get gang faction because player is not in a gang.");
+  }
+  return gang.facName === facName;
 }
 
 export function inGang(this: IPlayer): boolean {
diff --git a/src/PersonObjects/Player/PlayerObjectGeneralMethods.jsx b/src/PersonObjects/Player/PlayerObjectGeneralMethods.tsx
similarity index 84%
rename from src/PersonObjects/Player/PlayerObjectGeneralMethods.jsx
rename to src/PersonObjects/Player/PlayerObjectGeneralMethods.tsx
index 872bb633b..b788202a7 100644
--- a/src/PersonObjects/Player/PlayerObjectGeneralMethods.jsx
+++ b/src/PersonObjects/Player/PlayerObjectGeneralMethods.tsx
@@ -1,3 +1,4 @@
+import { IPlayer } from "../IPlayer";
 import { Augmentations } from "../../Augmentation/Augmentations";
 import { applyAugmentation } from "../../Augmentation/AugmentationHelpers";
 import { PlayerOwnedAugmentation } from "../../Augmentation/PlayerOwnedAugmentation";
@@ -9,11 +10,14 @@ import { Companies } from "../../Company/Companies";
 import { getNextCompanyPositionHelper } from "../../Company/GetNextCompanyPosition";
 import { getJobRequirementText } from "../../Company/GetJobRequirementText";
 import { CompanyPositions } from "../../Company/CompanyPositions";
+import { CompanyPosition } from "../../Company/CompanyPosition";
 import * as posNames from "../../Company/data/companypositionnames";
 import { CONSTANTS } from "../../Constants";
 import { Programs } from "../../Programs/Programs";
 import { determineCrimeSuccess } from "../../Crime/CrimeHelpers";
+import { ICodingContractReward } from "../../CodingContracts";
 import { Crimes } from "../../Crime/Crimes";
+import { Exploit } from "../../Exploits/Exploit";
 import { Faction } from "../../Faction/Faction";
 import { Factions } from "../../Faction/Factions";
 import { resetGangs } from "../../Gang/AllGangs";
@@ -31,6 +35,7 @@ import {
   getFactionFieldWorkRepGain,
 } from "../formulas/reputation";
 import { AllServers, AddToAllServers, createUniqueRandomIp } from "../../Server/AllServers";
+import { Server } from "../../Server/Server";
 import { safetlyCreateUniqueServer } from "../../Server/ServerHelpers";
 import { Settings } from "../../Settings/Settings";
 import { SpecialServerIps, SpecialServerNames } from "../../Server/SpecialServerIps";
@@ -40,10 +45,12 @@ import { SourceFiles } from "../../SourceFile/SourceFiles";
 import { SourceFileFlags } from "../../SourceFile/SourceFileFlags";
 import { influenceStockThroughCompanyWork } from "../../StockMarket/PlayerInfluencing";
 import { getHospitalizationCost } from "../../Hospital/Hospital";
+import { WorkerScript } from "../../Netscript/WorkerScript";
 
 import Decimal from "decimal.js";
 
 import { numeralWrapper } from "../../ui/numeralFormat";
+import { IRouter } from "../../ui/Router";
 import { MoneySourceTracker } from "../../utils/MoneySourceTracker";
 import { dialogBoxCreate } from "../../../utils/DialogBox";
 import { convertTimeMsToTimeElapsedString } from "../../../utils/StringHelperFunctions";
@@ -53,7 +60,7 @@ import { Money } from "../../ui/React/Money";
 
 import React from "react";
 
-export function init() {
+export function init(this: IPlayer) {
   /* Initialize Player's home computer */
   var t_homeComp = safetlyCreateUniqueServer({
     adminRights: true,
@@ -71,7 +78,7 @@ export function init() {
   this.getHomeComputer().programs.push(Programs.NukeProgram.name);
 }
 
-export function prestigeAugmentation() {
+export function prestigeAugmentation(this: IPlayer) {
   var homeComp = this.getHomeComputer();
   this.currentServer = homeComp.ip;
   this.homeComputer = homeComp.ip;
@@ -99,7 +106,7 @@ export function prestigeAugmentation() {
   this.money = new Decimal(1000);
 
   this.city = CityName.Sector12;
-  this.location = "";
+  this.location = LocationName.TravelAgency;
 
   this.companyName = "";
   this.jobs = {};
@@ -113,7 +120,7 @@ export function prestigeAugmentation() {
 
   this.resleeves = [];
 
-  let numSleeves = Math.min(3, SourceFileFlags[10] + (this.bitNodeN === 10 ? 1 : 0)) + this.sleevesFromCovenant;
+  const numSleeves = Math.min(3, SourceFileFlags[10] + (this.bitNodeN === 10 ? 1 : 0)) + this.sleevesFromCovenant;
   if (this.sleeves.length > numSleeves) this.sleeves.length = numSleeves;
   for (let i = this.sleeves.length; i < numSleeves; i++) {
     this.sleeves.push(new Sleeve(this));
@@ -171,7 +178,7 @@ export function prestigeAugmentation() {
   this.hp = this.max_hp;
 }
 
-export function prestigeSourceFile() {
+export function prestigeSourceFile(this: IPlayer) {
   this.prestigeAugmentation();
   // Duplicate sleeves are reset to level 1 every Bit Node (but the number of sleeves you have persists)
   for (let i = 0; i < this.sleeves.length; ++i) {
@@ -202,27 +209,26 @@ export function prestigeSourceFile() {
   this.has4SDataTixApi = false;
 
   // BitNode 3: Corporatocracy
-  this.corporation = 0;
+  this.corporation = null;
 
   this.moneySourceB.reset();
   this.playtimeSinceLastBitnode = 0;
   this.augmentations = [];
 }
 
-export function receiveInvite(factionName) {
+export function receiveInvite(this: IPlayer, factionName: string): void {
   if (this.factionInvitations.includes(factionName) || this.factions.includes(factionName)) {
     return;
   }
-  this.firstFacInvRecvd = true;
   this.factionInvitations.push(factionName);
 }
 
 //Calculates skill level based on experience. The same formula will be used for every skill
-export function calculateSkill(exp, mult = 1) {
+export function calculateSkill(this: IPlayer, exp: number, mult = 1): number {
   return calculateSkillF(exp, mult);
 }
 
-export function updateSkillLevels() {
+export function updateSkillLevels(this: IPlayer): void {
   this.hacking_skill = Math.max(
     1,
     Math.floor(this.calculateSkill(this.hacking_exp, this.hacking_mult * BitNodeMultipliers.HackingLevelMultiplier)),
@@ -261,7 +267,7 @@ export function updateSkillLevels() {
   this.hp = Math.round(this.max_hp * ratio);
 }
 
-export function resetMultipliers() {
+export function resetMultipliers(this: IPlayer): void {
   this.hacking_chance_mult = 1;
   this.hacking_speed_mult = 1;
   this.hacking_money_mult = 1;
@@ -301,7 +307,7 @@ export function resetMultipliers() {
   this.bladeburner_success_chance_mult = 1;
 }
 
-export function hasProgram(programName) {
+export function hasProgram(this: IPlayer, programName: string): boolean {
   const home = this.getHomeComputer();
   if (home == null) {
     return false;
@@ -315,7 +321,7 @@ export function hasProgram(programName) {
   return false;
 }
 
-export function setMoney(money) {
+export function setMoney(this: IPlayer, money: number): void {
   if (isNaN(money)) {
     console.error("NaN passed into Player.setMoney()");
     return;
@@ -323,7 +329,7 @@ export function setMoney(money) {
   this.money = new Decimal(money);
 }
 
-export function gainMoney(money) {
+export function gainMoney(this: IPlayer, money: number): void {
   if (isNaN(money)) {
     console.error("NaN passed into Player.gainMoney()");
     return;
@@ -331,7 +337,7 @@ export function gainMoney(money) {
   this.money = this.money.plus(money);
 }
 
-export function loseMoney(money) {
+export function loseMoney(this: IPlayer, money: number): void {
   if (isNaN(money)) {
     console.error("NaN passed into Player.loseMoney()");
     return;
@@ -340,7 +346,7 @@ export function loseMoney(money) {
   this.money = this.money.minus(money);
 }
 
-export function canAfford(cost) {
+export function canAfford(this: IPlayer, cost: number): boolean {
   if (isNaN(cost)) {
     console.error(`NaN passed into Player.canAfford()`);
     return false;
@@ -348,7 +354,7 @@ export function canAfford(cost) {
   return this.money.gte(cost);
 }
 
-export function recordMoneySource(amt, source) {
+export function recordMoneySource(this: IPlayer, amt: number, source: string) {
   if (!(this.moneySourceA instanceof MoneySourceTracker)) {
     console.warn(`Player.moneySourceA was not properly initialized. Resetting`);
     this.moneySourceA = new MoneySourceTracker();
@@ -361,7 +367,7 @@ export function recordMoneySource(amt, source) {
   this.moneySourceB.record(amt, source);
 }
 
-export function gainHackingExp(exp) {
+export function gainHackingExp(this: IPlayer, exp: number) {
   if (isNaN(exp)) {
     console.error("ERR: NaN passed into Player.gainHackingExp()");
     return;
@@ -374,7 +380,7 @@ export function gainHackingExp(exp) {
   this.hacking_skill = calculateSkillF(this.hacking_exp, this.hacking_mult * BitNodeMultipliers.HackingLevelMultiplier);
 }
 
-export function gainStrengthExp(exp) {
+export function gainStrengthExp(this: IPlayer, exp: number) {
   if (isNaN(exp)) {
     console.error("ERR: NaN passed into Player.gainStrengthExp()");
     return;
@@ -387,7 +393,7 @@ export function gainStrengthExp(exp) {
   this.strength = calculateSkillF(this.strength_exp, this.strength_mult * BitNodeMultipliers.StrengthLevelMultiplier);
 }
 
-export function gainDefenseExp(exp) {
+export function gainDefenseExp(this: IPlayer, exp: number) {
   if (isNaN(exp)) {
     console.error("ERR: NaN passed into player.gainDefenseExp()");
     return;
@@ -400,7 +406,7 @@ export function gainDefenseExp(exp) {
   this.defense = calculateSkillF(this.defense_exp, this.defense_mult * BitNodeMultipliers.DefenseLevelMultiplier);
 }
 
-export function gainDexterityExp(exp) {
+export function gainDexterityExp(this: IPlayer, exp: number) {
   if (isNaN(exp)) {
     console.error("ERR: NaN passed into Player.gainDexterityExp()");
     return;
@@ -416,7 +422,7 @@ export function gainDexterityExp(exp) {
   );
 }
 
-export function gainAgilityExp(exp) {
+export function gainAgilityExp(this: IPlayer, exp: number): void {
   if (isNaN(exp)) {
     console.error("ERR: NaN passed into Player.gainAgilityExp()");
     return;
@@ -429,7 +435,7 @@ export function gainAgilityExp(exp) {
   this.agility = calculateSkillF(this.agility_exp, this.agility_mult * BitNodeMultipliers.AgilityLevelMultiplier);
 }
 
-export function gainCharismaExp(exp) {
+export function gainCharismaExp(this: IPlayer, exp: number): void {
   if (isNaN(exp)) {
     console.error("ERR: NaN passed into Player.gainCharismaExp()");
     return;
@@ -442,7 +448,7 @@ export function gainCharismaExp(exp) {
   this.charisma = calculateSkillF(this.charisma_exp, this.charisma_mult * BitNodeMultipliers.CharismaLevelMultiplier);
 }
 
-export function gainIntelligenceExp(exp) {
+export function gainIntelligenceExp(this: IPlayer, exp: number): void {
   if (isNaN(exp)) {
     console.error("ERROR: NaN passed into Player.gainIntelligenceExp()");
     return;
@@ -453,7 +459,7 @@ export function gainIntelligenceExp(exp) {
 }
 
 //Given a string expression like "str" or "strength", returns the given stat
-export function queryStatFromString(str) {
+export function queryStatFromString(this: IPlayer, str: string): number {
   const tempStr = str.toLowerCase();
   if (tempStr.includes("hack")) {
     return this.hacking_skill;
@@ -476,10 +482,11 @@ export function queryStatFromString(str) {
   if (tempStr.includes("int")) {
     return this.intelligence;
   }
+  return 0;
 }
 
 /******* Working functions *******/
-export function resetWorkStatus(generalType, group, workType) {
+export function resetWorkStatus(this: IPlayer, generalType?: string, group?: string, workType?: string): void {
   if (this.workType !== CONSTANTS.WorkTypeFaction && generalType === this.workType && group === this.companyName)
     return;
   if (generalType === this.workType && group === this.currentWorkFactionName && workType === this.factionWorkType)
@@ -513,7 +520,7 @@ export function resetWorkStatus(generalType, group, workType) {
   this.className = "";
 }
 
-export function processWorkEarnings(numCycles = 1) {
+export function processWorkEarnings(this: IPlayer, numCycles = 1) {
   const focusBonus = this.focus ? 1 : 0.8;
   const hackExpGain = focusBonus * this.workHackExpGainRate * numCycles;
   const strExpGain = focusBonus * this.workStrExpGainRate * numCycles;
@@ -547,7 +554,7 @@ export function processWorkEarnings(numCycles = 1) {
 }
 
 /* Working for Company */
-export function startWork(router, companyName) {
+export function startWork(this: IPlayer, router: IRouter, companyName: string): void {
   this.resetWorkStatus(CONSTANTS.WorkTypeCompany, companyName);
   this.isWorking = true;
   this.focus = true;
@@ -567,16 +574,18 @@ export function startWork(router, companyName) {
   router.toWork();
 }
 
-export function cancelationPenalty() {
+export function cancelationPenalty(this: IPlayer) {
   const specialIp = SpecialServerIps[this.companyName];
-  if (specialIp) {
+  if (typeof specialIp === "string" && specialIp !== "") {
     const server = AllServers[specialIp];
-    if (server && server.backdoorInstalled) return 0.75;
+    if (server instanceof Server) {
+      if (server && server.backdoorInstalled) return 0.75;
+    }
   }
   return 0.5;
 }
 
-export function work(numCycles) {
+export function work(this: IPlayer, numCycles: number): boolean {
   // Cap the number of cycles being processed to whatever would put you at
   // the work time limit (8 hours)
   var overMax = false;
@@ -594,13 +603,13 @@ export function work(numCycles) {
 
   // If timeWorked == 8 hours, then finish. You can only gain 8 hours worth of exp and money
   if (overMax || this.timeWorked >= CONSTANTS.MillisecondsPer8Hours) {
-    return this.finishWork(false);
+    this.finishWork(false);
     return true;
   }
   return false;
 }
 
-export function finishWork(cancelled, sing = false) {
+export function finishWork(this: IPlayer, cancelled: boolean, sing = false): string {
   //Since the work was cancelled early, player only gains half of what they've earned so far
   if (cancelled) {
     this.workRepGained *= this.cancelationPenalty();
@@ -652,8 +661,9 @@ export function finishWork(cancelled, sing = false) {
 
   this.isWorking = false;
 
+  this.resetWorkStatus();
   if (sing) {
-    var res =
+    const res =
       "You worked a short shift of " +
       convertTimeMsToTimeElapsedString(this.timeWorked) +
       " and " +
@@ -674,13 +684,14 @@ export function finishWork(cancelled, sing = false) {
       " agility exp, and " +
       numeralWrapper.formatExp(this.workChaExpGained) +
       " charisma exp.";
-    this.resetWorkStatus();
+
     return res;
   }
-  this.resetWorkStatus();
+
+  return "";
 }
 
-export function startWorkPartTime(router, companyName) {
+export function startWorkPartTime(this: IPlayer, router: IRouter, companyName: string): void {
   this.resetWorkStatus(CONSTANTS.WorkTypeCompanyPartTime, companyName);
   this.isWorking = true;
   this.focus = true;
@@ -700,7 +711,7 @@ export function startWorkPartTime(router, companyName) {
   router.toWork();
 }
 
-export function workPartTime(numCycles) {
+export function workPartTime(this: IPlayer, numCycles: number): boolean {
   //Cap the number of cycles being processed to whatever would put you at the
   //work time limit (8 hours)
   var overMax = false;
@@ -715,13 +726,13 @@ export function workPartTime(numCycles) {
 
   //If timeWorked == 8 hours, then finish. You can only gain 8 hours worth of exp and money
   if (overMax || this.timeWorked >= CONSTANTS.MillisecondsPer8Hours) {
-    return this.finishWorkPartTime();
+    this.finishWorkPartTime();
     return true;
   }
   return false;
 }
 
-export function finishWorkPartTime(sing = false) {
+export function finishWorkPartTime(this: IPlayer, sing = false): string {
   var company = Companies[this.companyName];
   company.playerReputation += this.workRepGained;
 
@@ -750,6 +761,8 @@ export function finishWorkPartTime(sing = false) {
   }
 
   this.isWorking = false;
+  this.resetWorkStatus();
+
   if (sing) {
     var res =
       "You worked for " +
@@ -773,22 +786,21 @@ export function finishWorkPartTime(sing = false) {
       " agility exp, and " +
       numeralWrapper.formatExp(this.workChaExpGained) +
       " charisma exp";
-    this.resetWorkStatus();
     return res;
   }
-  this.resetWorkStatus();
+  return "";
 }
 
-export function startFocusing() {
+export function startFocusing(this: IPlayer): void {
   this.focus = true;
 }
 
-export function stopFocusing() {
+export function stopFocusing(this: IPlayer): void {
   this.focus = false;
 }
 
 /* Working for Faction */
-export function startFactionWork(router, faction) {
+export function startFactionWork(this: IPlayer, router: IRouter, faction: Faction): void {
   //Update reputation gain rate to account for faction favor
   var favorMult = 1 + faction.favor / 100;
   if (isNaN(favorMult)) {
@@ -806,7 +818,7 @@ export function startFactionWork(router, faction) {
   router.toWork();
 }
 
-export function startFactionHackWork(router, faction) {
+export function startFactionHackWork(this: IPlayer, router: IRouter, faction: Faction) {
   this.resetWorkStatus(CONSTANTS.WorkTypeFaction, faction.name, CONSTANTS.FactionWorkHacking);
 
   this.workHackExpGainRate = 0.15 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
@@ -821,7 +833,7 @@ export function startFactionHackWork(router, faction) {
   this.startFactionWork(router, faction);
 }
 
-export function startFactionFieldWork(router, faction) {
+export function startFactionFieldWork(this: IPlayer, router: IRouter, faction: Faction) {
   this.resetWorkStatus(CONSTANTS.WorkTypeFaction, faction.name, CONSTANTS.FactionWorkField);
 
   this.workHackExpGainRate = 0.1 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
@@ -838,7 +850,7 @@ export function startFactionFieldWork(router, faction) {
   this.startFactionWork(router, faction);
 }
 
-export function startFactionSecurityWork(router, faction) {
+export function startFactionSecurityWork(this: IPlayer, router: IRouter, faction: Faction) {
   this.resetWorkStatus(CONSTANTS.WorkTypeFaction, faction.name, CONSTANTS.FactionWorkSecurity);
 
   this.workHackExpGainRate = 0.05 * this.hacking_exp_mult * BitNodeMultipliers.FactionWorkExpGain;
@@ -855,7 +867,7 @@ export function startFactionSecurityWork(router, faction) {
   this.startFactionWork(router, faction);
 }
 
-export function workForFaction(numCycles) {
+export function workForFaction(this: IPlayer, numCycles: number) {
   const faction = Factions[this.currentWorkFactionName];
 
   //Constantly update the rep gain rate
@@ -874,7 +886,7 @@ export function workForFaction(numCycles) {
   }
 
   //Cap the number of cycles being processed to whatever would put you at limit (20 hours)
-  var overMax = false;
+  let overMax = false;
   if (this.timeWorked + CONSTANTS._idleSpeed * numCycles >= CONSTANTS.MillisecondsPer20Hours) {
     overMax = true;
     numCycles = Math.round((CONSTANTS.MillisecondsPer20Hours - this.timeWorked) / CONSTANTS._idleSpeed);
@@ -891,8 +903,8 @@ export function workForFaction(numCycles) {
   return false;
 }
 
-export function finishFactionWork(cancelled, sing = false) {
-  var faction = Factions[this.currentWorkFactionName];
+export function finishFactionWork(this: IPlayer, cancelled: boolean, sing = false): string {
+  const faction = Factions[this.currentWorkFactionName];
   faction.playerReputation += this.workRepGained;
 
   this.updateSkillLevels();
@@ -919,7 +931,7 @@ export function finishFactionWork(cancelled, sing = false) {
   }
 
   this.isWorking = false;
-
+  this.resetWorkStatus();
   if (sing) {
     var res =
       "You worked for your faction " +
@@ -942,14 +954,14 @@ export function finishFactionWork(cancelled, sing = false) {
       " agi exp, and " +
       numeralWrapper.formatExp(this.workChaExpGained) +
       " cha exp.";
-    this.resetWorkStatus();
+
     return res;
   }
-  this.resetWorkStatus();
+  return "";
 }
 
 //Money gained per game cycle
-export function getWorkMoneyGain() {
+export function getWorkMoneyGain(this: IPlayer): number {
   // If player has SF-11, calculate salary multiplier from favor
   let bn11Mult = 1;
   const company = Companies[this.companyName];
@@ -975,7 +987,7 @@ export function getWorkMoneyGain() {
 }
 
 //Hack exp gained per game cycle
-export function getWorkHackExpGain() {
+export function getWorkHackExpGain(this: IPlayer): number {
   const company = Companies[this.companyName];
   const companyPositionName = this.jobs[this.companyName];
   const companyPosition = CompanyPositions[companyPositionName];
@@ -999,7 +1011,7 @@ export function getWorkHackExpGain() {
 }
 
 //Str exp gained per game cycle
-export function getWorkStrExpGain() {
+export function getWorkStrExpGain(this: IPlayer): number {
   const company = Companies[this.companyName];
   const companyPositionName = this.jobs[this.companyName];
   const companyPosition = CompanyPositions[companyPositionName];
@@ -1023,7 +1035,7 @@ export function getWorkStrExpGain() {
 }
 
 //Def exp gained per game cycle
-export function getWorkDefExpGain() {
+export function getWorkDefExpGain(this: IPlayer): number {
   const company = Companies[this.companyName];
   const companyPositionName = this.jobs[this.companyName];
   const companyPosition = CompanyPositions[companyPositionName];
@@ -1047,7 +1059,7 @@ export function getWorkDefExpGain() {
 }
 
 //Dex exp gained per game cycle
-export function getWorkDexExpGain() {
+export function getWorkDexExpGain(this: IPlayer): number {
   const company = Companies[this.companyName];
   const companyPositionName = this.jobs[this.companyName];
   const companyPosition = CompanyPositions[companyPositionName];
@@ -1071,7 +1083,7 @@ export function getWorkDexExpGain() {
 }
 
 //Agi exp gained per game cycle
-export function getWorkAgiExpGain() {
+export function getWorkAgiExpGain(this: IPlayer): number {
   const company = Companies[this.companyName];
   const companyPositionName = this.jobs[this.companyName];
   const companyPosition = CompanyPositions[companyPositionName];
@@ -1095,7 +1107,7 @@ export function getWorkAgiExpGain() {
 }
 
 //Charisma exp gained per game cycle
-export function getWorkChaExpGain() {
+export function getWorkChaExpGain(this: IPlayer): number {
   const company = Companies[this.companyName];
   const companyPositionName = this.jobs[this.companyName];
   const companyPosition = CompanyPositions[companyPositionName];
@@ -1119,7 +1131,7 @@ export function getWorkChaExpGain() {
 }
 
 //Reputation gained per game cycle
-export function getWorkRepGain() {
+export function getWorkRepGain(this: IPlayer): number {
   const company = Companies[this.companyName];
   const companyPositionName = this.jobs[this.companyName];
   const companyPosition = CompanyPositions[companyPositionName];
@@ -1154,7 +1166,7 @@ export function getWorkRepGain() {
   return jobPerformance * this.company_rep_mult * favorMult;
 }
 
-// export function getFactionSecurityWorkRepGain() {
+// export function getFactionSecurityWorkRepGain(this: IPlayer) {
 //     var t = 0.9 * (this.hacking_skill  / CONSTANTS.MaxSkillLevel +
 //                    this.strength       / CONSTANTS.MaxSkillLevel +
 //                    this.defense        / CONSTANTS.MaxSkillLevel +
@@ -1163,7 +1175,7 @@ export function getWorkRepGain() {
 //     return t * this.faction_rep_mult;
 // }
 
-// export function getFactionFieldWorkRepGain() {
+// export function getFactionFieldWorkRepGain(this: IPlayer) {
 //     var t = 0.9 * (this.hacking_skill  / CONSTANTS.MaxSkillLevel +
 //                    this.strength       / CONSTANTS.MaxSkillLevel +
 //                    this.defense        / CONSTANTS.MaxSkillLevel +
@@ -1175,7 +1187,13 @@ export function getWorkRepGain() {
 // }
 
 /* Creating a Program */
-export function startCreateProgramWork(router, programName, time, reqLevel) {
+export function startCreateProgramWork(
+  this: IPlayer,
+  router: IRouter,
+  programName: string,
+  time: number,
+  reqLevel: number,
+): void {
   this.resetWorkStatus();
   this.isWorking = true;
   this.focus = true;
@@ -1210,7 +1228,7 @@ export function startCreateProgramWork(router, programName, time, reqLevel) {
   router.toWork();
 }
 
-export function createProgramWork(numCycles) {
+export function createProgramWork(this: IPlayer, numCycles: number): boolean {
   //Higher hacking skill will allow you to create programs faster
   var reqLvl = this.createProgramReqLvl;
   var skillMult = (this.hacking_skill / reqLvl) * this.getIntelligenceBonus(3); //This should always be greater than 1;
@@ -1227,7 +1245,7 @@ export function createProgramWork(numCycles) {
   return false;
 }
 
-export function finishCreateProgramWork(cancelled) {
+export function finishCreateProgramWork(this: IPlayer, cancelled: boolean): string {
   var programName = this.createProgramName;
   if (cancelled === false) {
     dialogBoxCreate(
@@ -1248,10 +1266,11 @@ export function finishCreateProgramWork(cancelled) {
   this.isWorking = false;
 
   this.resetWorkStatus();
+  return "You've finished creating " + programName + "! The new program can be found on your home computer.";
 }
 
 /* Studying/Taking Classes */
-export function startClass(router, costMult, expMult, className) {
+export function startClass(this: IPlayer, router: IRouter, costMult: number, expMult: number, className: string): void {
   this.resetWorkStatus();
   this.isWorking = true;
   this.focus = true;
@@ -1325,7 +1344,7 @@ export function startClass(router, costMult, expMult, className) {
   router.toWork();
 }
 
-export function takeClass(numCycles) {
+export function takeClass(this: IPlayer, numCycles: number): boolean {
   this.timeWorked += CONSTANTS._idleSpeed * numCycles;
   this.processWorkEarnings(numCycles);
   return false;
@@ -1333,7 +1352,7 @@ export function takeClass(numCycles) {
 
 //The 'sing' argument defines whether or not this function was called
 //through a Singularity Netscript function
-export function finishClass(sing = false) {
+export function finishClass(this: IPlayer, sing = false): string {
   this.gainIntelligenceExp(CONSTANTS.IntelligenceClassBaseExpGain * Math.round(this.timeWorked / 1000));
 
   if (this.workMoneyGained > 0) {
@@ -1388,22 +1407,24 @@ export function finishClass(sing = false) {
     return res;
   }
   this.resetWorkStatus();
+  return "";
 }
 
 //The EXP and $ gains are hardcoded. Time is in ms
 export function startCrime(
-  router,
-  crimeType,
-  hackExp,
-  strExp,
-  defExp,
-  dexExp,
-  agiExp,
-  chaExp,
-  money,
-  time,
-  singParams = null,
-) {
+  this: IPlayer,
+  router: IRouter,
+  crimeType: string,
+  hackExp: number,
+  strExp: number,
+  defExp: number,
+  dexExp: number,
+  agiExp: number,
+  chaExp: number,
+  money: number,
+  time: number,
+  workerscript: WorkerScript | null = null,
+): void {
   this.crimeType = crimeType;
 
   this.resetWorkStatus();
@@ -1411,9 +1432,9 @@ export function startCrime(
   this.focus = true;
   this.workType = CONSTANTS.WorkTypeCrime;
 
-  if (singParams && singParams.workerscript) {
+  if (workerscript !== null) {
     this.committingCrimeThruSingFn = true;
-    this.singFnCrimeWorkerScript = singParams.workerscript;
+    this.singFnCrimeWorkerScript = workerscript;
   }
 
   this.workHackExpGained = hackExp * this.hacking_exp_mult * BitNodeMultipliers.CrimeExpGain;
@@ -1428,7 +1449,7 @@ export function startCrime(
   router.toWork();
 }
 
-export function commitCrime(numCycles) {
+export function commitCrime(this: IPlayer, numCycles: number): boolean {
   this.timeWorked += CONSTANTS._idleSpeed * numCycles;
 
   if (this.timeWorked >= this.timeNeededToCompleteWork) {
@@ -1438,7 +1459,7 @@ export function commitCrime(numCycles) {
   return false;
 }
 
-export function finishCrime(cancelled) {
+export function finishCrime(this: IPlayer, cancelled: boolean): string {
   //Determine crime success/failure
   if (!cancelled) {
     if (determineCrimeSuccess(this, this.crimeType)) {
@@ -1454,6 +1475,7 @@ export function finishCrime(cancelled) {
         dialogBoxCreate(
           `ERR: Unrecognized crime type (${this.crimeType}). This is probably a bug please contact the developer`,
         );
+        return "";
       }
       this.gainMoney(this.workMoneyGained);
       this.recordMoneySource(this.workMoneyGained, "crime");
@@ -1470,12 +1492,10 @@ export function finishCrime(cancelled) {
       this.workDexExpGained *= 2;
       this.workAgiExpGained *= 2;
       this.workChaExpGained *= 2;
-      if (this.committingCrimeThruSingFn) {
-        if (
-          this.singFnCrimeWorkerScript.disableLogs.ALL == null &&
-          this.singFnCrimeWorkerScript.disableLogs.commitCrime == null
-        ) {
-          this.singFnCrimeWorkerScript.scriptRef.log(
+      const ws = this.singFnCrimeWorkerScript;
+      if (this.committingCrimeThruSingFn && ws !== null) {
+        if (ws.disableLogs.ALL == null && ws.disableLogs.commitCrime == null) {
+          ws.scriptRef.log(
             "Crime successful! Gained " +
               numeralWrapper.formatMoney(this.workMoneyGained) +
               ", " +
@@ -1524,12 +1544,10 @@ export function finishCrime(cancelled) {
       this.workDexExpGained /= 2;
       this.workAgiExpGained /= 2;
       this.workChaExpGained /= 2;
-      if (this.committingCrimeThruSingFn) {
-        if (
-          this.singFnCrimeWorkerScript.disableLogs.ALL == null &&
-          this.singFnCrimeWorkerScript.disableLogs.commitCrime == null
-        ) {
-          this.singFnCrimeWorkerScript.scriptRef.log(
+      const ws = this.singFnCrimeWorkerScript;
+      if (this.committingCrimeThruSingFn && ws !== null) {
+        if (ws.disableLogs.ALL == null && ws.disableLogs.commitCrime == null) {
+          ws.scriptRef.log(
             "Crime failed! Gained " +
               numeralWrapper.formatExp(this.workHackExpGained) +
               " hack exp, " +
@@ -1580,15 +1598,16 @@ export function finishCrime(cancelled) {
   this.isWorking = false;
   this.crimeType = "";
   this.resetWorkStatus();
+  return "";
 }
 
 //Cancels the player's current "work" assignment and gives the proper rewards
 //Used only for Singularity functions, so no popups are created
-export function singularityStopWork() {
+export function singularityStopWork(this: IPlayer): string {
   if (!this.isWorking) {
     return "";
   }
-  var res; //Earnings text for work
+  let res = ""; //Earnings text for work
   switch (this.workType) {
     case CONSTANTS.WorkTypeStudyClass:
       res = this.finishClass(true);
@@ -1616,10 +1635,10 @@ export function singularityStopWork() {
 }
 
 // Returns true if hospitalized, false otherwise
-export function takeDamage(amt) {
+export function takeDamage(this: IPlayer, amt: number): boolean {
   if (typeof amt !== "number") {
     console.warn(`Player.takeDamage() called without a numeric argument: ${amt}`);
-    return;
+    return false;
   }
 
   this.hp -= amt;
@@ -1631,7 +1650,7 @@ export function takeDamage(amt) {
   }
 }
 
-export function regenerateHp(amt) {
+export function regenerateHp(this: IPlayer, amt: number): void {
   if (typeof amt !== "number") {
     console.warn(`Player.regenerateHp() called without a numeric argument: ${amt}`);
     return;
@@ -1642,7 +1661,7 @@ export function regenerateHp(amt) {
   }
 }
 
-export function hospitalize() {
+export function hospitalize(this: IPlayer): number {
   const cost = getHospitalizationCost(this);
   if (Settings.SuppressHospitalizationPopup === false) {
     dialogBoxCreate(
@@ -1664,7 +1683,7 @@ export function hospitalize() {
 //Determines the job that the Player should get (if any) at the current company
 //The 'sing' argument designates whether or not this is being called from
 //the applyToCompany() Netscript Singularity function
-export function applyForJob(entryPosType, sing = false) {
+export function applyForJob(this: IPlayer, entryPosType: CompanyPosition, sing = false): boolean {
   // Get current company and job
   let currCompany = null;
   if (this.companyName !== "") {
@@ -1675,25 +1694,18 @@ export function applyForJob(entryPosType, sing = false) {
   // Get company that's being applied to
   const company = Companies[this.location]; //Company being applied to
   if (!(company instanceof Company)) {
-    if (sing) {
-      return "ERROR: Invalid company name: " + this.location + ". applyToCompany() failed";
-    } else {
-      console.error(
-        `Could not find company that matches the location: ${this.location}. Player.applyToCompany() failed`,
-      );
-      return;
-    }
+    console.error(`Could not find company that matches the location: ${this.location}. Player.applyToCompany() failed`);
+    return false;
   }
 
   let pos = entryPosType;
 
   if (!this.isQualified(company, pos)) {
-    var reqText = getJobRequirementText(company, pos);
-    if (sing) {
-      return false;
+    const reqText = getJobRequirementText(company, pos);
+    if (!sing) {
+      dialogBoxCreate("Unforunately, you do not qualify for this position<br>" + reqText);
     }
-    dialogBoxCreate("Unforunately, you do not qualify for this position<br>" + reqText);
-    return;
+    return false;
   }
 
   while (true) {
@@ -1717,40 +1729,43 @@ export function applyForJob(entryPosType, sing = false) {
   //Check if the determined job is the same as the player's current job
   if (currCompany != null) {
     if (currCompany.name == company.name && pos.name == currPositionName) {
-      var nextPos = getNextCompanyPositionHelper(pos);
+      const nextPos = getNextCompanyPositionHelper(pos);
       if (nextPos == null) {
-        if (sing) {
-          return false;
+        if (!sing) {
+          dialogBoxCreate("You are already at the highest position for your field! No promotion available");
         }
-        dialogBoxCreate("You are already at the highest position for your field! No promotion available");
+        return false;
       } else if (company.hasPosition(nextPos)) {
-        if (sing) {
-          return false;
+        if (!sing) {
+          var reqText = getJobRequirementText(company, nextPos);
+          dialogBoxCreate("Unfortunately, you do not qualify for a promotion<br>" + reqText);
         }
-        var reqText = getJobRequirementText(company, nextPos);
-        dialogBoxCreate("Unfortunately, you do not qualify for a promotion<br>" + reqText);
+        return false;
       } else {
-        if (sing) {
-          return false;
+        if (!sing) {
+          dialogBoxCreate("You are already at the highest position for your field! No promotion available");
         }
-        dialogBoxCreate("You are already at the highest position for your field! No promotion available");
+        return false;
       }
-      return; //Same job, do nothing
+      return false; //Same job, do nothing
     }
   }
 
   this.jobs[company.name] = pos.name;
   this.companyName = this.location;
 
-  if (sing) {
-    return true;
+  if (!sing) {
+    dialogBoxCreate("Congratulations! You were offered a new job at " + this.companyName + " as a " + pos.name + "!");
   }
-
-  dialogBoxCreate("Congratulations! You were offered a new job at " + this.companyName + " as a " + pos.name + "!");
+  return true;
 }
 
 //Returns your next position at a company given the field (software, business, etc.)
-export function getNextCompanyPosition(company, entryPosType) {
+export function getNextCompanyPosition(
+  this: IPlayer,
+  company: Company,
+  entryPosType: CompanyPosition,
+): CompanyPosition | null {
   var currCompany = null;
   if (this.companyName !== "") {
     currCompany = Companies[this.companyName];
@@ -1784,143 +1799,149 @@ export function getNextCompanyPosition(company, entryPosType) {
   return entryPosType;
 }
 
-export function quitJob(company) {
+export function quitJob(this: IPlayer, company: string): void {
   this.isWorking = false;
   this.companyName = "";
   delete this.jobs[company];
 }
 
-export function applyForSoftwareJob(sing = false) {
+export function applyForSoftwareJob(this: IPlayer, sing = false): boolean {
   return this.applyForJob(CompanyPositions[posNames.SoftwareCompanyPositions[0]], sing);
 }
 
-export function applyForSoftwareConsultantJob(sing = false) {
+export function applyForSoftwareConsultantJob(this: IPlayer, sing = false): boolean {
   return this.applyForJob(CompanyPositions[posNames.SoftwareConsultantCompanyPositions[0]], sing);
 }
 
-export function applyForItJob(sing = false) {
+export function applyForItJob(this: IPlayer, sing = false): boolean {
   return this.applyForJob(CompanyPositions[posNames.ITCompanyPositions[0]], sing);
 }
 
-export function applyForSecurityEngineerJob(sing = false) {
+export function applyForSecurityEngineerJob(this: IPlayer, sing = false): boolean {
   var company = Companies[this.location]; //Company being applied to
   if (this.isQualified(company, CompanyPositions[posNames.SecurityEngineerCompanyPositions[0]])) {
     return this.applyForJob(CompanyPositions[posNames.SecurityEngineerCompanyPositions[0]], sing);
   } else {
-    if (sing) {
-      return false;
+    if (!sing) {
+      dialogBoxCreate("Unforunately, you do not qualify for this position");
     }
-    dialogBoxCreate("Unforunately, you do not qualify for this position");
+    return false;
   }
 }
 
-export function applyForNetworkEngineerJob(sing = false) {
+export function applyForNetworkEngineerJob(this: IPlayer, sing = false): boolean {
   var company = Companies[this.location]; //Company being applied to
   if (this.isQualified(company, CompanyPositions[posNames.NetworkEngineerCompanyPositions[0]])) {
-    return this.applyForJob(CompanyPositions[posNames.NetworkEngineerCompanyPositions[0]], sing);
+    const pos = CompanyPositions[posNames.NetworkEngineerCompanyPositions[0]];
+    return this.applyForJob(pos, sing);
   } else {
-    if (sing) {
-      return false;
+    if (!sing) {
+      dialogBoxCreate("Unforunately, you do not qualify for this position");
     }
-    dialogBoxCreate("Unforunately, you do not qualify for this position");
+    return false;
   }
 }
 
-export function applyForBusinessJob(sing = false) {
+export function applyForBusinessJob(this: IPlayer, sing = false): boolean {
   return this.applyForJob(CompanyPositions[posNames.BusinessCompanyPositions[0]], sing);
 }
 
-export function applyForBusinessConsultantJob(sing = false) {
+export function applyForBusinessConsultantJob(this: IPlayer, sing = false): boolean {
   return this.applyForJob(CompanyPositions[posNames.BusinessConsultantCompanyPositions[0]], sing);
 }
 
-export function applyForSecurityJob(sing = false) {
+export function applyForSecurityJob(this: IPlayer, sing = false): boolean {
   // TODO Police Jobs
   // Indexing starts at 2 because 0 is for police officer
   return this.applyForJob(CompanyPositions[posNames.SecurityCompanyPositions[2]], sing);
 }
 
-export function applyForAgentJob(sing = false) {
+export function applyForAgentJob(this: IPlayer, sing = false): boolean {
   var company = Companies[this.location]; //Company being applied to
   if (this.isQualified(company, CompanyPositions[posNames.AgentCompanyPositions[0]])) {
-    return this.applyForJob(CompanyPositions[posNames.AgentCompanyPositions[0]], sing);
+    const pos = CompanyPositions[posNames.AgentCompanyPositions[0]];
+    return this.applyForJob(pos, sing);
   } else {
-    if (sing) {
-      return false;
+    if (!sing) {
+      dialogBoxCreate("Unforunately, you do not qualify for this position");
     }
-    dialogBoxCreate("Unforunately, you do not qualify for this position");
+    return false;
   }
 }
 
-export function applyForEmployeeJob(sing = false) {
+export function applyForEmployeeJob(this: IPlayer, sing = false): boolean {
   var company = Companies[this.location]; //Company being applied to
   if (this.isQualified(company, CompanyPositions[posNames.MiscCompanyPositions[1]])) {
     this.companyName = company.name;
     this.jobs[company.name] = posNames.MiscCompanyPositions[1];
-    if (sing) {
-      return true;
+    if (!sing) {
+      dialogBoxCreate("Congratulations, you are now employed at " + this.companyName);
     }
-    dialogBoxCreate("Congratulations, you are now employed at " + this.companyName);
+
+    return true;
   } else {
-    if (sing) {
-      return false;
+    if (!sing) {
+      dialogBoxCreate("Unforunately, you do not qualify for this position");
     }
-    dialogBoxCreate("Unforunately, you do not qualify for this position");
+
+    return false;
   }
 }
 
-export function applyForPartTimeEmployeeJob(sing = false) {
+export function applyForPartTimeEmployeeJob(this: IPlayer, sing = false): boolean {
   var company = Companies[this.location]; //Company being applied to
   if (this.isQualified(company, CompanyPositions[posNames.PartTimeCompanyPositions[1]])) {
     this.jobs[company.name] = posNames.PartTimeCompanyPositions[1];
-    if (sing) {
-      return true;
+    if (!sing) {
+      dialogBoxCreate("Congratulations, you are now employed part-time at " + this.companyName);
     }
-    dialogBoxCreate("Congratulations, you are now employed part-time at " + this.companyName);
+
+    return true;
   } else {
-    if (sing) {
-      return false;
+    if (!sing) {
+      dialogBoxCreate("Unforunately, you do not qualify for this position");
     }
-    dialogBoxCreate("Unforunately, you do not qualify for this position");
+
+    return false;
   }
 }
 
-export function applyForWaiterJob(sing = false) {
+export function applyForWaiterJob(this: IPlayer, sing = false): boolean {
   var company = Companies[this.location]; //Company being applied to
   if (this.isQualified(company, CompanyPositions[posNames.MiscCompanyPositions[0]])) {
     this.companyName = company.name;
     this.jobs[company.name] = posNames.MiscCompanyPositions[0];
-    if (sing) {
-      return true;
+    if (!sing) {
+      dialogBoxCreate("Congratulations, you are now employed as a waiter at " + this.companyName);
     }
-    dialogBoxCreate("Congratulations, you are now employed as a waiter at " + this.companyName);
+    return true;
   } else {
-    if (sing) {
-      return false;
+    if (!sing) {
+      dialogBoxCreate("Unforunately, you do not qualify for this position");
     }
-    dialogBoxCreate("Unforunately, you do not qualify for this position");
+    return false;
   }
 }
 
-export function applyForPartTimeWaiterJob(sing = false) {
+export function applyForPartTimeWaiterJob(this: IPlayer, sing = false): boolean {
   var company = Companies[this.location]; //Company being applied to
   if (this.isQualified(company, CompanyPositions[posNames.PartTimeCompanyPositions[0]])) {
     this.companyName = company.name;
     this.jobs[company.name] = posNames.PartTimeCompanyPositions[0];
-    if (sing) {
-      return true;
+    if (!sing) {
+      dialogBoxCreate("Congratulations, you are now employed as a part-time waiter at " + this.companyName);
     }
-    dialogBoxCreate("Congratulations, you are now employed as a part-time waiter at " + this.companyName);
+    return true;
   } else {
-    if (sing) {
-      return false;
+    if (!sing) {
+      dialogBoxCreate("Unforunately, you do not qualify for this position");
     }
-    dialogBoxCreate("Unforunately, you do not qualify for this position");
+    return false;
   }
 }
 
 //Checks if the Player is qualified for a certain position
-export function isQualified(company, position) {
+export function isQualified(this: IPlayer, company: Company, position: CompanyPosition): boolean {
   var offset = company.jobStatReqOffset;
   var reqHacking = position.requiredHacking > 0 ? position.requiredHacking + offset : 0;
   var reqStrength = position.requiredStrength > 0 ? position.requiredStrength + offset : 0;
@@ -1944,7 +1965,7 @@ export function isQualified(company, position) {
 }
 
 /********** Reapplying Augmentations and Source File ***********/
-export function reapplyAllAugmentations(resetMultipliers = true) {
+export function reapplyAllAugmentations(this: IPlayer, resetMultipliers = true): void {
   if (resetMultipliers) {
     this.resetMultipliers();
   }
@@ -1974,7 +1995,7 @@ export function reapplyAllAugmentations(resetMultipliers = true) {
   this.updateSkillLevels();
 }
 
-export function reapplyAllSourceFiles() {
+export function reapplyAllSourceFiles(this: IPlayer): void {
   //Will always be called after reapplyAllAugmentations() so multipliers do not have to be reset
   //this.resetMultipliers();
 
@@ -1994,7 +2015,7 @@ export function reapplyAllSourceFiles() {
 //This function sets the requirements to join a Faction. It checks whether the Player meets
 //those requirements and will return an array of all factions that the Player should
 //receive an invitation to
-export function checkForFactionInvitations() {
+export function checkForFactionInvitations(this: IPlayer) {
   let invitedFactions = []; //Array which will hold all Factions the player should be invited to
 
   var numAugmentations = this.augmentations.length;
@@ -2003,7 +2024,7 @@ export function checkForFactionInvitations() {
   const allPositions = Object.values(this.jobs);
 
   // Given a company name, safely returns the reputation (returns 0 if invalid company is specified)
-  function getCompanyRep(companyName) {
+  function getCompanyRep(companyName: string): number {
     const company = Companies[companyName];
     if (company == null) {
       return 0;
@@ -2016,7 +2037,7 @@ export function checkForFactionInvitations() {
   // the requirements for the specified company. There are two requirements:
   //      1. High enough reputation
   //      2. Player is employed at the company
-  function checkMegacorpRequirements(companyName, repNeeded = CONSTANTS.CorpFactionRepRequirement) {
+  function checkMegacorpRequirements(companyName: string, repNeeded = CONSTANTS.CorpFactionRepRequirement): boolean {
     return allCompanies.includes(companyName) && getCompanyRep(companyName) > repNeeded;
   }
 
@@ -2168,8 +2189,11 @@ export function checkForFactionInvitations() {
   }
 
   //Fulcrum Secret Technologies - If u've unlocked fulcrum secret technolgoies server and have a high rep with the company
-  var fulcrumsecrettechonologiesFac = Factions["Fulcrum Secret Technologies"];
-  var fulcrumSecretServer = AllServers[SpecialServerIps[SpecialServerNames.FulcrumSecretTechnologies]];
+  const fulcrumsecrettechonologiesFac = Factions["Fulcrum Secret Technologies"];
+  const fulcrumIP = SpecialServerIps[SpecialServerNames.BitRunnersServer];
+  if (typeof fulcrumIP !== "string") throw new Error("Fulcrum Secret Technologies should be string");
+  const fulcrumSecretServer = AllServers[fulcrumIP];
+  if (!(fulcrumSecretServer instanceof Server)) throw new Error("Fulcrum Secret Technologies should be normal server");
   if (fulcrumSecretServer == null) {
     console.error("Could not find Fulcrum Secret Technologies Server");
   } else {
@@ -2185,8 +2209,11 @@ export function checkForFactionInvitations() {
   }
 
   //BitRunners
-  var bitrunnersFac = Factions["BitRunners"];
-  var bitrunnersServer = AllServers[SpecialServerIps[SpecialServerNames.BitRunnersServer]];
+  const bitrunnersFac = Factions["BitRunners"];
+  const bitrunnerIP = SpecialServerIps[SpecialServerNames.BitRunnersServer];
+  if (typeof bitrunnerIP !== "string") throw new Error("BitRunners should be string");
+  const bitrunnersServer = AllServers[bitrunnerIP];
+  if (!(bitrunnersServer instanceof Server)) throw new Error("BitRunners should be normal server");
   if (bitrunnersServer == null) {
     console.error("Could not find BitRunners Server");
   } else if (
@@ -2199,8 +2226,12 @@ export function checkForFactionInvitations() {
   }
 
   //The Black Hand
-  var theblackhandFac = Factions["The Black Hand"];
-  var blackhandServer = AllServers[SpecialServerIps[SpecialServerNames.TheBlackHandServer]];
+
+  const theblackhandFac = Factions["The Black Hand"];
+  const tbhIP = SpecialServerIps[SpecialServerNames.TheBlackHandServer];
+  if (typeof tbhIP !== "string") throw new Error("TheBlackHand should be string");
+  const blackhandServer = AllServers[tbhIP];
+  if (!(blackhandServer instanceof Server)) throw new Error("TheBlackHand should be normal server");
   if (blackhandServer == null) {
     console.error("Could not find The Black Hand Server");
   } else if (
@@ -2213,8 +2244,11 @@ export function checkForFactionInvitations() {
   }
 
   //NiteSec
-  var nitesecFac = Factions["NiteSec"];
-  var nitesecServer = AllServers[SpecialServerIps[SpecialServerNames.NiteSecServer]];
+  const nitesecFac = Factions["NiteSec"];
+  const nitesecIP = SpecialServerIps[SpecialServerNames.NiteSecServer];
+  if (typeof nitesecIP !== "string") throw new Error("NiteSec should be string");
+  const nitesecServer = AllServers[nitesecIP];
+  if (!(nitesecServer instanceof Server)) throw new Error("NiteSec should be normal server");
   if (nitesecServer == null) {
     console.error("Could not find NiteSec Server");
   } else if (
@@ -2410,17 +2444,17 @@ export function checkForFactionInvitations() {
   var totalHacknetCores = 0;
   var totalHacknetLevels = 0;
   for (let i = 0; i < this.hacknetNodes.length; ++i) {
-    if (hasHacknetServers(this)) {
-      const hserver = AllServers[this.hacknetNodes[i]];
-      if (hserver) {
-        totalHacknetLevels += hserver.level;
-        totalHacknetRam += hserver.maxRam;
-        totalHacknetCores += hserver.cores;
-      }
+    const v = this.hacknetNodes[i];
+    if (typeof v === "string") {
+      const hserver = AllServers[v];
+      if (hserver instanceof Server) throw new Error("player hacknet server was not HacknetServer");
+      totalHacknetLevels += hserver.level;
+      totalHacknetRam += hserver.maxRam;
+      totalHacknetCores += hserver.cores;
     } else {
-      totalHacknetLevels += this.hacknetNodes[i].level;
-      totalHacknetRam += this.hacknetNodes[i].ram;
-      totalHacknetCores += this.hacknetNodes[i].cores;
+      totalHacknetLevels += v.level;
+      totalHacknetRam += v.ram;
+      totalHacknetCores += v.cores;
     }
   }
   if (
@@ -2449,8 +2483,11 @@ export function checkForFactionInvitations() {
   }
 
   //CyberSec
-  var cybersecFac = Factions["CyberSec"];
-  var cybersecServer = AllServers[SpecialServerIps[SpecialServerNames.CyberSecServer]];
+  const cybersecFac = Factions["CyberSec"];
+  const cyberSecIP = SpecialServerIps[SpecialServerNames.CyberSecServer];
+  if (typeof cyberSecIP !== "string") throw new Error("cybersec should be string");
+  const cybersecServer = AllServers[cyberSecIP];
+  if (!(cybersecServer instanceof Server)) throw new Error("cybersec should be normal server");
   if (cybersecServer == null) {
     console.error("Could not find CyberSec Server");
   } else if (
@@ -2466,11 +2503,11 @@ export function checkForFactionInvitations() {
 }
 
 /************* BitNodes **************/
-export function setBitNodeNumber(n) {
+export function setBitNodeNumber(this: IPlayer, n: number) {
   this.bitNodeN = n;
 }
 
-export function queueAugmentation(name) {
+export function queueAugmentation(this: IPlayer, name: string) {
   for (const i in this.queuedAugmentations) {
     if (this.queuedAugmentations[i].name == name) {
       console.warn(`tried to queue ${name} twice, this may be a bug`);
@@ -2485,12 +2522,11 @@ export function queueAugmentation(name) {
     }
   }
 
-  this.firstAugPurchased = true;
   this.queuedAugmentations.push(new PlayerOwnedAugmentation(name));
 }
 
 /************* Coding Contracts **************/
-export function gainCodingContractReward(reward, difficulty = 1) {
+export function gainCodingContractReward(this: IPlayer, reward: ICodingContractReward, difficulty = 1) {
   if (reward == null || reward.type == null || reward == null) {
     return `No reward for this contract`;
   }
@@ -2552,7 +2588,7 @@ export function gainCodingContractReward(reward, difficulty = 1) {
   /* eslint-enable no-case-declarations */
 }
 
-export function travel(to) {
+export function travel(this: IPlayer, to: CityName) {
   if (Cities[to] == null) {
     console.warn(`Player.travel() called with invalid city: ${to}`);
     return false;
@@ -2562,7 +2598,7 @@ export function travel(to) {
   return true;
 }
 
-export function gotoLocation(to) {
+export function gotoLocation(this: IPlayer, to: LocationName) {
   if (Locations[to] == null) {
     console.warn(`Player.gotoLocation() called with invalid location: ${to}`);
     return false;
@@ -2572,20 +2608,20 @@ export function gotoLocation(to) {
   return true;
 }
 
-export function canAccessResleeving() {
+export function canAccessResleeving(this: IPlayer) {
   return this.bitNodeN === 10 || SourceFileFlags[10] > 0;
 }
 
-export function giveExploit(exploit) {
+export function giveExploit(this: IPlayer, exploit: Exploit) {
   if (!this.exploits.includes(exploit)) {
     this.exploits.push(exploit);
   }
 }
 
-export function getIntelligenceBonus(weight) {
+export function getIntelligenceBonus(this: IPlayer, weight: number) {
   return calculateIntelligenceBonus(this.intelligence, weight);
 }
 
-export function getCasinoWinnings() {
+export function getCasinoWinnings(this: IPlayer) {
   return this.moneySourceA.casino;
 }
diff --git a/src/PersonObjects/Player/PlayerObjectServerMethods.ts b/src/PersonObjects/Player/PlayerObjectServerMethods.ts
index 2361fa6ef..b9bf0e60b 100644
--- a/src/PersonObjects/Player/PlayerObjectServerMethods.ts
+++ b/src/PersonObjects/Player/PlayerObjectServerMethods.ts
@@ -15,12 +15,16 @@ export function hasTorRouter(this: IPlayer): boolean {
   return SpecialServerIps.hasOwnProperty("Darkweb Server");
 }
 
-export function getCurrentServer(this: IPlayer): Server | HacknetServer | null {
-  return AllServers[this.currentServer];
+export function getCurrentServer(this: IPlayer): Server | HacknetServer {
+  const server = AllServers[this.currentServer];
+  if (server === null) throw new Error("somehow connected to a server that does not exist.");
+  return server;
 }
 
-export function getHomeComputer(this: IPlayer): Server | HacknetServer | null {
-  return AllServers[this.homeComputer];
+export function getHomeComputer(this: IPlayer): Server {
+  const home = AllServers[this.homeComputer];
+  if (home instanceof Server) return home;
+  throw new Error("home computer was not a normal server");
 }
 
 export function getUpgradeHomeRamCost(this: IPlayer): number {
diff --git a/src/Player.d.ts b/src/Player.d.ts
deleted file mode 100644
index a4e5b3f7a..000000000
--- a/src/Player.d.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { IPlayer } from "./PersonObjects/IPlayer";
-
-export declare let Player: IPlayer;
diff --git a/src/Player.js b/src/Player.ts
similarity index 85%
rename from src/Player.js
rename to src/Player.ts
index 79db73bfc..852d757c5 100644
--- a/src/Player.js
+++ b/src/Player.ts
@@ -8,7 +8,7 @@ import Decimal from "decimal.js";
 
 export let Player = new PlayerObject();
 
-export function loadPlayer(saveString) {
+export function loadPlayer(saveString: string): void {
   Player = JSON.parse(saveString, Reviver);
 
   // Parse Decimal.js objects
@@ -19,8 +19,8 @@ export function loadPlayer(saveString) {
     Player.corporation.revenue = new Decimal(Player.corporation.revenue);
     Player.corporation.expenses = new Decimal(Player.corporation.expenses);
 
-    for (var i = 0; i < Player.corporation.divisions.length; ++i) {
-      var ind = Player.corporation.divisions[i];
+    for (let i = 0; i < Player.corporation.divisions.length; ++i) {
+      const ind = Player.corporation.divisions[i];
       ind.lastCycleRevenue = new Decimal(ind.lastCycleRevenue);
       ind.lastCycleExpenses = new Decimal(ind.lastCycleExpenses);
       ind.thisCycleRevenue = new Decimal(ind.thisCycleRevenue);
diff --git a/src/Programs/data/ProgramsMetadata.ts b/src/Programs/data/ProgramsMetadata.ts
index 45ca6b7dc..09ea12820 100644
--- a/src/Programs/data/ProgramsMetadata.ts
+++ b/src/Programs/data/ProgramsMetadata.ts
@@ -53,10 +53,9 @@ export const programsMetadata: IProgramCreationParams[] = [
         terminal.print("You already have root access to this computer. There is no reason to run NUKE.exe");
         return;
       }
-
-      if (server.openPortCount >= player.getCurrentServer().numOpenPortsRequired) {
+      if (server.openPortCount >= server.numOpenPortsRequired) {
         server.hasAdminRights = true;
-        terminal.print("NUKE successful! Gained root access to " + player.getCurrentServer().hostname);
+        terminal.print("NUKE successful! Gained root access to " + server.hostname);
         // TODO: Make this take time rather than be instant
         return;
       }
diff --git a/src/Script/RunningScript.ts b/src/Script/RunningScript.ts
index 683b095be..acf9c0f52 100644
--- a/src/Script/RunningScript.ts
+++ b/src/Script/RunningScript.ts
@@ -3,7 +3,6 @@
  * A Script can have multiple active instances
  */
 import { Script } from "./Script";
-import { FconfSettings } from "../Fconf/FconfSettings";
 import { Settings } from "../Settings/Settings";
 import { IMap } from "../types";
 import { Terminal } from "../Terminal";
diff --git a/src/ScriptEditor/ui/Root.tsx b/src/ScriptEditor/ui/Root.tsx
index 5d6b0f53e..ec3fe83e9 100644
--- a/src/ScriptEditor/ui/Root.tsx
+++ b/src/ScriptEditor/ui/Root.tsx
@@ -11,7 +11,6 @@ import { isValidFilePath } from "../../Terminal/DirectoryHelpers";
 import { IPlayer } from "../../PersonObjects/IPlayer";
 import { IRouter } from "../../ui/Router";
 import { dialogBoxCreate } from "../../../utils/DialogBox";
-import { parseFconfSettings } from "../../Fconf/Fconf";
 import { isScriptFilename } from "../../Script/ScriptHelpersTS";
 import { Script } from "../../Script/Script";
 import { TextFile } from "../../TextFile";
@@ -144,7 +143,7 @@ export function Root(props: IProps): React.ReactElement {
       return;
     }
 
-    if (filename !== ".fconf" && !isValidFilePath(filename)) {
+    if (!isValidFilePath(filename)) {
       dialogBoxCreate(
         "Script filename can contain only alphanumerics, hyphens, and underscores, and must end with an extension.",
       );
@@ -153,14 +152,7 @@ export function Root(props: IProps): React.ReactElement {
 
     const server = props.player.getCurrentServer();
     if (server === null) throw new Error("Server should not be null but it is.");
-    if (filename === ".fconf") {
-      try {
-        parseFconfSettings(code);
-      } catch (e) {
-        dialogBoxCreate(`Invalid .fconf file: ${e}`);
-        return;
-      }
-    } else if (isScriptFilename(filename)) {
+    if (isScriptFilename(filename)) {
       //If the current script already exists on the server, overwrite it
       for (let i = 0; i < server.scripts.length; i++) {
         if (filename == server.scripts[i].filename) {
diff --git a/src/Sidebar/ui/SidebarRoot.tsx b/src/Sidebar/ui/SidebarRoot.tsx
index 4138a8522..af9986c05 100644
--- a/src/Sidebar/ui/SidebarRoot.tsx
+++ b/src/Sidebar/ui/SidebarRoot.tsx
@@ -295,7 +295,6 @@ export function SidebarRoot(props: IProps): React.ReactElement {
         event.preventDefault();
         clickCreateProgram();
       } else if (event.keyCode === KEY.F && event.altKey) {
-        // Overriden by Fconf
         if (props.page == Page.Terminal && Settings.EnableBashHotkeys) {
           return;
         }
diff --git a/src/Terminal.d.ts b/src/Terminal.d.ts
deleted file mode 100644
index 80b9d17aa..000000000
--- a/src/Terminal.d.ts
+++ /dev/null
@@ -1 +0,0 @@
-export declare const Terminal: ITerminal;
diff --git a/src/Terminal.jsx b/src/Terminal.ts
similarity index 100%
rename from src/Terminal.jsx
rename to src/Terminal.ts
diff --git a/src/Terminal/Terminal.ts b/src/Terminal/Terminal.ts
index 77a4140b6..45fa46b75 100644
--- a/src/Terminal/Terminal.ts
+++ b/src/Terminal/Terminal.ts
@@ -3,6 +3,7 @@ import { IRouter } from "../ui/Router";
 import { IPlayer } from "../PersonObjects/IPlayer";
 import { HacknetServer } from "../Hacknet/HacknetServer";
 import { BaseServer } from "../Server/BaseServer";
+import { Server } from "../Server/Server";
 import { Programs } from "../Programs/Programs";
 import { CodingContractResult } from "../CodingContracts";
 import { TerminalEvents, TerminalClearEvents } from "./TerminalEvents";
@@ -106,12 +107,22 @@ export class Terminal implements ITerminal {
 
   startHack(player: IPlayer): void {
     // Hacking through Terminal should be faster than hacking through a script
-    this.startAction(calculateHackingTime(player.getCurrentServer(), player) / 4, "h");
+    const server = player.getCurrentServer();
+    if (server instanceof HacknetServer) {
+      this.error("Cannot hack this kind of server");
+      return;
+    }
+    this.startAction(calculateHackingTime(server, player) / 4, "h");
   }
 
   startBackdoor(player: IPlayer): void {
     // Backdoor should take the same amount of time as hack
-    this.startAction(calculateHackingTime(player.getCurrentServer(), player) / 4, "b");
+    const server = player.getCurrentServer();
+    if (server instanceof HacknetServer) {
+      this.error("Cannot backdoor this kind of server");
+      return;
+    }
+    this.startAction(calculateHackingTime(server, player) / 4, "b");
   }
 
   startAnalyze(): void {
@@ -127,6 +138,10 @@ export class Terminal implements ITerminal {
   finishHack(router: IRouter, player: IPlayer, cancelled = false): void {
     if (cancelled) return;
     const server = player.getCurrentServer();
+    if (server instanceof HacknetServer) {
+      this.error("Cannot hack this kind of server");
+      return;
+    }
 
     // Calculate whether hack was successful
     const hackChance = calculateHackingChance(server, player);
@@ -179,6 +194,10 @@ export class Terminal implements ITerminal {
   finishBackdoor(router: IRouter, player: IPlayer, cancelled = false): void {
     if (!cancelled) {
       const server = player.getCurrentServer();
+      if (server instanceof HacknetServer) {
+        this.error("Cannot hack this kind of server");
+        return;
+      }
       if (
         SpecialServerIps[SpecialServerNames.WorldDaemon] &&
         SpecialServerIps[SpecialServerNames.WorldDaemon] == server.ip
@@ -203,24 +222,30 @@ export class Terminal implements ITerminal {
       this.print("Organization name: " + (!isHacknet ? org : "player"));
       const hasAdminRights = (!isHacknet && currServ.hasAdminRights) || isHacknet;
       this.print("Root Access: " + (hasAdminRights ? "YES" : "NO"));
-      const hackingSkill = currServ.requiredHackingSkill;
-      this.print("Required hacking skill: " + (!isHacknet ? hackingSkill : "N/A"));
-      const security = currServ.hackDifficulty;
-      this.print("Server security level: " + (!isHacknet ? numeralWrapper.formatServerSecurity(security) : "N/A"));
-      const hackingChance = calculateHackingChance(currServ, player);
-      this.print("Chance to hack: " + (!isHacknet ? numeralWrapper.formatPercentage(hackingChance) : "N/A"));
-      const hackingTime = calculateHackingTime(currServ, player) * 1000;
-      this.print("Time to hack: " + (!isHacknet ? convertTimeMsToTimeElapsedString(hackingTime, true) : "N/A"));
+      if (currServ instanceof Server) {
+        const hackingSkill = currServ.requiredHackingSkill;
+        this.print("Required hacking skill: " + (!isHacknet ? hackingSkill : "N/A"));
+        const security = currServ.hackDifficulty;
+        this.print("Server security level: " + (!isHacknet ? numeralWrapper.formatServerSecurity(security) : "N/A"));
+        const hackingChance = calculateHackingChance(currServ, player);
+        this.print("Chance to hack: " + (!isHacknet ? numeralWrapper.formatPercentage(hackingChance) : "N/A"));
+        const hackingTime = calculateHackingTime(currServ, player) * 1000;
+        this.print("Time to hack: " + (!isHacknet ? convertTimeMsToTimeElapsedString(hackingTime, true) : "N/A"));
+      }
       this.print(
-        `Total money available on server: ${!isHacknet ? numeralWrapper.formatMoney(currServ.moneyAvailable) : "N/A"}`,
+        `Total money available on server: ${
+          !(currServ instanceof HacknetServer) ? numeralWrapper.formatMoney(currServ.moneyAvailable) : "N/A"
+        }`,
       );
-      const numPort = currServ.numOpenPortsRequired;
-      this.print("Required number of open ports for NUKE: " + (!isHacknet ? numPort : "N/A"));
-      this.print("SSH port: " + (currServ.sshPortOpen ? "Open" : "Closed"));
-      this.print("FTP port: " + (currServ.ftpPortOpen ? "Open" : "Closed"));
-      this.print("SMTP port: " + (currServ.smtpPortOpen ? "Open" : "Closed"));
-      this.print("HTTP port: " + (currServ.httpPortOpen ? "Open" : "Closed"));
-      this.print("SQL port: " + (currServ.sqlPortOpen ? "Open" : "Closed"));
+      if (currServ instanceof Server) {
+        const numPort = currServ.numOpenPortsRequired;
+        this.print("Required number of open ports for NUKE: " + (!isHacknet ? numPort : "N/A"));
+        this.print("SSH port: " + (currServ.sshPortOpen ? "Open" : "Closed"));
+        this.print("FTP port: " + (currServ.ftpPortOpen ? "Open" : "Closed"));
+        this.print("SMTP port: " + (currServ.smtpPortOpen ? "Open" : "Closed"));
+        this.print("HTTP port: " + (currServ.httpPortOpen ? "Open" : "Closed"));
+        this.print("SQL port: " + (currServ.sqlPortOpen ? "Open" : "Closed"));
+      }
     }
   }
 
diff --git a/src/Terminal/commands/nano.ts b/src/Terminal/commands/nano.ts
index ad93cf2f9..b028cf9ac 100644
--- a/src/Terminal/commands/nano.ts
+++ b/src/Terminal/commands/nano.ts
@@ -3,7 +3,6 @@ import { IRouter } from "../../ui/Router";
 import { IPlayer } from "../../PersonObjects/IPlayer";
 import { BaseServer } from "../../Server/BaseServer";
 import { isScriptFilename } from "../../Script/ScriptHelpersTS";
-import { createFconf } from "../../Fconf/Fconf";
 
 export function nano(
   terminal: ITerminal,
@@ -19,11 +18,7 @@ export function nano(
 
   try {
     const filename = args[0] + "";
-    if (filename === ".fconf") {
-      const text = createFconf();
-      router.toScriptEditor(filename, text);
-      return;
-    } else if (isScriptFilename(filename)) {
+    if (isScriptFilename(filename)) {
       const filepath = terminal.getFilepath(filename);
       const script = terminal.getScript(player, filename);
       if (script == null) {
diff --git a/src/Terminal/ui/TerminalRoot.tsx b/src/Terminal/ui/TerminalRoot.tsx
index e4dc0caf9..1351d20ee 100644
--- a/src/Terminal/ui/TerminalRoot.tsx
+++ b/src/Terminal/ui/TerminalRoot.tsx
@@ -12,6 +12,7 @@ import { IRouter } from "../../ui/Router";
 import { IPlayer } from "../../PersonObjects/IPlayer";
 import { TerminalInput } from "./TerminalInput";
 import { TerminalEvents, TerminalClearEvents } from "../TerminalEvents";
+import _ from "lodash";
 
 interface IActionTimerProps {
   terminal: ITerminal;
@@ -60,8 +61,8 @@ export function TerminalRoot({ terminal, router, player }: IProps): React.ReactE
     setKey((key) => key + 1);
   }
 
-  useEffect(() => TerminalEvents.subscribe(rerender), []);
-  useEffect(() => TerminalClearEvents.subscribe(clear), []);
+  useEffect(() => TerminalEvents.subscribe(_.debounce(rerender, 50, { maxWait: 50 })), []);
+  useEffect(() => TerminalClearEvents.subscribe(_.debounce(clear, 50, { maxWait: 50 })), []);
 
   function doScroll(): void {
     const hook = scrollHook.current;
diff --git a/src/ui/GameRoot.tsx b/src/ui/GameRoot.tsx
index 66539c6df..41ccc8e57 100644
--- a/src/ui/GameRoot.tsx
+++ b/src/ui/GameRoot.tsx
@@ -326,11 +326,11 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
               ) : page === Page.DevMenu ? (
                 <DevMenuRoot player={player} engine={engine} router={Router} />
               ) : page === Page.Gang ? (
-                <GangRoot gang={player.gang} />
+                <GangRoot />
               ) : page === Page.Corporation ? (
-                <CorporationRoot corp={player.corporation} player={player} />
+                <CorporationRoot />
               ) : page === Page.Bladeburner ? (
-                <BladeburnerRoot bladeburner={player.bladeburner} />
+                <BladeburnerRoot />
               ) : page === Page.Resleeves ? (
                 <ResleeveRoot player={player} />
               ) : page === Page.Travel ? (
diff --git a/src/ui/MainMenu/Headers.ts b/src/ui/MainMenu/Headers.ts
deleted file mode 100644
index 5ba3b971e..000000000
--- a/src/ui/MainMenu/Headers.ts
+++ /dev/null
@@ -1,177 +0,0 @@
-// Implement the collapsible main menu headers
-import { MainMenuLinks } from "./Links";
-import { IPlayer } from "../../PersonObjects/IPlayer";
-
-interface IMainMenuHeaders {
-  Hacking: HTMLElement | null;
-  Character: HTMLElement | null;
-  World: HTMLElement | null;
-  Help: HTMLElement | null;
-}
-
-export const MainMenuHeaders: IMainMenuHeaders = {
-  Hacking: null,
-  Character: null,
-  World: null,
-  Help: null,
-};
-
-// Implements collapsible toggle feature when a header is clicked
-function toggleHeader(open: boolean, elems: HTMLElement[], links: HTMLElement[]): void {
-  for (let i = 0; i < elems.length; ++i) {
-    if (open) {
-      elems[i].style.opacity = "1";
-      elems[i].style.maxHeight = elems[i].scrollHeight + "px";
-    } else {
-      elems[i].style.opacity = "0";
-      elems[i].style.maxHeight = "";
-    }
-  }
-
-  for (let i = 0; i < links.length; ++i) {
-    if (open) {
-      links[i].style.opacity = "1";
-      links[i].style.maxHeight = links[i].scrollHeight + "px";
-      links[i].style.pointerEvents = "auto";
-    } else {
-      links[i].style.opacity = "0";
-      links[i].style.maxHeight = "";
-      links[i].style.pointerEvents = "none";
-    }
-  }
-}
-
-export function initializeMainMenuHeaders(p: IPlayer, dev = false): boolean {
-  function safeGetElement(id: string): HTMLElement {
-    const elem: HTMLElement | null = document.getElementById(id);
-    if (elem == null) {
-      throw new Error(`Failed to find element with id ${id} in initializeMainMenuHeaders()`);
-    }
-
-    return elem;
-  }
-
-  try {
-    // Get references to the DOM elements
-    MainMenuHeaders.Hacking = safeGetElement("hacking-menu-header");
-    MainMenuHeaders.Character = safeGetElement("character-menu-header");
-    MainMenuHeaders.World = safeGetElement("world-menu-header");
-    MainMenuHeaders.Help = safeGetElement("help-menu-header");
-
-    // Set click handlers to turn the headers into collapsibles
-    MainMenuHeaders.Hacking.onclick = function () {
-      const terminal: HTMLElement = safeGetElement("terminal-tab");
-      const createScript: HTMLElement = safeGetElement("create-script-tab");
-      const activeScripts: HTMLElement = safeGetElement("active-scripts-tab");
-      const createProgram: HTMLElement = safeGetElement("create-program-tab");
-      const createProgramNot: HTMLElement = safeGetElement("create-program-notification");
-
-      createProgram.style.display = p.firstProgramAvailable ? "list-item" : "none";
-
-      (this as any).classList.toggle("opened");
-
-      const elems: HTMLElement[] = [terminal, createScript, activeScripts, createProgram];
-      const links: HTMLElement[] = [
-        MainMenuLinks.Terminal,
-        MainMenuLinks.ScriptEditor,
-        MainMenuLinks.ActiveScripts,
-        MainMenuLinks.CreateProgram,
-      ];
-      if (terminal.style.maxHeight) {
-        toggleHeader(false, elems, links);
-        createProgramNot.style.display = "none";
-      } else {
-        toggleHeader(true, elems, links);
-        createProgramNot.style.display = "block";
-      }
-    };
-
-    MainMenuHeaders.Character.onclick = function () {
-      const stats: HTMLElement = safeGetElement("stats-tab");
-      const factions: HTMLElement = safeGetElement("factions-tab");
-      const augmentations: HTMLElement = safeGetElement("augmentations-tab");
-      const hacknetnodes: HTMLElement = safeGetElement("hacknet-nodes-tab");
-      const sleeves: HTMLElement = safeGetElement("sleeves-tab");
-
-      sleeves.style.display = p.sleeves.length > 0 ? "list-item" : "none";
-
-      (this as any).classList.toggle("opened");
-
-      const elems: HTMLElement[] = [stats, factions, augmentations, hacknetnodes, sleeves];
-      const links: HTMLElement[] = [
-        MainMenuLinks.Stats,
-        MainMenuLinks.Factions,
-        MainMenuLinks.Augmentations,
-        MainMenuLinks.HacknetNodes,
-        MainMenuLinks.Sleeves,
-      ];
-      if (stats.style.maxHeight) {
-        toggleHeader(false, elems, links);
-      } else {
-        toggleHeader(true, elems, links);
-      }
-    };
-
-    MainMenuHeaders.World.onclick = function () {
-      const city: HTMLElement = safeGetElement("city-tab");
-      const travel: HTMLElement = safeGetElement("travel-tab");
-      const job: HTMLElement = safeGetElement("job-tab");
-      const stockmarket: HTMLElement = safeGetElement("stock-market-tab");
-      const bladeburner: HTMLElement = safeGetElement("bladeburner-tab");
-      const corporation: HTMLElement = safeGetElement("corporation-tab");
-      const gang: HTMLElement = safeGetElement("gang-tab");
-
-      // Determine whether certain links should show up
-      job.style.display = p.companyName !== "" ? "list-item" : "none";
-      stockmarket.style.display = p.hasWseAccount ? "list-item" : "none";
-      bladeburner.style.display = p.inBladeburner() ? "list-item" : "none";
-      corporation.style.display = p.hasCorporation() ? "list-item" : "none";
-      gang.style.display = p.inGang() ? "list-item" : "none";
-
-      (this as any).classList.toggle("opened");
-
-      const elems: HTMLElement[] = [city, travel, job, stockmarket, bladeburner, corporation, gang];
-      const links: HTMLElement[] = [
-        MainMenuLinks.City,
-        MainMenuLinks.Travel,
-        MainMenuLinks.Job,
-        MainMenuLinks.StockMarket,
-        MainMenuLinks.Bladeburner,
-        MainMenuLinks.Corporation,
-        MainMenuLinks.Gang,
-      ];
-      if (city.style.maxHeight) {
-        toggleHeader(false, elems, links);
-      } else {
-        toggleHeader(true, elems, links);
-      }
-    };
-
-    MainMenuHeaders.Help.onclick = function () {
-      const milestones: HTMLElement = safeGetElement("milestones-tab");
-      const tutorial: HTMLElement = safeGetElement("tutorial-tab");
-      const options: HTMLElement = safeGetElement("options-tab");
-
-      (this as any).classList.toggle("opened");
-
-      const elems: HTMLElement[] = [milestones, tutorial, options];
-      const links: HTMLElement[] = [MainMenuLinks.Milestones, MainMenuLinks.Tutorial, MainMenuLinks.Options];
-
-      if (dev) {
-        elems.push(safeGetElement("dev-tab"));
-        links.push(safeGetElement("dev-menu-link"));
-      }
-
-      if (tutorial.style.maxHeight) {
-        toggleHeader(false, elems, links);
-      } else {
-        toggleHeader(true, elems, links);
-      }
-    };
-
-    return true;
-  } catch (e) {
-    console.error(`Failed to initialize Main Menu Headers: ${e}`);
-    return false;
-  }
-}
diff --git a/src/ui/MainMenu/Links.ts b/src/ui/MainMenu/Links.ts
deleted file mode 100644
index b89ce5757..000000000
--- a/src/ui/MainMenu/Links.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-// Get references to the Main Menu link DOM elements
-// Does NOT include collapsible headers for the links
-import { clearEventListeners } from "../../../utils/uiHelpers/clearEventListeners";
-
-interface IMainMenuLinks {
-  [key: string]: HTMLElement | undefined;
-  Terminal: HTMLElement;
-  ScriptEditor: HTMLElement;
-  ActiveScripts: HTMLElement;
-  CreateProgram: HTMLElement;
-  Stats: HTMLElement;
-  Factions: HTMLElement;
-  Augmentations: HTMLElement;
-  HacknetNodes: HTMLElement;
-  Sleeves: HTMLElement;
-  City: HTMLElement;
-  Travel: HTMLElement;
-  Job: HTMLElement;
-  StockMarket: HTMLElement;
-  Bladeburner: HTMLElement;
-  Corporation: HTMLElement;
-  Gang: HTMLElement;
-  Milestones: HTMLElement;
-  Tutorial: HTMLElement;
-  Options: HTMLElement;
-  DevMenu: HTMLElement;
-}
-
-const emptyElement: HTMLElement = ((): HTMLElement => {
-  const elem = document.createElement("div");
-  if (elem === null) throw new Error("unable to create empty div element");
-  return elem;
-})();
-
-export const MainMenuLinks: IMainMenuLinks = {
-  Terminal: emptyElement,
-  ScriptEditor: emptyElement,
-  ActiveScripts: emptyElement,
-  CreateProgram: emptyElement,
-  Stats: emptyElement,
-  Factions: emptyElement,
-  Augmentations: emptyElement,
-  HacknetNodes: emptyElement,
-  Sleeves: emptyElement,
-  City: emptyElement,
-  Travel: emptyElement,
-  Job: emptyElement,
-  StockMarket: emptyElement,
-  Bladeburner: emptyElement,
-  Corporation: emptyElement,
-  Gang: emptyElement,
-  Milestones: emptyElement,
-  Tutorial: emptyElement,
-  Options: emptyElement,
-  DevMenu: emptyElement,
-};
-
-export function initializeMainMenuLinks(): boolean {
-  return true;
-  try {
-    function safeGetLink(id: string): HTMLElement {
-      const elem: HTMLElement | null = clearEventListeners(id);
-      if (elem == null) {
-        throw new Error(`clearEventListeners() failed for element with id: ${id}`);
-      }
-
-      return elem;
-    }
-
-    MainMenuLinks.Terminal = safeGetLink("terminal-menu-link");
-    MainMenuLinks.ScriptEditor = safeGetLink("create-script-menu-link");
-    MainMenuLinks.ActiveScripts = safeGetLink("active-scripts-menu-link");
-    MainMenuLinks.CreateProgram = safeGetLink("create-program-menu-link");
-    MainMenuLinks.Stats = safeGetLink("stats-menu-link");
-    MainMenuLinks.Factions = safeGetLink("factions-menu-link");
-    MainMenuLinks.Augmentations = safeGetLink("augmentations-menu-link");
-    MainMenuLinks.HacknetNodes = safeGetLink("hacknet-nodes-menu-link");
-    MainMenuLinks.Sleeves = safeGetLink("sleeves-menu-link");
-    MainMenuLinks.City = safeGetLink("city-menu-link");
-    MainMenuLinks.Travel = safeGetLink("travel-menu-link");
-    MainMenuLinks.Job = safeGetLink("job-menu-link");
-    MainMenuLinks.StockMarket = safeGetLink("stock-market-menu-link");
-    MainMenuLinks.Bladeburner = safeGetLink("bladeburner-menu-link");
-    MainMenuLinks.Corporation = safeGetLink("corporation-menu-link");
-    MainMenuLinks.Gang = safeGetLink("gang-menu-link");
-    MainMenuLinks.Milestones = safeGetLink("milestones-menu-link");
-    MainMenuLinks.Tutorial = safeGetLink("tutorial-menu-link");
-    // const op: HTMLElement | null = document.getElementById("options-menu-link");
-    // if (op === null) throw new Error(`Could not find element with id: "options-menu-link"`);
-    // MainMenuLinks.Options = op; // This click listener is already set, so don't clear it
-    MainMenuLinks.DevMenu = safeGetLink("dev-menu-link");
-
-    return true;
-  } catch (e) {
-    console.error(`Failed to initialize Main Menu Links: ${e}`);
-    return false;
-  }
-}
diff --git a/src/ui/React/GameOptionsRoot.tsx b/src/ui/React/GameOptionsRoot.tsx
index b557e22d4..e62d4e45b 100644
--- a/src/ui/React/GameOptionsRoot.tsx
+++ b/src/ui/React/GameOptionsRoot.tsx
@@ -195,7 +195,7 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
         return;
       }
       const contents = result;
-      save(contents).then(() => location.reload());
+      save(contents).then(() => setTimeout(() => location.reload(), 1000));
     };
     reader.readAsText(file);
   }
@@ -628,7 +628,7 @@ export function GameOptionsRoot(props: IProps): React.ReactElement {
         onConfirm={() => {
           setDeleteOpen(false);
           deleteGame()
-            .then(() => location.reload())
+            .then(() => setTimeout(() => location.reload(), 1000))
             .catch((r) => console.error(`Could not delete game: ${r}`));
         }}
         open={deleteGameOpen}