Skip to content

Commit 88d79e3

Browse files
committed
✨ Starting on the production dashboard.
Includes scraping and storage for active perks so they can be used elsewhere.
1 parent a24de72 commit 88d79e3

12 files changed

+8729
-85
lines changed

background.html

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
<html lang="en">
33
<head>
44
<meta charset="utf-8">
5-
<script type="module" src="idb-7.0.0.js"></script>
5+
<script type="module" src="vendor/idb-7.0.0.js"></script>
6+
<script src="vendor/luxon-2.3.1.js"></script>
67
<script type="module" src="farmrpg-ext-bg.js"></script>
78
</head>
89
</html>

farmrpg-ext-bg.js

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { setupWorkshop } from './lib/workshop.js'
1717
import { setupAnimals } from './lib/animals.js'
1818
import { setupSettings } from './lib/settings.js'
1919
import { setupLocksmith } from './lib/locksmith.js'
20+
import { setupProduction } from './lib/production.js'
2021

2122
class GlobalState {
2223
constructor() {
@@ -219,6 +220,7 @@ const main = async () => {
219220
setupAnimals(globalState)
220221
setupSettings(globalState)
221222
setupLocksmith(globalState)
223+
setupProduction(globalState)
222224

223225
// Kick off some initial data population.
224226
renderSidebarFromGlobalState()

farmrpg-ext.css

+8-3
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@
1010
justify-content: space-between;
1111
}
1212

13-
.farmrpg-ext-log, .farmrpg-ext-settings, .farmrpg-ext-perk {
13+
.farmrpg-ext-status-button {
1414
display: inline-block;
1515
margin-left: 5px;
1616
cursor: pointer;
1717
}
1818

19-
.farmrpg-ext-status-cropimg, .farmrpg-ext-log img, .farmrpg-ext-settings img, .farmrpg-ext-perk img {
19+
.farmrpg-ext-status img {
2020
height: 13px;
2121
}
2222

@@ -44,7 +44,7 @@
4444
background-color: rgba(255, 255, 255, 0.7);
4545
}
4646

47-
.farmrpg-ext-click, .farmrpg-ext-log, .farmrpg-ext-perk, .farmrpg-ext-crop {
47+
.farmrpg-ext-click, .farmrpg-ext-status img {
4848
cursor: pointer;
4949
}
5050

@@ -69,3 +69,8 @@
6969
height: 17px;
7070
margin-right: 5px;
7171
}
72+
73+
.farmrpg-ext-production-overflow {
74+
color: red;
75+
padding-right: 10px;
76+
}

lib/html.js

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
export const renderRowText = (text) => {
2+
return `
3+
<li class="farmrpg-ext-row">
4+
<div class="item-content">
5+
<div class="item-inner">
6+
${text}
7+
<div>
8+
</div>
9+
</li>
10+
`
11+
}
12+
13+
export const renderRowCheckbox = (label, name, checked, value = "1") => {
14+
const checkedAttr = checked ? 'checked="checked"' : ""
15+
return `
16+
<li class="farmrpg-ext-row">
17+
<div class="item-content">
18+
<div class="item-inner">
19+
<div class="item-title label" style="width:60%">${label}</div>
20+
<label class="label-switch">
21+
<input type="checkbox" name="${name}" value="${value}" ${checkedAttr}>
22+
<div class="checkbox"></div>
23+
</label>
24+
</div>
25+
</div>
26+
</li>
27+
`
28+
}
29+
30+
export const renderListBlock = (label, options, rows) => {
31+
return `
32+
<div class="content-block-title">${label}</div>
33+
<div class="list-block ${options.inset ? 'inset' : ''}">
34+
<ul>
35+
${rows.join("\n")}
36+
</ul>
37+
</div>
38+
`
39+
}
40+
41+
export const renderPage = (title, name, content) => {
42+
if (!Array.isArray(content)) {
43+
content = [content]
44+
}
45+
return `
46+
<div class="navbar">
47+
<div class="navbar-inner">
48+
<div class="left"><a href="x" class="back link"> <i class="icon icon-back"></i><span>Back</span></a></div>
49+
<div class="center sliding">${title}</div>
50+
<div class="right"><a href="x" data-panel="left" class="link open-panel icon-only"><i class="icon icon-bars"></i></a></div>
51+
</div>
52+
</div>
53+
<div class="pages">
54+
<div data-page="farmrpg-ext-${name}" class="page">
55+
<div class="page-content">
56+
<div class="content-block">
57+
<form id="farmrpg-ext-${name}-form">
58+
${content.join("\n")}
59+
</form>
60+
</div>
61+
</div>
62+
</div>
63+
</div>
64+
`
65+
}

lib/perks.js

+44-11
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,32 @@
11
import { renderSidebar } from "./sidebar.js"
2+
import { findSection } from "./utils.js"
23

34
const skillRE = /(farm)|(fish)|(craft)|(explor)/i
45

6+
// Parse a list of perks from either the parks page or farm supply page.
7+
const parsePerkList = (dom, farmSupply) => {
8+
const perksElm = findSection(dom, farmSupply ? "Available Upgrades" : "All Perks")
9+
if (perksElm === null) {
10+
throw "Unable to find perk list in DOM"
11+
}
12+
const perks = {}
13+
for (const row of perksElm.querySelectorAll(".item-content")) {
14+
const name = row.querySelector(".item-title strong").innerText
15+
const active = row.querySelector("button.btnblue") !== null
16+
perks[name] = active
17+
}
18+
return perks
19+
}
20+
521
const parsePerks = (page, url) => {
622
const parser = new DOMParser()
723
const dom = parser.parseFromString(page, "text/html")
824
// Parse out available perksets and which is active (if any).
9-
let perksetsElm = null
10-
for (const row of dom.querySelectorAll(".content-block-title")) {
11-
if (row.textContent.trim() === "My Perk Sets") {
12-
perksetsElm = row.nextElementSibling
13-
break
14-
}
15-
}
25+
const perks = parsePerkList(dom, false)
26+
let perksetsElm = findSection(dom, "My Perk Sets")
1627
if (perksetsElm === null) {
1728
// Non-beta/alpha user, leave the system disabled.
18-
return {perksets: null, currentPerkset: null}
29+
return {perksets: null, currentPerkset: null, perks}
1930
}
2031
const perksets = {}
2132
let currentPerkset = null
@@ -28,7 +39,7 @@ const parsePerks = (page, url) => {
2839
currentPerkset = name
2940
}
3041
}
31-
return {perksets, currentPerkset}
42+
return {perksets, currentPerkset, perks}
3243
}
3344

3445
export const imageForPerkset = async (state, name) => {
@@ -64,8 +75,12 @@ const visitPerks = async (state, page, url) => {
6475
for (const name in perks.perksets) {
6576
perks.perksets[name].image = await imageForPerkset(state, name, perks.perksets[name].id)
6677
}
78+
state.player.perks = perks.perks
6779
state.player.perksets = perks.perksets
6880
state.player.currentPerkset = perks.currentPerkset
81+
if (perks.currentPerkset !== null) {
82+
state.player.perksByPerkset[perks.currentPerkset] = perks.perks
83+
}
6984
await state.player.save(state.db)
7085
}
7186

@@ -78,13 +93,14 @@ const visitActivatePerkSet = async (state, page, url) => {
7893
const parsedUrl = new URL(url)
7994
const id = parsedUrl.searchParams.get("id")
8095
for (const name in state.player.perksets) {
81-
if (state.player.perksets[name] === id) {
96+
if (state.player.perksets[name].id === id) {
8297
state.player.currentPerkset = name
98+
state.player.perks = state.player.perksByPerkset[name] || {}
8399
await state.player.save(state.db)
84100
return
85101
}
86102
}
87-
console.log.error(`Unable to find perkset name for id ${id}`)
103+
console.error(`Unable to find perkset name for id ${id}`)
88104
}
89105

90106
const nextPerkset = state => {
@@ -131,15 +147,32 @@ const clickPerk = async state => {
131147
throw "Error activating perkset"
132148
}
133149
state.player.currentPerkset = next.name
150+
state.player.perks = state.player.perksByPerkset[next.name] || {}
134151
await state.player.save(state.db)
135152
state.perksetLoading = false
136153
await renderSidebar(state)
137154
}
138155

156+
const parseFarmSupply = (page, url, parsedUrl) => {
157+
const parser = new DOMParser()
158+
const dom = parser.parseFromString(page, "text/html")
159+
return parsePerkList(dom, true)
160+
}
161+
162+
const visitFarmSupply = async (state, page, url, parsedUrl) => {
163+
const perks = parseFarmSupply(page, url, parsedUrl)
164+
console.debug("farmSupply", perks)
165+
if (perks !== null) {
166+
state.player.farmSupply = perks
167+
await state.player.save(state.db)
168+
}
169+
}
170+
139171
export const setupPerks = state => {
140172
state.addPageHandler("perks", visitPerks)
141173
state.addWorkerHandler("activateperkset", visitActivatePerkSet)
142174
state.addClickHandler("perk", clickPerk)
175+
state.addPageHandler("supply", visitFarmSupply)
143176
}
144177

145178
export const fetchPerks = async state => {

lib/player.js

+14
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ class Player {
1010
this.currentPerkset = null
1111
this.perksets = null
1212
this.settings = {}
13+
this.perks = {}
14+
this.farmSupply = {}
15+
this.perksByPerkset = {}
16+
this.orchard = null
1317
}
1418

1519
async load(db) {
@@ -22,6 +26,16 @@ class Player {
2226
async save(db) {
2327
await db.put("player", this)
2428
}
29+
30+
perkValue(perks) {
31+
let total = 0
32+
for (const [key, value] of Object.entries(perks)) {
33+
if (this.perks[key] || this.farmSupply[key]) {
34+
total += value
35+
}
36+
}
37+
return total
38+
}
2539
}
2640

2741
export const setupPlayer = async state => {

lib/production.js

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { renderPage, renderListBlock, renderRowCheckbox, renderRowText } from "./html.js"
2+
3+
const renderProductionRow = async (state, itemName, quantity, overflow, link) => {
4+
const item = await state.items.get(itemName)
5+
return `
6+
<li class="farmrpg-ext-production-row">
7+
<div class="item-content">
8+
<div class="item-media">
9+
<a href="item.php?id=${item.id}">
10+
<img class="itemimg" src="${item.image}">
11+
</a>
12+
</div>
13+
<div class="item-inner">
14+
<div class="item-title">
15+
<strong>${item.name}</strong>
16+
</div>
17+
<div class="item-after">
18+
<span class="farmrpg-ext-production-overflow">${overflow}</span>
19+
${quantity - overflow}
20+
</div>
21+
</div>
22+
</div>
23+
</li>
24+
`
25+
}
26+
27+
const renderDaily = async state => {
28+
const rollover = luxon.DateTime.fromObject({}, {zone: "America/Chicago"}).startOf("day").plus({day: 1})
29+
let delta = rollover.diffNow().shiftTo("hours", "minutes").normalize()
30+
if (delta.hours == 0) {
31+
delta = delta.shiftTo("minutes").normalize()
32+
}
33+
34+
const rows = []
35+
// Orchard production.
36+
if (state.player.orchard !== null) {
37+
let multiplier = 1 + state.player.perkValue({
38+
"Forester I": 0.05,
39+
"Forester II": 0.1,
40+
"Forester III": 0.05,
41+
"Forester IV": 0.1,
42+
})
43+
44+
const appleProduction = Math.min(Math.round(state.player.orchard.Apple * multiplier), state.player.maxInventory)
45+
const appleOverflow = Math.max(state.player.inventory.Apple + appleProduction - state.player.maxInventory, 0)
46+
rows.push(await renderProductionRow(state, "Apple", appleProduction, appleOverflow, "orchard.php"))
47+
48+
const orangeProduction = Math.min(Math.round(state.player.orchard.Orange * multiplier), state.player.maxInventory)
49+
const orangeOverflow = Math.max(state.player.inventory.Orange + orangeProduction - state.player.maxInventory, 0)
50+
rows.push(await renderProductionRow(state, "Orange", orangeProduction, orangeOverflow, "orchard.php"))
51+
52+
const lemonProduction = Math.min(Math.round(state.player.orchard.Lemon * multiplier), state.player.maxInventory)
53+
const lemonOverflow = Math.max(state.player.inventory.Lemon + lemonProduction - state.player.maxInventory, 0)
54+
rows.push(await renderProductionRow(state, "Lemon", lemonProduction, lemonOverflow, "orchard.php"))
55+
}
56+
57+
return renderListBlock("Daily Production", {}, [
58+
renderRowText(`In ${delta.toHuman({maximumFractionDigits: 0})}, you will generate:`),
59+
...rows
60+
])
61+
}
62+
63+
const renderProduction = async state => {
64+
const html = renderPage("Production", "production", [
65+
await renderDaily(state),
66+
])
67+
state.postMessage({action: "LOAD_CONTENT", pageName: "farmrpg-ext-production", html})
68+
}
69+
70+
export const setupProduction = state => {
71+
state.addClickHandler("production", renderProduction)
72+
}

0 commit comments

Comments
 (0)