diff --git a/src/Bladeburner/Bladeburner.tsx b/src/Bladeburner/Bladeburner.tsx index 5b5112516..d3d668b8e 100644 --- a/src/Bladeburner/Bladeburner.tsx +++ b/src/Bladeburner/Bladeburner.tsx @@ -34,6 +34,12 @@ import { getTimestamp } from "../utils/helpers/getTimestamp"; import { joinFaction } from "../Faction/FactionHelpers"; import { WorkerScript } from "../Netscript/WorkerScript"; +interface BlackOpsAttempt { + error?: string; + isAvailable?: boolean; + action?: BlackOperation; +} + export class Bladeburner implements IBladeburner { numHosp = 0; moneyLost = 0; @@ -113,6 +119,44 @@ export class Bladeburner implements IBladeburner { return Math.min(1, this.stamina / (0.5 * this.maxStamina)); } + + canAttemptBlackOp(actionId: IActionIdentifier): BlackOpsAttempt { + // Safety measure - don't repeat BlackOps that are already done + if (this.blackops[actionId.name] != null) { + return { error: "Tried to start a Black Operation that had already been completed" } + } + + const action = this.getActionObject(actionId); + if (!(action instanceof BlackOperation)) throw new Error(`Action should be BlackOperation but isn't`); + if (action == null) throw new Error("Failed to get BlackOperation object for: " + actionId.name); + + if (action.reqdRank > this.rank) { + return { error: "Tried to start a Black Operation without the rank requirement" }; + } + + // Can't start a BlackOp if you haven't done the one before it + const blackops = []; + for (const nm in BlackOperations) { + if (BlackOperations.hasOwnProperty(nm)) { + blackops.push(nm); + } + } + blackops.sort(function (a, b) { + return BlackOperations[a].reqdRank - BlackOperations[b].reqdRank; // Sort black ops in intended order + }); + + const i = blackops.indexOf(actionId.name); + if (i === -1) { + return { error: `Invalid Black Op: '${name}'` }; + } + + if (i > 0 && this.blackops[blackops[i - 1]] == null) { + return { error: `Preceding Black Op must be completed before starting '${actionId.name}'.` } + } + + return { isAvailable: true, action } + } + startAction(player: IPlayer, actionId: IActionIdentifier): void { if (actionId == null) return; this.action = actionId; @@ -156,18 +200,13 @@ export class Bladeburner implements IBladeburner { case ActionTypes["BlackOp"]: case ActionTypes["BlackOperation"]: { try { - // Safety measure - don't repeat BlackOps that are already done - if (this.blackops[actionId.name] != null) { + const testBlackOp = this.canAttemptBlackOp(actionId); + if (!testBlackOp.isAvailable) { this.resetAction(); - this.log("Error: Tried to start a Black Operation that had already been completed"); + this.log(`Error: ${testBlackOp.error}`); break; } - - const action = this.getActionObject(actionId); - if (action == null) { - throw new Error("Failed to get BlackOperation object for: " + actionId.name); - } - this.actionTimeToComplete = action.getActionTime(this); + this.actionTimeToComplete = testBlackOp.action.getActionTime(this); } catch (e: any) { exceptionAlert(e); } @@ -502,6 +541,7 @@ export class Bladeburner implements IBladeburner { const skill = Skills[skillName]; if (skill == null || !(skill instanceof Skill)) { this.postToConsole("Invalid skill name (Note that it is case-sensitive): " + skillName); + break; } if (args[1].toLowerCase() === "list") { let level = 0; @@ -515,7 +555,11 @@ export class Bladeburner implements IBladeburner { currentLevel = this.skills[skillName]; } const pointCost = skill.calculateCost(currentLevel); - if (this.skillPoints >= pointCost) { + if (skill.maxLvl !== 0 && currentLevel >= skill.maxLvl) { + this.postToConsole( + `This skill ${skill.name} is already at max level (${currentLevel}/${skill.maxLvl}).`, + ); + } else if (this.skillPoints >= pointCost) { this.skillPoints -= pointCost; this.upgradeSkill(skill); this.log(skill.name + " upgraded to Level " + this.skills[skillName]); @@ -2032,44 +2076,9 @@ export class Bladeburner implements IBladeburner { // Special logic for Black Ops if (actionId.type === ActionTypes["BlackOp"]) { - // Can't start a BlackOp if you don't have the required rank - const action = this.getActionObject(actionId); - if (action == null) throw new Error(`Action not found ${actionId.type}, ${actionId.name}`); - if (!(action instanceof BlackOperation)) throw new Error(`Action should be BlackOperation but isn't`); - //const blackOp = (action as BlackOperation); - if (action.reqdRank > this.rank) { - workerScript.log("bladeburner.startAction", () => `Insufficient rank to start Black Op '${actionId.name}'.`); - return false; - } - - // Can't start a BlackOp if its already been done - if (this.blackops[actionId.name] != null) { - workerScript.log("bladeburner.startAction", () => `Black Op ${actionId.name} has already been completed.`); - return false; - } - - // Can't start a BlackOp if you haven't done the one before it - const blackops = []; - for (const nm in BlackOperations) { - if (BlackOperations.hasOwnProperty(nm)) { - blackops.push(nm); - } - } - blackops.sort(function (a, b) { - return BlackOperations[a].reqdRank - BlackOperations[b].reqdRank; // Sort black ops in intended order - }); - - const i = blackops.indexOf(actionId.name); - if (i === -1) { - workerScript.log("bladeburner.startAction", () => `Invalid Black Op: '${name}'`); - return false; - } - - if (i > 0 && this.blackops[blackops[i - 1]] == null) { - workerScript.log( - "bladeburner.startAction", - () => `Preceding Black Op must be completed before starting '${actionId.name}'.`, - ); + const canRunOp = this.canAttemptBlackOp(actionId); + if (!canRunOp.isAvailable) { + workerScript.log("bladeburner.startAction", () => canRunOp.error); return false; } } diff --git a/src/Bladeburner/ui/Console.tsx b/src/Bladeburner/ui/Console.tsx index 48ca32bc1..671345b1a 100644 --- a/src/Bladeburner/ui/Console.tsx +++ b/src/Bladeburner/ui/Console.tsx @@ -54,7 +54,6 @@ interface IProps { export function Console(props: IProps): React.ReactElement { const classes = useStyles(); - const scrollHook = useRef(null); const [command, setCommand] = useState(""); const setRerender = useState(false)[1]; @@ -64,22 +63,14 @@ export function Console(props: IProps): React.ReactElement { const [consoleHistoryIndex, setConsoleHistoryIndex] = useState(props.bladeburner.consoleHistory.length); - // TODO: Figure out how to actually make the scrolling work correctly. - function scrollToBottom(): void { - if (!scrollHook.current) return; - scrollHook.current.scrollTop = scrollHook.current.scrollHeight; - } - function rerender(): void { setRerender((old) => !old); } useEffect(() => { const id = setInterval(rerender, 1000); - const id2 = setInterval(scrollToBottom, 100); return () => { clearInterval(id); - clearInterval(id2); }; }, []); @@ -113,6 +104,7 @@ export function Console(props: IProps): React.ReactElement { setConsoleHistoryIndex(i); const prevCommand = consoleHistory[i]; event.currentTarget.value = prevCommand; + setCommand(prevCommand); } if (event.keyCode === 40) { @@ -134,39 +126,68 @@ export function Console(props: IProps): React.ReactElement { setConsoleHistoryIndex(consoleHistoryIndex + 1); const prevCommand = consoleHistory[consoleHistoryIndex + 1]; event.currentTarget.value = prevCommand; + setCommand(prevCommand); } } } return ( - - - - {props.bladeburner.consoleLogs.map((log: any, i: number) => ( - - ))} - - - - ), - spellCheck: false, - }} - /> - -
+ + + + + -
+ + + + ), + spellCheck: false, + }} + /> + + ); +} + +interface ILogProps { + entries: string[]; +} + +function Logs({entries}: ILogProps): React.ReactElement { + const scrollHook = useRef(null); + + // TODO: Text gets shifted up as new entries appear, if the user scrolled up it should attempt to keep the text focused + function scrollToBottom(): void { + if (!scrollHook.current) return; + scrollHook.current.scrollTop = scrollHook.current.scrollHeight; + } + + useEffect(() => { + scrollToBottom(); + }, [entries]); + + return ( + + {entries && entries.map((log: any, i: number) => ( + + ))} + ); }