XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESpaceNinjaServer

A simple server for a small space ninja game

公开
关注 0 Fork 0 Star 0
返回提交历史

XFEstudio/XFESpaceNinjaServer

feat(webui): add Mods card (#252)

f83f80c9
Sainan <sainan@calamity.gg>
提交于

代码差异

4 个文件 +282 -60
Modified src/controllers/custom/getItemListsController.ts +9 -2
@@ -1,16 +1,19 @@
1 1 import { RequestHandler } from "express";
2 2 import { MinItem, warframes, weapons, items } from "@/src/services/itemDataService";
3 import badItems from "@/static/json/exclude-mods.json";
3 4
4 5 interface ListedItem {
5 6 uniqueName: string;
6 7 name: string;
8 fusionLimit?: number;
7 9 }
8 10
9 11 function reduceItems(items: MinItem[]): ListedItem[] {
10 12 return items.map((item: MinItem): ListedItem => {
11 13 return {
12 14 uniqueName: item.uniqueName,
13 name: item.name
15 name: item.name,
16 fusionLimit: (item as any).fusionLimit
14 17 };
15 18 });
16 19 }
@@ -19,7 +22,11 @@ const getItemListsController: RequestHandler = (_req, res) => {
19 22 res.json({
20 23 warframes: reduceItems(warframes),
21 24 weapons: reduceItems(weapons.filter(item => item.productCategory != "OperatorAmps")),
22 miscitems: reduceItems(items.filter(item => item.category == "Misc" || item.category == "Resources"))
25 miscitems: reduceItems(
26 items.filter(item => item.category == "Misc" || item.category == "Resources" || item.category == "Fish")
27 ),
28 mods: reduceItems(items.filter(item => item.category == "Mods" || item.category == "Arcanes")),
29 badItems
23 30 });
24 31 };
25 32
Modified static/webui/index.html +80 -35
@@ -45,14 +45,30 @@
45 45 ></button>
46 46 </div>
47 47 <div class="offcanvas-body">
48 <ul>
49 <li>
50 <a href="/webui/inventory" data-bs-dismiss="offcanvas" data-bs-target="#sidebar"
51 >Inventory</a
52 >
53 </li>
54 <li><a href="/webui/mods" data-bs-dismiss="offcanvas" data-bs-target="#sidebar">Mods</a></li>
55 </ul>
48 <div class="navbar p-0">
49 <ul class="navbar-nav justify-content-end">
50 <li class="nav-item">
51 <a
52 class="nav-link"
53 href="/webui/inventory"
54 data-bs-dismiss="offcanvas"
55 data-bs-target="#sidebar"
56 >
57 Inventory
58 </a>
59 </li>
60 <li class="nav-item">
61 <a
62 class="nav-link"
63 href="/webui/mods"
64 data-bs-dismiss="offcanvas"
65 data-bs-target="#sidebar"
66 >
67 Mods
68 </a>
69 </li>
70 </ul>
71 </div>
56 72 </div>
57 73 </div>
58 74 <div>
@@ -117,33 +133,61 @@
117 133 </div>
118 134 </div>
119 135 <div data-route="/webui/mods" data-title="Mods | OpenWF WebUI">
120 <div class="card mb-4">
121 <h5 class="card-header">Add Riven</h5>
122 <form class="card-body" onsubmit="doAcquireRiven();return false;">
123 <select class="form-control mb-3" id="addriven-type">
124 <option value="LotusArchgunRandomModRare">LotusArchgunRandomModRare</option>
125 <option value="LotusModularMeleeRandomModRare">LotusModularMeleeRandomModRare</option>
126 <option value="LotusModularPistolRandomModRare">LotusModularPistolRandomModRare</option>
127 <option value="LotusPistolRandomModRare">LotusPistolRandomModRare</option>
128 <option value="LotusRifleRandomModRare" selected>LotusRifleRandomModRare</option>
129 <option value="LotusShotgunRandomModRare">LotusShotgunRandomModRare</option>
130 <option value="PlayerMeleeWeaponRandomModRare">PlayerMeleeWeaponRandomModRare</option>
131 </select>
132 <textarea
133 id="addriven-fingerprint"
134 class="form-control mb-3"
135 placeholder="Fingerprint"
136 ></textarea>
137 <button class="btn btn-primary" style="margin-right: 5px" type="submit">Add</button>
138 <a href="https://riven.builds.wf/" target="_blank">Need help with the fingerprint?</a>
139 </form>
140 </div>
141 <div class="card mb-4 col-xxl-6">
142 <h5 class="card-header">Rivens</h5>
143 <div class="card-body">
144 <table class="table table-hover w-100">
145 <tbody id="riven-list"></tbody>
146 </table>
136 <div class="row">
137 <div class="col-xxl-6">
138 <div class="card mb-4">
139 <h5 class="card-header">Add Riven</h5>
140 <form class="card-body" onsubmit="doAcquireRiven();return false;">
141 <select class="form-control mb-3" id="addriven-type">
142 <option value="LotusArchgunRandomModRare">LotusArchgunRandomModRare</option>
143 <option value="LotusModularMeleeRandomModRare">
144 LotusModularMeleeRandomModRare
145 </option>
146 <option value="LotusModularPistolRandomModRare">
147 LotusModularPistolRandomModRare
148 </option>
149 <option value="LotusPistolRandomModRare">LotusPistolRandomModRare</option>
150 <option value="LotusRifleRandomModRare" selected>
151 LotusRifleRandomModRare
152 </option>
153 <option value="LotusShotgunRandomModRare">LotusShotgunRandomModRare</option>
154 <option value="PlayerMeleeWeaponRandomModRare">
155 PlayerMeleeWeaponRandomModRare
156 </option>
157 </select>
158 <textarea
159 id="addriven-fingerprint"
160 class="form-control mb-3"
161 placeholder="Fingerprint"
162 ></textarea>
163 <button class="btn btn-primary" style="margin-right: 5px" type="submit">Add</button>
164 <a href="https://riven.builds.wf/" target="_blank">
165 Need help with the fingerprint?
166 </a>
167 </form>
168 </div>
169 <div class="card mb-4">
170 <h5 class="card-header">Rivens</h5>
171 <div class="card-body">
172 <table class="table table-hover w-100">
173 <tbody id="riven-list"></tbody>
174 </table>
175 </div>
176 </div>
177 </div>
178 <div class="col-xxl-6">
179 <div class="card mb-4">
180 <h5 class="card-header">Mods</h5>
181 <div class="card-body">
182 <table class="table table-hover w-100">
183 <tbody id="mods-list"></tbody>
184 </table>
185 <form class="input-group" onsubmit="doAcquireMod();return false;">
186 <input class="form-control" id="mod-to-acquire" list="datalist-mods" />
187 <button class="btn btn-primary" type="submit">Add</button>
188 </form>
189 </div>
190 </div>
147 191 </div>
148 192 </div>
149 193 </div>
@@ -152,6 +196,7 @@
152 196 <datalist id="datalist-warframes"></datalist>
153 197 <datalist id="datalist-weapons"></datalist>
154 198 <datalist id="datalist-miscitems"></datalist>
199 <datalist id="datalist-mods"></datalist>
155 200 <script src="libs/jquery-3.6.0.min.js"></script>
156 201 <script src="libs/whirlpool-js.min.js"></script>
157 202 <script src="libs/single.js"></script>
Modified static/webui/script.js +191 -20
@@ -76,33 +76,47 @@ single.on("route_load", function (event) {
76 76 } else {
77 77 $("body").removeClass("logged-in");
78 78 }
79
80 $(".nav-link").removeClass("active");
81 const navLink = document.querySelector(".nav-link[href='" + event.route.paths[0] + "']");
82 if (navLink) {
83 navLink.classList.add("active");
84 }
79 85 });
80 86
81 87 window.itemListPromise = new Promise(resolve => {
82 88 const req = $.get("/custom/getItemLists");
83 89 req.done(data => {
84 const itemMap = {};
90 const itemMap = {
91 // Generics for rivens
92 "/Lotus/Weapons/Tenno/Archwing/Primary/ArchGun": { name: "Archgun" },
93 "/Lotus/Weapons/Tenno/Melee/PlayerMeleeWeapon": { name: "Melee" },
94 "/Lotus/Weapons/Tenno/Pistol/LotusPistol": { name: "Pistol" },
95 "/Lotus/Weapons/Tenno/Rifle/LotusRifle": { name: "Rifle" },
96 "/Lotus/Weapons/Tenno/Shotgun/LotusShotgun": { name: "Shotgun" },
97 // Missing in data sources
98 "/Lotus/Upgrades/CosmeticEnhancers/Peculiars/CyoteMod": { name: "Traumatic Peculiar" },
99 "/Lotus/Weapons/Tenno/Grimoire/TnGrimoire": { name: "Grimoire" }
100 };
85 101 for (const [type, items] of Object.entries(data)) {
86 items.forEach(item => {
87 const option = document.createElement("option");
88 option.setAttribute("data-key", item.uniqueName);
89 option.value = item.name;
90 document.getElementById("datalist-" + type).appendChild(option);
91 itemMap[item.uniqueName] = { ...item, type };
92 });
102 if (type != "badItems") {
103 items.forEach(item => {
104 if (item.uniqueName in data.badItems) {
105 item.name += " (Imposter)";
106 } else if (item.uniqueName.substr(0, 18) != "/Lotus/Types/Game/") {
107 const option = document.createElement("option");
108 option.setAttribute("data-key", item.uniqueName);
109 option.value = item.name;
110 document.getElementById("datalist-" + type).appendChild(option);
111 }
112 itemMap[item.uniqueName] = { ...item, type };
113 });
114 }
93 115 }
94 116 resolve(itemMap);
95 117 });
96 118 });
97 119
98 const rivenGenericCompatNames = {
99 "/Lotus/Weapons/Tenno/Archwing/Primary/ArchGun": "Archgun",
100 "/Lotus/Weapons/Tenno/Melee/PlayerMeleeWeapon": "Melee",
101 "/Lotus/Weapons/Tenno/Pistol/LotusPistol": "Pistol",
102 "/Lotus/Weapons/Tenno/Rifle/LotusRifle": "Rifle",
103 "/Lotus/Weapons/Tenno/Shotgun/LotusShotgun": "Shotgun"
104 };
105
106 120 function updateInventory() {
107 121 const req = $.get("/api/inventory.php?" + window.authz);
108 122 req.done(data => {
@@ -190,6 +204,7 @@ function updateInventory() {
190 204 });
191 205
192 206 document.getElementById("riven-list").innerHTML = "";
207 document.getElementById("mods-list").innerHTML = "";
193 208 data.Upgrades.forEach(item => {
194 209 if (item.ItemType.substr(0, 32) == "/Lotus/Upgrades/Mods/Randomized/") {
195 210 const rivenType = item.ItemType.substr(32);
@@ -198,10 +213,7 @@ function updateInventory() {
198 213 const tr = document.createElement("tr");
199 214 {
200 215 const td = document.createElement("td");
201 td.textContent =
202 itemMap[fingerprint.compat]?.name ??
203 rivenGenericCompatNames[fingerprint.compat] ??
204 fingerprint.compat;
216 td.textContent = itemMap[fingerprint.compat]?.name ?? fingerprint.compat;
205 217 td.textContent += " " + RivenParser.parseRiven(rivenType, fingerprint, 1).name;
206 218 td.innerHTML += " <span title='Number of buffs'>▲ " + fingerprint.buffs.length + "</span>";
207 219 td.innerHTML += " <span title='Number of curses'>▼ " + fingerprint.curses.length + "</span>";
@@ -245,6 +257,92 @@ function updateInventory() {
245 257 tr.appendChild(td);
246 258 }
247 259 document.getElementById("riven-list").appendChild(tr);
260 } else {
261 const tr = document.createElement("tr");
262 const rank = parseInt(JSON.parse(item.UpgradeFingerprint).lvl);
263 const maxRank = itemMap[item.ItemType]?.fusionLimit ?? 5;
264 {
265 const td = document.createElement("td");
266 td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
267 td.innerHTML += " <span title='Rank'>★ " + rank + "/" + maxRank + "</span>";
268 tr.appendChild(td);
269 }
270 {
271 const td = document.createElement("td");
272 td.classList = "text-end";
273 if (rank < maxRank) {
274 const a = document.createElement("a");
275 a.href = "#";
276 a.onclick = function (event) {
277 event.preventDefault();
278 setFingerprint(item.ItemType, item.ItemId, { lvl: maxRank });
279 };
280 a.textContent = "Max Rank";
281 td.appendChild(a);
282
283 const span = document.createElement("span");
284 span.innerHTML = " &middot; ";
285 td.appendChild(span);
286 }
287 {
288 const a = document.createElement("a");
289 a.href = "#";
290 a.onclick = function (event) {
291 event.preventDefault();
292 disposeOfGear("Upgrades", item.ItemId.$oid);
293 };
294 a.textContent = "Remove";
295 td.appendChild(a);
296 }
297 tr.appendChild(td);
298 }
299 document.getElementById("mods-list").appendChild(tr);
300 }
301 });
302 data.RawUpgrades.forEach(item => {
303 if (item.ItemCount > 0) {
304 const maxRank = itemMap[item.ItemType]?.fusionLimit ?? 5;
305 const tr = document.createElement("tr");
306 {
307 const td = document.createElement("td");
308 td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
309 td.innerHTML += " <span title='Rank'>★ 0/" + maxRank + "</span>";
310 if (item.ItemCount > 1) {
311 td.innerHTML += " <span title='Count'>🗍 " + parseInt(item.ItemCount) + "</span>";
312 }
313 tr.appendChild(td);
314 }
315 {
316 const td = document.createElement("td");
317 td.classList = "text-end";
318 {
319 const a = document.createElement("a");
320 a.href = "#";
321 a.onclick = function (event) {
322 event.preventDefault();
323 setFingerprint(item.ItemType, item.LastAdded, { lvl: maxRank });
324 };
325 a.textContent = "Max Rank";
326 td.appendChild(a);
327 }
328 {
329 const span = document.createElement("span");
330 span.innerHTML = " &middot; ";
331 td.appendChild(span);
332 }
333 {
334 const a = document.createElement("a");
335 a.href = "#";
336 a.onclick = function (event) {
337 event.preventDefault();
338 disposeOfItems("Upgrades", item.ItemType, item.ItemCount);
339 };
340 a.textContent = "Remove";
341 td.appendChild(a);
342 }
343 tr.appendChild(td);
344 }
345 document.getElementById("mods-list").appendChild(tr);
248 346 }
249 347 });
250 348 });
@@ -352,6 +450,29 @@ function disposeOfGear(category, oid) {
352 450 });
353 451 }
354 452
453 function disposeOfItems(category, type, count) {
454 const data = {
455 SellCurrency: "SC_RegularCredits",
456 SellPrice: 0,
457 Items: {}
458 };
459 data.Items[category] = [
460 {
461 String: type,
462 Count: count
463 }
464 ];
465 revalidateAuthz(() => {
466 $.post({
467 url: "/api/sell.php?" + window.authz,
468 contentType: "text/plain",
469 data: JSON.stringify(data)
470 }).done(function () {
471 updateInventory();
472 });
473 });
474 }
475
355 476 function doAcquireMiscItems() {
356 477 const uniqueName = getKey(document.getElementById("miscitem-type"));
357 478 if (!uniqueName) {
@@ -445,3 +566,53 @@ function doAcquireRiven() {
445 566 $("#addriven-fingerprint").on("input", () => {
446 567 $("#addriven-fingerprint").removeClass("is-invalid");
447 568 });
569
570 function setFingerprint(ItemType, ItemId, fingerprint) {
571 revalidateAuthz(() => {
572 $.post({
573 url: "/api/artifacts.php?" + window.authz,
574 contentType: "text/plain",
575 data: JSON.stringify({
576 Upgrade: {
577 ItemType,
578 ItemId,
579 UpgradeFingerprint: JSON.stringify(fingerprint)
580 },
581 LevelDiff: 0,
582 Cost: 0,
583 FusionPointCost: 0
584 })
585 }).done(function () {
586 updateInventory();
587 });
588 });
589 }
590
591 function doAcquireMod() {
592 const uniqueName = getKey(document.getElementById("mod-to-acquire"));
593 if (!uniqueName) {
594 $("#mod-to-acquire").addClass("is-invalid").focus();
595 return;
596 }
597 revalidateAuthz(() => {
598 $.post({
599 url: "/api/missionInventoryUpdate.php?" + window.authz,
600 contentType: "text/plain",
601 data: JSON.stringify({
602 RawUpgrades: [
603 {
604 ItemType: uniqueName,
605 ItemCount: 1
606 }
607 ]
608 })
609 }).done(function () {
610 document.getElementById("mod-to-acquire").value = "";
611 updateInventory();
612 });
613 });
614 }
615
616 $("#mod-to-acquire").on("input", () => {
617 $("#mod-to-acquire").removeClass("is-invalid");
618 });
Modified static/webui/style.css +2 -3
@@ -1,14 +1,13 @@
1 1 @media (min-width: 992px) {
2 2 body.logged-in #main-view {
3 display: grid;
4 grid-template-columns: 1fr 8fr;
5 gap: 1.5rem;
3 display: flex;
6 4 }
7 5
8 6 body.logged-in #sidebar {
9 7 position: sticky;
10 8 top: 5rem;
11 9 height: 100px;
10 margin-right: 3rem;
12 11 }
13 12
14 13 body:not(.logged-in) #sidebar {