forked from noxious/server
Compare commits
26 Commits
feature/#2
...
feature/im
Author | SHA1 | Date | |
---|---|---|---|
bd8caaf27c | |||
3cf86e322c | |||
6f32fbdc79 | |||
743d4594df | |||
2be49c010f | |||
2ac9416fe6 | |||
43fe6ab33e | |||
1cbf116ad4 | |||
3f10b03d24 | |||
a525d80530 | |||
4748044ab3 | |||
3b0138130b | |||
54c75896f9 | |||
9467797dc9 | |||
a8934f8e40 | |||
65cae5d824 | |||
179ccdbc55 | |||
ff39628f0c | |||
d4680b198e | |||
550b961505 | |||
1839bd9a22 | |||
1017013032 | |||
d5c7cd0294 | |||
4a62bbb118 | |||
40c7f6289a | |||
72d731c6f2 |
311
package-lock.json
generated
311
package-lock.json
generated
@ -729,18 +729,18 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.17.6",
|
"version": "20.17.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.10.tgz",
|
||||||
"integrity": "sha512-VEI7OdvK2wP7XHnsuXbAJnEpEkF6NjSN45QJlL4VGqZSXsnicpesdTWsg9RISeSdYd3yeRj/y3k5KGjUXYnFwQ==",
|
"integrity": "sha512-/jrvh5h6NXhEauFFexRin69nA0uHJ5gwk4iDivp/DeoEua3uwCUto6PC86IpRITBOs4+6i2I56K5x5b6WYGXHA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.19.2"
|
"undici-types": "~6.19.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/nodemailer": {
|
"node_modules/@types/nodemailer": {
|
||||||
"version": "6.4.16",
|
"version": "6.4.17",
|
||||||
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.16.tgz",
|
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.17.tgz",
|
||||||
"integrity": "sha512-uz6hN6Pp0upXMcilM61CoKyjT7sskBoOWpptkjjJp8jIMlTdc3xG01U7proKkXzruMS4hS0zqtHNkNPFB20rKQ==",
|
"integrity": "sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -949,9 +949,9 @@
|
|||||||
"license": "BSD-3-Clause"
|
"license": "BSD-3-Clause"
|
||||||
},
|
},
|
||||||
"node_modules/bullmq": {
|
"node_modules/bullmq": {
|
||||||
"version": "5.26.2",
|
"version": "5.34.3",
|
||||||
"resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.26.2.tgz",
|
"resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.34.3.tgz",
|
||||||
"integrity": "sha512-UdHBrJoRkpXoF8b/FVEkuRBnaUZoA7+qHQNyTx1n2oNVZ4iWxqGqss+M9xAwXOpBmSNvOSlaBdHpf+5QJTU8GQ==",
|
"integrity": "sha512-S8/V11w7p6jYAGvv+00skLza/4inTOupWPe0uCD8mZSUiYKzvmW4/YEB+KVjZI2CC2oD3KJ3t7/KkUd31MxMig==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cron-parser": "^4.6.0",
|
"cron-parser": "^4.6.0",
|
||||||
@ -972,17 +972,27 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/call-bind": {
|
"node_modules/call-bind-apply-helpers": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz",
|
||||||
"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
|
"integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-define-property": "^1.0.0",
|
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
"function-bind": "^1.1.2",
|
"function-bind": "^1.1.2"
|
||||||
"get-intrinsic": "^1.2.4",
|
},
|
||||||
"set-function-length": "^1.2.1"
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/call-bound": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind-apply-helpers": "^1.0.1",
|
||||||
|
"get-intrinsic": "^1.2.6"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@ -1150,23 +1160,6 @@
|
|||||||
"ms": "2.0.0"
|
"ms": "2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/define-data-property": {
|
|
||||||
"version": "1.1.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
|
||||||
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"es-define-property": "^1.0.0",
|
|
||||||
"es-errors": "^1.3.0",
|
|
||||||
"gopd": "^1.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/denque": {
|
"node_modules/denque": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
||||||
@ -1215,9 +1208,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/dotenv": {
|
"node_modules/dotenv": {
|
||||||
"version": "16.4.5",
|
"version": "16.4.7",
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
|
||||||
"integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
|
"integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
@ -1226,6 +1219,20 @@
|
|||||||
"url": "https://dotenvx.com"
|
"url": "https://dotenvx.com"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dunder-proto": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind-apply-helpers": "^1.0.1",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"gopd": "^1.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ecdsa-sig-formatter": {
|
"node_modules/ecdsa-sig-formatter": {
|
||||||
"version": "1.0.11",
|
"version": "1.0.11",
|
||||||
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
||||||
@ -1313,13 +1320,10 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/es-define-property": {
|
"node_modules/es-define-property": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||||
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
|
||||||
"get-intrinsic": "^1.2.4"
|
|
||||||
},
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
@ -1333,6 +1337,18 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/es-object-atoms": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/escape-html": {
|
"node_modules/escape-html": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||||
@ -1349,9 +1365,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/express": {
|
"node_modules/express": {
|
||||||
"version": "4.21.1",
|
"version": "4.21.2",
|
||||||
"resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
|
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
|
||||||
"integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
|
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"accepts": "~1.3.8",
|
"accepts": "~1.3.8",
|
||||||
@ -1373,7 +1389,7 @@
|
|||||||
"methods": "~1.1.2",
|
"methods": "~1.1.2",
|
||||||
"on-finished": "2.4.1",
|
"on-finished": "2.4.1",
|
||||||
"parseurl": "~1.3.3",
|
"parseurl": "~1.3.3",
|
||||||
"path-to-regexp": "0.1.10",
|
"path-to-regexp": "0.1.12",
|
||||||
"proxy-addr": "~2.0.7",
|
"proxy-addr": "~2.0.7",
|
||||||
"qs": "6.13.0",
|
"qs": "6.13.0",
|
||||||
"range-parser": "~1.2.1",
|
"range-parser": "~1.2.1",
|
||||||
@ -1388,6 +1404,10 @@
|
|||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.10.0"
|
"node": ">= 0.10.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fast-redact": {
|
"node_modules/fast-redact": {
|
||||||
@ -1472,16 +1492,21 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/get-intrinsic": {
|
"node_modules/get-intrinsic": {
|
||||||
"version": "1.2.4",
|
"version": "1.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz",
|
||||||
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
|
"integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"call-bind-apply-helpers": "^1.0.1",
|
||||||
|
"dunder-proto": "^1.0.0",
|
||||||
|
"es-define-property": "^1.0.1",
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
|
"es-object-atoms": "^1.0.0",
|
||||||
"function-bind": "^1.1.2",
|
"function-bind": "^1.1.2",
|
||||||
"has-proto": "^1.0.1",
|
"gopd": "^1.2.0",
|
||||||
"has-symbols": "^1.0.3",
|
"has-symbols": "^1.1.0",
|
||||||
"hasown": "^2.0.0"
|
"hasown": "^2.0.2",
|
||||||
|
"math-intrinsics": "^1.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@ -1504,12 +1529,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/gopd": {
|
"node_modules/gopd": {
|
||||||
"version": "1.0.1",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"engines": {
|
||||||
"get-intrinsic": "^1.1.3"
|
"node": ">= 0.4"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
@ -1525,34 +1550,10 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/has-property-descriptors": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
|
|
||||||
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"es-define-property": "^1.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/has-proto": {
|
|
||||||
"version": "1.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
|
|
||||||
"integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/has-symbols": {
|
"node_modules/has-symbols": {
|
||||||
"version": "1.0.3",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@ -1615,9 +1616,9 @@
|
|||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/ioredis": {
|
"node_modules/ioredis": {
|
||||||
"version": "5.4.1",
|
"version": "5.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.4.2.tgz",
|
||||||
"integrity": "sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==",
|
"integrity": "sha512-0SZXGNGZ+WzISQ67QDyZ2x0+wVxjjUndtD8oSeik/4ajifeiRufed8fCb8QW8VMyi4MXcS+UO1k/0NGhvq1PAg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ioredis/commands": "^1.1.1",
|
"@ioredis/commands": "^1.1.1",
|
||||||
@ -1639,9 +1640,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ioredis/node_modules/debug": {
|
"node_modules/ioredis/node_modules/debug": {
|
||||||
"version": "4.3.7",
|
"version": "4.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||||
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "^2.1.3"
|
"ms": "^2.1.3"
|
||||||
@ -1841,6 +1842,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/math-intrinsics": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/media-typer": {
|
"node_modules/media-typer": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||||
@ -1991,9 +2001,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/nodemon": {
|
"node_modules/nodemon": {
|
||||||
"version": "3.1.7",
|
"version": "3.1.9",
|
||||||
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz",
|
||||||
"integrity": "sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==",
|
"integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -2020,9 +2030,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/nodemon/node_modules/debug": {
|
"node_modules/nodemon/node_modules/debug": {
|
||||||
"version": "4.3.7",
|
"version": "4.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||||
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -2106,9 +2116,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/path-to-regexp": {
|
"node_modules/path-to-regexp": {
|
||||||
"version": "0.1.10",
|
"version": "0.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
|
||||||
"integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==",
|
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
@ -2162,9 +2172,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/prettier": {
|
"node_modules/prettier": {
|
||||||
"version": "3.3.3",
|
"version": "3.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz",
|
||||||
"integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==",
|
"integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
@ -2411,23 +2421,6 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/set-function-length": {
|
|
||||||
"version": "1.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
|
||||||
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"define-data-property": "^1.1.4",
|
|
||||||
"es-errors": "^1.3.0",
|
|
||||||
"function-bind": "^1.1.2",
|
|
||||||
"get-intrinsic": "^1.2.4",
|
|
||||||
"gopd": "^1.0.1",
|
|
||||||
"has-property-descriptors": "^1.0.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/setprototypeof": {
|
"node_modules/setprototypeof": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||||
@ -2474,15 +2467,69 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/side-channel": {
|
"node_modules/side-channel": {
|
||||||
"version": "1.0.6",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
|
||||||
"integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
|
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.7",
|
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
"get-intrinsic": "^1.2.4",
|
"object-inspect": "^1.13.3",
|
||||||
"object-inspect": "^1.13.1"
|
"side-channel-list": "^1.0.0",
|
||||||
|
"side-channel-map": "^1.0.1",
|
||||||
|
"side-channel-weakmap": "^1.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/side-channel-list": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"object-inspect": "^1.13.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/side-channel-map": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bound": "^1.0.2",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"get-intrinsic": "^1.2.5",
|
||||||
|
"object-inspect": "^1.13.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/side-channel-weakmap": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bound": "^1.0.2",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"get-intrinsic": "^1.2.5",
|
||||||
|
"object-inspect": "^1.13.3",
|
||||||
|
"side-channel-map": "^1.0.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@ -2774,9 +2821,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.6.3",
|
"version": "5.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz",
|
||||||
"integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
|
"integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
@ -2878,9 +2925,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/zod": {
|
"node_modules/zod": {
|
||||||
"version": "3.23.8",
|
"version": "3.24.1",
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz",
|
||||||
"integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
|
"integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
-- AlterTable
|
|
||||||
ALTER TABLE `Chat` MODIFY `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3);
|
|
@ -1,2 +0,0 @@
|
|||||||
-- AlterTable
|
|
||||||
ALTER TABLE `CharacterType` ADD COLUMN `isEnabledForCharCreation` BOOLEAN NOT NULL DEFAULT false;
|
|
@ -15,7 +15,7 @@ CREATE TABLE `Chat` (
|
|||||||
`characterId` INTEGER NOT NULL,
|
`characterId` INTEGER NOT NULL,
|
||||||
`zoneId` INTEGER NOT NULL,
|
`zoneId` INTEGER NOT NULL,
|
||||||
`message` VARCHAR(191) NOT NULL,
|
`message` VARCHAR(191) NOT NULL,
|
||||||
`createdAt` DATETIME(3) NOT NULL,
|
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
|
||||||
PRIMARY KEY (`id`)
|
PRIMARY KEY (`id`)
|
||||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
@ -47,6 +47,20 @@ CREATE TABLE `SpriteAction` (
|
|||||||
PRIMARY KEY (`id`)
|
PRIMARY KEY (`id`)
|
||||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE `Item` (
|
||||||
|
`id` VARCHAR(191) NOT NULL,
|
||||||
|
`name` VARCHAR(191) NOT NULL,
|
||||||
|
`description` VARCHAR(191) NULL,
|
||||||
|
`itemType` ENUM('WEAPON', 'HELMET', 'CHEST', 'LEGS', 'BOOTS', 'GLOVES', 'RING', 'NECKLACE') NOT NULL,
|
||||||
|
`stackable` BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
`rarity` ENUM('COMMON', 'UNCOMMON', 'RARE', 'EPIC', 'LEGENDARY') NOT NULL DEFAULT 'COMMON',
|
||||||
|
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
`updatedAt` DATETIME(3) NOT NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE `User` (
|
CREATE TABLE `User` (
|
||||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||||
@ -77,6 +91,7 @@ CREATE TABLE `CharacterType` (
|
|||||||
`name` VARCHAR(191) NOT NULL,
|
`name` VARCHAR(191) NOT NULL,
|
||||||
`gender` ENUM('MALE', 'FEMALE') NOT NULL,
|
`gender` ENUM('MALE', 'FEMALE') NOT NULL,
|
||||||
`race` ENUM('HUMAN', 'ELF', 'DWARF', 'ORC', 'GOBLIN') NOT NULL,
|
`race` ENUM('HUMAN', 'ELF', 'DWARF', 'ORC', 'GOBLIN') NOT NULL,
|
||||||
|
`isEnabledForCharCreation` BOOLEAN NOT NULL DEFAULT false,
|
||||||
`spriteId` VARCHAR(191) NULL,
|
`spriteId` VARCHAR(191) NULL,
|
||||||
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
`updatedAt` DATETIME(3) NOT NULL,
|
`updatedAt` DATETIME(3) NOT NULL,
|
||||||
@ -84,23 +99,39 @@ CREATE TABLE `CharacterType` (
|
|||||||
PRIMARY KEY (`id`)
|
PRIMARY KEY (`id`)
|
||||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE `CharacterHair` (
|
||||||
|
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||||
|
`name` VARCHAR(191) NOT NULL,
|
||||||
|
`gender` ENUM('MALE', 'FEMALE') NOT NULL DEFAULT 'MALE',
|
||||||
|
`isEnabledForCharCreation` BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
`spriteId` VARCHAR(191) NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE `Character` (
|
CREATE TABLE `Character` (
|
||||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||||
`userId` INTEGER NOT NULL,
|
`userId` INTEGER NOT NULL,
|
||||||
`name` VARCHAR(191) NOT NULL,
|
`name` VARCHAR(191) NOT NULL,
|
||||||
`online` BOOLEAN NOT NULL DEFAULT false,
|
`online` BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
`role` VARCHAR(191) NOT NULL DEFAULT 'player',
|
||||||
|
`zoneId` INTEGER NOT NULL DEFAULT 1,
|
||||||
|
`positionX` INTEGER NOT NULL DEFAULT 0,
|
||||||
|
`positionY` INTEGER NOT NULL DEFAULT 0,
|
||||||
|
`rotation` INTEGER NOT NULL DEFAULT 0,
|
||||||
|
`characterTypeId` INTEGER NULL,
|
||||||
|
`characterHairId` INTEGER NULL,
|
||||||
|
`alignment` INTEGER NOT NULL DEFAULT 50,
|
||||||
`hitpoints` INTEGER NOT NULL DEFAULT 100,
|
`hitpoints` INTEGER NOT NULL DEFAULT 100,
|
||||||
`mana` INTEGER NOT NULL DEFAULT 100,
|
`mana` INTEGER NOT NULL DEFAULT 100,
|
||||||
`level` INTEGER NOT NULL DEFAULT 1,
|
`level` INTEGER NOT NULL DEFAULT 1,
|
||||||
`experience` INTEGER NOT NULL DEFAULT 0,
|
`experience` INTEGER NOT NULL DEFAULT 0,
|
||||||
`alignment` INTEGER NOT NULL DEFAULT 50,
|
`strength` INTEGER NOT NULL DEFAULT 10,
|
||||||
`role` VARCHAR(191) NOT NULL DEFAULT 'player',
|
`dexterity` INTEGER NOT NULL DEFAULT 10,
|
||||||
`positionX` INTEGER NOT NULL DEFAULT 0,
|
`intelligence` INTEGER NOT NULL DEFAULT 10,
|
||||||
`positionY` INTEGER NOT NULL DEFAULT 0,
|
`wisdom` INTEGER NOT NULL DEFAULT 10,
|
||||||
`rotation` INTEGER NOT NULL DEFAULT 0,
|
|
||||||
`zoneId` INTEGER NOT NULL DEFAULT 1,
|
|
||||||
`characterTypeId` INTEGER NULL,
|
|
||||||
|
|
||||||
UNIQUE INDEX `Character_name_key`(`name`),
|
UNIQUE INDEX `Character_name_key`(`name`),
|
||||||
PRIMARY KEY (`id`)
|
PRIMARY KEY (`id`)
|
||||||
@ -116,6 +147,17 @@ CREATE TABLE `CharacterItem` (
|
|||||||
PRIMARY KEY (`id`)
|
PRIMARY KEY (`id`)
|
||||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE `CharacterEquipment` (
|
||||||
|
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||||
|
`characterId` INTEGER NOT NULL,
|
||||||
|
`itemId` VARCHAR(191) NOT NULL,
|
||||||
|
`quantity` INTEGER NOT NULL,
|
||||||
|
`slot` ENUM('HEAD', 'BODY', 'ARMS', 'LEGS', 'NECK', 'RING') NOT NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE `Tile` (
|
CREATE TABLE `Tile` (
|
||||||
`id` VARCHAR(191) NOT NULL,
|
`id` VARCHAR(191) NOT NULL,
|
||||||
@ -144,18 +186,6 @@ CREATE TABLE `Object` (
|
|||||||
PRIMARY KEY (`id`)
|
PRIMARY KEY (`id`)
|
||||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE `Item` (
|
|
||||||
`id` VARCHAR(191) NOT NULL,
|
|
||||||
`name` VARCHAR(191) NOT NULL,
|
|
||||||
`description` VARCHAR(191) NULL,
|
|
||||||
`stackable` BOOLEAN NOT NULL DEFAULT false,
|
|
||||||
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
|
||||||
`updatedAt` DATETIME(3) NOT NULL,
|
|
||||||
|
|
||||||
PRIMARY KEY (`id`)
|
|
||||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE `Zone` (
|
CREATE TABLE `Zone` (
|
||||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||||
@ -232,6 +262,9 @@ ALTER TABLE `PasswordResetToken` ADD CONSTRAINT `PasswordResetToken_userId_fkey`
|
|||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
ALTER TABLE `CharacterType` ADD CONSTRAINT `CharacterType_spriteId_fkey` FOREIGN KEY (`spriteId`) REFERENCES `Sprite`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE `CharacterType` ADD CONSTRAINT `CharacterType_spriteId_fkey` FOREIGN KEY (`spriteId`) REFERENCES `Sprite`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `CharacterHair` ADD CONSTRAINT `CharacterHair_spriteId_fkey` FOREIGN KEY (`spriteId`) REFERENCES `Sprite`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
ALTER TABLE `Character` ADD CONSTRAINT `Character_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE `Character` ADD CONSTRAINT `Character_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
@ -241,12 +274,21 @@ ALTER TABLE `Character` ADD CONSTRAINT `Character_zoneId_fkey` FOREIGN KEY (`zon
|
|||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
ALTER TABLE `Character` ADD CONSTRAINT `Character_characterTypeId_fkey` FOREIGN KEY (`characterTypeId`) REFERENCES `CharacterType`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE `Character` ADD CONSTRAINT `Character_characterTypeId_fkey` FOREIGN KEY (`characterTypeId`) REFERENCES `CharacterType`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `Character` ADD CONSTRAINT `Character_characterHairId_fkey` FOREIGN KEY (`characterHairId`) REFERENCES `CharacterHair`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
ALTER TABLE `CharacterItem` ADD CONSTRAINT `CharacterItem_characterId_fkey` FOREIGN KEY (`characterId`) REFERENCES `Character`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE `CharacterItem` ADD CONSTRAINT `CharacterItem_characterId_fkey` FOREIGN KEY (`characterId`) REFERENCES `Character`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
ALTER TABLE `CharacterItem` ADD CONSTRAINT `CharacterItem_itemId_fkey` FOREIGN KEY (`itemId`) REFERENCES `Item`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE `CharacterItem` ADD CONSTRAINT `CharacterItem_itemId_fkey` FOREIGN KEY (`itemId`) REFERENCES `Item`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `CharacterEquipment` ADD CONSTRAINT `CharacterEquipment_characterId_fkey` FOREIGN KEY (`characterId`) REFERENCES `Character`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `CharacterEquipment` ADD CONSTRAINT `CharacterEquipment_itemId_fkey` FOREIGN KEY (`itemId`) REFERENCES `Item`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
ALTER TABLE `ZoneEffect` ADD CONSTRAINT `ZoneEffect_zoneId_fkey` FOREIGN KEY (`zoneId`) REFERENCES `Zone`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE `ZoneEffect` ADD CONSTRAINT `ZoneEffect_zoneId_fkey` FOREIGN KEY (`zoneId`) REFERENCES `Zone`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
@ -23,6 +23,7 @@ model Sprite {
|
|||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
spriteActions SpriteAction[]
|
spriteActions SpriteAction[]
|
||||||
characterTypes CharacterType[]
|
characterTypes CharacterType[]
|
||||||
|
characterHairs CharacterHair[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model SpriteAction {
|
model SpriteAction {
|
||||||
@ -39,3 +40,35 @@ model SpriteAction {
|
|||||||
frameHeight Int @default(0)
|
frameHeight Int @default(0)
|
||||||
frameSpeed Int @default(0)
|
frameSpeed Int @default(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model Item {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
name String
|
||||||
|
description String?
|
||||||
|
itemType ItemType
|
||||||
|
stackable Boolean @default(false)
|
||||||
|
rarity ItemRarity @default(COMMON)
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
characters CharacterItem[]
|
||||||
|
equipment CharacterEquipment[]
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ItemType {
|
||||||
|
WEAPON
|
||||||
|
HELMET
|
||||||
|
CHEST
|
||||||
|
LEGS
|
||||||
|
BOOTS
|
||||||
|
GLOVES
|
||||||
|
RING
|
||||||
|
NECKLACE
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ItemRarity {
|
||||||
|
COMMON
|
||||||
|
UNCOMMON
|
||||||
|
RARE
|
||||||
|
EPIC
|
||||||
|
LEGENDARY
|
||||||
|
}
|
||||||
|
@ -1,3 +1,16 @@
|
|||||||
|
enum CharacterGender {
|
||||||
|
MALE
|
||||||
|
FEMALE
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CharacterRace {
|
||||||
|
HUMAN
|
||||||
|
ELF
|
||||||
|
DWARF
|
||||||
|
ORC
|
||||||
|
GOBLIN
|
||||||
|
}
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
username String @unique
|
username String @unique
|
||||||
@ -16,19 +29,6 @@ model PasswordResetToken {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
}
|
}
|
||||||
|
|
||||||
enum CharacterGender {
|
|
||||||
MALE
|
|
||||||
FEMALE
|
|
||||||
}
|
|
||||||
|
|
||||||
enum CharacterRace {
|
|
||||||
HUMAN
|
|
||||||
ELF
|
|
||||||
DWARF
|
|
||||||
ORC
|
|
||||||
GOBLIN
|
|
||||||
}
|
|
||||||
|
|
||||||
model CharacterType {
|
model CharacterType {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
name String
|
name String
|
||||||
@ -42,27 +42,52 @@ model CharacterType {
|
|||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model CharacterHair {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
name String
|
||||||
|
gender CharacterGender @default(MALE)
|
||||||
|
isEnabledForCharCreation Boolean @default(false)
|
||||||
|
sprite Sprite? @relation(fields: [spriteId], references: [id], onDelete: Cascade)
|
||||||
|
spriteId String?
|
||||||
|
characters Character[]
|
||||||
|
}
|
||||||
|
|
||||||
model Character {
|
model Character {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
userId Int
|
userId Int
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
name String @unique
|
name String @unique
|
||||||
online Boolean @default(false)
|
online Boolean @default(false)
|
||||||
hitpoints Int @default(100)
|
role String @default("player")
|
||||||
mana Int @default(100)
|
chats Chat[]
|
||||||
level Int @default(1)
|
|
||||||
experience Int @default(0)
|
// Position
|
||||||
alignment Int @default(50)
|
zoneId Int @default(1)
|
||||||
role String @default("player")
|
zone Zone @relation(fields: [zoneId], references: [id], onDelete: Cascade)
|
||||||
positionX Int @default(0)
|
positionX Int @default(0)
|
||||||
positionY Int @default(0)
|
positionY Int @default(0)
|
||||||
rotation Int @default(0)
|
rotation Int @default(0)
|
||||||
zoneId Int @default(1)
|
|
||||||
zone Zone @relation(fields: [zoneId], references: [id], onDelete: Cascade)
|
// Customization
|
||||||
characterTypeId Int?
|
characterTypeId Int?
|
||||||
characterType CharacterType? @relation(fields: [characterTypeId], references: [id], onDelete: Cascade)
|
characterType CharacterType? @relation(fields: [characterTypeId], references: [id], onDelete: Cascade)
|
||||||
chats Chat[]
|
characterHairId Int?
|
||||||
items CharacterItem[]
|
characterHair CharacterHair? @relation(fields: [characterHairId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
// Inventory
|
||||||
|
items CharacterItem[]
|
||||||
|
equipment CharacterEquipment[]
|
||||||
|
|
||||||
|
// Stats
|
||||||
|
alignment Int @default(50)
|
||||||
|
hitpoints Int @default(100)
|
||||||
|
mana Int @default(100)
|
||||||
|
level Int @default(1)
|
||||||
|
experience Int @default(0)
|
||||||
|
strength Int @default(10)
|
||||||
|
dexterity Int @default(10)
|
||||||
|
intelligence Int @default(10)
|
||||||
|
wisdom Int @default(10)
|
||||||
}
|
}
|
||||||
|
|
||||||
model CharacterItem {
|
model CharacterItem {
|
||||||
@ -73,3 +98,22 @@ model CharacterItem {
|
|||||||
item Item @relation(fields: [itemId], references: [id], onDelete: Cascade)
|
item Item @relation(fields: [itemId], references: [id], onDelete: Cascade)
|
||||||
quantity Int
|
quantity Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model CharacterEquipment {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
characterId Int
|
||||||
|
character Character @relation(fields: [characterId], references: [id], onDelete: Cascade)
|
||||||
|
itemId String
|
||||||
|
item Item @relation(fields: [itemId], references: [id], onDelete: Cascade)
|
||||||
|
quantity Int
|
||||||
|
slot CharacterEquipmentSlotType
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CharacterEquipmentSlotType {
|
||||||
|
HEAD
|
||||||
|
BODY
|
||||||
|
ARMS
|
||||||
|
LEGS
|
||||||
|
NECK
|
||||||
|
RING
|
||||||
|
}
|
||||||
|
@ -21,16 +21,6 @@ model Object {
|
|||||||
ZoneObject ZoneObject[]
|
ZoneObject ZoneObject[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model Item {
|
|
||||||
id String @id @default(uuid())
|
|
||||||
name String
|
|
||||||
description String?
|
|
||||||
stackable Boolean @default(false)
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
characters CharacterItem[]
|
|
||||||
}
|
|
||||||
|
|
||||||
model Zone {
|
model Zone {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
name String
|
name String
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Character } from '@prisma/client'
|
import { Character } from '@prisma/client'
|
||||||
import { CharacterService } from '../services/character/characterService'
|
import { CharacterService } from '../services/characterService'
|
||||||
|
|
||||||
class ZoneCharacter {
|
class ZoneCharacter {
|
||||||
public readonly character: Character
|
public readonly character: Character
|
||||||
|
17
src/repositories/characterHairRepository.ts
Normal file
17
src/repositories/characterHairRepository.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import prisma from '../utilities/prisma' // Import the global Prisma instance
|
||||||
|
import { CharacterHair } from '@prisma/client'
|
||||||
|
|
||||||
|
class CharacterHairRepository {
|
||||||
|
async getAll(): Promise<CharacterHair[]> {
|
||||||
|
return prisma.characterHair.findMany()
|
||||||
|
}
|
||||||
|
async getIsEnabledForCharCreationHair(): Promise<CharacterHair[]> {
|
||||||
|
return prisma.characterHair.findMany({
|
||||||
|
where: {
|
||||||
|
isEnabledForCharCreation: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new CharacterHairRepository()
|
@ -1,9 +1,8 @@
|
|||||||
import prisma from '../utilities/prisma' // Import the global Prisma instance
|
import prisma from '../utilities/prisma' // Import the global Prisma instance
|
||||||
import { Character } from '@prisma/client'
|
|
||||||
import { appLogger } from '../utilities/logger'
|
import { appLogger } from '../utilities/logger'
|
||||||
|
|
||||||
class CharacterRepository {
|
class CharacterRepository {
|
||||||
async getByUserId(userId: number): Promise<Character[] | null> {
|
async getByUserId(userId: number) {
|
||||||
try {
|
try {
|
||||||
return await prisma.character.findMany({
|
return await prisma.character.findMany({
|
||||||
where: {
|
where: {
|
||||||
@ -15,6 +14,11 @@ class CharacterRepository {
|
|||||||
include: {
|
include: {
|
||||||
sprite: true
|
sprite: true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
characterHair: {
|
||||||
|
include: {
|
||||||
|
sprite: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -25,7 +29,7 @@ class CharacterRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getByUserAndId(userId: number, characterId: number): Promise<Character | null> {
|
async getByUserAndId(userId: number, characterId: number) {
|
||||||
try {
|
try {
|
||||||
return await prisma.character.findFirst({
|
return await prisma.character.findFirst({
|
||||||
where: {
|
where: {
|
||||||
@ -38,6 +42,11 @@ class CharacterRepository {
|
|||||||
include: {
|
include: {
|
||||||
sprite: true
|
sprite: true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
characterHair: {
|
||||||
|
include: {
|
||||||
|
sprite: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -48,7 +57,7 @@ class CharacterRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getById(id: number): Promise<Character | null> {
|
async getById(id: number) {
|
||||||
try {
|
try {
|
||||||
return await prisma.character.findUnique({
|
return await prisma.character.findUnique({
|
||||||
where: {
|
where: {
|
||||||
@ -60,6 +69,11 @@ class CharacterRepository {
|
|||||||
include: {
|
include: {
|
||||||
sprite: true
|
sprite: true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
characterHair: {
|
||||||
|
include: {
|
||||||
|
sprite: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -70,40 +84,7 @@ class CharacterRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async updatePosition(id: number, positionX: number, positionY: number): Promise<Character | null> {
|
async getByName(name: string) {
|
||||||
try {
|
|
||||||
return await prisma.character.update({
|
|
||||||
where: {
|
|
||||||
id: id
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
positionX,
|
|
||||||
positionY
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} catch (error: any) {
|
|
||||||
// Handle error
|
|
||||||
appLogger.error(`Failed to update character: ${error instanceof Error ? error.message : String(error)}`)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async deleteByUserIdAndId(userId: number, characterId: number): Promise<Character | null> {
|
|
||||||
try {
|
|
||||||
return await prisma.character.delete({
|
|
||||||
where: {
|
|
||||||
userId,
|
|
||||||
id: characterId
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} catch (error: any) {
|
|
||||||
// Handle error
|
|
||||||
appLogger.error(`Failed to delete character by user ID and character ID: ${error instanceof Error ? error.message : String(error)}`)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getByName(name: string): Promise<Character | null> {
|
|
||||||
try {
|
try {
|
||||||
return await prisma.character.findFirst({
|
return await prisma.character.findFirst({
|
||||||
where: {
|
where: {
|
||||||
@ -115,6 +96,11 @@ class CharacterRepository {
|
|||||||
include: {
|
include: {
|
||||||
sprite: true
|
sprite: true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
characterHair: {
|
||||||
|
include: {
|
||||||
|
sprite: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -3,7 +3,6 @@ import prisma from '../utilities/prisma'
|
|||||||
import { appLogger } from '../utilities/logger'
|
import { appLogger } from '../utilities/logger'
|
||||||
|
|
||||||
class ZoneEventTileRepository {
|
class ZoneEventTileRepository {
|
||||||
|
|
||||||
async getAll(id: number): Promise<ZoneEventTile[]> {
|
async getAll(id: number): Promise<ZoneEventTile[]> {
|
||||||
try {
|
try {
|
||||||
return await prisma.zoneEventTile.findMany({
|
return await prisma.zoneEventTile.findMany({
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { AStar } from '../../utilities/character/aStar'
|
import { AStar } from '../utilities/character/aStar'
|
||||||
import ZoneManager from '../../managers/zoneManager'
|
import ZoneManager from '../managers/zoneManager'
|
||||||
import prisma from '../../utilities/prisma'
|
import prisma from '../utilities/prisma'
|
||||||
import Rotation from '../../utilities/character/rotation'
|
import Rotation from '../utilities/character/rotation'
|
||||||
import { gameLogger } from '../../utilities/logger'
|
import { appLogger, gameLogger } from '../utilities/logger'
|
||||||
import { Character, CharacterGender, CharacterRace } from '@prisma/client'
|
import { Character } from '@prisma/client'
|
||||||
|
|
||||||
interface Position {
|
interface Position {
|
||||||
x: number
|
x: number
|
||||||
@ -11,7 +11,53 @@ interface Position {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class CharacterService {
|
export class CharacterService {
|
||||||
private static readonly MOVEMENT_DELAY_MS = 250
|
private readonly MOVEMENT_DELAY_MS = 250
|
||||||
|
|
||||||
|
async create(name: string, userId: number) {
|
||||||
|
return prisma.character.create({
|
||||||
|
data: {
|
||||||
|
name,
|
||||||
|
userId
|
||||||
|
// characterTypeId: 1 // @TODO set to chosen character type
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateHair(characterId: number, characterHairId: number | null) {
|
||||||
|
await prisma.character.update({
|
||||||
|
where: { id: characterId },
|
||||||
|
data: {
|
||||||
|
characterHairId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteByUserIdAndId(userId: number, characterId: number): Promise<Character | null> {
|
||||||
|
try {
|
||||||
|
return await prisma.character.delete({
|
||||||
|
where: {
|
||||||
|
userId,
|
||||||
|
id: characterId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (error: any) {
|
||||||
|
// Handle error
|
||||||
|
appLogger.error(`Failed to delete character by user ID and character ID: ${error instanceof Error ? error.message : String(error)}`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateCharacterPosition(id: number, positionX: number, positionY: number, rotation: number, zoneId: number) {
|
||||||
|
await prisma.character.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
positionX,
|
||||||
|
positionY,
|
||||||
|
rotation,
|
||||||
|
zoneId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
public updatePosition(character: Character, position: Position, newZoneId?: number): void {
|
public updatePosition(character: Character, position: Position, newZoneId?: number): void {
|
||||||
if (!this.isValidPosition(position)) {
|
if (!this.isValidPosition(position)) {
|
||||||
@ -49,32 +95,10 @@ export class CharacterService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async applyMovementDelay(): Promise<void> {
|
public async applyMovementDelay(): Promise<void> {
|
||||||
await new Promise((resolve) => setTimeout(resolve, CharacterService.MOVEMENT_DELAY_MS))
|
await new Promise((resolve) => setTimeout(resolve, this.MOVEMENT_DELAY_MS))
|
||||||
}
|
}
|
||||||
|
|
||||||
private isValidPosition(position: Position): boolean {
|
private isValidPosition(position: Position): boolean {
|
||||||
return Number.isFinite(position.x) && Number.isFinite(position.y) && position.x >= 0 && position.y >= 0
|
return Number.isFinite(position.x) && Number.isFinite(position.y) && position.x >= 0 && position.y >= 0
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(name: string, userId: number) {
|
|
||||||
return prisma.character.create({
|
|
||||||
data: {
|
|
||||||
name,
|
|
||||||
userId
|
|
||||||
// characterTypeId: 1 // @TODO set to chosen character type
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateCharacterPosition(id: number, positionX: number, positionY: number, rotation: number, zoneId: number) {
|
|
||||||
await prisma.character.update({
|
|
||||||
where: { id },
|
|
||||||
data: {
|
|
||||||
positionX,
|
|
||||||
positionY,
|
|
||||||
rotation,
|
|
||||||
zoneId
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
import { Server } from 'socket.io'
|
||||||
|
import { CharacterHair } from '@prisma/client'
|
||||||
|
import { TSocket } from '../../../utilities/types'
|
||||||
|
import characterHairRepository from '../../../repositories/characterHairRepository'
|
||||||
|
|
||||||
|
interface IPayload {}
|
||||||
|
|
||||||
|
export default class characterHairListEvent {
|
||||||
|
constructor(
|
||||||
|
private readonly io: Server,
|
||||||
|
private readonly socket: TSocket
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public listen(): void {
|
||||||
|
this.socket.on('character:hair:list', this.handleEvent.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleEvent(data: IPayload, callback: (response: CharacterHair[]) => void): Promise<void> {
|
||||||
|
const items = await characterHairRepository.getIsEnabledForCharCreationHair()
|
||||||
|
callback(items)
|
||||||
|
}
|
||||||
|
}
|
@ -3,9 +3,11 @@ import { TSocket } from '../../utilities/types'
|
|||||||
import CharacterRepository from '../../repositories/characterRepository'
|
import CharacterRepository from '../../repositories/characterRepository'
|
||||||
import { gameLogger } from '../../utilities/logger'
|
import { gameLogger } from '../../utilities/logger'
|
||||||
import ZoneManager from '../../managers/zoneManager'
|
import ZoneManager from '../../managers/zoneManager'
|
||||||
|
import { CharacterService } from '../../services/characterService'
|
||||||
|
|
||||||
interface CharacterConnectPayload {
|
interface CharacterConnectPayload {
|
||||||
characterId: number
|
characterId: number
|
||||||
|
characterHairId?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class CharacterConnectEvent {
|
export default class CharacterConnectEvent {
|
||||||
@ -18,7 +20,7 @@ export default class CharacterConnectEvent {
|
|||||||
this.socket.on('character:connect', this.handleCharacterConnect.bind(this))
|
this.socket.on('character:connect', this.handleCharacterConnect.bind(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleCharacterConnect({ characterId }: CharacterConnectPayload): Promise<void> {
|
private async handleCharacterConnect({ characterId, characterHairId }: CharacterConnectPayload): Promise<void> {
|
||||||
if (!this.socket.userId) {
|
if (!this.socket.userId) {
|
||||||
this.emitError('User not authenticated')
|
this.emitError('User not authenticated')
|
||||||
return
|
return
|
||||||
@ -30,7 +32,11 @@ export default class CharacterConnectEvent {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const character = await this.connectCharacter(characterId)
|
// Update hair
|
||||||
|
const characterService = new CharacterService()
|
||||||
|
await characterService.updateHair(characterId, characterHairId ?? null)
|
||||||
|
|
||||||
|
const character = await CharacterRepository.getByUserAndId(this.socket.userId!, characterId)
|
||||||
if (!character) {
|
if (!character) {
|
||||||
this.emitError('Character not found or does not belong to this user')
|
this.emitError('Character not found or does not belong to this user')
|
||||||
return
|
return
|
||||||
@ -48,10 +54,6 @@ export default class CharacterConnectEvent {
|
|||||||
return characters?.some((char) => ZoneManager.getCharacter(char.id)) ?? false
|
return characters?.some((char) => ZoneManager.getCharacter(char.id)) ?? false
|
||||||
}
|
}
|
||||||
|
|
||||||
private async connectCharacter(characterId: number) {
|
|
||||||
return CharacterRepository.getByUserAndId(this.socket.userId!, characterId)
|
|
||||||
}
|
|
||||||
|
|
||||||
private emitError(message: string): void {
|
private emitError(message: string): void {
|
||||||
this.socket.emit('notification', { title: 'Server message', message })
|
this.socket.emit('notification', { title: 'Server message', message })
|
||||||
gameLogger.error('character:connect error', `Player ${this.socket.userId}: ${message}`)
|
gameLogger.error('character:connect error', `Player ${this.socket.userId}: ${message}`)
|
||||||
|
@ -2,7 +2,7 @@ import { Server } from 'socket.io'
|
|||||||
import { TSocket } from '../../utilities/types'
|
import { TSocket } from '../../utilities/types'
|
||||||
import { Character } from '@prisma/client'
|
import { Character } from '@prisma/client'
|
||||||
import CharacterRepository from '../../repositories/characterRepository'
|
import CharacterRepository from '../../repositories/characterRepository'
|
||||||
import { CharacterService } from '../../services/character/characterService'
|
import { CharacterService } from '../../services/characterService'
|
||||||
import { ZCharacterCreate } from '../../utilities/zodTypes'
|
import { ZCharacterCreate } from '../../utilities/zodTypes'
|
||||||
import { gameLogger } from '../../utilities/logger'
|
import { gameLogger } from '../../utilities/logger'
|
||||||
import { ZodError } from 'zod'
|
import { ZodError } from 'zod'
|
||||||
|
@ -2,6 +2,7 @@ import { Server } from 'socket.io'
|
|||||||
import { TSocket } from '../../utilities/types'
|
import { TSocket } from '../../utilities/types'
|
||||||
import { Character, Zone } from '@prisma/client'
|
import { Character, Zone } from '@prisma/client'
|
||||||
import CharacterRepository from '../../repositories/characterRepository'
|
import CharacterRepository from '../../repositories/characterRepository'
|
||||||
|
import { CharacterService } from '../../services/characterService'
|
||||||
|
|
||||||
type TypePayload = {
|
type TypePayload = {
|
||||||
characterId: number
|
characterId: number
|
||||||
@ -24,7 +25,8 @@ export default class CharacterDeleteEvent {
|
|||||||
|
|
||||||
private async handleCharacterDelete(data: TypePayload, callback: (response: TypeResponse) => void): Promise<any> {
|
private async handleCharacterDelete(data: TypePayload, callback: (response: TypeResponse) => void): Promise<any> {
|
||||||
try {
|
try {
|
||||||
await CharacterRepository.deleteByUserIdAndId(this.socket.userId!, data.characterId!)
|
const characterService = new CharacterService()
|
||||||
|
await characterService.deleteByUserIdAndId(this.socket.userId!, data.characterId!)
|
||||||
|
|
||||||
const characters: Character[] = (await CharacterRepository.getByUserId(this.socket.userId!)) as Character[]
|
const characters: Character[] = (await CharacterRepository.getByUserId(this.socket.userId!)) as Character[]
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import ZoneRepository from '../../../repositories/zoneRepository'
|
|||||||
import { gameLogger, gameMasterLogger } from '../../../utilities/logger'
|
import { gameLogger, gameMasterLogger } from '../../../utilities/logger'
|
||||||
import ZoneManager from '../../../managers/zoneManager'
|
import ZoneManager from '../../../managers/zoneManager'
|
||||||
import ZoneCharacter from '../../../models/zoneCharacter'
|
import ZoneCharacter from '../../../models/zoneCharacter'
|
||||||
|
import zoneManager from '../../../managers/zoneManager'
|
||||||
|
|
||||||
type TypePayload = {
|
type TypePayload = {
|
||||||
message: string
|
message: string
|
||||||
@ -64,10 +65,12 @@ export default class TeleportCommandEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove character from current zone
|
// Remove character from current zone
|
||||||
|
zoneManager.removeCharacter(character.id)
|
||||||
this.io.to(character.zoneId.toString()).emit('zone:character:leave', character.id)
|
this.io.to(character.zoneId.toString()).emit('zone:character:leave', character.id)
|
||||||
this.socket.leave(character.zoneId.toString())
|
this.socket.leave(character.zoneId.toString())
|
||||||
|
|
||||||
// Add character to new zone
|
// Add character to new zone
|
||||||
|
zoneManager.getZoneById(zone.id)?.addCharacter(character)
|
||||||
this.io.to(zone.id.toString()).emit('zone:character:join', character)
|
this.io.to(zone.id.toString()).emit('zone:character:join', character)
|
||||||
this.socket.join(zone.id.toString())
|
this.socket.join(zone.id.toString())
|
||||||
|
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
import { Server } from 'socket.io'
|
||||||
|
import { TSocket } from '../../../../utilities/types'
|
||||||
|
import prisma from '../../../../utilities/prisma'
|
||||||
|
import characterRepository from '../../../../repositories/characterRepository'
|
||||||
|
|
||||||
|
export default class CharacterHairCreateEvent {
|
||||||
|
constructor(
|
||||||
|
private readonly io: Server,
|
||||||
|
private readonly socket: TSocket
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public listen(): void {
|
||||||
|
this.socket.on('gm:characterHair:create', this.handleEvent.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleEvent(data: undefined, callback: (response: boolean, characterType?: any) => void): Promise<void> {
|
||||||
|
try {
|
||||||
|
const character = await characterRepository.getById(this.socket.characterId as number)
|
||||||
|
if (!character) return callback(false)
|
||||||
|
|
||||||
|
if (character.role !== 'gm') {
|
||||||
|
return callback(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const newCharacterHair = await prisma.characterHair.create({
|
||||||
|
data: {
|
||||||
|
name: 'New hair'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
callback(true, newCharacterHair)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating character hair:', error)
|
||||||
|
callback(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
import { Server } from 'socket.io'
|
||||||
|
import { TSocket } from '../../../../utilities/types'
|
||||||
|
import prisma from '../../../../utilities/prisma'
|
||||||
|
import characterRepository from '../../../../repositories/characterRepository'
|
||||||
|
import { gameMasterLogger } from '../../../../utilities/logger'
|
||||||
|
|
||||||
|
interface IPayload {
|
||||||
|
id: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class characterHairDeleteEvent {
|
||||||
|
constructor(
|
||||||
|
private readonly io: Server,
|
||||||
|
private readonly socket: TSocket
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public listen(): void {
|
||||||
|
this.socket.on('gm:characterHair:remove', this.handleEvent.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleEvent(data: IPayload, callback: (response: boolean) => void): Promise<void> {
|
||||||
|
const character = await characterRepository.getById(this.socket.characterId as number)
|
||||||
|
if (!character) return callback(false)
|
||||||
|
|
||||||
|
if (character.role !== 'gm') {
|
||||||
|
return callback(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await prisma.characterHair.delete({
|
||||||
|
where: { id: data.id }
|
||||||
|
})
|
||||||
|
|
||||||
|
callback(true)
|
||||||
|
} catch (error) {
|
||||||
|
gameMasterLogger.error(`Error deleting character type ${data.id}: ${error instanceof Error ? error.message : String(error)}`)
|
||||||
|
callback(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
import { Server } from 'socket.io'
|
||||||
|
import { TSocket } from '../../../../utilities/types'
|
||||||
|
import { CharacterHair } from '@prisma/client'
|
||||||
|
import characterRepository from '../../../../repositories/characterRepository'
|
||||||
|
import { gameMasterLogger } from '../../../../utilities/logger'
|
||||||
|
import characterHairRepository from '../../../../repositories/characterHairRepository'
|
||||||
|
|
||||||
|
interface IPayload {}
|
||||||
|
|
||||||
|
export default class characterHairListEvent {
|
||||||
|
constructor(
|
||||||
|
private readonly io: Server,
|
||||||
|
private readonly socket: TSocket
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public listen(): void {
|
||||||
|
this.socket.on('gm:characterHair:list', this.handleEvent.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleEvent(data: IPayload, callback: (response: CharacterHair[]) => void): Promise<void> {
|
||||||
|
const character = await characterRepository.getById(this.socket.characterId as number)
|
||||||
|
if (!character) {
|
||||||
|
gameMasterLogger.error('gm:characterHair:list error', 'Character not found')
|
||||||
|
return callback([])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (character.role !== 'gm') {
|
||||||
|
gameMasterLogger.info(`User ${character.id} tried to list character hair but is not a game master.`)
|
||||||
|
return callback([])
|
||||||
|
}
|
||||||
|
|
||||||
|
// get all objects
|
||||||
|
const items = await characterHairRepository.getAll()
|
||||||
|
callback(items)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
import { Server } from 'socket.io'
|
||||||
|
import { TSocket } from '../../../../utilities/types'
|
||||||
|
import prisma from '../../../../utilities/prisma'
|
||||||
|
import characterRepository from '../../../../repositories/characterRepository'
|
||||||
|
import { CharacterGender, CharacterRace } from '@prisma/client'
|
||||||
|
import { gameMasterLogger } from '../../../../utilities/logger'
|
||||||
|
|
||||||
|
type Payload = {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
gender: CharacterGender
|
||||||
|
isEnabledForCharCreation: boolean
|
||||||
|
spriteId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class CharacterHairUpdateEvent {
|
||||||
|
constructor(
|
||||||
|
private readonly io: Server,
|
||||||
|
private readonly socket: TSocket
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public listen(): void {
|
||||||
|
this.socket.on('gm:characterHair:update', this.handleObjectUpdate.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleObjectUpdate(data: Payload, callback: (success: boolean) => void): Promise<void> {
|
||||||
|
const character = await characterRepository.getById(this.socket.characterId as number)
|
||||||
|
if (!character) return callback(false)
|
||||||
|
|
||||||
|
if (character.role !== 'gm') {
|
||||||
|
return callback(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await prisma.characterHair.update({
|
||||||
|
where: { id: data.id },
|
||||||
|
data: {
|
||||||
|
name: data.name,
|
||||||
|
gender: data.gender,
|
||||||
|
isEnabledForCharCreation: data.isEnabledForCharCreation,
|
||||||
|
spriteId: data.spriteId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return callback(true)
|
||||||
|
} catch (error) {
|
||||||
|
gameMasterLogger.error(`Error updating character hair: ${error instanceof Error ? error.message : String(error)}`)
|
||||||
|
return callback(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,7 @@
|
|||||||
import fs from 'fs'
|
|
||||||
import { Server } from 'socket.io'
|
import { Server } from 'socket.io'
|
||||||
import { TSocket } from '../../../../utilities/types'
|
import { TSocket } from '../../../../utilities/types'
|
||||||
import prisma from '../../../../utilities/prisma'
|
import prisma from '../../../../utilities/prisma'
|
||||||
import characterRepository from '../../../../repositories/characterRepository'
|
import characterRepository from '../../../../repositories/characterRepository'
|
||||||
import { getPublicPath } from '../../../../utilities/storage'
|
|
||||||
import { gameMasterLogger } from '../../../../utilities/logger'
|
import { gameMasterLogger } from '../../../../utilities/logger'
|
||||||
|
|
||||||
interface IPayload {
|
interface IPayload {
|
||||||
|
@ -8,11 +8,34 @@ import { getPublicPath } from '../../../../utilities/storage'
|
|||||||
import CharacterRepository from '../../../../repositories/characterRepository'
|
import CharacterRepository from '../../../../repositories/characterRepository'
|
||||||
import { gameMasterLogger } from '../../../../utilities/logger'
|
import { gameMasterLogger } from '../../../../utilities/logger'
|
||||||
|
|
||||||
type SpriteActionInput = Omit<SpriteAction, 'id' | 'spriteId' | 'frameWidth' | 'frameHeight'> & {
|
// Constants
|
||||||
|
const ISOMETRIC_CONFIG = {
|
||||||
|
tileWidth: 64,
|
||||||
|
tileHeight: 32,
|
||||||
|
centerOffset: 32,
|
||||||
|
bodyRatios: {
|
||||||
|
topStart: 0.15,
|
||||||
|
topEnd: 0.45,
|
||||||
|
weightUpper: 0.7,
|
||||||
|
weightLower: 0.3
|
||||||
|
}
|
||||||
|
} as const
|
||||||
|
|
||||||
|
// Types
|
||||||
|
interface ContentBounds {
|
||||||
|
left: number
|
||||||
|
right: number
|
||||||
|
top: number
|
||||||
|
bottom: number
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SpriteActionInput extends Omit<SpriteAction, 'id' | 'spriteId' | 'frameWidth' | 'frameHeight'> {
|
||||||
sprites: string[]
|
sprites: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
type Payload = {
|
interface UpdatePayload {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
spriteActions: Prisma.JsonValue
|
spriteActions: Prisma.JsonValue
|
||||||
@ -21,11 +44,19 @@ type Payload = {
|
|||||||
interface ProcessedSpriteAction extends SpriteActionInput {
|
interface ProcessedSpriteAction extends SpriteActionInput {
|
||||||
frameWidth: number
|
frameWidth: number
|
||||||
frameHeight: number
|
frameHeight: number
|
||||||
buffersWithDimensions: Array<{
|
buffersWithDimensions: ProcessedFrame[]
|
||||||
buffer: Buffer
|
}
|
||||||
width: number | undefined
|
|
||||||
height: number | undefined
|
interface ProcessedFrame {
|
||||||
}>
|
buffer: Buffer
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SpriteAnalysis {
|
||||||
|
massCenter: number
|
||||||
|
spinePosition: number
|
||||||
|
contentBounds: ContentBounds
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class SpriteUpdateEvent {
|
export default class SpriteUpdateEvent {
|
||||||
@ -38,119 +69,322 @@ export default class SpriteUpdateEvent {
|
|||||||
this.socket.on('gm:sprite:update', this.handleSpriteUpdate.bind(this))
|
this.socket.on('gm:sprite:update', this.handleSpriteUpdate.bind(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleSpriteUpdate(data: Payload, callback: (success: boolean) => void): Promise<void> {
|
private async handleSpriteUpdate(payload: UpdatePayload, callback: (success: boolean) => void): Promise<void> {
|
||||||
const character = await CharacterRepository.getById(this.socket.characterId!)
|
|
||||||
if (character?.role !== 'gm') {
|
|
||||||
return callback(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const parsedSpriteActions = validateSpriteActions(data.spriteActions)
|
if (!await this.validateGameMasterAccess()) {
|
||||||
const processedActions = await processSprites(parsedSpriteActions)
|
return callback(false)
|
||||||
|
}
|
||||||
|
|
||||||
await updateDatabase(data.id, data.name, processedActions)
|
const parsedActions = this.validateSpriteActions(payload.spriteActions)
|
||||||
await saveSpritesToDisk(data.id, processedActions)
|
const processedActions = await this.processSprites(parsedActions)
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
this.updateDatabase(payload.id, payload.name, processedActions),
|
||||||
|
this.saveSpritesToDisk(payload.id, processedActions)
|
||||||
|
])
|
||||||
|
|
||||||
callback(true)
|
callback(true)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
gameMasterLogger.error(`Error updating sprite ${data.id}: ${error instanceof Error ? error.message : String(error)}`)
|
this.handleError(error, payload.id, callback)
|
||||||
callback(false)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function validateSpriteActions(spriteActions: Prisma.JsonValue): SpriteActionInput[] {
|
private async validateGameMasterAccess(): Promise<boolean> {
|
||||||
try {
|
const character = await CharacterRepository.getById(this.socket.characterId!)
|
||||||
const parsed = JSON.parse(JSON.stringify(spriteActions)) as SpriteActionInput[]
|
return character?.role === 'gm'
|
||||||
if (!Array.isArray(parsed)) {
|
}
|
||||||
gameMasterLogger.error('Error parsing spriteActions: spriteActions is not an array')
|
|
||||||
|
private validateSpriteActions(actions: Prisma.JsonValue): SpriteActionInput[] {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(JSON.stringify(actions)) as SpriteActionInput[]
|
||||||
|
if (!Array.isArray(parsed)) {
|
||||||
|
throw new Error('Sprite actions must be an array')
|
||||||
|
}
|
||||||
|
return parsed
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Invalid sprite actions format: ${this.getErrorMessage(error)}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async processSprites(actions: SpriteActionInput[]): Promise<ProcessedSpriteAction[]> {
|
||||||
|
return Promise.all(actions.map(async (action) => {
|
||||||
|
const spriteBuffers = await this.convertBase64ToBuffers(action.sprites)
|
||||||
|
const frameWidth = ISOMETRIC_CONFIG.tileWidth
|
||||||
|
const frameHeight = await this.calculateOptimalHeight(spriteBuffers)
|
||||||
|
const processedFrames = await this.normalizeFrames(spriteBuffers, frameWidth, frameHeight)
|
||||||
|
|
||||||
|
return {
|
||||||
|
...action,
|
||||||
|
frameWidth,
|
||||||
|
frameHeight,
|
||||||
|
buffersWithDimensions: processedFrames
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
private async convertBase64ToBuffers(sprites: string[]): Promise<Buffer[]> {
|
||||||
|
return sprites.map(sprite => Buffer.from(sprite.split(',')[1], 'base64'))
|
||||||
|
}
|
||||||
|
|
||||||
|
private async normalizeFrames(buffers: Buffer[], frameWidth: number, frameHeight: number): Promise<ProcessedFrame[]> {
|
||||||
|
return Promise.all(
|
||||||
|
buffers.map(async (buffer) => {
|
||||||
|
const normalizedBuffer = await this.normalizeIsometricSprite(buffer, frameWidth, frameHeight)
|
||||||
|
return {
|
||||||
|
buffer: normalizedBuffer,
|
||||||
|
width: frameWidth,
|
||||||
|
height: frameHeight
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private async calculateOptimalHeight(buffers: Buffer[]): Promise<number> {
|
||||||
|
const heights = await Promise.all(
|
||||||
|
buffers.map(async buffer => {
|
||||||
|
const bounds = await this.findContentBounds(buffer)
|
||||||
|
return bounds.height
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return Math.ceil(Math.max(...heights) / 2) * 2
|
||||||
|
}
|
||||||
|
|
||||||
|
private async normalizeIsometricSprite(buffer: Buffer, frameWidth: number, frameHeight: number): Promise<Buffer> {
|
||||||
|
const analysis = await this.analyzeIsometricSprite(buffer)
|
||||||
|
const idealCenter = Math.floor(frameWidth / 2)
|
||||||
|
const offset = Math.round(idealCenter - analysis.massCenter)
|
||||||
|
|
||||||
|
// Process the input sprite
|
||||||
|
const processedInput = await sharp(buffer)
|
||||||
|
.ensureAlpha()
|
||||||
|
.resize({
|
||||||
|
kernel: sharp.kernel.nearest,
|
||||||
|
fit: 'contain',
|
||||||
|
position: 'center'
|
||||||
|
})
|
||||||
|
.png({
|
||||||
|
compressionLevel: 9,
|
||||||
|
adaptiveFiltering: false,
|
||||||
|
palette: true,
|
||||||
|
quality: 100,
|
||||||
|
colors: 256
|
||||||
|
})
|
||||||
|
.toBuffer()
|
||||||
|
|
||||||
|
// Create the final composition
|
||||||
|
return sharp({
|
||||||
|
create: {
|
||||||
|
width: frameWidth,
|
||||||
|
height: frameHeight,
|
||||||
|
channels: 4,
|
||||||
|
background: { r: 0, g: 0, b: 0, alpha: 0 }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.composite([{
|
||||||
|
input: processedInput,
|
||||||
|
left: offset,
|
||||||
|
top: 0,
|
||||||
|
blend: 'over'
|
||||||
|
}])
|
||||||
|
.png({
|
||||||
|
compressionLevel: 9,
|
||||||
|
adaptiveFiltering: false,
|
||||||
|
palette: true,
|
||||||
|
quality: 100,
|
||||||
|
colors: 256
|
||||||
|
})
|
||||||
|
.toBuffer()
|
||||||
|
}
|
||||||
|
|
||||||
|
private async analyzeIsometricSprite(buffer: Buffer): Promise<SpriteAnalysis> {
|
||||||
|
const { data, info } = await sharp(buffer).raw().ensureAlpha().toBuffer({ resolveWithObject: true })
|
||||||
|
const { width, height } = info
|
||||||
|
const upperStart = Math.floor(height * ISOMETRIC_CONFIG.bodyRatios.topStart)
|
||||||
|
const upperEnd = Math.floor(height * ISOMETRIC_CONFIG.bodyRatios.topEnd)
|
||||||
|
|
||||||
|
const { columnDensity, upperBodyDensity, bounds } = this.calculatePixelDistribution(data, width, height, upperStart, upperEnd)
|
||||||
|
const spinePosition = this.findSpinePosition(upperBodyDensity)
|
||||||
|
const massCenter = this.calculateWeightedMassCenter(columnDensity, upperBodyDensity)
|
||||||
|
|
||||||
|
return {
|
||||||
|
massCenter,
|
||||||
|
spinePosition,
|
||||||
|
contentBounds: bounds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private calculatePixelDistribution(data: Buffer, width: number, height: number, upperStart: number, upperEnd: number) {
|
||||||
|
const columnDensity = new Array(width).fill(0)
|
||||||
|
const upperBodyDensity = new Array(width).fill(0)
|
||||||
|
const bounds = { left: width, right: 0, top: height, bottom: 0 }
|
||||||
|
|
||||||
|
for (let y = 0; y < height; y++) {
|
||||||
|
for (let x = 0; x < width; x++) {
|
||||||
|
if (data[(y * width + x) * 4 + 3] > 0) {
|
||||||
|
columnDensity[x]++
|
||||||
|
if (y >= upperStart && y <= upperEnd) {
|
||||||
|
upperBodyDensity[x]++
|
||||||
|
}
|
||||||
|
this.updateBounds(bounds, x, y)
|
||||||
}
|
}
|
||||||
return parsed
|
|
||||||
} catch (error) {
|
|
||||||
gameMasterLogger.error(`Error parsing spriteActions: ${error instanceof Error ? error.message : String(error)}`)
|
|
||||||
throw error
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processSprites(spriteActions: SpriteActionInput[]): Promise<ProcessedSpriteAction[]> {
|
return {
|
||||||
return Promise.all(
|
columnDensity,
|
||||||
spriteActions.map(async (spriteAction) => {
|
upperBodyDensity,
|
||||||
const { action, sprites } = spriteAction
|
bounds: {
|
||||||
|
...bounds,
|
||||||
if (!Array.isArray(sprites) || sprites.length === 0) {
|
width: bounds.right - bounds.left + 1,
|
||||||
gameMasterLogger.error(`Invalid sprites array for action: ${action}`)
|
height: bounds.bottom - bounds.top + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
const buffersWithDimensions = await Promise.all(
|
|
||||||
sprites.map(async (sprite: string) => {
|
|
||||||
const buffer = Buffer.from(sprite.split(',')[1], 'base64')
|
|
||||||
const { width, height } = await sharp(buffer).metadata()
|
|
||||||
return { buffer, width, height }
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
const frameWidth = Math.max(...buffersWithDimensions.map((b) => b.width || 0))
|
|
||||||
const frameHeight = Math.max(...buffersWithDimensions.map((b) => b.height || 0))
|
|
||||||
|
|
||||||
return {
|
|
||||||
...spriteAction,
|
|
||||||
frameWidth,
|
|
||||||
frameHeight,
|
|
||||||
buffersWithDimensions
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateDatabase(id: string, name: string, processedActions: ProcessedSpriteAction[]) {
|
|
||||||
await prisma.sprite.update({
|
|
||||||
where: { id },
|
|
||||||
data: {
|
|
||||||
name,
|
|
||||||
spriteActions: {
|
|
||||||
deleteMany: { spriteId: id },
|
|
||||||
create: processedActions.map(({ action, sprites, originX, originY, isAnimated, isLooping, frameWidth, frameHeight, frameSpeed }) => ({
|
|
||||||
action,
|
|
||||||
sprites,
|
|
||||||
originX,
|
|
||||||
originY,
|
|
||||||
isAnimated,
|
|
||||||
isLooping,
|
|
||||||
frameWidth,
|
|
||||||
frameHeight,
|
|
||||||
frameSpeed
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function saveSpritesToDisk(id: string, processedActions: ProcessedSpriteAction[]) {
|
|
||||||
const publicFolder = getPublicPath('sprites', id)
|
|
||||||
await mkdir(publicFolder, { recursive: true })
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
processedActions.map(async ({ action, buffersWithDimensions, frameWidth, frameHeight }) => {
|
|
||||||
const combinedImage = await sharp({
|
|
||||||
create: {
|
|
||||||
width: frameWidth * buffersWithDimensions.length,
|
|
||||||
height: frameHeight,
|
|
||||||
channels: 4,
|
|
||||||
background: { r: 0, g: 0, b: 0, alpha: 0 }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.composite(
|
|
||||||
buffersWithDimensions.map(({ buffer }, index) => ({
|
|
||||||
input: buffer,
|
|
||||||
left: index * frameWidth,
|
|
||||||
top: 0
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
.png()
|
|
||||||
.toBuffer()
|
|
||||||
|
|
||||||
const filename = getPublicPath('sprites', id, `${action}.png`)
|
|
||||||
await writeFile(filename, combinedImage)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private updateBounds(bounds: { left: number; right: number; top: number; bottom: number }, x: number, y: number): void {
|
||||||
|
bounds.left = Math.min(bounds.left, x)
|
||||||
|
bounds.right = Math.max(bounds.right, x)
|
||||||
|
bounds.top = Math.min(bounds.top, y)
|
||||||
|
bounds.bottom = Math.max(bounds.bottom, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
private findSpinePosition(density: number[]): number {
|
||||||
|
return density.reduce((maxIdx, curr, idx, arr) => curr > arr[maxIdx] ? idx : maxIdx, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
private calculateWeightedMassCenter(columnDensity: number[], upperBodyDensity: number[]): number {
|
||||||
|
const upperMassCenter = this.calculateMassCenter(upperBodyDensity)
|
||||||
|
const lowerMassCenter = this.calculateMassCenter(columnDensity)
|
||||||
|
|
||||||
|
return Math.round(
|
||||||
|
upperMassCenter * ISOMETRIC_CONFIG.bodyRatios.weightUpper +
|
||||||
|
lowerMassCenter * ISOMETRIC_CONFIG.bodyRatios.weightLower
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private calculateMassCenter(density: number[]): number {
|
||||||
|
const totalMass = density.reduce((sum, mass) => sum + mass, 0)
|
||||||
|
if (!totalMass) return 0
|
||||||
|
|
||||||
|
const weightedSum = density.reduce((sum, mass, position) => sum + position * mass, 0)
|
||||||
|
return Math.round(weightedSum / totalMass)
|
||||||
|
}
|
||||||
|
|
||||||
|
private async findContentBounds(buffer: Buffer) {
|
||||||
|
const { data, info } = await sharp(buffer)
|
||||||
|
.raw()
|
||||||
|
.ensureAlpha()
|
||||||
|
.toBuffer({ resolveWithObject: true })
|
||||||
|
|
||||||
|
const width = info.width
|
||||||
|
const height = info.height
|
||||||
|
|
||||||
|
let left = width
|
||||||
|
let right = 0
|
||||||
|
let top = height
|
||||||
|
let bottom = 0
|
||||||
|
|
||||||
|
// Find actual content boundaries by checking alpha channel
|
||||||
|
for (let y = 0; y < height; y++) {
|
||||||
|
for (let x = 0; x < width; x++) {
|
||||||
|
const idx = (y * width + x) * 4
|
||||||
|
if (data[idx + 3] > 0) { // If pixel is not transparent
|
||||||
|
left = Math.min(left, x)
|
||||||
|
right = Math.max(right, x)
|
||||||
|
top = Math.min(top, y)
|
||||||
|
bottom = Math.max(bottom, y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
width: right - left + 1,
|
||||||
|
height: bottom - top + 1,
|
||||||
|
leftOffset: left,
|
||||||
|
topOffset: top
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async saveSpritesToDisk(id: string, actions: ProcessedSpriteAction[]): Promise<void> {
|
||||||
|
const publicFolder = getPublicPath('sprites', id)
|
||||||
|
await mkdir(publicFolder, { recursive: true })
|
||||||
|
|
||||||
|
await Promise.all(actions.map(async (action) => {
|
||||||
|
const spritesheet = await this.createSpritesheet(action.buffersWithDimensions)
|
||||||
|
await writeFile(getPublicPath('sprites', id, `${action.action}.png`), spritesheet)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
private async createSpritesheet(frames: ProcessedFrame[]): Promise<Buffer> {
|
||||||
|
const background = await sharp({
|
||||||
|
create: {
|
||||||
|
width: ISOMETRIC_CONFIG.tileWidth * frames.length,
|
||||||
|
height: frames[0].height,
|
||||||
|
channels: 4,
|
||||||
|
background: { r: 0, g: 0, b: 0, alpha: 0 }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.png({
|
||||||
|
compressionLevel: 9,
|
||||||
|
adaptiveFiltering: false,
|
||||||
|
palette: true,
|
||||||
|
quality: 100,
|
||||||
|
colors: 256,
|
||||||
|
dither: 0
|
||||||
|
})
|
||||||
|
.toBuffer()
|
||||||
|
|
||||||
|
return sharp(background)
|
||||||
|
.composite(frames.map((frame, index) => ({
|
||||||
|
input: frame.buffer,
|
||||||
|
left: index * ISOMETRIC_CONFIG.tileWidth,
|
||||||
|
top: 0,
|
||||||
|
blend: 'over'
|
||||||
|
})))
|
||||||
|
.png({
|
||||||
|
compressionLevel: 9,
|
||||||
|
adaptiveFiltering: false,
|
||||||
|
palette: true,
|
||||||
|
quality: 100,
|
||||||
|
colors: 256,
|
||||||
|
dither: 0
|
||||||
|
})
|
||||||
|
.toBuffer()
|
||||||
|
}
|
||||||
|
|
||||||
|
private async updateDatabase(id: string, name: string, actions: ProcessedSpriteAction[]): Promise<void> {
|
||||||
|
await prisma.sprite.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
name,
|
||||||
|
spriteActions: {
|
||||||
|
deleteMany: { spriteId: id },
|
||||||
|
create: actions.map(this.mapActionToDatabase)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapActionToDatabase(action: ProcessedSpriteAction) {
|
||||||
|
return {
|
||||||
|
action: action.action,
|
||||||
|
sprites: action.sprites,
|
||||||
|
originX: action.originX,
|
||||||
|
originY: action.originY,
|
||||||
|
isAnimated: action.isAnimated,
|
||||||
|
isLooping: action.isLooping,
|
||||||
|
frameWidth: action.frameWidth,
|
||||||
|
frameHeight: action.frameHeight,
|
||||||
|
frameSpeed: action.frameSpeed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleError(error: unknown, spriteId: string, callback: (success: boolean) => void): void {
|
||||||
|
gameMasterLogger.error(`Error updating sprite ${spriteId}: ${this.getErrorMessage(error)}`)
|
||||||
|
callback(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private getErrorMessage(error: unknown): string {
|
||||||
|
return error instanceof Error ? error.message : String(error)
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import { Server } from 'socket.io'
|
import { Server } from 'socket.io'
|
||||||
import { TSocket, ZoneEventTileWithTeleport } from '../../utilities/types'
|
import { TSocket, ZoneEventTileWithTeleport } from '../../utilities/types'
|
||||||
import { CharacterService } from '../../services/character/characterService'
|
import { CharacterService } from '../../services/characterService'
|
||||||
import { ZoneEventTileService } from '../../services/zoneEventTileService'
|
import { ZoneEventTileService } from '../../services/zoneEventTileService'
|
||||||
import Rotation from '../../utilities/character/rotation'
|
import Rotation from '../../utilities/character/rotation'
|
||||||
import { gameLogger } from '../../utilities/logger'
|
import { gameLogger } from '../../utilities/logger'
|
||||||
|
Reference in New Issue
Block a user