Merge pull request #1406 from danielyxie/dev

research tree
This commit is contained in:
hydroflame 2021-10-02 00:26:07 -04:00 committed by GitHub
commit 4b95545f61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 177 additions and 5917 deletions

36
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

@ -53,5 +53,4 @@
<body> <body>
<div id="root"/> <div id="root"/>
<script type="text/javascript" src="dist/vendor.bundle.js"></script><script type="text/javascript" src="main.bundle.js"></script></body> <script type="text/javascript" src="dist/vendor.bundle.js"></script><script type="text/javascript" src="main.bundle.js"></script></body>
<script src="src/ThirdParty/raphael.min.js"></script>
</html> </html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -396,6 +396,15 @@ BitNodes["BitNode9"] = new BitNode(
<br /> <br />
(Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT when installing (Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT when installing
Augmentations) Augmentations)
<br />
<br />
This Source-File also increases your hacknet multipliers by:
<br />
Level 1: 8%
<br />
Level 2: 12%
<br />
Level 3: 14%
</> </>
), ),
); );

@ -8,8 +8,7 @@ import { IndustryUpgrades } from "../IndustryUpgrades";
import { numeralWrapper } from "../../ui/numeralFormat"; import { numeralWrapper } from "../../ui/numeralFormat";
import { createProgressBarText } from "../../utils/helpers/createProgressBarText"; import { createProgressBarText } from "../../utils/helpers/createProgressBarText";
import { MakeProductModal } from "./MakeProductModal"; import { MakeProductModal } from "./MakeProductModal";
import { ResearchPopup } from "./ResearchPopup"; import { ResearchModal } from "./ResearchModal";
import { createPopup } from "../../ui/React/createPopup";
import { Money } from "../../ui/React/Money"; import { Money } from "../../ui/React/Money";
import { MoneyRate } from "../../ui/React/MoneyRate"; import { MoneyRate } from "../../ui/React/MoneyRate";
import { StatsTable } from "../../ui/React/StatsTable"; import { StatsTable } from "../../ui/React/StatsTable";
@ -96,6 +95,7 @@ function Text(): React.ReactElement {
const corp = useCorporation(); const corp = useCorporation();
const division = useDivision(); const division = useDivision();
const [helpOpen, setHelpOpen] = useState(false); const [helpOpen, setHelpOpen] = useState(false);
const [researchOpen, setResearchOpen] = useState(false);
const vechain = corp.unlockUpgrades[4] === 1; const vechain = corp.unlockUpgrades[4] === 1;
const profit = division.lastCycleRevenue.minus(division.lastCycleExpenses).toNumber(); const profit = division.lastCycleRevenue.minus(division.lastCycleExpenses).toNumber();
@ -116,14 +116,6 @@ function Text(): React.ReactElement {
}); });
} }
function openResearchPopup(): void {
const popupId = "corporation-research-popup-box";
createPopup(popupId, ResearchPopup, {
industry: division,
popupId: popupId,
});
}
return ( return (
<> <>
<Typography> <Typography>
@ -214,9 +206,10 @@ function Text(): React.ReactElement {
> >
<Typography>Scientific Research: {numeralWrapper.format(division.sciResearch.qty, "0.000a")}</Typography> <Typography>Scientific Research: {numeralWrapper.format(division.sciResearch.qty, "0.000a")}</Typography>
</Tooltip> </Tooltip>
<Button sx={{ mx: 1 }} onClick={openResearchPopup}> <Button sx={{ mx: 1 }} onClick={() => setResearchOpen(true)}>
Research Research
</Button> </Button>
<ResearchModal open={researchOpen} onClose={() => setResearchOpen(false)} industry={division} />
</Box> </Box>
</> </>
); );

@ -0,0 +1,122 @@
import React, { useState } from "react";
import { Modal } from "../../ui/React/Modal";
import { IndustryResearchTrees } from "../IndustryData";
import { CorporationConstants } from "../data/Constants";
import { IIndustry } from "../IIndustry";
import { Research } from "../Actions";
import { Node } from "../ResearchTree";
import { ResearchMap } from "../ResearchMap";
import { dialogBoxCreate } from "../../ui/React/DialogBox";
import Typography from "@mui/material/Typography";
import Tooltip from "@mui/material/Tooltip";
import Button from "@mui/material/Button";
import Box from "@mui/material/Box";
import ListItemButton from "@mui/material/ListItemButton";
import ListItemText from "@mui/material/ListItemText";
import Collapse from "@mui/material/Collapse";
import ExpandMore from "@mui/icons-material/ExpandMore";
import ExpandLess from "@mui/icons-material/ExpandLess";
interface INodeProps {
n: Node | null;
division: IIndustry;
}
function Upgrade({ n, division }: INodeProps): React.ReactElement {
const [open, setOpen] = useState(false);
if (n === null) return <></>;
const r = ResearchMap[n.text];
let disabled = division.sciResearch.qty < r.cost;
const parent = n.parent;
if (parent !== null) {
disabled = disabled || !parent.researched;
}
function research(): void {
if (n === null || disabled) return;
try {
Research(division, n.text);
} catch (err) {
dialogBoxCreate(err + "");
return;
}
dialogBoxCreate(
`Researched ${n.text}. It may take a market cycle ` +
`(~${CorporationConstants.SecsPerMarketCycle} seconds) before the effects of ` +
`the Research apply.`,
);
}
const but = (
<Box>
<Tooltip
title={
<Typography>
Research points: {r.cost}
<br />
{r.desc}
</Typography>
}
>
<span>
<Button disabled={disabled} onClick={research}>
{n.text}
</Button>
</span>
</Tooltip>
</Box>
);
if (n.children.length === 0) return but;
return (
<Box>
<Box display="flex">
{but}
<ListItemButton onClick={() => setOpen((old) => !old)}>
<ListItemText />
{open ? <ExpandLess color="primary" /> : <ExpandMore color="primary" />}
</ListItemButton>
</Box>
<Collapse in={open} unmountOnExit>
<Box m={4}>
{n.children.map((m) => (
<Upgrade key={m.text} division={division} n={m} />
))}
</Box>
</Collapse>
</Box>
);
}
interface IProps {
open: boolean;
onClose: () => void;
industry: IIndustry;
}
// Create the Research Tree UI for this Industry
export function ResearchModal(props: IProps): React.ReactElement {
const researchTree = IndustryResearchTrees[props.industry.type];
if (researchTree === undefined) return <></>;
return (
<Modal open={props.open} onClose={props.onClose}>
<Upgrade division={props.industry} n={researchTree.root} />
<Typography>
Research points: {props.industry.sciResearch.qty}
<br />
Multipliers from research:
<br />* Advertising Multiplier: x{researchTree.getAdvertisingMultiplier()}
<br />* Employee Charisma Multiplier: x{researchTree.getEmployeeChaMultiplier()}
<br />* Employee Creativity Multiplier: x{researchTree.getEmployeeCreMultiplier()}
<br />* Employee Efficiency Multiplier: x{researchTree.getEmployeeEffMultiplier()}
<br />* Employee Intelligence Multiplier: x{researchTree.getEmployeeIntMultiplier()}
<br />* Production Multiplier: x{researchTree.getProductionMultiplier()}
<br />* Sales Multiplier: x{researchTree.getSalesMultiplier()}
<br />* Scientific Research Multiplier: x{researchTree.getScientificResearchMultiplier()}
<br />* Storage Multiplier: x{researchTree.getStorageMultiplier()}
</Typography>
</Modal>
);
}

@ -1,96 +0,0 @@
import React, { useEffect } from "react";
import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { removePopup } from "../../ui/React/createPopup";
import { IndustryResearchTrees } from "../IndustryData";
import { CorporationConstants } from "../data/Constants";
import { Treant } from "treant-js";
import { IIndustry } from "../IIndustry";
import { Research } from "../Actions";
interface IProps {
industry: IIndustry;
popupId: string;
}
// Create the Research Tree UI for this Industry
export function ResearchPopup(props: IProps): React.ReactElement {
const researchTree = IndustryResearchTrees[props.industry.type];
if (researchTree === undefined) return <></>;
useEffect(() => {
{
const boxContent = document.getElementById(`${props.popupId}-content`);
if (boxContent != null) {
boxContent.style.minHeight = "80vh";
}
}
// Get the tree's markup (i.e. config) for Treant
const markup = researchTree.createTreantMarkup();
markup.chart.container = "#" + props.popupId + "-content";
markup.chart.nodeAlign = "BOTTOM";
markup.chart.rootOrientation = "WEST";
markup.chart.siblingSeparation = 40;
markup.chart.connectors = {
type: "step",
style: {
"arrow-end": "block-wide-long",
stroke: "white",
"stroke-width": 2,
},
};
Treant(markup);
// Add Event Listeners for all Nodes
const allResearch = researchTree.getAllNodes();
for (let i = 0; i < allResearch.length; ++i) {
// If this is already Researched, skip it
if (props.industry.researched[allResearch[i]] === true) {
continue;
}
// Get the DOM Element to add a click listener to it
const sanitizedName = allResearch[i].replace(/\s/g, "");
const div = document.getElementById(sanitizedName + "-corp-research-click-listener");
if (div == null) {
console.warn(`Could not find Research Tree div for ${sanitizedName}`);
continue;
}
div.addEventListener("click", () => {
try {
Research(props.industry, allResearch[i]);
} catch (err) {
dialogBoxCreate(err + "");
return;
}
dialogBoxCreate(
`Researched ${allResearch[i]}. It may take a market cycle ` +
`(~${CorporationConstants.SecsPerMarketCycle} seconds) before the effects of ` +
`the Research apply.`,
);
removePopup(props.popupId);
});
}
});
return (
<div id={props.popupId}>
<div>
Research points: {props.industry.sciResearch.qty}
<br />
Multipliers from research:
<br />* Advertising Multiplier: x{researchTree.getAdvertisingMultiplier()}
<br />* Employee Charisma Multiplier: x{researchTree.getEmployeeChaMultiplier()}
<br />* Employee Creativity Multiplier: x{researchTree.getEmployeeCreMultiplier()}
<br />* Employee Efficiency Multiplier: x{researchTree.getEmployeeEffMultiplier()}
<br />* Employee Intelligence Multiplier: x{researchTree.getEmployeeIntMultiplier()}
<br />* Production Multiplier: x{researchTree.getProductionMultiplier()}
<br />* Sales Multiplier: x{researchTree.getSalesMultiplier()}
<br />* Scientific Research Multiplier: x{researchTree.getScientificResearchMultiplier()}
<br />* Storage Multiplier: x{researchTree.getStorageMultiplier()}
</div>
</div>
);
}

@ -18,7 +18,7 @@ export const HacknetNodeConstants: {
MaxRam: number; MaxRam: number;
MaxCores: number; MaxCores: number;
} = { } = {
MoneyGainPerLevel: 1.6, MoneyGainPerLevel: 1.5,
BaseCost: 1000, BaseCost: 1000,
LevelBaseCost: 1, LevelBaseCost: 1,

@ -150,10 +150,6 @@ import { LogBoxEvents } from "./ui/React/LogBoxManager";
import { arrayToString } from "./utils/helpers/arrayToString"; import { arrayToString } from "./utils/helpers/arrayToString";
import { isString } from "./utils/helpers/isString"; import { isString } from "./utils/helpers/isString";
import { createElement } from "./ui/uiHelpers/createElement";
import { createPopup } from "./ui/uiHelpers/createPopup";
import { removeElementById } from "./ui/uiHelpers/removeElementById";
import { OfficeSpace } from "./Corporation/OfficeSpace"; import { OfficeSpace } from "./Corporation/OfficeSpace";
import { Employee } from "./Corporation/Employee"; import { Employee } from "./Corporation/Employee";
import { Product } from "./Corporation/Product"; import { Product } from "./Corporation/Product";

@ -152,6 +152,15 @@ SourceFiles["SourceFile9"] = new SourceFile(
<br /> <br />
(Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT when installing (Note that the Level 3 effect of this Source-File only applies when entering a new BitNode, NOT when installing
Augmentations) Augmentations)
<br />
<br />
This Source-File also increases your hacknet multipliers by:
<br />
Level 1: 8%
<br />
Level 2: 12%
<br />
Level 3: 14%
</> </>
), ),
); );

@ -133,7 +133,17 @@ export function applySourceFile(srcFile: PlayerOwnedSourceFile): void {
} }
case 9: { case 9: {
// Hacktocracy // Hacktocracy
// This has non-multiplier effects let mult = 0;
for (let i = 0; i < srcFile.lvl; ++i) {
mult += 8 / Math.pow(2, i);
}
const incMult = 1 + mult / 100;
const decMult = 1 - mult / 100;
Player.hacknet_node_core_cost_mult *= decMult;
Player.hacknet_node_level_cost_mult *= decMult;
Player.hacknet_node_money_mult *= incMult;
Player.hacknet_node_purchase_cost_mult *= decMult;
Player.hacknet_node_ram_cost_mult *= decMult;
break; break;
} }
case 10: { case 10: {

File diff suppressed because it is too large Load Diff

@ -1 +0,0 @@
declare module "treant-js";

@ -53,5 +53,4 @@
<body> <body>
<div id="root" /> <div id="root" />
</body> </body>
<script src="src/ThirdParty/raphael.min.js"></script>
</html> </html>

@ -1,81 +0,0 @@
/**
* Wrapper around material-ui's TextField component that styles it with
* Bitburner's UI theme
*/
import React from "react";
import { TextField, TextFieldProps } from "@mui/material";
import makeStyles from "@mui/styles/makeStyles";
const backgroundColorStyles = {
backgroundColor: "rgba(57, 54, 54, 0.9)",
"&:hover": {
backgroundColor: "rgba(70, 70, 70, 0.9)",
},
};
const formControlStyles = {
border: "1px solid #e2e2e1",
overflow: "hidden",
borderRadius: 4,
color: "white",
...backgroundColorStyles,
};
const useStyles = makeStyles({
root: {
...formControlStyles,
},
});
const useInputStyles = makeStyles({
root: {
...backgroundColorStyles,
color: "white",
},
focused: {
backgroundColor: "rgba(70, 70, 70, 0.9)",
},
disabled: {
color: "white",
},
});
const useLabelStyles = makeStyles({
root: {
color: "white",
},
focused: {
color: "white !important", // Need important to override styles from FormLabel
},
disabled: {
color: "white !important", // Need important to override styles from FormLabel
},
});
export const MuiTextField: React.FC<TextFieldProps> = (props: TextFieldProps) => {
return (
<TextField
{...props}
classes={{
...useStyles(),
...props.classes,
}}
InputProps={{
classes: {
...useInputStyles(),
...props.InputProps?.classes,
},
...props.InputProps,
}}
InputLabelProps={{
classes: {
...useLabelStyles(),
...props.InputLabelProps?.classes,
},
...props.InputLabelProps,
}}
/>
);
};

@ -1,38 +0,0 @@
/**
* Wrapper around material-ui's Button component that styles it with
* Bitburner's UI theme
*/
import React from "react";
import { Select as MuiSelect, SelectProps } from "@mui/material";
import { Theme } from "@mui/material/styles";
import makeStyles from "@mui/styles/makeStyles";
import createStyles from "@mui/styles/createStyles";
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
backgroundColor: theme.palette.background.paper,
color: theme.palette.primary.dark,
margin: "5px",
padding: "3px 5px",
"&:after": {
backgroundColor: theme.palette.background.paper,
},
borderRadius: 0,
},
}),
);
export const Select: React.FC<SelectProps> = (props: SelectProps) => {
return (
<MuiSelect
{...props}
classes={{
...useStyles(),
...props.classes,
}}
/>
);
};

@ -1,95 +0,0 @@
/**
* Create a pop-up dialog box using React.
*
* Calling this function with the same ID and React Root Component will trigger a re-render
*
* @param id The (hopefully) unique identifier for the popup container
* @param rootComponent Root React Component for the content (NOT the popup containers themselves)
*/
import * as React from "react";
import * as ReactDOM from "react-dom";
import { Popup } from "./Popup";
import { createElement } from "../uiHelpers/createElement";
import { removeElementById } from "../uiHelpers/removeElementById";
let gameContainer: HTMLElement;
(function () {
function getGameContainer(): void {
const container = document.getElementById("root");
if (container == null) {
throw new Error(`Failed to find game container DOM element`);
}
gameContainer = container;
document.removeEventListener("DOMContentLoaded", getGameContainer);
}
document.addEventListener("DOMContentLoaded", getGameContainer);
})();
// This variable is used to avoid setting the semi-transparent background
// several times on top of one another. Sometimes there's several popup at once.
let deepestPopupId = "";
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function createPopup<T>(
id: string,
rootComponent: (props: T) => React.ReactElement,
props: T,
onClose?: () => void,
): HTMLElement | null {
let container = document.getElementById(id);
if (container == null) {
function onClick(this: HTMLElement, event: MouseEvent): void {
if (!event.srcElement) return;
if (!(event.srcElement instanceof HTMLElement)) return;
const clickedId = (event.srcElement as HTMLElement).id;
if (clickedId !== id) return;
removePopup(id);
if (onClose) onClose();
}
const backgroundColor = deepestPopupId === "" ? "rgba(0,0,0,0.5)" : "rgba(0,0,0,0)";
container = createElement("div", {
class: "popup-box-container",
display: "flex",
id: id,
backgroundColor: backgroundColor,
mouseDown: onClick,
});
gameContainer.appendChild(container);
}
if (deepestPopupId === "") deepestPopupId = id;
ReactDOM.render(
<Popup
content={rootComponent}
id={id}
props={props}
removePopup={() => {
removePopup(id);
if (onClose) onClose();
}}
/>,
container,
);
return container;
}
/**
* Closes a popup created with the createPopup() function above
*/
export function removePopup(id: string): void {
const content = document.getElementById(`${id}`);
if (content == null) return;
ReactDOM.unmountComponentAtNode(content);
removeElementById(id);
removeElementById(`${id}-close`);
if (id === deepestPopupId) deepestPopupId = "";
}

@ -1,36 +0,0 @@
import { setTimeoutRef } from "../utils/SetTimeoutRef";
import { getElementById } from "./uiHelpers/getElementById";
import { Action } from "../types";
const threeSeconds = 3000;
let x: number | undefined;
/**
* Displays a status message to the player for approximately 3 seconds.
* @param text The status text to display
*/
export function createStatusText(text: string): void {
const statusElement: HTMLElement = getElementById("status-text");
const handler: Action = () => {
statusElement.innerText = "";
statusElement.style.display = "none";
statusElement.classList.remove("status-text");
};
if (x !== undefined) {
clearTimeout(x);
// Likely not needed due to clearTimeout, but just in case...
x = undefined;
// reset the element's animation
statusElement.style.animation = "none";
setTimeout(function () {
statusElement.style.animation = "";
}, 10);
}
statusElement.style.display = "block";
statusElement.classList.add("status-text");
statusElement.innerText = text;
x = setTimeoutRef(handler, threeSeconds);
}

@ -1,161 +0,0 @@
/**
* The full-screen page the player is currently be on.
* These pages are mutually exclusive.
*/
export enum Page {
/**
* (Default) The terminal is where the player issues all commands, executes scripts, etc.
*/
Terminal = "Terminal",
/**
* Displays most of the statistics about the player.
*/
CharacterInfo = "CharacterInfo",
/**
* The console for editing Netscript files.
*/
ScriptEditor = "ScriptEditor",
/**
* Monitor the scripts currently executing across the servers.
*/
ActiveScripts = "ActiveScripts",
/**
* View, purchase, and upgrade Hacknet nodes.
*/
HacknetNodes = "HacknetNodes",
/**
* The list of programs the player could potentially build.
*/
CreateProgram = "CreateProgram",
/**
* The list of all factions, and invites, available to the player.
*/
Factions = "Factions",
/**
* Information about a specific faction.
*/
Faction = "Faction",
/**
* The list of installed, and yet-to-be installed, augmentations the player has purchased.
*/
Augmentations = "Augmentations",
/**
* List of milestones that players should follow.
*/
Milestones = "Milestones",
/**
* A collection of in-game material to learn about the game.
*/
Tutorial = "Tutorial",
/**
* A collection of items to manipulate the state of the game. Useful for development.
*/
DevMenu = "Dev Menu",
/**
* Visiting a location in the world
*/
Location = "Location",
/**
* A blocking page to show the player they are currently doing some action (building a program, working, etc.).
*/
workInProgress = "WorkInProgress",
/**
* A special screen to show the player they've reached a certain point in the game.
*/
RedPill = "RedPill",
/**
* A special screen to show the player they've reached a certain point in the game.
*/
CinematicText = "CinematicText",
/**
* Mini-game to infiltrate a company, gaining experience from successful progress.
*/
Infiltration = "Infiltration",
/**
* View the in-game stock market.
*/
StockMarket = "StockMarket",
/**
* Manage gang actions and members.
*/
Gang = "Gang",
/**
* Perform missions for a Faction.
*/
Mission = "Mission",
/**
* Manage a corporation.
*/
Corporation = "Corporation",
/**
* Manage special Bladeburner activities.
*/
Bladeburner = "Bladeburner",
/**
* Manage your Sleeves
*/
Sleeves = "Sleeves",
/**
* Purchase Resleeves
*/
Resleeves = "Re-sleeving",
/**
* Game options
*/
GameOptions = "GameOptions",
}
/**
* This class keeps track of player navigation/routing within the game.
*/
class Routing {
/**
* Tracking the what page the user is currently on.
*/
private currentPage: Page | null = null;
/**
* Determines if the player is currently on the specified page.
* @param page The page to compare against the current state.
*/
isOn(page: Page): boolean {
return this.currentPage === page;
}
/**
* Routes the player to the appropriate page.
* @param page The page to navigate to.
*/
navigateTo(page: Page): void {
this.currentPage = page;
}
}
/**
* The routing instance for tracking page navigation.
*/
export const routing: Routing = new Routing();

@ -1,29 +0,0 @@
import { getElementById } from "./getElementById";
/**
* Given an element by its ID, removes all event listeners from that element by cloning and
* replacing. Then returns the new cloned element.
* @param elemId The HTML ID to retrieve the element by.
*/
export function clearEventListeners(elemId: string | HTMLElement): HTMLElement | null {
try {
let elem: HTMLElement;
if (typeof elemId === "string") {
elem = getElementById(elemId);
} else {
elem = elemId;
}
const newElem: HTMLElement = elem.cloneNode(true) as HTMLElement;
if (elem.parentNode !== null) {
elem.parentNode.replaceChild(newElem, elem);
}
return newElem;
} catch (e) {
// tslint:disable-next-line:no-console
console.error(e);
return null;
}
}

@ -1,308 +0,0 @@
/**
* Options specific to creating an anchor ("<a>") element.
*/
interface ICreateElementAnchorOptions {
href?: string;
target?: string;
text?: string;
}
/**
* Options specific to creating an input ("<input>") element.
*/
interface ICreateElementInputOptions {
checked?: boolean;
max?: string;
maxLength?: number;
min?: string;
name?: string;
pattern?: string;
placeholder?: string;
step?: string;
type?: string;
value?: string;
}
/**
* Options specific to creating a label ("<label>") element.
*/
interface ICreateElementLabelOptions {
for?: string;
}
/**
* Options for setting up event listeners on the element.
*/
interface ICreateElementListenerOptions {
changeListener?(this: HTMLElement, ev: Event): any;
clickListener?(this: HTMLElement, ev: MouseEvent): any;
mouseDown?(this: HTMLElement, ev: MouseEvent): any;
inputListener?(this: HTMLElement, ev: Event): any;
onfocus?(this: HTMLElement, ev: FocusEvent): any;
onkeydown?(this: HTMLElement, ev: KeyboardEvent): any;
onkeyup?(this: HTMLElement, ev: KeyboardEvent): any;
}
/**
* Options for setting up the inline-styling of element.
* NOTE: Relying on CSS styling should be preferred over forcing the higher specificity via inline styles.
*/
interface ICreateElementStyleOptions {
backgroundColor?: string;
border?: string;
color?: string;
display?: string;
float?: string;
fontSize?: string;
margin?: string;
marginLeft?: string;
marginTop?: string;
overflow?: string;
padding?: string;
position?: string;
visibility?: string;
whiteSpace?: string;
width?: string;
height?: string;
top?: string;
left?: string;
}
/**
* Options for adding an in-game tooltip to the element.
*/
interface ICreateElementTooltipOptions {
tooltip?: string;
tooltipleft?: string;
tooltipsmall?: string;
tooltiplow?: string;
}
/**
* All possible configuration options when creating an element.
*/
interface ICreateElementOptions
extends ICreateElementStyleOptions,
ICreateElementListenerOptions,
ICreateElementInputOptions,
ICreateElementAnchorOptions,
ICreateElementLabelOptions,
ICreateElementTooltipOptions {
/**
* CSS Class(es) to initially set.
*/
class?: string;
/**
* A (hopefully) unique identifier for the element.
*/
id?: string;
innerHTML?: string;
innerText?: string;
tabIndex?: number;
}
function setElementAnchor(el: HTMLAnchorElement, params: ICreateElementAnchorOptions): void {
if (params.text !== undefined) {
el.text = params.text;
}
if (params.href !== undefined) {
el.href = params.href;
}
if (params.target !== undefined) {
el.target = params.target;
}
}
function setElementInput(el: HTMLInputElement, params: ICreateElementInputOptions): void {
if (params.name !== undefined) {
el.name = params.name;
}
if (params.value !== undefined) {
el.value = params.value;
}
if (params.type !== undefined) {
el.type = params.type;
}
if (params.checked !== undefined) {
el.checked = params.checked;
}
if (params.pattern !== undefined) {
el.pattern = params.pattern;
}
if (params.maxLength !== undefined) {
el.maxLength = params.maxLength;
}
if (params.placeholder !== undefined) {
el.placeholder = params.placeholder;
}
if (params.max !== undefined) {
el.max = params.max;
}
if (params.min !== undefined) {
el.min = params.min;
}
if (params.step !== undefined) {
el.step = params.step;
}
}
function setElementLabel(el: HTMLLabelElement, params: ICreateElementLabelOptions): void {
if (params.for !== undefined) {
el.htmlFor = params.for;
}
}
function setElementListeners(el: HTMLElement, params: ICreateElementListenerOptions): void {
// tslint:disable:no-unbound-method
if (params.clickListener !== undefined) {
el.addEventListener("click", params.clickListener);
}
if (params.mouseDown !== undefined) {
el.addEventListener("mousedown", params.mouseDown);
}
if (params.inputListener !== undefined) {
el.addEventListener("input", params.inputListener);
}
if (params.changeListener !== undefined) {
el.addEventListener("change", params.changeListener);
}
if (params.onkeyup !== undefined) {
el.addEventListener("keyup", params.onkeyup);
}
if (params.onkeydown !== undefined) {
el.addEventListener("keydown", params.onkeydown);
}
if (params.onfocus !== undefined) {
el.addEventListener("focus", params.onfocus);
}
// tslint:enable:no-unbound-method
}
function setElementStyle(el: HTMLElement, params: ICreateElementStyleOptions): void {
if (params.display !== undefined) {
el.style.display = params.display;
}
if (params.visibility !== undefined) {
el.style.visibility = params.visibility;
}
if (params.margin !== undefined) {
el.style.margin = params.margin;
}
if (params.marginLeft !== undefined) {
el.style.marginLeft = params.marginLeft;
}
if (params.marginTop !== undefined) {
el.style.marginTop = params.marginTop;
}
if (params.padding !== undefined) {
el.style.padding = params.padding;
}
if (params.color !== undefined) {
el.style.color = params.color;
}
if (params.border !== undefined) {
el.style.border = params.border;
}
if (params.float !== undefined) {
el.style.cssFloat = params.float;
}
if (params.fontSize !== undefined) {
el.style.fontSize = params.fontSize;
}
if (params.whiteSpace !== undefined) {
el.style.whiteSpace = params.whiteSpace;
}
if (params.width !== undefined) {
el.style.width = params.width;
}
if (params.height !== undefined) {
el.style.height = params.height;
}
if (params.top !== undefined) {
el.style.top = params.top;
}
if (params.left !== undefined) {
el.style.left = params.left;
}
if (params.backgroundColor !== undefined) {
el.style.backgroundColor = params.backgroundColor;
}
if (params.position !== undefined) {
el.style.position = params.position;
}
if (params.overflow !== undefined) {
el.style.overflow = params.overflow;
}
}
function setElementTooltip(el: HTMLElement, params: ICreateElementTooltipOptions): void {
if (params.tooltip !== undefined && params.tooltip !== "") {
el.className += " tooltip";
el.appendChild(
createElement("span", {
class: "tooltiptext",
innerHTML: params.tooltip,
}),
);
} else if (params.tooltipleft !== undefined) {
el.className += " tooltip";
el.appendChild(
createElement("span", {
class: "tooltiptextleft",
innerHTML: params.tooltipleft,
}),
);
} else if (params.tooltipsmall !== undefined) {
el.className += " tooltip";
el.appendChild(
createElement("span", {
class: "tooltiptext smallfont",
innerHTML: params.tooltipsmall,
}),
);
} else if (params.tooltiplow !== undefined) {
el.className += "tooltip";
el.appendChild(
createElement("span", {
class: "tooltiptextlow",
innerHTML: params.tooltiplow,
}),
);
}
}
/**
* An all-in-one-call way of creating an element to be added to the DOM at some point.
* @param tagName The HTML tag/element name
* @param params Additional parameters to set on the element
*/
export function createElement(tagName: string, params: ICreateElementOptions = {}): HTMLElement {
const el: HTMLElement = document.createElement(tagName);
if (params.id !== undefined) {
el.id = params.id;
}
if (params.class !== undefined) {
el.className = params.class;
}
if (params.innerHTML !== undefined) {
el.innerHTML = params.innerHTML;
}
if (params.innerText !== undefined) {
el.innerText = params.innerText;
}
if (params.tabIndex !== undefined) {
el.tabIndex = params.tabIndex;
}
setElementAnchor(el as HTMLAnchorElement, params);
setElementInput(el as HTMLInputElement, params);
setElementLabel(el as HTMLLabelElement, params);
setElementListeners(el, params);
setElementStyle(el, params);
setElementTooltip(el, params);
return el;
}

@ -1,37 +0,0 @@
import { createElement } from "./createElement";
import { getElementById } from "./getElementById";
interface ICreatePopupOptions {
backgroundColor?: string;
}
/**
* Creates the necessary DOM elements to present an in-game popup to the player.
* @param id The (hopefully) unique identifier for the popup container.
* @param elems The collection of HTML Elements to show within the popup.
*/
export function createPopup(id: string, elems: HTMLElement[], options: ICreatePopupOptions = {}): HTMLDivElement {
const container: HTMLDivElement = createElement("div", {
class: "popup-box-container",
display: "flex",
id: id,
}) as HTMLDivElement;
const content: HTMLElement = createElement("div", {
class: "popup-box-content",
id: `${id}-content`,
});
for (const elem of elems) {
content.appendChild(elem);
}
// Configurable Options
if (options.backgroundColor) {
content.style.backgroundColor = options.backgroundColor;
}
container.appendChild(content);
getElementById("root").appendChild(container);
return container;
}

@ -1,14 +0,0 @@
/**
* Returns a reference to the first object with the specified value of the ID or NAME attribute,
* throwing an error if it is unable to find it.
* @param elementId The HTML ID to retrieve the element by.
* @throws {Error} When the 'elementId' cannot be found.
*/
export function getElementById(elementId: string): HTMLElement {
const el: HTMLElement | null = document.getElementById(elementId);
if (el === null) {
throw new Error(`Unable to find element with id '${elementId}'`);
}
return el;
}

@ -1,25 +0,0 @@
export function getSelectValue(selector: HTMLSelectElement | null): string {
if (selector == null) {
return "";
}
if (selector.options.length <= 0) {
return "";
}
if (selector.selectedIndex < 0) {
return "";
}
return selector.options[selector.selectedIndex].value;
}
export function getSelectText(selector: HTMLSelectElement | null): string {
if (selector == null) {
return "";
}
if (selector.options.length <= 0) {
return "";
}
if (selector.selectedIndex < 0) {
return "";
}
return selector.options[selector.selectedIndex].text;
}

@ -1,28 +0,0 @@
import { isString } from "../../utils/helpers/isString";
import { getElementById } from "./getElementById";
/**
* Clears out all children from the provided element.
* If a string is passed in, it will treat it as an ID and search for the element to delete all children from.
* @param el The element or ID of an element to remove all children from.
*/
export function removeChildrenFromElement(el: string | null | Element): void {
if (el === null) {
return;
}
try {
const elem: HTMLElement | Element = isString(el) ? getElementById(el as string) : (el as Element);
if (elem instanceof Element) {
while (elem.firstChild !== null) {
elem.removeChild(elem.firstChild);
}
}
} catch (e) {
// tslint:disable-next-line:no-console
console.debug(e);
return;
}
}

@ -1,26 +0,0 @@
/**
* For a given element, this function removes it AND its children
* @param elem The element to remove.
*/
export function removeElement(elem: Element | null): void {
if (elem === null) {
// tslint:disable-next-line:no-console
console.debug("The element passed into 'removeElement' was null.");
return;
}
if (!(elem instanceof Element)) {
// tslint:disable-next-line:no-console
console.debug("The element passed into 'removeElement' was not an instance of an Element.");
return;
}
while (elem.firstChild !== null) {
elem.removeChild(elem.firstChild);
}
if (elem.parentNode !== null) {
elem.parentNode.removeChild(elem);
}
}

@ -1,15 +0,0 @@
import { getElementById } from "./getElementById";
import { removeElement } from "./removeElement";
/**
* Given its id, this function removes an element AND its children
* @param id The HTML identifier to search for and remove.
*/
export function removeElementById(id: string): void {
try {
const elem: HTMLElement = getElementById(id);
removeElement(elem);
} catch (e) {
// Probably should log this as we're trying to remove elements that don't exist.
}
}