pre-dialogbox-convert

This commit is contained in:
Olivier Gagnon
2021-10-01 01:00:50 -04:00
parent 0d9caac455
commit 62cd8ffcc6
26 changed files with 989 additions and 883 deletions

View File

@@ -148,6 +148,7 @@
"format": "prettier --write .", "format": "prettier --write .",
"start": "http-server -p 8000", "start": "http-server -p 8000",
"start:dev": "webpack-dev-server --progress --env.devServer --mode development", "start:dev": "webpack-dev-server --progress --env.devServer --mode development",
"start:dev-fast": "webpack-dev-server --progress --env.devServer --mode development --fast true",
"start:container": "webpack-dev-server --progress --env.devServer --mode development --env.runInContainer", "start:container": "webpack-dev-server --progress --env.devServer --mode development --env.runInContainer",
"build": "webpack --mode production", "build": "webpack --mode production",
"build:dev": "webpack --mode development", "build:dev": "webpack --mode development",

View File

@@ -7,9 +7,11 @@ import { Deck } from "./CardDeck/Deck";
import { Hand } from "./CardDeck/Hand"; import { Hand } from "./CardDeck/Hand";
import { InputAdornment } from "@mui/material"; import { InputAdornment } from "@mui/material";
import { ReactCard } from "./CardDeck/ReactCard"; import { ReactCard } from "./CardDeck/ReactCard";
import { MuiTextField } from "../ui/React/MuiTextField"; import Button from "@mui/material/Button";
import { MuiButton } from "../ui/React/MuiButton"; import Paper from "@mui/material/Paper";
import { MuiPaper } from "../ui/React/MuiPaper"; import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField";
const MAX_BET = 100e6; const MAX_BET = 100e6;
@@ -308,7 +310,7 @@ export class Blackjack extends Game<Props, State> {
<div> <div>
{/* Wager input */} {/* Wager input */}
<div> <div>
<MuiTextField <TextField
value={betInput} value={betInput}
label={ label={
<> <>
@@ -322,12 +324,15 @@ export class Blackjack extends Game<Props, State> {
error={wagerInvalid} error={wagerInvalid}
helperText={wagerInvalid ? wagerInvalidHelperText : ""} helperText={wagerInvalid ? wagerInvalidHelperText : ""}
type="number" type="number"
variant="filled"
style={{ style={{
width: "200px", width: "200px",
}} }}
InputProps={{ InputProps={{
startAdornment: <InputAdornment position="start">$</InputAdornment>, startAdornment: (
<InputAdornment position="start">
<Typography>$</Typography>
</InputAdornment>
),
}} }}
/> />
@@ -340,16 +345,16 @@ export class Blackjack extends Game<Props, State> {
{/* Buttons */} {/* Buttons */}
{!gameInProgress ? ( {!gameInProgress ? (
<div> <div>
<MuiButton onClick={this.startOnClick} disabled={wagerInvalid || !this.canStartGame()}> <Button onClick={this.startOnClick} disabled={wagerInvalid || !this.canStartGame()}>
Start Start
</MuiButton> </Button>
</div> </div>
) : ( ) : (
<div> <div>
<MuiButton onClick={this.playerHit}>Hit</MuiButton> <Button onClick={this.playerHit}>Hit</Button>
<MuiButton color="secondary" onClick={this.playerStay}> <Button color="secondary" onClick={this.playerStay}>
Stay Stay
</MuiButton> </Button>
</div> </div>
)} )}
@@ -357,36 +362,40 @@ export class Blackjack extends Game<Props, State> {
* the cards that led to that result. */} * the cards that led to that result. */}
{(gameInProgress || result !== Result.Pending) && ( {(gameInProgress || result !== Result.Pending) && (
<div> <div>
<MuiPaper variant="outlined" elevation={2}> <Box display="flex">
<pre>Player</pre> <Paper elevation={2}>
{playerHand.cards.map((card, i) => ( <pre>Player</pre>
<ReactCard card={card} key={i} /> {playerHand.cards.map((card, i) => (
))} <ReactCard card={card} key={i} />
))}
<pre>Value(s): </pre> <pre>Value(s): </pre>
{playerHandValues.map((value, i) => ( {playerHandValues.map((value, i) => (
<pre key={i}>{value}</pre> <pre key={i}>{value}</pre>
))} ))}
</MuiPaper> </Paper>
</Box>
<br /> <br />
<MuiPaper variant="outlined" elevation={2}> <Box display="flex">
<pre>Dealer</pre> <Paper elevation={2}>
{dealerHand.cards.map((card, i) => ( <pre>Dealer</pre>
// Hide every card except the first while game is in progress {dealerHand.cards.map((card, i) => (
<ReactCard card={card} hidden={gameInProgress && i !== 0} key={i} /> // Hide every card except the first while game is in progress
))} <ReactCard card={card} hidden={gameInProgress && i !== 0} key={i} />
))}
{!gameInProgress && ( {!gameInProgress && (
<> <>
<pre>Value(s): </pre> <pre>Value(s): </pre>
{dealerHandValues.map((value, i) => ( {dealerHandValues.map((value, i) => (
<pre key={i}>{value}</pre> <pre key={i}>{value}</pre>
))} ))}
</> </>
)} )}
</MuiPaper> </Paper>
</Box>
</div> </div>
)} )}

View File

@@ -1,12 +1,43 @@
import React, { FC } from "react"; import React, { FC } from "react";
import { Card, Suit } from "./Card"; import { Card, Suit } from "./Card";
import { Theme } from "@mui/material";
import makeStyles from "@mui/styles/makeStyles";
import createStyles from "@mui/styles/createStyles";
type Props = { type Props = {
card: Card; card: Card;
hidden?: boolean; hidden?: boolean;
}; };
const useStyles = makeStyles(() =>
createStyles({
card: {
padding: "10px",
border: "solid 1px #808080",
backgroundColor: "white",
display: "inline-block",
borderRadius: "10px",
fontSize: "18.5px",
textAlign: "center",
margin: "3px",
fontWeight: "bold",
},
red: {
color: "red",
},
black: {
color: "black",
},
value: {
fontSize: "20px",
fontFamily: "sans-serif",
},
}),
);
export const ReactCard: FC<Props> = ({ card, hidden }) => { export const ReactCard: FC<Props> = ({ card, hidden }) => {
const classes = useStyles();
let suit: React.ReactNode; let suit: React.ReactNode;
switch (card.suit) { switch (card.suit) {
case Suit.Clubs: case Suit.Clubs:
@@ -25,10 +56,10 @@ export const ReactCard: FC<Props> = ({ card, hidden }) => {
throw new Error(`MissingCaseException: ${card.suit}`); throw new Error(`MissingCaseException: ${card.suit}`);
} }
return ( return (
<div className={`casino-card ${card.isRedSuit() ? "red" : "black"}`}> <div className={`${classes.card} ${card.isRedSuit() ? classes.red : classes.black}`}>
<> <>
<div className="value">{hidden ? " - " : card.formatValue()}</div> <div className={classes.value}>{hidden ? " - " : card.formatValue()}</div>
<div className={`suit`}>{hidden ? " - " : suit}</div> <div>{hidden ? " - " : suit}</div>
</> </>
</div> </div>
); );

View File

@@ -3,44 +3,32 @@
* *
* This subcomponent renders all of the buttons for training at the gym * This subcomponent renders all of the buttons for training at the gym
*/ */
import * as React from "react"; import React, { useState } from "react";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { StdButton } from "../ui/React/StdButton";
import { BadRNG } from "./RNG"; import { BadRNG } from "./RNG";
import { Game } from "./Game"; import { win, reachedLimit } from "./Game";
import { trusted } from "./utils"; import { trusted } from "./utils";
import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";
import Box from "@mui/material/Box";
type IProps = { type IProps = {
p: IPlayer; p: IPlayer;
}; };
type IState = {
investment: number;
result: any;
status: string;
playLock: boolean;
};
const minPlay = 0; const minPlay = 0;
const maxPlay = 10e3; const maxPlay = 10e3;
export class CoinFlip extends Game<IProps, IState> { export function CoinFlip(props: IProps): React.ReactElement {
constructor(props: IProps) { const [investment, setInvestment] = useState(1000);
super(props); const [result, setResult] = useState(<span> </span>);
const [status, setStatus] = useState("");
const [playLock, setPlayLock] = useState(false);
this.state = { function updateInvestment(e: React.ChangeEvent<HTMLInputElement>): void {
investment: 1000,
result: <span> </span>,
status: "",
playLock: false,
};
this.play = this.play.bind(this);
this.updateInvestment = this.updateInvestment.bind(this);
}
updateInvestment(e: React.FormEvent<HTMLInputElement>): void {
let investment: number = parseInt(e.currentTarget.value); let investment: number = parseInt(e.currentTarget.value);
if (isNaN(investment)) { if (isNaN(investment)) {
investment = minPlay; investment = minPlay;
@@ -51,11 +39,11 @@ export class CoinFlip extends Game<IProps, IState> {
if (investment < minPlay) { if (investment < minPlay) {
investment = minPlay; investment = minPlay;
} }
this.setState({ investment: investment }); setInvestment(investment);
} }
play(guess: string): void { function play(guess: string): void {
if (this.reachedLimit(this.props.p)) return; if (reachedLimit(props.p)) return;
const v = BadRNG.random(); const v = BadRNG.random();
let letter: string; let letter: string;
if (v < 0.5) { if (v < 0.5) {
@@ -64,39 +52,51 @@ export class CoinFlip extends Game<IProps, IState> {
letter = "T"; letter = "T";
} }
const correct: boolean = guess === letter; const correct: boolean = guess === letter;
this.setState({
result: <span className={correct ? "text" : "failure"}>{letter}</span>, setResult(<span className={correct ? "text" : "failure"}>{letter}</span>);
status: correct ? " win!" : "lose!", setStatus(correct ? " win!" : "lose!");
playLock: true, setPlayLock(true);
});
setTimeout(() => this.setState({ playLock: false }), 250); setTimeout(() => setPlayLock(false), 250);
if (correct) { if (correct) {
this.win(this.props.p, this.state.investment); win(props.p, investment);
} else { } else {
this.win(this.props.p, -this.state.investment); win(props.p, -investment);
} }
if (this.reachedLimit(this.props.p)) return; if (reachedLimit(props.p)) return;
} }
render(): React.ReactNode { return (
return ( <>
<> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>{`+———————+`}</Typography>
<pre>{`+———————+`}</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>{`| | | |`}</Typography>
<pre>{`| | | |`}</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>
<pre> {`| | `}
{`| | `} {result}
{this.state.result} {` | |`}
{` | |`} </Typography>
</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>{`| | | |`}</Typography>
<pre>{`| | | |`}</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>{`+———————+`}</Typography>
<pre>{`+———————+`}</pre> <Box display="flex" alignItems="center">
<span className="text">Play for: </span> <TextField
<input type="number" className="text-input" onChange={this.updateInvestment} value={this.state.investment} /> type="number"
<br /> onChange={updateInvestment}
<StdButton onClick={trusted(() => this.play("H"))} text={"Head!"} disabled={this.state.playLock} /> InputProps={{
<StdButton onClick={trusted(() => this.play("T"))} text={"Tail!"} disabled={this.state.playLock} /> endAdornment: (
<h1>{this.state.status}</h1> <>
</> <Button onClick={trusted(() => play("H"))} disabled={playLock}>
); Head!
} </Button>
<Button onClick={trusted(() => play("T"))} disabled={playLock}>
Tail!
</Button>
</>
),
}}
/>
</Box>
<Typography variant="h3">{status}</Typography>
</>
);
} }

View File

@@ -4,6 +4,19 @@ import { dialogBoxCreate } from "../ui/React/DialogBox";
const gainLimit = 10e9; const gainLimit = 10e9;
export function win(p: IPlayer, n: number): void {
p.gainMoney(n);
p.recordMoneySource(n, "casino");
}
export function reachedLimit(p: IPlayer): boolean {
const reached = p.getCasinoWinnings() > gainLimit;
if (reached) {
dialogBoxCreate(<>Alright cheater get out of here. You're not allowed here anymore.</>);
}
return reached;
}
export class Game<T, U> extends React.Component<T, U> { export class Game<T, U> extends React.Component<T, U> {
win(p: IPlayer, n: number): void { win(p: IPlayer, n: number): void {
p.gainMoney(n); p.gainMoney(n);

View File

@@ -1,25 +1,18 @@
import * as React from "react"; import React, { useState, useEffect } from "react";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { StdButton } from "../ui/React/StdButton";
import { Money } from "../ui/React/Money"; import { Money } from "../ui/React/Money";
import { Game } from "./Game"; import { win, reachedLimit } from "./Game";
import { WHRNG } from "./RNG"; import { WHRNG } from "./RNG";
import { trusted } from "./utils"; import { trusted } from "./utils";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField";
type IProps = { type IProps = {
p: IPlayer; p: IPlayer;
}; };
type IState = {
investment: number;
canPlay: boolean;
status: string | JSX.Element;
n: number;
lock: boolean;
strategy: Strategy;
};
const minPlay = 0; const minPlay = 0;
const maxPlay = 1e7; const maxPlay = 1e7;
@@ -118,48 +111,32 @@ function Single(s: number): Strategy {
}; };
} }
export class Roulette extends Game<IProps, IState> { export function Roulette(props: IProps): React.ReactElement {
interval = -1; const [rng] = useState(new WHRNG(new Date().getTime()));
rng: WHRNG; const [investment, setInvestment] = useState(1000);
const [canPlay, setCanPlay] = useState(true);
const [status, setStatus] = useState<string | JSX.Element>("waiting");
const [n, setN] = useState(0);
const [lock, setLock] = useState(true);
const [strategy, setStrategy] = useState<Strategy>({
payout: 0,
match: (): boolean => {
return false;
},
});
constructor(props: IProps) { useEffect(() => {
super(props); const i = window.setInterval(step, 50);
return () => clearInterval(i);
});
this.rng = new WHRNG(new Date().getTime()); function step(): void {
this.state = { if (!lock) {
investment: 1000, setN(Math.floor(Math.random() * 37));
canPlay: true,
status: "waiting",
n: 0,
lock: true,
strategy: {
payout: 0,
match: (): boolean => {
return false;
},
},
};
this.step = this.step.bind(this);
this.currentNumber = this.currentNumber.bind(this);
this.updateInvestment = this.updateInvestment.bind(this);
}
componentDidMount(): void {
this.interval = window.setInterval(this.step, 50);
}
step(): void {
if (!this.state.lock) {
this.setState({ n: Math.floor(Math.random() * 37) });
} }
} }
componentWillUnmount(): void { function updateInvestment(e: React.ChangeEvent<HTMLInputElement>): void {
clearInterval(this.interval);
}
updateInvestment(e: React.FormEvent<HTMLInputElement>): void {
let investment: number = parseInt(e.currentTarget.value); let investment: number = parseInt(e.currentTarget.value);
if (isNaN(investment)) { if (isNaN(investment)) {
investment = minPlay; investment = minPlay;
@@ -170,265 +147,312 @@ export class Roulette extends Game<IProps, IState> {
if (investment < minPlay) { if (investment < minPlay) {
investment = minPlay; investment = minPlay;
} }
this.setState({ investment: investment }); setInvestment(investment);
} }
currentNumber(): string { function currentNumber(): string {
if (this.state.n === 0) return "0"; if (n === 0) return "0";
const color = isRed(this.state.n) ? "R" : "B"; const color = isRed(n) ? "R" : "B";
return `${this.state.n}${color}`; return `${n}${color}`;
} }
play(s: Strategy): void { function play(s: Strategy): void {
if (this.reachedLimit(this.props.p)) return; if (reachedLimit(props.p)) return;
this.setState({
canPlay: false, setCanPlay(false);
lock: false, setLock(false);
status: "playing", setStatus("playing");
strategy: s, setStrategy(s);
});
setTimeout(() => { setTimeout(() => {
let n = Math.floor(this.rng.random() * 37); let n = Math.floor(rng.random() * 37);
let status = <></>; let status = <></>;
let gain = 0; let gain = 0;
let playerWin = this.state.strategy.match(n); let playerWin = strategy.match(n);
// oh yeah, the house straight up cheats. Try finding the seed now! // oh yeah, the house straight up cheats. Try finding the seed now!
if (playerWin && Math.random() > 0.9) { if (playerWin && Math.random() > 0.9) {
playerWin = false; playerWin = false;
while (this.state.strategy.match(n)) { while (strategy.match(n)) {
n = (n + 1) % 36; n = (n + 1) % 36;
} }
} }
if (playerWin) { if (playerWin) {
gain = this.state.investment * this.state.strategy.payout; gain = investment * strategy.payout;
status = ( status = (
<> <>
won <Money money={gain} /> won <Money money={gain} />
</> </>
); );
} else { } else {
gain = -this.state.investment; gain = -investment;
status = ( status = (
<> <>
lost <Money money={-gain} /> lost <Money money={-gain} />
</> </>
); );
} }
this.win(this.props.p, gain); win(props.p, gain);
this.setState({
canPlay: true, setCanPlay(true);
lock: true, setLock(true);
status: status, setStatus(status);
n: n, setN(n);
});
this.reachedLimit(this.props.p); reachedLimit(props.p);
}, 1600); }, 1600);
} }
render(): React.ReactNode { return (
return ( <>
<> <Typography variant="h4">{currentNumber()}</Typography>
<h1>{this.currentNumber()}</h1> <TextField type="number" onChange={updateInvestment} placeholder={"Amount to play"} disabled={!canPlay} />
<input <Typography variant="h4">{status}</Typography>
type="number" <table>
className="text-input" <tbody>
onChange={this.updateInvestment} <tr>
placeholder={"Amount to play"} <td>
value={this.state.investment} <Button disabled={!canPlay} onClick={trusted(() => play(Single(3)))}>
disabled={!this.state.canPlay} 3
/> </Button>
<h1>{this.state.status}</h1> </td>
<table> <td>
<tbody> <Button disabled={!canPlay} onClick={trusted(() => play(Single(6)))}>
<tr> 6
<td> </Button>
<StdButton text={"3"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(3)))} /> </td>
</td> <td>
<td> <Button disabled={!canPlay} onClick={trusted(() => play(Single(9)))}>
<StdButton text={"6"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(6)))} /> 9
</td> </Button>
<td> </td>
<StdButton text={"9"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(9)))} /> <td>
</td> <Button disabled={!canPlay} onClick={trusted(() => play(Single(12)))}>
<td> 12
<StdButton text={"12"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(12)))} /> </Button>
</td> </td>
<td> <td>
<StdButton text={"15"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(15)))} /> <Button disabled={!canPlay} onClick={trusted(() => play(Single(15)))}>
</td> 15
<td> </Button>
<StdButton text={"18"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(18)))} /> </td>
</td> <td>
<td> <Button disabled={!canPlay} onClick={trusted(() => play(Single(18)))}>
<StdButton text={"21"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(21)))} /> 18
</td> </Button>
<td> </td>
<StdButton text={"24"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(24)))} /> <td>
</td> <Button disabled={!canPlay} onClick={trusted(() => play(Single(21)))}>
<td> 21
<StdButton text={"27"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(27)))} /> </Button>
</td> </td>
<td> <td>
<StdButton text={"30"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(30)))} /> <Button disabled={!canPlay} onClick={trusted(() => play(Single(24)))}>
</td> 24
<td> </Button>
<StdButton text={"33"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(33)))} /> </td>
</td> <td>
<td> <Button disabled={!canPlay} onClick={trusted(() => play(Single(27)))}>
<StdButton text={"36"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(36)))} /> 27
</td> </Button>
</tr> </td>
<tr> <td>
<td> <Button disabled={!canPlay} onClick={trusted(() => play(Single(30)))}>
<StdButton text={"2"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(2)))} /> 30
</td> </Button>
<td> </td>
<StdButton text={"5"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(5)))} /> <td>
</td> <Button disabled={!canPlay} onClick={trusted(() => play(Single(33)))}>
<td> 33
<StdButton text={"8"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(8)))} /> </Button>
</td> </td>
<td> <td>
<StdButton text={"11"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(11)))} /> <Button disabled={!canPlay} onClick={trusted(() => play(Single(36)))}>
</td> 36
<td> </Button>
<StdButton text={"14"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(14)))} /> </td>
</td> </tr>
<td> <tr>
<StdButton text={"17"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(17)))} /> <td>
</td> <Button disabled={!canPlay} onClick={trusted(() => play(Single(2)))}>
<td> 2
<StdButton text={"20"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(20)))} /> </Button>
</td> </td>
<td> <td>
<StdButton text={"23"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(23)))} /> <Button disabled={!canPlay} onClick={trusted(() => play(Single(5)))}>
</td> 5
<td> </Button>
<StdButton text={"26"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(26)))} /> </td>
</td> <td>
<td> <Button disabled={!canPlay} onClick={trusted(() => play(Single(8)))}>
<StdButton text={"29"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(29)))} /> 8
</td> </Button>
<td> </td>
<StdButton text={"32"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(32)))} /> <td>
</td> <Button disabled={!canPlay} onClick={trusted(() => play(Single(11)))}>
<td> 11
<StdButton text={"35"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(35)))} /> </Button>
</td> </td>
</tr> <td>
<tr> <Button disabled={!canPlay} onClick={trusted(() => play(Single(14)))}>
<td> 14
<StdButton text={"1"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(1)))} /> </Button>
</td> </td>
<td> <td>
<StdButton text={"4"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(4)))} /> <Button disabled={!canPlay} onClick={trusted(() => play(Single(17)))}>
</td> 17
<td> </Button>
<StdButton text={"7"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(7)))} /> </td>
</td> <td>
<td> <Button disabled={!canPlay} onClick={trusted(() => play(Single(20)))}>
<StdButton text={"10"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(10)))} /> 20
</td> </Button>
<td> </td>
<StdButton text={"13"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(13)))} /> <td>
</td> <Button disabled={!canPlay} onClick={trusted(() => play(Single(23)))}>
<td> 23
<StdButton text={"16"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(16)))} /> </Button>
</td> </td>
<td> <td>
<StdButton text={"19"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(19)))} /> <Button disabled={!canPlay} onClick={trusted(() => play(Single(26)))}>
</td> 26
<td> </Button>
<StdButton text={"22"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(22)))} /> </td>
</td> <td>
<td> <Button disabled={!canPlay} onClick={trusted(() => play(Single(29)))}>
<StdButton text={"25"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(25)))} /> 29
</td> </Button>
<td> </td>
<StdButton text={"28"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(28)))} /> <td>
</td> <Button disabled={!canPlay} onClick={trusted(() => play(Single(32)))}>
<td> 32
<StdButton text={"31"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(31)))} /> </Button>
</td> </td>
<td> <td>
<StdButton text={"34"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(34)))} /> <Button disabled={!canPlay} onClick={trusted(() => play(Single(35)))}>
</td> 35
</tr> </Button>
<tr> </td>
<td colSpan={4}> </tr>
<StdButton <tr>
text={"1 to 12"} <td>
disabled={!this.state.canPlay} <Button disabled={!canPlay} onClick={trusted(() => play(Single(1)))}>
onClick={trusted(() => this.play(strategies.Third1))} 1
/> </Button>
</td> </td>
<td colSpan={4}> <td>
<StdButton <Button disabled={!canPlay} onClick={trusted(() => play(Single(4)))}>
text={"13 to 24"} 4
disabled={!this.state.canPlay} </Button>
onClick={trusted(() => this.play(strategies.Third2))} </td>
/> <td>
</td> <Button disabled={!canPlay} onClick={trusted(() => play(Single(7)))}>
<td colSpan={4}> 7
<StdButton </Button>
text={"25 to 36"} </td>
disabled={!this.state.canPlay} <td>
onClick={trusted(() => this.play(strategies.Third3))} <Button disabled={!canPlay} onClick={trusted(() => play(Single(10)))}>
/> 10
</td> </Button>
</tr> </td>
<tr> <td>
<td colSpan={2}> <Button disabled={!canPlay} onClick={trusted(() => play(Single(13)))}>
<StdButton 13
text={"Red"} </Button>
disabled={!this.state.canPlay} </td>
onClick={trusted(() => this.play(strategies.Red))} <td>
/> <Button disabled={!canPlay} onClick={trusted(() => play(Single(16)))}>
</td> 16
<td colSpan={2}> </Button>
<StdButton </td>
text={"Black"} <td>
disabled={!this.state.canPlay} <Button disabled={!canPlay} onClick={trusted(() => play(Single(19)))}>
onClick={trusted(() => this.play(strategies.Black))} 19
/> </Button>
</td> </td>
<td colSpan={2}> <td>
<StdButton <Button disabled={!canPlay} onClick={trusted(() => play(Single(22)))}>
text={"Odd"} 22
disabled={!this.state.canPlay} </Button>
onClick={trusted(() => this.play(strategies.Odd))} </td>
/> <td>
</td> <Button disabled={!canPlay} onClick={trusted(() => play(Single(25)))}>
<td colSpan={2}> 25
<StdButton </Button>
text={"Even"} </td>
disabled={!this.state.canPlay} <td>
onClick={trusted(() => this.play(strategies.Even))} <Button disabled={!canPlay} onClick={trusted(() => play(Single(28)))}>
/> 28
</td> </Button>
<td colSpan={2}> </td>
<StdButton <td>
text={"High"} <Button disabled={!canPlay} onClick={trusted(() => play(Single(31)))}>
disabled={!this.state.canPlay} 31
onClick={trusted(() => this.play(strategies.High))} </Button>
/> </td>
</td> <td>
<td colSpan={2}> <Button disabled={!canPlay} onClick={trusted(() => play(Single(34)))}>
<StdButton 34
text={"Low"} </Button>
disabled={!this.state.canPlay} </td>
onClick={trusted(() => this.play(strategies.Low))} </tr>
/> <tr>
</td> <td colSpan={4}>
</tr> <Button disabled={!canPlay} onClick={trusted(() => play(strategies.Third1))}>
<tr> 1 to 12
<td> </Button>
<StdButton text={"0"} disabled={!this.state.canPlay} onClick={trusted(() => this.play(Single(0)))} /> </td>
</td> <td colSpan={4}>
</tr> <Button disabled={!canPlay} onClick={trusted(() => play(strategies.Third2))}>
</tbody> 13 to 24
</table> </Button>
</> </td>
); <td colSpan={4}>
} <Button disabled={!canPlay} onClick={trusted(() => play(strategies.Third3))}>
25 to 36
</Button>
</td>
</tr>
<tr>
<td colSpan={2}>
<Button disabled={!canPlay} onClick={trusted(() => play(strategies.Red))}>
Red
</Button>
</td>
<td colSpan={2}>
<Button disabled={!canPlay} onClick={trusted(() => play(strategies.Black))}>
Black
</Button>
</td>
<td colSpan={2}>
<Button disabled={!canPlay} onClick={trusted(() => play(strategies.Odd))}>
Odd
</Button>
</td>
<td colSpan={2}>
<Button disabled={!canPlay} onClick={trusted(() => play(strategies.Even))}>
Even
</Button>
</td>
<td colSpan={2}>
<Button disabled={!canPlay} onClick={trusted(() => play(strategies.High))}>
High
</Button>
</td>
<td colSpan={2}>
<Button disabled={!canPlay} onClick={trusted(() => play(strategies.Low))}>
Low
</Button>
</td>
</tr>
<tr>
<td>
<Button disabled={!canPlay} onClick={trusted(() => play(Single(0)))}>
0
</Button>
</td>
</tr>
</tbody>
</table>
</>
);
} }

View File

@@ -1,11 +1,13 @@
import * as React from "react"; import React, { useState, useEffect } from "react";
import { IPlayer } from "../PersonObjects/IPlayer"; import { IPlayer } from "../PersonObjects/IPlayer";
import { StdButton } from "../ui/React/StdButton";
import { Money } from "../ui/React/Money"; import { Money } from "../ui/React/Money";
import { WHRNG } from "./RNG"; import { WHRNG } from "./RNG";
import { Game } from "./Game"; import { win, reachedLimit } from "./Game";
import { trusted } from "./utils"; import { trusted } from "./utils";
import Typography from "@mui/material/Typography";
import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";
type IProps = { type IProps = {
p: IPlayer; p: IPlayer;
@@ -147,104 +149,76 @@ const payLines = [
const minPlay = 0; const minPlay = 0;
const maxPlay = 1e6; const maxPlay = 1e6;
export class SlotMachine extends Game<IProps, IState> { export function SlotMachine(props: IProps): React.ReactElement {
rng: WHRNG; const [rng] = useState(new WHRNG(props.p.totalPlaytime));
interval = -1; const [index, setIndex] = useState<number[]>([0, 0, 0, 0, 0]);
const [locks, setLocks] = useState<number[]>([0, 0, 0, 0, 0]);
const [investment, setInvestment] = useState(1000);
const [canPlay, setCanPlay] = useState(true);
const [status, setStatus] = useState<string | JSX.Element>("waiting");
constructor(props: IProps) { useEffect(() => {
super(props); const i = window.setInterval(step, 50);
this.rng = new WHRNG(this.props.p.totalPlaytime); return () => clearInterval(i);
});
this.state = { function step(): void {
index: [0, 0, 0, 0, 0],
investment: 1000,
locks: [0, 0, 0, 0, 0],
canPlay: true,
status: "waiting",
};
this.play = this.play.bind(this);
this.lock = this.lock.bind(this);
this.unlock = this.unlock.bind(this);
this.step = this.step.bind(this);
this.checkWinnings = this.checkWinnings.bind(this);
this.getTable = this.getTable.bind(this);
this.updateInvestment = this.updateInvestment.bind(this);
}
componentDidMount(): void {
this.interval = window.setInterval(this.step, 50);
}
step(): void {
let stoppedOne = false; let stoppedOne = false;
const index = this.state.index.slice(); const copy = index.slice();
for (const i in index) { for (const i in copy) {
if (index[i] === this.state.locks[i] && !stoppedOne) continue; if (copy[i] === locks[i] && !stoppedOne) continue;
index[i] = (index[i] + 1) % symbols.length; copy[i] = (copy[i] + 1) % symbols.length;
stoppedOne = true; stoppedOne = true;
} }
this.setState({ index: index }); setIndex(copy);
if (stoppedOne && index.every((e, i) => e === this.state.locks[i])) { if (stoppedOne && copy.every((e, i) => e === locks[i])) {
this.checkWinnings(); checkWinnings();
} }
} }
componentWillUnmount(): void { function getTable(): string[][] {
clearInterval(this.interval);
}
getTable(): string[][] {
return [ return [
[ [
symbols[(this.state.index[0] + symbols.length - 1) % symbols.length], symbols[(index[0] + symbols.length - 1) % symbols.length],
symbols[(this.state.index[1] + symbols.length - 1) % symbols.length], symbols[(index[1] + symbols.length - 1) % symbols.length],
symbols[(this.state.index[2] + symbols.length - 1) % symbols.length], symbols[(index[2] + symbols.length - 1) % symbols.length],
symbols[(this.state.index[3] + symbols.length - 1) % symbols.length], symbols[(index[3] + symbols.length - 1) % symbols.length],
symbols[(this.state.index[4] + symbols.length - 1) % symbols.length], symbols[(index[4] + symbols.length - 1) % symbols.length],
], ],
[symbols[index[0]], symbols[index[1]], symbols[index[2]], symbols[index[3]], symbols[index[4]]],
[ [
symbols[this.state.index[0]], symbols[(index[0] + 1) % symbols.length],
symbols[this.state.index[1]], symbols[(index[1] + 1) % symbols.length],
symbols[this.state.index[2]], symbols[(index[2] + 1) % symbols.length],
symbols[this.state.index[3]], symbols[(index[3] + 1) % symbols.length],
symbols[this.state.index[4]], symbols[(index[4] + 1) % symbols.length],
],
[
symbols[(this.state.index[0] + 1) % symbols.length],
symbols[(this.state.index[1] + 1) % symbols.length],
symbols[(this.state.index[2] + 1) % symbols.length],
symbols[(this.state.index[3] + 1) % symbols.length],
symbols[(this.state.index[4] + 1) % symbols.length],
], ],
]; ];
} }
play(): void { function play(): void {
if (this.reachedLimit(this.props.p)) return; if (reachedLimit(props.p)) return;
this.setState({ status: "playing" }); setStatus("playing");
this.win(this.props.p, -this.state.investment); win(props.p, -investment);
if (!this.state.canPlay) return; if (!canPlay) return;
this.unlock(); unlock();
setTimeout(this.lock, this.rng.random() * 2000 + 1000); setTimeout(lock, rng.random() * 2000 + 1000);
} }
lock(): void { function lock(): void {
this.setState({ setLocks([
locks: [ Math.floor(rng.random() * symbols.length),
Math.floor(this.rng.random() * symbols.length), Math.floor(rng.random() * symbols.length),
Math.floor(this.rng.random() * symbols.length), Math.floor(rng.random() * symbols.length),
Math.floor(this.rng.random() * symbols.length), Math.floor(rng.random() * symbols.length),
Math.floor(this.rng.random() * symbols.length), Math.floor(rng.random() * symbols.length),
Math.floor(this.rng.random() * symbols.length), ]);
],
});
} }
checkWinnings(): void { function checkWinnings(): void {
const t = this.getTable(); const t = getTable();
const getPaylineData = function (payline: number[][]): string[] { const getPaylineData = function (payline: number[][]): string[] {
const data = []; const data = [];
for (const point of payline) { for (const point of payline) {
@@ -263,35 +237,31 @@ export class SlotMachine extends Game<IProps, IState> {
return count; return count;
}; };
let gains = -this.state.investment; let gains = -investment;
for (const payline of payLines) { for (const payline of payLines) {
const data = getPaylineData(payline); const data = getPaylineData(payline);
const count = countSequence(data); const count = countSequence(data);
if (count < 3) continue; if (count < 3) continue;
const payout = getPayout(data[0], count - 3); const payout = getPayout(data[0], count - 3);
gains += this.state.investment * payout; gains += investment * payout;
this.win(this.props.p, this.state.investment * payout); win(props.p, investment * payout);
} }
this.setState({ setStatus(
status: ( <>
<> {gains > 0 ? "gained" : "lost"} <Money money={Math.abs(gains)} />
{gains > 0 ? "gained" : "lost"} <Money money={Math.abs(gains)} /> </>,
</> );
), setCanPlay(true);
canPlay: true, if (reachedLimit(props.p)) return;
});
if (this.reachedLimit(this.props.p)) return;
} }
unlock(): void { function unlock(): void {
this.setState({ setLocks([-1, -1, -1, -1, -1]);
locks: [-1, -1, -1, -1, -1], setCanPlay(false);
canPlay: false,
});
} }
updateInvestment(e: React.FormEvent<HTMLInputElement>): void { function updateInvestment(e: React.ChangeEvent<HTMLInputElement>): void {
let investment: number = parseInt(e.currentTarget.value); let investment: number = parseInt(e.currentTarget.value);
if (isNaN(investment)) { if (isNaN(investment)) {
investment = minPlay; investment = minPlay;
@@ -302,53 +272,49 @@ export class SlotMachine extends Game<IProps, IState> {
if (investment < minPlay) { if (investment < minPlay) {
investment = minPlay; investment = minPlay;
} }
this.setState({ investment: investment }); setInvestment(investment);
} }
render(): React.ReactNode { const t = getTable();
const t = this.getTable(); // prettier-ignore
// prettier-ignore return (
return (
<> <>
<pre>++</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>++</Typography>
<pre>| | {t[0][0]} | {t[0][1]} | {t[0][2]} | {t[0][3]} | {t[0][4]} | |</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>| | {t[0][0]} | {t[0][1]} | {t[0][2]} | {t[0][3]} | {t[0][4]} | |</Typography>
<pre>| | | | | | | |</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>| | | | | | | |</Typography>
<pre>| | {symbols[this.state.index[0]]} | {symbols[this.state.index[1]]} | {symbols[this.state.index[2]]} | {symbols[this.state.index[3]]} | {symbols[this.state.index[4]]} | |</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>| | {symbols[index[0]]} | {symbols[index[1]]} | {symbols[index[2]]} | {symbols[index[3]]} | {symbols[index[4]]} | |</Typography>
<pre>| | | | | | | |</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>| | | | | | | |</Typography>
<pre>| | {symbols[(this.state.index[0]+1)%symbols.length]} | {symbols[(this.state.index[1]+1)%symbols.length]} | {symbols[(this.state.index[2]+1)%symbols.length]} | {symbols[(this.state.index[3]+1)%symbols.length]} | {symbols[(this.state.index[4]+1)%symbols.length]} | |</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>| | {symbols[(index[0]+1)%symbols.length]} | {symbols[(index[1]+1)%symbols.length]} | {symbols[(index[2]+1)%symbols.length]} | {symbols[(index[3]+1)%symbols.length]} | {symbols[(index[4]+1)%symbols.length]} | |</Typography>
<pre>++</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>++</Typography>
<input <TextField
type="number" type="number"
className="text-input" onChange={updateInvestment}
onChange={this.updateInvestment}
placeholder={"Amount to play"} placeholder={"Amount to play"}
value={this.state.investment} disabled={!canPlay}
disabled={!this.state.canPlay} InputProps={{endAdornment:(<Button
onClick={trusted(play)}
disabled={!canPlay}
>Spin!</Button>)}}
/> />
<StdButton
onClick={trusted(this.play)} <Typography variant="h4">{status}</Typography>
text={"Spin!"} <Typography>Pay lines</Typography>
disabled={!this.state.canPlay}
/>
<h1>{this.state.status}</h1>
<h2>Pay lines</h2>
<pre>----- ····· ·····</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>----- ····· ·····</Typography>
<pre>····· ----- ·····</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>····· ----- ·····</Typography>
<pre>····· ····· -----</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>····· ····· -----</Typography>
<br /> <br />
<pre>··^·· \···/ \···/</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>··^·· \···/ \···/</Typography>
<pre>·/·\· ·\·/· ·---·</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>·/·\· ·\·/· ·---·</Typography>
<pre>/···\ ··v·· ·····</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>/···\ ··v·· ·····</Typography>
<br /> <br />
<pre>····· ·---· ·····</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>····· ·---· ·····</Typography>
<pre>·---· /···\ \···/</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>·---· /···\ \···/</Typography>
<pre>/···\ ····· ·---·</pre> <Typography sx={{ lineHeight: "1em", whiteSpace: "pre" }}>/···\ ····· ·---·</Typography>
</> </>
); );
}
} }
// https://felgo.com/doc/how-to-make-a-slot-game-tutorial/ // https://felgo.com/doc/how-to-make-a-slot-game-tutorial/

View File

@@ -3,14 +3,13 @@
* *
* This subcomponent renders all of the buttons for training at the gym * This subcomponent renders all of the buttons for training at the gym
*/ */
import * as React from "react"; import React, { useState } from "react";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import { Blackjack } from "../../Casino/Blackjack"; import { Blackjack } from "../../Casino/Blackjack";
import { CoinFlip } from "../../Casino/CoinFlip"; import { CoinFlip } from "../../Casino/CoinFlip";
import { Roulette } from "../../Casino/Roulette"; import { Roulette } from "../../Casino/Roulette";
import { SlotMachine } from "../../Casino/SlotMachine"; import { SlotMachine } from "../../Casino/SlotMachine";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { StdButton } from "../../ui/React/StdButton";
enum GameType { enum GameType {
None = "none", None = "none",
@@ -28,71 +27,35 @@ type IState = {
game: GameType; game: GameType;
}; };
export class CasinoLocation extends React.Component<IProps, IState> { export function CasinoLocation(props: IProps): React.ReactElement {
constructor(props: IProps) { const [game, setGame] = useState(GameType.None);
super(props);
this.state = { function updateGame(game: GameType): void {
game: GameType.None, setGame(game);
};
this.updateGame = this.updateGame.bind(this);
} }
updateGame(game: GameType): void { return (
this.setState({ <>
game, {game === GameType.None && (
}); <>
} <Button onClick={() => updateGame(GameType.Coin)}>Play coin flip</Button>
<br />
renderGames(): React.ReactNode { <Button onClick={() => updateGame(GameType.Slots)}>Play slots</Button>
return ( <br />
<> <Button onClick={() => updateGame(GameType.Roulette)}>Play roulette</Button>
<Button onClick={() => this.updateGame(GameType.Coin)}>Play coin flip</Button> <br />
<br /> <Button onClick={() => updateGame(GameType.Blackjack)}>Play blackjack</Button>
<Button onClick={() => this.updateGame(GameType.Slots)}>Play slots</Button> </>
<br /> )}
<Button onClick={() => this.updateGame(GameType.Roulette)}>Play roulette</Button> {game !== GameType.None && (
<br /> <>
<Button onClick={() => this.updateGame(GameType.Blackjack)}>Play blackjack</Button> <Button onClick={() => updateGame(GameType.None)}>Stop playing</Button>
</> {game === GameType.Coin && <CoinFlip p={props.p} />}
); {game === GameType.Slots && <SlotMachine p={props.p} />}
} {game === GameType.Roulette && <Roulette p={props.p} />}
{game === GameType.Blackjack && <Blackjack p={props.p} />}
renderGame(): React.ReactNode { </>
let elem = null; )}
switch (this.state.game) { </>
case GameType.Coin: );
elem = <CoinFlip p={this.props.p} />;
break;
case GameType.Slots:
elem = <SlotMachine p={this.props.p} />;
break;
case GameType.Roulette:
elem = <Roulette p={this.props.p} />;
break;
case GameType.Blackjack:
elem = <Blackjack p={this.props.p} />;
break;
case GameType.None:
break;
default:
throw new Error(`MissingCaseException: ${this.state.game}`);
}
return (
<>
<StdButton onClick={() => this.updateGame(GameType.None)} text={"Stop playing"} />
{elem}
</>
);
}
render(): React.ReactNode {
if (this.state.game === GameType.None) {
return this.renderGames();
} else {
return this.renderGame();
}
}
} }

View File

@@ -12,9 +12,10 @@ import { Locations } from "../Locations";
import { Location } from "../Location"; import { Location } from "../Location";
import { Settings } from "../../Settings/Settings"; import { Settings } from "../../Settings/Settings";
import { StdButton } from "../../ui/React/StdButton";
import { use } from "../../ui/Context"; import { use } from "../../ui/Context";
import { IRouter } from "../../ui/Router"; import { IRouter } from "../../ui/Router";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
type IProps = { type IProps = {
city: City; city: City;
@@ -111,29 +112,30 @@ function ASCIICity(props: IProps): React.ReactElement {
elems.push(<pre key={i}>{lineElems(lines[i])}</pre>); elems.push(<pre key={i}>{lineElems(lines[i])}</pre>);
} }
return <div className="noselect">{elems}</div>; return <>{elems}</>;
} }
function ListCity(props: IProps): React.ReactElement { function ListCity(props: IProps): React.ReactElement {
const router = use.Router(); const router = use.Router();
const locationButtons = props.city.locations.map((locName) => { const locationButtons = props.city.locations.map((locName) => {
return ( return (
<li key={locName}> <React.Fragment key={locName}>
<StdButton onClick={() => toLocation(router, Locations[locName])} text={locName} /> <Button onClick={() => toLocation(router, Locations[locName])}>{locName}</Button>
</li> <br />
</React.Fragment>
); );
}); });
return <ul>{locationButtons}</ul>; return <>{locationButtons}</>;
} }
export function LocationCity(): React.ReactElement { export function LocationCity(): React.ReactElement {
const player = use.Player(); const player = use.Player();
const city = Cities[player.city]; const city = Cities[player.city];
return ( return (
<div className="noselect"> <>
<h2>{city.name}</h2> <Typography>{city.name}</Typography>
{Settings.DisableASCIIArt ? <ListCity city={city} /> : <ASCIICity city={city} />} {Settings.DisableASCIIArt ? <ListCity city={city} /> : <ASCIICity city={city} />}
</div> </>
); );
} }

View File

@@ -212,7 +212,7 @@ export function CompanyLocation(props: IProps): React.ReactElement {
</> </>
} }
> >
<Typography className={"tooltip"}>Company Favor: {Favor(company.favor)}</Typography> <Typography>Company Favor: {Favor(company.favor)}</Typography>
</Tooltip> </Tooltip>
</Box> </Box>
<Typography>-------------------------</Typography> <Typography>-------------------------</Typography>

View File

@@ -90,7 +90,7 @@ export function GenericLocation({ loc }: IProps): React.ReactElement {
return ( return (
<> <>
<Button onClick={() => router.toCity()}>Return to World</Button> <Button onClick={() => router.toCity()}>Return to World</Button>
<Typography variant="h4" className="noselect"> <Typography variant="h4">
{backdoorInstalled && !Settings.DisableTextEffects ? <CorruptableText content={loc.name} /> : loc.name} {backdoorInstalled && !Settings.DisableTextEffects ? <CorruptableText content={loc.name} /> : loc.name}
</Typography> </Typography>
{locContent} {locContent}

View File

@@ -70,7 +70,7 @@ export function TechVendorLocation(props: IProps): React.ReactElement {
<div> <div>
{purchaseServerButtons} {purchaseServerButtons}
<br /> <br />
<Typography className="noselect"> <Typography>
<i>"You can order bigger servers via scripts. We don't take custom order in person."</i> <i>"You can order bigger servers via scripts. We don't take custom order in person."</i>
</Typography> </Typography>
<br /> <br />

View File

@@ -13,7 +13,6 @@ import { IPlayer } from "../../PersonObjects/IPlayer";
import { IRouter } from "../../ui/Router"; import { IRouter } from "../../ui/Router";
import { Settings } from "../../Settings/Settings"; import { Settings } from "../../Settings/Settings";
import { StdButton } from "../../ui/React/StdButton";
import { use } from "../../ui/Context"; import { use } from "../../ui/Context";
import { Money } from "../../ui/React/Money"; import { Money } from "../../ui/React/Money";
import { WorldMap } from "../../ui/React/WorldMap"; import { WorldMap } from "../../ui/React/WorldMap";
@@ -36,7 +35,7 @@ function travel(p: IPlayer, router: IRouter, to: CityName): void {
p.loseMoney(cost); p.loseMoney(cost);
p.travel(to); p.travel(to);
dialogBoxCreate(<span className="noselect">You are now in {to}!</span>); dialogBoxCreate(<>You are now in {to}!</>);
router.toCity(); router.toCity();
} }

View File

@@ -146,7 +146,7 @@ import { setTimeoutRef } from "./utils/SetTimeoutRef";
import { is2DArray } from "./utils/helpers/is2DArray"; import { is2DArray } from "./utils/helpers/is2DArray";
import { convertTimeMsToTimeElapsedString } from "./utils/StringHelperFunctions"; import { convertTimeMsToTimeElapsedString } from "./utils/StringHelperFunctions";
import { logBoxCreate } from "./ui/React/LogBox"; 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";
@@ -1203,7 +1203,7 @@ function NetscriptFunctions(workerScript: WorkerScript): NS {
return; return;
} }
logBoxCreate(runningScriptObj); LogBoxEvents.emit(runningScriptObj);
}, },
nuke: function (ip: any): any { nuke: function (ip: any): any {
updateDynamicRam("nuke", getRamCost("nuke")); updateDynamicRam("nuke", getRamCost("nuke"));

View File

@@ -8,6 +8,13 @@ import { Money } from "../../../ui/React/Money";
import { numeralWrapper } from "../../../ui/numeralFormat"; import { numeralWrapper } from "../../../ui/numeralFormat";
import { dialogBoxCreate } from "../../../ui/React/DialogBox"; import { dialogBoxCreate } from "../../../ui/React/DialogBox";
import Typography from "@mui/material/Typography";
import Paper from "@mui/material/Paper";
import Button from "@mui/material/Button";
import Select, { SelectChangeEvent } from "@mui/material/Select";
import MenuItem from "@mui/material/MenuItem";
import Grid from "@mui/material/Grid";
interface IProps { interface IProps {
resleeve: Resleeve; resleeve: Resleeve;
player: IPlayer; player: IPlayer;
@@ -19,81 +26,83 @@ export function ResleeveElem(props: IProps): React.ReactElement {
function openStats(): void { function openStats(): void {
dialogBoxCreate( dialogBoxCreate(
<> <>
<h2> <Typography variant="h5" color="primary">
<u>Total Multipliers:</u> Total Multipliers:
</h2> </Typography>
Hacking Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_mult)} <Typography>
<br /> Hacking Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_mult)}
Hacking Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_exp_mult)} <br />
<br /> Hacking Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_exp_mult)}
Strength Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.strength_mult)} <br />
<br /> Strength Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.strength_mult)}
Strength Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.strength_exp_mult)} <br />
<br /> Strength Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.strength_exp_mult)}
Defense Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.defense_mult)} <br />
<br /> Defense Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.defense_mult)}
Defense Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.defense_exp_mult)} <br />
<br /> Defense Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.defense_exp_mult)}
Dexterity Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.dexterity_mult)} <br />
<br /> Dexterity Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.dexterity_mult)}
Dexterity Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.dexterity_exp_mult)} <br />
<br /> Dexterity Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.dexterity_exp_mult)}
Agility Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.agility_mult)} <br />
<br /> Agility Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.agility_mult)}
Agility Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.agility_exp_mult)} <br />
<br /> Agility Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.agility_exp_mult)}
Charisma Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.charisma_mult)} <br />
<br /> Charisma Level multiplier: {numeralWrapper.formatPercentage(props.resleeve.charisma_mult)}
Charisma Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.charisma_exp_mult)} <br />
<br /> Charisma Experience multiplier: {numeralWrapper.formatPercentage(props.resleeve.charisma_exp_mult)}
Hacking Chance multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_chance_mult)} <br />
<br /> Hacking Chance multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_chance_mult)}
Hacking Speed multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_speed_mult)} <br />
<br /> Hacking Speed multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_speed_mult)}
Hacking Money multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_money_mult)} <br />
<br /> Hacking Money multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_money_mult)}
Hacking Growth multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_grow_mult)} <br />
<br /> Hacking Growth multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacking_grow_mult)}
Salary multiplier: {numeralWrapper.formatPercentage(props.resleeve.work_money_mult)} <br />
<br /> Salary multiplier: {numeralWrapper.formatPercentage(props.resleeve.work_money_mult)}
Company Reputation Gain multiplier: {numeralWrapper.formatPercentage(props.resleeve.company_rep_mult)} <br />
<br /> Company Reputation Gain multiplier: {numeralWrapper.formatPercentage(props.resleeve.company_rep_mult)}
Faction Reputation Gain multiplier: {numeralWrapper.formatPercentage(props.resleeve.faction_rep_mult)} <br />
<br /> Faction Reputation Gain multiplier: {numeralWrapper.formatPercentage(props.resleeve.faction_rep_mult)}
Crime Money multiplier: {numeralWrapper.formatPercentage(props.resleeve.crime_money_mult)} <br />
<br /> Crime Money multiplier: {numeralWrapper.formatPercentage(props.resleeve.crime_money_mult)}
Crime Success multiplier: {numeralWrapper.formatPercentage(props.resleeve.crime_success_mult)} <br />
<br /> Crime Success multiplier: {numeralWrapper.formatPercentage(props.resleeve.crime_success_mult)}
Hacknet Income multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacknet_node_money_mult)} <br />
<br /> Hacknet Income multiplier: {numeralWrapper.formatPercentage(props.resleeve.hacknet_node_money_mult)}
Hacknet Purchase Cost multiplier: <br />
{numeralWrapper.formatPercentage(props.resleeve.hacknet_node_purchase_cost_mult)} Hacknet Purchase Cost multiplier:
<br /> {numeralWrapper.formatPercentage(props.resleeve.hacknet_node_purchase_cost_mult)}
Hacknet Level Upgrade Cost multiplier: <br />
{numeralWrapper.formatPercentage(props.resleeve.hacknet_node_level_cost_mult)} Hacknet Level Upgrade Cost multiplier:
<br /> {numeralWrapper.formatPercentage(props.resleeve.hacknet_node_level_cost_mult)}
Hacknet Ram Upgrade Cost multiplier: <br />
{numeralWrapper.formatPercentage(props.resleeve.hacknet_node_ram_cost_mult)} Hacknet Ram Upgrade Cost multiplier:
<br /> {numeralWrapper.formatPercentage(props.resleeve.hacknet_node_ram_cost_mult)}
Hacknet Core Upgrade Cost multiplier: <br />
{numeralWrapper.formatPercentage(props.resleeve.hacknet_node_core_cost_mult)} Hacknet Core Upgrade Cost multiplier:
<br /> {numeralWrapper.formatPercentage(props.resleeve.hacknet_node_core_cost_mult)}
Bladeburner Max Stamina multiplier: <br />
{numeralWrapper.formatPercentage(props.resleeve.bladeburner_max_stamina_mult)} Bladeburner Max Stamina multiplier:
<br /> {numeralWrapper.formatPercentage(props.resleeve.bladeburner_max_stamina_mult)}
Bladeburner Stamina Gain multiplier: <br />
{numeralWrapper.formatPercentage(props.resleeve.bladeburner_stamina_gain_mult)} Bladeburner Stamina Gain multiplier:
<br /> {numeralWrapper.formatPercentage(props.resleeve.bladeburner_stamina_gain_mult)}
Bladeburner Field Analysis multiplier: <br />
{numeralWrapper.formatPercentage(props.resleeve.bladeburner_analysis_mult)} Bladeburner Field Analysis multiplier:
<br /> {numeralWrapper.formatPercentage(props.resleeve.bladeburner_analysis_mult)}
Bladeburner Success Chance multiplier: <br />
{numeralWrapper.formatPercentage(props.resleeve.bladeburner_success_chance_mult)} Bladeburner Success Chance multiplier:
{numeralWrapper.formatPercentage(props.resleeve.bladeburner_success_chance_mult)}
</Typography>
</>, </>,
); );
} }
function onAugChange(event: React.ChangeEvent<HTMLSelectElement>): void { function onAugChange(event: SelectChangeEvent<string>): void {
setAug(event.target.value); setAug(event.target.value);
} }
@@ -110,50 +119,48 @@ export function ResleeveElem(props: IProps): React.ReactElement {
} }
return ( return (
<div className="resleeve-elem" style={{ display: "block" }}> <Paper sx={{ my: 1 }}>
<div className="resleeve-panel" style={{ width: "30%" }}> <Grid container>
<p> <Grid item xs={3}>
Hacking: {numeralWrapper.formatSkill(props.resleeve.hacking_skill)} ( <Typography>
{numeralWrapper.formatExp(props.resleeve.hacking_exp)} exp) Hacking: {numeralWrapper.formatSkill(props.resleeve.hacking_skill)} (
<br /> {numeralWrapper.formatExp(props.resleeve.hacking_exp)} exp)
Strength: {numeralWrapper.formatSkill(props.resleeve.strength)} ( <br />
{numeralWrapper.formatExp(props.resleeve.strength_exp)} exp) Strength: {numeralWrapper.formatSkill(props.resleeve.strength)} (
<br /> {numeralWrapper.formatExp(props.resleeve.strength_exp)} exp)
Defense: {numeralWrapper.formatSkill(props.resleeve.defense)} ( <br />
{numeralWrapper.formatExp(props.resleeve.defense_exp)} exp) Defense: {numeralWrapper.formatSkill(props.resleeve.defense)} (
<br /> {numeralWrapper.formatExp(props.resleeve.defense_exp)} exp)
Dexterity: {numeralWrapper.formatSkill(props.resleeve.dexterity)} ( <br />
{numeralWrapper.formatExp(props.resleeve.dexterity_exp)} exp) Dexterity: {numeralWrapper.formatSkill(props.resleeve.dexterity)} (
<br /> {numeralWrapper.formatExp(props.resleeve.dexterity_exp)} exp)
Agility: {numeralWrapper.formatSkill(props.resleeve.agility)} ( <br />
{numeralWrapper.formatExp(props.resleeve.agility_exp)} exp) Agility: {numeralWrapper.formatSkill(props.resleeve.agility)} (
<br /> {numeralWrapper.formatExp(props.resleeve.agility_exp)} exp)
Charisma: {numeralWrapper.formatSkill(props.resleeve.charisma)} ( <br />
{numeralWrapper.formatExp(props.resleeve.charisma_exp)} exp) Charisma: {numeralWrapper.formatSkill(props.resleeve.charisma)} (
<br /># Augmentations: {props.resleeve.augmentations.length} {numeralWrapper.formatExp(props.resleeve.charisma_exp)} exp)
</p> <br /># Augmentations: {props.resleeve.augmentations.length}
<button className="std-button" onClick={openStats}> </Typography>
Multipliers <Button onClick={openStats}>Multipliers</Button>
</button> </Grid>
</div> <Grid item xs={6}>
<div className="resleeve-panel" style={{ width: "50%" }}> <Select value={aug} onChange={onAugChange}>
<select className="resleeve-aug-selector dropdown" onChange={onAugChange}> {props.resleeve.augmentations.map((aug) => (
{props.resleeve.augmentations.map((aug) => ( <MenuItem key={aug.name} value={aug.name}>
<option key={aug.name} value={aug.name}> {aug.name}
{aug.name} </MenuItem>
</option> ))}
))} </Select>
</select> <Typography>{currentAug !== undefined && currentAug.info}</Typography>
<p>{currentAug !== undefined && currentAug.info}</p> </Grid>
</div> <Grid item xs={3}>
<div className="resleeve-panel" style={{ width: "20%" }}> <Typography>
<p> It costs <Money money={cost} player={props.player} /> to purchase this Sleeve.
It costs <Money money={cost} player={props.player} /> to purchase this Sleeve. </Typography>
</p> <Button onClick={purchase}>Purchase</Button>
<button className="std-button" onClick={purchase}> </Grid>
Purchase </Grid>
</button> </Paper>
</div>
</div>
); );
} }

View File

@@ -1,9 +1,13 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { IPlayer } from "../../IPlayer";
import { generateResleeves } from "../Resleeving"; import { generateResleeves } from "../Resleeving";
import { Resleeve } from "../Resleeve"; import { Resleeve } from "../Resleeve";
import { ResleeveElem } from "./ResleeveElem"; import { ResleeveElem } from "./ResleeveElem";
import { use } from "../../../ui/Context";
import Typography from "@mui/material/Typography";
import Select, { SelectChangeEvent } from "@mui/material/Select";
import MenuItem from "@mui/material/MenuItem";
import Box from "@mui/material/Box";
const SortOption: { const SortOption: {
[key: string]: string | undefined; [key: string]: string | undefined;
@@ -69,28 +73,25 @@ const SortFunctions: {
TotalNumAugmentations: (a: Resleeve, b: Resleeve): number => a.augmentations.length - b.augmentations.length, TotalNumAugmentations: (a: Resleeve, b: Resleeve): number => a.augmentations.length - b.augmentations.length,
}; };
interface IProps { export function ResleeveRoot(): React.ReactElement {
player: IPlayer; const player = use.Player();
}
export function ResleeveRoot(props: IProps): React.ReactElement {
const [sort, setSort] = useState(SortOption.Cost); const [sort, setSort] = useState(SortOption.Cost);
// Randomly create all Resleeves if they dont already exist // Randomly create all Resleeves if they dont already exist
if (props.player.resleeves.length === 0) { if (player.resleeves.length === 0) {
props.player.resleeves = generateResleeves(); player.resleeves = generateResleeves();
} }
function onSortChange(event: React.ChangeEvent<HTMLSelectElement>): void { function onSortChange(event: SelectChangeEvent<string>): void {
setSort(event.target.value); setSort(event.target.value);
} }
const sortFunction = SortFunctions[sort]; const sortFunction = SortFunctions[sort];
if (sortFunction === undefined) throw new Error(`sort function '${sort}' is undefined`); if (sortFunction === undefined) throw new Error(`sort function '${sort}' is undefined`);
props.player.resleeves.sort(sortFunction); player.resleeves.sort(sortFunction);
return ( return (
<> <>
<p style={{ display: "block", width: "75%" }}> <Typography>
Re-sleeving is the process of digitizing and transferring your consciousness into a new human body, or 'sleeve'. Re-sleeving is the process of digitizing and transferring your consciousness into a new human body, or 'sleeve'.
Here at VitaLife, you can purchase new specially-engineered bodies for the re-sleeve process. Many of these Here at VitaLife, you can purchase new specially-engineered bodies for the re-sleeve process. Many of these
bodies even come with genetic and cybernetic Augmentations! bodies even come with genetic and cybernetic Augmentations!
@@ -104,17 +105,19 @@ export function ResleeveRoot(props: IProps): React.ReactElement {
<br /> <br />
<br /> <br />
NOTE: The stats and multipliers displayed on this page do NOT include your bonuses from Source-File. NOTE: The stats and multipliers displayed on this page do NOT include your bonuses from Source-File.
</p> </Typography>
<p style={{ display: "inline-block" }}>Sort By: </p> <Box display="flex" alignItems="center">
<select className="dropdown" defaultValue={sort} onChange={onSortChange}> <Typography>Sort By: </Typography>
{Object.keys(SortOption).map((opt) => ( <Select value={sort} onChange={onSortChange}>
<option key={opt} value={opt}> {Object.keys(SortOption).map((opt) => (
{SortOption[opt]} <MenuItem key={opt} value={opt}>
</option> {SortOption[opt]}
))} </MenuItem>
</select> ))}
{props.player.resleeves.map((resleeve, i) => ( </Select>
<ResleeveElem key={i} player={props.player} resleeve={resleeve} /> </Box>
{player.resleeves.map((resleeve, i) => (
<ResleeveElem key={i} player={player} resleeve={resleeve} />
))} ))}
</> </>
); );

View File

@@ -2,7 +2,7 @@ import { ITerminal } from "../ITerminal";
import { IRouter } from "../../ui/Router"; import { IRouter } from "../../ui/Router";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer"; import { BaseServer } from "../../Server/BaseServer";
import { logBoxCreate } from "../../ui/React/LogBox"; import { LogBoxEvents } from "../../ui/React/LogBoxManager";
import { startWorkerScript } from "../../NetscriptWorker"; import { startWorkerScript } from "../../NetscriptWorker";
import { RunningScript } from "../../Script/RunningScript"; import { RunningScript } from "../../Script/RunningScript";
import { findRunningScript } from "../../Script/ScriptHelpers"; import { findRunningScript } from "../../Script/ScriptHelpers";
@@ -84,7 +84,7 @@ export function runScript(
`Running script with ${numThreads} thread(s), pid ${runningScript.pid} and args: ${JSON.stringify(args)}.`, `Running script with ${numThreads} thread(s), pid ${runningScript.pid} and args: ${JSON.stringify(args)}.`,
); );
if (tailFlag) { if (tailFlag) {
logBoxCreate(runningScript); LogBoxEvents.emit(runningScript);
} }
return; return;
} }

View File

@@ -2,10 +2,10 @@ import { ITerminal } from "../ITerminal";
import { IRouter } from "../../ui/Router"; import { IRouter } from "../../ui/Router";
import { IPlayer } from "../../PersonObjects/IPlayer"; import { IPlayer } from "../../PersonObjects/IPlayer";
import { BaseServer } from "../../Server/BaseServer"; import { BaseServer } from "../../Server/BaseServer";
import { logBoxCreate } from "../../ui/React/LogBox";
import { findRunningScriptByPid } from "../../Script/ScriptHelpers"; import { findRunningScriptByPid } from "../../Script/ScriptHelpers";
import { isScriptFilename } from "../../Script/isScriptFilename"; import { isScriptFilename } from "../../Script/isScriptFilename";
import { compareArrays } from "../../utils/helpers/compareArrays"; import { compareArrays } from "../../utils/helpers/compareArrays";
import { LogBoxEvents } from "../../ui/React/LogBoxManager";
export function tail( export function tail(
terminal: ITerminal, terminal: ITerminal,
@@ -34,7 +34,7 @@ export function tail(
// match, use it! // match, use it!
for (let i = 0; i < server.runningScripts.length; ++i) { for (let i = 0; i < server.runningScripts.length; ++i) {
if (server.runningScripts[i].filename === scriptName && compareArrays(server.runningScripts[i].args, args)) { if (server.runningScripts[i].filename === scriptName && compareArrays(server.runningScripts[i].args, args)) {
logBoxCreate(server.runningScripts[i]); LogBoxEvents.emit(server.runningScripts[i]);
return; return;
} }
} }
@@ -53,7 +53,7 @@ export function tail(
// If there's only 1 possible choice, use that. // If there's only 1 possible choice, use that.
if (candidates.length === 1) { if (candidates.length === 1) {
logBoxCreate(candidates[0]); LogBoxEvents.emit(candidates[0]);
return; return;
} }
@@ -73,7 +73,7 @@ export function tail(
terminal.error("No such script exists"); terminal.error("No such script exists");
return; return;
} }
logBoxCreate(runningScript); LogBoxEvents.emit(runningScript);
} }
} catch (e) { } catch (e) {
terminal.error(e + ""); terminal.error(e + "");

View File

@@ -42,7 +42,7 @@ import { Money } from "./ui/React/Money";
import { Hashes } from "./ui/React/Hashes"; import { Hashes } from "./ui/React/Hashes";
import { Reputation } from "./ui/React/Reputation"; import { Reputation } from "./ui/React/Reputation";
import { dialogBoxCreate } from "./ui/React/DialogBox"; import { AlertEvents } from "./ui/React/AlertManager";
import { exceptionAlert } from "./utils/helpers/exceptionAlert"; import { exceptionAlert } from "./utils/helpers/exceptionAlert";
import { startTampering } from "./Exploits/tampering"; import { startTampering } from "./Exploits/tampering";
@@ -399,12 +399,16 @@ const Engine: {
Player.lastUpdate = Engine._lastUpdate; Player.lastUpdate = Engine._lastUpdate;
Engine.start(); // Run main game loop and Scripts loop Engine.start(); // Run main game loop and Scripts loop
const timeOfflineString = convertTimeMsToTimeElapsedString(time); const timeOfflineString = convertTimeMsToTimeElapsedString(time);
dialogBoxCreate( setTimeout(
<> () =>
Offline for {timeOfflineString}. While you were offline, your scripts generated{" "} AlertEvents.emit(
<Money money={offlineHackingIncome} />, your Hacknet Nodes generated {hacknetProdInfo} and you gained{" "} <>
{Reputation(offlineReputation)} divided amongst your factions. Offline for {timeOfflineString}. While you were offline, your scripts generated{" "}
</>, <Money money={offlineHackingIncome} />, your Hacknet Nodes generated {hacknetProdInfo} and you gained{" "}
{Reputation(offlineReputation)} divided amongst your factions.
</>,
),
250,
); );
} else { } else {
// No save found, start new game // No save found, start new game

View File

@@ -28,7 +28,7 @@ import { killWorkerScript } from "../../Netscript/killWorkerScript";
import { WorkerScript } from "../../Netscript/WorkerScript"; import { WorkerScript } from "../../Netscript/WorkerScript";
import { dialogBoxCreate } from "../React/DialogBox"; import { dialogBoxCreate } from "../React/DialogBox";
import { logBoxCreate } from "../React/LogBox"; import { LogBoxEvents } from "../React/LogBoxManager";
import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions"; import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
import { arrayToString } from "../../utils/helpers/arrayToString"; import { arrayToString } from "../../utils/helpers/arrayToString";
import { Money } from "../React/Money"; import { Money } from "../React/Money";
@@ -50,7 +50,9 @@ export function WorkerScriptAccordion(props: IProps): React.ReactElement {
const workerScript = props.workerScript; const workerScript = props.workerScript;
const scriptRef = workerScript.scriptRef; const scriptRef = workerScript.scriptRef;
const logClickHandler = logBoxCreate.bind(null, scriptRef); function logClickHandler(): void {
LogBoxEvents.emit(scriptRef);
}
const killScript = killWorkerScript.bind(null, scriptRef as any, scriptRef.server); const killScript = killWorkerScript.bind(null, scriptRef as any, scriptRef.server);
function killScriptClickHandler(): void { function killScriptClickHandler(): void {

View File

@@ -67,6 +67,8 @@ import { BladeburnerCinematic } from "../Bladeburner/ui/BladeburnerCinematic";
import { workerScripts } from "../Netscript/WorkerScripts"; import { workerScripts } from "../Netscript/WorkerScripts";
import { Unclickable } from "../Exploits/Unclickable"; import { Unclickable } from "../Exploits/Unclickable";
import { Snackbar } from "./React/Snackbar"; import { Snackbar } from "./React/Snackbar";
import { LogBoxManager } from "./React/LogBoxManager";
import { AlertManager } from "./React/AlertManager";
import { enterBitNode } from "../RedPill"; import { enterBitNode } from "../RedPill";
import { Context } from "./Context"; import { Context } from "./Context";
@@ -334,7 +336,7 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
) : page === Page.Bladeburner ? ( ) : page === Page.Bladeburner ? (
<BladeburnerRoot /> <BladeburnerRoot />
) : page === Page.Resleeves ? ( ) : page === Page.Resleeves ? (
<ResleeveRoot player={player} /> <ResleeveRoot />
) : page === Page.Travel ? ( ) : page === Page.Travel ? (
<TravelAgencyRoot p={player} router={Router} /> <TravelAgencyRoot p={player} router={Router} />
) : page === Page.StockMarket ? ( ) : page === Page.StockMarket ? (
@@ -394,6 +396,8 @@ export function GameRoot({ player, engine, terminal }: IProps): React.ReactEleme
)} )}
<Unclickable /> <Unclickable />
<Snackbar /> <Snackbar />
<LogBoxManager />
<AlertManager />
</Context.Router.Provider> </Context.Router.Provider>
</Context.Player.Provider> </Context.Player.Provider>
); );

View File

@@ -0,0 +1,50 @@
import React, { useState, useEffect } from "react";
import { EventEmitter } from "../../utils/EventEmitter";
import { Modal } from "../../ui/React/Modal";
import Typography from "@mui/material/Typography";
export const AlertEvents = new EventEmitter<[string | JSX.Element]>();
interface Alert {
id: string;
text: string | JSX.Element;
}
let i = 0;
export function AlertManager(): React.ReactElement {
const [alerts, setAlerts] = useState<Alert[]>([]);
useEffect(
() =>
AlertEvents.subscribe((text: string | JSX.Element) => {
const id = i + "";
i++;
setAlerts((old) => {
return [
...old,
{
id: id,
text: text,
},
];
});
}),
[],
);
function close(): void {
console.log("close");
setAlerts((old) => {
return old.slice(0, -1);
});
}
return (
<>
{alerts.length > 0 && (
<Modal open={true} onClose={close}>
<Typography>{alerts[0].text}</Typography>
</Modal>
)}
</>
);
}

View File

@@ -1,134 +0,0 @@
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import { killWorkerScript } from "../../Netscript/killWorkerScript";
import { RunningScript } from "../../Script/RunningScript";
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);
})();
interface IProps {
script: RunningScript;
container: HTMLElement;
id: string;
}
function ScriptLogPopup(props: IProps): React.ReactElement {
const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
}
useEffect(() => {
const id = setInterval(rerender, 1000);
return () => clearInterval(id);
}, []);
function close(): void {
const content = document.getElementById(props.id);
if (content == null) return;
ReactDOM.unmountComponentAtNode(content);
removeElementById(props.id);
}
useEffect(() => {
function closeHandler(event: KeyboardEvent): void {
if (event.keyCode === 27) {
close();
}
}
document.addEventListener("keydown", closeHandler);
return () => {
document.removeEventListener("keydown", closeHandler);
};
}, []);
function kill(): void {
killWorkerScript(props.script, props.script.server, true);
close();
}
function drag(event: React.MouseEvent<HTMLElement, MouseEvent>): void {
event.preventDefault();
let x = event.clientX;
let y = event.clientY;
let left = props.container.offsetLeft + props.container.clientWidth / 2;
let top = props.container.offsetTop + props.container.clientWidth / 5;
function mouseMove(event: MouseEvent): void {
left += event.clientX - x;
top += event.clientY - y;
props.container.style.left = left + "px";
props.container.style.top = top + "px";
// reset right and bottom to avoid the window stretching
props.container.style.right = "";
props.container.style.bottom = "";
x = event.clientX;
y = event.clientY;
}
function mouseUp(): void {
document.removeEventListener("mouseup", mouseUp);
document.removeEventListener("mousemove", mouseMove);
}
document.addEventListener("mouseup", mouseUp);
document.addEventListener("mousemove", mouseMove);
}
return (
<>
<div className="log-box-header" onMouseDown={drag}>
<p>
{props.script.filename} {props.script.args.map((x: any): string => `${x}`).join(" ")}
</p>
<div>
<button className="log-box-button" onClick={kill}>
Kill Script
</button>
<button className="log-box-button" onClick={close}>
Close
</button>
</div>
</div>
<div className="log-box-log-container">
<p>
{props.script.logs.map(
(line: string, i: number): JSX.Element => (
<span key={i} style={{ whiteSpace: "pre-line" }}>
{line}
<br />
</span>
),
)}
</p>
</div>
</>
);
}
export function logBoxCreate(script: RunningScript): void {
const id = script.server + "-" + script.filename + script.args.map((x: any): string => `${x}`).join("-");
if (document.getElementById(id) !== null) return;
const container = createElement("div", {
class: "log-box-container",
id: id,
});
gameContainer.appendChild(container);
ReactDOM.render(<ScriptLogPopup script={script} id={id} container={container} />, container);
}

View File

@@ -0,0 +1,161 @@
import React, { useState, useEffect, useRef } from "react";
import { EventEmitter } from "../../utils/EventEmitter";
import { RunningScript } from "../../Script/RunningScript";
import { killWorkerScript } from "../../Netscript/killWorkerScript";
import Typography from "@mui/material/Typography";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import Paper from "@mui/material/Paper";
export const LogBoxEvents = new EventEmitter<[RunningScript]>();
interface Log {
id: string;
script: RunningScript;
}
export function LogBoxManager(): React.ReactElement {
const [logs, setLogs] = useState<Log[]>([]);
useEffect(
() =>
LogBoxEvents.subscribe((script: RunningScript) => {
const id = script.server + "-" + script.filename + script.args.map((x: any): string => `${x}`).join("-");
setLogs((old) => {
return [
...old,
{
id: id,
script: script,
},
];
});
}),
[],
);
function close(id: string): void {
setLogs((old) => old.filter((l) => l.id !== id));
}
return (
<>
{logs.map((log) => (
<LogWindow key={log.id} script={log.script} id={log.id} onClose={() => close(log.id)} />
))}
</>
);
}
interface IProps {
script: RunningScript;
id: string;
onClose: () => void;
}
function LogWindow(props: IProps): React.ReactElement {
const container = useRef<HTMLDivElement>(null);
const setRerender = useState(false)[1];
function rerender(): void {
setRerender((old) => !old);
}
useEffect(() => {
const id = setInterval(rerender, 1000);
return () => clearInterval(id);
}, []);
useEffect(() => {
function closeHandler(event: KeyboardEvent): void {
if (event.keyCode === 27) {
props.onClose();
}
}
document.addEventListener("keydown", closeHandler);
return () => {
document.removeEventListener("keydown", closeHandler);
};
}, []);
function kill(): void {
killWorkerScript(props.script, props.script.server, true);
props.onClose();
}
function drag(event: React.MouseEvent<HTMLElement, MouseEvent>): void {
const c = container.current;
if (c === null) return;
event.preventDefault();
let x = event.clientX;
let y = event.clientY;
let left = c.offsetLeft + c.clientWidth / 2;
let top = c.offsetTop + c.clientWidth / 5;
function mouseMove(event: MouseEvent): void {
const c = container.current;
if (c === null) return;
left += event.clientX - x;
top += event.clientY - y;
c.style.left = left + "px";
c.style.top = top + "px";
// reset right and bottom to avoid the window stretching
c.style.right = "";
c.style.bottom = "";
x = event.clientX;
y = event.clientY;
}
function mouseUp(): void {
document.removeEventListener("mouseup", mouseUp);
document.removeEventListener("mousemove", mouseMove);
}
document.addEventListener("mouseup", mouseUp);
document.addEventListener("mousemove", mouseMove);
}
return (
<Paper
style={{
display: "flex",
flexFlow: "column",
backgroundColor: "gray",
width: "50%",
position: "fixed",
left: "50%",
top: "40%",
margin: "-10% 0 0 -25%",
height: "auto",
maxHeight: "50%",
zIndex: 10,
border: "2px solid $hacker-green",
}}
ref={container}
>
<Paper
style={{
cursor: "grab",
}}
>
<Box display="flex" alignItems="center" onMouseDown={drag}>
<Typography color="primary" variant="h6" noWrap component="div">
{props.script.filename} {props.script.args.map((x: any): string => `${x}`).join(" ")}
</Typography>
<Box display="flex" marginLeft="auto">
<Button onClick={kill}>Kill Script</Button>
<Button onClick={props.onClose}>Close</Button>
</Box>
</Box>
</Paper>
<Paper>
{props.script.logs.map(
(line: string, i: number): JSX.Element => (
<Typography key={i} style={{ whiteSpace: "pre-line" }}>
{line}
<br />
</Typography>
),
)}
</Paper>
</Paper>
);
}

View File

@@ -33,7 +33,7 @@ interface IProps {
export function WorldMap(props: IProps): React.ReactElement { export function WorldMap(props: IProps): React.ReactElement {
// prettier-ignore // prettier-ignore
return ( return (
<div className="noselect"> <>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> ,_ . ._. _. .</Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> ,_ . ._. _. .</Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> , _-\','|~\~ ~/ ;-'_ _-' ,;_;_, ~~-</Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> , _-\','|~\~ ~/ ;-'_ _-' ,;_;_, ~~-</Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> /~~-\_/-'~'--' \~~| ', ,' / / ~|-_\_/~/~ ~~--~~~~'--_</Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> /~~-\_/-'~'--' \~~| ', ,' / / ~|-_\_/~/~ ~~--~~~~'--_</Typography>
@@ -56,6 +56,6 @@ export function WorldMap(props: IProps): React.ReactElement {
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> / ,' ~</Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> / ,' ~</Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> ',| ~</Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> ',| ~</Typography>
<Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> ~'</Typography> <Typography sx={{lineHeight: '1em',whiteSpace: 'pre'}}> ~'</Typography>
</div> </>
); );
} }

View File

@@ -12,6 +12,7 @@ module.exports = (env, argv) => {
const isDevServer = (env || {}).devServer === true; const isDevServer = (env || {}).devServer === true;
const runInContainer = (env || {}).runInContainer === true; const runInContainer = (env || {}).runInContainer === true;
const isDevelopment = argv.mode === "development"; const isDevelopment = argv.mode === "development";
const isFastRefresh = argv.fast === "true";
const outputDirectory = isDevServer ? "dist-dev" : "dist"; const outputDirectory = isDevServer ? "dist-dev" : "dist";
const entry = "./src/index.tsx"; const entry = "./src/index.tsx";
@@ -130,7 +131,7 @@ module.exports = (env, argv) => {
columns: true, columns: true,
module: true, module: true,
}), }),
isDevelopment && new ReactRefreshWebpackPlugin(), isFastRefresh && new ReactRefreshWebpackPlugin(),
new DeadCodePlugin({ new DeadCodePlugin({
patterns: ["src/**/*.(js|jsx|css|ts|tsx)"], patterns: ["src/**/*.(js|jsx|css|ts|tsx)"],
exclude: ["**/*.(stories|spec).(js|jsx)"], exclude: ["**/*.(stories|spec).(js|jsx)"],
@@ -150,7 +151,7 @@ module.exports = (env, argv) => {
use: { use: {
loader: "babel-loader", loader: "babel-loader",
options: { options: {
plugins: [isDevelopment && require.resolve("react-refresh/babel")].filter(Boolean), plugins: [isFastRefresh && require.resolve("react-refresh/babel")].filter(Boolean),
cacheDirectory: true, cacheDirectory: true,
}, },
}, },