Initial implementation of BitNode-8: Ghost of Wall Street. Added TextFile.js to git. Added design for company management

This commit is contained in:
danielyxie 2017-10-20 16:59:54 -05:00
parent 153831afe9
commit 4ccad83e5e
8 changed files with 3614 additions and 2785 deletions

@ -596,21 +596,25 @@ div.faction-clear {
margin: 10px; margin: 10px;
} }
.stock-market-qty-input { .stock-market-input {
display: inline-block;
padding: 4px;
margin: 2px;
background-color: black;
border: 1px solid white; border: 1px solid white;
color: var(--my-font-color); color: var(--my-font-color);
padding: 4px; }
margin: 4px;
background-color:black; .stock-market-position-text {
color:white;
white-space: pre;
display:block;
} }
.stock-market-buy-sell-button { .stock-market-buy-sell-button {
color: #aaa; color: #aaa;
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
padding: 2px;
margin: 6px;
border: 1px solid white;
} }
.stock-market-buy-sell-button:hover, .stock-market-buy-sell-button:hover,
@ -620,6 +624,11 @@ div.faction-clear {
cursor: pointer; cursor: pointer;
} }
.stock-market-order-list {
overflow-y:auto;
max-height: 100px;
}
/* Gang */ /* Gang */
#gang-container { #gang-container {
position: fixed; position: fixed;

5498
dist/bundle.js vendored

File diff suppressed because it is too large Load Diff

@ -1,8 +1,156 @@
/* CompanyManagement.js */
/* /*
Company made up of Products
For certain industries, players can created their own custom products
Essentially, these are just things you give a certain name to.
Products have certain properties that affect how well they sell. These properties
are just numbers. For each Industry, only some of these properties are applicable
(e.g. Performance isnt applicable for food industry)
Demand: Determined by industry. For most industries this will slowly decrease over time, meaning
that you must create new and better products to remain successful. The speed at which this
decreases over time is dependent on industry
Competition: Determined by industry
Markup : Determined by Industry
Quality:
Performance:
Durability:
Reliability:
Aesthetics:
Features:
Location: Only valid for 'building' products like Restaurants, Hospitals, etc.
Scientific Research affects the properties of products
Materials:
To create Products, you need materials. There are different tiers of Materials
Materials have several properties that determine how profitable they can be:
Quality:
Demand:
Competition:
Markup: How much price markup a material can have before theres a significant dropoff in how much its bought
Materials Types:
1st tier:
Water - High Stable Demand, Medium competition, low markup
Energy - Suuuuuuper high stable demand, High competition, low markup
2nd Tier:
Food - High Stable Demand, Lots of competition, medium markup
Plants - Initially high but volatile demand. Decent competition, low markup
Metal - Very high stable demand, lots of competition, low markup
3rd Tier:
Hardware - Very high stable demand, lots of competition, med markup
Chemicals - High stable demand, good amount of competition, med markup
Real estate - Initially high but volatile demand. Decent competition, med markup. Tied to a certain city
4th tier:
Drugs - High stable demand, lots of competition, medium markup
Robots - Very high stable demand, looots of competition, high markup
AI Cores - Very high stable demand, looooots of competition, veeery high markup
5th tier:
Scientific Research
Industries:
- Some Industries let you create your own custom "Products", while others just produce Materials
- Each Industry has different characteristics for things
- List of Industries:
Energy - Requires hardware, real estate
Produces Energy
Can Use Hardware/AI Cores to increase production
More real estate = more production with very little dimishing returns
Production increased by scientific research
High starting cost
Utilities - Requires hardware, Real Estate
Produces Water
Can use Hardware, Robotics, and AI Cores to increase production
More real estate = more production with medium diminishing returns
Production increased by scientific research
High starting cost
Agriculture - Requires Water and Energy
Produces food and plants
Can use Hardware/Robotics/AI Cores to increase production
Production increased by scientific research
More real estate = more production with very little diminishing returns
Medium starting cost
Fishing - Requires energy
Produces lots of food
Can use Hardware/Robotics/AI Cores to increase production
Production increased by scientific research
More real estate = slightly more production with very high diminishing returns
Medium starting cost (higher than agriculture)
Mining - Requires Energy
Produces Metal
Can use hardware/Robotics/AI Cores to increase production
Production increased by scientific research
More real estate = more production with medium diminishing returns
High starting cost
Food - Create your own "restaurant" products
Restaurants require food, water, energy, and real estate
Restaurants in general are high stable demand, but lots of competition, and medium markup
Low starting cost
Production increase from real estate diminishes greatly in city. e.g. making many restaurants
in one city has high diminishing returns, but making a few in every city is good
Tobacco - Create your own tobacco products
Requires plants, water, and real estate
High volatile demand, but not much competition. Low markup
Low starting cost
Product quality significantly affected by scientific research
Chemical - Create your own chemical products.
Requires plants, energy, water, and real estate
High stable demand, high competition, low markup
Medium starting cost
Advertising does very little
Product quality significantly affected by scientific research
Pharmaceutical - Create your own drug products
Requires chemicals, energy, water, and real estate
Very high stable demand. High competition, very high markup
High starting cost
Requires constant creation of new and better products to be successful
Product quality significantly affected by scientific research
Computer - Creates 'Hardware' material
Requires metal, energy, real estate
Can use Robotics/AI Cores to increase production
More real estate = more production with high diminishing returns
Production significantly affected by scientific research
High starting cost
Robotics - Create 'Robots' material and create your own 'Robot' products
Requires hardware, energy, and real estate
Production can be improved by using AI Cores
Extremely high stable demand, medium competition, high markup
Extremely high starting cost
Product quality significantly affected by scientific research
more real estate = more production with medium diminishing returns for 'Robot' materials
Software - Create 'AI Cores' material and create your own software products
Requires hardware, energy, real estate
Very high stable demand, high competition, low markup
Low starting cost
Product quality slightly affected by scientific research
Healthcare - Open your own hospitals (each is its own product).
Requires real estate, robots, AI Cores, energy, water
Extremely high stable demand, semi-high competition, super high markup
Extremely high starting cost
Production increase from real estate diminishes greatly in city. e.g. making many hospitals
in one city has high diminishing returns, but making a few in every city is good
Real Estate - Create 'Real Estate'.
Requires metal, energy, water, hardware
Can use Hardware/Robotics/AI Cores to increase production
Production slightly affected by scientific research
Heavily affected by advertising
Biotechnology -
Entertainment -
Finance -
Mass Media -
Telecommunications -
Employees:
Has morale and energy that must be managed to maintain productivity
Stats:
Intelligence, Charisma, Experience, Creativity, Efficiency
Assigned to different positions. The productivity at each position is determined by
stats. I.e. each employe should be assigned to positions based on stats to optimize production
Position
Operations -
Engineer -
Business -
Accounting -
Management -
Research and Development -
*/ */

@ -1132,7 +1132,7 @@ function setGangMemberClickHandlers() {
//Server panel click handlers //Server panel click handlers
var gangMemberHdrs = document.getElementsByClassName("gang-member-header"); var gangMemberHdrs = document.getElementsByClassName("gang-member-header");
if (gangMemberHdrs == null) { if (gangMemberHdrs == null) {
console.log("ERROR: Could not find Active Scripts server panels"); console.log("ERROR: Could not find Gang Member Headers");
return; return;
} }
for (let i = 0; i < gangMemberHdrs.length; ++i) { for (let i = 0; i < gangMemberHdrs.length; ++i) {
@ -1243,7 +1243,6 @@ function createGangMemberDisplayElement(memberObj) {
taskDescP.style.display = "inline"; taskDescP.style.display = "inline";
taskDescDiv.appendChild(taskDescP); taskDescDiv.appendChild(taskDescP);
statsDiv.style.width = "30%"; statsDiv.style.width = "30%";
taskDiv.style.width = "30%"; taskDiv.style.width = "30%";
taskDescDiv.style.width = "30%"; taskDescDiv.style.width = "30%";

@ -59,8 +59,8 @@ import {printArray, powerOfTwo} from "../utils/HelperFunctio
import {createRandomIp} from "../utils/IPAddress.js"; import {createRandomIp} from "../utils/IPAddress.js";
import {formatNumber, isString, isHTML} from "../utils/StringHelperFunctions.js"; import {formatNumber, isString, isHTML} from "../utils/StringHelperFunctions.js";
var hasSingularitySF = false, hasAISF = false, hasBn11SF = false; var hasSingularitySF=false, hasAISF=false, hasBn11SF=false, hasWallStreetSF=false;
var singularitySFLvl = 1; var singularitySFLvl=1, wallStreetSFLvl=1;
//Used to check and set flags for every Source File, despite the name of the function //Used to check and set flags for every Source File, despite the name of the function
function initSingularitySFFlags() { function initSingularitySFFlags() {
@ -72,6 +72,10 @@ function initSingularitySFFlags() {
if (Player.sourceFiles[i].n === 5) { if (Player.sourceFiles[i].n === 5) {
hasAISF = true; hasAISF = true;
} }
if (Player.sourceFiles[i].n === 8) {
hasWallStreetSF = true;
wallStreetSFLvl = Player.sourceFiles[i].lvl;
}
if (Player.sourceFiles[i].n === 11) { if (Player.sourceFiles[i].n === 11) {
hasBn11SF = true; hasBn11SF = true;
} }
@ -1979,6 +1983,30 @@ function NetscriptFunctions(workerScript) {
workerScript.scriptRef.log(txt); workerScript.scriptRef.log(txt);
} }
//Set Location to slums
switch(Player.city) {
case Locations.Aevum:
Player.location = Locations.AevumSlums;
break;
case Locations.Chongqing:
Player.location = Locations.ChongqingSlums;
break;
case Locations.Sector12:
Player.location = Locations.Sector12Slums;
break;
case Locations.NewTokyo:
Player.location = Locations.NewTokyoSlums;
break;
case Locations.Ishima:
Player.location = Locations.IshimaSlums;
break;
case Locations.Volhaven:
Player.location = Locations.VolhavenSlums;
break;
default:
console.log("Invalid Player.city value");
}
crime = crime.toLowerCase(); crime = crime.toLowerCase();
if (crime.includes("shoplift")) { if (crime.includes("shoplift")) {
workerScript.scriptRef.log("Attempting to shoplift..."); workerScript.scriptRef.log("Attempting to shoplift...");
@ -2190,4 +2218,5 @@ function NetscriptFunctions(workerScript) {
} }
} }
export {NetscriptFunctions, initSingularitySFFlags, hasSingularitySF, hasBn11SF}; export {NetscriptFunctions, initSingularitySFFlags, hasSingularitySF, hasBn11SF, hasWallStreetSF,
wallStreetSFLvl};

@ -89,6 +89,7 @@ BitburnerSaveObject.prototype.saveGame = function(db) {
window.localStorage.setItem("bitburnerSave", saveString); window.localStorage.setItem("bitburnerSave", saveString);
} catch(e) { } catch(e) {
if (e.code == 22) { if (e.code == 22) {
Engine.createStatusText("Save failed for localStorage! Check console(F12)");
console.log("Failed to save game to localStorage because the size of the save file " + console.log("Failed to save game to localStorage because the size of the save file " +
"is too large. However, the game will still be saved to IndexedDb if your browser " + "is too large. However, the game will still be saved to IndexedDb if your browser " +
"supports it. If you would like to save to localStorage as well, then " + "supports it. If you would like to save to localStorage as well, then " +

@ -1,6 +1,7 @@
import {CONSTANTS} from "./Constants.js"; import {CONSTANTS} from "./Constants.js";
import {Engine} from "./engine.js"; import {Engine} from "./engine.js";
import {Locations} from "./Location.js"; import {Locations} from "./Location.js";
import {hasWallStreetSF, wallStreetSFLvl} from "./NetscriptFunctions.js";
import {WorkerScript} from "./NetscriptWorker.js"; import {WorkerScript} from "./NetscriptWorker.js";
import {Player} from "./Player.js"; import {Player} from "./Player.js";
@ -10,6 +11,11 @@ import {Reviver, Generic_toJSON,
Generic_fromJSON} from "../utils/JSONReviver.js"; Generic_fromJSON} from "../utils/JSONReviver.js";
import numeral from "../utils/numeral.min.js"; import numeral from "../utils/numeral.min.js";
import {formatNumber} from "../utils/StringHelperFunctions.js"; import {formatNumber} from "../utils/StringHelperFunctions.js";
import {yesNoBoxCreate, yesNoTxtInpBoxCreate,
yesNoBoxGetYesButton, yesNoBoxGetNoButton,
yesNoTxtInpBoxGetYesButton, yesNoTxtInpBoxGetNoButton,
yesNoTxtInpBoxGetInput, yesNoBoxClose,
yesNoTxtInpBoxClose, yesNoBoxOpen} from "../utils/YesNoBox.js";
/* StockMarket.js */ /* StockMarket.js */
function Stock(name, symbol, mv, b, otlkMag, initPrice=10000) { function Stock(name, symbol, mv, b, otlkMag, initPrice=10000) {
@ -36,9 +42,95 @@ Stock.fromJSON = function(value) {
Reviver.constructors.Stock = Stock; Reviver.constructors.Stock = Stock;
//Order types (long and short for each): var OrderTypes = {
// - Limit Order LimitBuy: "Limit Buy Order",
// - Stop Order LimitSell: "Limit Sell Order",
StopBuy: "Stop Buy Order",
StopSell: "Stop Sell Order"
}
var PositionTypes = {
Long: "L",
Short: "S"
}
function placeOrder(stock, shares, price, type, position, workerScript=null) {
var tixApi = (workerScript instanceof WorkerScript);
var order = new Order(stock, shares, price, type, position);
if (isNaN(shares)) {
dialogBoxCreate("ERROR: Invalid number of shares specifies for order");
return false;
}
if (StockMarket["Orders"] === null) {
var orders = {};
for (var name in StockMarket) {
if (StockMarket.hasOwnProperty(name)) {
var stock = StockMarket[name];
if (!(stock instanceof Stock)) {continue;}
orders[stock.symbol] = [];
}
}
StockMarket["Orders"] = orders;
}
StockMarket["Orders"].push(order);
//Process to see if it should be executed immediately
processOrders(order.stock, order.type, order.pos);
return true;
}
function executeOrder(order) {
var stock = order.stock;
var orderBook = StockMarket["Orders"];
var stockOrders = orderBook[stock.symbol];
var res = true;
switch (order.type) {
case OrderTypes.LimitBuy:
case OrderTypes.StopBuy:
if (order.pos === PositionTypes.Long) {
res = buyStock(order.stock, order.shares) && res;
} else if (order.pos === PositionTypes.Short) {
res = shortStock(oder.stock, order.shares) && res;
}
break;
case OrderTypes.LimitSell:
case OrderTypes.StopSell:
if (order.pos === PositionTypes.Long) {
res = sellStock(order.stock, order.shares) && res;
} else if (order.pos === PositionTypes.Short) {
res = sellShort(order.stock, order.shares) && res;
}
break;
}
if (res) {
//Remove order from order book
for (var i = 0; i < stockOrders.length; ++i) {
if (order == stockOrders[i]) {
stockOrders.splice(i, 1);
return;
}
}
console.log("ERROR: Could not find the following Order in Order Book: ");
console.log(order);
}
}
function Order(stock, price, type, position) {
this.stock = stock;
this.shares = shares;
this.price = price;
this.type = type;
this.pos = position;
}
Order.prototype.toJSON = function() {
return Generic_toJSON("Order", this);
}
Order.fromJSON = function(value) {
return Generic_fromJSON(Order, value.data);
}
Reviver.constructors.Order = Order;
let StockMarket = {} //Full name to stock object let StockMarket = {} //Full name to stock object
let StockSymbols = {} //Full name to symbol let StockSymbols = {} //Full name to symbol
@ -229,6 +321,16 @@ function initStockMarket() {
var titanlabs = "Titan Laboratories"; var titanlabs = "Titan Laboratories";
var titanlabsStk = new Stock(titanlabs, StockSymbols[titanlabs], 0.6, true, 11, getRandomInt(15000, 20000)); var titanlabsStk = new Stock(titanlabs, StockSymbols[titanlabs], 0.6, true, 11, getRandomInt(15000, 20000));
StockMarket[titanlabs] = titanlabsStk; StockMarket[titanlabs] = titanlabsStk;
var orders = {};
for (var name in StockMarket) {
if (StockMarket.hasOwnProperty(name)) {
var stock = StockMarket[name];
if (!(stock instanceof Stock)) {continue;}
orders[stock.symbol] = [];
}
}
StockMarket["Orders"] = orders;
} }
function initSymbolToStockMap() { function initSymbolToStockMap() {
@ -408,6 +510,7 @@ function updateStockPrices() {
var v = Math.random(); var v = Math.random();
for (var name in StockMarket) { for (var name in StockMarket) {
if (StockMarket.hasOwnProperty(name)) { if (StockMarket.hasOwnProperty(name)) {
if (!(stock instanceof Stock)) {continue;}
var stock = StockMarket[name]; var stock = StockMarket[name];
var av = (v * stock.mv) / 100; var av = (v * stock.mv) / 100;
if (isNaN(av)) {av = .02;} if (isNaN(av)) {av = .02;}
@ -424,11 +527,19 @@ function updateStockPrices() {
var c = Math.random(); var c = Math.random();
if (c < chc) { if (c < chc) {
stock.price *= (1 + av); stock.price *= (1 + av);
processOrders(stock, OrderTypes.LimitBuy, PositionTypes.Short);
processOrders(stock, OrderTypes.LimitSell, PositionTypes.Long);
processOrders(stock, OrderTypes.StopBuy, PositionTypes.Long);
processOrders(stock, OrderTypes.StopSell, PositionTypes.Short);
if (Engine.currentPage == Engine.Page.StockMarket) { if (Engine.currentPage == Engine.Page.StockMarket) {
updateStockTicker(stock, true); updateStockTicker(stock, true);
} }
} else { } else {
stock.price /= (1 + av); stock.price /= (1 + av);
processOrders(stock, OrderTypes.LimitBuy, PositionTypes.Long);
processOrders(stock, OrderTypes.LimitSell, PositionTypes.Short);
processOrders(stock, OrderTypes.StopBuy, PositionTypes.Short);
processOrders(stock, OrderTypes.StopSell, PositionTypes.Long);
if (Engine.currentPage == Engine.Page.StockMarket) { if (Engine.currentPage == Engine.Page.StockMarket) {
updateStockTicker(stock, false); updateStockTicker(stock, false);
} }
@ -447,6 +558,68 @@ function updateStockPrices() {
stock.otlkMag *= -1; stock.otlkMag *= -1;
stock.b = !stock.b; stock.b = !stock.b;
} }
}
}
}
//Checks and triggers any orders for the specified stock
function processOrders(stock, orderType, posType) {
var orderBook = StockMarket["Orders"];
if (orderBook === null) {
var orders = {};
for (var name in StockMarket) {
if (StockMarket.hasOwnProperty(name)) {
var stock = StockMarket[name];
if (!(stock instanceof Stock)) {continue;}
orders[stock.symbol] = [];
}
}
StockMarket["Orders"] = orders;
return; //Newly created, so no orders to process
}
var stockOrders = orderBook[stock.symbol];
if (stockOrders === null || !(stockOrders.constructor === Array)) {
console.log("ERROR: Invalid Order book for " + stock.symbol + " in processOrders()");
stockOrders = [];
return;
}
for (var i = 0; i < stockOrders.length; ++i) {
var order = stockOrders[i];
if (order.type === orderType && order.pos === posType) {
switch(order.type) {
case OrderTypes.LimitBuy:
if (order.pos === PositionTypes.Long && stock.price <= order.price) {
executeOrder/*66*/(order);
} else if (order.pos === PositionTypes.Short && stock.price >= order.price) {
executeOrder/*66*/(order);
}
break;
case OrderTypes.LimitSell:
if (order.pos === PositionTypes.Long && stock.price >= order.price) {
executeOrder/*66*/(order);
} else if (order.pos === PositionTypes.Short && stock.price <= order.price) {
executeOrder/*66*/(order);
}
break;
case OrderTypes.StopBuy:
if (order.pos === PositionTypes.Long && stock.price >= order.price) {
executeOrder/*66*/(order);
} else if (order.pos === PositionTypes.Short && stock.price <= order.price) {
executeOrder/*66*/(order);
}
break;
case OrderTypes.StopSell:
if (order.pos === PositionTypes.Long && stock.price <= order.price) {
executeOrder/*66*/(order);
} else if (order.pos === PositionTypes.Short && stock.price >= order.price) {
executeOrder/*66*/(order);
}
break;
default:
console.log("Invalid order type: " + order.type);
return;
}
} }
} }
} }
@ -514,138 +687,14 @@ function displayStockMarketContent() {
"This means all your positions are lost, so make sure to sell your stocks before installing " + "This means all your positions are lost, so make sure to sell your stocks before installing " +
"Augmentations!"; "Augmentations!";
var hdrLi = document.createElement("li");
var hdrName = document.createElement("p");
var hdrSym = document.createElement("p");
var hdrPrice = document.createElement("p");
var hdrQty = document.createElement("p");
var hdrBuySell = document.createElement("p")
var hdrAvgPrice = document.createElement("p");
var hdrShares = document.createElement("p");
var hdrReturn = document.createElement("p");
hdrName.style.display = "inline-block";
hdrName.innerText = "Stock Name";
hdrName.style.width = "8%";
hdrSym.style.display = "inline-block";
hdrSym.innerText = "Symbol";
hdrSym.style.width = "4%";
hdrPrice.style.display = "inline-block";
hdrPrice.innerText = "Price";
hdrPrice.style.width = "8%";
hdrQty.style.display = "inline-block";
hdrQty.innerText = "Quantity";
hdrQty.style.width = "3%";
hdrBuySell.style.display = "inline-block";
hdrBuySell.innerText = "Buy/Sell";
hdrBuySell.style.width = "5%";
hdrAvgPrice.style.display = "inline-block";
hdrAvgPrice.innerText = "Avg price of owned shares";
hdrAvgPrice.style.width = "7.5%";
hdrShares.style.display = "inline-block";
hdrShares.innerText = "Shares owned";
hdrShares.style.width = "4%";
hdrReturn.style.display = "inline-block";
hdrReturn.innerText = "Total Return";
hdrReturn.style.width = "6%";
hdrLi.appendChild(hdrName);
hdrLi.appendChild(hdrSym);
hdrLi.appendChild(hdrPrice);
hdrLi.appendChild(hdrQty);
hdrLi.appendChild(hdrBuySell);
hdrLi.appendChild(hdrAvgPrice);
hdrLi.appendChild(hdrShares);
hdrLi.appendChild(hdrReturn);
stockList.appendChild(hdrLi);
for (var name in StockMarket) { for (var name in StockMarket) {
if (StockMarket.hasOwnProperty(name)) { if (StockMarket.hasOwnProperty(name)) {
(function() {
var stock = StockMarket[name]; var stock = StockMarket[name];
if (!(stock instanceof Stock)) {continue;} //orders property is an array
var li = document.createElement("li"); createStockTicker(stock);
var stkName = document.createElement("p");
var stkSym = document.createElement("p");
var stkPrice = document.createElement("p");
var qtyInput = document.createElement("input");
var buyButton = document.createElement("span");
var sellButton = document.createElement("span");
var avgPriceTxt = document.createElement("p");
var sharesTxt = document.createElement("p");
var returnTxt = document.createElement("p");
var tickerId = "stock-market-ticker-" + stock.symbol;
stkName.setAttribute("id", tickerId + "-name");
stkSym.setAttribute("id", tickerId + "-sym");
stkPrice.setAttribute("id", tickerId + "-price");
stkName.style.display = "inline-block";
stkName.style.width = "8%";
stkSym.style.display = "inline-block";
stkSym.style.width = "4%";
stkPrice.style.display = "inline-block";
stkPrice.style.width = "9%";
li.setAttribute("display", "inline-block");
qtyInput.setAttribute("type", "text");
qtyInput.setAttribute("id", tickerId + "-qty-input");
qtyInput.setAttribute("class", "stock-market-qty-input");
qtyInput.setAttribute("onkeydown", "return ( event.ctrlKey || event.altKey " +
" || (47<event.keyCode && event.keyCode<58 && event.shiftKey==false) " +
" || (95<event.keyCode && event.keyCode<106) " +
" || (event.keyCode==8) || (event.keyCode==9) " +
" || (event.keyCode>34 && event.keyCode<40) " +
" || (event.keyCode==46) )");
qtyInput.style.width = "3%";
qtyInput.style.display = "inline-block";
buyButton.innerHTML = "Buy";
buyButton.setAttribute("class", "stock-market-buy-sell-button");
buyButton.style.width = "3%";
buyButton.style.display = "inline-block";
buyButton.addEventListener("click", function() {
var shares = document.getElementById(tickerId + "-qty-input").value;
shares = Number(shares);
if (isNaN(shares)) {return false;}
buyStock(stock, shares);
});
sellButton.innerHTML = "Sell";
sellButton.setAttribute("class", "stock-market-buy-sell-button");
sellButton.style.width = "3%";
sellButton.style.display = "inline-block";
sellButton.addEventListener("click", function() {
var shares = document.getElementById(tickerId + "-qty-input").value;
shares = Number(shares);
if (isNaN(shares)) {return false;}
sellStock(stock, shares);
});
avgPriceTxt.setAttribute("id", tickerId + "-avgprice");
avgPriceTxt.style.display = "inline-block";
avgPriceTxt.style.width = "8%";
avgPriceTxt.style.color = "white";
sharesTxt.setAttribute("id", tickerId + "-shares");
sharesTxt.style.display = "inline-block";
sharesTxt.style.width = "4%";
sharesTxt.style.color = "white";
returnTxt.setAttribute("id", tickerId + "-return");
returnTxt.style.display = "inline-block";
returnTxt.style.width = "6%";
returnTxt.style.color = "white";
li.appendChild(stkName);
li.appendChild(stkSym);
li.appendChild(stkPrice);
li.appendChild(qtyInput);
li.appendChild(buyButton);
li.appendChild(sellButton);
li.appendChild(avgPriceTxt);
li.appendChild(sharesTxt);
li.appendChild(returnTxt);
stockList.appendChild(li);
}()); //Immediate invocation
}//End if
} }
}
setStockTickerClickHandlers(); //Clicking headers opens/closes panels
stockMarketContentCreated = true; stockMarketContentCreated = true;
} }
@ -660,59 +709,278 @@ function displayStockMarketContent() {
} }
} }
//'increase' argument is a boolean indicating whether the price increased or decreased function createStockTicker(stock) {
function updateStockTicker(stock, increase) { if (!(stock instanceof Stock)) {
var tickerId = "stock-market-ticker-" + stock.symbol; console.log("Invalid stock in createStockSticker()");
let stkName = document.getElementById(tickerId + "-name");
let stkSym = document.getElementById(tickerId + "-sym");
let stkPrice = document.getElementById(tickerId + "-price");
if (stkName == null || stkSym == null || stkPrice == null) {
console.log("ERROR, couldn't find elements with tickerId " + tickerId);
return; return;
} }
stkName.innerText = stock.name; var tickerId = "stock-market-ticker-" + stock.symbol;
stkSym.innerText = stock.symbol; var li = document.createElement("li"), hdr = document.createElement("button");
stkPrice.innerText = "$" + formatNumber(stock.price, 2).toString(); hdr.classList.add("accordion-header");
hdr.setAttribute("id", tickerId + "-hdr");
hdr.innerHTML = stock.name + " - " + stock.symbol + " - $" + stock.price;
var returnTxt = document.getElementById(tickerId + "-return"); //Div for entire panel
var totalCost = stock.playerShares * stock.playerAvgPx; var stockDiv = document.createElement("div");
var gains = (stock.price - stock.playerAvgPx) * stock.playerShares; stockDiv.classList.add("accordion-panel");
var percentageGains = gains / totalCost;
if (totalCost > 0) { /* Create panel DOM */
returnTxt.innerText = "$" + formatNumber(gains, 2) + " (" + var qtyInput = document.createElement("input"),
formatNumber(percentageGains * 100, 2) + "%)"; longShortSelect = document.createElement("select"),
} else { orderTypeSelect = document.createElement("select"),
returnTxt.innerText = "N/A"; buyButton = document.createElement("span"),
sellButton = document.createElement("span"),
positionTxt = document.createElement("p"),
orderList = document.createElement("ul");
qtyInput.classList.add("stock-market-input");
qtyInput.setAttribute("id", tickerId + "-qty-input");
qtyInput.setAttribute("onkeydown", "return ( event.ctrlKey || event.altKey " +
" || (47<event.keyCode && event.keyCode<58 && event.shiftKey==false) " +
" || (95<event.keyCode && event.keyCode<106) " +
" || (event.keyCode==8) || (event.keyCode==9) " +
" || (event.keyCode>34 && event.keyCode<40) " +
" || (event.keyCode==46) )");
longShortSelect.classList.add("stock-market-input");
longShortSelect.setAttribute("id", tickerId + "-pos-selector");
var longOpt = document.createElement("option");
longOpt.text = "Long";
longShortSelect.add(longOpt);
if (Player.bitNodeN === 8 || (hasWallStreetSF && wallStreetSFLvl >= 2)) {
var shortOpt = document.createElement("option");
shortOpt.text = "Short";
longShortSelect.add(shortOpt);
} }
orderTypeSelect.classList.add("stock-market-input");
orderTypeSelect.setAttribute("id", tickerId + "-order-selector");
var marketOpt = document.createElement("option");
marketOpt.text = "Market Order";
orderTypeSelect.add(marketOpt);
if (Player.bitNodeN === 8 || (hasWallStreetSF && wallStreetSFLvl >= 3)) {
var limitOpt = document.createElement("option");
limitOpt.text = "Limit Order";
orderTypeSelect.add(limitOpt);
var stopOpt = document.createElement("option");
stopOpt.text = "Stop Order";
orderTypeSelect.add(stopOpt);
}
if (increase) { buyButton.classList.add("stock-market-input");
stkName.style.color = "#66ff33"; buyButton.innerHTML = "Buy";
stkSym.style.color = "#66ff33"; buyButton.addEventListener("click", ()=>{
stkPrice.style.color = "#66ff33"; var pos = longShortSelect.options[longShortSelect.selectedIndex].text;
pos === "Long" ? pos = PositionTypes.Long : pos = PositionTypes.Short;
var ordType = orderTypeSelect.options[orderTypeSelect.selectedIndex].text;
var shares = Number(document.getElementById(tickerId + "-qty-input").value);
if (isNaN(shares)) {return false;}
switch (ordType) {
case "Market Order":
buyStock(stock, shares);
break;
case "Limit Order":
case "Stop Order":
var yesBtn = yesNoTxtInpBoxGetYesButton(),
noBtn = yesNoTxtInpBoxGetNoButton();
yesBtn.innerText = "Place Buy" + ordType;
noBtn.innerText = "Cancel Order";
yesBtn.addEventListener("click", ()=>{
var price = Number(yesNoTxtInpBoxGetInput()), type;
if (ordType === "Limit Order") {
type = OrderTypes.LimitBuy;
} else { } else {
stkName.style.color = "red"; type = OrderTypes.StopBuy;
stkSym.style.color = "red"; }
stkPrice.style.color = "red"; placeOrder(stock, shares, price, type, pos);
});
noBtn.addEventListener("click", ()=>{
yesNoTxtInpBoxClose();
});
yesNoTxtInpBoxCreate("Enter the price for your " + ordType);
break;
default:
console.log("ERROR: Invalid order type");
break;
}
return false;
});
sellButton.classList.add("stock-market-input");
sellButton.innerHTML = "Sell";
sellButton.addEventListener("click", ()=>{
var pos = longShortSelect.options[longShortSelect.selectedIndex].text;
pos === "Long" ? pos = PositionTypes.Long : pos = PositionTypes.Short;
var ordType = orderTypeSelect.options[orderTypeSelect.selectedIndex].text;
var shares = Number(document.getElementById(tickerId + "-qty-input").value);
if (isNaN(shares)) {return false;}
switch (ordType) {
case "Market Order":
buyStock(stock, shares);
break;
case "Limit Order":
case "Stop Order":
var yesBtn = yesNoTxtInpBoxGetYesButton(),
noBtn = yesNoTxtInpBoxGetNoButton();
yesBtn.innerText = "Place Sell" + ordType;
noBtn.innerText = "Cancel Order";
yesBtn.addEventListener("click", ()=>{
var price = Number(yesNoTxtInpBoxGetInput()), type;
if (ordType === "Limit Order") {
type = OrderTypes.LimitSell;
} else {
type = OrderTypes.StopSell;
}
placeOrder(stock, shares, price, type, pos);
});
noBtn.addEventListener("click", ()=>{
yesNoTxtInpBoxClose();
});
break;
default:
console.log("ERROR: Invalid order type");
break;
}
return false;
});
positionTxt.setAttribute("id", tickerId + "-position-text");
positionTxt.classList.add("stock-market-position-text");
orderList.setAttribute("id", tickerId + "-order-list");
orderList.classList.add("stock-market-order-list");
stockDiv.appendChild(qtyInput);
stockDiv.appendChild(longShortSelect);
stockDiv.appendChild(orderTypeSelect);
stockDiv.appendChild(buyButton);
stockDiv.appendChild(sellButton);
stockDiv.appendChild(positionTxt);
stockDiv.appendChild(orderList);
li.appendChild(hdr);
li.appendChild(stockDiv);
document.getElementById("stock-market-list").appendChild(li);
updateStockTicker(stock, true);
}
function setStockTickerClickHandlers() {
var stockList = document.getElementById("stock-market-list");
var tickerHdrs = stockList.getElementsByClassName("accordion-header");
if (tickerHdrs === null) {
console.log("ERROR: Could not find header elements for stock tickers");
return;
}
for (var i = 0; i < tickerHdrs.length; ++i) {
tickerHdrs[i].onclick = function() {
this.classList.toggle("active");
var panel = this.nextElementSibling;
if (panel.style.display === "block") {
panel.style.display = "none";
} else {
panel.style.display = "block";
}
}
}
}
//'increase' argument is a boolean indicating whether the price increased or decreased
function updateStockTicker(stock, increase) {
if (!(stock instanceof Stock)) {
console.log("Invalid stock in updateStockTicker()");
return;
}
var tickerId = "stock-market-ticker-" + stock.symbol;
var hdr = document.getElementById(tickerId + "-hdr");
if (hdr === null) {
console.log("ERROR: Couldn't find ticker element for stock: " + stock.symbol);
return;
}
hdr.innerHTML = stock.name + " - " + stock.symbol + " - $" + stock.price;
increase ? hdr.style.color = "#66ff33" : hdr.style.color = "red";
if (stock.playerShares > 0 || stock.playerShortShares > 0) {
updateStockPlayerPosition(stock);
} }
} }
function updateStockPlayerPosition(stock) { function updateStockPlayerPosition(stock) {
var tickerId = "stock-market-ticker-" + stock.symbol; if (!(stock instanceof Stock)) {
var avgPriceTxt = document.getElementById(tickerId + "-avgprice"); console.log("Invalid stock in updateStockTicker()");
var sharesTxt = document.getElementById(tickerId + "-shares");
if (avgPriceTxt == null || sharesTxt == null) {
dialogBoxCreate("Could not find element for player positions for stock " +
stock.symbol + ". This is a bug please contact developer");
return; return;
} }
avgPriceTxt.innerText = "$" + formatNumber(stock.playerAvgPx, 2); var tickerId = "stock-market-ticker-" + stock.symbol,
sharesTxt.innerText = stock.playerShares.toString(); positionTxt = document.getElementById(tickerId + "-position-text");
if (positionTxt === null) {
console.log("ERROR: Could not find stock position element for: " + stock.symbol);
return;
}
//Calculate returns
var totalCost = stock.playerShares * stock.playerAvgPx,
gains = (stock.price - stock.playerAvgPx) * stock.playerShares,
percentageGains = gains / totalCost;
var shortTotalCost = stock.playerShortShares * stock.playerAvgShortPx,
shortGains = (stock.playerAvgShortPx - stock.price) * stock.playerShortShares,
shortPercentageGains = shortGains/ shortTotalCost;
positionTxt.innerHTML =
"<h1 class='tooltip stock-market-position-text'>Long Position: " +
"<span class='tooltiptext'>Shares in the long position will increase " +
"in value if the price of the corresponding stock increases</span></h1>" +
"<br>Shares: " + formatNumber(stock.playerShares, 0) +
"<br>Average Price: " + numeral(stock.playerAvgPx).format('$0.000a') +
"<br>Profit: " + numeral(gains).format('$0.000a') +
" (" + formatNumber(percentageGains, 2) + "%)<br><br>" +
"<h1 class='tooltip stock-market-position-text'>Short Position: " +
"<span class='tooltiptext'>Shares in short position will increase " +
"in value if the price of the corresponding stock decreases</span></h1>" +
"<br>Shares: " + formatNumber(stock.playerShortShares, 0) +
"<br>Average Price: " + numeral(stock.playerAvgShortPx).format('$0.000a') +
"<br>Profit: " + numeral(shortGains).format('$0.000a') +
" (" + formatNumber(shortPercentageGains, 2) + "%)";
}
function updateStockOrderList(stock) {
var tickerId = "stock-market-ticker-" + stock.symbol;
var orderList = document.getElementById(tickerId + "-order-list");
if (orderList === null) {
console.log("ERROR: Could not find order list for " + stock.symbol);
return;
}
var orderBook = StockMarket["Orders"];
if (orderBook === null) {
console.log("ERROR: Could not find order book in stock market");
return;
}
var stockOrders = orderBook[stock.symbol];
if (stockOrders === null) {
console.log("ERROR: Could nto find orders for: " + stock.symbol);
return;
}
//Remove everything from list
while (orderList.firstChild) {
orderList.removeChild(orderList.firstChild);
}
for (var i = 0; i < stockOrders.length; ++i) {
var order = stockOrders[i];
var li = document.createElement("li");
var posText = (order.pos === PositionTypes.Long ? "Long Position" : "Short Position");
li.innerText = order.type + " - " + posText + " - " +
order.shares + " @ $" + formatNumber(order.price, 2);
orderList.appendChild(li);
}
} }
export {StockMarket, StockSymbols, SymbolToStockMap, initStockSymbols, export {StockMarket, StockSymbols, SymbolToStockMap, initStockSymbols,
initStockMarket, initSymbolToStockMap, stockMarketCycle, buyStock, initStockMarket, initSymbolToStockMap, stockMarketCycle, buyStock,
sellStock, updateStockPrices, displayStockMarketContent, sellStock, updateStockPrices, displayStockMarketContent,
updateStockTicker, updateStockPlayerPosition, loadStockMarket, updateStockTicker, updateStockPlayerPosition, loadStockMarket,
setStockMarketContentCreated}; setStockMarketContentCreated, placeOrder, Order, OrderTypes};

77
src/TextFile.js Normal file

@ -0,0 +1,77 @@
import {Server} from "./Server.js";
import {dialogBoxCreate} from "../utils/DialogBox.js";
import {Reviver, Generic_toJSON,
Generic_fromJSON} from "../utils/JSONReviver.js";
function TextFile(fn="", txt="") {
this.fn = fn.endsWith(".txt") ? fn : fn + ".txt";
this.text = String(txt);
}
TextFile.prototype.append = function(txt) {
this.text += String(txt);
}
TextFile.prototype.write = function(txt) {
this.text = String(txt);
}
TextFile.prototype.read = function() {
return this.txt;
}
TextFile.prototype.show = function() {
dialogBoxCreate(this.fn + "<br><br>" + this.text);
}
TextFile.prototype.download = function() {
var filename = this.fn;
var file = new Blob([this.text], {type: 'text/plain'});
if (window.navigator.msSaveOrOpenBlob) {// IE10+
window.navigator.msSaveOrOpenBlob(file, filename);
} else { // Others
var a = document.createElement("a"),
url = URL.createObjectURL(file);
a.href = url;
a.download = this.fn;
document.body.appendChild(a);
a.click();
setTimeout(function() {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
}
}
TextFile.prototype.toJSON = function() {
return Generic_toJSON("TextFile", this);
}
TextFile.fromJSON = function(value) {
return Generic_fromJSON(TextFile, value.data);
}
Reviver.constructors.TextFile = TextFile;
function getTextFile(fn, server) {
for (var i = 0; i < server.textFiles.length; ++i) {
if (server.textFiles[i].fn === fn) {
return server.textFiles[i];
}
}
return null;
}
//Returns the TextFile object that was just created
function createTextFile(fn, txt, server) {
if (getTextFile(fn, server) !== null) {
console.log("ERROR: createTextFile failed because the specified " +
"server already has a text file with the same fn");
return;
}
var file = new TextFile(fn, txt);
server.textFiles.push(file);
return file;
}
export {TextFile, getTextFile, createTextFile};