feature: praesi2

This commit is contained in:
Hikyu
2026-07-02 17:27:21 +02:00
parent aa3e89407e
commit 13154fbb53
34 changed files with 2179 additions and 94 deletions
+34
View File
@@ -0,0 +1,34 @@
# dependencies (bun install)
node_modules
# output
out
dist
*.tgz
# code coverage
coverage
*.lcov
# logs
logs
_.log
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# caches
.eslintcache
.cache
*.tsbuildinfo
# IntelliJ based IDEs
.idea
# Finder (MacOS) folder config
.DS_Store
+110
View File
@@ -0,0 +1,110 @@
Default to using Bun instead of Node.js.
- Use `bun <file>` instead of `node <file>` or `ts-node <file>`
- Use `bun test` instead of `jest` or `vitest`
- Use `bun build <file.html|file.ts|file.css>` instead of `webpack` or `esbuild`
- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install`
- Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>` or `pnpm run <script>`
- Bun automatically loads .env, so don't use dotenv.
## APIs
- `Bun.serve()` supports WebSockets, HTTPS, and routes. Don't use `express`.
- `bun:sqlite` for SQLite. Don't use `better-sqlite3`.
- `Bun.redis` for Redis. Don't use `ioredis`.
- `Bun.sql` for Postgres. Don't use `pg` or `postgres.js`.
- `WebSocket` is built-in. Don't use `ws`.
- Prefer `Bun.file` over `node:fs`'s readFile/writeFile
- Bun.$`ls` instead of execa.
## Testing
Use `bun test` to run tests.
```ts#index.test.ts
import { test, expect } from "bun:test";
test("hello world", () => {
expect(1).toBe(1);
});
```
## Frontend
Use HTML imports with `Bun.serve()`. Don't use `vite`. HTML imports fully support React, CSS, Tailwind.
Server:
```ts#index.ts
import index from "./index.html"
Bun.serve({
routes: {
"/": index,
"/api/users/:id": {
GET: (req) => {
return new Response(JSON.stringify({ id: req.params.id }));
},
},
},
// optional websocket support
websocket: {
open: (ws) => {
ws.send("Hello, world!");
},
message: (ws, message) => {
ws.send(message);
},
close: (ws) => {
// handle close
}
},
development: {
hmr: true,
console: true,
}
})
```
HTML files can import .tsx, .jsx or .js files directly and Bun's bundler will transpile & bundle automatically. `<link>` tags can point to stylesheets and Bun's CSS bundler will bundle.
```html#index.html
<html>
<body>
<h1>Hello, world!</h1>
<script type="module" src="./frontend.tsx"></script>
</body>
</html>
```
With the following `frontend.tsx`:
```tsx#frontend.tsx
import React from "react";
// import .css files directly and it works
import './index.css';
import { createRoot } from "react-dom/client";
const root = createRoot(document.body);
export default function Frontend() {
return <h1>Hello, world!</h1>;
}
root.render(<Frontend />);
```
Then, run index.ts
```sh
bun --hot ./index.ts
```
For more information, read the Bun API docs in `node_modules/bun-types/docs/**.md`.
## Coding Style Preferences
- Write everything WITHOUT comments.
+8
View File
@@ -0,0 +1,8 @@
# Projekt-Dokumentation: Entwicklungsreihenfolge
1. Projekt init
2. Docker compose mit redis
3. Einfache verbindung
4. Redis getestet (mit set, get und incr)
5. Init-Skript (db/init.ts) mit ~100 statischen Entitäten erstellt (für reproduzierbare Tests)
6. Client (index.ts) aktualisiert Player-Daten live und liest statische Gegner-Daten
+15
View File
@@ -0,0 +1,15 @@
# client-redit
To install dependencies:
```bash
bun install
```
To run:
```bash
bun run index.ts
```
This project was created using `bun init` in bun v1.3.3. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
+26
View File
@@ -0,0 +1,26 @@
{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "client-redit",
"devDependencies": {
"@types/bun": "latest",
},
"peerDependencies": {
"typescript": "^5",
},
},
},
"packages": {
"@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
"@types/node": ["@types/node@26.1.0", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-O0A1G3xPGy4w7AgQdAQYUlQ+BKk2Oovw8eRpofyp5KdBZULnbe+WqaOVNrm705SHphCiG4XHsACrSmPu1f+Kgw=="],
"bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="],
}
}
+210
View File
@@ -0,0 +1,210 @@
import { RedisClient } from "bun";
const redis = new RedisClient("redis://localhost:40379");
console.log("Populating database with ~100 static Ringstats entries...");
const effects = [
{ id: 1, name: "Stärke", value: 5 },
{ id: 2, name: "Geschick", value: 5 },
{ id: 3, name: "Blutung", value: 50 },
{ id: 4, name: "Gift", value: 45 },
{ id: 5, name: "Scharlachfäule", value: 50 },
{ id: 6, name: "Frost", value: 55 },
{ id: 7, name: "Heilung", value: 100 },
{ id: 8, name: "Magieschaden", value: 20 },
{ id: 9, name: "Feuerschaden", value: 30 },
{ id: 10, name: "Blitzschaden", value: 25 },
];
const types = [
{ id: 1, item_id: 101, name: "Dolch" },
{ id: 2, item_id: 102, name: "Gerades Schwert" },
{ id: 3, item_id: 103, name: "Großschwert" },
{ id: 4, item_id: 104, name: "Katana" },
{ id: 5, item_id: 105, name: "Helm" },
{ id: 6, item_id: 106, name: "Brustpanzer" },
{ id: 7, item_id: 107, name: "Stulpen" },
{ id: 8, item_id: 108, name: "Beinschutz" },
{ id: 9, item_id: 109, name: "Mittlerer Schild" },
{ id: 10, item_id: 110, name: "Talisman-Amulett" },
];
const weapons = [
{ id: 1, item_effect_id: 3, item_type_id: 4, weight: 5.5, damage: 115 }, // Uchigatana
{ id: 2, item_effect_id: 3, item_type_id: 4, weight: 6.5, damage: 118 }, // Rivers of Blood
{ id: 3, item_effect_id: 8, item_type_id: 3, weight: 10.0, damage: 160 }, // Dark Moon Greatsword
{ id: 4, item_effect_id: 1, item_type_id: 3, weight: 23.0, damage: 210 }, // Guts Greatsword
{ id: 5, item_effect_id: 3, item_type_id: 1, weight: 1.5, damage: 74 }, // Reduvia
{ id: 6, item_effect_id: 9, item_type_id: 2, weight: 4.0, damage: 105 }, // Blasphemous Blade
{ id: 7, item_effect_id: 10, item_type_id: 4, weight: 6.0, damage: 110 }, // Dragonscale Blade
{ id: 8, item_effect_id: 1, item_type_id: 3, weight: 15.0, damage: 175 }, // Claymore
{ id: 9, item_effect_id: 2, item_type_id: 1, weight: 2.0, damage: 80 }, // Misericorde
{ id: 10, item_effect_id: 6, item_type_id: 2, weight: 4.5, damage: 100 }, // Icerind Hatchet (simulated as straight sword for simplicity)
];
const armors = [
{ id: 1, item_effect_id: 1, item_type_id: 5, weight: 4.6, defense: 14 },
{ id: 2, item_effect_id: 1, item_type_id: 6, weight: 10.6, defense: 35 },
{ id: 3, item_effect_id: 1, item_type_id: 7, weight: 3.5, defense: 11 },
{ id: 4, item_effect_id: 1, item_type_id: 8, weight: 6.6, defense: 20 },
{ id: 5, item_effect_id: 8, item_type_id: 5, weight: 2.2, defense: 8 },
{ id: 6, item_effect_id: 8, item_type_id: 6, weight: 5.1, defense: 15 },
{ id: 7, item_effect_id: 9, item_type_id: 5, weight: 5.5, defense: 18 },
{ id: 8, item_effect_id: 9, item_type_id: 6, weight: 11.8, defense: 42 },
{ id: 9, item_effect_id: 10, item_type_id: 5, weight: 3.0, defense: 10 },
{ id: 10, item_effect_id: 10, item_type_id: 6, weight: 7.0, defense: 22 },
];
const shields = [
{ id: 1, item_effect_id: 1, item_type_id: 9, weight: 3.5, damage: 45, defense: 100 },
{ id: 2, item_effect_id: 8, item_type_id: 9, weight: 4.5, damage: 55, defense: 100 },
{ id: 3, item_effect_id: 9, item_type_id: 9, weight: 5.5, damage: 65, defense: 100 },
{ id: 4, item_effect_id: 10, item_type_id: 9, weight: 4.0, damage: 50, defense: 100 },
{ id: 5, item_effect_id: 6, item_type_id: 9, weight: 3.0, damage: 40, defense: 95 },
];
const talismans = [
{ id: 1, item_effect_id: 1 },
{ id: 2, item_effect_id: 2 },
{ id: 3, item_effect_id: 7 },
{ id: 4, item_effect_id: 8 },
{ id: 5, item_effect_id: 9 },
];
const items = [
// Weapons (id 1-10)
{ id: 1, name: "Uchigatana", gear_type: "weapon", gear_id: 1 },
{ id: 2, name: "Rivers of Blood", gear_type: "weapon", gear_id: 2 },
{ id: 3, name: "Dark Moon Greatsword", gear_type: "weapon", gear_id: 3 },
{ id: 4, name: "Guts Greatsword", gear_type: "weapon", gear_id: 4 },
{ id: 5, name: "Reduvia", gear_type: "weapon", gear_id: 5 },
{ id: 6, name: "Blasphemous Blade", gear_type: "weapon", gear_id: 6 },
{ id: 7, name: "Dragonscale Blade", gear_type: "weapon", gear_id: 7 },
{ id: 8, name: "Claymore", gear_type: "weapon", gear_id: 8 },
{ id: 9, name: "Misericorde", gear_type: "weapon", gear_id: 9 },
{ id: 10, name: "Icerind Hatchet", gear_type: "weapon", gear_id: 10 },
// Armors (id 11-20)
{ id: 11, name: "Knight Helm", gear_type: "armor", gear_id: 1 },
{ id: 12, name: "Knight Armor", gear_type: "armor", gear_id: 2 },
{ id: 13, name: "Knight Gauntlets", gear_type: "armor", gear_id: 3 },
{ id: 14, name: "Knight Greaves", gear_type: "armor", gear_id: 4 },
{ id: 15, name: "Astrologer Hood", gear_type: "armor", gear_id: 5 },
{ id: 16, name: "Astrologer Robe", gear_type: "armor", gear_id: 6 },
{ id: 17, name: "Radahn Helm", gear_type: "armor", gear_id: 7 },
{ id: 18, name: "Radahn Lion Armor", gear_type: "armor", gear_id: 8 },
{ id: 19, name: "Carian Knight Helm", gear_type: "armor", gear_id: 9 },
{ id: 20, name: "Carian Knight Armor", gear_type: "armor", gear_id: 10 },
// Shields (id 21-25)
{ id: 21, name: "Brass Shield", gear_type: "shield", gear_id: 1 },
{ id: 22, name: "Carian Knight Shield", gear_type: "shield", gear_id: 2 },
{ id: 23, name: "Kite Shield", gear_type: "shield", gear_id: 3 },
{ id: 24, name: "Banished Knight Shield", gear_type: "shield", gear_id: 4 },
{ id: 25, name: "Heater Shield", gear_type: "shield", gear_id: 5 },
// Talismans (id 26-30)
{ id: 26, name: "Erdtree's Favor", gear_type: "talisman", gear_id: 1 },
{ id: 27, name: "Radagon's Soreseal", gear_type: "talisman", gear_id: 2 },
{ id: 28, name: "Crimson Amber Medallion", gear_type: "talisman", gear_id: 3 },
{ id: 29, name: "Graven-Mass Talisman", gear_type: "talisman", gear_id: 4 },
{ id: 30, name: "Fire Scorpion Charm", gear_type: "talisman", gear_id: 5 },
{ id: 31, name: "Crimson Flask", gear_type: "consumable" },
{ id: 32, name: "Cerulean Flask", gear_type: "consumable" },
{ id: 33, name: "Rune Arc", gear_type: "consumable" },
{ id: 34, name: "Golden Rune [1]", gear_type: "consumable" },
{ id: 35, name: "Golden Rune [5]", gear_type: "consumable" },
{ id: 36, name: "Golden Rune [10]", gear_type: "consumable" },
{ id: 37, name: "Hero's Rune [1]", gear_type: "consumable" },
{ id: 38, name: "Lord's Rune", gear_type: "consumable" },
{ id: 39, name: "Remembrance of the Grafted", gear_type: "consumable" },
{ id: 40, name: "Remembrance of the Full Moon Queen", gear_type: "consumable" },
{ id: 41, name: "Remembrance of the Starscourge", gear_type: "consumable" },
{ id: 42, name: "Remembrance of the Omen King", gear_type: "consumable" },
{ id: 43, name: "Remembrance of the Fire Giant", gear_type: "consumable" },
{ id: 44, name: "Remembrance of the Black Blade", gear_type: "consumable" },
{ id: 45, name: "Smithing Stone [1]", gear_type: "material" },
{ id: 46, name: "Smithing Stone [4]", gear_type: "material" },
{ id: 47, name: "Somber Smithing Stone [1]", gear_type: "material" },
{ id: 48, name: "Somber Smithing Stone [7]", gear_type: "material" },
{ id: 49, name: "Glovewort [1]", gear_type: "material" },
{ id: 50, name: "Ghost Glovewort [9]", gear_type: "material" },
];
const regions = [
{ id: 1, name: "Limgrave", items: ["1", "8", "21", "26"], enemies: ["1", "2", "3", "11", "12", "13", "14"] },
{ id: 2, name: "Liurnia of the Lakes", items: ["3", "15", "16", "19", "20", "22", "29"], enemies: ["4", "5", "15", "16", "17", "18"] },
{ id: 3, name: "Caelid", items: ["4", "17", "18"], enemies: ["6", "7", "19", "20", "21", "22"] },
{ id: 4, name: "Altus Plateau", items: ["2", "23", "24"], enemies: ["8", "23", "24", "25"] },
{ id: 5, name: "Mountaintops of the Giants", items: ["10"], enemies: ["9", "26", "27", "28"] },
{ id: 6, name: "Farum Azula", items: ["6", "25", "30"], enemies: ["10", "29", "30"] },
{ id: 7, name: "Weeping Peninsula", items: [], enemies: [] },
{ id: 8, name: "Siofra River", items: [], enemies: [] },
{ id: 9, name: "Ainsel River", items: [], enemies: [] },
{ id: 10, name: "Deeproot Depths", items: [], enemies: [] },
{ id: 11, name: "Lake of Rot", items: [], enemies: [] },
{ id: 12, name: "Consecrated Snowfield", items: [], enemies: [] },
{ id: 13, name: "Miquella's Haligtree", items: [], enemies: [] },
{ id: 14, name: "Mohgwyn Palace", items: [], enemies: [] },
{ id: 15, name: "Leyndell, Royal Capital", items: [], enemies: [] },
{ id: 16, name: "Leyndell, Ashen Capital", items: [], enemies: [] },
];
const enemies = [
{ id: 1, region_id: 1, name: "Tree Sentinel", health: 3000, level: 20, damage: 70, items: ["1", "47", "34"] },
{ id: 2, region_id: 1, name: "Margit, the Fell Omen", health: 4100, level: 25, damage: 100, items: ["26", "33", "34"] },
{ id: 3, region_id: 1, name: "Godrick the Grafted", health: 6000, level: 30, damage: 150, items: ["33", "39"] },
{ id: 4, region_id: 2, name: "Rennala, Queen of the Full Moon", health: 5000, level: 40, damage: 200, items: ["15", "16", "33", "40"] },
{ id: 5, region_id: 2, name: "Red Wolf of Radagon", health: 3000, level: 35, damage: 120, items: ["29", "35", "46"] },
{ id: 6, region_id: 3, name: "Starscourge Radahn", health: 10000, level: 60, damage: 400, items: ["17", "18", "33", "41"] },
{ id: 7, region_id: 3, name: "Commander O'Neil", health: 7000, level: 50, damage: 250, items: ["33", "35"] },
{ id: 8, region_id: 4, name: "Morgott, the Omen King", health: 12000, level: 70, damage: 500, items: ["33", "42"] },
{ id: 9, region_id: 5, name: "Fire Giant", health: 25000, level: 90, damage: 800, items: ["30", "33", "43"] },
{ id: 10, region_id: 6, name: "Maliketh, the Black Blade", health: 18000, level: 110, damage: 1000, items: ["33", "44"] },
{ id: 11, region_id: 1, name: "Godrick Soldier", health: 150, level: 5, damage: 20, items: ["21", "34", "45"] },
{ id: 12, region_id: 1, name: "Wandering Noble", health: 80, level: 2, damage: 10, items: ["31", "34"] },
{ id: 13, region_id: 1, name: "Limgrave Knight", health: 350, level: 12, damage: 40, items: ["11", "12", "13", "14", "23", "45"] },
{ id: 14, region_id: 1, name: "Demi-Human", health: 120, level: 4, damage: 15, items: ["34", "45"] },
{ id: 15, region_id: 2, name: "Raya Lucaria Soldier", health: 200, level: 15, damage: 30, items: ["22", "34", "46"] },
{ id: 16, region_id: 2, name: "Marionette Soldier", health: 180, level: 16, damage: 25, items: ["45", "49"] },
{ id: 17, region_id: 2, name: "Albinauric", health: 250, level: 18, damage: 35, items: ["34", "35"] },
{ id: 18, region_id: 2, name: "Carian Knight", health: 600, level: 28, damage: 80, items: ["19", "20", "22", "47"] },
{ id: 19, region_id: 3, name: "Radahn Soldier", health: 300, level: 25, damage: 45, items: ["35", "46"] },
{ id: 20, region_id: 3, name: "Monstrous Dog", health: 800, level: 32, damage: 110, items: ["35", "49"] },
{ id: 21, region_id: 3, name: "Monstrous Crow", health: 750, level: 33, damage: 120, items: ["35", "49"] },
{ id: 22, region_id: 3, name: "Cleanrot Knight", health: 900, level: 38, damage: 100, items: ["36", "48"] },
{ id: 23, region_id: 4, name: "Leyndell Soldier", health: 400, level: 40, damage: 60, items: ["36", "46"] },
{ id: 24, region_id: 4, name: "Leyndell Knight", health: 1200, level: 48, damage: 140, items: ["24", "36", "48"] },
{ id: 25, region_id: 4, name: "Omen", health: 1500, level: 50, damage: 180, items: ["36", "48", "33"] },
{ id: 26, region_id: 5, name: "Fire Monk", health: 800, level: 55, damage: 120, items: ["36", "48"] },
{ id: 27, region_id: 5, name: "Giant Dog", health: 1100, level: 60, damage: 160, items: ["36"] },
{ id: 28, region_id: 5, name: "Troll", health: 2200, level: 65, damage: 220, items: ["37", "48"] },
{ id: 29, region_id: 6, name: "Beastman of Farum Azula", health: 1000, level: 80, damage: 200, items: ["37", "48"] },
{ id: 30, region_id: 6, name: "Banished Knight", health: 2000, level: 85, damage: 280, items: ["24", "37", "48", "50"] },
];
// Helper for hset
const setHash = async (prefix: string, list: any[]) => {
for (const obj of list) {
// extract items/enemies sets if they exist
const { items, enemies, ...hashData } = obj;
await redis.hset(`${prefix}:${obj.id}`, hashData);
if (items && items.length > 0) {
await redis.sadd(`${prefix}:${obj.id}:items`, ...items);
}
if (enemies && enemies.length > 0) {
await redis.sadd(`${prefix}:${obj.id}:enemies`, ...enemies);
}
}
};
await setHash("item_effect", effects);
await setHash("item_type", types);
await setHash("weapon", weapons);
await setHash("armor", armors);
await setHash("shield", shields);
await setHash("talisman", talismans);
await setHash("item", items);
await setHash("region", regions);
await setHash("enemy", enemies);
console.log(`Inserted ${effects.length + types.length + weapons.length + armors.length + shields.length + talismans.length + items.length + regions.length + enemies.length} primary entities into Redis.`);
console.log("Database successfully seeded.");
process.exit(0);
@@ -0,0 +1,6 @@
services:
redis:
image: redis:alpine
ports:
- "40379:6379"
restart: always
+144
View File
@@ -0,0 +1,144 @@
import { RedisClient } from "bun";
const redis = new RedisClient("redis://localhost:40379");
const colors = {
reset: "\x1b[0m",
cyan: "\x1b[36m",
green: "\x1b[32m",
yellow: "\x1b[33m",
magenta: "\x1b[35m",
red: "\x1b[31m",
gray: "\x1b[90m",
bold: "\x1b[1m",
};
let totalRequests = 0;
function log(msg: string, color: string = colors.reset) {
const timestamp = new Date().toISOString().split('T')[1].slice(0, -1);
console.log(`${colors.gray}[${timestamp}]${colors.reset} ${color}${msg}${colors.reset}`);
}
const introMessage = "Starting Ringstats Desktop Application...";
log(introMessage, colors.cyan);
const pingStart = performance.now();
await redis.ping();
totalRequests++;
log(`Connected to Ringstats Database successfully. (Reqs: 1 | Total: ${totalRequests} | Time: ${(performance.now() - pingStart).toFixed(2)}ms)`, colors.green);
log("Looking for Elden Ring process...", colors.gray);
await Bun.sleep(1500);
log("Found Elden Ring process!", colors.green);
log("Equipping initial gear...", colors.gray);
await redis.del("player:1:weapon", "player:1:armor", "player:1:talisman");
await redis.sadd("player:1:weapon", "1");
await redis.sadd("player:1:armor", "11");
await redis.sadd("player:1:talisman", "26");
totalRequests += 4;
let playerHealth = 100;
let playerStamina = 100;
let playerLevel = 1;
let currentRegionId = 1;
log("Entering live sync loop (press Ctrl+C to exit)...", colors.gray);
while (true) {
playerHealth = Math.floor(Math.max(10, playerHealth - (Math.random() * 10) + 5));
playerStamina = Math.floor(Math.max(10, playerStamina - (Math.random() * 20) + 10));
if (Math.random() > 0.9) {
playerLevel += 1;
}
if (Math.random() > 0.85) {
currentRegionId = Math.floor(Math.random() * 16) + 1;
const startTravel = performance.now();
const regionName = await redis.hget(`region:${currentRegionId}`, "name");
const regionEnemiesIds = await redis.smembers(`region:${currentRegionId}:enemies`);
const regionItemsIds = await redis.smembers(`region:${currentRegionId}:items`);
const enemyNames = await Promise.all(regionEnemiesIds.map(id => redis.hget(`enemy:${id}`, "name")));
const itemNames = await Promise.all(regionItemsIds.map(id => redis.hget(`item:${id}`, "name")));
const queries = 3 + regionEnemiesIds.length + regionItemsIds.length;
totalRequests += queries;
const travelTime = (performance.now() - startTravel).toFixed(2);
console.log();
log(`>>> Fast Traveled to: [ID: ${currentRegionId}] ${regionName} <<<`, colors.magenta + colors.bold);
log(`[Region Data] Enemies nearby: ${enemyNames.join(", ") || "None"}`, colors.magenta);
log(`[Region Data] Items nearby: ${itemNames.join(", ") || "None"}`, colors.magenta);
log(`[Stats] Query Time: ${travelTime}ms | Reqs in Block: ${queries} | Total Reqs: ${totalRequests}`, colors.gray);
console.log();
}
if (Math.random() > 0.90) { // 10% chance to swap gear
const newWeaponId = Math.floor(Math.random() * 10) + 1; // 1 to 10
const newArmorId = Math.floor(Math.random() * 10) + 11; // 11 to 20
const newTalismanId = Math.floor(Math.random() * 5) + 26; // 26 to 30
const startSwap = performance.now();
await redis.del("player:1:weapon", "player:1:armor", "player:1:talisman");
await redis.sadd("player:1:weapon", newWeaponId.toString());
await redis.sadd("player:1:armor", newArmorId.toString());
await redis.sadd("player:1:talisman", newTalismanId.toString());
const weaponName = await redis.hget(`item:${newWeaponId}`, "name");
const armorName = await redis.hget(`item:${newArmorId}`, "name");
const talismanName = await redis.hget(`item:${newTalismanId}`, "name");
totalRequests += 7; // 1 del + 3 sadd + 3 hget
const swapTime = (performance.now() - startSwap).toFixed(2);
console.log();
log(`>>> Gear Swapped <<<`, colors.cyan + colors.bold);
log(`Equipped: ${weaponName} | ${armorName} | ${talismanName}`, colors.cyan);
log(`[Stats] Query Time: ${swapTime}ms | Reqs in Block: 7 | Total Reqs: ${totalRequests}`, colors.gray);
console.log();
}
const startSync = performance.now();
await redis.hset("player:1", {
id: 1,
name: "Jonas",
health: playerHealth,
stamina: playerStamina,
level: playerLevel,
region_id: currentRegionId
});
totalRequests += 1;
const syncTime = (performance.now() - startSync).toFixed(2);
log(`[Live Sync] [ID: 1] Jonas - Lvl: ${playerLevel} | HP: ${playerHealth} | Stamina: ${playerStamina}`, colors.green);
log(`[Stats] Query Time: ${syncTime}ms | Reqs in Block: 1 | Total Reqs: ${totalRequests}`, colors.gray);
if (Math.random() > 0.7) {
const startLock = performance.now();
const randomEnemyId = await redis.srandmember(`region:${currentRegionId}:enemies`);
totalRequests += 1;
if (randomEnemyId) {
console.log();
log("--- Target Lock Detected ---", colors.red + colors.bold);
const enemyInfo = await redis.hgetall(`enemy:${randomEnemyId}`);
const dropItemIds = await redis.smembers(`enemy:${randomEnemyId}:items`);
const dropItemNames = await Promise.all(dropItemIds.map(id => redis.hget(`item:${id}`, "name")));
const queries = 2 + dropItemIds.length;
totalRequests += queries;
const lockTime = (performance.now() - startLock).toFixed(2);
log(`Overlay -> [ID: ${enemyInfo.id}] ${enemyInfo.name} | Lvl: ${enemyInfo.level} | HP: ${enemyInfo.health} | DMG: ${enemyInfo.damage}`, colors.yellow);
log(`Drops -> ${dropItemNames.length > 0 ? dropItemNames.join(", ") : "None"}`, colors.yellow);
log(`[Stats] Query Time: ${lockTime}ms | Reqs in Block: ${queries + 1} | Total Reqs: ${totalRequests}`, colors.gray);
console.log();
}
}
await Bun.sleep(2000);
}
+12
View File
@@ -0,0 +1,12 @@
{
"name": "client-redit",
"module": "index.ts",
"type": "module",
"private": true,
"devDependencies": {
"@types/bun": "latest"
},
"peerDependencies": {
"typescript": "^5"
}
}
+29
View File
@@ -0,0 +1,29 @@
{
"compilerOptions": {
// Environment setup & latest features
"lib": ["ESNext"],
"target": "ESNext",
"module": "Preserve",
"moduleDetection": "force",
"jsx": "react-jsx",
"allowJs": true,
// Bundler mode
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
// Best practices
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false
}
}