screepsmod-market-deals-seeder 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +48 -0
  3. package/index.js +174 -0
  4. package/package.json +33 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 TuEye
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # screepsmod-market-deals-seeder
2
+ Seeds synthetic market deals to bootstrap market history on Screeps private servers.
3
+
4
+ ## What it does
5
+ This mod inserts synthetic `market.sell` deals into `users.money` using average prices from
6
+ active `market.orders`. It helps new private servers show a market history immediately.
7
+
8
+ ## Install
9
+ ```bash
10
+ npm install screepsmod-market-deals-seeder
11
+ ```
12
+
13
+ ## Enable in Screeps server
14
+ Add the mod to your server config (example `config.json`):
15
+ ```json
16
+ {
17
+ "mods": [
18
+ "screepsmod-market-deals-seeder"
19
+ ]
20
+ }
21
+ ```
22
+
23
+ If you keep it locally, you can also use a relative or absolute path in `mods`.
24
+
25
+ ## Configuration
26
+ All settings are inside `index.js` and can be edited directly.
27
+ The mod entry point is `module.exports = function(config)` and uses
28
+ `config.common.storage.db`.
29
+
30
+ Defaults:
31
+ - `DAYS` = 14
32
+ - `MIN_DEALS_PER_DAY` = 10
33
+ - `AMOUNT_PER_DEAL` = 1000
34
+ - `SEED_TAG` = `market-deals-seed`
35
+ - `USER_ID` = `system`
36
+ - `RUN_EVERY_MS` = 12 hours
37
+ - `PRICE_SCALE` = 1000 (use 1 if your orders are full credits)
38
+ - `COUNT_ALL_DEALS` = true (count real + seeded deals)
39
+
40
+ Environment:
41
+ - `MARKET_SEED_BLACKLIST` = comma separated list of resources to exclude
42
+
43
+ ## Notes
44
+ - The mod waits for `market.orders` and `users.money` to exist before seeding.
45
+ - It runs once at startup and then periodically.
46
+
47
+ ## Publishing to GitHub Packages
48
+ Publish to npmjs with `npm publish`.
package/index.js ADDED
@@ -0,0 +1,174 @@
1
+ 'use strict';
2
+
3
+ module.exports = function(config) {
4
+ const db = config && config.common && config.common.storage && config.common.storage.db;
5
+ if (!db) {
6
+ console.log('[market-deals-seed] ERROR: config.common.storage.db not available');
7
+ return;
8
+ }
9
+
10
+ // ---- Settings ----
11
+ var DAYS = 14;
12
+ var MIN_DEALS_PER_DAY = 10;
13
+ var AMOUNT_PER_DEAL = 1000;
14
+ var SEED_TAG = 'market-deals-seed';
15
+ var USER_ID = 'system'; // not a real player
16
+ var RUN_EVERY_MS = 12 * 60 * 60 * 1000; // 12h
17
+ var PRICE_SCALE = 1000; // if Orders are milli-Credits
18
+ var COUNT_ALL_DEALS = true; // Default: all deals (real + seeded) count
19
+ // Resources that are NOT seeded (default: empty) e.g. ['energy', 'G', 'X']
20
+ var BLACKLIST = (process.env.MARKET_SEED_BLACKLIST || '')
21
+ .split(',')
22
+ .map(function(s){ return s.trim(); })
23
+ .filter(Boolean);
24
+
25
+ function dayWindowLocal(daysAgo) {
26
+ var now = new Date();
27
+ var base = new Date(now.getFullYear(), now.getMonth(), now.getDate() - daysAgo, 0, 0, 0, 0);
28
+ var start = new Date(base.getTime()); start.setHours(10,0,0,0);
29
+ var end = new Date(base.getTime()); end.setHours(18,0,0,0);
30
+ return { start: start, end: end };
31
+ }
32
+
33
+ function randomDateBetween(start, end) {
34
+ var t = start.getTime() + Math.floor(Math.random() * (end.getTime() - start.getTime() + 1));
35
+ return new Date(t);
36
+ }
37
+
38
+ function safeNumber(n, fallback) {
39
+ return (typeof n === 'number' && isFinite(n)) ? n : fallback;
40
+ }
41
+
42
+ function hasCollections() {
43
+ return !!(db && db['market.orders'] && db['users.money']);
44
+ }
45
+
46
+ async function calcAvgPricesFromOrders() {
47
+ if (!db['market.orders']) return {};
48
+
49
+ var orders = await db['market.orders'].find({ active: true }).catch(function() { return []; });
50
+ var mp = {}; // rt -> [prices]
51
+
52
+ (orders || []).forEach(function(o) {
53
+ if (!o || !o.resourceType) return;
54
+ var p = o.price;
55
+ if (typeof p !== 'number' || !isFinite(p)) return;
56
+ if (!mp[o.resourceType]) mp[o.resourceType] = [];
57
+ mp[o.resourceType].push(p);
58
+ });
59
+
60
+ var avg = {};
61
+ Object.keys(mp).forEach(function(rt) {
62
+ var arr = mp[rt];
63
+ if (!arr || !arr.length) return;
64
+ var sum = arr.reduce(function(a, b) { return a + b; }, 0);
65
+ avg[rt] = sum / arr.length;
66
+ });
67
+
68
+ return avg;
69
+ }
70
+
71
+ async function countDeals(rt, start, end) {
72
+ if (!db['users.money']) return 0;
73
+
74
+ var andParts = [
75
+ { type: 'market.sell' },
76
+ { 'market.resourceType': rt },
77
+ { date: { $gte: start, $lte: end } }
78
+ ];
79
+
80
+ // If NOT all deals are to be counted, only count our seeded ones.
81
+ if (!COUNT_ALL_DEALS) {
82
+ andParts.push({ __seededBy: SEED_TAG });
83
+ }
84
+
85
+ var docs = await db['users.money'].find({ $and: andParts }).catch(function() { return []; });
86
+ return (docs || []).length;
87
+ }
88
+
89
+ async function insertSeedDeal(rt, avgPrice, start, end) {
90
+ var jitter = 1 + ((Math.random() - 0.5) * 0.10); // +/-5%
91
+ var raw = safeNumber(avgPrice, 1);
92
+ var price = (raw / PRICE_SCALE) * jitter;
93
+ var amount = AMOUNT_PER_DEAL;
94
+
95
+ var doc = {
96
+ user: USER_ID,
97
+ type: 'market.sell',
98
+ date: randomDateBetween(start, end),
99
+ change: amount * price, // Credits inflow
100
+ market: {
101
+ resourceType: rt,
102
+ amount: amount,
103
+ price: price
104
+ },
105
+ __seededBy: SEED_TAG
106
+ };
107
+
108
+ return db['users.money'].insert(doc);
109
+ }
110
+
111
+ async function seedOnce() {
112
+ var avgPrices = await calcAvgPricesFromOrders();
113
+ var rts = Object.keys(avgPrices);
114
+
115
+ if (BLACKLIST && BLACKLIST.length) {
116
+ rts = rts.filter(function(rt) { return BLACKLIST.indexOf(rt) === -1; });
117
+ }
118
+
119
+ if (!rts.length) {
120
+ console.log('[market-deals-seed] No avg prices found from active orders; nothing to seed.');
121
+ return { resources: 0, inserted: 0 };
122
+ }
123
+
124
+ var inserted = 0;
125
+
126
+ for (var ri = 0; ri < rts.length; ri++) {
127
+ var rt = rts[ri];
128
+ var avg = avgPrices[rt];
129
+
130
+ for (var d = 0; d < DAYS; d++) {
131
+ var range = dayWindowLocal(d);
132
+ var have = await countDeals(rt, range.start, range.end);
133
+ var need = Math.max(0, MIN_DEALS_PER_DAY - have);
134
+
135
+ for (var k = 0; k < need; k++) {
136
+ await insertSeedDeal(rt, avg, range.start, range.end);
137
+ inserted++;
138
+ }
139
+ }
140
+ }
141
+
142
+ console.log('[market-deals-seed] Done. resources=' + rts.length + ' inserted=' + inserted);
143
+ return { resources: rts.length, inserted: inserted };
144
+ }
145
+
146
+ function runWithRetry(attempt) {
147
+ attempt = attempt || 1;
148
+
149
+ if (!hasCollections()) {
150
+ if (attempt === 1) {
151
+ console.log('[market-deals-seed] waiting for collections: market.orders / users.money ...');
152
+ }
153
+ if (attempt <= 60) {
154
+ return setTimeout(function() { runWithRetry(attempt + 1); }, 1000);
155
+ }
156
+ console.log('[market-deals-seed] giving up after 60s; collections still not ready');
157
+ return;
158
+ }
159
+
160
+ seedOnce()
161
+ .then(function(r) { console.log('[market-deals-seed] startup seed ok', r); })
162
+ .catch(function(e) { console.log('[market-deals-seed] ERROR (startup seed)', e); });
163
+
164
+ // Periodically retrigger (if another job regularly recalculates/trims the stats)
165
+ setInterval(function() {
166
+ seedOnce().catch(function(e) { console.log('[market-deals-seed] ERROR (periodic seed)', e); });
167
+ }, RUN_EVERY_MS);
168
+
169
+ console.log('[market-deals-seed] periodic seeding scheduled every ' + (RUN_EVERY_MS/3600000) + 'h');
170
+ }
171
+
172
+ // Start
173
+ runWithRetry(1);
174
+ };
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "screepsmod-market-deals-seeder",
3
+ "version": "0.1.0",
4
+ "description": "Seeds synthetic market deals to bootstrap market history on Screeps private servers.",
5
+ "main": "index.js",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/TuEye/screepsmod-market-deals-seeder.git"
10
+ },
11
+ "keywords": [
12
+ "screeps",
13
+ "screepsmod",
14
+ "market",
15
+ "private-server",
16
+ "seed"
17
+ ],
18
+ "files": [
19
+ "index.js",
20
+ "README.md",
21
+ "LICENSE"
22
+ ],
23
+ "scripts": {
24
+ "lint": "eslint ."
25
+ },
26
+ "devDependencies": {
27
+ "eslint": "^8.57.0"
28
+ },
29
+ "engines": {
30
+ "node": ">=10"
31
+ },
32
+ "screeps_mod": true
33
+ }