Compare commits
48 Commits
d486fe9594
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 3325c63631 | |||
| 3a12b6a4ab | |||
| 9ea2278dfb | |||
| a4391c84d0 | |||
| ee6dc58a21 | |||
| 0d3995764b | |||
| f30c41afba | |||
| 2ef9968920 | |||
| d8db9f650b | |||
| 349510db56 | |||
| b44834343a | |||
| b41c01e6f7 | |||
| 9ad991a70e | |||
| 6806eeff8a | |||
| 3c3c880a3c | |||
| 542f24c12d | |||
| 23e6da2808 | |||
| 61604355c1 | |||
| b4d52283aa | |||
| b24753afe7 | |||
| 89cb09adb6 | |||
| 716e25f0ba | |||
| d20f2a37c4 | |||
| 701c36112c | |||
| e5933104cc | |||
| 46ec236ed5 | |||
| b8b35645ac | |||
| ed3c116d13 | |||
| 719108fd6a | |||
| f88cd21b33 | |||
| fd32ae5dcc | |||
| 9fec45a91f | |||
| ba3ad023ad | |||
| ed90250876 | |||
| cdceaab2fd | |||
| 70aa8adbba | |||
| cd9021d9c0 | |||
| 0ee2e7e545 | |||
| 8961490ff8 | |||
| d1039a409b | |||
| cca0de9812 | |||
| 3b37d7d798 | |||
| e2f55f0b8b | |||
| 56d2bd17e4 | |||
| c68d9ba749 | |||
| dc49c0a958 | |||
| 853b7069f9 | |||
| dfc1f269a0 |
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"kendoai": {
|
||||||
|
"type": "stdio",
|
||||||
|
"command": "npx",
|
||||||
|
"args": ["-y", "@progress/kendo-angular-mcp@latest"],
|
||||||
|
"env": {
|
||||||
|
"TELERIK_LICENSE_PATH":"C:/Users/Chris/AppData/Roaming/Telerik/telerik-license.txt"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
|
/.angular/cache
|
||||||
/.sass-cache
|
/.sass-cache
|
||||||
/connect.lock
|
/connect.lock
|
||||||
/coverage
|
/coverage
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"development"
|
||||||
|
],
|
||||||
|
"hints": {
|
||||||
|
"axe/text-alternatives": [
|
||||||
|
"default",
|
||||||
|
{
|
||||||
|
"image-alt": "off"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"defaults",
|
||||||
|
"not ie 11",
|
||||||
|
"not ie <= 11"
|
||||||
|
]
|
||||||
|
}
|
||||||
+12
-51
@@ -24,8 +24,8 @@
|
|||||||
"src/web.config",
|
"src/web.config",
|
||||||
{
|
{
|
||||||
"glob": "**/*",
|
"glob": "**/*",
|
||||||
"input": "node_modules/leaflet/dist/images",
|
"input": "node_modules/tinymce",
|
||||||
"output": "/assets/img/markers"
|
"output": "/tinymce/"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"styles": [
|
"styles": [
|
||||||
@@ -38,16 +38,13 @@
|
|||||||
"node_modules/nebular-icons/scss/nebular-icons.scss",
|
"node_modules/nebular-icons/scss/nebular-icons.scss",
|
||||||
"node_modules/pace-js/templates/pace-theme-flash.tmpl.css",
|
"node_modules/pace-js/templates/pace-theme-flash.tmpl.css",
|
||||||
"node_modules/leaflet/dist/leaflet.css",
|
"node_modules/leaflet/dist/leaflet.css",
|
||||||
|
"node_modules/@progress/kendo-theme-default/dist/all.css",
|
||||||
"src/app/@theme/styles/styles.scss",
|
"src/app/@theme/styles/styles.scss",
|
||||||
"src/assets/styles/site.scss"
|
"src/assets/styles/site.scss"
|
||||||
],
|
],
|
||||||
"scripts": [
|
"scripts": [
|
||||||
"node_modules/pace-js/pace.min.js",
|
"node_modules/pace-js/pace.min.js",
|
||||||
"node_modules/tinymce/tinymce.min.js",
|
"node_modules/tinymce/tinymce.min.js",
|
||||||
"node_modules/tinymce/themes/modern/theme.min.js",
|
|
||||||
"node_modules/tinymce/plugins/link/plugin.min.js",
|
|
||||||
"node_modules/tinymce/plugins/paste/plugin.min.js",
|
|
||||||
"node_modules/tinymce/plugins/table/plugin.min.js",
|
|
||||||
"node_modules/echarts/dist/echarts.min.js",
|
"node_modules/echarts/dist/echarts.min.js",
|
||||||
"node_modules/echarts/dist/extension/bmap.min.js",
|
"node_modules/echarts/dist/extension/bmap.min.js",
|
||||||
"node_modules/chart.js/dist/Chart.min.js"
|
"node_modules/chart.js/dist/Chart.min.js"
|
||||||
@@ -57,7 +54,10 @@
|
|||||||
"echarts",
|
"echarts",
|
||||||
"lodash",
|
"lodash",
|
||||||
"zrender/lib/svg/svg",
|
"zrender/lib/svg/svg",
|
||||||
"zrender/lib/vml/vml"
|
"zrender/lib/vml/vml",
|
||||||
|
"file-saver",
|
||||||
|
"eva-icons",
|
||||||
|
"rxjs-compat"
|
||||||
],
|
],
|
||||||
"vendorChunk": true,
|
"vendorChunk": true,
|
||||||
"extractLicenses": false,
|
"extractLicenses": false,
|
||||||
@@ -94,18 +94,18 @@
|
|||||||
"serve": {
|
"serve": {
|
||||||
"builder": "@angular-devkit/build-angular:dev-server",
|
"builder": "@angular-devkit/build-angular:dev-server",
|
||||||
"options": {
|
"options": {
|
||||||
"browserTarget": "ngx-admin-demo:build"
|
"buildTarget": "ngx-admin-demo:build"
|
||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
"production": {
|
"production": {
|
||||||
"browserTarget": "ngx-admin-demo:build:production"
|
"buildTarget": "ngx-admin-demo:build:production"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"extract-i18n": {
|
"extract-i18n": {
|
||||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||||
"options": {
|
"options": {
|
||||||
"browserTarget": "ngx-admin-demo:build"
|
"buildTarget": "ngx-admin-demo:build"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"test": {
|
"test": {
|
||||||
@@ -118,10 +118,6 @@
|
|||||||
"scripts": [
|
"scripts": [
|
||||||
"node_modules/pace-js/pace.min.js",
|
"node_modules/pace-js/pace.min.js",
|
||||||
"node_modules/tinymce/tinymce.min.js",
|
"node_modules/tinymce/tinymce.min.js",
|
||||||
"node_modules/tinymce/themes/modern/theme.min.js",
|
|
||||||
"node_modules/tinymce/plugins/link/plugin.min.js",
|
|
||||||
"node_modules/tinymce/plugins/paste/plugin.min.js",
|
|
||||||
"node_modules/tinymce/plugins/table/plugin.min.js",
|
|
||||||
"node_modules/echarts/dist/echarts.min.js",
|
"node_modules/echarts/dist/echarts.min.js",
|
||||||
"node_modules/echarts/dist/extension/bmap.min.js",
|
"node_modules/echarts/dist/extension/bmap.min.js",
|
||||||
"node_modules/chart.js/dist/Chart.min.js"
|
"node_modules/chart.js/dist/Chart.min.js"
|
||||||
@@ -144,50 +140,15 @@
|
|||||||
"src/web.config",
|
"src/web.config",
|
||||||
{
|
{
|
||||||
"glob": "**/*",
|
"glob": "**/*",
|
||||||
"input": "node_modules/leaflet/dist/images",
|
"input": "node_modules/tinymce",
|
||||||
"output": "/assets/img/markers"
|
"output": "/tinymce/"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"lint": {
|
|
||||||
"builder": "@angular-devkit/build-angular:tslint",
|
|
||||||
"options": {
|
|
||||||
"tsConfig": [
|
|
||||||
"src/tsconfig.app.json",
|
|
||||||
"src/tsconfig.spec.json"
|
|
||||||
],
|
|
||||||
"typeCheck": true,
|
|
||||||
"exclude": []
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ngx-admin-demo-e2e": {
|
|
||||||
"root": "",
|
|
||||||
"sourceRoot": "",
|
|
||||||
"projectType": "application",
|
|
||||||
"architect": {
|
|
||||||
"e2e": {
|
|
||||||
"builder": "@angular-devkit/build-angular:protractor",
|
|
||||||
"options": {
|
|
||||||
"protractorConfig": "./protractor.conf.js",
|
|
||||||
"devServerTarget": "ngx-admin-demo:serve"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"lint": {
|
|
||||||
"builder": "@angular-devkit/build-angular:tslint",
|
|
||||||
"options": {
|
|
||||||
"tsConfig": [
|
|
||||||
"e2e/tsconfig.e2e.json"
|
|
||||||
],
|
|
||||||
"exclude": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"defaultProject": "ngx-admin-demo",
|
|
||||||
"schematics": {
|
"schematics": {
|
||||||
"@schematics/angular:component": {
|
"@schematics/angular:component": {
|
||||||
"prefix": "ngx",
|
"prefix": "ngx",
|
||||||
|
|||||||
Generated
+28734
-14732
File diff suppressed because it is too large
Load Diff
+43
-41
@@ -12,9 +12,9 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
"conventional-changelog": "conventional-changelog",
|
"conventional-changelog": "conventional-changelog",
|
||||||
"start": "ng serve",
|
"start": "ng serve --host=127.0.0.1",
|
||||||
"build": "ng build --output-path \\\\ArkNAS\\docker\\nginx-proxy\\data\\ChurchAngular --configuration production",
|
"build": "ng build",
|
||||||
"build:prod": "npm run build -- --configuration production --aot",
|
"build:prod": "ng build --output-path \\\\ArkNAS\\docker\\nginx-proxy\\data\\ChurchAngular --configuration production --aot",
|
||||||
"test": "ng test",
|
"test": "ng test",
|
||||||
"test:coverage": "rimraf coverage && npm run test -- --code-coverage",
|
"test:coverage": "rimraf coverage && npm run test -- --code-coverage",
|
||||||
"lint": "ng lint",
|
"lint": "ng lint",
|
||||||
@@ -25,35 +25,41 @@
|
|||||||
"e2e": "ng e2e",
|
"e2e": "ng e2e",
|
||||||
"docs": "compodoc -p src/tsconfig.app.json -d docs",
|
"docs": "compodoc -p src/tsconfig.app.json -d docs",
|
||||||
"docs:serve": "compodoc -p src/tsconfig.app.json -d docs -s",
|
"docs:serve": "compodoc -p src/tsconfig.app.json -d docs -s",
|
||||||
"prepush": "npm run lint:ci",
|
"prepush": "echo 'Pre-push hook disabled - style linting skipped'",
|
||||||
"release:changelog": "npm run conventional-changelog -- -p angular -i CHANGELOG.md -s",
|
"release:changelog": "npm run conventional-changelog -- -p angular -i CHANGELOG.md -s"
|
||||||
"postinstall": "ngcc --properties es2015 es5 browser module main --first-only --create-ivy-entry-points --tsconfig \"./src/tsconfig.app.json\""
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^12.2.16",
|
"@angular/animations": "^17.3.3",
|
||||||
"@angular/cdk": "12.1.0",
|
"@angular/cdk": "17.3.3",
|
||||||
"@angular/common": "^12.2.16",
|
"@angular/common": "^17.3.3",
|
||||||
"@angular/compiler": "^12.2.16",
|
"@angular/compiler": "^17.3.3",
|
||||||
"@angular/core": "^12.2.16",
|
"@angular/core": "^17.3.3",
|
||||||
"@angular/forms": "^12.2.16",
|
"@angular/forms": "^17.3.3",
|
||||||
"@angular/google-maps": "^12.2.13",
|
"@angular/platform-browser": "^17.3.3",
|
||||||
"@angular/platform-browser": "^12.2.16",
|
"@angular/platform-browser-dynamic": "^17.3.3",
|
||||||
"@angular/platform-browser-dynamic": "^12.2.16",
|
"@angular/router": "^17.3.3",
|
||||||
"@angular/router": "^12.2.16",
|
|
||||||
"@asymmetrik/ngx-leaflet": "3.0.1",
|
"@asymmetrik/ngx-leaflet": "3.0.1",
|
||||||
"@microsoft/signalr": "^6.0.8",
|
"@microsoft/signalr": "^6.0.8",
|
||||||
"@nebular/auth": "8.0.0",
|
"@nebular/auth": "13.0.0",
|
||||||
"@nebular/date-fns": "^9.0.3",
|
"@nebular/date-fns": "^13.0.0",
|
||||||
"@nebular/eva-icons": "8.0.0",
|
"@nebular/eva-icons": "13.0.0",
|
||||||
"@nebular/security": "8.0.0",
|
"@nebular/security": "13.0.0",
|
||||||
"@nebular/theme": "8.0.0",
|
"@nebular/theme": "13.0.0",
|
||||||
"@swimlane/ngx-charts": "^14.0.0",
|
"@progress/kendo-angular-buttons": "^20.1.1",
|
||||||
|
"@progress/kendo-angular-dialog": "^20.1.1",
|
||||||
|
"@progress/kendo-angular-dropdowns": "^20.1.1",
|
||||||
|
"@progress/kendo-angular-editor": "^20.1.1",
|
||||||
|
"@progress/kendo-angular-grid": "^20.1.1",
|
||||||
|
"@progress/kendo-angular-inputs": "^20.1.1",
|
||||||
|
"@progress/kendo-angular-toolbar": "^20.1.1",
|
||||||
|
"@progress/kendo-licensing": "^1.7.1",
|
||||||
|
"@progress/kendo-svg-icons": "^4.5.0",
|
||||||
|
"@progress/kendo-theme-default": "^12.2.0",
|
||||||
|
"@tinymce/tinymce-angular": "^7.0.0",
|
||||||
"angular2-chartjs": "0.4.1",
|
"angular2-chartjs": "0.4.1",
|
||||||
"angular2-qrcode": "^2.0.3",
|
"angular2-qrcode": "^2.0.3",
|
||||||
"bootstrap": "4.3.1",
|
"bootstrap": "4.3.1",
|
||||||
"chart.js": "2.7.1",
|
"chart.js": "2.7.1",
|
||||||
"ckeditor": "4.7.3",
|
|
||||||
"classlist.js": "1.1.20150312",
|
|
||||||
"core-js": "2.5.1",
|
"core-js": "2.5.1",
|
||||||
"echarts": "^4.9.0",
|
"echarts": "^4.9.0",
|
||||||
"eva-icons": "^1.1.3",
|
"eva-icons": "^1.1.3",
|
||||||
@@ -63,11 +69,9 @@
|
|||||||
"leaflet": "1.2.0",
|
"leaflet": "1.2.0",
|
||||||
"nebular-icons": "1.1.0",
|
"nebular-icons": "1.1.0",
|
||||||
"ng-in-viewport": "^13.0.1",
|
"ng-in-viewport": "^13.0.1",
|
||||||
"ng2-ckeditor": "~1.2.9",
|
|
||||||
"ng2-completer": "^9.0.1",
|
"ng2-completer": "^9.0.1",
|
||||||
"ng2-smart-table": "^1.6.0",
|
|
||||||
"ngx-echarts": "^4.2.2",
|
"ngx-echarts": "^4.2.2",
|
||||||
"ngx-infinite-scroll": "^13.0.2",
|
"ngx-infinite-scroll": "^17.0.0",
|
||||||
"ngx-mask": "^12.0.0",
|
"ngx-mask": "^12.0.0",
|
||||||
"node-sass": "^4.14.1",
|
"node-sass": "^4.14.1",
|
||||||
"normalize.css": "6.0.0",
|
"normalize.css": "6.0.0",
|
||||||
@@ -77,18 +81,18 @@
|
|||||||
"rxjs-compat": "6.3.0",
|
"rxjs-compat": "6.3.0",
|
||||||
"socicon": "3.0.5",
|
"socicon": "3.0.5",
|
||||||
"style-loader": "^1.3.0",
|
"style-loader": "^1.3.0",
|
||||||
"tinymce": "4.5.7",
|
"tinymce": "^7.0.0",
|
||||||
"tslib": "^2.3.1",
|
"tslib": "^2.3.1",
|
||||||
"typeface-exo": "0.0.22",
|
"typeface-exo": "0.0.22",
|
||||||
"typeit": "^8.7.0",
|
"typeit": "^8.7.0",
|
||||||
"web-animations-js": "^2.3.2",
|
"zone.js": "~0.14.4"
|
||||||
"zone.js": "~0.11.4"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-devkit/build-angular": "^12.1.4",
|
"@angular-devkit/build-angular": "^17.3.3",
|
||||||
"@angular/cli": "^12.2.17",
|
"@angular/cli": "^17.3.3",
|
||||||
"@angular/compiler-cli": "^12.2.16",
|
"@angular/compiler-cli": "^17.3.3",
|
||||||
"@angular/language-service": "12.1.0",
|
"@angular/language-service": "17.3.3",
|
||||||
|
"@angular/localize": "^17.3.3",
|
||||||
"@compodoc/compodoc": "1.0.1",
|
"@compodoc/compodoc": "1.0.1",
|
||||||
"@fortawesome/fontawesome-free": "^5.2.0",
|
"@fortawesome/fontawesome-free": "^5.2.0",
|
||||||
"@schematics/angular": "^14.1.3",
|
"@schematics/angular": "^14.1.3",
|
||||||
@@ -96,25 +100,23 @@
|
|||||||
"@types/jasmine": "~3.3.0",
|
"@types/jasmine": "~3.3.0",
|
||||||
"@types/jasminewd2": "2.0.3",
|
"@types/jasminewd2": "2.0.3",
|
||||||
"@types/leaflet": "1.2.3",
|
"@types/leaflet": "1.2.3",
|
||||||
"@types/node": "^12.12.70",
|
"@types/node": "^18.19.30",
|
||||||
"codelyzer": "^6.0.2",
|
"codelyzer": "^6.0.2",
|
||||||
"conventional-changelog-cli": "1.3.4",
|
"conventional-changelog-cli": "1.3.4",
|
||||||
"husky": "0.13.3",
|
"husky": "0.13.3",
|
||||||
"jasmine-core": "~3.6.0",
|
"jasmine-core": "~5.1.2",
|
||||||
"jasmine-spec-reporter": "~5.0.0",
|
"jasmine-spec-reporter": "~5.0.0",
|
||||||
"karma": "~6.3.19",
|
"karma": "~6.3.19",
|
||||||
"karma-chrome-launcher": "~3.1.1",
|
"karma-chrome-launcher": "~3.1.1",
|
||||||
"karma-cli": "1.0.1",
|
"karma-cli": "1.0.1",
|
||||||
"karma-coverage-istanbul-reporter": "~3.0.2",
|
"karma-coverage-istanbul-reporter": "~3.0.2",
|
||||||
"karma-jasmine": "~4.0.2",
|
"karma-jasmine": "~5.1.0",
|
||||||
"karma-jasmine-html-reporter": "^1.7.0",
|
"karma-jasmine-html-reporter": "^2.1.0",
|
||||||
"npm-run-all": "4.0.2",
|
"npm-run-all": "4.0.2",
|
||||||
"protractor": "~7.0.0",
|
"protractor": "~7.0.0",
|
||||||
"rimraf": "2.6.1",
|
"rimraf": "2.6.1",
|
||||||
"stylelint": "7.13.0",
|
"stylelint": "7.13.0",
|
||||||
"ts-node": "3.2.2",
|
"ts-node": "3.2.2",
|
||||||
"tslint": "~6.1.0",
|
"typescript": "~5.4.4"
|
||||||
"tslint-language-service": "^0.9.9",
|
|
||||||
"typescript": "~4.2.3||~4.3.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
@import '../../styles/themes';
|
@import "../../styles/themes";
|
||||||
@import '~@nebular/theme/styles/global/breakpoints';
|
@import "@nebular/theme/styles/global/breakpoints";
|
||||||
@import '~bootstrap/scss/mixins/breakpoints';
|
@import "bootstrap/scss/mixins/breakpoints";
|
||||||
|
|
||||||
@include nb-install-component() {
|
@include nb-install-component() {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
@import '~bootstrap/scss/mixins/breakpoints';
|
@import "bootstrap/scss/mixins/breakpoints";
|
||||||
@import '~@nebular/theme/styles/global/breakpoints';
|
@import "@nebular/theme/styles/global/breakpoints";
|
||||||
@import '../../styles/themes';
|
@import "../../styles/themes";
|
||||||
|
|
||||||
@include nb-install-component() {
|
@include nb-install-component() {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
@import '../../styles/themes';
|
@import "../../styles/themes";
|
||||||
@import '~bootstrap/scss/mixins/breakpoints';
|
@import "bootstrap/scss/mixins/breakpoints";
|
||||||
@import '~@nebular/theme/styles/global/breakpoints';
|
@import "@nebular/theme/styles/global/breakpoints";
|
||||||
|
|
||||||
@include nb-install-component() {
|
@include nb-install-component() {
|
||||||
.menu-sidebar ::ng-deep .scrollable {
|
.menu-sidebar ::ng-deep .scrollable {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
@import '../../styles/themes';
|
@import "../../styles/themes";
|
||||||
@import '~bootstrap/scss/mixins/breakpoints';
|
@import "bootstrap/scss/mixins/breakpoints";
|
||||||
@import '~@nebular/theme/styles/global/breakpoints';
|
@import "@nebular/theme/styles/global/breakpoints";
|
||||||
|
|
||||||
@include nb-install-component() {
|
@include nb-install-component() {
|
||||||
.menu-sidebar ::ng-deep .scrollable {
|
.menu-sidebar ::ng-deep .scrollable {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
@import '../../styles/themes';
|
@import "../../styles/themes";
|
||||||
@import '~bootstrap/scss/mixins/breakpoints';
|
@import "bootstrap/scss/mixins/breakpoints";
|
||||||
@import '~@nebular/theme/styles/global/breakpoints';
|
@import "@nebular/theme/styles/global/breakpoints";
|
||||||
|
|
||||||
@include nb-install-component() {
|
@include nb-install-component() {
|
||||||
.menu-sidebar ::ng-deep .scrollable {
|
.menu-sidebar ::ng-deep .scrollable {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
@import '../../styles/themes';
|
@import "../../styles/themes";
|
||||||
@import '~bootstrap/scss/mixins/breakpoints';
|
@import "bootstrap/scss/mixins/breakpoints";
|
||||||
@import '~@nebular/theme/styles/global/breakpoints';
|
@import "@nebular/theme/styles/global/breakpoints";
|
||||||
|
|
||||||
@include nb-install-component() {
|
@include nb-install-component() {
|
||||||
.menu-sidebar ::ng-deep .scrollable {
|
.menu-sidebar ::ng-deep .scrollable {
|
||||||
|
|||||||
@@ -1,26 +1,25 @@
|
|||||||
@import url('https://fonts.googleapis.com/css?family=Open+Sans:400,600,700&display=swap');
|
@import url("https://fonts.googleapis.com/css?family=Open+Sans:400,600,700&display=swap");
|
||||||
|
|
||||||
// themes - our custom or/and out of the box themes
|
// themes - our custom or/and out of the box themes
|
||||||
@import 'themes';
|
@import "themes";
|
||||||
|
|
||||||
// framework component themes (styles tied to theme variables)
|
// framework component themes (styles tied to theme variables)
|
||||||
@import '~@nebular/theme/styles/globals';
|
@import "@nebular/theme/styles/globals";
|
||||||
@import '~@nebular/auth/styles/globals';
|
@import "@nebular/auth/styles/globals";
|
||||||
|
|
||||||
@import '~bootstrap/scss/functions';
|
@import "bootstrap/scss/functions";
|
||||||
@import '~bootstrap/scss/variables';
|
@import "bootstrap/scss/variables";
|
||||||
@import '~bootstrap/scss/mixins';
|
@import "bootstrap/scss/mixins";
|
||||||
@import '~bootstrap/scss/grid';
|
@import "bootstrap/scss/grid";
|
||||||
|
|
||||||
// loading progress bar theme
|
// loading progress bar theme
|
||||||
@import './pace.theme';
|
@import "./pace.theme";
|
||||||
|
|
||||||
@import './layout';
|
@import "./layout";
|
||||||
@import './overrides';
|
@import "./overrides";
|
||||||
|
|
||||||
// install the framework and custom global styles
|
// install the framework and custom global styles
|
||||||
@include nb-install() {
|
@include nb-install() {
|
||||||
|
|
||||||
// framework global styles
|
// framework global styles
|
||||||
@include nb-theme-global();
|
@include nb-theme-global();
|
||||||
@include nb-auth-global();
|
@include nb-auth-global();
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
// @nebular theming framework
|
// @nebular theming framework
|
||||||
@import '~@nebular/theme/styles/theming';
|
@import "@nebular/theme/styles/theming";
|
||||||
// @nebular out of the box themes
|
// @nebular out of the box themes
|
||||||
@import '~@nebular/theme/styles/themes';
|
@import "@nebular/theme/styles/themes";
|
||||||
|
|
||||||
$nb-themes: nb-register-theme((
|
$nb-themes: nb-register-theme(
|
||||||
|
(
|
||||||
layout-padding-top: 2.25rem,
|
layout-padding-top: 2.25rem,
|
||||||
|
|
||||||
menu-item-icon-margin: 0 0.5rem 0 0,
|
menu-item-icon-margin: 0 0.5rem 0 0,
|
||||||
@@ -22,9 +23,13 @@ $nb-themes: nb-register-theme((
|
|||||||
slide-out-background: #f7f9fc,
|
slide-out-background: #f7f9fc,
|
||||||
slide-out-shadow-color: 0 4px 14px 0 #8f9bb3,
|
slide-out-shadow-color: 0 4px 14px 0 #8f9bb3,
|
||||||
slide-out-shadow-color-rtl: 0 4px 14px 0 #8f9bb3,
|
slide-out-shadow-color-rtl: 0 4px 14px 0 #8f9bb3,
|
||||||
), default, default);
|
),
|
||||||
|
default,
|
||||||
|
default
|
||||||
|
);
|
||||||
|
|
||||||
$nb-themes: nb-register-theme((
|
$nb-themes: nb-register-theme(
|
||||||
|
(
|
||||||
layout-padding-top: 2.25rem,
|
layout-padding-top: 2.25rem,
|
||||||
|
|
||||||
menu-item-icon-margin: 0 0.5rem 0 0,
|
menu-item-icon-margin: 0 0.5rem 0 0,
|
||||||
@@ -43,9 +48,13 @@ $nb-themes: nb-register-theme((
|
|||||||
slide-out-background: #252547,
|
slide-out-background: #252547,
|
||||||
slide-out-shadow-color: 2px 0 3px #29157a,
|
slide-out-shadow-color: 2px 0 3px #29157a,
|
||||||
slide-out-shadow-color-rtl: -2px 0 3px #29157a,
|
slide-out-shadow-color-rtl: -2px 0 3px #29157a,
|
||||||
), cosmic, cosmic);
|
),
|
||||||
|
cosmic,
|
||||||
|
cosmic
|
||||||
|
);
|
||||||
|
|
||||||
$nb-themes: nb-register-theme((
|
$nb-themes: nb-register-theme(
|
||||||
|
(
|
||||||
layout-padding-top: 2.25rem,
|
layout-padding-top: 2.25rem,
|
||||||
|
|
||||||
menu-item-icon-margin: 0 0.5rem 0 0,
|
menu-item-icon-margin: 0 0.5rem 0 0,
|
||||||
@@ -64,9 +73,13 @@ $nb-themes: nb-register-theme((
|
|||||||
slide-out-background: linear-gradient(270deg, #edf1f7 0%, #e4e9f2 100%),
|
slide-out-background: linear-gradient(270deg, #edf1f7 0%, #e4e9f2 100%),
|
||||||
slide-out-shadow-color: 0 4px 14px 0 #8f9bb3,
|
slide-out-shadow-color: 0 4px 14px 0 #8f9bb3,
|
||||||
slide-out-shadow-color-rtl: 0 4px 14px 0 #8f9bb3,
|
slide-out-shadow-color-rtl: 0 4px 14px 0 #8f9bb3,
|
||||||
), corporate, corporate);
|
),
|
||||||
|
corporate,
|
||||||
|
corporate
|
||||||
|
);
|
||||||
|
|
||||||
$nb-themes: nb-register-theme((
|
$nb-themes: nb-register-theme(
|
||||||
|
(
|
||||||
layout-padding-top: 2.25rem,
|
layout-padding-top: 2.25rem,
|
||||||
|
|
||||||
menu-item-icon-margin: 0 0.5rem 0 0,
|
menu-item-icon-margin: 0 0.5rem 0 0,
|
||||||
@@ -85,4 +98,7 @@ $nb-themes: nb-register-theme((
|
|||||||
slide-out-background: linear-gradient(270deg, #222b45 0%, #151a30 100%),
|
slide-out-background: linear-gradient(270deg, #222b45 0%, #151a30 100%),
|
||||||
slide-out-shadow-color: 0 4px 14px 0 #8f9bb3,
|
slide-out-shadow-color: 0 4px 14px 0 #8f9bb3,
|
||||||
slide-out-shadow-color-rtl: 0 4px 14px 0 #8f9bb3,
|
slide-out-shadow-color-rtl: 0 4px 14px 0 #8f9bb3,
|
||||||
), dark, dark);
|
),
|
||||||
|
dark,
|
||||||
|
dark
|
||||||
|
);
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import { FancyTableModule } from '../ui/fancy-table/fancy-table.module';
|
|||||||
import { FamilyMembersComponent } from './family-members/family-members.component';
|
import { FamilyMembersComponent } from './family-members/family-members.component';
|
||||||
import { FamilyMemberEditorComponent } from './family-members/family-member-editor/family-member-editor.component';
|
import { FamilyMemberEditorComponent } from './family-members/family-member-editor/family-member-editor.component';
|
||||||
import { DropDownListModule } from '../ui/drop-down-list/drop-down-list.module';
|
import { DropDownListModule } from '../ui/drop-down-list/drop-down-list.module';
|
||||||
import { NgxMaskModule } from 'ngx-mask';
|
|
||||||
import { PastoralDomainsComponent } from './pastoral-domains/pastoral-domains.component';
|
import { PastoralDomainsComponent } from './pastoral-domains/pastoral-domains.component';
|
||||||
import { PastoralDomainEditorComponent } from './pastoral-domains/pastoral-domain-editor/pastoral-domain-editor.component';
|
import { PastoralDomainEditorComponent } from './pastoral-domains/pastoral-domain-editor/pastoral-domain-editor.component';
|
||||||
import { AssignMemberCellGroupComponent } from './family-members/assign-member-cell-group/assign-member-cell-group.component';
|
import { AssignMemberCellGroupComponent } from './family-members/assign-member-cell-group/assign-member-cell-group.component';
|
||||||
@@ -61,7 +60,6 @@ import { LineMessagingAccountEditorComponent } from './lines/line-messaging-acco
|
|||||||
AlertDlgModule,
|
AlertDlgModule,
|
||||||
FancyTableModule,
|
FancyTableModule,
|
||||||
DropDownListModule,
|
DropDownListModule,
|
||||||
NgxMaskModule,
|
|
||||||
CurrencyInputModule,
|
CurrencyInputModule,
|
||||||
MaskDirectiveModule,
|
MaskDirectiveModule,
|
||||||
DateInputModule
|
DateInputModule
|
||||||
|
|||||||
@@ -3,14 +3,12 @@ import { ActivatedRoute } from '@angular/router';
|
|||||||
import { NbDialogService } from '@nebular/theme';
|
import { NbDialogService } from '@nebular/theme';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { first } from 'rxjs/operators';
|
import { first } from 'rxjs/operators';
|
||||||
import { inherits } from 'util';
|
|
||||||
import { CellGroupRoutineEvents } from '../../entity/CellGroupRoutineEvents';
|
import { CellGroupRoutineEvents } from '../../entity/CellGroupRoutineEvents';
|
||||||
import { CellGroupRoutineEventsService } from '../../services/crudServices/cell-group-routine-events.service';
|
import { CellGroupRoutineEventsService } from '../../services/crudServices/cell-group-routine-events.service';
|
||||||
import { MsgBoxService } from '../../services/msg-box.service';
|
import { MsgBoxService } from '../../services/msg-box.service';
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
import { FancySettings } from '../../ui/fancy-table/fancy-settings.model';
|
import { FancySettings } from '../../ui/fancy-table/fancy-settings.model';
|
||||||
import { FancyTableComponent } from '../../ui/fancy-table/fancy-table.component';
|
import { FancyTableComponent } from '../../ui/fancy-table/fancy-table.component';
|
||||||
import { ObjectUtils } from '../../utilities/object-utils';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ngx-cell-group-routine-events',
|
selector: 'ngx-cell-group-routine-events',
|
||||||
|
|||||||
-1
@@ -2,7 +2,6 @@ import { Component, OnInit } from '@angular/core';
|
|||||||
import { NbDialogRef } from '@nebular/theme';
|
import { NbDialogRef } from '@nebular/theme';
|
||||||
import { DropDownOption } from '../../../entity/dropDownOption';
|
import { DropDownOption } from '../../../entity/dropDownOption';
|
||||||
import { DomainMemberRelationship, PastoralDomain } from '../../../entity/PastoralDomain';
|
import { DomainMemberRelationship, PastoralDomain } from '../../../entity/PastoralDomain';
|
||||||
import { DialogComponent } from '../../../pages/modal-overlays/dialog/dialog.component';
|
|
||||||
import { ArrayUtils } from '../../../utilities/array-utils';
|
import { ArrayUtils } from '../../../utilities/array-utils';
|
||||||
import { first } from "rxjs/operators"
|
import { first } from "rxjs/operators"
|
||||||
import { FamilyMember } from '../../../entity/Member';
|
import { FamilyMember } from '../../../entity/Member';
|
||||||
|
|||||||
@@ -17,11 +17,6 @@ export const routes: Routes = [
|
|||||||
loadChildren: () => import('./invitation/invitation.module')
|
loadChildren: () => import('./invitation/invitation.module')
|
||||||
.then(m => m.InvitationModule),
|
.then(m => m.InvitationModule),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: 'pages',
|
|
||||||
loadChildren: () => import('./pages/pages.module')
|
|
||||||
.then(m => m.PagesModule),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: 'auth',
|
path: 'auth',
|
||||||
component: NbAuthComponent,
|
component: NbAuthComponent,
|
||||||
|
|||||||
@@ -74,7 +74,6 @@ const socialLinks = [
|
|||||||
closeOnBackdropClick: false,
|
closeOnBackdropClick: false,
|
||||||
closeOnEsc: false
|
closeOnEsc: false
|
||||||
}),
|
}),
|
||||||
NgxMaskModule.forRoot(maskConfig),
|
|
||||||
NbDateFnsDateModule.forRoot({ format: 'MM/dd/yyyy' }),
|
NbDateFnsDateModule.forRoot({ format: 'MM/dd/yyyy' }),
|
||||||
CoreModule.forRoot(),
|
CoreModule.forRoot(),
|
||||||
ThemeModule.forRoot(),
|
ThemeModule.forRoot(),
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import { ActivatedRoute } from "@angular/router";
|
import { ActivatedRoute } from "@angular/router";
|
||||||
import { basename } from "path";
|
|
||||||
import { Observable, Subject } from "rxjs";
|
import { Observable, Subject } from "rxjs";
|
||||||
import { ScreenBase } from "../ScreenBase";
|
|
||||||
import { ICrudService } from "../services/crudServices/crud.service";
|
|
||||||
import { PastoralDomainService } from "../services/crudServices/pastoral-domain.service";
|
import { PastoralDomainService } from "../services/crudServices/pastoral-domain.service";
|
||||||
import { StateService } from "../services/state.service";
|
import { StateService } from "../services/state.service";
|
||||||
import { first } from 'rxjs/operators';
|
import { first } from 'rxjs/operators';
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ export class PrayerComponent extends MyAppBase {
|
|||||||
// message += "\n======= 備註 =======" + "\n" + comment;
|
// message += "\n======= 備註 =======" + "\n" + comment;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// message += "\n請使用方舟晚宴系統新增菜單唷!" + "\n" + "https://happiness.tours/CellGroup/dinner?openExternalBrowser=1"
|
// message += "\n請使用方舟晚宴系統新增菜單唷!" + "\n" + "https://golife.love/CellGroup/dinner?openExternalBrowser=1"
|
||||||
// this.lineService.pushLineMessage(message);
|
// this.lineService.pushLineMessage(message);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,14 +9,11 @@ const components = [RightClickMenuDirective,];
|
|||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [...components],
|
declarations: [...components],
|
||||||
entryComponents: [
|
|
||||||
ContextMenuComponent
|
|
||||||
],
|
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
NbDialogModule,
|
NbDialogModule,
|
||||||
ContextMenuModule
|
ContextMenuModule
|
||||||
],
|
],
|
||||||
exports: [...components],
|
exports: [...components]
|
||||||
})
|
})
|
||||||
export class RightClickMenuModule { }
|
export class RightClickMenuModule { }
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export interface LoginTokenViewModel {
|
|||||||
avatarImage: string;
|
avatarImage: string;
|
||||||
role: Role;
|
role: Role;
|
||||||
cellGroup: PastoralDomain;
|
cellGroup: PastoralDomain;
|
||||||
signalRSessionId;
|
signalRConnectionId;
|
||||||
sessionTabId: string;
|
sessionTabId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ const teamSize: number[][] = [
|
|||||||
const fourthQuestNeed2Failed = 7;
|
const fourthQuestNeed2Failed = 7;
|
||||||
const SIGNAL_R_URL = (id: string = null) => { return `${environment.signalRUrl}/${id}Hub` }
|
const SIGNAL_R_URL = (id: string = null) => { return `${environment.signalRUrl}/${id}Hub` }
|
||||||
//const SIGNAL_R_URL = (id: string = null) => { return `http://localhost:12071/hub` }
|
//const SIGNAL_R_URL = (id: string = null) => { return `http://localhost:12071/hub` }
|
||||||
//const SIGNAL_R_URL = (id: string = null) => { return `http://happiness.tours:8088/${id}hub` }
|
//const SIGNAL_R_URL = (id: string = null) => { return `http://golife.love:8088/${id}hub` }
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ngx-avalon',
|
selector: 'ngx-avalon',
|
||||||
|
|||||||
@@ -26,5 +26,5 @@
|
|||||||
<ng-template #WaitingMessage>
|
<ng-template #WaitingMessage>
|
||||||
<h1>等待遊戲開始中...</h1>
|
<h1>等待遊戲開始中...</h1>
|
||||||
|
|
||||||
<qr-code *ngIf="isHost" [size]="qrCodeWidth" [value]="'http://happiness.tours/games/avalon'"></qr-code>
|
<qr-code *ngIf="isHost" [size]="qrCodeWidth" [value]="'http://golife.love/games/avalon'"></qr-code>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
@@ -5,6 +5,8 @@ import { AvalonComponent } from './avalon/avalon.component';
|
|||||||
import { GamesComponent } from './games.component';
|
import { GamesComponent } from './games.component';
|
||||||
import { HeroDashboardComponent } from './massive-darkness2/hero-dashboard/hero-dashboard.component';
|
import { HeroDashboardComponent } from './massive-darkness2/hero-dashboard/hero-dashboard.component';
|
||||||
import { MassiveDarkness2Component } from './massive-darkness2/massive-darkness2.component';
|
import { MassiveDarkness2Component } from './massive-darkness2/massive-darkness2.component';
|
||||||
|
import { MD2MobInfoMaintenanceComponent } from './massive-darkness2/md2-mob-info-maintenance/md2-mob-info-maintenance.component';
|
||||||
|
import { MD2HeroProfileMaintenanceComponent } from './massive-darkness2/md2-hero-profile-maintenance/md2-hero-profile-maintenance.component';
|
||||||
export class GameRoomMenuConfig {
|
export class GameRoomMenuConfig {
|
||||||
public static HostMenu: NbMenuItem[] = [
|
public static HostMenu: NbMenuItem[] = [
|
||||||
|
|
||||||
@@ -47,6 +49,8 @@ const routes: Routes = [
|
|||||||
{ path: 'avalonHost', component: AvalonComponent },
|
{ path: 'avalonHost', component: AvalonComponent },
|
||||||
{ path: 'MD2', component: MassiveDarkness2Component },
|
{ path: 'MD2', component: MassiveDarkness2Component },
|
||||||
{ path: 'MD2_Hero/:roomId', component: HeroDashboardComponent },
|
{ path: 'MD2_Hero/:roomId', component: HeroDashboardComponent },
|
||||||
|
{ path: 'MD2MobInfo', component: MD2MobInfoMaintenanceComponent },
|
||||||
|
{ path: 'MD2HeroProfile', component: MD2HeroProfileMaintenanceComponent },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -3,4 +3,6 @@
|
|||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
</ngx-plain-layout>
|
</ngx-plain-layout>
|
||||||
|
|
||||||
|
<div kendoDialogContainer></div>
|
||||||
|
|
||||||
<!-- ngx-plain-layout ngx-one-column-layout-->
|
<!-- ngx-plain-layout ngx-one-column-layout-->
|
||||||
@@ -5,6 +5,7 @@ export interface IGamePlayer {
|
|||||||
isPlayer: boolean;
|
isPlayer: boolean;
|
||||||
signalRClientId: string;
|
signalRClientId: string;
|
||||||
tabId: string;
|
tabId: string;
|
||||||
|
isDisconnected: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GamePlayer implements IGamePlayer {
|
export class GamePlayer implements IGamePlayer {
|
||||||
@@ -14,4 +15,5 @@ export class GamePlayer implements IGamePlayer {
|
|||||||
isPlayer: boolean;
|
isPlayer: boolean;
|
||||||
signalRClientId: string;
|
signalRClientId: string;
|
||||||
tabId: string;
|
tabId: string;
|
||||||
|
isDisconnected: boolean;
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { QRCodeModule } from 'angular2-qrcode';
|
|
||||||
import { GamesRoutingModule } from './games-routing.module';
|
import { GamesRoutingModule } from './games-routing.module';
|
||||||
import { GamesComponent } from './games.component';
|
import { GamesComponent } from './games.component';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
@@ -33,6 +32,29 @@ import { MobAttackInfoComponent } from './massive-darkness2/mobs/mob-detail-info
|
|||||||
import { MobDefInfoComponent } from './massive-darkness2/mobs/mob-detail-info/mob-def-info/mob-def-info.component';
|
import { MobDefInfoComponent } from './massive-darkness2/mobs/mob-detail-info/mob-def-info/mob-def-info.component';
|
||||||
import { MobCombatInfoComponent } from './massive-darkness2/mobs/mob-detail-info/mob-combat-info/mob-combat-info.component';
|
import { MobCombatInfoComponent } from './massive-darkness2/mobs/mob-detail-info/mob-combat-info/mob-combat-info.component';
|
||||||
import { MobStandInfoComponent } from './massive-darkness2/mobs/mob-stand-info/mob-stand-info.component';
|
import { MobStandInfoComponent } from './massive-darkness2/mobs/mob-stand-info/mob-stand-info.component';
|
||||||
|
import { HtmlEditorModule } from '../ui/html-editor/html-editor.module';
|
||||||
|
import { EditorModule } from '@tinymce/tinymce-angular';
|
||||||
|
import { MD2HtmlEditorComponent } from './massive-darkness2/md2-html-editor/md2-html-editor.component';
|
||||||
|
import { MD2IconPickerDlgComponent } from './massive-darkness2/md2-html-editor/md2-icon-picker-dlg.component';
|
||||||
|
import { EditorModule as KendoEditorModule } from '@progress/kendo-angular-editor';
|
||||||
|
import { ToolBarModule } from '@progress/kendo-angular-toolbar';
|
||||||
|
import { ButtonsModule } from '@progress/kendo-angular-buttons';
|
||||||
|
import { GridModule } from '@progress/kendo-angular-grid';
|
||||||
|
import { DialogModule } from '@progress/kendo-angular-dialog';
|
||||||
|
import { InputsModule } from '@progress/kendo-angular-inputs';
|
||||||
|
import { DropDownsModule } from '@progress/kendo-angular-dropdowns';
|
||||||
|
import { LayoutModule } from '@progress/kendo-angular-layout';
|
||||||
|
import { ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { MD2MobInfoMaintenanceComponent } from './massive-darkness2/md2-mob-info-maintenance/md2-mob-info-maintenance.component';
|
||||||
|
import { MD2MobInfoEditorComponent } from './massive-darkness2/md2-mob-info-maintenance/md2-mob-info-editor/md2-mob-info-editor.component';
|
||||||
|
import { MD2MobInfoDetailComponent } from './massive-darkness2/md2-mob-info-maintenance/md2-mob-info-detail/md2-mob-info-detail.component';
|
||||||
|
import { MD2MobSkillEditorComponent } from './massive-darkness2/md2-mob-info-maintenance/md2-mob-skill-editor/md2-mob-skill-editor.component';
|
||||||
|
import { MD2MobLevelEditorComponent } from './massive-darkness2/md2-mob-info-maintenance/md2-mob-level-editor/md2-mob-level-editor.component';
|
||||||
|
import { MD2BossFightEditorComponent } from './massive-darkness2/md2-mob-info-maintenance/md2-boss-fight-editor/md2-boss-fight-editor.component';
|
||||||
|
import { MD2PhaseBuffEditorComponent } from './massive-darkness2/md2-mob-info-maintenance/md2-phase-buff-editor/md2-phase-buff-editor.component';
|
||||||
|
import { MD2HeroProfileMaintenanceComponent } from './massive-darkness2/md2-hero-profile-maintenance/md2-hero-profile-maintenance.component';
|
||||||
|
import { MD2HeroProfileEditorComponent } from './massive-darkness2/md2-hero-profile-maintenance/md2-hero-profile-editor/md2-hero-profile-editor.component';
|
||||||
|
import { GameInitDlgComponent } from './massive-darkness2/game-init-dlg/game-init-dlg.component';
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@@ -60,12 +82,25 @@ import { MobStandInfoComponent } from './massive-darkness2/mobs/mob-stand-info/m
|
|||||||
MobAttackInfoComponent,
|
MobAttackInfoComponent,
|
||||||
MobDefInfoComponent,
|
MobDefInfoComponent,
|
||||||
MobCombatInfoComponent,
|
MobCombatInfoComponent,
|
||||||
MobStandInfoComponent
|
MobStandInfoComponent,
|
||||||
|
MD2HtmlEditorComponent,
|
||||||
|
MD2IconPickerDlgComponent,
|
||||||
|
MD2MobInfoMaintenanceComponent,
|
||||||
|
MD2MobInfoEditorComponent,
|
||||||
|
MD2MobInfoDetailComponent,
|
||||||
|
MD2MobSkillEditorComponent,
|
||||||
|
MD2MobLevelEditorComponent,
|
||||||
|
MD2BossFightEditorComponent,
|
||||||
|
MD2PhaseBuffEditorComponent,
|
||||||
|
MD2HeroProfileMaintenanceComponent,
|
||||||
|
MD2HeroProfileEditorComponent,
|
||||||
|
GameInitDlgComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
GamesRoutingModule,
|
GamesRoutingModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
AdminRoutingModule,
|
AdminRoutingModule,
|
||||||
ThemeModule,
|
ThemeModule,
|
||||||
NbMenuModule,
|
NbMenuModule,
|
||||||
@@ -90,8 +125,17 @@ import { MobStandInfoComponent } from './massive-darkness2/mobs/mob-stand-info/m
|
|||||||
CurrencyInputModule,
|
CurrencyInputModule,
|
||||||
NbDialogModule.forRoot(),
|
NbDialogModule.forRoot(),
|
||||||
AlertDlgModule,
|
AlertDlgModule,
|
||||||
QRCodeModule,
|
DropDownListModule,
|
||||||
DropDownListModule
|
HtmlEditorModule,
|
||||||
|
EditorModule,
|
||||||
|
KendoEditorModule,
|
||||||
|
ToolBarModule,
|
||||||
|
ButtonsModule,
|
||||||
|
GridModule,
|
||||||
|
DialogModule,
|
||||||
|
InputsModule,
|
||||||
|
DropDownsModule,
|
||||||
|
LayoutModule
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class GamesModule { }
|
export class GamesModule { }
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { SignalRMessage } from "../../services/signal-r.service";
|
|||||||
import { StateService } from "../../services/state.service";
|
import { StateService } from "../../services/state.service";
|
||||||
import { ADIcon, MessageBoxConfig } from "../../ui/alert-dlg/alert-dlg.model";
|
import { ADIcon, MessageBoxConfig } from "../../ui/alert-dlg/alert-dlg.model";
|
||||||
import { MD2HeroInfo, MD2Icon, MobInfo, RoundPhase } from "./massive-darkness2.model";
|
import { MD2HeroInfo, MD2Icon, MobInfo, RoundPhase } from "./massive-darkness2.model";
|
||||||
|
import { LoginUserService } from "../../services/login-user.service";
|
||||||
|
import { GamePlayer } from "../games.model";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export abstract class MD2Base {
|
export abstract class MD2Base {
|
||||||
@@ -58,14 +60,14 @@ export abstract class MD2Base {
|
|||||||
}
|
}
|
||||||
|
|
||||||
imgUrl(imgPath: string) {
|
imgUrl(imgPath: string) {
|
||||||
return this.md2Service.stateService.imgUrl(imgPath);
|
return this.md2Service.imgUrl(imgPath);
|
||||||
}
|
}
|
||||||
fileList(folderPath: string) {
|
fileList(folderPath: string) {
|
||||||
return this.md2Service.fileList(folderPath);
|
return this.md2Service.fileList(folderPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
iconHtml(icon: MD2Icon, cssClass = '') {
|
iconHtml(icon: MD2Icon, cssClass = '') {
|
||||||
return this.md2Service.stateService.iconHtml(icon, cssClass);
|
return this.md2Service.iconHtml(icon, cssClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
imgHtml(imgFile: string, cssClass = '') {
|
imgHtml(imgFile: string, cssClass = '') {
|
||||||
@@ -81,12 +83,18 @@ export abstract class MD2Base {
|
|||||||
}
|
}
|
||||||
abstract refreshUI();
|
abstract refreshUI();
|
||||||
handleSignalRCallback(message: SignalRMessage): void {
|
handleSignalRCallback(message: SignalRMessage): void {
|
||||||
|
console.log('handleSignalRCallback', message);
|
||||||
|
if (message.from) {
|
||||||
if (message.from.isGroup) {
|
if (message.from.isGroup) {
|
||||||
if (!this.isHeroDashboard) return;
|
if (!this.isHeroDashboard) return;
|
||||||
} else {
|
} else {
|
||||||
if (this.isHeroDashboard && this.md2Service.playerHero.playerInfo.signalRClientId == message.from.sessionId) return;
|
if (this.isHeroDashboard && this.md2Service.playerHero?.playerInfo?.signalRClientId == message.from.connectionId) return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.isHeroDashboard) {
|
||||||
|
|
||||||
|
}
|
||||||
switch (message.actionType) {
|
switch (message.actionType) {
|
||||||
case 'hero':
|
case 'hero':
|
||||||
let heroInfo = new MD2HeroInfo(JSON.parse(message.parameters['hero']));
|
let heroInfo = new MD2HeroInfo(JSON.parse(message.parameters['hero']));
|
||||||
@@ -95,43 +103,12 @@ export abstract class MD2Base {
|
|||||||
this.md2Service.heros.push(heroInfo);
|
this.md2Service.heros.push(heroInfo);
|
||||||
break;
|
break;
|
||||||
case 'update':
|
case 'update':
|
||||||
let exitingHero = this.md2Service.heros.find(h => h.playerInfo.signalRClientId == heroInfo.playerInfo.signalRClientId);
|
this.updateHeroInfo(heroInfo);
|
||||||
if (exitingHero) {
|
|
||||||
let activateBoss = exitingHero.uiActivating && !heroInfo.uiActivating;
|
|
||||||
|
|
||||||
this.md2Service.heros[this.md2Service.heros.indexOf(exitingHero)] = heroInfo;
|
|
||||||
if (this.isHeroDashboard && this.md2Service.stateService.playerHero.playerInfo.tabId == heroInfo.playerInfo.tabId) {
|
|
||||||
this.md2Service.stateService.playerHero = heroInfo;
|
|
||||||
}
|
|
||||||
if (!this.isHeroDashboard && this.md2Service.info.isBossFight && activateBoss) {
|
|
||||||
this.md2Service.activateBoss();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.md2Service.heros.push(heroInfo);
|
|
||||||
}
|
|
||||||
if (!this.isHeroDashboard) {
|
|
||||||
if (this.gameInfo.roundPhase == RoundPhase.HeroPhase) {
|
|
||||||
if (!this.md2Service.heros.some(h => h.remainActions > 0) && !this.md2Service.heros.some(h => h.uiActivating)) {
|
|
||||||
if (!this.md2Service.info.isBossFight) {
|
|
||||||
if (this.md2Service.mobs.length > 0 || this.md2Service.roamingMonsters.length > 0) {
|
|
||||||
this.md2Service.msgBoxService.show('Enemy Phase', { icon: ADIcon.WARNING }).pipe(first()).subscribe(result => {
|
|
||||||
this.md2Service.runNextPhase();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.md2Service.runNextPhase();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Object.assign(heroInfo, exitingHero);
|
//Object.assign(heroInfo, exitingHero);
|
||||||
break;
|
break;
|
||||||
case 'updateMyHero':
|
case 'updateMyHero':
|
||||||
if (this.isHeroDashboard) {
|
if (this.isHeroDashboard) {
|
||||||
this.md2Service.stateService.playerHero = heroInfo;
|
this.md2Service.playerHero = heroInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -140,15 +117,54 @@ export abstract class MD2Base {
|
|||||||
}
|
}
|
||||||
this.detectChanges();
|
this.detectChanges();
|
||||||
break;
|
break;
|
||||||
|
case 'heroes':
|
||||||
|
switch (message.actionName) {
|
||||||
|
case 'updateAll':
|
||||||
|
if (this.isHeroDashboard) {
|
||||||
|
let allHeroes = (JSON.parse(message.parameters['heros']) as MD2HeroInfo[]).map(h => new MD2HeroInfo(h));
|
||||||
|
//Remove heroes that are not in the list
|
||||||
|
this.md2Service.info.heros = this.md2Service.heros.filter(h => allHeroes.some(h2 => h2.playerInfo.tabId == h.playerInfo.tabId));
|
||||||
|
allHeroes.forEach(heroInfo => {
|
||||||
|
this.updateHeroInfo(heroInfo);
|
||||||
|
});
|
||||||
|
this.detectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'GameRoom':
|
case 'GameRoom':
|
||||||
switch (message.actionName) {
|
switch (message.actionName) {
|
||||||
case 'Leaving':
|
case 'Leaving':
|
||||||
this.md2Service.heros.splice(this.md2Service.heros.findIndex(h => h.playerInfo.signalRClientId == message.from.sessionId));
|
let leavingPlayerInfo = message.value as GamePlayer;
|
||||||
|
let leavingHero = this.md2Service.heros.find(h => h.playerInfo.tabId == leavingPlayerInfo.tabId);
|
||||||
|
if (leavingHero) {
|
||||||
|
leavingHero.playerInfo.isDisconnected = true;
|
||||||
|
}
|
||||||
|
//var disconnectHero = this.md2Service.heros.splice(this.md2Service.heros.findIndex(h => h.playerInfo.signalRClientId == leavingPlayerInfo.signalRClientId));
|
||||||
|
//this.md2Service.info.disconnectedHeroes.push(...disconnectHero);
|
||||||
this.detectChanges();
|
this.detectChanges();
|
||||||
break;
|
break;
|
||||||
case 'update':
|
case 'update':
|
||||||
if (this.isHeroDashboard) {
|
if (this.isHeroDashboard) {
|
||||||
|
//Before update game info check the current Hero level
|
||||||
|
let playerHeroLevel = this.md2Service.playerHero?.level;
|
||||||
this.md2Service.info = new MD2GameInfo(JSON.parse(message.parameters['gameInfo']) as MD2GameInfo);
|
this.md2Service.info = new MD2GameInfo(JSON.parse(message.parameters['gameInfo']) as MD2GameInfo);
|
||||||
|
let playerHero = this.md2Service.heros.find(h => h.playerInfo.tabId == this.stateService.loginUserService.sessionTabId);
|
||||||
|
if (playerHero) {
|
||||||
|
|
||||||
|
playerHero.playerInfo = this.md2Service.gameRoomService.currentPlayer();
|
||||||
|
playerHero.playerInfo.isDisconnected = false;
|
||||||
|
this.md2Service.playerHero = playerHero;
|
||||||
|
this.md2Service.broadcastMyHeroInfo();
|
||||||
|
//When fetch game info, if the hero level is changed, show the level up message
|
||||||
|
if (playerHeroLevel && playerHeroLevel != playerHero.level) {
|
||||||
|
//do i--
|
||||||
|
for (let i = playerHero.level; i > playerHeroLevel; i--) {
|
||||||
|
this.md2Service.msgBoxService.show(`Level Up Lv.${i}`, { text: 'Please do a skill level up!', icon: ADIcon.INFO });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
this.detectChanges();
|
this.detectChanges();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -158,6 +174,21 @@ export abstract class MD2Base {
|
|||||||
this.detectChanges();
|
this.detectChanges();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'getGameInfo':
|
||||||
|
|
||||||
|
if (!this.isHeroDashboard) {
|
||||||
|
|
||||||
|
this.md2Service.broadcastGameInfo();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'sendJoinInfo':
|
||||||
|
//When hero join the game, or reconnect to the game will receive this message
|
||||||
|
if (this.isHeroDashboard && this.md2Service.playerHero) {
|
||||||
|
this.md2Service.playerHero.playerInfo.signalRClientId = message.parameters['signalrconnid'];
|
||||||
|
//Send fetch game info to the hero
|
||||||
|
this.md2Service.broadcastFetchGameInfo();
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -206,7 +237,7 @@ export abstract class MD2Base {
|
|||||||
break;
|
break;
|
||||||
case 'tradeAction':
|
case 'tradeAction':
|
||||||
this.md2Service.msgBoxService.show('Trade and Equip', {
|
this.md2Service.msgBoxService.show('Trade and Equip', {
|
||||||
text: `every one in the <b>same zone with <b>${this.md2Service.heroFullName(this.md2Service.currentActivateHero)}</b> may freely trade and
|
text: `every one in the <b>same zone with ${this.md2Service.heroFullName(this.md2Service.currentActivateHero)}</b> may freely trade and
|
||||||
equip items!`,
|
equip items!`,
|
||||||
icon: ADIcon.INFO
|
icon: ADIcon.INFO
|
||||||
});
|
});
|
||||||
@@ -223,6 +254,41 @@ export abstract class MD2Base {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
updateHeroInfo(heroInfo: MD2HeroInfo) {
|
||||||
|
let exitingHero = this.md2Service.heros.find(h => h.playerInfo.tabId == heroInfo.playerInfo.tabId);
|
||||||
|
if (exitingHero) {
|
||||||
|
//For boss fight, if the hero finished activating, activate the boss
|
||||||
|
let activateBoss = exitingHero.uiActivating && !heroInfo.uiActivating;
|
||||||
|
|
||||||
|
this.md2Service.heros[this.md2Service.heros.indexOf(exitingHero)] = heroInfo;
|
||||||
|
//My hero update
|
||||||
|
if (this.isHeroDashboard && this.md2Service.loginUserService.sessionTabId == heroInfo.playerInfo.tabId) {
|
||||||
|
this.md2Service.playerHero = heroInfo;
|
||||||
|
}
|
||||||
|
if (!this.isHeroDashboard && this.md2Service.info.isBossFight && activateBoss) {
|
||||||
|
this.md2Service.activateBoss();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.md2Service.heros.push(heroInfo);
|
||||||
|
}
|
||||||
|
if (!this.isHeroDashboard) {
|
||||||
|
if (this.gameInfo.roundPhase == RoundPhase.HeroPhase) {
|
||||||
|
if (!this.md2Service.heros.some(h => h.remainActions > 0) && !this.md2Service.heros.some(h => h.uiActivating)) {
|
||||||
|
if (!this.md2Service.info.isBossFight) {
|
||||||
|
if (this.md2Service.mobs.length > 0 || this.md2Service.roamingMonsters.length > 0) {
|
||||||
|
this.md2Service.msgBoxService.show('Enemy Phase', { icon: ADIcon.WARNING }).pipe(first()).subscribe(result => {
|
||||||
|
this.md2Service.runNextPhase();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.md2Service.runNextPhase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
abstract heroAction(hero: MD2HeroInfo, action: string);
|
abstract heroAction(hero: MD2HeroInfo, action: string);
|
||||||
}
|
}
|
||||||
@@ -254,14 +320,14 @@ export abstract class MD2ComponentBase {
|
|||||||
this.destroy$.complete();
|
this.destroy$.complete();
|
||||||
}
|
}
|
||||||
imgUrl(imgPath: string) {
|
imgUrl(imgPath: string) {
|
||||||
return this.md2Service.stateService.imgUrl(imgPath);
|
return this.md2Service.imgUrl(imgPath);
|
||||||
}
|
}
|
||||||
fileList(folderPath: string) {
|
fileList(folderPath: string) {
|
||||||
return this.md2Service.fileList(folderPath);
|
return this.md2Service.fileList(folderPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
iconHtml(icon: MD2Icon, cssClass = '') {
|
iconHtml(icon: MD2Icon, cssClass = '') {
|
||||||
return this.md2Service.stateService.iconHtml(icon, cssClass);
|
return this.md2Service.iconHtml(icon, cssClass);
|
||||||
}
|
}
|
||||||
detectChanges() {
|
detectChanges() {
|
||||||
if (!this.cdRef['destroyed']) {
|
if (!this.cdRef['destroyed']) {
|
||||||
|
|||||||
+3
-3
@@ -3,7 +3,7 @@
|
|||||||
<nb-card-body class="g-overflow-hidden">
|
<nb-card-body class="g-overflow-hidden">
|
||||||
<div class="row form-group">
|
<div class="row form-group">
|
||||||
<div class="col-md-5 g-height-700px">
|
<div class="col-md-5 g-height-700px">
|
||||||
<md2-mob-stand-info [mob]="boss.info" [mode]="mode"></md2-mob-stand-info>
|
<md2-mob-stand-info [mob]="boss" [mode]="mode"></md2-mob-stand-info>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-7">
|
<div class="col-md-7">
|
||||||
@@ -17,11 +17,11 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
|
|
||||||
<md2-mob-attack-info [mob]="boss.info"></md2-mob-attack-info>
|
<md2-mob-attack-info [mob]="boss"></md2-mob-attack-info>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-8 MD2IconContainer-lg">
|
<div class="col-md-8 MD2IconContainer-lg">
|
||||||
|
|
||||||
<md2-mob-combat-info [mob]="boss.info"></md2-mob-combat-info>
|
<md2-mob-combat-info [mob]="boss"></md2-mob-combat-info>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+4
-3
@@ -6,10 +6,11 @@ import { takeUntil } from 'rxjs/operators';
|
|||||||
import { MD2Service } from '../../../../services/MD2/md2.service';
|
import { MD2Service } from '../../../../services/MD2/md2.service';
|
||||||
import { MsgBoxService } from '../../../../services/msg-box.service';
|
import { MsgBoxService } from '../../../../services/msg-box.service';
|
||||||
import { StateService } from '../../../../services/state.service';
|
import { StateService } from '../../../../services/state.service';
|
||||||
import { MobDlgType, MD2Icon, MD2HeroInfo, RoundPhase } from '../../massive-darkness2.model';
|
import { MobDlgType, MD2Icon, MD2HeroInfo, RoundPhase, MobInfo } from '../../massive-darkness2.model';
|
||||||
import { MobSkill, IBossFight } from '../../massive-darkness2.model.boss';
|
import { MobSkill, IBossFight } from '../../massive-darkness2.model.boss';
|
||||||
import { MD2ComponentBase } from '../../MD2Base';
|
import { MD2ComponentBase } from '../../MD2Base';
|
||||||
import { SpawnMobDlgComponent } from '../../mobs/spawn-mob-dlg/spawn-mob-dlg.component';
|
import { SpawnMobDlgComponent } from '../../mobs/spawn-mob-dlg/spawn-mob-dlg.component';
|
||||||
|
import { MD2MobInfo, MD2MobSkill } from '../../massive-darkness2.db.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ngx-boss-activation',
|
selector: 'ngx-boss-activation',
|
||||||
@@ -17,8 +18,8 @@ import { SpawnMobDlgComponent } from '../../mobs/spawn-mob-dlg/spawn-mob-dlg.com
|
|||||||
styleUrls: ['./boss-activation.component.scss']
|
styleUrls: ['./boss-activation.component.scss']
|
||||||
})
|
})
|
||||||
export class BossActivationComponent implements OnInit {
|
export class BossActivationComponent implements OnInit {
|
||||||
boss: IBossFight;
|
boss: MobInfo;
|
||||||
bossAction: MobSkill;
|
bossAction: MD2MobSkill;
|
||||||
currentAction: number;
|
currentAction: number;
|
||||||
allActions: number;
|
allActions: number;
|
||||||
MobDlgType = MobDlgType;
|
MobDlgType = MobDlgType;
|
||||||
|
|||||||
@@ -3,34 +3,50 @@
|
|||||||
{{boss.name}}
|
{{boss.name}}
|
||||||
<button nbButton hero status="primary" (click)="activate()">Action</button>
|
<button nbButton hero status="primary" (click)="activate()">Action</button>
|
||||||
</nb-card-header>
|
</nb-card-header>
|
||||||
<nb-card-body>
|
<nb-card-body class="g-overflow-hidden">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-5">
|
<div class="col-md-5">
|
||||||
<img src="{{boss.standUrl}}" class="w-100 g-max-height-80vh">
|
<!-- <img src="{{boss.standUrl}}" class="w-100 bossStandImg"> -->
|
||||||
|
|
||||||
|
<md2-mob-stand-info [mob]="boss" [mode]="MobDlgType.PreView"></md2-mob-stand-info>
|
||||||
|
<!-- HP and Mana Bars -->
|
||||||
|
<div class="hero-stats-overlay">
|
||||||
|
<div class="stat-bar-overlay hp-bar-overlay">
|
||||||
|
<div class="stat-bar-label-overlay">
|
||||||
|
<md2-icon [icon]="MD2Icon.HP_Color" size="sm"></md2-icon>
|
||||||
|
<span class="stat-value-overlay">{{boss.unitRemainHp}}/{{boss.hp}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-progress-bar-overlay">
|
||||||
|
<div class="stat-progress-fill-overlay hp-fill-overlay"
|
||||||
|
[style.width.%]="(boss.unitRemainHp / boss.hp) * 100">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-7">
|
<div class="col-md-7">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md">
|
<div class="col-md">
|
||||||
<adj-number-input name="mob{{boss.info.name}}" [(ngModel)]="boss.info.unitRemainHp" minimum="0"
|
<adj-number-input name="mob{{boss.name}}" [(ngModel)]="boss.unitRemainHp" minimum="0"
|
||||||
class="mb-3" title="Boss HP" (hitMinimum)="WIN()">
|
class="mb-3" title="Boss HP" (hitMinimum)="WIN()">
|
||||||
</adj-number-input>
|
</adj-number-input>
|
||||||
<md2-mob-attack-info [mob]="boss.info">
|
<md2-mob-attack-info [mob]="boss">
|
||||||
</md2-mob-attack-info>
|
</md2-mob-attack-info>
|
||||||
<md2-mob-def-info [mob]="boss.info"></md2-mob-def-info>
|
<md2-mob-def-info [mob]="boss"></md2-mob-def-info>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-9 h6" *ngIf="boss.extraRules">
|
<div class="col-md-9 bossSpecialRules" *ngIf="boss.bossFightProfile.specialRules">
|
||||||
<div [innerHtml]="boss.extraRules"></div>
|
<div [innerHtml]="boss.bossFightProfile.specialRules"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<md2-mob-combat-info [mob]="boss.info"></md2-mob-combat-info>
|
<md2-mob-combat-info [mob]="boss"></md2-mob-combat-info>
|
||||||
<!--
|
<!--
|
||||||
<button nbButton hero status="danger" size="small" (click)="attack(boss.info)">Attack It</button> -->
|
<button nbButton hero status="danger" size="small" (click)="attack(boss)">Attack It</button> -->
|
||||||
|
|
||||||
<!-- <label class="MD2Text mt-3" [innerHtml]="boss.info.combatSkill.skillName">
|
<!-- <label class="MD2Text mt-3" [innerHtml]="boss.combatSkill.skillName">
|
||||||
</label>
|
</label>
|
||||||
<label class="MD2Text" [innerHtml]="boss.info.combatInfo.skillDescription">
|
<label class="MD2Text" [innerHtml]="boss.combatInfo.skillDescription">
|
||||||
</label> -->
|
</label> -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -2,3 +2,136 @@ nb-card {
|
|||||||
height: 80vh;
|
height: 80vh;
|
||||||
//width: 80vw;
|
//width: 80vw;
|
||||||
}
|
}
|
||||||
|
.bossStandImg {
|
||||||
|
max-height: 67vh;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
::ng-deep .bossSpecialRules {
|
||||||
|
.MD2Icon {
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// HP and Mana Bars Overlay
|
||||||
|
.hero-stats-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
background: linear-gradient(to bottom, rgba(0, 0, 0, 0.7) 0%, rgba(0, 0, 0, 0.5) 70%, transparent 100%);
|
||||||
|
border-radius: 0 0 8px 8px;
|
||||||
|
z-index: 2;
|
||||||
|
width: 95%;
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
padding: 0.35rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-bar-overlay {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
margin-bottom: 0.35rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-bar-label-overlay {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
|
md2-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value-overlay {
|
||||||
|
color: white;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
margin-bottom: 0.15rem;
|
||||||
|
gap: 0.3rem;
|
||||||
|
|
||||||
|
md2-icon {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value-overlay {
|
||||||
|
font-size: 0.65rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-progress-bar-overlay {
|
||||||
|
width: 100%;
|
||||||
|
height: 20px;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-progress-fill-overlay {
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 10px;
|
||||||
|
transition: width 0.5s ease-out;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
|
||||||
|
animation: shimmer 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.full-stat {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hp-fill-overlay {
|
||||||
|
background: linear-gradient(90deg, #ff6b6b, #ee5a6f);
|
||||||
|
box-shadow: 0 0 8px rgba(238, 90, 111, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mp-fill-overlay {
|
||||||
|
background: linear-gradient(90deg, #4ecdc4, #44a08d);
|
||||||
|
box-shadow: 0 0 8px rgba(68, 160, 141, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shimmer {
|
||||||
|
0% {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ import { SpawnMobDlgComponent } from '../mobs/spawn-mob-dlg/spawn-mob-dlg.compon
|
|||||||
styleUrls: ['./boss-fight.component.scss']
|
styleUrls: ['./boss-fight.component.scss']
|
||||||
})
|
})
|
||||||
export class BossFightComponent extends MD2ComponentBase {
|
export class BossFightComponent extends MD2ComponentBase {
|
||||||
|
MobDlgType = MobDlgType;
|
||||||
|
MD2Icon = MD2Icon;
|
||||||
public get boss() {
|
public get boss() {
|
||||||
return this.md2Service.info.boss;
|
return this.md2Service.info.boss;
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,7 @@ export class BossFightComponent extends MD2ComponentBase {
|
|||||||
super.ngOnInit();
|
super.ngOnInit();
|
||||||
this.md2Service.heroAttackingSubject.pipe(takeUntil(this.destroy$)).subscribe(result => {
|
this.md2Service.heroAttackingSubject.pipe(takeUntil(this.destroy$)).subscribe(result => {
|
||||||
if (this.md2Service.info.isBossFight) {
|
if (this.md2Service.info.isBossFight) {
|
||||||
this.attack(this.boss.info);
|
this.attack(this.boss);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -49,10 +49,14 @@ export class BossFightComponent extends MD2ComponentBase {
|
|||||||
this.destroy$.complete();
|
this.destroy$.complete();
|
||||||
}
|
}
|
||||||
activate() {
|
activate() {
|
||||||
this.boss.activating();
|
this.md2Service.activateBoss();
|
||||||
}
|
}
|
||||||
WIN() {
|
WIN() {
|
||||||
|
this.msgBoxService.show('Win', { text: 'You Win the Boss Fight', icon: ADIcon.INFO });
|
||||||
|
this.md2Service.info.isBossFight = false;
|
||||||
|
this.md2Service.info.boss = undefined;
|
||||||
|
this.md2Service.heros.forEach(h => h.uiBossFight = false);
|
||||||
|
this.md2Service.broadcastGameInfo();
|
||||||
}
|
}
|
||||||
attack(mob: MobInfo) {
|
attack(mob: MobInfo) {
|
||||||
|
|
||||||
@@ -61,7 +65,10 @@ export class BossFightComponent extends MD2ComponentBase {
|
|||||||
if (mobResult) {
|
if (mobResult) {
|
||||||
let attackDamage = mobResult.uiWounds;
|
let attackDamage = mobResult.uiWounds;
|
||||||
if (attackDamage) {
|
if (attackDamage) {
|
||||||
this.boss.info.hp -= attackDamage;
|
this.boss.unitRemainHp -= attackDamage;
|
||||||
|
if (this.boss.unitRemainHp <= 0) {
|
||||||
|
this.WIN();
|
||||||
|
}
|
||||||
this.cdRef.detectChanges();
|
this.cdRef.detectChanges();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export class MD2Clone {
|
|||||||
let cloneObj = null;
|
let cloneObj = null;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "TreasureItem":
|
case "TreasureItem":
|
||||||
|
//let copy = structuredClone(obj);
|
||||||
return new TreasureItem(obj['type'], 1);
|
return new TreasureItem(obj['type'], 1);
|
||||||
break;
|
break;
|
||||||
case "MobInfo":
|
case "MobInfo":
|
||||||
|
|||||||
@@ -1,222 +0,0 @@
|
|||||||
import { environment } from "../../../../../environments/environment";
|
|
||||||
import { DefenseInfo, IMobFactory, MD2Icon, MobInfo } from "../../massive-darkness2.model";
|
|
||||||
import { MobSkill, MobSkillType } from "../../massive-darkness2.model.boss";
|
|
||||||
|
|
||||||
const MD2_IMG_URL = (id: string = null) => { return `${environment.apiUrl}/Files/Images/MD2/Mobs${(id ? `${encodeURI(id)}` : '')}` }
|
|
||||||
const CORE_GAME_MOB_LEVEL = [
|
|
||||||
{ name: 'Gargoyles', level: 1, hp: 2, rewardTokens: 1, defBlue: 1 },
|
|
||||||
{ name: 'Gargoyles', level: 3, hp: 3, rewardTokens: 1, defBlue: 2 },
|
|
||||||
{ name: 'Gargoyles', level: 5, hp: 6, rewardTokens: 2, defBlue: 3 },
|
|
||||||
|
|
||||||
{ name: 'Demons', level: 1, hp: 3, rewardTokens: 1, defBlue: 1 },
|
|
||||||
{ name: 'Demons', level: 3, hp: 4, rewardTokens: 1, defBlue: 2 },
|
|
||||||
{ name: 'Demons', level: 5, hp: 6, rewardTokens: 2, defBlue: 4 },
|
|
||||||
|
|
||||||
{ name: 'Undead', level: 1, hp: 4, rewardTokens: 1, defBlue: 1 },
|
|
||||||
{ name: 'Undead', level: 3, hp: 5, rewardTokens: 1, defBlue: 1 },
|
|
||||||
{ name: 'Undead', level: 5, hp: 8, rewardTokens: 2, defBlue: 1 },
|
|
||||||
|
|
||||||
{ name: 'Fire Entities', level: 1, hp: 3, rewardTokens: 1, defBlue: 1 },
|
|
||||||
{ name: 'Fire Entities', level: 3, hp: 4, rewardTokens: 1, defBlue: 2 },
|
|
||||||
{ name: 'Fire Entities', level: 5, hp: 7, rewardTokens: 2, defBlue: 3 },
|
|
||||||
|
|
||||||
{ name: 'Fallen Angels', level: 1, hp: 2, rewardTokens: 1, defBlue: 2 },
|
|
||||||
{ name: 'Fallen Angels', level: 3, hp: 3, rewardTokens: 1, defBlue: 3 },
|
|
||||||
{ name: 'Fallen Angels', level: 5, hp: 5, rewardTokens: 2, defBlue: 5 },
|
|
||||||
|
|
||||||
{ name: 'Infernal Imps', level: 1, hp: 3, rewardTokens: 1, defBlue: 1 },
|
|
||||||
{ name: 'Infernal Imps', level: 3, hp: 4, rewardTokens: 1, defBlue: 1 },
|
|
||||||
{ name: 'Infernal Imps', level: 5, hp: 5, rewardTokens: 2, defBlue: 3 },
|
|
||||||
|
|
||||||
{ name: 'Skeletons', level: 1, hp: 2, rewardTokens: 1, defBlue: 1 },
|
|
||||||
{ name: 'Skeletons', level: 3, hp: 3, rewardTokens: 1, defBlue: 2 },
|
|
||||||
{ name: 'Skeletons', level: 5, hp: 5, rewardTokens: 2, defBlue: 4 },
|
|
||||||
|
|
||||||
{ name: 'Satyrs', level: 1, hp: 3, rewardTokens: 1, defBlue: 1 },
|
|
||||||
{ name: 'Satyrs', level: 3, hp: 4, rewardTokens: 1, defBlue: 2 },
|
|
||||||
{ name: 'Satyrs', level: 5, hp: 6, rewardTokens: 2, defBlue: 4 }]
|
|
||||||
|
|
||||||
export abstract class MobFactory implements IMobFactory {
|
|
||||||
abstract mobName: string;
|
|
||||||
abstract generate(level: number): MobInfo
|
|
||||||
protected mob: MobInfo;
|
|
||||||
protected loadLevelInfo(mobName: string, level: number) {
|
|
||||||
let levelInfo = CORE_GAME_MOB_LEVEL.find(m => m.name == mobName && level >= m.level);
|
|
||||||
|
|
||||||
this.mob = new MobInfo({
|
|
||||||
name: mobName, hp: levelInfo.hp, level: level, rewardTokens: levelInfo.rewardTokens,
|
|
||||||
defenseInfo: new DefenseInfo(levelInfo.defBlue)
|
|
||||||
});
|
|
||||||
this.mob.leaderImgUrl = MD2_IMG_URL(`/CoreGame/Mobs/${this.mob.name}/Leader.png`);
|
|
||||||
this.mob.minionImgUrl = MD2_IMG_URL(`/CoreGame/Mobs/${this.mob.name}/Minion.png`);
|
|
||||||
|
|
||||||
}
|
|
||||||
iconHtml(icon: MD2Icon, cssClass = 'g-font-size-24') {
|
|
||||||
if (icon == MD2Icon.Fire) {
|
|
||||||
cssClass += ' g-color-google-plus ';
|
|
||||||
}
|
|
||||||
if (icon == MD2Icon.Frost || icon == MD2Icon.Mana) {
|
|
||||||
cssClass += ' g-color-aqua ';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (icon < MD2Icon.RedDice) {
|
|
||||||
return `<span class='MD2Icon ${cssClass}'>${String.fromCharCode(65 + icon)}</span>`
|
|
||||||
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return `<span class='MD2Icon dice ${MD2Icon[icon].replace('Dice', '')} ${cssClass}'></span>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export class MobDemonsFactory extends MobFactory {
|
|
||||||
mobName: string = 'Demons';
|
|
||||||
generate(level: number): MobInfo {
|
|
||||||
this.loadLevelInfo('Demons', level);
|
|
||||||
this.mob.combatSkill = new MobSkill(
|
|
||||||
{
|
|
||||||
description: `Attacking or defending Hero discards 1 ${this.iconHtml(MD2Icon.Mana)}`
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return this.mob;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
export class MobFallenAngelFactory extends MobFactory {
|
|
||||||
mobName: string = 'Fallen Angels';
|
|
||||||
generate(level: number): MobInfo {
|
|
||||||
this.loadLevelInfo('Fallen Angels', level);
|
|
||||||
this.mob.combatSkill = new MobSkill(
|
|
||||||
{
|
|
||||||
description: `Defender -${level == 1 ? 1 : 2} ${this.iconHtml(MD2Icon.Defense)}`,
|
|
||||||
type: MobSkillType.Attack
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return this.mob;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class MobFireEntitiesFactory extends MobFactory {
|
|
||||||
mobName: string = 'Fire Entities';
|
|
||||||
generate(level: number): MobInfo {
|
|
||||||
this.loadLevelInfo('Fire Entities', level);
|
|
||||||
this.mob.combatSkill = new MobSkill(
|
|
||||||
{
|
|
||||||
description: `Add 1 ${this.iconHtml(MD2Icon.Fire)} to the attacking or defending Hero.`,
|
|
||||||
type: MobSkillType.Combat
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return this.mob;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class MobGargoylesFactory extends MobFactory {
|
|
||||||
mobName: string = 'Gargoyles';
|
|
||||||
generate(level: number): MobInfo {
|
|
||||||
this.loadLevelInfo('Gargoyles', level);
|
|
||||||
this.mob.combatSkill = new MobSkill(
|
|
||||||
{
|
|
||||||
description: `+ ${level < 5 ? 1 : 2} ${this.iconHtml(MD2Icon.Defense)}`,
|
|
||||||
type: MobSkillType.Defense
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return this.mob;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export class MobInfernalImpsFactory extends MobFactory {
|
|
||||||
mobName: string = 'Infernal Imps';
|
|
||||||
generate(level: number): MobInfo {
|
|
||||||
this.loadLevelInfo('Infernal Imps', level);
|
|
||||||
let damage = 1;
|
|
||||||
switch (level) {
|
|
||||||
case 1:
|
|
||||||
case 2:
|
|
||||||
damage = 1;
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
case 4:
|
|
||||||
damage = 2;
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
damage = 3;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
damage = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
this.mob.combatSkill = new MobSkill(
|
|
||||||
{
|
|
||||||
description: `Kill 1 Imp, then deal ${damage} Wound to each Hero in the attacker's Zone(once per roll).`,
|
|
||||||
type: MobSkillType.Defense
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return this.mob;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export class MobSatyrsFactory extends MobFactory {
|
|
||||||
mobName: string = 'Satyrs';
|
|
||||||
generate(level: number): MobInfo {
|
|
||||||
this.loadLevelInfo('Satyrs', level);
|
|
||||||
this.mob.combatSkill = new MobSkill(
|
|
||||||
{
|
|
||||||
description: `+ ${level < 3 ? 1 : 2} ${this.iconHtml(MD2Icon.Attack)}`,
|
|
||||||
type: MobSkillType.Attack
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return this.mob;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class MobSkeletonsFactory extends MobFactory {
|
|
||||||
mobName: string = 'Skeletons';
|
|
||||||
generate(level: number): MobInfo {
|
|
||||||
this.loadLevelInfo('Skeletons', level);
|
|
||||||
this.mob.combatSkill = new MobSkill(
|
|
||||||
{
|
|
||||||
description: `Add 1 minion to this Mob(if possible) unless the Hero discards ${level < 5 ? 1 : 2} ${this.iconHtml(MD2Icon.Mana)}.`,
|
|
||||||
type: MobSkillType.Defense,
|
|
||||||
skillRoll: 2
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return this.mob;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class MobUndeadFactory extends MobFactory {
|
|
||||||
mobName: string = 'Undead';
|
|
||||||
generate(level: number): MobInfo {
|
|
||||||
this.loadLevelInfo('Undead', level);
|
|
||||||
|
|
||||||
let skillDesc = '';
|
|
||||||
if (level < 3) {
|
|
||||||
skillDesc = `+1 ${this.iconHtml(MD2Icon.YellowDice)}`;
|
|
||||||
} else if (level < 5) {
|
|
||||||
skillDesc = `+2 ${this.iconHtml(MD2Icon.YellowDice)}`;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
skillDesc = `+1 ${this.iconHtml(MD2Icon.YellowDice)} 1 ${this.iconHtml(MD2Icon.OrangeDice)}`;
|
|
||||||
|
|
||||||
}
|
|
||||||
skillDesc += ' and this Mob takes 2 wounds';
|
|
||||||
this.mob.combatSkill = new MobSkill(
|
|
||||||
{
|
|
||||||
description: skillDesc,
|
|
||||||
type: MobSkillType.Attack
|
|
||||||
}
|
|
||||||
)
|
|
||||||
this.mob.drawingWeight = 1;
|
|
||||||
return this.mob;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export const CoreGameMobFactories = [
|
|
||||||
new MobDemonsFactory(),
|
|
||||||
new MobFallenAngelFactory(),
|
|
||||||
new MobFireEntitiesFactory(),
|
|
||||||
new MobGargoylesFactory(),
|
|
||||||
new MobInfernalImpsFactory(),
|
|
||||||
new MobSatyrsFactory(),
|
|
||||||
new MobSkeletonsFactory(),
|
|
||||||
new MobUndeadFactory(),
|
|
||||||
];
|
|
||||||
@@ -3,80 +3,11 @@ import { first, map } from "rxjs/operators";
|
|||||||
import { environment } from "../../../../../environments/environment";
|
import { environment } from "../../../../../environments/environment";
|
||||||
import { ADButtons, ADIcon } from "../../../../ui/alert-dlg/alert-dlg.model";
|
import { ADButtons, ADIcon } from "../../../../ui/alert-dlg/alert-dlg.model";
|
||||||
import { MD2Logic } from "../../massive-darkness2.logic";
|
import { MD2Logic } from "../../massive-darkness2.logic";
|
||||||
import { AttackInfo, AttackTarget, DefenseInfo, IMobFactory, MD2Icon, MobInfo, MobType, TreasureItem, TreasureType } from "../../massive-darkness2.model";
|
import { AttackInfo, AttackTarget, IMobFactory, MD2Icon, MobInfo, MobType, TreasureItem, TreasureType } from "../../massive-darkness2.model";
|
||||||
import { MobSkill, MobSkillType } from "../../massive-darkness2.model.boss";
|
import { MobSkillType } from "../../massive-darkness2.model.boss";
|
||||||
|
import { MD2DiceSet, MD2MobSkill } from "../../massive-darkness2.db.model";
|
||||||
|
|
||||||
const MD2_IMG_URL = (id: string = null) => { return `${environment.apiUrl}/Files/Images/MD2/Mobs${(id ? `${encodeURI(id)}` : '')}` }
|
const MD2_IMG_URL = (id: string = null) => { return `${environment.apiUrl}/Files/Images/MD2/Mobs${(id ? `${encodeURI(id)}` : '')}` }
|
||||||
const CORE_GAME_MOB_LEVEL = [
|
|
||||||
|
|
||||||
new MobInfo({
|
|
||||||
name: 'Andra', level: 1, hp: 5,
|
|
||||||
|
|
||||||
attackInfos: [new AttackInfo(MD2Icon.Melee, 1, 0, 0, 1), new AttackInfo(MD2Icon.Range, 1, 0, 0, 1)],
|
|
||||||
defenseInfo: new DefenseInfo(2, 1),
|
|
||||||
}),
|
|
||||||
new MobInfo({
|
|
||||||
name: 'Andra', level: 3, hp: 7,
|
|
||||||
|
|
||||||
attackInfos: [new AttackInfo(MD2Icon.Melee, 1, 1, 0, 1), new AttackInfo(MD2Icon.Range, 1, 1, 0, 1)],
|
|
||||||
defenseInfo: new DefenseInfo(3, 1),
|
|
||||||
}),
|
|
||||||
new MobInfo({
|
|
||||||
name: 'Andra', level: 5, hp: 5,
|
|
||||||
attackInfos: [new AttackInfo(MD2Icon.Melee, 1, 2, 0, 1), new AttackInfo(MD2Icon.Range, 1, 2, 0, 1)],
|
|
||||||
defenseInfo: new DefenseInfo(5, 1),
|
|
||||||
}),
|
|
||||||
|
|
||||||
new MobInfo({
|
|
||||||
name: 'Ytheria, Undead Queen', level: 1, hp: 4,
|
|
||||||
attackInfos: [new AttackInfo(MD2Icon.Melee, 1), new AttackInfo(MD2Icon.Range, 2, 0, 0, 1)],
|
|
||||||
defenseInfo: new DefenseInfo(1, 1),
|
|
||||||
|
|
||||||
}),
|
|
||||||
new MobInfo({
|
|
||||||
name: 'Ytheria, Undead Queen', level: 3, hp: 6,
|
|
||||||
attackInfos: [new AttackInfo(MD2Icon.Melee, 0, 1), new AttackInfo(MD2Icon.Range, 1, 1, 0, 1)],
|
|
||||||
defenseInfo: new DefenseInfo(2, 1),
|
|
||||||
|
|
||||||
}),
|
|
||||||
new MobInfo({
|
|
||||||
name: 'Ytheria, Undead Queen', level: 5, hp: 8,
|
|
||||||
attackInfos: [new AttackInfo(MD2Icon.Melee, 2, 1), new AttackInfo(MD2Icon.Range, 2, 1, 0, 1)],
|
|
||||||
defenseInfo: new DefenseInfo(4, 1),
|
|
||||||
}),
|
|
||||||
|
|
||||||
new MobInfo({
|
|
||||||
name: 'Lyidan, Incubus Lord', level: 1, hp: 7,
|
|
||||||
attackInfos: [new AttackInfo(MD2Icon.Melee, 0, 1, 0, 2)],
|
|
||||||
defenseInfo: new DefenseInfo(2, 1),
|
|
||||||
}),
|
|
||||||
new MobInfo({
|
|
||||||
name: 'Lyidan, Incubus Lord', level: 3, hp: 10,
|
|
||||||
attackInfos: [new AttackInfo(MD2Icon.Melee, 0, 2, 0, 1)],
|
|
||||||
defenseInfo: new DefenseInfo(2, 1),
|
|
||||||
}),
|
|
||||||
new MobInfo({
|
|
||||||
name: 'Lyidan, Incubus Lord', level: 5, hp: 12,
|
|
||||||
attackInfos: [new AttackInfo(MD2Icon.Melee, 2, 2, 0, 1)],
|
|
||||||
defenseInfo: new DefenseInfo(4, 1),
|
|
||||||
}),
|
|
||||||
|
|
||||||
new MobInfo({
|
|
||||||
name: 'The Ghoul', level: 1, hp: 5,
|
|
||||||
attackInfos: [new AttackInfo(MD2Icon.Melee, 0, 1, 0, 1)],
|
|
||||||
defenseInfo: new DefenseInfo(2, 1),
|
|
||||||
}),
|
|
||||||
new MobInfo({
|
|
||||||
name: 'The Ghoul', level: 3, hp: 8,
|
|
||||||
attackInfos: [new AttackInfo(MD2Icon.Melee, 0, 2, 0, 2)],
|
|
||||||
defenseInfo: new DefenseInfo(3, 1),
|
|
||||||
}),
|
|
||||||
new MobInfo({
|
|
||||||
name: 'The Ghoul', level: 5, hp: 10,
|
|
||||||
attackInfos: [new AttackInfo(MD2Icon.Melee, 0, 3, 0, 3)],
|
|
||||||
defenseInfo: new DefenseInfo(4, 1),
|
|
||||||
}),
|
|
||||||
]
|
|
||||||
|
|
||||||
export abstract class CoreGameRMFactory implements IMobFactory {
|
export abstract class CoreGameRMFactory implements IMobFactory {
|
||||||
abstract mobName: string;
|
abstract mobName: string;
|
||||||
@@ -168,15 +99,16 @@ export class RMUndeadQueenFactory extends CoreGameRMFactory {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.mob.combatSkill = new MobSkill(
|
this.mob.skills = [
|
||||||
{
|
{
|
||||||
description: `Add 1 Minion to each Mob in the Dungeon, if possible.`,
|
description: `Add 1 Minion to each Mob in the Dungeon, if possible.`,
|
||||||
type: MobSkillType.Attack
|
type: MobSkillType.Attack,
|
||||||
}
|
|
||||||
)
|
skillRoll: 1
|
||||||
|
} as MD2MobSkill];
|
||||||
return this.mob;
|
return this.mob;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -200,9 +132,6 @@ export class RMAndraFactory extends CoreGameRMFactory {
|
|||||||
}).pipe(first()).subscribe(result => {
|
}).pipe(first()).subscribe(result => {
|
||||||
if (result) {
|
if (result) {
|
||||||
mob.actions = 0;
|
mob.actions = 0;
|
||||||
mob.actionSubject.next(
|
|
||||||
`Undead Queen attacks each Hero in LoS(resolve each attack separately).`
|
|
||||||
);
|
|
||||||
|
|
||||||
msgBoxService.show('Is Any Hero in the LoS of Andra?', {
|
msgBoxService.show('Is Any Hero in the LoS of Andra?', {
|
||||||
icon: ADIcon.QUESTION,
|
icon: ADIcon.QUESTION,
|
||||||
@@ -229,12 +158,12 @@ export class RMAndraFactory extends CoreGameRMFactory {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.mob.combatSkill = new MobSkill(
|
this.mob.skills = [
|
||||||
{
|
{
|
||||||
description: `Deal ${damage} wound to another Hero with the lowest HP in LoS`,
|
description: `Deal ${damage} wound to another Hero with the lowest HP in LoS`,
|
||||||
type: MobSkillType.Combat
|
type: MobSkillType.Combat,
|
||||||
}
|
skillRoll: 1
|
||||||
)
|
} as MD2MobSkill];
|
||||||
return this.mob;
|
return this.mob;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -273,12 +202,12 @@ export class RMTheGhoulFactory extends CoreGameRMFactory {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.mob.combatSkill = new MobSkill(
|
this.mob.skills = [
|
||||||
{
|
{
|
||||||
description: `Move the closest <b>Mob with minion</b> 1 Zone toward The Ghoul.`,
|
description: `Move the closest <b>Mob with minion</b> 1 Zone toward The Ghoul.`,
|
||||||
type: MobSkillType.Combat
|
type: MobSkillType.Combat,
|
||||||
}
|
skillRoll: 1
|
||||||
)
|
} as MD2MobSkill];
|
||||||
return this.mob;
|
return this.mob;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -300,7 +229,7 @@ export class RMLyidanIncubusLordFactory extends CoreGameRMFactory {
|
|||||||
`The Incubus Lord got 3 Wounds, Move it to the closest Shadow Zone.`
|
`The Incubus Lord got 3 Wounds, Move it to the closest Shadow Zone.`
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
msgBoxService.show('Is there a Herp up to 3 Zones away(regardless of LoS) from The Incubus Lord?', {
|
msgBoxService.show('Is there a Hero up to 3 Zones away(regardless of LoS) from The Incubus Lord?', {
|
||||||
icon: ADIcon.QUESTION,
|
icon: ADIcon.QUESTION,
|
||||||
buttons: ADButtons.YesNo
|
buttons: ADButtons.YesNo
|
||||||
}).pipe(first()).subscribe(result => {
|
}).pipe(first()).subscribe(result => {
|
||||||
@@ -320,12 +249,48 @@ export class RMLyidanIncubusLordFactory extends CoreGameRMFactory {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.mob.combatSkill = new MobSkill(
|
this.mob.skills = [
|
||||||
{
|
{
|
||||||
description: `After combat, resolve all ${this.iconHtml(MD2Icon.Fire)} on the defending Hero(once per combat).`,
|
description: `After combat, resolve all ${this.iconHtml(MD2Icon.Fire)} on the defending Hero(once per combat).`,
|
||||||
type: MobSkillType.Attack
|
type: MobSkillType.Attack,
|
||||||
|
skillRoll: 1
|
||||||
|
} as MD2MobSkill];
|
||||||
|
return this.mob;
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class RMBalrogFactory extends CoreGameRMFactory {
|
||||||
|
mobName: string = 'Balrog';
|
||||||
|
generate(level: number): MobInfo {
|
||||||
|
this.loadLevelInfo('Balrog', level);
|
||||||
|
this.mob.extraRule = `When Balrog is in the Dungeon, ${this.iconHtml(MD2Icon.Fire)} on Heros can't be removed, Heros still suffer its effects when activating.`
|
||||||
|
this.mob.activateFunction = (mob, msgBoxService, heros) => {
|
||||||
|
let actionResult = '';
|
||||||
|
|
||||||
|
mob.actions = 0;
|
||||||
|
let noFireHeros = heros.filter(h => h.fireToken == 0);
|
||||||
|
if (noFireHeros.length == 0) {
|
||||||
|
|
||||||
|
mob.actions = 3;
|
||||||
|
mob.actionSubject.next(
|
||||||
|
`The Balrog gains 3 Actions`
|
||||||
|
);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
let fireTokens = Math.round(Math.random() * 2) + 1;
|
||||||
|
mob.actionSubject.next(
|
||||||
|
`Balrog ,moves 2 Zones toward to ${MD2Logic.getTargetHerosHtml(noFireHeros)} and Each Hero in ${this.iconHtml(MD2Icon.Magic)} range takes ${fireTokens} ${this.iconHtml(MD2Icon.Fire)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.mob.skills = [
|
||||||
|
{
|
||||||
|
description: `The Hero takes 1 ${this.iconHtml(MD2Icon.Fire)}`,
|
||||||
|
type: MobSkillType.Combat,
|
||||||
|
skillRoll: 1
|
||||||
|
} as MD2MobSkill];
|
||||||
return this.mob;
|
return this.mob;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -334,4 +299,5 @@ export const CoreGameRMFactories = [
|
|||||||
new RMAndraFactory(),
|
new RMAndraFactory(),
|
||||||
new RMTheGhoulFactory(),
|
new RMTheGhoulFactory(),
|
||||||
new RMLyidanIncubusLordFactory(),
|
new RMLyidanIncubusLordFactory(),
|
||||||
|
new RMBalrogFactory()
|
||||||
];
|
];
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<nb-card status="info" size="large">
|
||||||
|
<nb-card-header>
|
||||||
|
<img src="{{md2Service.imgUrl('HeroIcon.png')}}" width="40px">
|
||||||
|
<span class="ml-2 g-font-size-17">Initialize Game</span>
|
||||||
|
</nb-card-header>
|
||||||
|
<nb-card-body>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="label">Select Game Bundles:</label>
|
||||||
|
<div class="form-group" *ngFor="let bundle of bundleOptions">
|
||||||
|
<nb-checkbox [checked]="isBundleEnabled(bundle.value)" (checkedChange)="toggleBundle(bundle.value)">
|
||||||
|
{{ bundle.label }}
|
||||||
|
</nb-checkbox>
|
||||||
|
</div>
|
||||||
|
<small class="form-text text-muted" *ngIf="enabledBundles.length === 0">
|
||||||
|
At least one bundle must be selected.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<nb-checkbox [(checked)]="enableMobSpecialRule">
|
||||||
|
Enable Mob Special Rules
|
||||||
|
</nb-checkbox>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<nb-checkbox [(checked)]="enableHeroBetrayal">
|
||||||
|
Enable Hero Betrayal
|
||||||
|
</nb-checkbox>
|
||||||
|
</div>
|
||||||
|
</nb-card-body>
|
||||||
|
<nb-card-footer>
|
||||||
|
<button class="float-right" nbButton hero status="warning" size="small" (click)="cancel()">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button class="float-right mr-2" nbButton hero status="primary" size="small"
|
||||||
|
[disabled]="enabledBundles.length === 0" (click)="submit()">
|
||||||
|
Initialize
|
||||||
|
</button>
|
||||||
|
</nb-card-footer>
|
||||||
|
</nb-card>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
nb-card {
|
||||||
|
min-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
nb-checkbox {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import { Component, OnInit, Input } from '@angular/core';
|
||||||
|
import { NbDialogRef } from '@nebular/theme';
|
||||||
|
import { GameBundle } from '../massive-darkness2.db.model';
|
||||||
|
import { MD2Service } from '../../../services/MD2/md2.service';
|
||||||
|
import { StringUtils } from '../../../utilities/string-utils';
|
||||||
|
|
||||||
|
export interface GameInitConfig {
|
||||||
|
enabledBundles: GameBundle[];
|
||||||
|
enableMobSpecialRule: boolean;
|
||||||
|
enableHeroBetrayal: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ngx-game-init-dlg',
|
||||||
|
templateUrl: './game-init-dlg.component.html',
|
||||||
|
styleUrls: ['./game-init-dlg.component.scss']
|
||||||
|
})
|
||||||
|
export class GameInitDlgComponent implements OnInit {
|
||||||
|
GameBundle = GameBundle;
|
||||||
|
|
||||||
|
@Input() initialConfig: GameInitConfig;
|
||||||
|
|
||||||
|
enabledBundles: GameBundle[] = [GameBundle.CoreGame];
|
||||||
|
enableMobSpecialRule: boolean = false;
|
||||||
|
enableHeroBetrayal: boolean = false;
|
||||||
|
|
||||||
|
bundleOptions = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private dlgRef: NbDialogRef<GameInitDlgComponent>,
|
||||||
|
public md2Service: MD2Service
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
//For each GameBundle, create a new option
|
||||||
|
this.bundleOptions = Object.values(GameBundle).filter(b => !isNaN(Number(b))).map(b => ({
|
||||||
|
value: b as GameBundle, label: StringUtils.camelToTitle(GameBundle[b as GameBundle] as string)
|
||||||
|
}));
|
||||||
|
// Initialize from context if provided
|
||||||
|
if (this.initialConfig) {
|
||||||
|
this.enabledBundles = [...this.initialConfig.enabledBundles];
|
||||||
|
this.enableMobSpecialRule = this.initialConfig.enableMobSpecialRule;
|
||||||
|
this.enableHeroBetrayal = this.initialConfig.enableHeroBetrayal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleBundle(bundle: GameBundle): void {
|
||||||
|
const index = this.enabledBundles.indexOf(bundle);
|
||||||
|
if (index > -1) {
|
||||||
|
this.enabledBundles.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
this.enabledBundles.push(bundle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isBundleEnabled(bundle: GameBundle): boolean {
|
||||||
|
return this.enabledBundles.includes(bundle);
|
||||||
|
}
|
||||||
|
|
||||||
|
submit(): void {
|
||||||
|
const config: GameInitConfig = {
|
||||||
|
enabledBundles: this.enabledBundles,
|
||||||
|
enableMobSpecialRule: this.enableMobSpecialRule,
|
||||||
|
enableHeroBetrayal: this.enableHeroBetrayal
|
||||||
|
};
|
||||||
|
this.dlgRef.close(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel(): void {
|
||||||
|
this.dlgRef.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,24 +1,203 @@
|
|||||||
<nb-card *ngIf="!hero">
|
<!-- Hero Selection Screen - Initial -->
|
||||||
<nb-card-body>
|
<nb-card *ngIf="!hero && !isSelectingHero" class="hero-selection-card">
|
||||||
<button nbButton hero status="primary" fullWidth (click)="initHero()">Choose Hero</button>
|
<nb-card-body class="hero-selection-body">
|
||||||
|
<div class="hero-selection-content">
|
||||||
|
<h2 class="hero-selection-title">Choose Your Hero</h2>
|
||||||
|
<p class="hero-selection-subtitle">Begin your epic adventure</p>
|
||||||
|
<button nbButton hero status="primary" size="large" class="hero-selection-btn" (click)="initHero()">
|
||||||
|
<nb-icon icon="star-outline" class="mr-2"></nb-icon>
|
||||||
|
Select Hero
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</nb-card-body>
|
</nb-card-body>
|
||||||
</nb-card>
|
</nb-card>
|
||||||
|
|
||||||
|
<!-- Hero Selection Panel -->
|
||||||
|
<div *ngIf="!hero && isSelectingHero && currentSelectingHero" class="hero-selection-panel">
|
||||||
|
<div class="row no-gutters hero-selection-row">
|
||||||
|
<div class="col-12 hero-selection-left">
|
||||||
|
<div class="hero-selection-card-wrapper">
|
||||||
|
<div class="hero-selection-header">
|
||||||
|
<div class="hero-selection-title-bar">
|
||||||
|
<h3 class="hero-selection-hero-name">{{currentSelectingHero.name}}</h3>
|
||||||
|
<span class="hero-selection-class">{{HeroClass[selectedHeroClass]}}</span>
|
||||||
|
<span class="hero-selection-counter">({{currentHeroIndex + 1}} / {{heros.length}})</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="hero-selection-content-area">
|
||||||
|
<div class="row no-gutters h-100">
|
||||||
|
<div class="col-6 hero-select-image-col">
|
||||||
|
<div class="hero-select-image-wrapper"
|
||||||
|
[style.background-image]="'url(' + imgUrl('/Mobs/BG.png') + ')'">
|
||||||
|
<img src="{{imgUrl('Heros/'+className+'.png')}}" class="hero-select-image"
|
||||||
|
alt="{{currentSelectingHero.name}}">
|
||||||
|
<!-- HP and Mana Bars -->
|
||||||
|
<div class="hero-select-stats-overlay">
|
||||||
|
<div class="stat-bar-overlay hp-bar-overlay">
|
||||||
|
<div class="stat-bar-label-overlay">
|
||||||
|
<md2-icon [icon]="MD2Icon.HP_Color" size="sm"></md2-icon>
|
||||||
|
<span class="stat-value-overlay">{{currentSelectingHero.hpMaximum}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-progress-bar-overlay">
|
||||||
|
<div class="stat-progress-fill-overlay hp-fill-overlay full-stat"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-bar-overlay mp-bar-overlay">
|
||||||
|
<div class="stat-bar-label-overlay">
|
||||||
|
<md2-icon [icon]="MD2Icon.Mana_Color" size="sm"></md2-icon>
|
||||||
|
<span class="stat-value-overlay">{{currentSelectingHero.mpMaximum}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-progress-bar-overlay">
|
||||||
|
<div class="stat-progress-fill-overlay mp-fill-overlay full-stat"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-6 hero-select-skills-col">
|
||||||
|
<div class="hero-select-skills">
|
||||||
|
<div class="skills-title">Abilities</div>
|
||||||
|
<div class="skill-content" [innerHTML]="currentSelectingHero.skillHtml"></div>
|
||||||
|
<div class="skills-title shadow-skills-title">Shadow Abilities</div>
|
||||||
|
<div class="skill-content shadow-skill-content"
|
||||||
|
[innerHTML]="currentSelectingHero.shadowSkillHtml"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="hero-selection-actions">
|
||||||
|
<button nbButton hero status="basic" class="nav-hero-btn" (click)="previousHero()">
|
||||||
|
<nb-icon icon="chevron-back-outline" class="mr-1"></nb-icon>
|
||||||
|
Previous
|
||||||
|
</button>
|
||||||
|
<button nbButton hero status="primary" class="select-hero-btn" (click)="selectCurrentHero()">
|
||||||
|
<nb-icon icon="checkmark-circle-outline" class="mr-2"></nb-icon>
|
||||||
|
It's Me!
|
||||||
|
</button>
|
||||||
|
<button nbButton hero status="basic" class="nav-hero-btn" (click)="nextHero()">
|
||||||
|
Next
|
||||||
|
<nb-icon icon="chevron-forward-outline" class="ml-1"></nb-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div *ngIf="hero">
|
<div *ngIf="hero">
|
||||||
<div class="row no-gutters">
|
<div class="row no-gutters">
|
||||||
<div class="col-12 col-sm-7">
|
<div class="col-12 col-sm-7">
|
||||||
<div class="tp-wrapper mb-2">
|
<div class="tp-wrapper mb-2">
|
||||||
<div class="tp-box g-height-300 g-height-350--sm g-height-500--md" (click)="toggleFlip()"
|
<div class="tp-box" [@flipState]="flip">
|
||||||
[@flipState]="flip">
|
|
||||||
<div class="tp-box__side tp-box__front ">
|
<div class="tp-box__side tp-box__front ">
|
||||||
|
|
||||||
<img class="MD2HeroCard " src="{{hero.imgUrl}}">
|
<div class="hero-card-content">
|
||||||
|
<div class="row no-gutters h-100">
|
||||||
|
<div class="col-6 hero-image-col">
|
||||||
|
<div class="hero-image-wrapper"
|
||||||
|
[style.background-image]="'url(' + imgUrl('/Mobs/BG.png') + ')'">
|
||||||
|
<img src="{{imgUrl('Heros/'+className+'.png')}}" class="hero-image"
|
||||||
|
(click)="toggleFlip()" alt="{{hero.name}}">
|
||||||
|
<!-- HP and Mana Bars -->
|
||||||
|
<div class="hero-stats-overlay">
|
||||||
|
<div class="stat-bar-overlay hp-bar-overlay">
|
||||||
|
<div class="stat-bar-label-overlay">
|
||||||
|
<md2-icon [icon]="MD2Icon.HP_Color" size="sm"></md2-icon>
|
||||||
|
<span
|
||||||
|
class="stat-value-overlay">{{hero.hp}}/{{hero.hpMaximum}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-progress-bar-overlay">
|
||||||
|
<div class="stat-progress-fill-overlay hp-fill-overlay"
|
||||||
|
[style.width.%]="(hero.hp / hero.hpMaximum) * 100"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-bar-overlay mp-bar-overlay">
|
||||||
|
<div class="stat-bar-label-overlay">
|
||||||
|
<md2-icon [icon]="MD2Icon.Mana_Color" size="sm"></md2-icon>
|
||||||
|
<span
|
||||||
|
class="stat-value-overlay">{{hero.mp}}/{{hero.mpMaximum}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-progress-bar-overlay">
|
||||||
|
<div class="stat-progress-fill-overlay mp-fill-overlay"
|
||||||
|
[style.width.%]="(hero.mp / hero.mpMaximum) * 100"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-6 hero-skills-col">
|
||||||
|
<div class="hero-skills">
|
||||||
|
<div class="skills-title" (click)="showSkills('abilities')">Abilities</div>
|
||||||
|
<div class="skill-content" [innerHTML]="hero.skillHtml"></div>
|
||||||
|
<div class="skills-title shadow-skills-title" (click)="showSkills('shadow')">
|
||||||
|
Shadow Abilities</div>
|
||||||
|
<div class="skill-content shadow-skill-content"
|
||||||
|
[innerHTML]="hero.shadowSkillHtml"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <img class="MD2HeroCard " src="{{imgUrl('Heros/'+className+'.jpg')}}" (click)="toggleFlip()"> -->
|
||||||
|
|
||||||
|
<!-- Action Buttons (Desktop/Landscape) -->
|
||||||
|
<div class="hero-actions d-block">
|
||||||
|
<div class="action-buttons-group" *ngIf="hero.uiActivating && hero.remainActions > 0">
|
||||||
|
<button nbButton hero class="action-btn" status="info" (click)="moveAction()"
|
||||||
|
*ngIf="!showMoveAction">
|
||||||
|
<nb-icon icon="arrow-forward-outline" class="mr-1"></nb-icon>
|
||||||
|
Move
|
||||||
|
</button>
|
||||||
|
<button nbButton hero class="action-btn" status="info" (click)="moveActionEnd()"
|
||||||
|
*ngIf="showMoveAction">
|
||||||
|
<nb-icon icon="checkmark-outline" class="mr-1"></nb-icon>
|
||||||
|
End Move
|
||||||
|
</button>
|
||||||
|
<button nbButton hero class="action-btn" status="danger"
|
||||||
|
(click)="action('attackAction')" *ngIf="!showMoveAction && allowAttack">
|
||||||
|
<nb-icon icon="flash-outline" class="mr-1"></nb-icon>
|
||||||
|
Attack!
|
||||||
|
</button>
|
||||||
|
<button nbButton hero class="action-btn" status="info" (click)="action('tradeAction')"
|
||||||
|
*ngIf="!showMoveAction">
|
||||||
|
<nb-icon icon="swap-outline" class="mr-1"></nb-icon>
|
||||||
|
Trade
|
||||||
|
</button>
|
||||||
|
<button nbButton hero class="action-btn" status="success"
|
||||||
|
(click)="action('recoveryAction')" *ngIf="!showMoveAction">
|
||||||
|
<nb-icon icon="heart-outline" class="mr-1"></nb-icon>
|
||||||
|
Recovery
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button nbButton hero class="action-btn special-action-btn" status="info" fullWidth
|
||||||
|
(click)="openDoor()" *ngIf="showMoveAction">
|
||||||
|
<nb-icon icon="grid-outline" class="mr-1"></nb-icon>
|
||||||
|
Open Door
|
||||||
|
</button>
|
||||||
|
<button nbButton hero fullWidth status="info" class="start-activation-btn"
|
||||||
|
*ngIf="allowStartAction" (click)="startActivation()">
|
||||||
|
<nb-icon icon="play-circle-outline" class="mr-2"></nb-icon>
|
||||||
|
Start Activation
|
||||||
|
</button>
|
||||||
|
<button nbButton hero fullWidth status="warning" class="end-activation-btn"
|
||||||
|
*ngIf="hero.uiActivating" (click)="endActivation()">
|
||||||
|
<nb-icon icon="stop-circle-outline" class="mr-2"></nb-icon>
|
||||||
|
End Activation
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tp-box__side tp-box__back">
|
<div class="tp-box__side tp-box__back">
|
||||||
|
<div class="row no-gutters">
|
||||||
|
<div class="col-6">
|
||||||
|
<img class="MD2HeroCard " src="{{imgUrl('Heros/Guide/'+className+'.jpg')}}"
|
||||||
|
(click)="toggleFlip()">
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<img class="MD2HeroCard " src="{{imgUrl('Sets/Shadowbane/'+className+'.png')}}"
|
||||||
|
(click)="toggleFlip()">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<img class="MD2HeroCard " src="{{imgUrl('Heros/Guide/'+className+'.jpg')}}">
|
|
||||||
|
|
||||||
<img class="MD2HeroCard " src="{{imgUrl('Sets/Shadowbane/'+className+'.png')}}">
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -33,6 +212,7 @@
|
|||||||
<img class="MD2HeroCard" src="{{hero.imgUrl}}">
|
<img class="MD2HeroCard" src="{{hero.imgUrl}}">
|
||||||
<img class="MD2HeroCard HpMpBar" src="{{imgUrl('/Heros/Template/Border.png')}}">
|
<img class="MD2HeroCard HpMpBar" src="{{imgUrl('/Heros/Template/Border.png')}}">
|
||||||
</div> -->
|
</div> -->
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 col-sm-5">
|
<div class="col-12 col-sm-5">
|
||||||
@@ -41,78 +221,79 @@
|
|||||||
<div class="row no-gutters">
|
<div class="row no-gutters">
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
|
|
||||||
<!-- <adj-number-input name="heroHP" [(ngModel)]="hero.hp"
|
|
||||||
[maximum]="hero.hpMaximum" minimum="0"
|
|
||||||
title="{{iconHtml(MD2Icon.HP,'g-color-google-plus mr-1 g-font-size-18')}}HP" showMaximum
|
|
||||||
(blur)="heroUpdateDebounceTimer.resetTimer()" (hitDecreasing)="increaseRage()">
|
|
||||||
</adj-number-input> -->
|
|
||||||
|
|
||||||
|
|
||||||
<adj-number-input name="heroHP" [(ngModel)]="hero.hp" [maximum]="hero.hpMaximum" minimum="0"
|
<adj-number-input name="heroHP" [(ngModel)]="hero.hp" [maximum]="hero.hpMaximum" minimum="0"
|
||||||
title="{{imgHtml('HpIcon.png','g-height-25 mr-1')}}HP" showMaximum
|
title="{{imgHtml('HpIcon.png','g-height-25')}}" showMaximum
|
||||||
(blur)="heroUpdateDebounceTimer.resetTimer()" (hitDecreasing)="increaseRage()">
|
(blur)="heroUpdateDebounceTimer.resetTimer()" (hitDecreasing)="increaseRage()">
|
||||||
</adj-number-input>
|
</adj-number-input>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
|
||||||
|
|
||||||
|
<adj-number-input name="heroLevel" [(ngModel)]="hero.level" minimum="1" maximum="5"
|
||||||
|
title="Level" (blur)="heroUpdateDebounceTimer.resetTimer()">
|
||||||
|
</adj-number-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-6">
|
||||||
|
|
||||||
<adj-number-input name="heroMana" [(ngModel)]="hero.mp" [maximum]="hero.mpMaximum"
|
<adj-number-input name="heroMana" [(ngModel)]="hero.mp" [maximum]="hero.mpMaximum"
|
||||||
minimum="0" title="{{imgHtml('HeroIcon.png','g-height-25 mr-1')}}Mana" showMaximum
|
minimum="0" title="{{imgHtml('HeroIcon.png','g-height-25')}}" showMaximum
|
||||||
(blur)="heroUpdateDebounceTimer.resetTimer()">
|
(blur)="heroUpdateDebounceTimer.resetTimer()">
|
||||||
</adj-number-input>
|
</adj-number-input>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
|
||||||
|
<adj-number-input name="heroExp" [(ngModel)]="hero.exp" minimum="0" title="Exp"
|
||||||
|
(blur)="heroUpdateDebounceTimer.resetTimer()">
|
||||||
|
</adj-number-input>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
|
||||||
<adj-number-input name="heroFire" [(ngModel)]="hero.fireToken" minimum="0"
|
<adj-number-input name="heroFire" [(ngModel)]="hero.fireToken" minimum="0"
|
||||||
title="{{iconHtml(MD2Icon.Fire,'g-color-google-plus mr-1 g-font-size-18')}}Fire Token"
|
title="{{iconHtml(MD2Icon.Fire,'g-color-google-plus mr-1 g-font-size-18')}}Fire Token"
|
||||||
(blur)="heroUpdateDebounceTimer.resetTimer()">
|
(blur)="heroUpdateDebounceTimer.resetTimer()">
|
||||||
</adj-number-input>
|
</adj-number-input>
|
||||||
<adj-number-input name="heroFire" [(ngModel)]="hero.frozenToken" minimum="0"
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
|
||||||
|
<adj-number-input name="heroFrozen" [(ngModel)]="hero.frozenToken" minimum="0"
|
||||||
title="{{iconHtml(MD2Icon.Frost,'g-color-aqua mr-1 g-font-size-18')}}Frozen Token"
|
title="{{iconHtml(MD2Icon.Frost,'g-color-aqua mr-1 g-font-size-18')}}Frozen Token"
|
||||||
(blur)="heroUpdateDebounceTimer.resetTimer()">
|
(blur)="heroUpdateDebounceTimer.resetTimer()">
|
||||||
</adj-number-input>
|
</adj-number-input>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
|
|
||||||
<adj-number-input name="remainActions" [(ngModel)]="hero.remainActions" minimum="0"
|
<adj-number-input name="remainActions" [(ngModel)]="hero.remainActions" minimum="0"
|
||||||
title="Remain Actions" (blur)="heroUpdateDebounceTimer.resetTimer()" hideIncreaseBtn
|
title="Remain Actions" (blur)="heroUpdateDebounceTimer.resetTimer()" hideIncreaseBtn
|
||||||
*ngIf="hero.uiActivating">
|
*ngIf="hero.uiActivating">
|
||||||
</adj-number-input>
|
</adj-number-input>
|
||||||
<adj-number-input name="heroLevel" [(ngModel)]="hero.level" minimum="1" maximum="5"
|
</div>
|
||||||
title="Level" (blur)="heroUpdateDebounceTimer.resetTimer()">
|
<div class="col-6" *ngIf="hero.class==HeroClass.Berserker">
|
||||||
</adj-number-input>
|
|
||||||
<adj-number-input name="heroExp" [(ngModel)]="hero.exp" minimum="0" title="Exp"
|
|
||||||
(blur)="heroUpdateDebounceTimer.resetTimer()">
|
|
||||||
</adj-number-input>
|
|
||||||
<adj-number-input name="heroRage" [(ngModel)]="hero.rage" minimum="0" maximum="7"
|
<adj-number-input name="heroRage" [(ngModel)]="hero.rage" minimum="0" maximum="7"
|
||||||
title="{{iconHtml(MD2Icon.Rage,'g-color-google-plus mr-1 g-font-size-18')}}Rage"
|
title="{{iconHtml(MD2Icon.Rage,'g-color-google-plus mr-1 g-font-size-18')}}Rage"
|
||||||
(blur)="heroUpdateDebounceTimer.resetTimer()" *ngIf="hero.class==HeroClass.Berserker">
|
(blur)="heroUpdateDebounceTimer.resetTimer()">
|
||||||
</adj-number-input>
|
|
||||||
<adj-number-input name="heroCorruption" [(ngModel)]="hero.corruptionToken" minimum="0"
|
|
||||||
title="{{imgHtml('Tokens/CorruptToken.png','g-height-18')}} Corruption"
|
|
||||||
(blur)="heroUpdateDebounceTimer.resetTimer()" *ngIf="hero.uiShowCorruptionToken">
|
|
||||||
</adj-number-input>
|
</adj-number-input>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-6" *ngIf="hero.uiShowExtraToken">
|
||||||
|
<adj-number-input name="heroExtraToken" [(ngModel)]="hero.extraToken" minimum="0"
|
||||||
|
title="{{hero.uiExtraTokenHtml}} {{hero.uiExtraTokenName}}"
|
||||||
|
(blur)="heroUpdateDebounceTimer.resetTimer()">
|
||||||
|
</adj-number-input>
|
||||||
|
</div>
|
||||||
|
<div class="col-6" *ngIf="hero.uiShowExtraToken2">
|
||||||
|
<adj-number-input name="heroExtraToken2" [(ngModel)]="hero.extraToken2" minimum="0"
|
||||||
|
title="{{hero.uiExtraTokenHtml2}} {{hero.uiExtraTokenName2}}"
|
||||||
|
(blur)="heroUpdateDebounceTimer.resetTimer()">
|
||||||
|
</adj-number-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="md2Service.info.isBossFight"></div>
|
<div *ngIf="md2Service.info.isBossFight"></div>
|
||||||
<div *ngIf="hero.uiActivating&&hero.remainActions>0">
|
|
||||||
<button nbButton hero class="mr-2" status="info" (click)="moveAction()"
|
|
||||||
*ngIf="!showMoveAction">Move</button>
|
|
||||||
<button nbButton hero class="mr-2" status="info" (click)="moveActionEnd()"
|
|
||||||
*ngIf="showMoveAction">Move End</button>
|
|
||||||
<button nbButton hero class="mr-2" status="danger" (click)="action('attackAction')"
|
|
||||||
*ngIf="!showMoveAction&&allowAttack">Attack!</button>
|
|
||||||
<button nbButton hero class="mr-2" status="info" (click)="action('tradeAction')"
|
|
||||||
*ngIf="!showMoveAction">Trade</button>
|
|
||||||
<button nbButton hero status="success" (click)="action('recoveryAction')"
|
|
||||||
*ngIf="!showMoveAction">Recovery</button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button nbButton hero fullWidth status="info" *ngIf="allowStartAction"
|
|
||||||
(click)="startActivation()">Start Activation</button>
|
|
||||||
|
|
||||||
<button nbButton hero status="info" class="mt-2" (click)="openDoor()" *ngIf="showMoveAction">Open
|
|
||||||
Door</button>
|
|
||||||
<button nbButton hero fullWidth status="warning" class="mt-3" *ngIf="hero.uiActivating"
|
|
||||||
(click)="endActivation()">End
|
|
||||||
Activation</button>
|
|
||||||
|
|
||||||
</nb-card-body>
|
</nb-card-body>
|
||||||
</nb-card>
|
</nb-card>
|
||||||
|
|||||||
@@ -1,3 +1,367 @@
|
|||||||
|
// Hero Selection Screen
|
||||||
|
.hero-selection-card {
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
border: none;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-selection-body {
|
||||||
|
padding: 3rem 2rem;
|
||||||
|
text-align: center;
|
||||||
|
background: linear-gradient(135deg, rgba(102, 126, 234, 0.9) 0%, rgba(118, 75, 162, 0.9) 100%);
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
padding: 2rem 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-selection-content {
|
||||||
|
max-width: 500px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-selection-title {
|
||||||
|
color: white;
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-selection-subtitle {
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-selection-btn {
|
||||||
|
padding: 0.75rem 2rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: 50px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||||
|
transition: all 0.3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
padding: 0.6rem 1.5rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hero Selection Panel
|
||||||
|
.hero-selection-panel {
|
||||||
|
height: 85vh;
|
||||||
|
max-height: 85vh;
|
||||||
|
padding: 0.5rem;
|
||||||
|
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
@media (orientation: landscape) {
|
||||||
|
height: 85vh;
|
||||||
|
max-height: 85vh;
|
||||||
|
padding: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (orientation: portrait) {
|
||||||
|
height: 85vh;
|
||||||
|
max-height: 85vh;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (orientation: portrait) and (max-height: 667px) {
|
||||||
|
height: 85vh;
|
||||||
|
max-height: 85vh;
|
||||||
|
padding: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
height: 85vh;
|
||||||
|
max-height: 85vh;
|
||||||
|
padding: 0.15rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-selection-row {
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-selection-left {
|
||||||
|
padding: 0;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-selection-card-wrapper {
|
||||||
|
height: 100%;
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-selection-header {
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
border-bottom: 2px solid rgba(255, 255, 255, 0.2);
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-selection-title-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (orientation: portrait) and (max-width: 767px) {
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-selection-hero-name {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 0;
|
||||||
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (orientation: portrait) and (max-width: 767px) {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-selection-class {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
opacity: 0.9;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (orientation: portrait) and (max-width: 767px) {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-selection-counter {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
opacity: 0.8;
|
||||||
|
margin-left: auto;
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
font-size: 0.65rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (orientation: portrait) and (max-width: 767px) {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-selection-content-area {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0.75rem;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
padding: 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (orientation: portrait) and (max-width: 767px) {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-select-image-col,
|
||||||
|
.hero-select-skills-col {
|
||||||
|
padding: 0.4rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
padding: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (orientation: portrait) and (max-width: 767px) {
|
||||||
|
padding: 0.3rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-select-image-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-select-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-select-stats-overlay {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
background: linear-gradient(to top, rgba(0, 0, 0, 0.7) 0%, rgba(0, 0, 0, 0.5) 70%, transparent 100%);
|
||||||
|
border-radius: 0 0 8px 8px;
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
padding: 0.35rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-select-skills {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 0.75rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
padding: 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (orientation: portrait) and (max-width: 767px) {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-selection-actions {
|
||||||
|
padding: 0.75rem;
|
||||||
|
border-top: 2px solid #e9ecef;
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
padding: 0.5rem;
|
||||||
|
gap: 0.35rem;
|
||||||
|
border-top-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (orientation: portrait) and (max-width: 767px) {
|
||||||
|
padding: 0.5rem;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-hero-btn {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 40px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
transition: all 0.3s;
|
||||||
|
padding: 0.5rem;
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
min-height: 32px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
padding: 0.35rem 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (orientation: portrait) and (max-width: 767px) {
|
||||||
|
min-height: 38px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-hero-btn {
|
||||||
|
flex: 2;
|
||||||
|
min-height: 40px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
||||||
|
transition: all 0.3s;
|
||||||
|
padding: 0.5rem;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 16px rgba(102, 126, 234, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
min-height: 32px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
padding: 0.35rem 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (orientation: portrait) and (max-width: 767px) {
|
||||||
|
min-height: 38px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.MD2Hp {
|
.MD2Hp {
|
||||||
font-size: xx-large;
|
font-size: xx-large;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -36,45 +400,416 @@
|
|||||||
.tp-wrapper {
|
.tp-wrapper {
|
||||||
-webkit-perspective: 800px;
|
-webkit-perspective: 800px;
|
||||||
perspective: 800px;
|
perspective: 800px;
|
||||||
|
height: 40vh; // Default for portrait
|
||||||
|
|
||||||
|
@media (orientation: landscape) {
|
||||||
|
height: 85vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (orientation: portrait) and (max-height: 667px) {
|
||||||
|
height: 50vh;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tp-box {
|
.tp-box {
|
||||||
position: relative;
|
position: relative;
|
||||||
//width: 200px;
|
width: 100%;
|
||||||
//height: 100px;
|
height: 100%;
|
||||||
//margin: 3rem auto;
|
|
||||||
-webkit-transform-style: preserve-3d;
|
-webkit-transform-style: preserve-3d;
|
||||||
transform-style: preserve-3d;
|
transform-style: preserve-3d;
|
||||||
-webkit-transform: transform 1s;
|
-webkit-transform: transform 1s;
|
||||||
-ms-transform: transform 1s;
|
-ms-transform: transform 1s;
|
||||||
transform: transform 1s;
|
transform: transform 1s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tp-box__side {
|
.tp-box__side {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
||||||
-webkit-backface-visibility: hidden;
|
-webkit-backface-visibility: hidden;
|
||||||
backface-visibility: hidden;
|
backface-visibility: hidden;
|
||||||
|
|
||||||
color: #fff;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 100px;
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: 700;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
-moz-user-select: none;
|
-moz-user-select: none;
|
||||||
-ms-user-select: none;
|
-ms-user-select: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hero Card Content Section
|
||||||
|
.hero-card-content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0.5rem;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
padding: 0.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-image-col {
|
||||||
|
padding: 0.25rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
padding: 0.15rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-image-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
display: block;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.02);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HP and Mana Bars Overlay
|
||||||
|
.hero-stats-overlay {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
background: linear-gradient(to top, rgba(0, 0, 0, 0.7) 0%, rgba(0, 0, 0, 0.5) 70%, transparent 100%);
|
||||||
|
border-radius: 0 0 8px 8px;
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
padding: 0.35rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-bar-overlay {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
margin-bottom: 0.35rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-bar-label-overlay {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
|
md2-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value-overlay {
|
||||||
|
color: white;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
margin-bottom: 0.15rem;
|
||||||
|
gap: 0.3rem;
|
||||||
|
|
||||||
|
md2-icon {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value-overlay {
|
||||||
|
font-size: 0.65rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-progress-bar-overlay {
|
||||||
|
width: 100%;
|
||||||
|
height: 20px;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-progress-fill-overlay {
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 10px;
|
||||||
|
transition: width 0.5s ease-out;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
|
||||||
|
animation: shimmer 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.full-stat {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hp-fill-overlay {
|
||||||
|
background: linear-gradient(90deg, #ff6b6b, #ee5a6f);
|
||||||
|
box-shadow: 0 0 8px rgba(238, 90, 111, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mp-fill-overlay {
|
||||||
|
background: linear-gradient(90deg, #4ecdc4, #44a08d);
|
||||||
|
box-shadow: 0 0 8px rgba(68, 160, 141, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shimmer {
|
||||||
|
0% {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-skills-col {
|
||||||
|
padding: 0.25rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
padding: 0.15rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-skills {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 0.75rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.skills-title {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #667eea;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
padding-bottom: 0.25rem;
|
||||||
|
border-bottom: 2px solid #667eea;
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
margin-bottom: 0.3rem;
|
||||||
|
padding-bottom: 0.15rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.shadow-skills-title {
|
||||||
|
margin-top: 1rem;
|
||||||
|
color: #764ba2;
|
||||||
|
border-bottom-color: #764ba2;
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-content {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #495057;
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.shadow-skill-content {
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Action Buttons Section
|
||||||
|
.hero-actions {
|
||||||
|
padding: 0.75rem;
|
||||||
|
border-top: 2px solid #e9ecef;
|
||||||
|
margin-top: auto;
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-top-width: 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons-group {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
gap: 0.25rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 100px;
|
||||||
|
min-height: 40px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
transition: all 0.3s;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
min-width: 70px;
|
||||||
|
min-height: 36px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
padding: 0.35rem 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.special-action-btn {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
min-height: 40px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
transition: all 0.3s;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
margin-bottom: 0.35rem;
|
||||||
|
min-height: 32px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
padding: 0.35rem 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.start-activation-btn {
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 0.75rem;
|
||||||
|
min-height: 44px;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
transition: all 0.3s;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
padding: 0.5rem;
|
||||||
|
min-height: 36px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.end-activation-btn {
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 0.75rem;
|
||||||
|
min-height: 44px;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
transition: all 0.3s;
|
||||||
|
box-shadow: 0 2px 8px rgba(255, 193, 7, 0.3);
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
background: linear-gradient(135deg, #ffc107 0%, #ff9800 100%);
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(255, 193, 7, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
padding: 0.5rem;
|
||||||
|
min-height: 36px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
margin-top: 0.35rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.tp-box__front {
|
.tp-box__front {
|
||||||
-webkit-transform: rotateY(0deg);
|
-webkit-transform: rotateY(0deg);
|
||||||
-ms-transform: rotateY(0deg);
|
-ms-transform: rotateY(0deg);
|
||||||
transform: rotateY(0deg);
|
transform: rotateY(0deg);
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
||||||
}
|
}
|
||||||
.tp-box__back {
|
.tp-box__back {
|
||||||
-webkit-transform: rotateY(-180deg);
|
-webkit-transform: rotateY(-180deg);
|
||||||
-ms-transform: rotateY(-180deg);
|
-ms-transform: rotateY(-180deg);
|
||||||
transform: rotateY(-180deg);
|
transform: rotateY(-180deg);
|
||||||
}
|
}
|
||||||
|
::ng-deep .skill-content .MD2Icon {
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,8 +11,11 @@ import { ADButtonColor, ADButtons } from '../../../ui/alert-dlg/alert-dlg.model'
|
|||||||
import { ArrayUtils } from '../../../utilities/array-utils';
|
import { ArrayUtils } from '../../../utilities/array-utils';
|
||||||
import { StringUtils } from '../../../utilities/string-utils';
|
import { StringUtils } from '../../../utilities/string-utils';
|
||||||
import { DebounceTimer } from '../../../utilities/timer-utils';
|
import { DebounceTimer } from '../../../utilities/timer-utils';
|
||||||
import { HeroClass, MD2HeroInfo } from '../massive-darkness2.model';
|
import { HeroClass, MD2HeroInfo, MD2HeroProfile, MD2Icon } from '../massive-darkness2.model';
|
||||||
import { MD2Base } from '../MD2Base';
|
import { MD2Base } from '../MD2Base';
|
||||||
|
import { MD2HeroProfileService } from '../service/massive-darkness2.service';
|
||||||
|
import { SignalRService } from '../../../services/signal-r.service';
|
||||||
|
import { NbToastrService } from '@nebular/theme';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ngx-hero-dashboard',
|
selector: 'ngx-hero-dashboard',
|
||||||
@@ -33,6 +36,7 @@ import { MD2Base } from '../MD2Base';
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class HeroDashboardComponent extends MD2Base implements OnInit {
|
export class HeroDashboardComponent extends MD2Base implements OnInit {
|
||||||
|
MD2Icon = MD2Icon;
|
||||||
heroAction(hero: MD2HeroInfo, action: string) {
|
heroAction(hero: MD2HeroInfo, action: string) {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
@@ -43,7 +47,9 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
heroUpdateDebounceTimer = new DebounceTimer(1000, () => { this.broadcastHeroInfo(); })
|
heroUpdateDebounceTimer = new DebounceTimer(1000, () => {
|
||||||
|
this.broadcastHeroInfo();
|
||||||
|
})
|
||||||
|
|
||||||
classOptions: DropDownOption[] = [
|
classOptions: DropDownOption[] = [
|
||||||
new DropDownOption(HeroClass.Berserker, 'Berserker'),
|
new DropDownOption(HeroClass.Berserker, 'Berserker'),
|
||||||
@@ -53,36 +59,65 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
|
|||||||
new DropDownOption(HeroClass.Wizard, 'Wizard'),
|
new DropDownOption(HeroClass.Wizard, 'Wizard'),
|
||||||
new DropDownOption(HeroClass.Shaman, 'Shaman'),
|
new DropDownOption(HeroClass.Shaman, 'Shaman'),
|
||||||
new DropDownOption(HeroClass.Druid, 'Druid'),
|
new DropDownOption(HeroClass.Druid, 'Druid'),
|
||||||
|
new DropDownOption(HeroClass.Necromancer, 'Necromancer'),
|
||||||
|
new DropDownOption(HeroClass.Monk, 'Monk'),
|
||||||
|
new DropDownOption(HeroClass.Thinker, 'Thinker'),
|
||||||
|
new DropDownOption(HeroClass.Bard, 'Bard'),
|
||||||
];
|
];
|
||||||
heros = [] as MD2HeroInfo[];
|
heros = [] as MD2HeroInfo[];
|
||||||
wizards: MD2HeroInfo[] = [
|
heroProfiles: MD2HeroProfile[] = [];
|
||||||
new MD2HeroInfo({ name: 'Ajax', mpMaximum: 6, hpMaximum: 4, skillHtml: '', shadowSkillHtml: '' }),
|
currentHeroIndex: number = 0;
|
||||||
new MD2HeroInfo({ name: 'Baldric', mpMaximum: 5, hpMaximum: 4, skillHtml: '', shadowSkillHtml: '' }),
|
isSelectingHero: boolean = false;
|
||||||
new MD2HeroInfo({ name: 'Ego', mpMaximum: 5, hpMaximum: 6, skillHtml: '', shadowSkillHtml: '' }),
|
selectedHeroClass: HeroClass;
|
||||||
new MD2HeroInfo({ name: 'Elias', mpMaximum: 6, hpMaximum: 5, skillHtml: '', shadowSkillHtml: '' }),
|
|
||||||
new MD2HeroInfo({ name: 'Megan', mpMaximum: 5, hpMaximum: 5, skillHtml: '', shadowSkillHtml: '' }),
|
public get hero() {
|
||||||
new MD2HeroInfo({ name: 'Moira', mpMaximum: 6, hpMaximum: 5, skillHtml: '', shadowSkillHtml: '' }),
|
return this.md2Service.playerHero;
|
||||||
new MD2HeroInfo({ name: 'Myriam', mpMaximum: 7, hpMaximum: 4, skillHtml: '', shadowSkillHtml: '' }),
|
}
|
||||||
new MD2HeroInfo({ name: 'Valdis', mpMaximum: 6, hpMaximum: 4, skillHtml: '', shadowSkillHtml: '' })
|
|
||||||
]
|
public get className() {
|
||||||
|
if (this.md2Service.playerHero) {
|
||||||
|
return HeroClass[this.md2Service.playerHero.class];
|
||||||
|
}
|
||||||
|
if (this.selectedHeroClass) {
|
||||||
|
return HeroClass[this.selectedHeroClass];
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
public get currentSelectingHero(): MD2HeroInfo {
|
||||||
|
return this.heros[this.currentHeroIndex];
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private gameRoomService: GameRoomService,
|
private gameRoomService: GameRoomService,
|
||||||
public md2Service: MD2Service,
|
public md2Service: MD2Service,
|
||||||
|
private heroProfileService: MD2HeroProfileService,
|
||||||
protected stateService: StateService,
|
protected stateService: StateService,
|
||||||
protected route: ActivatedRoute,
|
protected route: ActivatedRoute,
|
||||||
protected cdRef: ChangeDetectorRef,
|
protected cdRef: ChangeDetectorRef,
|
||||||
private msgBoxService: MsgBoxService,
|
private msgBoxService: MsgBoxService,
|
||||||
|
private signalRService: SignalRService,
|
||||||
|
private toastrService: NbToastrService
|
||||||
) {
|
) {
|
||||||
super(md2Service, stateService, route, cdRef);
|
super(md2Service, stateService, route, cdRef);
|
||||||
this.isHeroDashboard = true;
|
this.isHeroDashboard = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get allowAttack(): boolean {
|
public get allowAttack(): boolean {
|
||||||
return this.md2Service.playerHero.uiBossFight || (!!this.md2Service.mobs && this.md2Service.mobs.length > 0) || (!!this.md2Service.roamingMonsters && this.md2Service.roamingMonsters.length > 0);
|
return this.hero.uiBossFight || this.md2Service.mobs?.length > 0 || this.md2Service.roamingMonsters?.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
super.ngOnInit();
|
super.ngOnInit();
|
||||||
|
|
||||||
|
this.gameRoomService.gameRoomId = this.roomId;
|
||||||
|
this.gameRoomService.joinGameRoom(this.roomId);
|
||||||
|
//this.fetchGameInfo();
|
||||||
|
this.signalRService.signalRMessageConnSubject.subscribe(state => {
|
||||||
|
//fetchGameInfo is called in MD2Base.handleSignalRCallback sendJoinInfo message
|
||||||
|
// if (state.status == 'connected') {
|
||||||
|
// this.fetchGameInfo();
|
||||||
|
// }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
override signalRInitialized() {
|
override signalRInitialized() {
|
||||||
@@ -93,9 +128,8 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
initHero() {
|
initHero() {
|
||||||
this.gameRoomService.gameRoomId = this.roomId;
|
if (!this.md2Service.heros.some(h => h.playerInfo.signalRClientId == this.stateService.loginUserService.userAccess.signalRConnectionId)) {
|
||||||
this.gameRoomService.joinGameRoom(this.roomId);
|
|
||||||
if (!this.md2Service.heros.some(h => h.playerInfo.signalRClientId == this.stateService.loginUserService.userAccess.signalRSessionId)) {
|
|
||||||
this.msgBoxService.showInputbox('Select Hero Class', '', { dropDownOptions: this.classOptions, inputType: 'dropdown' })
|
this.msgBoxService.showInputbox('Select Hero Class', '', { dropDownOptions: this.classOptions, inputType: 'dropdown' })
|
||||||
.pipe(first()).subscribe(heroClass => {
|
.pipe(first()).subscribe(heroClass => {
|
||||||
if (heroClass != null) {
|
if (heroClass != null) {
|
||||||
@@ -116,50 +150,75 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
className: string;
|
|
||||||
|
|
||||||
|
|
||||||
initClassHeroList(heroClass: HeroClass) {
|
initClassHeroList(heroClass: HeroClass) {
|
||||||
this.heros = [];
|
this.heros = [];
|
||||||
this.className = HeroClass[heroClass];
|
this.selectedHeroClass = heroClass;
|
||||||
this.fileList(`Heros/${this.className}`).pipe(first()).subscribe(fileNames => {
|
this.heroProfileService.getAll().pipe(first()).subscribe(result => {
|
||||||
for (let i = 0; i < fileNames.length; i++) {
|
|
||||||
const heroNames = fileNames[i].split('.')[0].split('-');
|
|
||||||
|
|
||||||
this.heros.push(new MD2HeroInfo({
|
this.heroProfiles = result.filter(h => h.heroClass == heroClass);
|
||||||
name: heroNames[0].replace('/', ''),
|
for (let i = 0; i < this.heroProfiles.length; i++) {
|
||||||
mpMaximum: Number.parseInt(heroNames[1]),
|
const heroProfile = this.heroProfiles[i];
|
||||||
hpMaximum: Number.parseInt(heroNames[2]),
|
const heroInfo = new MD2HeroInfo({
|
||||||
imgUrl: this.imgUrl(`Heros/${this.className}/${fileNames[i]}`),
|
name: heroProfile.title,
|
||||||
|
mpMaximum: heroProfile.mana,
|
||||||
|
hpMaximum: heroProfile.hp,
|
||||||
|
hp: heroProfile.hp,
|
||||||
|
mp: heroProfile.mana,
|
||||||
|
skillHtml: heroProfile.skillHtml,
|
||||||
|
shadowSkillHtml: heroProfile.shadowSkillHtml.replace("<p>", '<p>' + this.iconHtml(MD2Icon.Shadow) + ' : '),
|
||||||
class: heroClass
|
class: heroClass
|
||||||
}))
|
});
|
||||||
|
heroInfo.imgUrl = this.imgUrl('Heros/' + HeroClass[heroClass] + '.jpg');
|
||||||
|
this.heros.push(heroInfo);
|
||||||
}
|
}
|
||||||
this.heros = ArrayUtils.Shuffle(this.heros);//.sort((a, b) => StringUtils.compareSemVer(a.name, b.name));
|
this.heros = ArrayUtils.Shuffle(this.heros);//.sort((a, b) => StringUtils.compareSemVer(a.name, b.name));
|
||||||
this.showHeroList(heroClass, 0);
|
this.currentHeroIndex = 0;
|
||||||
});
|
this.isSelectingHero = true;
|
||||||
}
|
|
||||||
showHeroList(heroClass: HeroClass, index: number) {
|
|
||||||
let className = HeroClass[heroClass];
|
|
||||||
let heroInfo = this.heros[index];
|
|
||||||
this.msgBoxService.show(`${className}(${index + 1}/${this.heros.length})`, {
|
|
||||||
text: `<img src='${heroInfo.imgUrl}' class="g-width-50vw-md g-width-80vw">`,
|
|
||||||
buttons: ADButtons.YesNo,
|
|
||||||
cardWidthClass: '',
|
|
||||||
confirmButtonText: 'It\'s Me!',
|
|
||||||
cancelButtonText: 'Next',
|
|
||||||
cancelButtonColor: ADButtonColor.INFO
|
|
||||||
}).pipe(first()).subscribe(result => {
|
|
||||||
if (result) {
|
|
||||||
this.md2Service.playerJoin(heroInfo);
|
|
||||||
this.detectChanges();
|
this.detectChanges();
|
||||||
} else {
|
|
||||||
index++;
|
|
||||||
if (index == this.heros.length) index = 0;
|
|
||||||
this.showHeroList(heroClass, index);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
selectCurrentHero() {
|
||||||
|
if (this.currentSelectingHero) {
|
||||||
|
this.md2Service.playerJoin(this.currentSelectingHero);
|
||||||
|
this.md2Service.broadcastMyHeroInfo();
|
||||||
|
this.isSelectingHero = false;
|
||||||
|
this.detectChanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
showSkills(type: string) {
|
||||||
|
if (type == 'abilities') {
|
||||||
|
this.msgBoxService.show('Abilities', { text: this.currentSelectingHero.skillHtml });
|
||||||
|
} else {
|
||||||
|
this.msgBoxService.show('Shadow Abilities', { text: this.currentSelectingHero.shadowSkillHtml });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nextHero() {
|
||||||
|
this.currentHeroIndex++;
|
||||||
|
if (this.currentHeroIndex >= this.heros.length) {
|
||||||
|
this.currentHeroIndex = 0;
|
||||||
|
}
|
||||||
|
this.detectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
previousHero() {
|
||||||
|
this.currentHeroIndex--;
|
||||||
|
if (this.currentHeroIndex < 0) {
|
||||||
|
this.currentHeroIndex = this.heros.length - 1;
|
||||||
|
}
|
||||||
|
this.detectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchGameInfo() {
|
||||||
|
this.md2Service.broadcastFetchGameInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
broadcastHeroInfo() {
|
broadcastHeroInfo() {
|
||||||
this.md2Service.broadcastService.broadcastMyHeroInfo();
|
this.md2Service.broadcastMyHeroInfo();
|
||||||
this.heroUpdateDebounceTimer.clearOut();
|
this.heroUpdateDebounceTimer.clearOut();
|
||||||
}
|
}
|
||||||
increaseRage() {
|
increaseRage() {
|
||||||
@@ -168,8 +227,8 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
openDoor() {
|
openDoor() {
|
||||||
this.md2Service.broadcastService.broadcastHeroAction('openDoor');
|
this.md2Service.broadcastHeroAction('openDoor');
|
||||||
this.showMoveAction = false;
|
//this.showMoveAction = false;
|
||||||
this.detectChanges();
|
this.detectChanges();
|
||||||
}
|
}
|
||||||
moveAction() {
|
moveAction() {
|
||||||
@@ -193,7 +252,7 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this.md2Service.broadcastService.broadcastHeroAction(action);
|
this.md2Service.broadcastHeroAction(action);
|
||||||
this.reduceAction();
|
this.reduceAction();
|
||||||
}
|
}
|
||||||
reduceAction() {
|
reduceAction() {
|
||||||
@@ -210,12 +269,25 @@ export class HeroDashboardComponent extends MD2Base implements OnInit {
|
|||||||
get allowStartAction() {
|
get allowStartAction() {
|
||||||
return !this.md2Service.heros.some(h => h.uiActivating) && !this.hero.uiActivating && this.hero.remainActions > 0;
|
return !this.md2Service.heros.some(h => h.uiActivating) && !this.hero.uiActivating && this.hero.remainActions > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get hero() {
|
|
||||||
return this.md2Service.playerHero;
|
|
||||||
}
|
|
||||||
startActivation() {
|
startActivation() {
|
||||||
this.hero.uiActivating = true;
|
this.hero.uiActivating = true;
|
||||||
|
//this.hero.remainActions = 3;
|
||||||
|
if (this.hero.fireToken > 0) {
|
||||||
|
this.msgBoxService.show(`You Are On ${this.iconHtml(MD2Icon.Fire)}!`, {
|
||||||
|
text: `Roll ${this.iconHtml(MD2Icon.YellowDice)} ${this.hero.fireToken} times.`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.hero.frozenToken > 0) {
|
||||||
|
let loseActions = Math.min(this.hero.frozenToken, this.hero.remainActions);
|
||||||
|
this.hero.remainActions -= loseActions;
|
||||||
|
this.hero.frozenToken -= loseActions;
|
||||||
|
this.msgBoxService.show(`It's So Cold ${this.iconHtml(MD2Icon.Frost)}!`, {
|
||||||
|
text: `Lose ${loseActions} actions.`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.hero.remainActions == 0) {
|
||||||
|
this.hero.uiActivating = false;
|
||||||
|
}
|
||||||
this.broadcastHeroInfo();
|
this.broadcastHeroInfo();
|
||||||
}
|
}
|
||||||
endActivation() {
|
endActivation() {
|
||||||
|
|||||||
@@ -10,17 +10,24 @@
|
|||||||
</nb-accordion-item-body>
|
</nb-accordion-item-body>
|
||||||
</nb-accordion-item>
|
</nb-accordion-item>
|
||||||
</nb-accordion>
|
</nb-accordion>
|
||||||
</div> -->
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
<div class="col-12 col-md-5">
|
<div class="col-12 col-md-5">
|
||||||
<nb-card>
|
<nb-card>
|
||||||
<nb-card-header>
|
<nb-card-header>
|
||||||
<img src="{{imgUrl('HeroIcon.png')}}" width="40px"> Game Info
|
<img src="{{imgUrl('HeroIcon.png')}}" width="40px">
|
||||||
|
<span class="ml-2 g-font-size-17 MD2text" [innerHtml]="round"></span>
|
||||||
|
|
||||||
<button nbButton hero status="info" size="small" (click)="showQrCode()"
|
<button nbButton hero status="info" size="small" (click)="showQrCode()"
|
||||||
class="float-right">Invite</button>
|
class="float-right">Invite</button>
|
||||||
<button nbButton hero status="info" size="small" [disabled]="anyHeroRemainAction"
|
<button nbButton hero status="info" size="small" [disabled]="anyHeroRemainAction"
|
||||||
(click)="md2Service.runNextPhase()" class="float-right mr-2">Next Phase</button>
|
(click)="md2Service.runNextPhase()" class="float-right mr-2">Next Phase</button>
|
||||||
|
<button nbButton hero status="info" size="small" (click)="broadcastHeros()"
|
||||||
|
class="float-right mr-2">Broadcast</button>
|
||||||
</nb-card-header>
|
</nb-card-header>
|
||||||
<nb-card-body>
|
<nb-card-body>
|
||||||
|
|
||||||
@@ -41,8 +48,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row" *ngIf="md2Service.heros.length>0">
|
<div class="row" *ngIf="md2Service.heros.length>0">
|
||||||
<div class="col-12 g-font-size-17" [innerHtml]="roundPhase"></div>
|
<!-- <div class="col-12 g-font-size-17" [innerHtml]="roundPhase"></div> -->
|
||||||
|
|
||||||
<!-- <div class="col-6">
|
<!-- <div class="col-6">
|
||||||
<label for='playerAmount' class='label'>Hero Amount ({{md2Service.playerAmount}})</label>
|
<label for='playerAmount' class='label'>Hero Amount ({{md2Service.playerAmount}})</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -51,20 +57,44 @@
|
|||||||
({{md2Service.highestPlayerLevel}})</label>
|
({{md2Service.highestPlayerLevel}})</label>
|
||||||
</div> -->
|
</div> -->
|
||||||
<div class="col-12" *ngFor="let hero of md2Service.heros">
|
<div class="col-12" *ngFor="let hero of md2Service.heros">
|
||||||
<label class='label mr-1'>{{hero.playerInfo.name}} ({{heroClassName(hero)}} -
|
<label class='label mr-1'
|
||||||
|
(click)="adjustHeroValue(hero,'remainActions')">{{hero.playerInfo.name}}
|
||||||
|
({{heroClassName(hero)}} -
|
||||||
{{hero.name}})</label>
|
{{hero.name}})</label>
|
||||||
<span class="badge badge-primary mr-1">Lv.:{{hero.level}}</span>
|
<span class="badge badge-primary mr-1"
|
||||||
<span class="badge badge-primary mr-1">HP: {{hero.hp}}/{{hero.hpMaximum}}</span>
|
(click)="adjustHeroValue(hero,'level')">Lv.:{{hero.level}}</span>
|
||||||
<span class="badge badge-primary mr-1">Mana: {{hero.mp}}/{{hero.mpMaximum}}</span>
|
<span class="badge badge-primary mr-1" (click)="adjustHeroValue(hero,'hp')">HP:
|
||||||
<span class="badge badge-success mr-1">Exp: {{hero.exp}}</span>
|
{{hero.hp}}/{{hero.hpMaximum}}</span>
|
||||||
<span class="badge badge-danger mr-1" *ngIf="hero.fireToken">Fire:{{hero.fireToken}}</span>
|
<span class="badge badge-primary mr-1" (click)="adjustHeroValue(hero,'mp')">Mana:
|
||||||
<span class="badge badge-info mr-1" *ngIf="hero.frozenToken">Frozen:{{hero.frozenToken}}</span>
|
{{hero.mp}}/{{hero.mpMaximum}}</span>
|
||||||
<span class="badge badge-success mr-1" *ngIf="hero.remainActions>0">Remain
|
<span class="badge badge-success mr-1" (click)="adjustHeroValue(hero,'exp')">Exp:
|
||||||
Actions: {{hero.remainActions}}</span>
|
{{hero.exp}}</span>
|
||||||
<span class="badge badge-light mr-1" *ngIf=" !hero.uiActivating">Inactive</span>
|
<span class="badge mr-1" *ngIf="hero.fireToken">
|
||||||
|
<md2-icon [icon]="MD2Icon.FireToken" size="sm"></md2-icon> {{hero.fireToken}}
|
||||||
|
</span>
|
||||||
|
<span class="badge mr-1" *ngIf="hero.frozenToken">
|
||||||
|
<md2-icon [icon]="MD2Icon.FrozenToken" size="sm"></md2-icon>{{hero.frozenToken}}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="badge mr-1" *ngIf="hero.extraToken">
|
||||||
|
<span [innerHtml]="hero.uiExtraTokenHtml"></span> {{hero.extraToken}}
|
||||||
|
</span>
|
||||||
|
<span class="badge mr-1" *ngIf="hero.extraToken2">
|
||||||
|
<span [innerHtml]="hero.uiExtraTokenHtml2"></span> {{hero.extraToken2}}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="badge badge-success mr-1" *ngIf="hero.remainActions>0"
|
||||||
|
(click)="adjustHeroValue(hero,'remainActions')">Actions:
|
||||||
|
{{hero.remainActions}}</span>
|
||||||
|
<span class="badge badge-light mr-1" *ngIf=" !hero.uiActivating"
|
||||||
|
(click)="activatingHero(hero)">Inactive</span>
|
||||||
<span class="badge badge-primary mr-1" *ngIf="hero.uiActivating">Activating</span>
|
<span class="badge badge-primary mr-1" *ngIf="hero.uiActivating">Activating</span>
|
||||||
|
<span class="badge badge-warning mr-1"
|
||||||
|
*ngIf="hero.playerInfo.isDisconnected">Disconnected</span>
|
||||||
<!-- <span class="badge badge-success mr-1">{{hero.playerInfo.signalRClientId}}</span> -->
|
<!-- <span class="badge badge-success mr-1">{{hero.playerInfo.signalRClientId}}</span> -->
|
||||||
|
|
||||||
|
<span class="badge badge-danger mr-1" (click)="removeHero(hero)">X
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -77,10 +107,38 @@
|
|||||||
|
|
||||||
<div class="col-12 col-md-3">
|
<div class="col-12 col-md-3">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="form-group col-12">
|
<!-- <div class="form-group col-4">
|
||||||
|
|
||||||
<button nbButton hero fullWidth status="primary" (click)="enterBossFight()">Enter Boss Fight</button>
|
<button nbButton hero fullWidth status="primary" (click)="enterBossFight()">Enter Boss Fight</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group col-4">
|
||||||
|
|
||||||
|
<button nbButton hero fullWidth status="primary" (click)="enterBossFight()">Enter Boss Fight</button>
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-4">
|
||||||
|
openGreatTreasureChest
|
||||||
|
<button nbButton hero fullWidth status="success" (click)="accessHealFountain()">Access Heal Fountain</button>
|
||||||
|
</div> -->
|
||||||
|
<ng-container *ngIf="md2Service.currentActivateHero">
|
||||||
|
<div class="form-group col-12">
|
||||||
|
<button nbButton hero fullWidth status="info" (click)="md2Service.openTreasureChest()">Open
|
||||||
|
Treasure Chest</button>
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-12">
|
||||||
|
<button nbButton hero fullWidth status="primary" (click)="md2Service.openGreatTreasureChest()">Open
|
||||||
|
Great
|
||||||
|
Treasure Chest</button>
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-12">
|
||||||
|
<button nbButton hero fullWidth status="success" (click)="accessHealFountain()">Access Heal
|
||||||
|
Fountain</button>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<div class="form-group col-12">
|
||||||
|
|
||||||
|
<button nbButton hero fullWidth status="danger" (click)="enterBossFight()">Enter Boss Fight</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
.badge {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { MsgBoxService } from '../../services/msg-box.service';
|
|||||||
import { ArrayUtils } from '../../utilities/array-utils';
|
import { ArrayUtils } from '../../utilities/array-utils';
|
||||||
import { ObjectUtils } from '../../utilities/object-utils';
|
import { ObjectUtils } from '../../utilities/object-utils';
|
||||||
import { first, map, take, takeUntil } from 'rxjs/operators';
|
import { first, map, take, takeUntil } from 'rxjs/operators';
|
||||||
import { TreasureType, DrawingBag, DrawingItem, HeroClass, MD2HeroInfo, RoundPhase, MobInfo, MobDlgType } from './massive-darkness2.model';
|
import { TreasureType, DrawingBag, DrawingItem, HeroClass, MD2HeroInfo, RoundPhase, MobInfo, MobDlgType, MD2Icon } from './massive-darkness2.model';
|
||||||
import { MD2Service } from '../../services/MD2/md2.service';
|
import { MD2Service } from '../../services/MD2/md2.service';
|
||||||
import { GameRoomService } from '../../services/game-room.service';
|
import { GameRoomService } from '../../services/game-room.service';
|
||||||
import { MD2Base } from './MD2Base';
|
import { MD2Base } from './MD2Base';
|
||||||
@@ -16,6 +16,9 @@ import { StringUtils } from '../../utilities/string-utils';
|
|||||||
import { SpawnMobDlgComponent } from './mobs/spawn-mob-dlg/spawn-mob-dlg.component';
|
import { SpawnMobDlgComponent } from './mobs/spawn-mob-dlg/spawn-mob-dlg.component';
|
||||||
import { BossMicheal } from './massive-darkness2.model.boss';
|
import { BossMicheal } from './massive-darkness2.model.boss';
|
||||||
import { MD2InitService } from '../../services/MD2/md2-init.service';
|
import { MD2InitService } from '../../services/MD2/md2-init.service';
|
||||||
|
import { NumberUtils } from '../../utilities/number-utils';
|
||||||
|
import { GameInitDlgComponent, GameInitConfig } from './game-init-dlg/game-init-dlg.component';
|
||||||
|
import { GameBundle } from './massive-darkness2.db.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ngx-massive-darkness2',
|
selector: 'ngx-massive-darkness2',
|
||||||
@@ -24,7 +27,8 @@ import { MD2InitService } from '../../services/MD2/md2-init.service';
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
})
|
})
|
||||||
export class MassiveDarkness2Component extends MD2Base implements OnInit {
|
export class MassiveDarkness2Component extends MD2Base implements OnInit {
|
||||||
HeroClass: HeroClass
|
MD2Icon = MD2Icon;
|
||||||
|
HeroClass = HeroClass;
|
||||||
constructor(
|
constructor(
|
||||||
private fileService: FileService,
|
private fileService: FileService,
|
||||||
private initService: MD2InitService,
|
private initService: MD2InitService,
|
||||||
@@ -41,20 +45,100 @@ export class MassiveDarkness2Component extends MD2Base implements OnInit {
|
|||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
super.ngOnInit();
|
super.ngOnInit();
|
||||||
|
|
||||||
this.md2Service.enemyPhaseSubject.pipe(takeUntil(this.destroy$)).subscribe(result => {
|
this.md2Service.enemyPhaseSubject.pipe(takeUntil(this.destroy$)).subscribe(result => {
|
||||||
this.showEnemyPhaseAction(0);
|
this.showEnemyPhaseAction(0);
|
||||||
});
|
});
|
||||||
|
this.showGameInitDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
private showGameInitDialog() {
|
||||||
|
// Only show dialog if game hasn't been initialized yet
|
||||||
|
if (!this.md2Service.initialized) {
|
||||||
|
const dialogRef = this.msgBoxService.dlgService.open(GameInitDlgComponent, {
|
||||||
|
closeOnBackdropClick: false,
|
||||||
|
closeOnEsc: false,
|
||||||
|
context: {
|
||||||
|
initialConfig: {
|
||||||
|
enabledBundles: this.md2Service.info.enabledBundles?.length > 0
|
||||||
|
? this.md2Service.info.enabledBundles
|
||||||
|
: [GameBundle.CoreGame],
|
||||||
|
enableMobSpecialRule: this.md2Service.info.enableMobSpecialRule || false,
|
||||||
|
enableHeroBetrayal: this.md2Service.info.enableHeroBetrayal || false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.onClose.pipe(first()).subscribe((config: GameInitConfig) => {
|
||||||
|
if (config) {
|
||||||
|
this.initGameBundles(config);
|
||||||
|
} else {
|
||||||
|
// User cancelled, use defaults
|
||||||
|
this.initGameBundles({
|
||||||
|
enabledBundles: [GameBundle.CoreGame],
|
||||||
|
enableMobSpecialRule: false,
|
||||||
|
enableHeroBetrayal: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Game already initialized, just use current settings
|
||||||
|
this.initGameBundles({
|
||||||
|
enabledBundles: this.md2Service.info.enabledBundles,
|
||||||
|
enableMobSpecialRule: this.md2Service.info.enableMobSpecialRule,
|
||||||
|
enableHeroBetrayal: this.md2Service.info.enableHeroBetrayal
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private initGameBundles(config: GameInitConfig) {
|
||||||
|
this.md2Service.info.enabledBundles = config.enabledBundles;
|
||||||
|
this.md2Service.info.enableMobSpecialRule = config.enableMobSpecialRule;
|
||||||
|
this.md2Service.info.enableHeroBetrayal = config.enableHeroBetrayal;
|
||||||
|
|
||||||
|
if (this.md2Service.initialized == false) {
|
||||||
|
this.gameRoomService.createGameRoom('MD2');
|
||||||
|
this.md2Service.initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
this.initService.initMobDecks();
|
this.initService.initMobDecks();
|
||||||
this.initService.initTreasureBag();
|
this.initService.initTreasureBag();
|
||||||
}
|
}
|
||||||
override signalRInitialized() {
|
override signalRInitialized() {
|
||||||
|
|
||||||
|
}
|
||||||
|
adjustHeroValue(hero: MD2HeroInfo, value: string) {
|
||||||
|
this.msgBoxService.showInputbox(`Adjust ${value} for ${hero.playerInfo.name}`, `Enter the new value for ${value}`, {
|
||||||
|
inputType: 'number',
|
||||||
|
inputValue: hero[value].toString()
|
||||||
|
}).pipe(first()).subscribe(result => {
|
||||||
|
if (![false, null, undefined].includes(result)) {
|
||||||
|
hero[value] = Number.parseInt(result);
|
||||||
|
this.md2Service.broadcastHeroInfoToAll(hero, true);
|
||||||
|
this.detectChanges();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
activatingHero(hero: MD2HeroInfo) {
|
||||||
|
if (hero.remainActions > 0) {
|
||||||
|
this.msgBoxService.show('Activating', { text: `Are you sure you want to activate ${hero.playerInfo.name}?` }).pipe(first()).subscribe(result => {
|
||||||
|
if (result) {
|
||||||
|
this.md2Service.heros.forEach(h => h.uiActivating = false);
|
||||||
|
hero.uiActivating = true;
|
||||||
|
this.md2Service.broadcastAllHeroInfoToAll();
|
||||||
|
this.detectChanges();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
showQrCode() {
|
showQrCode() {
|
||||||
|
|
||||||
if (this.md2Service.initialized == false) {
|
if (this.md2Service.initialized == false) {
|
||||||
this.gameRoomService.createGameRoom('MD2');
|
this.gameRoomService.createGameRoom('MD2');
|
||||||
this.md2Service.initialized = true;
|
this.md2Service.initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let initUrl = `${window.location.origin}/games/MD2_Hero/${this.gameRoomService.gameRoomId}`;
|
let initUrl = `${window.location.origin}/games/MD2_Hero/${this.gameRoomService.gameRoomId}`;
|
||||||
this.msgBoxService.show("Scan To Join", { text: `<img src='${this.qrCodeService.QRCodeUrl(initUrl, 5)}'><br><a href='${initUrl}' target='_blank'>Link</a>` });
|
this.msgBoxService.show("Scan To Join", { text: `<img src='${this.qrCodeService.QRCodeUrl(initUrl, 5)}'><br><a href='${initUrl}' target='_blank'>Link</a>` });
|
||||||
}
|
}
|
||||||
@@ -137,4 +221,31 @@ export class MassiveDarkness2Component extends MD2Base implements OnInit {
|
|||||||
this.md2Service.enterBossFight();
|
this.md2Service.enterBossFight();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
accessHealFountain() {
|
||||||
|
this.md2Service.drawingHealFountain();
|
||||||
|
}
|
||||||
|
broadcastHeros() {
|
||||||
|
this.md2Service.heros.forEach(hero => {
|
||||||
|
hero.uiShowAttackBtn = this.md2Service.mobs.length > 0 || this.md2Service.roamingMonsters.length > 0 || this.md2Service.info.isBossFight;
|
||||||
|
});
|
||||||
|
this.md2Service.broadcastAllHeroInfoToAll();
|
||||||
|
}
|
||||||
|
removeHero(hero) {
|
||||||
|
this.msgBoxService.showConfirmDeleteBox().pipe(first()).subscribe(result => {
|
||||||
|
if (result) {
|
||||||
|
this.md2Service.info.heros.splice(this.md2Service.info.heros.indexOf(hero));
|
||||||
|
this.cdRef.detectChanges();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public get round(): string {
|
||||||
|
if (this.md2Service.info.isBossFight) {
|
||||||
|
return `Boss Fight ${NumberUtils.Ordinal(this.md2Service.info.bossRound)} Round`;
|
||||||
|
} else {
|
||||||
|
return NumberUtils.Ordinal(this.md2Service.info.round) + ' Round';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
import { MobType } from "./massive-darkness2.model";
|
||||||
|
import { MobSkillType } from "./massive-darkness2.model.boss";
|
||||||
|
|
||||||
|
export enum MobSkillTarget {
|
||||||
|
Random = 40,
|
||||||
|
LeastHp = 50,
|
||||||
|
LeastMp = 60,
|
||||||
|
HighestHp = 70,
|
||||||
|
HighestMp = 80,
|
||||||
|
LowestLevel = 90,
|
||||||
|
HighestLevel = 100,
|
||||||
|
MostExtraToken = 200,
|
||||||
|
LeastExtraToken,
|
||||||
|
MostExtraToken2,
|
||||||
|
LeastExtraToken2
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum GameBundle {
|
||||||
|
CoreGame,
|
||||||
|
HeavenFallen,
|
||||||
|
Zombicide,
|
||||||
|
ZombicideWhiteDeath,
|
||||||
|
DarkBringerPack
|
||||||
|
}
|
||||||
|
export interface MD2MobInfo {
|
||||||
|
id: string;
|
||||||
|
type: MobType;
|
||||||
|
from: GameBundle;
|
||||||
|
name: string;
|
||||||
|
leaderImgUrl: string;
|
||||||
|
minionImgUrl: string;
|
||||||
|
mobLevelInfos: MD2MobLevelInfo[];
|
||||||
|
skills: MD2MobSkill[];
|
||||||
|
bossFightProfile?: BossFightProfile;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MD2MobLevelInfo {
|
||||||
|
id: string;
|
||||||
|
level: number;
|
||||||
|
mobInfoId: string;
|
||||||
|
rewardTokens: number;
|
||||||
|
fixedRareTreasure: number;
|
||||||
|
fixedEpicTreasure: number;
|
||||||
|
fixedLegendTreasure: number;
|
||||||
|
fixedHp: number;
|
||||||
|
hpPerHero: number;
|
||||||
|
actions: number;
|
||||||
|
attackInfo: MD2DiceSet;
|
||||||
|
alterAttackInfo?: MD2DiceSet;
|
||||||
|
defenceInfo: MD2DiceSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MD2MobSkill {
|
||||||
|
constructor(config: Partial<MD2MobSkill> = {}) {
|
||||||
|
Object.assign(this, config);
|
||||||
|
}
|
||||||
|
id: string;
|
||||||
|
seq: number;
|
||||||
|
level: number;
|
||||||
|
mobInfoId: string;
|
||||||
|
type: MobSkillType;
|
||||||
|
skillTarget: MobSkillTarget | null;
|
||||||
|
clawRoll: number;
|
||||||
|
skillRoll: number;
|
||||||
|
name: string;
|
||||||
|
skillCondition: string;
|
||||||
|
description: string;
|
||||||
|
uiDisplay?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MD2DiceSet {
|
||||||
|
type: MobSkillType;
|
||||||
|
yellow: number | null;
|
||||||
|
orange: number | null;
|
||||||
|
red: number | null;
|
||||||
|
blue: number | null;
|
||||||
|
green: number | null;
|
||||||
|
black: number | null;
|
||||||
|
}
|
||||||
|
export interface BossFightProfile {
|
||||||
|
mobInfoId: string;
|
||||||
|
id: string;
|
||||||
|
prerequisite: string;
|
||||||
|
objective: string;
|
||||||
|
specialRules: string;
|
||||||
|
extraTokenName: string;
|
||||||
|
extraTokenHtml: string;
|
||||||
|
extraTokenName2: string;
|
||||||
|
extraTokenHtml2: string;
|
||||||
|
phaseBuffs: BossFightPhaseBuff[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BossFightPhaseBuff {
|
||||||
|
id: string;
|
||||||
|
bossFightProfileId: string;
|
||||||
|
phase: number;
|
||||||
|
extraAction: number;
|
||||||
|
extraAttackDice: MD2DiceSet;
|
||||||
|
extraDefenceDice: MD2DiceSet;
|
||||||
|
extraHp: number;
|
||||||
|
extraTokenCount: number;
|
||||||
|
extraTokenCount2: number;
|
||||||
|
enableExtraBuffDescription: boolean;
|
||||||
|
extraBuffDescription: string;
|
||||||
|
}
|
||||||
@@ -6,47 +6,65 @@ import { StringUtils } from "../../utilities/string-utils";
|
|||||||
import { GamePlayer } from "../games.model";
|
import { GamePlayer } from "../games.model";
|
||||||
import { MD2HeroInfo, AttackTarget, HeroClass } from "./massive-darkness2.model";
|
import { MD2HeroInfo, AttackTarget, HeroClass } from "./massive-darkness2.model";
|
||||||
import { MobSkill } from "./massive-darkness2.model.boss";
|
import { MobSkill } from "./massive-darkness2.model.boss";
|
||||||
|
import { MobSkillTarget } from "./massive-darkness2.db.model";
|
||||||
export class MD2Logic {
|
export class MD2Logic {
|
||||||
|
public static getTargetHeroByFilter(heros: MD2HeroInfo[], targetType: MobSkillTarget) {
|
||||||
public static getTargetHerosByFilter(heros: MD2HeroInfo[], targetType: AttackTarget, onlyOne: boolean = false) {
|
return this.getTargetHerosByFilter(heros, targetType, true)[0];
|
||||||
|
}
|
||||||
|
public static getTargetHerosByFilter(heros: MD2HeroInfo[], targetType: MobSkillTarget, onlyOne: boolean = false) {
|
||||||
let beenAttackedHero = [] as MD2HeroInfo[];
|
let beenAttackedHero = [] as MD2HeroInfo[];
|
||||||
switch (targetType) {
|
switch (targetType) {
|
||||||
case AttackTarget.LeastHp:
|
case MobSkillTarget.LeastHp:
|
||||||
let lowestHp = Math.min(...heros.map(h => h.hp));
|
let lowestHp = Math.min(...heros.map(h => h.hp));
|
||||||
beenAttackedHero = heros.filter(h => h.hp == lowestHp);
|
beenAttackedHero = heros.filter(h => h.hp == lowestHp);
|
||||||
//this.otherAttackTarget = 'attacking the other <b>Lowest HP</b> hero.';
|
//this.otherAttackTarget = 'attacking the other <b>Lowest HP</b> hero.';
|
||||||
break;
|
break;
|
||||||
case AttackTarget.LeastMp:
|
case MobSkillTarget.LeastMp:
|
||||||
let lowestMp = Math.min(...heros.map(h => h.mp));
|
let lowestMp = Math.min(...heros.map(h => h.mp));
|
||||||
beenAttackedHero = heros.filter(h => h.hp == lowestMp);
|
beenAttackedHero = heros.filter(h => h.mp == lowestMp);
|
||||||
//this.otherAttackTarget = 'attacking the other <b>Lowest HP</b> hero.';
|
//this.otherAttackTarget = 'attacking the other <b>Lowest HP</b> hero.';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AttackTarget.HighestHp:
|
case MobSkillTarget.HighestHp:
|
||||||
let highestHp = Math.max(...heros.map(h => h.hp));
|
let highestHp = Math.max(...heros.map(h => h.hp));
|
||||||
beenAttackedHero = heros.filter(h => h.hp == highestHp);
|
beenAttackedHero = heros.filter(h => h.hp == highestHp);
|
||||||
//this.otherAttackTarget = 'attacking the other <b>Highest HP</b> hero.';
|
//this.otherAttackTarget = 'attacking the other <b>Highest HP</b> hero.';
|
||||||
break;
|
break;
|
||||||
case AttackTarget.HighestMp:
|
case MobSkillTarget.HighestMp:
|
||||||
let highestMp = Math.max(...heros.map(h => h.mp));
|
let highestMp = Math.max(...heros.map(h => h.mp));
|
||||||
beenAttackedHero = heros.filter(h => h.mp == highestMp);
|
beenAttackedHero = heros.filter(h => h.mp == highestMp);
|
||||||
//this.otherAttackTarget = 'attacking the other <b>Highest Mp</b> hero.';
|
//this.otherAttackTarget = 'attacking the other <b>Highest Mp</b> hero.';
|
||||||
break;
|
break;
|
||||||
case AttackTarget.LowestLevel:
|
case MobSkillTarget.LowestLevel:
|
||||||
let lowestLevel = Math.max(...heros.map(h => h.level));
|
let lowestLevel = Math.min(...heros.map(h => h.level));
|
||||||
beenAttackedHero = heros.filter(h => h.level == lowestLevel);
|
beenAttackedHero = heros.filter(h => h.level == lowestLevel);
|
||||||
//this.otherAttackTarget = 'attacking the other <b>Lowest Level</b> hero.';
|
//this.otherAttackTarget = 'attacking the other <b>Lowest Level</b> hero.';
|
||||||
break;
|
break;
|
||||||
case AttackTarget.LeastCorruption:
|
case MobSkillTarget.HighestLevel:
|
||||||
|
let highestLevel = Math.max(...heros.map(h => h.level));
|
||||||
|
beenAttackedHero = heros.filter(h => h.level == highestLevel);
|
||||||
|
//this.otherAttackTarget = 'attacking the other <b>Lowest Level</b> hero.';
|
||||||
|
break;
|
||||||
|
case MobSkillTarget.LeastExtraToken:
|
||||||
|
|
||||||
let leastCor = Math.min(...heros.map(h => h.corruptionToken));
|
let leastExtraToken = Math.min(...heros.map(h => h.extraToken));
|
||||||
beenAttackedHero = heros.filter(h => h.corruptionToken == leastCor);
|
beenAttackedHero = heros.filter(h => h.extraToken == leastExtraToken);
|
||||||
break;
|
break;
|
||||||
case AttackTarget.MostCorruption:
|
case MobSkillTarget.MostExtraToken:
|
||||||
let mostCor = Math.max(...heros.map(h => h.corruptionToken));
|
let mostExtraToken = Math.max(...heros.map(h => h.extraToken));
|
||||||
beenAttackedHero = heros.filter(h => h.corruptionToken == mostCor);
|
beenAttackedHero = heros.filter(h => h.extraToken == mostExtraToken);
|
||||||
break;
|
break;
|
||||||
case AttackTarget.Random:
|
|
||||||
|
case MobSkillTarget.LeastExtraToken2:
|
||||||
|
|
||||||
|
let leastExtraToken2 = Math.min(...heros.map(h => h.extraToken2));
|
||||||
|
beenAttackedHero = heros.filter(h => h.extraToken2 == leastExtraToken2);
|
||||||
|
break;
|
||||||
|
case MobSkillTarget.MostExtraToken2:
|
||||||
|
let mostExtraToken2 = Math.max(...heros.map(h => h.extraToken2));
|
||||||
|
beenAttackedHero = heros.filter(h => h.extraToken2 == mostExtraToken2);
|
||||||
|
break;
|
||||||
|
case MobSkillTarget.Random:
|
||||||
default:
|
default:
|
||||||
beenAttackedHero = [heros[Math.round(Math.random() * (heros.length - 1))]];
|
beenAttackedHero = [heros[Math.round(Math.random() * (heros.length - 1))]];
|
||||||
//this.otherAttackTarget = 'Just act like normal.';
|
//this.otherAttackTarget = 'Just act like normal.';
|
||||||
|
|||||||
@@ -1,17 +1,25 @@
|
|||||||
import { stringify } from "querystring"
|
|
||||||
import { Observable, Subject, Subscription } from "rxjs"
|
import { Observable, Subject, Subscription } from "rxjs"
|
||||||
import { first } from "rxjs/operators"
|
import { first } from "rxjs/operators"
|
||||||
import { MD2Service } from "../../services/MD2/md2.service"
|
import { MD2Service } from "../../services/MD2/md2.service"
|
||||||
import { StringUtils } from "../../utilities/string-utils"
|
import { StringUtils } from "../../utilities/string-utils"
|
||||||
import { BossActivationComponent } from "./boss-fight/boss-activation/boss-activation.component"
|
import { BossActivationComponent } from "./boss-fight/boss-activation/boss-activation.component"
|
||||||
import { TreasureType, AttackInfo, DefenseInfo, AttackType, MD2Icon, MD2HeroInfo, AttackTarget, MobInfo, MobType } from "./massive-darkness2.model"
|
import { TreasureType, AttackInfo, AttackType, MD2Icon, MD2HeroInfo, AttackTarget, MobInfo, MobType } from "./massive-darkness2.model"
|
||||||
import { RollingBlackDice } from "./massive-darkness2.model.dice"
|
import { RollingBlackDice } from "./massive-darkness2.model.dice"
|
||||||
|
import { MD2DiceSet, MD2MobSkill, MobSkillTarget } from "./massive-darkness2.db.model"
|
||||||
|
|
||||||
|
|
||||||
export enum MobSkillType {
|
export enum MobSkillType {
|
||||||
Attack,
|
Attack,
|
||||||
Defense,
|
Defense,
|
||||||
Combat
|
Combat,
|
||||||
|
Passive,
|
||||||
|
ConditionalSkill,
|
||||||
|
OtherWiseSkill,
|
||||||
|
ActiveSkill,
|
||||||
|
MeleeAttack = 15,
|
||||||
|
RangeAttack,
|
||||||
|
MagicAttack,
|
||||||
}
|
}
|
||||||
export class MobSkill {
|
export class MobSkill {
|
||||||
constructor(config: Partial<MobSkill> = {}) {
|
constructor(config: Partial<MobSkill> = {}) {
|
||||||
@@ -45,6 +53,7 @@ export interface IBossFight {
|
|||||||
imgUrl: string
|
imgUrl: string
|
||||||
standUrl: string
|
standUrl: string
|
||||||
extraRules: string
|
extraRules: string
|
||||||
|
md2Service: MD2Service
|
||||||
activating(): boolean
|
activating(): boolean
|
||||||
prepareForBossFight(): void
|
prepareForBossFight(): void
|
||||||
darknessPhase(): void
|
darknessPhase(): void
|
||||||
@@ -65,7 +74,7 @@ export abstract class BossFight implements IBossFight {
|
|||||||
extraRules: string
|
extraRules: string
|
||||||
protected subscription: Subscription
|
protected subscription: Subscription
|
||||||
|
|
||||||
constructor(protected md2Service: MD2Service) {
|
constructor(public md2Service: MD2Service) {
|
||||||
this.rounds = 1;
|
this.rounds = 1;
|
||||||
}
|
}
|
||||||
activating(): boolean {
|
activating(): boolean {
|
||||||
@@ -97,35 +106,39 @@ export abstract class BossFight implements IBossFight {
|
|||||||
|
|
||||||
}
|
}
|
||||||
export class BossMicheal extends BossFight {
|
export class BossMicheal extends BossFight {
|
||||||
constructor(protected md2Service: MD2Service) {
|
constructor(public md2Service: MD2Service) {
|
||||||
super(md2Service);
|
super(md2Service);
|
||||||
this.corruptionTokenHtml = this.md2Service.stateService.imgHtml('Tokens/CorruptToken.png');
|
this.corruptionTokenHtml = this.md2Service.imgHtml('Tokens/CorruptToken.png');
|
||||||
|
|
||||||
this.name = 'Michael - The Corrupted Archangel';
|
this.name = 'Michael - The Corrupted Archangel';
|
||||||
this.imgUrl = md2Service.stateService.imgUrl('/Boss/Michael - The Corrupted Archangel.jpg');
|
this.imgUrl = md2Service.imgUrl('/Boss/Michael - The Corrupted Archangel.jpg');
|
||||||
this.standUrl = md2Service.stateService.imgUrl('/Boss/Michael.png');
|
this.standUrl = md2Service.imgUrl('/Boss/Michael.png');
|
||||||
|
|
||||||
this.info = new MobInfo({
|
this.info = new MobInfo({
|
||||||
description: this.name,
|
description: this.name,
|
||||||
type: MobType.Boss,
|
type: MobType.Boss,
|
||||||
hpPerHero: 15,
|
hpPerHero: 15,
|
||||||
level: 10,
|
level: 10,
|
||||||
combatSkill: new MobSkill(
|
imageUrl: md2Service.imgUrl('/Boss/Michael.png')
|
||||||
{
|
|
||||||
name: `Combat 1 ${this.md2Service.stateService.iconHtml(MD2Icon.EnemySkill)}`,
|
|
||||||
description: `Deal 1 Wound for each ${this.corruptionTokenHtml} on the attacking or defending Hero. Discard the tokens afterwards(once per combat).`
|
|
||||||
}),
|
|
||||||
imageUrl: md2Service.stateService.imgUrl('/Boss/Michael.png')
|
|
||||||
});
|
});
|
||||||
this.info.defenseInfo = new DefenseInfo(5, 1);
|
if (!this.info.skills) {
|
||||||
|
this.info.skills = [];
|
||||||
|
}
|
||||||
|
this.info.skills.push({
|
||||||
|
name: `Combat 1 ${this.md2Service.iconHtml(MD2Icon.EnemySkill)}`,
|
||||||
|
description: `Deal 1 Wound for each ${this.corruptionTokenHtml} on the attacking or defending Hero. Discard the tokens afterwards(once per combat).`,
|
||||||
|
type: MobSkillType.Combat,
|
||||||
|
skillRoll: 1
|
||||||
|
} as MD2MobSkill);
|
||||||
|
this.info.defenseInfo = { blue: 5, black: 1 } as MD2DiceSet;
|
||||||
this.info.attackInfos = [new AttackInfo(MD2Icon.Melee, 2, 2, 0, 1)];
|
this.info.attackInfos = [new AttackInfo(MD2Icon.Melee, 2, 2, 0, 1)];
|
||||||
this.actions = 1;
|
this.actions = 1;
|
||||||
this.actionBlackDice = 2;
|
this.actionBlackDice = 2;
|
||||||
|
|
||||||
this.extraRules = `Archangel Michael can’t be the target of any attack, skill, ability or take Wounds until there are no Corruption tokens in the whole Tile.<br><br>` +
|
this.extraRules = `Archangel Michael can’t be the target of any attack, skill, ability or take Wounds until there are no Corruption tokens in the whole Tile.<br><br>` +
|
||||||
`Any Hero on a Zone with a ${this.corruptionTokenHtml} may spend 1 action to remove it. Each time a Hero removes a ${this.corruptionTokenHtml} from a Zone they must roll 1 ${this.md2Service.stateService.iconHtml(MD2Icon.BlackDice)}.` +
|
`Any Hero on a Zone with a ${this.corruptionTokenHtml} may spend 1 action to remove it. Each time a Hero removes a ${this.corruptionTokenHtml} from a Zone they must roll 1 ${this.md2Service.iconHtml(MD2Icon.BlackDice)}.` +
|
||||||
`If ${this.md2Service.stateService.iconHtml(MD2Icon.EnemyClaw)} the Hero takes 1 Wound.<br>If ${this.md2Service.stateService.iconHtml(MD2Icon.EnemySkill)} place 1 ${this.corruptionTokenHtml} on their Dashboard.<br>` +
|
`If ${this.md2Service.iconHtml(MD2Icon.EnemyClaw)} the Hero takes 1 Wound.<br>If ${this.md2Service.iconHtml(MD2Icon.EnemySkill)} place 1 ${this.corruptionTokenHtml} on their Dashboard.<br>` +
|
||||||
`If ${this.md2Service.stateService.iconHtml(MD2Icon.EnemyClaw)}/${this.md2Service.stateService.iconHtml(MD2Icon.EnemySkill)} the Hero takes 1 Wound and places 1 ${this.corruptionTokenHtml} on their Dashboard.`
|
`If ${this.md2Service.iconHtml(MD2Icon.EnemyClaw)}/${this.md2Service.iconHtml(MD2Icon.EnemySkill)} the Hero takes 1 Wound and places 1 ${this.corruptionTokenHtml} on their Dashboard.`
|
||||||
}
|
}
|
||||||
activatedTimes: number
|
activatedTimes: number
|
||||||
acted: number
|
acted: number
|
||||||
@@ -143,14 +156,14 @@ export class BossMicheal extends BossFight {
|
|||||||
|
|
||||||
bossAction(): Observable<boolean> {
|
bossAction(): Observable<boolean> {
|
||||||
|
|
||||||
let actionResult = new RollingBlackDice().roll(this.actionBlackDice);
|
let actionResult = new RollingBlackDice().roll(2);
|
||||||
let actionHtml = '';
|
let actionHtml = '';
|
||||||
let beenAttackedHero = [] as MD2HeroInfo[];
|
let beenAttackedHero = [] as MD2HeroInfo[];
|
||||||
let bossAction: MobSkill;
|
let bossAction: MobSkill;
|
||||||
switch (actionResult.claws) {
|
switch (actionResult.claws) {
|
||||||
case 0:
|
case 0:
|
||||||
//Justice From Above
|
//Justice From Above
|
||||||
beenAttackedHero = this.md2Service.getTargetHerosByFilter(AttackTarget.MostCorruption, true);
|
beenAttackedHero = this.md2Service.getTargetHerosByFilter(MobSkillTarget.MostExtraToken, true);
|
||||||
bossAction = new MobSkill(
|
bossAction = new MobSkill(
|
||||||
{
|
{
|
||||||
name: 'Justice From Above',
|
name: 'Justice From Above',
|
||||||
@@ -160,7 +173,7 @@ export class BossMicheal extends BossFight {
|
|||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
//Lance Dash
|
//Lance Dash
|
||||||
beenAttackedHero = this.md2Service.getTargetHerosByFilter(AttackTarget.LeastCorruption, true);
|
beenAttackedHero = this.md2Service.getTargetHerosByFilter(MobSkillTarget.LeastExtraToken, true);
|
||||||
bossAction = new MobSkill({
|
bossAction = new MobSkill({
|
||||||
name: 'Lance Dash',
|
name: 'Lance Dash',
|
||||||
description:
|
description:
|
||||||
@@ -180,12 +193,13 @@ export class BossMicheal extends BossFight {
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return this.md2Service.dlgService.open(BossActivationComponent, { context: { boss: this, bossAction: bossAction, currentAction: this.activatedTimes, allActions: this.actions } }).onClose;
|
return null;
|
||||||
|
//return this.md2Service.dlgService.open(BossActivationComponent, { context: { boss: this, bossAction: bossAction, currentAction: this.activatedTimes, allActions: this.actions } }).onClose;
|
||||||
|
|
||||||
}
|
}
|
||||||
prepareForBossFight(): void {
|
prepareForBossFight(): void {
|
||||||
this.md2Service.heros.forEach(hero => {
|
this.md2Service.heros.forEach(hero => {
|
||||||
hero.uiShowCorruptionToken = true;
|
hero.uiShowExtraToken = true;
|
||||||
});
|
});
|
||||||
this.md2Service.msgBoxService.show('Prepare Boss Fight', {
|
this.md2Service.msgBoxService.show('Prepare Boss Fight', {
|
||||||
text: `<h6>Place ${this.md2Service.heros.length * 2} ${this.corruptionTokenHtml} on the Corruption Stone Zones (Shadow
|
text: `<h6>Place ${this.md2Service.heros.length * 2} ${this.corruptionTokenHtml} on the Corruption Stone Zones (Shadow
|
||||||
@@ -220,28 +234,30 @@ export class BossMicheal extends BossFight {
|
|||||||
|
|
||||||
export class BossReaper extends BossFight {
|
export class BossReaper extends BossFight {
|
||||||
|
|
||||||
constructor(protected md2Service: MD2Service) {
|
constructor(public md2Service: MD2Service) {
|
||||||
super(md2Service);
|
super(md2Service);
|
||||||
this.timeTokenHtml = this.md2Service.stateService.imgHtml('Tokens/TimeToken.png');
|
this.timeTokenHtml = this.md2Service.imgHtml('Tokens/TimeToken.png');
|
||||||
|
|
||||||
this.name = 'The Reaper';
|
this.name = 'The Reaper';
|
||||||
this.imgUrl = md2Service.stateService.imgUrl('/Boss/The Reaper.jpg');
|
this.imgUrl = md2Service.imgUrl('/Boss/The Reaper.jpg');
|
||||||
this.standUrl = md2Service.stateService.imgUrl('/Boss/The Reaper-Stand.png');
|
this.standUrl = md2Service.imgUrl('/Boss/The Reaper-Stand.png');
|
||||||
|
|
||||||
this.info = new MobInfo({
|
this.info = new MobInfo({
|
||||||
description: this.name,
|
description: this.name,
|
||||||
type: MobType.Boss,
|
type: MobType.Boss,
|
||||||
hpPerHero: 25,
|
hpPerHero: 25,
|
||||||
level: 10,
|
level: 10,
|
||||||
combatSkill: new MobSkill(
|
imageUrl: md2Service.imgUrl('/Boss/The Reaper-Stand.png')
|
||||||
{
|
|
||||||
description: `If the Hero has no ${this.md2Service.stateService.iconHtml(MD2Icon.Mana)}, they take 1 ${this.md2Service.stateService.iconHtml(MD2Icon.Frost)}`,
|
|
||||||
type: MobSkillType.Attack,
|
|
||||||
|
|
||||||
}),
|
|
||||||
imageUrl: md2Service.stateService.imgUrl('/Boss/The Reaper-Stand.png')
|
|
||||||
});
|
});
|
||||||
this.info.defenseInfo = new DefenseInfo(4, 3);
|
if (!this.info.skills) {
|
||||||
|
this.info.skills = [];
|
||||||
|
}
|
||||||
|
this.info.skills.push({
|
||||||
|
description: `If the Hero has no ${this.md2Service.iconHtml(MD2Icon.Mana_Color)}, they take 1 ${this.md2Service.iconHtml(MD2Icon.FrozenToken)}`,
|
||||||
|
type: MobSkillType.Attack,
|
||||||
|
skillRoll: 1
|
||||||
|
} as MD2MobSkill);
|
||||||
|
this.info.defenseInfo = { blue: 4, black: 3 } as MD2DiceSet;
|
||||||
this.info.attackInfos = [new AttackInfo(MD2Icon.Melee, 1, 2, 0, 3)];
|
this.info.attackInfos = [new AttackInfo(MD2Icon.Melee, 1, 2, 0, 3)];
|
||||||
this.actions = 1;
|
this.actions = 1;
|
||||||
this.actionBlackDice = 2;
|
this.actionBlackDice = 2;
|
||||||
@@ -267,7 +283,7 @@ export class BossReaper extends BossFight {
|
|||||||
switch (actionResult.claws) {
|
switch (actionResult.claws) {
|
||||||
case 0:
|
case 0:
|
||||||
//Justice From Above
|
//Justice From Above
|
||||||
beenAttackedHero = this.md2Service.getTargetHerosByFilter(AttackTarget.LeastMp, true);
|
beenAttackedHero = this.md2Service.getTargetHerosByFilter(MobSkillTarget.LeastMp, true);
|
||||||
bossAction = new MobSkill(
|
bossAction = new MobSkill(
|
||||||
{
|
{
|
||||||
name: 'Soul Drain',
|
name: 'Soul Drain',
|
||||||
@@ -277,7 +293,7 @@ export class BossReaper extends BossFight {
|
|||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
//Lance Dash
|
//Lance Dash
|
||||||
beenAttackedHero = this.md2Service.getTargetHerosByFilter(AttackTarget.LeastCorruption, true);
|
beenAttackedHero = this.md2Service.getTargetHerosByFilter(MobSkillTarget.LeastExtraToken, true);
|
||||||
bossAction = new MobSkill({
|
bossAction = new MobSkill({
|
||||||
name: 'Time Ticking',
|
name: 'Time Ticking',
|
||||||
description:
|
description:
|
||||||
@@ -290,15 +306,16 @@ export class BossReaper extends BossFight {
|
|||||||
name: 'Death Is Coming',
|
name: 'Death Is Coming',
|
||||||
description:
|
description:
|
||||||
`Place The Reaper in the central Zone.<br>` +
|
`Place The Reaper in the central Zone.<br>` +
|
||||||
`Roll 1 ${this.md2Service.stateService.iconHtml(MD2Icon.YellowDice)}. Remove ${this.timeTokenHtml} equal to ${this.md2Service.stateService.iconHtml(MD2Icon.Melee)} rolled from both <b>Hourglass Zone</b>.<br>` +
|
`Roll 1 ${this.md2Service.iconHtml(MD2Icon.YellowDice)}. Remove ${this.timeTokenHtml} equal to ${this.md2Service.iconHtml(MD2Icon.Melee)} rolled from both <b>Hourglass Zone</b>.<br>` +
|
||||||
`Each Hero discards ${this.md2Service.stateService.iconHtml(MD2Icon.MP)} equal to ${this.md2Service.stateService.iconHtml(MD2Icon.Melee)} rolled.`
|
`Each Hero discards ${this.md2Service.iconHtml(MD2Icon.MP)} equal to ${this.md2Service.iconHtml(MD2Icon.Melee)} rolled.`
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return this.md2Service.dlgService.open(BossActivationComponent, { context: { boss: this, bossAction: bossAction, currentAction: this.activatedTimes, allActions: this.actions } }).onClose;
|
return null;
|
||||||
|
//return this.md2Service.dlgService.open(BossActivationComponent, { context: { boss: this, bossAction: bossAction, currentAction: this.activatedTimes, allActions: this.actions } }).onClose;
|
||||||
}
|
}
|
||||||
prepareForBossFight(): void {
|
prepareForBossFight(): void {
|
||||||
this.md2Service.msgBoxService.show('Prepare Boss Fight', {
|
this.md2Service.msgBoxService.show('Prepare Boss Fight', {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { ObjectUtils } from "../../utilities/object-utils";
|
|||||||
import { GamePlayer } from "../games.model";
|
import { GamePlayer } from "../games.model";
|
||||||
import { MD2Clone } from "./factorys/md2-clone";
|
import { MD2Clone } from "./factorys/md2-clone";
|
||||||
import { MobSkill } from "./massive-darkness2.model.boss";
|
import { MobSkill } from "./massive-darkness2.model.boss";
|
||||||
|
import { BossFightProfile, GameBundle, MD2DiceSet, MD2MobSkill } from "./massive-darkness2.db.model";
|
||||||
|
|
||||||
const MD2_IMG_URL = (id: string = null) => { return `${environment.apiUrl}/Files/Images/MD2/${(id ? `${encodeURI(id)}` : '')}` }
|
const MD2_IMG_URL = (id: string = null) => { return `${environment.apiUrl}/Files/Images/MD2/${(id ? `${encodeURI(id)}` : '')}` }
|
||||||
export enum MobDlgType {
|
export enum MobDlgType {
|
||||||
@@ -22,6 +23,7 @@ export enum RoundPhase {
|
|||||||
BossActivation
|
BossActivation
|
||||||
}
|
}
|
||||||
export enum TreasureType {
|
export enum TreasureType {
|
||||||
|
Cover,
|
||||||
Common,
|
Common,
|
||||||
Rare,
|
Rare,
|
||||||
Epic,
|
Epic,
|
||||||
@@ -35,6 +37,10 @@ export enum HeroClass {
|
|||||||
Shaman,
|
Shaman,
|
||||||
Paladin,
|
Paladin,
|
||||||
Druid,
|
Druid,
|
||||||
|
Necromancer,
|
||||||
|
Monk,
|
||||||
|
Thinker,
|
||||||
|
Bard
|
||||||
}
|
}
|
||||||
export enum MobType {
|
export enum MobType {
|
||||||
Mob,
|
Mob,
|
||||||
@@ -71,9 +77,23 @@ export enum MD2Icon {
|
|||||||
Rage,
|
Rage,
|
||||||
RedDice,
|
RedDice,
|
||||||
BlueDice,
|
BlueDice,
|
||||||
|
GreenDice,
|
||||||
YellowDice,
|
YellowDice,
|
||||||
OrangeDice,
|
OrangeDice,
|
||||||
BlackDice
|
BlackDice,
|
||||||
|
//Below are image based icons
|
||||||
|
TreasureToken = 300,
|
||||||
|
TreasureToken_Common,
|
||||||
|
TreasureToken_Rare,
|
||||||
|
TreasureToken_Epic,
|
||||||
|
TreasureToken_Legendary,
|
||||||
|
HP_Color,
|
||||||
|
Mana_Color,
|
||||||
|
CorruptToken,
|
||||||
|
TimeToken,
|
||||||
|
FireToken,
|
||||||
|
FrozenToken
|
||||||
|
|
||||||
}
|
}
|
||||||
export enum AttackTarget {
|
export enum AttackTarget {
|
||||||
Random = 40,
|
Random = 40,
|
||||||
@@ -110,14 +130,7 @@ export class AttackInfo {
|
|||||||
yellow: number
|
yellow: number
|
||||||
black: number
|
black: number
|
||||||
}
|
}
|
||||||
export class DefenseInfo {
|
|
||||||
constructor(blue: number, black: number = 0) {
|
|
||||||
this.blue = blue
|
|
||||||
this.black = black
|
|
||||||
}
|
|
||||||
blue: number
|
|
||||||
black: number
|
|
||||||
}
|
|
||||||
export class MD2LevelUpReward {
|
export class MD2LevelUpReward {
|
||||||
constructor(config: Partial<MD2LevelUpReward>) {
|
constructor(config: Partial<MD2LevelUpReward>) {
|
||||||
Object.assign(this, config);
|
Object.assign(this, config);
|
||||||
@@ -294,6 +307,8 @@ export class MobInfo implements IDrawingItem {
|
|||||||
this.drawingWeight = 1;
|
this.drawingWeight = 1;
|
||||||
this.unitRemainHp = config.hp
|
this.unitRemainHp = config.hp
|
||||||
}
|
}
|
||||||
|
id: string;
|
||||||
|
from: GameBundle;
|
||||||
type: MobType = MobType.Mob;
|
type: MobType = MobType.Mob;
|
||||||
imageUrl: string
|
imageUrl: string
|
||||||
standUrl: string
|
standUrl: string
|
||||||
@@ -311,20 +326,24 @@ export class MobInfo implements IDrawingItem {
|
|||||||
fixedCarriedTreasure: TreasureItem[];
|
fixedCarriedTreasure: TreasureItem[];
|
||||||
unitRemainHp: number;
|
unitRemainHp: number;
|
||||||
attackInfos: AttackInfo[];
|
attackInfos: AttackInfo[];
|
||||||
defenseInfo: DefenseInfo;
|
defenseInfo: MD2DiceSet;
|
||||||
|
|
||||||
|
skills: MD2MobSkill[];
|
||||||
actions: number = 0;
|
actions: number = 0;
|
||||||
activateDescription: string;
|
activateDescription: string;
|
||||||
combatSkill: MobSkill
|
|
||||||
|
|
||||||
fireToken: number = 0;
|
fireToken: number = 0;
|
||||||
frozenToken: number = 0;
|
frozenToken: number = 0;
|
||||||
corruptionToken: number = 0;
|
corruptionToken: number = 0;
|
||||||
|
uiExtraTokenCount: number = 0;
|
||||||
|
uiExtraTokenCount2: number = 0;
|
||||||
uiWounds: number;
|
uiWounds: number;
|
||||||
uiFireTokens: number;
|
uiFireTokens: number;
|
||||||
uiFrozenTokens: number;
|
uiFrozenTokens: number;
|
||||||
uiCorruptionTokens: number;
|
uiCorruptionTokens: number;
|
||||||
uiAttackedBy: string;
|
uiAttackedBy: string;
|
||||||
|
extraRule: string;
|
||||||
|
bossFightProfile?: BossFightProfile;
|
||||||
get identifyName(): string {
|
get identifyName(): string {
|
||||||
return `${this.name}_${this.level}`;
|
return `${this.name}_${this.level}`;
|
||||||
}
|
}
|
||||||
@@ -367,10 +386,17 @@ export class MobInfo implements IDrawingItem {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public actionSubject: Subject<string>;
|
|
||||||
public activateFunction?: (mob: MobInfo, msgBoxService: MsgBoxService, heros: MD2HeroInfo[]) => void;
|
public activateFunction?: (mob: MobInfo, msgBoxService: MsgBoxService, heros: MD2HeroInfo[]) => void;
|
||||||
}
|
}
|
||||||
|
export interface MD2HeroProfile {
|
||||||
|
id: string;
|
||||||
|
heroClass: HeroClass;
|
||||||
|
title: string;
|
||||||
|
hp: number;
|
||||||
|
mana: number;
|
||||||
|
skillHtml: string;
|
||||||
|
shadowSkillHtml: string;
|
||||||
|
}
|
||||||
export class MD2HeroInfo {
|
export class MD2HeroInfo {
|
||||||
constructor(
|
constructor(
|
||||||
config: Partial<MD2HeroInfo> = {}
|
config: Partial<MD2HeroInfo> = {}
|
||||||
@@ -388,7 +414,8 @@ export class MD2HeroInfo {
|
|||||||
level: number = 1;
|
level: number = 1;
|
||||||
fireToken: number = 0;
|
fireToken: number = 0;
|
||||||
frozenToken: number = 0;
|
frozenToken: number = 0;
|
||||||
corruptionToken: number = 0;
|
extraToken: number = 0;
|
||||||
|
extraToken2: number = 0;
|
||||||
playerInfo: GamePlayer;
|
playerInfo: GamePlayer;
|
||||||
imgUrl: string;
|
imgUrl: string;
|
||||||
skillHtml: string;
|
skillHtml: string;
|
||||||
@@ -396,9 +423,15 @@ export class MD2HeroInfo {
|
|||||||
remainActions: number = 3;
|
remainActions: number = 3;
|
||||||
rage: number = 0;
|
rage: number = 0;
|
||||||
uiActivating = false;
|
uiActivating = false;
|
||||||
uiShowCorruptionToken = false;
|
uiExtraTokenHtml: string = '';
|
||||||
|
uiExtraTokenHtml2: string = '';
|
||||||
|
uiExtraTokenName: string = '';
|
||||||
|
uiExtraTokenName2: string = '';
|
||||||
|
uiShowExtraToken = false;
|
||||||
|
uiShowExtraToken2 = false;
|
||||||
uiBossFight = false;
|
uiBossFight = false;
|
||||||
|
uiShowAttackBtn = false;
|
||||||
|
uiBetrayal = false;
|
||||||
public get heroFullName(): string {
|
public get heroFullName(): string {
|
||||||
return `${this.playerInfo.name} (${HeroClass[this.class]} - ${this.name})`
|
return `${this.playerInfo.name} (${HeroClass[this.class]} - ${this.name})`
|
||||||
}
|
}
|
||||||
@@ -488,7 +521,7 @@ export class CoreGameDarknessPhaseRule implements IDarknessPhaseRule {
|
|||||||
switch (this.round) {
|
switch (this.round) {
|
||||||
case 3:
|
case 3:
|
||||||
case 9:
|
case 9:
|
||||||
this.spawnMob.next();
|
this.spawnRoamingMonster.next();
|
||||||
return false;
|
return false;
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
@@ -496,7 +529,7 @@ export class CoreGameDarknessPhaseRule implements IDarknessPhaseRule {
|
|||||||
break;
|
break;
|
||||||
case 5:
|
case 5:
|
||||||
case 7:
|
case 7:
|
||||||
this.spawnRoamingMonster.next();
|
this.spawnMob.next();
|
||||||
return false;
|
return false;
|
||||||
break;
|
break;
|
||||||
case 6:
|
case 6:
|
||||||
|
|||||||
+55
@@ -0,0 +1,55 @@
|
|||||||
|
<div class="k-dialog-content">
|
||||||
|
<form #form="ngForm">
|
||||||
|
<div class="row form-group">
|
||||||
|
<div class="col-md-4">
|
||||||
|
|
||||||
|
<label class="k-label">Hero Class *</label>
|
||||||
|
<kendo-dropdownlist [(ngModel)]="selectedHeroClass" name="heroClass" [data]="heroClasses"
|
||||||
|
[valueField]="'value'" [textField]="'text'"
|
||||||
|
[defaultItem]="{ value: null, text: 'Select hero class...' }">
|
||||||
|
</kendo-dropdownlist>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
|
||||||
|
<label class="k-label">Title</label>
|
||||||
|
<input kendoTextBox [(ngModel)]="model.title" name="title" class="k-input"
|
||||||
|
placeholder="Enter hero profile title" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
|
||||||
|
<label class="k-label">Mana</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.mana" name="mana" [format]="'n0'" [min]="0" [max]="100"
|
||||||
|
class="k-input" placeholder="Enter mana value">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
|
||||||
|
<label class="k-label">HP</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.hp" name="hP" [format]="'n0'" [min]="0" [max]="100"
|
||||||
|
class="k-input" placeholder="Enter HP value">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">Skill HTML</label>
|
||||||
|
|
||||||
|
<md2-html-editor [(ngModel)]="model.skillHtml" name="skillHtml" class="htmlEditor"></md2-html-editor>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">Shadow Skill HTML</label>
|
||||||
|
<md2-html-editor [(ngModel)]="model.shadowSkillHtml" name="shadowSkillHtml"
|
||||||
|
class="htmlEditor"></md2-html-editor>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<kendo-dialog-actions>
|
||||||
|
<button kendoButton (click)="close()">Cancel</button>
|
||||||
|
<button kendoButton [primary]="true" (click)="save()" [disabled]="!isValid || processing">
|
||||||
|
{{ processing ? 'Saving...' : 'Save' }}
|
||||||
|
</button>
|
||||||
|
</kendo-dialog-actions>
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.k-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.k-form-field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.k-label {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.k-form-error {
|
||||||
|
color: #f31700;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
+98
@@ -0,0 +1,98 @@
|
|||||||
|
import { Component, Input, OnInit, ViewChild, ChangeDetectorRef } from '@angular/core';
|
||||||
|
import { DialogRef, DialogContentBase } from '@progress/kendo-angular-dialog';
|
||||||
|
import { NgForm } from '@angular/forms';
|
||||||
|
import { first } from 'rxjs/operators';
|
||||||
|
import { MD2HeroProfile, HeroClass } from '../../massive-darkness2.model';
|
||||||
|
import { MD2HeroProfileService } from '../../service/massive-darkness2.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ngx-md2-hero-profile-editor',
|
||||||
|
templateUrl: './md2-hero-profile-editor.component.html',
|
||||||
|
styleUrls: ['./md2-hero-profile-editor.component.scss']
|
||||||
|
})
|
||||||
|
export class MD2HeroProfileEditorComponent extends DialogContentBase implements OnInit {
|
||||||
|
@Input() public data: MD2HeroProfile;
|
||||||
|
@Input() public isAdding: boolean = false;
|
||||||
|
@ViewChild('form') form: NgForm;
|
||||||
|
|
||||||
|
public model: MD2HeroProfile;
|
||||||
|
public processing: boolean = false;
|
||||||
|
public heroClasses: Array<{ value: HeroClass; text: string }> = [];
|
||||||
|
public selectedHeroClass: { value: HeroClass; text: string } | null = null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public dialog: DialogRef,
|
||||||
|
private heroProfileService: MD2HeroProfileService,
|
||||||
|
private cdr: ChangeDetectorRef
|
||||||
|
) {
|
||||||
|
super(dialog);
|
||||||
|
this.initializeEnums();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.initializeModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public initializeModel(): void {
|
||||||
|
const classValue = this.data?.heroClass !== undefined && this.data?.heroClass !== null ? this.data.heroClass : HeroClass.Berserker;
|
||||||
|
|
||||||
|
this.model = {
|
||||||
|
id: this.data?.id || '',
|
||||||
|
heroClass: classValue,
|
||||||
|
title: this.data?.title || '',
|
||||||
|
hp: this.data?.hp || 0,
|
||||||
|
mana: this.data?.mana || 0,
|
||||||
|
skillHtml: this.data?.skillHtml || '',
|
||||||
|
shadowSkillHtml: this.data?.shadowSkillHtml || ''
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set selected object for dropdown
|
||||||
|
this.selectedHeroClass = this.heroClasses.find(c => c.value === classValue) || this.heroClasses[0] || null;
|
||||||
|
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
private initializeEnums(): void {
|
||||||
|
// Initialize HeroClass options
|
||||||
|
Object.keys(HeroClass).filter(key => isNaN(Number(key))).forEach(key => {
|
||||||
|
this.heroClasses.push({
|
||||||
|
value: HeroClass[key] as HeroClass,
|
||||||
|
text: key
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public close(): void {
|
||||||
|
this.dialog.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public save(): void {
|
||||||
|
if (this.model.title && !this.processing) {
|
||||||
|
this.processing = true;
|
||||||
|
|
||||||
|
// Extract enum value from selected object
|
||||||
|
const heroProfile: MD2HeroProfile = {
|
||||||
|
...this.model,
|
||||||
|
heroClass: this.selectedHeroClass?.value ?? HeroClass.Berserker
|
||||||
|
};
|
||||||
|
|
||||||
|
this.heroProfileService.createOrUpdate(heroProfile).pipe(first()).subscribe(result => {
|
||||||
|
this.processing = false;
|
||||||
|
this.dialog.close(result);
|
||||||
|
}, error => {
|
||||||
|
this.processing = false;
|
||||||
|
console.error('Error saving hero profile:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isValid(): boolean {
|
||||||
|
if (!this.model) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const titleValid = true;// this.model.title && this.model.title.trim().length > 0;
|
||||||
|
const classValid = this.selectedHeroClass !== null && this.selectedHeroClass !== undefined;
|
||||||
|
return titleValid && classValid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
<nb-card>
|
||||||
|
<nb-card-header>
|
||||||
|
<h4>MD2 Hero Profile Maintenance</h4>
|
||||||
|
<button class="float-right" kendoButton (click)="addHandler()" [primary]="true">
|
||||||
|
<span class="k-icon k-i-plus"></span> Add New
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</nb-card-header>
|
||||||
|
<nb-card-body>
|
||||||
|
<kendo-grid #grid [data]="gridData" [loading]="isLoading" [pageSize]="gridState.take" [skip]="gridState.skip"
|
||||||
|
[group]="gridState.group" [filter]="gridState.filter" [sort]="gridState.sort" [sortable]="true"
|
||||||
|
[filterable]="true" [pageable]="true" [selectable]="true" [groupable]="true"
|
||||||
|
(dataStateChange)="gridState = $event; processGridData()" (edit)="editHandler($event)"
|
||||||
|
(remove)="removeHandler($event)" (add)="addHandler()">
|
||||||
|
|
||||||
|
<kendo-grid-toolbar>
|
||||||
|
<button kendoGridAddCommand>Add new</button>
|
||||||
|
</kendo-grid-toolbar>
|
||||||
|
|
||||||
|
<kendo-grid-column field="title" title="Title" [width]="150">
|
||||||
|
</kendo-grid-column>
|
||||||
|
|
||||||
|
<kendo-grid-column field="heroClass" title="Hero Class" [width]="60">
|
||||||
|
<ng-template kendoGridCellTemplate let-dataItem>
|
||||||
|
{{ getHeroClassName(dataItem.heroClass) }}
|
||||||
|
</ng-template>
|
||||||
|
<ng-template kendoGridGroupHeaderTemplate let-value="value">
|
||||||
|
{{ getHeroClassName(value) }}
|
||||||
|
</ng-template>
|
||||||
|
</kendo-grid-column>
|
||||||
|
|
||||||
|
<kendo-grid-column field="hP" title="HP" [width]="80">
|
||||||
|
<ng-template kendoGridCellTemplate let-dataItem>
|
||||||
|
<md2-icon [icon]="MD2Icon.Mana_Color" size="sm"></md2-icon> {{ dataItem.mana }}
|
||||||
|
<md2-icon [icon]="MD2Icon.HP_Color" size="sm"></md2-icon> {{ dataItem.hp }}
|
||||||
|
</ng-template>
|
||||||
|
</kendo-grid-column>
|
||||||
|
|
||||||
|
<kendo-grid-column field="skill" title="Skill" [width]="500">
|
||||||
|
<ng-template kendoGridCellTemplate let-dataItem>
|
||||||
|
<div [innerHTML]="dataItem.skillHtml"></div>
|
||||||
|
<div>
|
||||||
|
<md2-icon [icon]="MD2Icon.Shadow" size="sm"></md2-icon> <b>Shadow:</b>
|
||||||
|
<div [innerHTML]="dataItem.shadowSkillHtml"></div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</kendo-grid-column>
|
||||||
|
|
||||||
|
<kendo-grid-command-column title="Actions" [width]="140">
|
||||||
|
<ng-template kendoGridCellTemplate let-isNew="isNew" let-dataItem="dataItem" let-rowIndex="rowIndex">
|
||||||
|
<button kendoGridEditCommand [primary]="true">Edit</button>
|
||||||
|
<button kendoGridRemoveCommand>Remove</button>
|
||||||
|
</ng-template>
|
||||||
|
</kendo-grid-command-column>
|
||||||
|
</kendo-grid>
|
||||||
|
</nb-card-body>
|
||||||
|
</nb-card>
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-right {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
kendo-grid {
|
||||||
|
height: 77vh;
|
||||||
|
}
|
||||||
+150
@@ -0,0 +1,150 @@
|
|||||||
|
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||||
|
import { GridComponent, GridDataResult } from '@progress/kendo-angular-grid';
|
||||||
|
import { State, process } from '@progress/kendo-data-query';
|
||||||
|
import { first } from 'rxjs/operators';
|
||||||
|
import { MD2HeroProfile, HeroClass, MD2Icon } from '../massive-darkness2.model';
|
||||||
|
import { MD2HeroProfileService } from '../service/massive-darkness2.service';
|
||||||
|
import { MD2HeroProfileEditorComponent } from './md2-hero-profile-editor/md2-hero-profile-editor.component';
|
||||||
|
import { DialogService } from '@progress/kendo-angular-dialog';
|
||||||
|
import { MsgBoxService } from '../../../services/msg-box.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ngx-md2-hero-profile-maintenance',
|
||||||
|
templateUrl: './md2-hero-profile-maintenance.component.html',
|
||||||
|
styleUrls: ['./md2-hero-profile-maintenance.component.scss']
|
||||||
|
})
|
||||||
|
export class MD2HeroProfileMaintenanceComponent implements OnInit {
|
||||||
|
@ViewChild('grid') grid: GridComponent;
|
||||||
|
MD2Icon = MD2Icon;
|
||||||
|
public gridData: GridDataResult = { data: [], total: 0 };
|
||||||
|
private allData: MD2HeroProfile[] = [];
|
||||||
|
private lastSelectedHero: MD2HeroProfile;
|
||||||
|
private lastSelectedHeroClass: HeroClass;
|
||||||
|
public gridState: State = {
|
||||||
|
skip: 0,
|
||||||
|
take: 1000,
|
||||||
|
sort: [{
|
||||||
|
field: 'title',
|
||||||
|
dir: 'asc'
|
||||||
|
}],
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: []
|
||||||
|
},
|
||||||
|
group: [{
|
||||||
|
field: 'heroClass',
|
||||||
|
dir: 'asc'
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
public isLoading: boolean = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private heroProfileService: MD2HeroProfileService,
|
||||||
|
private dialogService: DialogService,
|
||||||
|
private msgBoxService: MsgBoxService
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public loadData(): void {
|
||||||
|
this.isLoading = true;
|
||||||
|
this.heroProfileService.getAll().pipe(first()).subscribe(result => {
|
||||||
|
this.allData = result;
|
||||||
|
this.processGridData();
|
||||||
|
this.isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public processGridData(): void {
|
||||||
|
// Normalize filter state to handle null/undefined/empty filters
|
||||||
|
let normalizedFilter: { logic: 'and' | 'or'; filters: any[] } = { logic: 'and', filters: [] };
|
||||||
|
if (this.gridState.filter) {
|
||||||
|
const filters = this.gridState.filter.filters || [];
|
||||||
|
if (filters.length > 0) {
|
||||||
|
normalizedFilter = this.gridState.filter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedState: State = {
|
||||||
|
...this.gridState,
|
||||||
|
filter: normalizedFilter
|
||||||
|
};
|
||||||
|
|
||||||
|
this.gridData = process(this.allData, normalizedState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public addHandler(): void {
|
||||||
|
const editorData = { heroClass: this.lastSelectedHeroClass || HeroClass.Berserker } as MD2HeroProfile;
|
||||||
|
if (this.lastSelectedHero) {
|
||||||
|
editorData.heroClass = this.lastSelectedHero.heroClass;
|
||||||
|
editorData.skillHtml = this.lastSelectedHero.skillHtml;
|
||||||
|
editorData.shadowSkillHtml = this.lastSelectedHero.shadowSkillHtml;
|
||||||
|
}
|
||||||
|
const dialogRef = this.dialogService.open({
|
||||||
|
title: 'Add New Hero Profile',
|
||||||
|
content: MD2HeroProfileEditorComponent,
|
||||||
|
width: '90vw',
|
||||||
|
height: 600
|
||||||
|
});
|
||||||
|
|
||||||
|
const editor = dialogRef.content.instance;
|
||||||
|
editor.isAdding = true;
|
||||||
|
editor.data = editorData;
|
||||||
|
|
||||||
|
// Force model re-initialization after data is set
|
||||||
|
setTimeout(() => {
|
||||||
|
editor.initializeModel();
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
dialogRef.result.subscribe((result: MD2HeroProfile) => {
|
||||||
|
if (result) {
|
||||||
|
this.lastSelectedHero = result;
|
||||||
|
this.lastSelectedHeroClass = result.heroClass;
|
||||||
|
this.loadData();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public editHandler({ dataItem }: { dataItem: MD2HeroProfile }): void {
|
||||||
|
const dialogRef = this.dialogService.open({
|
||||||
|
title: 'Edit Hero Profile',
|
||||||
|
content: MD2HeroProfileEditorComponent,
|
||||||
|
width: '90vw',
|
||||||
|
height: 600
|
||||||
|
});
|
||||||
|
this.lastSelectedHero = dataItem;
|
||||||
|
const editor = dialogRef.content.instance;
|
||||||
|
editor.isAdding = false;
|
||||||
|
editor.data = JSON.parse(JSON.stringify(dataItem));
|
||||||
|
|
||||||
|
// Force model re-initialization after data is set
|
||||||
|
setTimeout(() => {
|
||||||
|
editor.initializeModel();
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
dialogRef.result.subscribe(result => {
|
||||||
|
if (result) {
|
||||||
|
this.loadData();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeHandler({ dataItem }: { dataItem: MD2HeroProfile }): void {
|
||||||
|
this.msgBoxService.showConfirmDeleteBox().pipe(first()).subscribe(answer => {
|
||||||
|
if (answer === true) {
|
||||||
|
this.isLoading = true;
|
||||||
|
this.heroProfileService.delete(dataItem.id).pipe(first()).subscribe(result => {
|
||||||
|
this.loadData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public getHeroClassName(heroClass: HeroClass): string {
|
||||||
|
return HeroClass[heroClass] || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<kendo-editor [value]="value" (valueChange)="onChange($event)" (blur)="onTouched()" [disabled]="disabled"
|
||||||
|
[schema]="messageTemplateSchema" [iframe]="false" class="h-100">
|
||||||
|
<kendo-toolbar>
|
||||||
|
<!-- Custom MD2 Icon button -->
|
||||||
|
<kendo-toolbar-button text="Insert Icon" (click)="showInsertMD2Icon()"></kendo-toolbar-button>
|
||||||
|
<!-- Standard editing tools -->
|
||||||
|
<kendo-toolbar-buttongroup>
|
||||||
|
<kendo-toolbar-button kendoEditorBoldButton></kendo-toolbar-button>
|
||||||
|
<kendo-toolbar-button kendoEditorItalicButton></kendo-toolbar-button>
|
||||||
|
<kendo-toolbar-button kendoEditorUnderlineButton></kendo-toolbar-button>
|
||||||
|
<kendo-toolbar-button kendoEditorStrikethroughButton></kendo-toolbar-button>
|
||||||
|
</kendo-toolbar-buttongroup>
|
||||||
|
<kendo-toolbar-buttongroup>
|
||||||
|
<kendo-toolbar-button kendoEditorAlignLeftButton></kendo-toolbar-button>
|
||||||
|
<kendo-toolbar-button kendoEditorAlignCenterButton></kendo-toolbar-button>
|
||||||
|
<kendo-toolbar-button kendoEditorAlignRightButton></kendo-toolbar-button>
|
||||||
|
<kendo-toolbar-button kendoEditorAlignJustifyButton></kendo-toolbar-button>
|
||||||
|
</kendo-toolbar-buttongroup>
|
||||||
|
<kendo-toolbar-dropdownlist kendoEditorFormat></kendo-toolbar-dropdownlist>
|
||||||
|
<kendo-toolbar-dropdownlist kendoEditorFontSize #fontSizeDropdown
|
||||||
|
[data]="fontSizeData"></kendo-toolbar-dropdownlist>
|
||||||
|
<kendo-toolbar-dropdownlist kendoEditorFontFamily></kendo-toolbar-dropdownlist>
|
||||||
|
<kendo-toolbar-colorpicker kendoEditorForeColor></kendo-toolbar-colorpicker>
|
||||||
|
<kendo-toolbar-colorpicker kendoEditorBackColor view="gradient"></kendo-toolbar-colorpicker>
|
||||||
|
<kendo-toolbar-buttongroup>
|
||||||
|
<kendo-toolbar-button kendoEditorInsertUnorderedListButton></kendo-toolbar-button>
|
||||||
|
<kendo-toolbar-button kendoEditorInsertOrderedListButton></kendo-toolbar-button>
|
||||||
|
<kendo-toolbar-button kendoEditorIndentButton></kendo-toolbar-button>
|
||||||
|
<kendo-toolbar-button kendoEditorOutdentButton></kendo-toolbar-button>
|
||||||
|
</kendo-toolbar-buttongroup>
|
||||||
|
<kendo-toolbar-buttongroup>
|
||||||
|
<kendo-toolbar-button kendoEditorUndoButton></kendo-toolbar-button>
|
||||||
|
<kendo-toolbar-button kendoEditorRedoButton></kendo-toolbar-button>
|
||||||
|
</kendo-toolbar-buttongroup>
|
||||||
|
<kendo-toolbar-button kendoEditorInsertFileButton></kendo-toolbar-button>
|
||||||
|
<kendo-toolbar-button kendoEditorInsertImageButton></kendo-toolbar-button>
|
||||||
|
<kendo-toolbar-button kendoEditorViewSourceButton></kendo-toolbar-button>
|
||||||
|
<kendo-toolbar-button kendoEditorCleanFormattingButton></kendo-toolbar-button>
|
||||||
|
</kendo-toolbar>
|
||||||
|
</kendo-editor>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
:host ::ng-deep .k-editor-content .MD2Icon {
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { MD2HtmlEditorComponent } from './md2-html-editor.component';
|
||||||
|
import { GamesModule } from '../../games.module';
|
||||||
|
|
||||||
|
describe('MD2HtmlEditorComponent', () => {
|
||||||
|
let component: MD2HtmlEditorComponent;
|
||||||
|
let fixture: ComponentFixture<MD2HtmlEditorComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [GamesModule]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(MD2HtmlEditorComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,443 @@
|
|||||||
|
import { Component, ElementRef, EventEmitter, Inject, Input, NgZone, Output, PLATFORM_ID, Renderer2, ViewChild, AfterViewInit, forwardRef, ChangeDetectorRef } from '@angular/core';
|
||||||
|
import { ControlValueAccessor, Validator, AbstractControl, ValidationErrors, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||||
|
import { EditorComponent, NodeSpec, schema, Schema, FontSizeItem } from '@progress/kendo-angular-editor';
|
||||||
|
|
||||||
|
import { MsgBoxService } from '../../../services/msg-box.service';
|
||||||
|
import { DropDownOption } from '../../../entity/dropDownOption';
|
||||||
|
import { MD2Icon } from '../massive-darkness2.model';
|
||||||
|
import { first } from 'rxjs/operators';
|
||||||
|
import { MD2IconPickerDlgComponent } from './md2-icon-picker-dlg.component';
|
||||||
|
import { NbDialogService } from '@nebular/theme';
|
||||||
|
import { DOMParser as ProseMirrorDOMParser } from 'prosemirror-model';
|
||||||
|
import { DialogService } from '@progress/kendo-angular-dialog';
|
||||||
|
@Component({
|
||||||
|
selector: 'md2-html-editor',
|
||||||
|
templateUrl: './md2-html-editor.component.html',
|
||||||
|
styleUrl: './md2-html-editor.component.scss',
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: NG_VALUE_ACCESSOR,
|
||||||
|
useExisting: forwardRef(() => MD2HtmlEditorComponent),
|
||||||
|
multi: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class MD2HtmlEditorComponent implements ControlValueAccessor, AfterViewInit {
|
||||||
|
@ViewChild(EditorComponent) editor: EditorComponent;
|
||||||
|
@ViewChild('fontSizeDropdown') fontSizeDropdown: any;
|
||||||
|
|
||||||
|
value: string = '';
|
||||||
|
disabled: boolean = false;
|
||||||
|
|
||||||
|
messageTemplateSchema = this.createCustomSchema();
|
||||||
|
customCssClass = '.MD2Icon{font-family: "Massive Darkness 2", sans-serif !important; font-size: 40px; margin-left:5px} body{font-size: 30px; }';
|
||||||
|
|
||||||
|
// Default font size: 30px
|
||||||
|
fontSizeData: FontSizeItem[] = [
|
||||||
|
{ size: 30, text: '30px' },
|
||||||
|
{ size: 8, text: '8px' },
|
||||||
|
{ size: 10, text: '10px' },
|
||||||
|
{ size: 12, text: '12px' },
|
||||||
|
{ size: 14, text: '14px' },
|
||||||
|
{ size: 16, text: '16px' },
|
||||||
|
{ size: 18, text: '18px' },
|
||||||
|
{ size: 20, text: '20px' },
|
||||||
|
{ size: 24, text: '24px' },
|
||||||
|
{ size: 30, text: '30px' },
|
||||||
|
{ size: 36, text: '36px' },
|
||||||
|
{ size: 48, text: '48px' },
|
||||||
|
{ size: 60, text: '60px' },
|
||||||
|
{ size: 72, text: '72px' }
|
||||||
|
];
|
||||||
|
|
||||||
|
defaultFontSize: FontSizeItem = { size: 30, text: '30px' };
|
||||||
|
|
||||||
|
// ControlValueAccessor interface
|
||||||
|
private onChangeFn = (value: string) => { };
|
||||||
|
private onTouchedFn = () => { };
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private msgBoxService: MsgBoxService,
|
||||||
|
private dialogService: DialogService,
|
||||||
|
private cdr: ChangeDetectorRef,
|
||||||
|
elementRef: ElementRef, ngZone: NgZone, @Inject(PLATFORM_ID) platformId: Object) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit(): void {
|
||||||
|
// Set default font size after view initialization
|
||||||
|
// The fontSizeDropdown is the EditorFontSizeComponent instance
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.fontSizeDropdown) {
|
||||||
|
// Access the fontSizeDropDownList component which has the defaultItem property
|
||||||
|
if (this.fontSizeDropdown.fontSizeDropDownList) {
|
||||||
|
this.fontSizeDropdown.fontSizeDropDownList.defaultItem = this.defaultFontSize;
|
||||||
|
}
|
||||||
|
// Also try setting it directly on the component if it has the property
|
||||||
|
if (this.fontSizeDropdown.defaultItem !== undefined) {
|
||||||
|
this.fontSizeDropdown.defaultItem = this.defaultFontSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the editor value is set if writeValue was called before view init
|
||||||
|
if (this.editor && this.value && this.editor.value !== this.value) {
|
||||||
|
this.editor.value = this.value;
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ControlValueAccessor implementation
|
||||||
|
writeValue(value: string | null | undefined): void {
|
||||||
|
const newValue = value || '';
|
||||||
|
|
||||||
|
// Only update if the value actually changed to avoid unnecessary updates
|
||||||
|
if (this.value !== newValue) {
|
||||||
|
this.value = newValue;
|
||||||
|
|
||||||
|
// Angular's [value] binding will handle updating the editor
|
||||||
|
// But if editor is already initialized and value is out of sync, ensure sync
|
||||||
|
if (this.editor && this.editor.value !== this.value) {
|
||||||
|
// Use setTimeout to avoid ExpressionChangedAfterItHasBeenCheckedError
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.editor && this.editor.value !== this.value) {
|
||||||
|
this.editor.value = this.value;
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger change detection to ensure the binding updates
|
||||||
|
if (!this.cdr['destroyed']) {
|
||||||
|
this.cdr.markForCheck();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerOnChange(fn: (value: string) => void): void {
|
||||||
|
this.onChangeFn = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerOnTouched(fn: () => void): void {
|
||||||
|
this.onTouchedFn = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDisabledState(isDisabled: boolean): void {
|
||||||
|
this.disabled = isDisabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange(value: string): void {
|
||||||
|
// Only update if value actually changed to prevent infinite loops
|
||||||
|
if (this.value !== value) {
|
||||||
|
this.value = value || '';
|
||||||
|
this.onChangeFn(this.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onTouched(): void {
|
||||||
|
this.onTouchedFn();
|
||||||
|
}
|
||||||
|
|
||||||
|
showInsertMD2Icon() {
|
||||||
|
this.dialogService.open({
|
||||||
|
title: 'Insert MD2 Icon',
|
||||||
|
content: MD2IconPickerDlgComponent,
|
||||||
|
width: '800px',
|
||||||
|
height: 600
|
||||||
|
}).result.subscribe((html: string) => {
|
||||||
|
if (html && this.editor) {
|
||||||
|
this.insertAfterSelection(html, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text manipulation methods for Kendo Editor
|
||||||
|
/**
|
||||||
|
* Parses HTML string to ProseMirror nodes
|
||||||
|
* @param htmlContent - HTML string to parse
|
||||||
|
* @returns ProseMirror Fragment
|
||||||
|
*/
|
||||||
|
private parseHtmlContent(htmlContent: string): any {
|
||||||
|
if (!this.editor || !this.editor.view) return null;
|
||||||
|
|
||||||
|
const view = this.editor.view;
|
||||||
|
const element = document.createElement('div');
|
||||||
|
element.innerHTML = htmlContent;
|
||||||
|
|
||||||
|
// Use ProseMirror's DOMParser to parse HTML into nodes
|
||||||
|
const parser = ProseMirrorDOMParser.fromSchema(view.state.schema);
|
||||||
|
const slice = parser.parseSlice(element);
|
||||||
|
return slice.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts HTML content at the beginning of the editor
|
||||||
|
* @param content - HTML string to insert
|
||||||
|
* @param asHtml - If true, parses as HTML; if false, inserts as plain text
|
||||||
|
*/
|
||||||
|
public insertAtBeginning(content: string, asHtml: boolean = false): void {
|
||||||
|
if (!this.editor || !this.editor.view) return;
|
||||||
|
|
||||||
|
const view = this.editor.view;
|
||||||
|
const contentNode = asHtml ? this.parseHtmlContent(content) : view.state.schema.text(content);
|
||||||
|
|
||||||
|
if (contentNode) {
|
||||||
|
const transaction = view.state.tr.insert(0, contentNode);
|
||||||
|
view.dispatch(transaction);
|
||||||
|
this.onChange(this.editor.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts HTML content at the end of the editor
|
||||||
|
* @param content - HTML string to insert
|
||||||
|
* @param asHtml - If true, parses as HTML; if false, inserts as plain text
|
||||||
|
*/
|
||||||
|
public insertAtEnd(content: string, asHtml: boolean = false): void {
|
||||||
|
if (!this.editor || !this.editor.view) return;
|
||||||
|
|
||||||
|
const view = this.editor.view;
|
||||||
|
const endPos = view.state.doc.content.size;
|
||||||
|
const contentNode = asHtml ? this.parseHtmlContent(content) : view.state.schema.text(content);
|
||||||
|
|
||||||
|
if (contentNode) {
|
||||||
|
const transaction = view.state.tr.insert(endPos, contentNode);
|
||||||
|
view.dispatch(transaction);
|
||||||
|
this.onChange(this.editor.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts HTML content before the current selection
|
||||||
|
* @param content - HTML string to insert
|
||||||
|
* @param asHtml - If true, parses as HTML; if false, inserts as plain text
|
||||||
|
*/
|
||||||
|
public insertBeforeSelection(content: string, asHtml: boolean = false): void {
|
||||||
|
if (!this.editor || !this.editor.view) return;
|
||||||
|
|
||||||
|
const view = this.editor.view;
|
||||||
|
const { from } = view.state.selection;
|
||||||
|
const contentNode = asHtml ? this.parseHtmlContent(content) : view.state.schema.text(content);
|
||||||
|
|
||||||
|
if (contentNode) {
|
||||||
|
const transaction = view.state.tr.insert(from, contentNode);
|
||||||
|
view.dispatch(transaction);
|
||||||
|
this.onChange(this.editor.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts HTML content after the current selection
|
||||||
|
* @param content - HTML string to insert
|
||||||
|
* @param asHtml - If true, parses as HTML; if false, inserts as plain text
|
||||||
|
*/
|
||||||
|
public insertAfterSelection(content: string, asHtml: boolean = false): void {
|
||||||
|
if (!this.editor || !this.editor.view) return;
|
||||||
|
|
||||||
|
const view = this.editor.view;
|
||||||
|
const { to } = view.state.selection;
|
||||||
|
const contentNode = asHtml ? this.parseHtmlContent(content) : view.state.schema.text(content);
|
||||||
|
|
||||||
|
if (contentNode) {
|
||||||
|
const transaction = view.state.tr.insert(to, contentNode);
|
||||||
|
view.dispatch(transaction);
|
||||||
|
this.onChange(this.editor.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces the currently selected text with new content
|
||||||
|
* @param content - HTML string to replace selection with
|
||||||
|
* @param asHtml - If true, parses as HTML; if false, inserts as plain text
|
||||||
|
*/
|
||||||
|
public replaceSelectedText(content: string, asHtml: boolean = false): void {
|
||||||
|
if (!this.editor || !this.editor.view) return;
|
||||||
|
|
||||||
|
const view = this.editor.view;
|
||||||
|
const { from, to } = view.state.selection;
|
||||||
|
const contentNode = asHtml ? this.parseHtmlContent(content) : view.state.schema.text(content);
|
||||||
|
|
||||||
|
if (!contentNode) return;
|
||||||
|
|
||||||
|
// If there's no selection, just insert at cursor position
|
||||||
|
if (from === to) {
|
||||||
|
const transaction = view.state.tr.insert(from, contentNode);
|
||||||
|
view.dispatch(transaction);
|
||||||
|
} else {
|
||||||
|
// Replace the selected content
|
||||||
|
const transaction = view.state.tr.replaceWith(from, to, contentNode);
|
||||||
|
view.dispatch(transaction);
|
||||||
|
}
|
||||||
|
this.onChange(this.editor.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the currently selected text in the editor
|
||||||
|
* @returns The selected text as a string
|
||||||
|
*/
|
||||||
|
public getSelectedText(): string {
|
||||||
|
if (!this.editor) return '';
|
||||||
|
return this.editor.selectionText || '';
|
||||||
|
}
|
||||||
|
public getSelectionTextOrWholeText(): string {
|
||||||
|
if (!this.editor) return '';
|
||||||
|
|
||||||
|
// If there's selected text, return it
|
||||||
|
if (this.editor.selectionText && this.editor.selectionText.trim().length > 0) {
|
||||||
|
return this.editor.selectionText;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, get the whole text content from the editor's document
|
||||||
|
if (this.editor.view && this.editor.view.state) {
|
||||||
|
return this.editor.view.state.doc.textContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: strip HTML tags and return plain text
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.innerHTML = this.editor.value;
|
||||||
|
return div.textContent || div.innerText || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current cursor position or selection range
|
||||||
|
* @returns Object with 'from' and 'to' positions
|
||||||
|
*/
|
||||||
|
public getSelectionRange(): { from: number; to: number } | null {
|
||||||
|
if (!this.editor || !this.editor.view) return null;
|
||||||
|
|
||||||
|
const { from, to } = this.editor.view.state.selection;
|
||||||
|
return { from, to };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts HTML content at a specific position
|
||||||
|
* @param content - HTML string to insert
|
||||||
|
* @param position - Position to insert at (0 = beginning)
|
||||||
|
* @param asHtml - If true, parses as HTML; if false, inserts as plain text
|
||||||
|
*/
|
||||||
|
public insertAtPosition(content: string, position: number, asHtml: boolean = false): void {
|
||||||
|
if (!this.editor || !this.editor.view) return;
|
||||||
|
|
||||||
|
const view = this.editor.view;
|
||||||
|
const maxPos = view.state.doc.content.size;
|
||||||
|
const safePos = Math.min(Math.max(0, position), maxPos);
|
||||||
|
const contentNode = asHtml ? this.parseHtmlContent(content) : view.state.schema.text(content);
|
||||||
|
|
||||||
|
if (contentNode) {
|
||||||
|
const transaction = view.state.tr.insert(safePos, contentNode);
|
||||||
|
view.dispatch(transaction);
|
||||||
|
this.onChange(this.editor.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces text in a specific range
|
||||||
|
* @param content - HTML string to insert
|
||||||
|
* @param from - Start position
|
||||||
|
* @param to - End position
|
||||||
|
* @param asHtml - If true, parses as HTML; if false, inserts as plain text
|
||||||
|
*/
|
||||||
|
public replaceRange(content: string, from: number, to: number, asHtml: boolean = false): void {
|
||||||
|
if (!this.editor || !this.editor.view) return;
|
||||||
|
|
||||||
|
const view = this.editor.view;
|
||||||
|
const maxPos = view.state.doc.content.size;
|
||||||
|
const safeFrom = Math.min(Math.max(0, from), maxPos);
|
||||||
|
const safeTo = Math.min(Math.max(safeFrom, to), maxPos);
|
||||||
|
const contentNode = asHtml ? this.parseHtmlContent(content) : view.state.schema.text(content);
|
||||||
|
|
||||||
|
if (contentNode) {
|
||||||
|
const transaction = view.state.tr.replaceWith(safeFrom, safeTo, contentNode);
|
||||||
|
view.dispatch(transaction);
|
||||||
|
this.onChange(this.editor.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// showVariablePicker() {
|
||||||
|
// this.easyEditorService.openTableMultiPicker(this.variables, this.variableTableSettings, "Please select a variable").pipe(first()).subscribe(result => {
|
||||||
|
// if (result) {
|
||||||
|
// result.forEach(c => {
|
||||||
|
// this.insertAfterSelection(`<${RbjTagNode} class="rbj-tag" ${RbjTagIdAttribute}="${c.name}" ${RbjTagValueAttribute}="${c.name}">${c.name}</${RbjTagNode}>`, true);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
// Private methods
|
||||||
|
private createCustomSchema(): Schema {
|
||||||
|
let nodes = schema.spec.nodes.addBefore("div", "rbjTag", rbjTagNodeSpec);
|
||||||
|
let marks = schema.spec.marks;
|
||||||
|
//marks = marks.addToStart("rbjSpanTag", rbjTagMarkSpec);
|
||||||
|
return new Schema({
|
||||||
|
nodes: nodes,
|
||||||
|
marks: marks
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define the custom node specification for the rbj-tag element.
|
||||||
|
export const rbjTagNodeSpec: NodeSpec = {
|
||||||
|
// Define the node attributes for the tag
|
||||||
|
attrs: {
|
||||||
|
"md2-icon": { default: "" },
|
||||||
|
"class": { default: "" },
|
||||||
|
// "tag-value": { default: "" },
|
||||||
|
// "tag-preview": { default: "" }
|
||||||
|
},
|
||||||
|
// Specify that this node should be treated as an inline element
|
||||||
|
inline: true,
|
||||||
|
// Allow the node to be part of inline content
|
||||||
|
group: "inline",
|
||||||
|
// Make it atomic (non-editable, treated as a single unit)
|
||||||
|
atom: true,
|
||||||
|
|
||||||
|
// Define how the node should be rendered in the DOM
|
||||||
|
toDOM: (node) => {
|
||||||
|
let md2IconText = node.attrs["md2-icon"] as string;
|
||||||
|
let classValue = node.attrs["class"] as string;
|
||||||
|
if (classValue.includes('dice')) {
|
||||||
|
md2IconText = '';
|
||||||
|
}
|
||||||
|
// let displayValue = tagPreview == 'true' ? node.attrs["tag-value"] : node.attrs["rbj-tag-id"];
|
||||||
|
|
||||||
|
return [
|
||||||
|
"span",
|
||||||
|
{
|
||||||
|
class: classValue,
|
||||||
|
"md2-icon": md2IconText,
|
||||||
|
// "tag-marker": node.attrs["tag-marker"],
|
||||||
|
// "tag-value": node.attrs["tag-value"],
|
||||||
|
// "tag-preview": node.attrs["tag-preview"],
|
||||||
|
contenteditable: "false",
|
||||||
|
//spellcheck: "false", style="font-size: 36px;"
|
||||||
|
style: "display: inline;"
|
||||||
|
},
|
||||||
|
//node.attrs["tag-marker"] + node.attrs["tag-value"] // Display the tag content directly
|
||||||
|
md2IconText
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
|
// Define how to parse the node from existing DOM elements
|
||||||
|
parseDOM: [
|
||||||
|
{
|
||||||
|
// Look for span elements with class rbj-tag (higher priority)
|
||||||
|
tag: "span[md2-icon]",
|
||||||
|
priority: 51, // Higher priority to catch before other parsers
|
||||||
|
// Extract attributes from the DOM element
|
||||||
|
getAttrs: (dom) => {
|
||||||
|
const element = dom as HTMLElement;
|
||||||
|
// Must have rbj-tag-id attribute to be valid
|
||||||
|
if (!element.hasAttribute("md2-icon")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
"md2-icon": element.getAttribute("md2-icon") || "",
|
||||||
|
"class": element.className || "",
|
||||||
|
// "tag-preview": element.getAttribute("tag-preview") || "false",
|
||||||
|
// "tag-value": element.getAttribute("tag-value") || element.getAttribute("rbj-tag-id")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { NbDialogRef } from '@nebular/theme';
|
||||||
|
import { MD2Icon } from '../massive-darkness2.model';
|
||||||
|
import { DialogRef } from '@progress/kendo-angular-dialog';
|
||||||
|
import { MD2Service } from '../../../services/MD2/md2.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'md2-icon-picker-dlg',
|
||||||
|
template: ` <div class="md2-icon-grid">
|
||||||
|
<div
|
||||||
|
*ngFor="let iconData of iconList"
|
||||||
|
(click)="selectIcon(iconData)"
|
||||||
|
class="icon-item" title="{{iconData.name}}"
|
||||||
|
[innerHTML]="iconData.html">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <nb-card>
|
||||||
|
<nb-card-header>
|
||||||
|
<h5>Insert MD2 Icon</h5>
|
||||||
|
</nb-card-header>
|
||||||
|
<nb-card-body>
|
||||||
|
|
||||||
|
</nb-card-body>
|
||||||
|
<nb-card-footer>
|
||||||
|
<button nbButton status="primary" (click)="cancel()">Cancel</button>
|
||||||
|
</nb-card-footer>
|
||||||
|
</nb-card> -->
|
||||||
|
`,
|
||||||
|
styles: [`
|
||||||
|
nb-card{
|
||||||
|
max-width: 800px;
|
||||||
|
z-index: 1050;
|
||||||
|
}
|
||||||
|
.md2-icon-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(60px, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
max-height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
//width:400px;
|
||||||
|
}
|
||||||
|
.icon-item {
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
.icon-item:hover {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-color: #007bff;
|
||||||
|
//transform: scale(1.1);
|
||||||
|
}
|
||||||
|
`]
|
||||||
|
})
|
||||||
|
export class MD2IconPickerDlgComponent implements OnInit {
|
||||||
|
|
||||||
|
iconList: Array<{ icon: MD2Icon, name: string, html: string }> = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private dlgRef: DialogRef,
|
||||||
|
private md2Service: MD2Service
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
// Get all icons
|
||||||
|
for (let icon of Object.values(MD2Icon).filter(val => typeof val === 'number') as MD2Icon[]) {
|
||||||
|
this.iconList.push({
|
||||||
|
icon: icon,
|
||||||
|
name: MD2Icon[icon],
|
||||||
|
html: this.md2Service.iconHtml(icon)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectIcon(iconData: { icon: MD2Icon, name: string, html: string }) {
|
||||||
|
this.dlgRef.close(iconData.html);
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel() {
|
||||||
|
this.dlgRef.close(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1 +1,5 @@
|
|||||||
<span class="MD2Icon {{iconName}} {{iconClass}} {{sizeClass}}"></span>
|
@if(isImageIcon) {
|
||||||
|
<span [innerHTML]="imgUrl" class="{{sizeClass}} mx-2"></span>
|
||||||
|
} @else {
|
||||||
|
<span [innerHtml]="iconHtml" class="{{sizeClass}} mx-2"></span>
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Component, Input, OnInit } from '@angular/core';
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
import { MD2Icon } from '../massive-darkness2.model';
|
import { MD2Icon, TreasureType } from '../massive-darkness2.model';
|
||||||
|
import { MD2Service } from '../../../services/MD2/md2.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'md2-icon',
|
selector: 'md2-icon',
|
||||||
@@ -9,14 +10,25 @@ import { MD2Icon } from '../massive-darkness2.model';
|
|||||||
export class MD2IconComponent implements OnInit {
|
export class MD2IconComponent implements OnInit {
|
||||||
|
|
||||||
@Input() iconClass: string = 'mr-1';
|
@Input() iconClass: string = 'mr-1';
|
||||||
|
isImageIcon: boolean = false;
|
||||||
|
imgUrl: string;
|
||||||
|
iconHtml: string;
|
||||||
private _icon: string | MD2Icon;
|
private _icon: string | MD2Icon;
|
||||||
|
|
||||||
@Input() public set icon(v: string | MD2Icon) {
|
@Input() public set icon(v: string | MD2Icon) {
|
||||||
|
if (v !== undefined) {
|
||||||
if (this._icon != v) {
|
if (this._icon != v) {
|
||||||
this._icon = v;
|
this._icon = v;
|
||||||
|
//if it's string, convert it to MD2Icon
|
||||||
|
if (typeof v === 'string') {
|
||||||
|
const key = Object.keys(MD2Icon).find(
|
||||||
|
k => k.toLowerCase() === v.toString().toLowerCase()
|
||||||
|
);
|
||||||
|
if (key) {
|
||||||
|
v = MD2Icon[key as keyof typeof MD2Icon];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.initIcon(v as MD2Icon);
|
||||||
}
|
}
|
||||||
if (this.isMD2Icon(v)) {
|
if (this.isMD2Icon(v)) {
|
||||||
this.iconName = MD2Icon[v].toLowerCase();
|
this.iconName = MD2Icon[v].toLowerCase();
|
||||||
@@ -25,30 +37,43 @@ export class MD2IconComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
isMD2Icon(icon: MD2Icon | string): icon is MD2Icon {
|
isMD2Icon(icon: MD2Icon | string): icon is MD2Icon {
|
||||||
return Number.isInteger(icon);
|
return Number.isInteger(icon);
|
||||||
}
|
}
|
||||||
@Input() size: string = 'sm';
|
@Input() size: string = 'sm';
|
||||||
iconName: string;
|
iconName: string;
|
||||||
constructor() { }
|
constructor(private md2Service: MD2Service) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private initIcon(icon: MD2Icon): void {
|
||||||
|
if (icon < MD2Icon.TreasureToken) {
|
||||||
|
this.isImageIcon = false;
|
||||||
|
this.iconHtml = this.md2Service.iconHtml(icon);
|
||||||
|
} else {
|
||||||
|
this.isImageIcon = true;
|
||||||
|
this.imgUrl = this.md2Service.iconHtml(icon);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public get sizeClass(): string {
|
public get sizeClass(): string {
|
||||||
switch (this.size) {
|
switch (this.size) {
|
||||||
case 'sm':
|
case 'sm':
|
||||||
return 'g-font-size-18'
|
return this.isImageIcon ? 'g-width-25 img-fluid' : 'g-font-size-18'
|
||||||
break;
|
break;
|
||||||
case 'med':
|
case 'med':
|
||||||
return 'g-font-size-30'
|
return this.isImageIcon ? 'g-width-35 img-fluid' : 'g-font-size-30'
|
||||||
break;
|
break;
|
||||||
case 'lg':
|
case 'lg':
|
||||||
return 'g-font-size-50'
|
return this.isImageIcon ? 'g-width-50 img-fluid' : 'g-font-size-50'
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return 'g-font-size-' + this.size;
|
return this.isImageIcon ? 'g-width-20 img-fluid' : 'g-font-size-' + this.size;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+135
@@ -0,0 +1,135 @@
|
|||||||
|
<div class="k-dialog-content">
|
||||||
|
<kendo-tabstrip tabPosition="top">
|
||||||
|
<kendo-tabstrip-tab title="Boss Fight Info" [selected]="true">
|
||||||
|
<ng-template kendoTabContent>
|
||||||
|
<form class="k-form" style="padding: 5px;">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label g-cursor-pointer"
|
||||||
|
(click)="showInsertMD2Icon('extraTokenHtml')">Extra Token Name<span
|
||||||
|
class="tokenIconDiv" [innerHTML]="model.extraTokenHtml"></span></label>
|
||||||
|
<input kendoTextBox [(ngModel)]="model.extraTokenName" name="extraTokenName"
|
||||||
|
class="k-input" placeholder="Enter extra token name" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label g-cursor-pointer"
|
||||||
|
(click)="showInsertMD2Icon('extraTokenHtml2')">Extra Token Name 2<span
|
||||||
|
class="tokenIconDiv" [innerHTML]="model.extraTokenHtml2"></span></label>
|
||||||
|
<input kendoTextBox [(ngModel)]="model.extraTokenName2" name="extraTokenName2"
|
||||||
|
class="k-input" placeholder="Enter extra token name 2" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">Prerequisite</label>
|
||||||
|
<md2-html-editor [(ngModel)]="model.prerequisite" name="prerequisite"
|
||||||
|
class="htmlEditor"></md2-html-editor>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">Objective</label>
|
||||||
|
<md2-html-editor [(ngModel)]="model.objective" name="objective"
|
||||||
|
class="htmlEditor"></md2-html-editor>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">Special Rules</label>
|
||||||
|
<md2-html-editor [(ngModel)]="model.specialRules" name="specialRules"
|
||||||
|
class="htmlEditor"></md2-html-editor>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</ng-template>
|
||||||
|
</kendo-tabstrip-tab>
|
||||||
|
|
||||||
|
<kendo-tabstrip-tab title="Phase Buffs">
|
||||||
|
<ng-template kendoTabContent>
|
||||||
|
<div style="padding: 20px;">
|
||||||
|
<div class="phase-buffs-toolbar" style="margin-bottom: 10px;">
|
||||||
|
<button kendoButton (click)="addPhaseBuffHandler()" [primary]="true">
|
||||||
|
<span class="k-icon k-i-plus"></span> Add Phase Buff
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<kendo-grid #phaseBuffsGrid [data]="phaseBuffsData" [loading]="isLoading"
|
||||||
|
[pageSize]="phaseBuffsState.take" [skip]="phaseBuffsState.skip" [sortable]="true"
|
||||||
|
[filterable]="true" [pageable]="true" [height]="400" (remove)="removePhaseBuffHandler($event)"
|
||||||
|
(dataStateChange)="phaseBuffsState = $event; loadPhaseBuffs()">
|
||||||
|
|
||||||
|
<kendo-grid-column field="phase" title="Phase" [width]="80">
|
||||||
|
<ng-template kendoGridCellTemplate let-dataItem>
|
||||||
|
{{ dataItem.phase }}
|
||||||
|
</ng-template>
|
||||||
|
</kendo-grid-column>
|
||||||
|
|
||||||
|
<kendo-grid-column field="extraAction" title="Extra Action" [width]="100">
|
||||||
|
<ng-template kendoGridCellTemplate let-dataItem>
|
||||||
|
{{ dataItem.extraAction }}
|
||||||
|
</ng-template>
|
||||||
|
</kendo-grid-column>
|
||||||
|
|
||||||
|
<kendo-grid-column field="extraHp" title="Extra HP" [width]="80">
|
||||||
|
<ng-template kendoGridCellTemplate let-dataItem>
|
||||||
|
{{ dataItem.extraHp }}
|
||||||
|
</ng-template>
|
||||||
|
</kendo-grid-column>
|
||||||
|
|
||||||
|
<kendo-grid-column field="extraTokenCount" title="Extra Token Count" [width]="120">
|
||||||
|
<ng-template kendoGridCellTemplate let-dataItem>
|
||||||
|
{{ dataItem.extraTokenCount }}
|
||||||
|
</ng-template>
|
||||||
|
</kendo-grid-column>
|
||||||
|
|
||||||
|
<kendo-grid-column field="extraTokenCount2" title="Extra Token Count 2" [width]="140">
|
||||||
|
<ng-template kendoGridCellTemplate let-dataItem>
|
||||||
|
{{ dataItem.extraTokenCount2 }}
|
||||||
|
</ng-template>
|
||||||
|
</kendo-grid-column>
|
||||||
|
|
||||||
|
<kendo-grid-column field="extraBuffDescription" title="Extra Buff Description" [width]="300">
|
||||||
|
<ng-template kendoGridCellTemplate let-dataItem>
|
||||||
|
<div *ngIf="dataItem.enableExtraBuffDescription"
|
||||||
|
[innerHTML]="dataItem.extraBuffDescription">
|
||||||
|
</div>
|
||||||
|
<span *ngIf="!dataItem.enableExtraBuffDescription">-</span>
|
||||||
|
</ng-template>
|
||||||
|
</kendo-grid-column>
|
||||||
|
|
||||||
|
<kendo-grid-command-column title="Actions" [width]="133">
|
||||||
|
<ng-template kendoGridCellTemplate let-isNew="isNew" let-dataItem="dataItem"
|
||||||
|
let-rowIndex="rowIndex">
|
||||||
|
<button kendoButton [primary]="true"
|
||||||
|
(click)="editPhaseBuffHandler(dataItem)">Edit</button>
|
||||||
|
<button kendoGridRemoveCommand>Remove</button>
|
||||||
|
</ng-template>
|
||||||
|
</kendo-grid-command-column>
|
||||||
|
</kendo-grid>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</kendo-tabstrip-tab>
|
||||||
|
</kendo-tabstrip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<kendo-dialog-actions>
|
||||||
|
<button kendoButton (click)="close()">Cancel</button>
|
||||||
|
<button kendoButton [primary]="true" (click)="save()" [disabled]="!isValid || processing">
|
||||||
|
{{ processing ? 'Saving...' : 'Save' }}
|
||||||
|
</button>
|
||||||
|
</kendo-dialog-actions>
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
// Boss Fight Editor styles
|
||||||
|
.tokenIconDiv {
|
||||||
|
margin-left: 5px;
|
||||||
|
font-size: 30px;
|
||||||
|
img {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
}
|
||||||
+216
@@ -0,0 +1,216 @@
|
|||||||
|
import { Component, Input, OnInit, ViewChild } from '@angular/core';
|
||||||
|
import { DialogRef, DialogContentBase, DialogService } from '@progress/kendo-angular-dialog';
|
||||||
|
import { GridComponent, GridDataResult } from '@progress/kendo-angular-grid';
|
||||||
|
import { State } from '@progress/kendo-data-query';
|
||||||
|
import { first } from 'rxjs/operators';
|
||||||
|
import { BossFightProfile, BossFightPhaseBuff } from '../../massive-darkness2.db.model';
|
||||||
|
import { MobSkillType } from '../../massive-darkness2.model.boss';
|
||||||
|
import { MD2BossFightProfileService, MD2PhaseBuffService } from '../../service/massive-darkness2.service';
|
||||||
|
import { MsgBoxService } from '../../../../services/msg-box.service';
|
||||||
|
import { MD2PhaseBuffEditorComponent } from '../md2-phase-buff-editor/md2-phase-buff-editor.component';
|
||||||
|
import { MD2IconPickerDlgComponent } from '../../md2-html-editor/md2-icon-picker-dlg.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ngx-md2-boss-fight-editor',
|
||||||
|
templateUrl: './md2-boss-fight-editor.component.html',
|
||||||
|
styleUrls: ['./md2-boss-fight-editor.component.scss']
|
||||||
|
})
|
||||||
|
export class MD2BossFightEditorComponent extends DialogContentBase implements OnInit {
|
||||||
|
@Input() public data: BossFightProfile;
|
||||||
|
@Input() public mobInfoId: string;
|
||||||
|
@ViewChild('phaseBuffsGrid') phaseBuffsGrid: GridComponent;
|
||||||
|
|
||||||
|
public model: BossFightProfile;
|
||||||
|
public phaseBuffs: BossFightPhaseBuff[] = [];
|
||||||
|
public phaseBuffsData: GridDataResult = { data: [], total: 0 };
|
||||||
|
public phaseBuffsState: State = {
|
||||||
|
skip: 0,
|
||||||
|
take: 10,
|
||||||
|
sort: [],
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
public isLoading: boolean = false;
|
||||||
|
public processing: boolean = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public dialog: DialogRef,
|
||||||
|
private dialogService: DialogService,
|
||||||
|
private bossFightProfileService: MD2BossFightProfileService,
|
||||||
|
private phaseBuffService: MD2PhaseBuffService,
|
||||||
|
private msgBoxService: MsgBoxService
|
||||||
|
) {
|
||||||
|
super(dialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.initializeModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public initializeModel(): void {
|
||||||
|
this.model = {
|
||||||
|
id: this.data?.id || '',
|
||||||
|
mobInfoId: this.mobInfoId || this.data?.mobInfoId || '',
|
||||||
|
prerequisite: this.data?.prerequisite || '',
|
||||||
|
objective: this.data?.objective || '',
|
||||||
|
specialRules: this.data?.specialRules || '',
|
||||||
|
extraTokenName: this.data?.extraTokenName || '',
|
||||||
|
extraTokenHtml: this.data?.extraTokenHtml || '',
|
||||||
|
extraTokenName2: this.data?.extraTokenName2 || '',
|
||||||
|
extraTokenHtml2: this.data?.extraTokenHtml2 || '',
|
||||||
|
phaseBuffs: this.data?.phaseBuffs || []
|
||||||
|
};
|
||||||
|
|
||||||
|
this.phaseBuffs = this.model.phaseBuffs || [];
|
||||||
|
this.loadPhaseBuffs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public loadPhaseBuffs(): void {
|
||||||
|
this.phaseBuffsData = {
|
||||||
|
data: this.phaseBuffs.sort((a, b) => a.phase - b.phase),
|
||||||
|
total: this.phaseBuffs.length
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public addPhaseBuffHandler(): void {
|
||||||
|
if (!this.model) return;
|
||||||
|
|
||||||
|
const lastPhaseBuff = this.phaseBuffs.length > 0
|
||||||
|
? this.phaseBuffs.reduce((prev, current) => (prev.phase > current.phase) ? prev : current)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const nextPhase = lastPhaseBuff ? lastPhaseBuff.phase + 1 : 1;
|
||||||
|
|
||||||
|
const newPhaseBuff: BossFightPhaseBuff = {
|
||||||
|
id: this.generatePhaseBuffId(),
|
||||||
|
bossFightProfileId: this.model.id,
|
||||||
|
phase: nextPhase,
|
||||||
|
extraAction: 0,
|
||||||
|
extraAttackDice: { type: MobSkillType.Attack, yellow: null, orange: null, red: null, blue: null, green: null, black: null },
|
||||||
|
extraDefenceDice: { type: MobSkillType.Defense, yellow: null, orange: null, red: null, blue: null, green: null, black: null },
|
||||||
|
extraHp: 0,
|
||||||
|
extraTokenCount: 0,
|
||||||
|
extraTokenCount2: 0,
|
||||||
|
enableExtraBuffDescription: false,
|
||||||
|
extraBuffDescription: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
this.openPhaseBuffEditor(newPhaseBuff, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public editPhaseBuffHandler(dataItem: BossFightPhaseBuff): void {
|
||||||
|
if (!this.model) return;
|
||||||
|
|
||||||
|
const phaseBuffCopy: BossFightPhaseBuff = JSON.parse(JSON.stringify(dataItem));
|
||||||
|
this.openPhaseBuffEditor(phaseBuffCopy, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private openPhaseBuffEditor(phaseBuff: BossFightPhaseBuff, isNew: boolean): void {
|
||||||
|
if (!this.model) return;
|
||||||
|
|
||||||
|
const dialogRef = this.dialogService.open({
|
||||||
|
title: isNew ? 'Add New Phase Buff' : 'Edit Phase Buff',
|
||||||
|
content: MD2PhaseBuffEditorComponent,
|
||||||
|
width: '80vw',
|
||||||
|
height: 700
|
||||||
|
});
|
||||||
|
|
||||||
|
const editor = dialogRef.content.instance as MD2PhaseBuffEditorComponent;
|
||||||
|
editor.isAdding = isNew;
|
||||||
|
editor.data = phaseBuff;
|
||||||
|
editor.bossFightProfileId = this.model.id;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
editor.initializeModel();
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
dialogRef.result.subscribe(result => {
|
||||||
|
if (result && typeof result === 'object' && 'id' in result) {
|
||||||
|
this.handlePhaseBuffSaved(result as BossFightPhaseBuff, isNew);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private handlePhaseBuffSaved(result: BossFightPhaseBuff, isNew: boolean): void {
|
||||||
|
if (!this.model) return;
|
||||||
|
|
||||||
|
if (isNew) {
|
||||||
|
if (!this.phaseBuffs) {
|
||||||
|
this.phaseBuffs = [];
|
||||||
|
}
|
||||||
|
this.phaseBuffs.push(result);
|
||||||
|
} else {
|
||||||
|
const index = this.phaseBuffs.findIndex(p => p.id === result.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.phaseBuffs[index] = result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.model.phaseBuffs = this.phaseBuffs;
|
||||||
|
this.loadPhaseBuffs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public removePhaseBuffHandler({ dataItem }: { dataItem: BossFightPhaseBuff }): void {
|
||||||
|
this.msgBoxService.showConfirmDeleteBox().pipe(first()).subscribe(answer => {
|
||||||
|
if (answer === true) {
|
||||||
|
this.isLoading = true;
|
||||||
|
this.phaseBuffService.delete(dataItem.id).pipe(first()).subscribe(result => {
|
||||||
|
const index = this.phaseBuffs.findIndex(p => p.id === dataItem.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.phaseBuffs.splice(index, 1);
|
||||||
|
this.model.phaseBuffs = this.phaseBuffs;
|
||||||
|
this.loadPhaseBuffs();
|
||||||
|
}
|
||||||
|
this.isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private generatePhaseBuffId(): string {
|
||||||
|
return 'phasebuff_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
||||||
|
}
|
||||||
|
|
||||||
|
public close(): void {
|
||||||
|
this.dialog.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public save(): void {
|
||||||
|
if (!this.processing && this.model) {
|
||||||
|
this.processing = true;
|
||||||
|
|
||||||
|
this.model.phaseBuffs = this.phaseBuffs;
|
||||||
|
this.model.mobInfoId = this.mobInfoId || this.model.mobInfoId;
|
||||||
|
|
||||||
|
this.bossFightProfileService.createOrUpdate(this.model).pipe(first()).subscribe(result => {
|
||||||
|
this.processing = false;
|
||||||
|
this.dialog.close(result);
|
||||||
|
}, error => {
|
||||||
|
this.processing = false;
|
||||||
|
console.error('Error saving boss fight profile:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
showInsertMD2Icon(attributeName: string) {
|
||||||
|
this.dialogService.open({
|
||||||
|
title: 'Select MD2 Icon',
|
||||||
|
content: MD2IconPickerDlgComponent,
|
||||||
|
width: '800px',
|
||||||
|
height: 600
|
||||||
|
}).result.subscribe((html: string) => {
|
||||||
|
if (html && typeof html === 'string') {
|
||||||
|
this.model[attributeName] = html;
|
||||||
|
} else {
|
||||||
|
this.model[attributeName] = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public get isValid(): boolean {
|
||||||
|
if (!this.model) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this.model.mobInfoId !== '';
|
||||||
|
}
|
||||||
|
}
|
||||||
+188
@@ -0,0 +1,188 @@
|
|||||||
|
<div class="k-dialog-content detail-container">
|
||||||
|
<div class="mob-info-section">
|
||||||
|
<h3>{{ mobInfo?.name }}</h3>
|
||||||
|
<div class="info-grid">
|
||||||
|
<div class="info-item">
|
||||||
|
<label>Type:</label>
|
||||||
|
<span>{{ getMobTypeName(mobInfo?.type) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<label>Game Bundle:</label>
|
||||||
|
<span>{{ getGameBundleName(mobInfo?.from) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<label>Leader Image:</label>
|
||||||
|
<span>{{ mobInfo?.leaderImgUrl || 'N/A' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<label>Minion Image:</label>
|
||||||
|
<span>{{ mobInfo?.minionImgUrl || 'N/A' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<kendo-tabstrip tabPosition="top">
|
||||||
|
<kendo-tabstrip-tab title="Levels" [selected]="true">
|
||||||
|
<ng-template kendoTabContent>
|
||||||
|
<div class="levels-toolbar">
|
||||||
|
<button kendoButton (click)="addLevelHandler()" [primary]="true">
|
||||||
|
<span class="k-icon k-i-plus"></span> Add Level
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<kendo-grid #levelsGrid [data]="mobLevelInfos" [loading]="isLoading" [pageSize]="levelsState.take"
|
||||||
|
[skip]="levelsState.skip" [sortable]="true" [filterable]="true" [pageable]="true" [height]="300"
|
||||||
|
(remove)="removeLevelHandler($event)" (dataStateChange)="levelsState = $event">
|
||||||
|
|
||||||
|
<kendo-grid-column field="level" title="Level" [width]="50">
|
||||||
|
<ng-template kendoGridCellTemplate let-dataItem>
|
||||||
|
{{ dataItem.level }}
|
||||||
|
</ng-template>
|
||||||
|
</kendo-grid-column>
|
||||||
|
|
||||||
|
<!-- <kendo-grid-column field="fixedHp" title="HP" [width]="80">
|
||||||
|
<ng-template kendoGridCellTemplate let-dataItem>
|
||||||
|
{{ dataItem.fixedHp }}
|
||||||
|
</ng-template>
|
||||||
|
<ng-template kendoGridEditTemplate let-dataItem="dataItem" let-rowIndex="rowIndex">
|
||||||
|
<kendo-numerictextbox [(ngModel)]="dataItem.fixedHp" [name]="'fixedHp_' + rowIndex" [min]="0" [decimals]="0"
|
||||||
|
[format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</ng-template>
|
||||||
|
</kendo-grid-column> -->
|
||||||
|
|
||||||
|
<kendo-grid-column field="hpPerHero" title="HP/Hero" [width]="80">
|
||||||
|
<ng-template kendoGridCellTemplate let-dataItem>
|
||||||
|
{{ dataItem.hpPerHero }}
|
||||||
|
</ng-template>
|
||||||
|
</kendo-grid-column>
|
||||||
|
|
||||||
|
<!-- <kendo-grid-column field="actions" title="Actions" [width]="100">
|
||||||
|
<ng-template kendoGridCellTemplate let-dataItem>
|
||||||
|
{{ dataItem.actions }}
|
||||||
|
</ng-template>
|
||||||
|
<ng-template kendoGridEditTemplate let-dataItem="dataItem" let-rowIndex="rowIndex">
|
||||||
|
<kendo-numerictextbox [(ngModel)]="dataItem.actions" [name]="'actions_' + rowIndex" [min]="0" [decimals]="0"
|
||||||
|
[format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</ng-template>
|
||||||
|
</kendo-grid-column> -->
|
||||||
|
|
||||||
|
<kendo-grid-column field="rewardTokens" title="Reward Tokens" [width]="80">
|
||||||
|
<ng-template kendoGridCellTemplate let-dataItem>
|
||||||
|
{{ dataItem.rewardTokens }}
|
||||||
|
</ng-template>
|
||||||
|
</kendo-grid-column>
|
||||||
|
|
||||||
|
<kendo-grid-column field="fixedRareTreasure" title="Rare Treasure" [width]="80">
|
||||||
|
<ng-template kendoGridCellTemplate let-dataItem>
|
||||||
|
{{ dataItem.fixedRareTreasure }}
|
||||||
|
</ng-template>
|
||||||
|
</kendo-grid-column>
|
||||||
|
|
||||||
|
<kendo-grid-column field="fixedEpicTreasure" title="Epic Treasure" [width]="80">
|
||||||
|
<ng-template kendoGridCellTemplate let-dataItem>
|
||||||
|
{{ dataItem.fixedEpicTreasure }}
|
||||||
|
</ng-template>
|
||||||
|
</kendo-grid-column>
|
||||||
|
|
||||||
|
<kendo-grid-column field="fixedLegendTreasure" title="Legend Treasure" [width]="60">
|
||||||
|
<ng-template kendoGridCellTemplate let-dataItem>
|
||||||
|
{{ dataItem.fixedLegendTreasure }}
|
||||||
|
</ng-template>
|
||||||
|
</kendo-grid-column>
|
||||||
|
<!-- result.defenceInfo.blue
|
||||||
|
result.defenceInfo.green -->
|
||||||
|
<kendo-grid-column field="defenceInfo.blue" title="Blue Dice" [width]="60">
|
||||||
|
<ng-template kendoGridCellTemplate let-dataItem>
|
||||||
|
{{ dataItem.defenceInfo?.blue }}
|
||||||
|
</ng-template>
|
||||||
|
</kendo-grid-column>
|
||||||
|
|
||||||
|
<kendo-grid-command-column title="Actions" [width]="133">
|
||||||
|
<ng-template kendoGridCellTemplate let-isNew="isNew" let-dataItem="dataItem" let-rowIndex="rowIndex">
|
||||||
|
<button kendoButton [primary]="true" (click)="editLevelHandler(dataItem)">Edit</button>
|
||||||
|
<button kendoGridRemoveCommand>Remove</button>
|
||||||
|
</ng-template>
|
||||||
|
</kendo-grid-command-column>
|
||||||
|
</kendo-grid>
|
||||||
|
</ng-template>
|
||||||
|
</kendo-tabstrip-tab>
|
||||||
|
<kendo-tabstrip-tab title="Skills">
|
||||||
|
<ng-template kendoTabContent>
|
||||||
|
<div class="skills-toolbar">
|
||||||
|
<button kendoButton (click)="addSkillHandler()" [primary]="true">
|
||||||
|
<span class="k-icon k-i-plus"></span> Add Skill
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<kendo-grid #skillsGrid [data]="skillsData" [loading]="isLoading" [pageSize]="skillsState.take"
|
||||||
|
[skip]="skillsState.skip" [sortable]="true" [filterable]="true" [pageable]="true" [height]="400"
|
||||||
|
(remove)="removeSkillHandler($event)" (dataStateChange)="skillsState = $event">
|
||||||
|
|
||||||
|
|
||||||
|
<kendo-grid-column field="seq" title="Seq" [width]="80">
|
||||||
|
<ng-template kendoGridCellTemplate let-dataItem>
|
||||||
|
{{ dataItem.seq }}
|
||||||
|
</ng-template>
|
||||||
|
</kendo-grid-column>
|
||||||
|
<kendo-grid-column field="level" title="Level" [width]="80">
|
||||||
|
<ng-template kendoGridCellTemplate let-dataItem>
|
||||||
|
{{ dataItem.level }}
|
||||||
|
</ng-template>
|
||||||
|
</kendo-grid-column>
|
||||||
|
<kendo-grid-column field="name" title="Name" [width]="150">
|
||||||
|
<ng-template kendoGridCellTemplate let-dataItem>
|
||||||
|
{{ dataItem.name }}
|
||||||
|
</ng-template>
|
||||||
|
</kendo-grid-column>
|
||||||
|
|
||||||
|
<kendo-grid-column field="type" title="Type" [width]="120">
|
||||||
|
<ng-template kendoGridCellTemplate let-dataItem>
|
||||||
|
{{ getSkillTypeName(dataItem.type) }}
|
||||||
|
</ng-template>
|
||||||
|
</kendo-grid-column>
|
||||||
|
|
||||||
|
<kendo-grid-column field="skillTarget" title="Target" [width]="150">
|
||||||
|
<ng-template kendoGridCellTemplate let-dataItem>
|
||||||
|
{{ getSkillTargetName(dataItem.skillTarget) }}
|
||||||
|
</ng-template>
|
||||||
|
</kendo-grid-column>
|
||||||
|
|
||||||
|
<kendo-grid-column field="skillRoll" title="Skill Roll" [width]="100">
|
||||||
|
<ng-template kendoGridCellTemplate let-dataItem>
|
||||||
|
{{ dataItem.skillRoll }}
|
||||||
|
</ng-template>
|
||||||
|
</kendo-grid-column>
|
||||||
|
|
||||||
|
<kendo-grid-column field="clawRoll" title="Claw Roll" [width]="100">
|
||||||
|
<ng-template kendoGridCellTemplate let-dataItem>
|
||||||
|
{{ dataItem.clawRoll }}
|
||||||
|
</ng-template>
|
||||||
|
</kendo-grid-column>
|
||||||
|
|
||||||
|
|
||||||
|
<kendo-grid-column field="description" title="Description" [width]="300">
|
||||||
|
<ng-template kendoGridCellTemplate let-dataItem>
|
||||||
|
<div *ngIf="dataItem.type == MobSkillType.ConditionalSkill" [innerHTML]="dataItem.skillCondition"></div>
|
||||||
|
<div [innerHTML]="dataItem.description"></div>
|
||||||
|
</ng-template>
|
||||||
|
</kendo-grid-column>
|
||||||
|
|
||||||
|
<kendo-grid-command-column title="Actions" [width]="133">
|
||||||
|
<ng-template kendoGridCellTemplate let-isNew="isNew" let-dataItem="dataItem" let-rowIndex="rowIndex">
|
||||||
|
<button kendoButton [primary]="true" (click)="editSkillHandler(dataItem)">Edit</button>
|
||||||
|
<button kendoGridRemoveCommand>Remove</button>
|
||||||
|
</ng-template>
|
||||||
|
</kendo-grid-command-column>
|
||||||
|
</kendo-grid>
|
||||||
|
</ng-template>
|
||||||
|
</kendo-tabstrip-tab>
|
||||||
|
</kendo-tabstrip>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<kendo-dialog-actions>
|
||||||
|
<button kendoButton *ngIf="mobInfo?.type === MobType.Boss" (click)="editBossFightHandler()">Edit Boss Fight</button>
|
||||||
|
<button kendoButton [primary]="true" (click)="close()">Close</button>
|
||||||
|
</kendo-dialog-actions>
|
||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
.k-dialog-content {
|
||||||
|
padding: 20px;
|
||||||
|
max-height: 80vh;
|
||||||
|
//overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mob-info-section {
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0 0 15px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-grid {
|
||||||
|
display: grd;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-weight: 500;
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.levels-section {
|
||||||
|
h4 {
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.levels-toolbar {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skills-section {
|
||||||
|
h4 {
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.skills-toolbar {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
+492
@@ -0,0 +1,492 @@
|
|||||||
|
import { Component, Input, OnInit, ViewChild } from '@angular/core';
|
||||||
|
import { DialogRef, DialogContentBase, DialogService } from '@progress/kendo-angular-dialog';
|
||||||
|
import { GridComponent, GridDataResult } from '@progress/kendo-angular-grid';
|
||||||
|
import { State } from '@progress/kendo-data-query';
|
||||||
|
import { first } from 'rxjs/operators';
|
||||||
|
import { MD2MobInfo, MD2MobLevelInfo, MD2MobSkill, MobSkillTarget, GameBundle, BossFightProfile } from '../../massive-darkness2.db.model';
|
||||||
|
import { MobType } from '../../massive-darkness2.model';
|
||||||
|
import { MobSkillType } from '../../massive-darkness2.model.boss';
|
||||||
|
import { MD2MobLevelInfoService, MD2MobSkillService } from '../../service/massive-darkness2.service';
|
||||||
|
import { MsgBoxService } from '../../../../services/msg-box.service';
|
||||||
|
import { MD2MobSkillEditorComponent } from '../md2-mob-skill-editor/md2-mob-skill-editor.component';
|
||||||
|
import { MD2MobLevelEditorComponent } from '../md2-mob-level-editor/md2-mob-level-editor.component';
|
||||||
|
import { MD2BossFightEditorComponent } from '../md2-boss-fight-editor/md2-boss-fight-editor.component';
|
||||||
|
import { MD2MobInfoService } from '../../service/massive-darkness2.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ngx-md2-mob-info-detail',
|
||||||
|
templateUrl: './md2-mob-info-detail.component.html',
|
||||||
|
styleUrls: ['./md2-mob-info-detail.component.scss']
|
||||||
|
})
|
||||||
|
export class MD2MobInfoDetailComponent extends DialogContentBase implements OnInit {
|
||||||
|
MobSkillType = MobSkillType;
|
||||||
|
MobType = MobType;
|
||||||
|
@Input() public mobInfo: MD2MobInfo;
|
||||||
|
@ViewChild('levelsGrid') levelsGrid: GridComponent;
|
||||||
|
@ViewChild('skillsGrid') skillsGrid: GridComponent;
|
||||||
|
|
||||||
|
public mobLevelInfos: MD2MobLevelInfo[] = [];
|
||||||
|
public levelsData: GridDataResult = { data: [], total: 0 };
|
||||||
|
public levelsState: State = {
|
||||||
|
skip: 0,
|
||||||
|
take: 10,
|
||||||
|
sort: [],
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
public selectedLevelInfo: MD2MobLevelInfo | null = null;
|
||||||
|
public skillsData: GridDataResult = { data: [], total: 0 };
|
||||||
|
public skillsState: State = {
|
||||||
|
skip: 0,
|
||||||
|
take: 10,
|
||||||
|
sort: [],
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
public isLoading: boolean = false;
|
||||||
|
|
||||||
|
public skillTypes: Array<{ value: MobSkillType; text: string }> = [];
|
||||||
|
public skillTargets: Array<{ value: MobSkillTarget | null; text: string }> = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public dialog: DialogRef,
|
||||||
|
private dialogService: DialogService,
|
||||||
|
private mobLevelInfoService: MD2MobLevelInfoService,
|
||||||
|
private mobSkillService: MD2MobSkillService,
|
||||||
|
private mobInfoService: MD2MobInfoService,
|
||||||
|
private msgBoxService: MsgBoxService
|
||||||
|
) {
|
||||||
|
super(dialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
if (this.mobInfo) {
|
||||||
|
this.mobLevelInfos = this.mobInfo.mobLevelInfos || [];
|
||||||
|
if (this.mobLevelInfos.length > 0) {
|
||||||
|
this.selectLevelInfo(this.mobLevelInfos[0]);
|
||||||
|
}
|
||||||
|
this.loadSkills();
|
||||||
|
}
|
||||||
|
this.initializeEnums();
|
||||||
|
}
|
||||||
|
|
||||||
|
private initializeEnums(): void {
|
||||||
|
// Initialize MobSkillType options
|
||||||
|
Object.keys(MobSkillType).filter(key => isNaN(Number(key))).forEach(key => {
|
||||||
|
this.skillTypes.push({
|
||||||
|
value: MobSkillType[key] as MobSkillType,
|
||||||
|
text: key
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize MobSkillTarget options
|
||||||
|
this.skillTargets.push({ value: null, text: 'None' });
|
||||||
|
Object.keys(MobSkillTarget).filter(key => isNaN(Number(key))).forEach(key => {
|
||||||
|
this.skillTargets.push({
|
||||||
|
value: MobSkillTarget[key] as MobSkillTarget,
|
||||||
|
text: key
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public selectLevelInfo(levelInfo: MD2MobLevelInfo): void {
|
||||||
|
if (levelInfo.defenceInfo == null) {
|
||||||
|
levelInfo.defenceInfo = { type: MobSkillType.Defense, blue: 0, green: 0, yellow: 0, orange: 0, red: 0, black: 0 };
|
||||||
|
}
|
||||||
|
if (levelInfo.attackInfo == null) {
|
||||||
|
levelInfo.attackInfo = { type: MobSkillType.Attack, blue: 0, green: 0, yellow: 0, orange: 0, red: 0, black: 0 };
|
||||||
|
}
|
||||||
|
this.selectedLevelInfo = levelInfo;
|
||||||
|
this.loadSkills();
|
||||||
|
}
|
||||||
|
|
||||||
|
public editLevelHandler(dataItem: MD2MobLevelInfo): void {
|
||||||
|
if (!this.mobInfo) return;
|
||||||
|
|
||||||
|
// Create a copy of the level info for editing
|
||||||
|
const levelCopy: MD2MobLevelInfo = JSON.parse(JSON.stringify(dataItem));
|
||||||
|
this.openLevelEditor(levelCopy, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public addLevelHandler(): void {
|
||||||
|
if (!this.mobInfo) return;
|
||||||
|
|
||||||
|
// Get the last level info (highest level) if any exists
|
||||||
|
const lastLevel = this.mobLevelInfos.length > 0
|
||||||
|
? this.mobLevelInfos.reduce((prev, current) => (prev.level > current.level) ? prev : current)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
// Calculate the next level number
|
||||||
|
const nextLevel = lastLevel ? lastLevel.level + 2 : 1;
|
||||||
|
let rewardTokens = 0;
|
||||||
|
let actions = 1;
|
||||||
|
|
||||||
|
|
||||||
|
let newLevel: MD2MobLevelInfo;
|
||||||
|
|
||||||
|
if (lastLevel) {
|
||||||
|
// Copy the last level's info as initial values
|
||||||
|
newLevel = {
|
||||||
|
id: this.generateLevelId(),
|
||||||
|
level: nextLevel,
|
||||||
|
mobInfoId: this.mobInfo.id,
|
||||||
|
rewardTokens: rewardTokens,
|
||||||
|
fixedRareTreasure: lastLevel.fixedRareTreasure ?? 0,
|
||||||
|
fixedEpicTreasure: lastLevel.fixedEpicTreasure ?? 0,
|
||||||
|
fixedLegendTreasure: lastLevel.fixedLegendTreasure ?? 0,
|
||||||
|
fixedHp: lastLevel.fixedHp ?? 0,
|
||||||
|
hpPerHero: lastLevel.hpPerHero ?? 0,
|
||||||
|
actions: lastLevel.actions ?? actions,
|
||||||
|
attackInfo: lastLevel.attackInfo
|
||||||
|
? { ...lastLevel.attackInfo }
|
||||||
|
: { type: MobSkillType.Attack, yellow: null, orange: null, red: null, blue: null, green: null, black: null },
|
||||||
|
alterAttackInfo: lastLevel.alterAttackInfo
|
||||||
|
? { ...lastLevel.alterAttackInfo }
|
||||||
|
: null,
|
||||||
|
defenceInfo: lastLevel.defenceInfo
|
||||||
|
? { ...lastLevel.defenceInfo }
|
||||||
|
: { type: MobSkillType.Defense, yellow: null, orange: null, red: null, blue: null, green: null, black: null }
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Default values if no levels exist
|
||||||
|
newLevel = {
|
||||||
|
id: this.generateLevelId(),
|
||||||
|
level: nextLevel,
|
||||||
|
mobInfoId: this.mobInfo.id,
|
||||||
|
rewardTokens: rewardTokens,
|
||||||
|
fixedRareTreasure: 0,
|
||||||
|
fixedEpicTreasure: 0,
|
||||||
|
fixedLegendTreasure: 0,
|
||||||
|
fixedHp: 0,
|
||||||
|
hpPerHero: 0,
|
||||||
|
actions: actions,
|
||||||
|
attackInfo: { type: MobSkillType.Attack, yellow: null, orange: null, red: null, blue: null, green: null, black: null },
|
||||||
|
alterAttackInfo: { type: MobSkillType.Attack, yellow: null, orange: null, red: null, blue: null, green: null, black: null },
|
||||||
|
defenceInfo: { type: MobSkillType.Defense, yellow: null, orange: null, red: null, blue: null, green: null, black: null }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (this.mobInfo.type) {
|
||||||
|
case MobType.Mob:
|
||||||
|
newLevel.actions = 2;
|
||||||
|
|
||||||
|
switch (nextLevel) {
|
||||||
|
case 1:
|
||||||
|
case 3:
|
||||||
|
newLevel.rewardTokens = 1;
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
newLevel.rewardTokens = 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MobType.Boss:
|
||||||
|
newLevel.actions = 1;
|
||||||
|
break;
|
||||||
|
case MobType.RoamingMonster:
|
||||||
|
newLevel.actions = 1;
|
||||||
|
|
||||||
|
switch (nextLevel) {
|
||||||
|
case 1:
|
||||||
|
newLevel.rewardTokens = 2;
|
||||||
|
newLevel.fixedRareTreasure = 1;
|
||||||
|
newLevel.fixedEpicTreasure = 0;
|
||||||
|
newLevel.fixedLegendTreasure = 0;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
newLevel.rewardTokens = 2;
|
||||||
|
newLevel.fixedRareTreasure = 0;
|
||||||
|
newLevel.fixedEpicTreasure = 1;
|
||||||
|
newLevel.fixedLegendTreasure = 0;
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
newLevel.rewardTokens = 0;
|
||||||
|
newLevel.fixedRareTreasure = 0;
|
||||||
|
newLevel.fixedEpicTreasure = 3;
|
||||||
|
newLevel.fixedLegendTreasure = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this.openLevelEditor(newLevel, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private openLevelEditor(level: MD2MobLevelInfo, isNew: boolean): void {
|
||||||
|
if (!this.mobInfo) return;
|
||||||
|
|
||||||
|
const dialogRef = this.dialogService.open({
|
||||||
|
title: isNew ? 'Add New Level' : 'Edit Level',
|
||||||
|
content: MD2MobLevelEditorComponent,
|
||||||
|
width: '80vw',
|
||||||
|
height: 700
|
||||||
|
});
|
||||||
|
|
||||||
|
const editor = dialogRef.content.instance as MD2MobLevelEditorComponent;
|
||||||
|
editor.isAdding = isNew;
|
||||||
|
editor.data = level;
|
||||||
|
editor.mobInfoId = this.mobInfo.id;
|
||||||
|
editor.mobType = this.mobInfo.type;
|
||||||
|
|
||||||
|
// Force model re-initialization after data is set
|
||||||
|
setTimeout(() => {
|
||||||
|
editor.initializeModel();
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
dialogRef.result.subscribe(result => {
|
||||||
|
if (result && typeof result === 'object' && 'id' in result) {
|
||||||
|
this.handleLevelSaved(result as MD2MobLevelInfo, isNew);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleLevelSaved(result: MD2MobLevelInfo, isNew: boolean): void {
|
||||||
|
if (!this.mobInfo) return;
|
||||||
|
|
||||||
|
if (isNew) {
|
||||||
|
if (!this.mobInfo.mobLevelInfos) {
|
||||||
|
this.mobInfo.mobLevelInfos = [];
|
||||||
|
}
|
||||||
|
result.attackInfo.black = 0;
|
||||||
|
this.mobLevelInfos.push(result);
|
||||||
|
} else {
|
||||||
|
const index = this.mobLevelInfos.findIndex(l => l.id === result.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.mobLevelInfos[index] = result;
|
||||||
|
// Update selected level if it's the one being edited
|
||||||
|
if (this.selectedLevelInfo?.id === result.id) {
|
||||||
|
this.selectedLevelInfo = result;
|
||||||
|
this.loadSkills();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeLevelHandler({ dataItem }: { dataItem: MD2MobLevelInfo }): void {
|
||||||
|
this.msgBoxService.showConfirmDeleteBox().pipe(first()).subscribe(answer => {
|
||||||
|
if (answer === true) {
|
||||||
|
this.isLoading = true;
|
||||||
|
this.mobLevelInfoService.delete(dataItem.id).pipe(first()).subscribe(result => {
|
||||||
|
const index = this.mobLevelInfos.findIndex(l => l.id === dataItem.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.mobLevelInfos.splice(index, 1);
|
||||||
|
// Clear selection if deleted level was selected
|
||||||
|
if (this.selectedLevelInfo?.id === dataItem.id) {
|
||||||
|
this.selectedLevelInfo = null;
|
||||||
|
this.loadSkills();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public loadSkills(): void {
|
||||||
|
if (this.mobInfo && this.mobInfo.skills) {
|
||||||
|
// Filter skills by selected level if a level is selected
|
||||||
|
let filteredSkills = this.mobInfo.skills.sort((a, b) => a.seq - b.seq);
|
||||||
|
// if (this.selectedLevelInfo) {
|
||||||
|
// filteredSkills = this.mobInfo.skills.filter(s => s.level === this.selectedLevelInfo.level);
|
||||||
|
// }
|
||||||
|
this.skillsData = {
|
||||||
|
data: filteredSkills,
|
||||||
|
total: filteredSkills.length
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
this.skillsData = { data: [], total: 0 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public addSkillHandler(): void {
|
||||||
|
if (!this.mobInfo) return;
|
||||||
|
|
||||||
|
// Get the last level info (highest level) if any exists
|
||||||
|
const lastLevel = this.mobInfo.skills.length > 0
|
||||||
|
? this.mobInfo.skills.reduce((prev, current) => (prev.seq > current.seq) ? prev : current)
|
||||||
|
: null;
|
||||||
|
let seq = 0;
|
||||||
|
let level = 1;
|
||||||
|
let type = MobSkillType.Combat;
|
||||||
|
|
||||||
|
if (lastLevel) {
|
||||||
|
seq = lastLevel.seq + 1;
|
||||||
|
level = lastLevel.level + 2;
|
||||||
|
type = lastLevel.type;
|
||||||
|
}
|
||||||
|
const newSkill: MD2MobSkill = {
|
||||||
|
id: this.generateId(),
|
||||||
|
seq: seq,
|
||||||
|
level: level,
|
||||||
|
mobInfoId: this.mobInfo.id,
|
||||||
|
type: type,
|
||||||
|
skillTarget: lastLevel?.skillTarget || MobSkillTarget.Random,
|
||||||
|
clawRoll: lastLevel?.clawRoll || 0,
|
||||||
|
skillRoll: lastLevel?.skillRoll || 1,
|
||||||
|
name: lastLevel?.name || 'Combat Skill',
|
||||||
|
skillCondition: lastLevel?.skillCondition || '',
|
||||||
|
description: lastLevel?.description || ''
|
||||||
|
};
|
||||||
|
|
||||||
|
this.openSkillEditor(newSkill, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public editSkillHandler(dataItem: MD2MobSkill): void {
|
||||||
|
if (!this.mobInfo) return;
|
||||||
|
|
||||||
|
// Create a copy of the skill for editing
|
||||||
|
const skillCopy: MD2MobSkill = JSON.parse(JSON.stringify(dataItem));
|
||||||
|
this.openSkillEditor(skillCopy, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private openSkillEditor(skill: MD2MobSkill, isNew: boolean): void {
|
||||||
|
if (!this.mobInfo) return;
|
||||||
|
|
||||||
|
const dialogRef = this.dialogService.open({
|
||||||
|
title: isNew ? 'Add New Skill' : 'Edit Skill',
|
||||||
|
content: MD2MobSkillEditorComponent,
|
||||||
|
width: '80vw',
|
||||||
|
height: 700
|
||||||
|
});
|
||||||
|
|
||||||
|
const editor = dialogRef.content.instance;
|
||||||
|
editor.isAdding = isNew;
|
||||||
|
editor.data = skill;
|
||||||
|
editor.mobInfoId = this.mobInfo.id;
|
||||||
|
editor.selectedLevel = this.selectedLevelInfo?.level || 1;
|
||||||
|
|
||||||
|
// Force model re-initialization after data is set
|
||||||
|
setTimeout(() => {
|
||||||
|
editor.initializeModel();
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
dialogRef.result.subscribe(result => {
|
||||||
|
if (result && typeof result === 'object' && 'id' in result) {
|
||||||
|
this.handleSkillSaved(result as MD2MobSkill, isNew);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleSkillSaved(result: MD2MobSkill, isNew: boolean): void {
|
||||||
|
if (!this.mobInfo) return;
|
||||||
|
|
||||||
|
if (isNew) {
|
||||||
|
if (!this.mobInfo.skills) {
|
||||||
|
this.mobInfo.skills = [];
|
||||||
|
}
|
||||||
|
this.mobInfo.skills.push(result);
|
||||||
|
} else {
|
||||||
|
const index = this.mobInfo.skills?.findIndex(s => s.id === result.id);
|
||||||
|
if (index !== undefined && index !== -1 && this.mobInfo.skills) {
|
||||||
|
this.mobInfo.skills[index] = result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.loadSkills();
|
||||||
|
}
|
||||||
|
|
||||||
|
public saveSkillHandler({ dataItem, isNew }: any): void {
|
||||||
|
// This method is no longer used but kept for backward compatibility
|
||||||
|
// Skills are now edited via dialog
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeSkillHandler({ dataItem }: { dataItem: MD2MobSkill }): void {
|
||||||
|
if (!this.mobInfo) return;
|
||||||
|
|
||||||
|
this.msgBoxService.showConfirmDeleteBox().pipe(first()).subscribe(answer => {
|
||||||
|
if (answer === true) {
|
||||||
|
this.isLoading = true;
|
||||||
|
this.mobSkillService.delete(dataItem.id).pipe(first()).subscribe(result => {
|
||||||
|
if (this.mobInfo.skills) {
|
||||||
|
const index = this.mobInfo.skills.findIndex(s => s.id === dataItem.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
this.mobInfo.skills.splice(index, 1);
|
||||||
|
}
|
||||||
|
this.loadSkills();
|
||||||
|
}
|
||||||
|
this.isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private generateId(): string {
|
||||||
|
return 'skill_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateLevelId(): string {
|
||||||
|
return 'level_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getSkillTypeName(type: MobSkillType): string {
|
||||||
|
return MobSkillType[type] || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public getSkillTargetName(target: MobSkillTarget | null): string {
|
||||||
|
if (target === null) return 'None';
|
||||||
|
return MobSkillTarget[target] || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public getMobTypeName(type: MobType): string {
|
||||||
|
return MobType[type] || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public getGameBundleName(bundle: GameBundle): string {
|
||||||
|
return GameBundle[bundle] || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public editBossFightHandler(): void {
|
||||||
|
if (!this.mobInfo || this.mobInfo.type !== MobType.Boss) return;
|
||||||
|
|
||||||
|
// Ensure bossFightProfile is initialized
|
||||||
|
if (!this.mobInfo.bossFightProfile) {
|
||||||
|
this.mobInfo.bossFightProfile = {
|
||||||
|
id: 'bossfight_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9),
|
||||||
|
mobInfoId: this.mobInfo.id,
|
||||||
|
prerequisite: '',
|
||||||
|
objective: '',
|
||||||
|
specialRules: '',
|
||||||
|
extraTokenName: '',
|
||||||
|
extraTokenHtml: '',
|
||||||
|
extraTokenName2: '',
|
||||||
|
extraTokenHtml2: '',
|
||||||
|
phaseBuffs: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a copy of the boss fight profile for editing
|
||||||
|
const bossFightProfileCopy: BossFightProfile = JSON.parse(JSON.stringify(this.mobInfo.bossFightProfile));
|
||||||
|
|
||||||
|
const dialogRef = this.dialogService.open({
|
||||||
|
title: `Edit Boss Fight: ${this.mobInfo.name}`,
|
||||||
|
content: MD2BossFightEditorComponent,
|
||||||
|
width: '90vw',
|
||||||
|
height: '90vh'
|
||||||
|
});
|
||||||
|
|
||||||
|
const editor = dialogRef.content.instance as MD2BossFightEditorComponent;
|
||||||
|
editor.data = bossFightProfileCopy;
|
||||||
|
editor.mobInfoId = this.mobInfo.id;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
editor.initializeModel();
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
dialogRef.result.subscribe(result => {
|
||||||
|
if (result && typeof result === 'object' && 'id' in result) {
|
||||||
|
// Reload mob info to get updated boss fight profile
|
||||||
|
this.mobInfoService.getById(this.mobInfo.id).pipe(first()).subscribe(mobInfo => {
|
||||||
|
this.mobInfo = mobInfo;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public close(): void {
|
||||||
|
this.dialog.close(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
<div class="k-dialog-content">
|
||||||
|
<form #form="ngForm" class="k-form">
|
||||||
|
<div class="k-form-field">
|
||||||
|
<label class="k-label">Name *</label>
|
||||||
|
<input kendoTextBox [(ngModel)]="model.name" name="name" required class="k-input"
|
||||||
|
placeholder="Enter mob name" />
|
||||||
|
<span class="k-form-error" *ngIf="form.controls['name']?.invalid && form.controls['name']?.touched">
|
||||||
|
Name is required
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="k-form-field">
|
||||||
|
<label class="k-label">Type *</label>
|
||||||
|
<kendo-dropdownlist [(ngModel)]="selectedMobType" name="type" [data]="mobTypes" [valueField]="'value'"
|
||||||
|
[textField]="'text'" [defaultItem]="{ value: null, text: 'Select type...' }">
|
||||||
|
</kendo-dropdownlist>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="k-form-field">
|
||||||
|
<label class="k-label">Game Bundle *</label>
|
||||||
|
<kendo-dropdownlist [(ngModel)]="selectedGameBundle" name="from" [data]="gameBundles" [valueField]="'value'"
|
||||||
|
[textField]="'text'" [defaultItem]="{ value: null, text: 'Select bundle...' }">
|
||||||
|
</kendo-dropdownlist>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="k-form-field">
|
||||||
|
<label class="k-label">Leader Image URL</label>
|
||||||
|
<input kendoTextBox [(ngModel)]="model.leaderImgUrl" name="leaderImgUrl" class="k-input"
|
||||||
|
placeholder="Enter leader image URL" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="k-form-field">
|
||||||
|
<label class="k-label">Minion Image URL</label>
|
||||||
|
<input kendoTextBox [(ngModel)]="model.minionImgUrl" name="minionImgUrl" class="k-input"
|
||||||
|
placeholder="Enter minion image URL" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<kendo-dialog-actions>
|
||||||
|
<button kendoButton (click)="close()">Cancel</button>
|
||||||
|
<button kendoButton [primary]="true" (click)="save()" [disabled]="!isValid || processing">
|
||||||
|
{{ processing ? 'Saving...' : 'Save' }}
|
||||||
|
</button>
|
||||||
|
</kendo-dialog-actions>
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
.k-dialog-content {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.k-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.k-form-field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.k-label {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.k-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.k-form-error {
|
||||||
|
color: red;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
+149
@@ -0,0 +1,149 @@
|
|||||||
|
import { Component, Input, OnInit, ViewChild, ChangeDetectorRef } from '@angular/core';
|
||||||
|
import { DialogRef, DialogContentBase } from '@progress/kendo-angular-dialog';
|
||||||
|
import { NgForm } from '@angular/forms';
|
||||||
|
import { first } from 'rxjs/operators';
|
||||||
|
import { MD2MobInfo, GameBundle, BossFightProfile } from '../../massive-darkness2.db.model';
|
||||||
|
import { MobType } from '../../massive-darkness2.model';
|
||||||
|
import { MD2MobInfoService } from '../../service/massive-darkness2.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ngx-md2-mob-info-editor',
|
||||||
|
templateUrl: './md2-mob-info-editor.component.html',
|
||||||
|
styleUrls: ['./md2-mob-info-editor.component.scss']
|
||||||
|
})
|
||||||
|
export class MD2MobInfoEditorComponent extends DialogContentBase implements OnInit {
|
||||||
|
@Input() public data: MD2MobInfo;
|
||||||
|
@Input() public isAdding: boolean = false;
|
||||||
|
@ViewChild('form') form: NgForm;
|
||||||
|
|
||||||
|
public model: MD2MobInfo;
|
||||||
|
public processing: boolean = false;
|
||||||
|
public mobTypes: Array<{ value: MobType; text: string }> = [];
|
||||||
|
public gameBundles: Array<{ value: GameBundle; text: string }> = [];
|
||||||
|
public selectedMobType: { value: MobType; text: string } | null = null;
|
||||||
|
public selectedGameBundle: { value: GameBundle; text: string } | null = null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public dialog: DialogRef,
|
||||||
|
private mobInfoService: MD2MobInfoService,
|
||||||
|
private cdr: ChangeDetectorRef
|
||||||
|
) {
|
||||||
|
super(dialog);
|
||||||
|
this.initializeEnums();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.initializeModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public initializeModel(): void {
|
||||||
|
const typeValue = this.data?.type !== undefined && this.data?.type !== null ? this.data.type : MobType.Mob;
|
||||||
|
const fromValue = this.data?.from !== undefined && this.data?.from !== null ? this.data.from : GameBundle.CoreGame;
|
||||||
|
|
||||||
|
this.model = {
|
||||||
|
id: this.data?.id || '',
|
||||||
|
name: this.data?.name || '',
|
||||||
|
type: typeValue,
|
||||||
|
from: fromValue,
|
||||||
|
leaderImgUrl: this.data?.leaderImgUrl || '',
|
||||||
|
minionImgUrl: this.data?.minionImgUrl || '',
|
||||||
|
mobLevelInfos: this.data?.mobLevelInfos || [],
|
||||||
|
skills: this.data?.skills || [],
|
||||||
|
bossFightProfile: this.data?.bossFightProfile
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize bossFightProfile for Boss type mobs if not already set
|
||||||
|
if (typeValue === MobType.Boss && !this.model.bossFightProfile) {
|
||||||
|
this.model.bossFightProfile = this.createDefaultBossFightProfile(this.model.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set selected objects for dropdowns
|
||||||
|
this.selectedMobType = this.mobTypes.find(t => t.value === typeValue) || this.mobTypes[0] || null;
|
||||||
|
this.selectedGameBundle = this.gameBundles.find(b => b.value === fromValue) || this.gameBundles[0] || null;
|
||||||
|
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
private createDefaultBossFightProfile(mobInfoId: string): BossFightProfile {
|
||||||
|
return {
|
||||||
|
id: 'bossfight_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9),
|
||||||
|
mobInfoId: mobInfoId || '',
|
||||||
|
prerequisite: '',
|
||||||
|
objective: '',
|
||||||
|
specialRules: '',
|
||||||
|
extraTokenName: '',
|
||||||
|
extraTokenHtml: '',
|
||||||
|
extraTokenName2: '',
|
||||||
|
extraTokenHtml2: '',
|
||||||
|
phaseBuffs: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private initializeEnums(): void {
|
||||||
|
// Initialize MobType options
|
||||||
|
Object.keys(MobType).filter(key => isNaN(Number(key))).forEach(key => {
|
||||||
|
this.mobTypes.push({
|
||||||
|
value: MobType[key] as MobType,
|
||||||
|
text: key
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize GameBundle options
|
||||||
|
Object.keys(GameBundle).filter(key => isNaN(Number(key))).forEach(key => {
|
||||||
|
this.gameBundles.push({
|
||||||
|
value: GameBundle[key] as GameBundle,
|
||||||
|
text: key
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public close(): void {
|
||||||
|
this.dialog.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public save(): void {
|
||||||
|
if (this.model.name && !this.processing) {
|
||||||
|
this.processing = true;
|
||||||
|
|
||||||
|
const mobType = this.selectedMobType?.value ?? MobType.Mob;
|
||||||
|
|
||||||
|
// Ensure bossFightProfile is initialized for Boss type
|
||||||
|
let bossFightProfile = this.model.bossFightProfile;
|
||||||
|
if (mobType === MobType.Boss && !bossFightProfile) {
|
||||||
|
bossFightProfile = this.createDefaultBossFightProfile(this.model.id);
|
||||||
|
} else if (mobType !== MobType.Boss) {
|
||||||
|
bossFightProfile = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract enum values from selected objects
|
||||||
|
const mobInfo: MD2MobInfo = {
|
||||||
|
...this.model,
|
||||||
|
type: mobType,
|
||||||
|
from: this.selectedGameBundle?.value ?? GameBundle.CoreGame,
|
||||||
|
mobLevelInfos: this.data?.mobLevelInfos || [],
|
||||||
|
skills: this.data?.skills || [],
|
||||||
|
bossFightProfile: bossFightProfile
|
||||||
|
};
|
||||||
|
|
||||||
|
this.mobInfoService.createOrUpdate(mobInfo).pipe(first()).subscribe(result => {
|
||||||
|
this.processing = false;
|
||||||
|
this.dialog.close(result);
|
||||||
|
}, error => {
|
||||||
|
this.processing = false;
|
||||||
|
console.error('Error saving mob info:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isValid(): boolean {
|
||||||
|
if (!this.model) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const nameValid = this.model.name && this.model.name.trim().length > 0;
|
||||||
|
const typeValid = this.selectedMobType !== null && this.selectedMobType !== undefined;
|
||||||
|
const fromValid = this.selectedGameBundle !== null && this.selectedGameBundle !== undefined;
|
||||||
|
return nameValid && typeValid && fromValid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+58
@@ -0,0 +1,58 @@
|
|||||||
|
<nb-card>
|
||||||
|
<nb-card-header>
|
||||||
|
<h4>MD2 Mob Info Maintenance</h4>
|
||||||
|
<div class="float-right">
|
||||||
|
<button kendoButton (click)="addHandler()" [primary]="true">
|
||||||
|
<span class="k-icon k-i-plus"></span> Add New
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</nb-card-header>
|
||||||
|
<nb-card-body>
|
||||||
|
<kendo-grid #grid [data]="gridData" [loading]="isLoading" [pageSize]="gridState.take" [skip]="gridState.skip"
|
||||||
|
[group]="gridState.group" [filter]="gridState.filter" [sort]="gridState.sort" [sortable]="true"
|
||||||
|
[filterable]="true" [pageable]="true" [selectable]="true" [groupable]="true"
|
||||||
|
(dataStateChange)="gridState = $event; processGridData()" (edit)="editHandler($event)"
|
||||||
|
(remove)="removeHandler($event)" (add)="addHandler()">
|
||||||
|
|
||||||
|
<kendo-grid-toolbar>
|
||||||
|
<button kendoGridAddCommand>Add new</button>
|
||||||
|
</kendo-grid-toolbar>
|
||||||
|
|
||||||
|
<kendo-grid-column field="name" title="Name" [width]="200">
|
||||||
|
</kendo-grid-column>
|
||||||
|
|
||||||
|
<kendo-grid-column field="type" title="Type" [width]="1">
|
||||||
|
<ng-template kendoGridCellTemplate let-dataItem>
|
||||||
|
<ng-template kendoGridCellTemplate let-dataItem>
|
||||||
|
{{ getMobTypeName(dataItem.type) }}
|
||||||
|
</ng-template>
|
||||||
|
<ng-template kendoGridGroupHeaderTemplate let-value="value">
|
||||||
|
{{ getMobTypeName(value) }}
|
||||||
|
</ng-template>
|
||||||
|
</ng-template>
|
||||||
|
</kendo-grid-column>
|
||||||
|
|
||||||
|
<kendo-grid-column field="from" title="Game Bundle" [width]="150">
|
||||||
|
<ng-template kendoGridCellTemplate let-dataItem>
|
||||||
|
{{ getGameBundleName(dataItem.from) }}
|
||||||
|
</ng-template>
|
||||||
|
</kendo-grid-column>
|
||||||
|
|
||||||
|
<kendo-grid-column field="leaderImgUrl" title="Leader Image" [width]="200">
|
||||||
|
</kendo-grid-column>
|
||||||
|
|
||||||
|
<kendo-grid-column field="minionImgUrl" title="Minion Image" [width]="200">
|
||||||
|
</kendo-grid-column>
|
||||||
|
|
||||||
|
<kendo-grid-command-column title="Actions" [width]="250">
|
||||||
|
<ng-template kendoGridCellTemplate let-isNew="isNew" let-dataItem="dataItem" let-rowIndex="rowIndex">
|
||||||
|
<button kendoGridEditCommand [primary]="true">Edit</button>
|
||||||
|
<button kendoGridRemoveCommand>Remove</button>
|
||||||
|
<button kendoButton (click)="viewDetailHandler({ dataItem })" [look]="'flat'">
|
||||||
|
View Details
|
||||||
|
</button>
|
||||||
|
</ng-template>
|
||||||
|
</kendo-grid-command-column>
|
||||||
|
</kendo-grid>
|
||||||
|
</nb-card-body>
|
||||||
|
</nb-card>
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-right {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
kendo-grid {
|
||||||
|
height: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
+162
@@ -0,0 +1,162 @@
|
|||||||
|
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||||
|
import { GridComponent, GridDataResult } from '@progress/kendo-angular-grid';
|
||||||
|
import { State, process } from '@progress/kendo-data-query';
|
||||||
|
import { first } from 'rxjs/operators';
|
||||||
|
import { MD2MobInfo, GameBundle } from '../massive-darkness2.db.model';
|
||||||
|
import { MobType } from '../massive-darkness2.model';
|
||||||
|
import { MD2MobInfoService } from '../service/massive-darkness2.service';
|
||||||
|
import { MD2MobInfoDetailComponent } from './md2-mob-info-detail/md2-mob-info-detail.component';
|
||||||
|
import { MD2MobInfoEditorComponent } from './md2-mob-info-editor/md2-mob-info-editor.component';
|
||||||
|
import { DialogService } from '@progress/kendo-angular-dialog';
|
||||||
|
import { MsgBoxService } from '../../../services/msg-box.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ngx-md2-mob-info-maintenance',
|
||||||
|
templateUrl: './md2-mob-info-maintenance.component.html',
|
||||||
|
styleUrls: ['./md2-mob-info-maintenance.component.scss']
|
||||||
|
})
|
||||||
|
export class MD2MobInfoMaintenanceComponent implements OnInit {
|
||||||
|
@ViewChild('grid') grid: GridComponent;
|
||||||
|
|
||||||
|
public gridData: GridDataResult = { data: [], total: 0 };
|
||||||
|
private allData: MD2MobInfo[] = [];
|
||||||
|
public gridState: State = {
|
||||||
|
skip: 0,
|
||||||
|
take: 10,
|
||||||
|
sort: [{
|
||||||
|
field: 'name',
|
||||||
|
dir: 'asc'
|
||||||
|
}],
|
||||||
|
filter: {
|
||||||
|
logic: 'and',
|
||||||
|
filters: []
|
||||||
|
},
|
||||||
|
group: [{
|
||||||
|
field: 'type',
|
||||||
|
dir: 'asc'
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
public isLoading: boolean = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private mobInfoService: MD2MobInfoService,
|
||||||
|
private dialogService: DialogService,
|
||||||
|
private msgBoxService: MsgBoxService
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public loadData(): void {
|
||||||
|
this.isLoading = true;
|
||||||
|
this.mobInfoService.getAll().pipe(first()).subscribe(result => {
|
||||||
|
this.allData = result;
|
||||||
|
this.processGridData();
|
||||||
|
this.isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public processGridData(): void {
|
||||||
|
// Normalize filter state to handle null/undefined/empty filters
|
||||||
|
let normalizedFilter: { logic: 'and' | 'or'; filters: any[] } = { logic: 'and', filters: [] };
|
||||||
|
if (this.gridState.filter) {
|
||||||
|
const filters = this.gridState.filter.filters || [];
|
||||||
|
if (filters.length > 0) {
|
||||||
|
normalizedFilter = this.gridState.filter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedState: State = {
|
||||||
|
...this.gridState,
|
||||||
|
filter: normalizedFilter
|
||||||
|
};
|
||||||
|
|
||||||
|
this.gridData = process(this.allData, normalizedState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public addHandler(): void {
|
||||||
|
const editorData = {} as MD2MobInfo;
|
||||||
|
const dialogRef = this.dialogService.open({
|
||||||
|
title: 'Add New Mob Info',
|
||||||
|
content: MD2MobInfoEditorComponent,
|
||||||
|
width: '90vw',
|
||||||
|
height: 600
|
||||||
|
});
|
||||||
|
|
||||||
|
const editor = dialogRef.content.instance;
|
||||||
|
editor.isAdding = true;
|
||||||
|
editor.data = editorData;
|
||||||
|
|
||||||
|
// Force model re-initialization after data is set
|
||||||
|
setTimeout(() => {
|
||||||
|
editor.initializeModel();
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
dialogRef.result.subscribe(result => {
|
||||||
|
if (result) {
|
||||||
|
this.loadData();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public editHandler({ dataItem }: { dataItem: MD2MobInfo }): void {
|
||||||
|
const dialogRef = this.dialogService.open({
|
||||||
|
title: 'Edit Mob Info',
|
||||||
|
content: MD2MobInfoEditorComponent,
|
||||||
|
width: '90vw',
|
||||||
|
height: 600
|
||||||
|
});
|
||||||
|
|
||||||
|
const editor = dialogRef.content.instance;
|
||||||
|
editor.isAdding = false;
|
||||||
|
editor.data = JSON.parse(JSON.stringify(dataItem));
|
||||||
|
|
||||||
|
dialogRef.result.subscribe(result => {
|
||||||
|
if (result) {
|
||||||
|
this.loadData();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeHandler({ dataItem }: { dataItem: MD2MobInfo }): void {
|
||||||
|
this.msgBoxService.showConfirmDeleteBox().pipe(first()).subscribe(answer => {
|
||||||
|
if (answer === true) {
|
||||||
|
this.isLoading = true;
|
||||||
|
this.mobInfoService.delete(dataItem.id).pipe(first()).subscribe(result => {
|
||||||
|
this.loadData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public viewDetailHandler({ dataItem }: { dataItem: MD2MobInfo }): void {
|
||||||
|
this.mobInfoService.getById(dataItem.id).pipe(first()).subscribe(mobInfo => {
|
||||||
|
const dialogRef = this.dialogService.open({
|
||||||
|
title: `Mob Info: ${mobInfo.name}`,
|
||||||
|
content: MD2MobInfoDetailComponent,
|
||||||
|
width: '90vw',
|
||||||
|
height: 800
|
||||||
|
});
|
||||||
|
|
||||||
|
const detail = dialogRef.content.instance;
|
||||||
|
detail.mobInfo = mobInfo;
|
||||||
|
|
||||||
|
dialogRef.result.subscribe(result => {
|
||||||
|
if (result) {
|
||||||
|
this.loadData();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public getMobTypeName(type: MobType): string {
|
||||||
|
return MobType[type] || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public getGameBundleName(bundle: GameBundle): string {
|
||||||
|
return GameBundle[bundle] || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+241
@@ -0,0 +1,241 @@
|
|||||||
|
<div class="k-dialog-content">
|
||||||
|
<form #form="ngForm" class="k-form">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">Level *</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.level" name="level" [min]="1" [decimals]="0"
|
||||||
|
[format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">
|
||||||
|
<md2-icon [icon]="MD2Icon.HP"></md2-icon>
|
||||||
|
{{mobType==MobType.Mob?'HP/Unit':'HP/Hero'}}
|
||||||
|
</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.hpPerHero" name="hpPerHero" [min]="0" [decimals]="0"
|
||||||
|
[format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">
|
||||||
|
<md2-icon [icon]="MD2Icon.TreasureToken"></md2-icon>Reward Tokens</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.rewardTokens" name="rewardTokens" [min]="0" [decimals]="0"
|
||||||
|
[format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">Fixed HP</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.fixedHp" name="fixedHp" [min]="0" [decimals]="0"
|
||||||
|
[format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">
|
||||||
|
<md2-icon [icon]="MD2Icon.TreasureToken_Rare"></md2-icon>Rare Treasure</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.fixedRareTreasure" name="fixedRareTreasure" [min]="0"
|
||||||
|
[decimals]="0" [format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">
|
||||||
|
<md2-icon [icon]="MD2Icon.TreasureToken_Epic"></md2-icon>Epic Treasure</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.fixedEpicTreasure" name="fixedEpicTreasure" [min]="0"
|
||||||
|
[decimals]="0" [format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">
|
||||||
|
<md2-icon [icon]="MD2Icon.TreasureToken_Legendary"></md2-icon>Legend Treasure</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.fixedLegendTreasure" name="fixedLegendTreasure" [min]="0"
|
||||||
|
[decimals]="0" [format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">Actions</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.actions" name="actions" [min]="0" [decimals]="0"
|
||||||
|
[format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h5>Defense Info</h5>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">
|
||||||
|
<md2-icon [icon]="MD2Icon.BlueDice"></md2-icon>Blue Dice</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.defenceInfo.blue" name="defenceInfo.blue" [min]="0"
|
||||||
|
[decimals]="0" [format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">
|
||||||
|
<md2-icon [icon]="MD2Icon.GreenDice"></md2-icon>Green Dice</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.defenceInfo.green" name="defenceInfo.green" [min]="0"
|
||||||
|
[decimals]="0" [format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">
|
||||||
|
<md2-icon [icon]="MD2Icon.BlackDice"></md2-icon>Black Dice</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.defenceInfo.black" name="defenceInfo.black" [min]="0"
|
||||||
|
[decimals]="0" [format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" *ngIf="mobType!=MobType.Mob">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h5>Attack Info</h5>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">
|
||||||
|
<md2-icon [icon]="MD2Icon.Attack"></md2-icon>Attack Type</label>
|
||||||
|
<!-- Dropdown list for attack type , the text is html string-->
|
||||||
|
<kendo-dropdownlist [(ngModel)]="selectedAttackType" name="attackInfo.type" [data]="attackTypes"
|
||||||
|
[textField]="'text'" [valueField]="'value'">
|
||||||
|
<ng-template kendoDropDownListItemTemplate let-dataItem>
|
||||||
|
<span [innerHTML]="dataItem.text"></span>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template kendoDropDownListValueTemplate let-dataItem>
|
||||||
|
<span [innerHTML]="dataItem?.text || ''"></span>
|
||||||
|
</ng-template>
|
||||||
|
</kendo-dropdownlist>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">
|
||||||
|
<md2-icon [icon]="MD2Icon.YellowDice"></md2-icon>Yellow Dice</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.attackInfo.yellow" name="attackInfo.yellow" [min]="0"
|
||||||
|
[decimals]="0" [format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">
|
||||||
|
<md2-icon [icon]="MD2Icon.OrangeDice"></md2-icon>Orange Dice</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.attackInfo.orange" name="attackInfo.orange" [min]="0"
|
||||||
|
[decimals]="0" [format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">
|
||||||
|
<md2-icon [icon]="MD2Icon.RedDice"></md2-icon>Red Dice</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.attackInfo.red" name="attackInfo.red" [min]="0"
|
||||||
|
[decimals]="0" [format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">
|
||||||
|
<md2-icon [icon]="MD2Icon.BlackDice"></md2-icon>Black Dice</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.attackInfo.black" name="attackInfo.black" [min]="0"
|
||||||
|
[decimals]="0" [format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" *ngIf="mobType!=MobType.Mob">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h5>Alter Attack Info</h5>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">
|
||||||
|
<md2-icon [icon]="MD2Icon.Attack"></md2-icon>Alter Attack Type</label>
|
||||||
|
<!-- Dropdown list for attack type , the text is html string-->
|
||||||
|
<kendo-dropdownlist [(ngModel)]="selectedAlterAttackType" name="alterAttackInfo.type"
|
||||||
|
[data]="attackTypes" [textField]="'text'" [valueField]="'value'">
|
||||||
|
<ng-template kendoDropDownListItemTemplate let-dataItem>
|
||||||
|
<span [innerHTML]="dataItem.text"></span>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template kendoDropDownListValueTemplate let-dataItem>
|
||||||
|
<span [innerHTML]="dataItem?.text || ''"></span>
|
||||||
|
</ng-template>
|
||||||
|
</kendo-dropdownlist>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">
|
||||||
|
<md2-icon [icon]="MD2Icon.YellowDice"></md2-icon>Yellow Dice</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.alterAttackInfo.yellow" name="alterAttackInfo.yellow"
|
||||||
|
[min]="0" [decimals]="0" [format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">
|
||||||
|
<md2-icon [icon]="MD2Icon.OrangeDice"></md2-icon>Orange Dice</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.alterAttackInfo.orange" name="alterAttackInfo.orange"
|
||||||
|
[min]="0" [decimals]="0" [format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">
|
||||||
|
<md2-icon [icon]="MD2Icon.RedDice"></md2-icon>Red Dice</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.alterAttackInfo.red" name="alterAttackInfo.red" [min]="0"
|
||||||
|
[decimals]="0" [format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">
|
||||||
|
<md2-icon [icon]="MD2Icon.BlackDice"></md2-icon>Black Dice</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.alterAttackInfo.black" name="alterAttackInfo.black"
|
||||||
|
[min]="0" [decimals]="0" [format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<kendo-dialog-actions>
|
||||||
|
<button kendoButton (click)="close()">Cancel</button>
|
||||||
|
<button kendoButton [primary]="true" (click)="save()" [disabled]="!isValid || processing">
|
||||||
|
{{ processing ? 'Saving...' : 'Save' }}
|
||||||
|
</button>
|
||||||
|
</kendo-dialog-actions>
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
.k-dialog-content {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.k-form {
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.k-label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+115
@@ -0,0 +1,115 @@
|
|||||||
|
import { Component, Input, OnInit, ChangeDetectorRef } from '@angular/core';
|
||||||
|
import { DialogRef, DialogContentBase } from '@progress/kendo-angular-dialog';
|
||||||
|
import { first } from 'rxjs/operators';
|
||||||
|
import { MD2MobLevelInfo } from '../../massive-darkness2.db.model';
|
||||||
|
import { MobSkillType } from '../../massive-darkness2.model.boss';
|
||||||
|
import { MD2MobLevelInfoService } from '../../service/massive-darkness2.service';
|
||||||
|
import { MD2Icon, MobType } from '../../massive-darkness2.model';
|
||||||
|
import { MD2Service } from '../../../../services/MD2/md2.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ngx-md2-mob-level-editor',
|
||||||
|
templateUrl: './md2-mob-level-editor.component.html',
|
||||||
|
styleUrls: ['./md2-mob-level-editor.component.scss']
|
||||||
|
})
|
||||||
|
export class MD2MobLevelEditorComponent extends DialogContentBase implements OnInit {
|
||||||
|
MobType = MobType;
|
||||||
|
MD2Icon = MD2Icon;
|
||||||
|
MobSkillType = MobSkillType;
|
||||||
|
@Input() public data: MD2MobLevelInfo;
|
||||||
|
@Input() public mobType: MobType;
|
||||||
|
@Input() public mobInfoId: string;
|
||||||
|
@Input() public isAdding: boolean = false;
|
||||||
|
|
||||||
|
public selectedAttackType: { value: MobSkillType; text: string } | null = null;
|
||||||
|
public model: MD2MobLevelInfo;
|
||||||
|
public processing: boolean = false;
|
||||||
|
public attackTypes: Array<{ value: MobSkillType; text: string }> = [];
|
||||||
|
public selectedAlterAttackType: { value: MobSkillType; text: string } | null = null;
|
||||||
|
constructor(
|
||||||
|
public dialog: DialogRef,
|
||||||
|
private mobLevelInfoService: MD2MobLevelInfoService,
|
||||||
|
private cdr: ChangeDetectorRef,
|
||||||
|
private md2Service: MD2Service
|
||||||
|
) {
|
||||||
|
super(dialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.initializeModel();
|
||||||
|
this.initializeEnums();
|
||||||
|
}
|
||||||
|
|
||||||
|
public initializeEnums(): void {
|
||||||
|
this.attackTypes = [
|
||||||
|
{ value: MobSkillType.Attack, text: 'None' },
|
||||||
|
{ value: MobSkillType.MeleeAttack, text: this.md2Service.iconHtml(MD2Icon.Melee) + ' Melee Attack' },
|
||||||
|
{ value: MobSkillType.RangeAttack, text: this.md2Service.iconHtml(MD2Icon.Range) + ' Range Attack' },
|
||||||
|
{ value: MobSkillType.MagicAttack, text: this.md2Service.iconHtml(MD2Icon.Magic) + ' Magic Attack' },
|
||||||
|
];
|
||||||
|
this.selectedAttackType = this.attackTypes.find(t => t.value === this.model.attackInfo.type) || this.attackTypes[0] || null;
|
||||||
|
this.selectedAlterAttackType = this.attackTypes.find(t => t.value === this.model.alterAttackInfo?.type) || this.attackTypes[0] || null;
|
||||||
|
}
|
||||||
|
public initializeModel(): void {
|
||||||
|
this.model = {
|
||||||
|
id: this.data?.id || '',
|
||||||
|
level: this.data?.level ?? 1,
|
||||||
|
mobInfoId: this.mobInfoId || this.data?.mobInfoId || '',
|
||||||
|
rewardTokens: this.data?.rewardTokens ?? 0,
|
||||||
|
fixedRareTreasure: this.data?.fixedRareTreasure ?? 0,
|
||||||
|
fixedEpicTreasure: this.data?.fixedEpicTreasure ?? 0,
|
||||||
|
fixedLegendTreasure: this.data?.fixedLegendTreasure ?? 0,
|
||||||
|
fixedHp: this.data?.fixedHp ?? 1,
|
||||||
|
hpPerHero: this.data?.hpPerHero ?? 0,
|
||||||
|
actions: this.data?.actions ?? 1,
|
||||||
|
attackInfo: this.data?.attackInfo
|
||||||
|
? { ...this.data.attackInfo }
|
||||||
|
: { type: MobSkillType.Attack, yellow: null, orange: null, red: null, blue: null, green: null, black: null },
|
||||||
|
alterAttackInfo: this.data?.alterAttackInfo
|
||||||
|
? { ...this.data.alterAttackInfo }
|
||||||
|
: { type: MobSkillType.Attack, yellow: null, orange: null, red: null, blue: null, green: null, black: null },
|
||||||
|
defenceInfo: this.data?.defenceInfo
|
||||||
|
? { ...this.data.defenceInfo }
|
||||||
|
: { type: MobSkillType.Defense, yellow: null, orange: null, red: null, blue: null, green: null, black: null }
|
||||||
|
};
|
||||||
|
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
public close(): void {
|
||||||
|
this.dialog.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public save(): void {
|
||||||
|
if (!this.processing) {
|
||||||
|
this.processing = true;
|
||||||
|
|
||||||
|
// Ensure required objects exist
|
||||||
|
if (!this.model.attackInfo) {
|
||||||
|
this.model.attackInfo = { type: MobSkillType.Attack, yellow: null, orange: null, red: null, blue: null, green: null, black: null };
|
||||||
|
}
|
||||||
|
if (!this.model.defenceInfo) {
|
||||||
|
this.model.defenceInfo = { type: MobSkillType.Defense, yellow: null, orange: null, red: null, blue: null, green: null, black: null };
|
||||||
|
}
|
||||||
|
this.model.attackInfo.type = this.selectedAttackType?.value ?? MobSkillType.Attack;
|
||||||
|
if (this.model.alterAttackInfo) {
|
||||||
|
this.model.alterAttackInfo.type = this.selectedAlterAttackType?.value ?? MobSkillType.Attack;
|
||||||
|
}
|
||||||
|
this.mobLevelInfoService.createOrUpdate(this.model).pipe(first()).subscribe(result => {
|
||||||
|
this.processing = false;
|
||||||
|
this.dialog.close(result);
|
||||||
|
}, error => {
|
||||||
|
this.processing = false;
|
||||||
|
console.error('Error saving mob level info:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isValid(): boolean {
|
||||||
|
if (!this.model) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this.model.level > 0 && this.model.mobInfoId !== '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+87
@@ -0,0 +1,87 @@
|
|||||||
|
<div class="k-dialog-content">
|
||||||
|
<form #form="ngForm" class="k-form">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">Sequence</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.seq" name="seq" [min]="0" [decimals]="0" [format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">Level</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.level" name="level" [min]="1" [decimals]="0"
|
||||||
|
[format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">Name</label>
|
||||||
|
<input kendoTextBox [(ngModel)]="model.name" name="name" class="k-input"
|
||||||
|
placeholder="Enter skill name" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">Type *</label>
|
||||||
|
<kendo-dropdownlist [(ngModel)]="selectedSkillType" name="type" [data]="skillTypes"
|
||||||
|
[valueField]="'value'" [textField]="'text'"
|
||||||
|
[defaultItem]="{ value: null, text: 'Select type...' }">
|
||||||
|
</kendo-dropdownlist>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">Target</label>
|
||||||
|
<kendo-dropdownlist [(ngModel)]="selectedSkillTarget" name="skillTarget" [data]="skillTargets"
|
||||||
|
[valueField]="'value'" [textField]="'text'">
|
||||||
|
</kendo-dropdownlist>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label"><md2-icon icon="EnemySkill"></md2-icon>Skill Roll</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.skillRoll" name="skillRoll" [min]="0" [decimals]="0"
|
||||||
|
[format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label"><md2-icon icon="EnemyClaw"></md2-icon>Claw Roll</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.clawRoll" name="clawRoll" [min]="0" [decimals]="0"
|
||||||
|
[format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-12" *ngIf="selectedSkillType.value == MobSkillType.ConditionalSkill">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">Skill Condition</label>
|
||||||
|
<md2-html-editor [(ngModel)]="model.skillCondition" name="skillCondition"
|
||||||
|
class="htmlEditor"></md2-html-editor>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">Description</label>
|
||||||
|
<md2-html-editor [(ngModel)]="model.description" name="description"
|
||||||
|
class="htmlEditor"></md2-html-editor>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<kendo-dialog-actions>
|
||||||
|
<button kendoButton (click)="close()">Cancel</button>
|
||||||
|
<button kendoButton [primary]="true" (click)="save()" [disabled]="!isValid || processing">
|
||||||
|
{{ processing ? 'Saving...' : 'Save' }}
|
||||||
|
</button>
|
||||||
|
</kendo-dialog-actions>
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
.k-label {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
md2-html-editor {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 300px;
|
||||||
|
}
|
||||||
+121
@@ -0,0 +1,121 @@
|
|||||||
|
import { Component, Input, OnInit, ViewChild, ChangeDetectorRef } from '@angular/core';
|
||||||
|
import { DialogRef, DialogContentBase } from '@progress/kendo-angular-dialog';
|
||||||
|
import { NgForm } from '@angular/forms';
|
||||||
|
import { first } from 'rxjs/operators';
|
||||||
|
import { MD2MobSkill, MobSkillTarget } from '../../massive-darkness2.db.model';
|
||||||
|
import { MobSkillType } from '../../massive-darkness2.model.boss';
|
||||||
|
import { MD2MobSkillService } from '../../service/massive-darkness2.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ngx-md2-mob-skill-editor',
|
||||||
|
templateUrl: './md2-mob-skill-editor.component.html',
|
||||||
|
styleUrls: ['./md2-mob-skill-editor.component.scss']
|
||||||
|
})
|
||||||
|
export class MD2MobSkillEditorComponent extends DialogContentBase implements OnInit {
|
||||||
|
MobSkillType = MobSkillType;
|
||||||
|
@Input() public data: MD2MobSkill;
|
||||||
|
@Input() public mobInfoId: string;
|
||||||
|
@Input() public selectedLevel: number = 1;
|
||||||
|
@Input() public isAdding: boolean = false;
|
||||||
|
@ViewChild('form') form: NgForm;
|
||||||
|
|
||||||
|
public model: MD2MobSkill;
|
||||||
|
public processing: boolean = false;
|
||||||
|
public skillTypes: Array<{ value: MobSkillType; text: string }> = [];
|
||||||
|
public skillTargets: Array<{ value: MobSkillTarget | null; text: string }> = [];
|
||||||
|
public selectedSkillType: { value: MobSkillType; text: string } | null = null;
|
||||||
|
public selectedSkillTarget: { value: MobSkillTarget | null; text: string } | null = null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public dialog: DialogRef,
|
||||||
|
private mobSkillService: MD2MobSkillService,
|
||||||
|
private cdr: ChangeDetectorRef
|
||||||
|
) {
|
||||||
|
super(dialog);
|
||||||
|
this.initializeEnums();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.initializeModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public initializeModel(): void {
|
||||||
|
const typeValue = this.data?.type !== undefined && this.data?.type !== null ? this.data.type : MobSkillType.Combat;
|
||||||
|
const targetValue = this.data?.skillTarget !== undefined ? this.data.skillTarget : null;
|
||||||
|
|
||||||
|
this.model = {
|
||||||
|
id: this.data?.id || '',
|
||||||
|
seq: this.data?.seq ?? 0,
|
||||||
|
level: this.data?.level ?? this.selectedLevel,
|
||||||
|
mobInfoId: this.mobInfoId || this.data?.mobInfoId || '',
|
||||||
|
type: typeValue,
|
||||||
|
skillTarget: targetValue,
|
||||||
|
clawRoll: this.data?.clawRoll ?? 0,
|
||||||
|
skillRoll: this.data?.skillRoll ?? 1,
|
||||||
|
name: this.data?.name || '',
|
||||||
|
skillCondition: this.data?.skillCondition || '',
|
||||||
|
description: this.data?.description || ''
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set selected objects for dropdowns
|
||||||
|
this.selectedSkillType = this.skillTypes.find(t => t.value === typeValue) || this.skillTypes[0] || null;
|
||||||
|
this.selectedSkillTarget = this.skillTargets.find(t => t.value === targetValue) || this.skillTargets[0] || null;
|
||||||
|
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
private initializeEnums(): void {
|
||||||
|
// Initialize MobSkillType options
|
||||||
|
Object.keys(MobSkillType).filter(key => isNaN(Number(key))).forEach(key => {
|
||||||
|
this.skillTypes.push({
|
||||||
|
value: MobSkillType[key] as MobSkillType,
|
||||||
|
text: key
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize MobSkillTarget options
|
||||||
|
this.skillTargets.push({ value: null, text: 'None' });
|
||||||
|
Object.keys(MobSkillTarget).filter(key => isNaN(Number(key))).forEach(key => {
|
||||||
|
this.skillTargets.push({
|
||||||
|
value: MobSkillTarget[key] as MobSkillTarget,
|
||||||
|
text: key
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public close(): void {
|
||||||
|
this.dialog.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public save(): void {
|
||||||
|
if (!this.processing) {
|
||||||
|
this.processing = true;
|
||||||
|
|
||||||
|
// Extract enum values from selected objects
|
||||||
|
const mobSkill: MD2MobSkill = {
|
||||||
|
...this.model,
|
||||||
|
type: this.selectedSkillType?.value ?? MobSkillType.Combat,
|
||||||
|
skillTarget: this.selectedSkillTarget?.value ?? null,
|
||||||
|
mobInfoId: this.mobInfoId || this.model.mobInfoId,
|
||||||
|
level: this.model.level || this.selectedLevel
|
||||||
|
};
|
||||||
|
|
||||||
|
this.mobSkillService.createOrUpdate(mobSkill).pipe(first()).subscribe(result => {
|
||||||
|
this.processing = false;
|
||||||
|
this.dialog.close(result);
|
||||||
|
}, error => {
|
||||||
|
this.processing = false;
|
||||||
|
console.error('Error saving mob skill:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isValid(): boolean {
|
||||||
|
if (!this.model) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const typeValid = this.selectedSkillType !== null && this.selectedSkillType !== undefined;
|
||||||
|
return typeValid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+180
@@ -0,0 +1,180 @@
|
|||||||
|
<div class="k-dialog-content">
|
||||||
|
<form #form="ngForm" class="k-form">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">Phase *</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.phase" name="phase" [min]="1" [decimals]="0"
|
||||||
|
[format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">Extra Action</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.extraAction" name="extraAction" [min]="0" [decimals]="0"
|
||||||
|
[format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">
|
||||||
|
<md2-icon [icon]="MD2Icon.HP"></md2-icon>Extra HP</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.extraHp" name="extraHp" [min]="0" [decimals]="0"
|
||||||
|
[format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">Extra Token Count</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.extraTokenCount" name="extraTokenCount" [min]="0"
|
||||||
|
[decimals]="0" [format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">Extra Token Count 2</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.extraTokenCount2" name="extraTokenCount2" [min]="0"
|
||||||
|
[decimals]="0" [format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">Enable Extra Buff Description</label>
|
||||||
|
<input type="checkbox" [(ngModel)]="model.enableExtraBuffDescription" name="enableExtraBuffDescription" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" *ngIf="model.enableExtraBuffDescription">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">Extra Buff Description</label>
|
||||||
|
<textarea [(ngModel)]="model.extraBuffDescription" name="extraBuffDescription" rows="4" class="k-input" style="width: 100%;"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h5>Extra Attack Dice</h5>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">
|
||||||
|
<md2-icon [icon]="MD2Icon.Attack"></md2-icon>Attack Type</label>
|
||||||
|
<kendo-dropdownlist [(ngModel)]="selectedAttackType" name="extraAttackDice.type"
|
||||||
|
[data]="attackTypes" [textField]="'text'" [valueField]="'value'">
|
||||||
|
<ng-template kendoDropDownListItemTemplate let-dataItem>
|
||||||
|
<span [innerHTML]="dataItem.text"></span>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template kendoDropDownListValueTemplate let-dataItem>
|
||||||
|
<span [innerHTML]="dataItem?.text || ''"></span>
|
||||||
|
</ng-template>
|
||||||
|
</kendo-dropdownlist>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">
|
||||||
|
<md2-icon [icon]="MD2Icon.YellowDice"></md2-icon>Yellow Dice</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.extraAttackDice.yellow" name="extraAttackDice.yellow"
|
||||||
|
[min]="0" [decimals]="0" [format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">
|
||||||
|
<md2-icon [icon]="MD2Icon.OrangeDice"></md2-icon>Orange Dice</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.extraAttackDice.orange" name="extraAttackDice.orange"
|
||||||
|
[min]="0" [decimals]="0" [format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">
|
||||||
|
<md2-icon [icon]="MD2Icon.RedDice"></md2-icon>Red Dice</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.extraAttackDice.red" name="extraAttackDice.red" [min]="0"
|
||||||
|
[decimals]="0" [format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">
|
||||||
|
<md2-icon [icon]="MD2Icon.BlackDice"></md2-icon>Black Dice</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.extraAttackDice.black" name="extraAttackDice.black"
|
||||||
|
[min]="0" [decimals]="0" [format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h5>Extra Defence Dice</h5>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">
|
||||||
|
<md2-icon [icon]="MD2Icon.Defense"></md2-icon>Defence Type</label>
|
||||||
|
<kendo-dropdownlist [(ngModel)]="selectedDefenceType" name="extraDefenceDice.type"
|
||||||
|
[data]="attackTypes" [textField]="'text'" [valueField]="'value'">
|
||||||
|
<ng-template kendoDropDownListItemTemplate let-dataItem>
|
||||||
|
<span [innerHTML]="dataItem.text"></span>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template kendoDropDownListValueTemplate let-dataItem>
|
||||||
|
<span [innerHTML]="dataItem?.text || ''"></span>
|
||||||
|
</ng-template>
|
||||||
|
</kendo-dropdownlist>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">
|
||||||
|
<md2-icon [icon]="MD2Icon.BlueDice"></md2-icon>Blue Dice</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.extraDefenceDice.blue" name="extraDefenceDice.blue"
|
||||||
|
[min]="0" [decimals]="0" [format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">
|
||||||
|
<md2-icon [icon]="MD2Icon.GreenDice"></md2-icon>Green Dice</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.extraDefenceDice.green" name="extraDefenceDice.green"
|
||||||
|
[min]="0" [decimals]="0" [format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="k-label">
|
||||||
|
<md2-icon [icon]="MD2Icon.BlackDice"></md2-icon>Black Dice</label>
|
||||||
|
<kendo-numerictextbox [(ngModel)]="model.extraDefenceDice.black" name="extraDefenceDice.black"
|
||||||
|
[min]="0" [decimals]="0" [format]="'n0'">
|
||||||
|
</kendo-numerictextbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<kendo-dialog-actions>
|
||||||
|
<button kendoButton (click)="close()">Cancel</button>
|
||||||
|
<button kendoButton [primary]="true" (click)="save()" [disabled]="!isValid || processing">
|
||||||
|
{{ processing ? 'Saving...' : 'Save' }}
|
||||||
|
</button>
|
||||||
|
</kendo-dialog-actions>
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
// Phase Buff Editor styles
|
||||||
+114
@@ -0,0 +1,114 @@
|
|||||||
|
import { Component, Input, OnInit, ViewChild, ChangeDetectorRef } from '@angular/core';
|
||||||
|
import { DialogRef, DialogContentBase } from '@progress/kendo-angular-dialog';
|
||||||
|
import { NgForm } from '@angular/forms';
|
||||||
|
import { first } from 'rxjs/operators';
|
||||||
|
import { BossFightPhaseBuff, MD2DiceSet } from '../../massive-darkness2.db.model';
|
||||||
|
import { MobSkillType } from '../../massive-darkness2.model.boss';
|
||||||
|
import { MD2PhaseBuffService } from '../../service/massive-darkness2.service';
|
||||||
|
import { MD2Icon } from '../../massive-darkness2.model';
|
||||||
|
import { MD2Service } from '../../../../services/MD2/md2.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ngx-md2-phase-buff-editor',
|
||||||
|
templateUrl: './md2-phase-buff-editor.component.html',
|
||||||
|
styleUrls: ['./md2-phase-buff-editor.component.scss']
|
||||||
|
})
|
||||||
|
export class MD2PhaseBuffEditorComponent extends DialogContentBase implements OnInit {
|
||||||
|
MD2Icon = MD2Icon;
|
||||||
|
MobSkillType = MobSkillType;
|
||||||
|
@Input() public data: BossFightPhaseBuff;
|
||||||
|
@Input() public bossFightProfileId: string;
|
||||||
|
@Input() public isAdding: boolean = false;
|
||||||
|
@ViewChild('form') form: NgForm;
|
||||||
|
|
||||||
|
public model: BossFightPhaseBuff;
|
||||||
|
public processing: boolean = false;
|
||||||
|
public attackTypes: Array<{ value: MobSkillType; text: string }> = [];
|
||||||
|
public selectedAttackType: { value: MobSkillType; text: string } | null = null;
|
||||||
|
public selectedDefenceType: { value: MobSkillType; text: string } | null = null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public dialog: DialogRef,
|
||||||
|
private phaseBuffService: MD2PhaseBuffService,
|
||||||
|
private cdr: ChangeDetectorRef,
|
||||||
|
private md2Service: MD2Service
|
||||||
|
) {
|
||||||
|
super(dialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.initializeModel();
|
||||||
|
this.initializeEnums();
|
||||||
|
}
|
||||||
|
|
||||||
|
public initializeEnums(): void {
|
||||||
|
this.attackTypes = [
|
||||||
|
{ value: MobSkillType.Attack, text: 'None' },
|
||||||
|
{ value: MobSkillType.MeleeAttack, text: this.md2Service.iconHtml(MD2Icon.Melee) + ' Melee Attack' },
|
||||||
|
{ value: MobSkillType.RangeAttack, text: this.md2Service.iconHtml(MD2Icon.Range) + ' Range Attack' },
|
||||||
|
{ value: MobSkillType.MagicAttack, text: this.md2Service.iconHtml(MD2Icon.Magic) + ' Magic Attack' },
|
||||||
|
{ value: MobSkillType.Defense, text: this.md2Service.iconHtml(MD2Icon.Defense) + ' Defense' }
|
||||||
|
];
|
||||||
|
this.selectedAttackType = this.attackTypes.find(t => t.value === this.model.extraAttackDice?.type) || this.attackTypes[0] || null;
|
||||||
|
this.selectedDefenceType = this.attackTypes.find(t => t.value === this.model.extraDefenceDice?.type) || this.attackTypes[4] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public initializeModel(): void {
|
||||||
|
this.model = {
|
||||||
|
id: this.data?.id || '',
|
||||||
|
bossFightProfileId: this.bossFightProfileId || this.data?.bossFightProfileId || '',
|
||||||
|
phase: this.data?.phase ?? 1,
|
||||||
|
extraAction: this.data?.extraAction ?? 0,
|
||||||
|
extraAttackDice: this.data?.extraAttackDice
|
||||||
|
? { ...this.data.extraAttackDice }
|
||||||
|
: { type: MobSkillType.Attack, yellow: null, orange: null, red: null, blue: null, green: null, black: null },
|
||||||
|
extraDefenceDice: this.data?.extraDefenceDice
|
||||||
|
? { ...this.data.extraDefenceDice }
|
||||||
|
: { type: MobSkillType.Defense, yellow: null, orange: null, red: null, blue: null, green: null, black: null },
|
||||||
|
extraHp: this.data?.extraHp ?? 0,
|
||||||
|
extraTokenCount: this.data?.extraTokenCount ?? 0,
|
||||||
|
extraTokenCount2: this.data?.extraTokenCount2 ?? 0,
|
||||||
|
enableExtraBuffDescription: this.data?.enableExtraBuffDescription ?? false,
|
||||||
|
extraBuffDescription: this.data?.extraBuffDescription || ''
|
||||||
|
};
|
||||||
|
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
public close(): void {
|
||||||
|
this.dialog.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public save(): void {
|
||||||
|
if (!this.processing) {
|
||||||
|
this.processing = true;
|
||||||
|
|
||||||
|
// Ensure required objects exist
|
||||||
|
if (!this.model.extraAttackDice) {
|
||||||
|
this.model.extraAttackDice = { type: MobSkillType.Attack, yellow: null, orange: null, red: null, blue: null, green: null, black: null };
|
||||||
|
}
|
||||||
|
if (!this.model.extraDefenceDice) {
|
||||||
|
this.model.extraDefenceDice = { type: MobSkillType.Defense, yellow: null, orange: null, red: null, blue: null, green: null, black: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
this.model.extraAttackDice.type = this.selectedAttackType?.value ?? MobSkillType.Attack;
|
||||||
|
this.model.extraDefenceDice.type = this.selectedDefenceType?.value ?? MobSkillType.Defense;
|
||||||
|
this.model.bossFightProfileId = this.bossFightProfileId || this.model.bossFightProfileId;
|
||||||
|
|
||||||
|
this.phaseBuffService.createOrUpdate(this.model).pipe(first()).subscribe(result => {
|
||||||
|
this.processing = false;
|
||||||
|
this.dialog.close(result);
|
||||||
|
}, error => {
|
||||||
|
this.processing = false;
|
||||||
|
console.error('Error saving phase buff:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isValid(): boolean {
|
||||||
|
if (!this.model) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this.model.phase > 0 && this.model.bossFightProfileId !== '';
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -20,9 +20,9 @@
|
|||||||
<span class="MD2text diceAmount">x{{info.red}}</span>
|
<span class="MD2text diceAmount">x{{info.red}}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="mob.defenseInfo.black" class="g-height-45 mt-1">
|
<div *ngIf="info.black" class="g-height-45 mt-1">
|
||||||
<span class="MD2Icon Black dice g-font-size-50">
|
<span class="MD2Icon Black dice g-font-size-50">
|
||||||
<span class="MD2text diceAmount">x{{mob.defenseInfo.black}}</span>
|
<span class="MD2text diceAmount">x{{info.black}}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+10
-3
@@ -15,10 +15,17 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class='form-group' *ngIf="showSkill">
|
<div class='form-group'>
|
||||||
<label for='' class='MD2text g-font-size-22 label mb-2'>
|
<!-- <label for='' class='MD2text g-font-size-22 label mb-2'>
|
||||||
<md2-icon icon="blackDice" size="lg"></md2-icon> {{skillTriggerHtml}} <md2-icon icon="enemySkill" size="md">
|
<md2-icon icon="blackDice" size="lg"></md2-icon> {{skillTriggerHtml}} <md2-icon icon="enemySkill" size="md">
|
||||||
</md2-icon>
|
</md2-icon>
|
||||||
|
</label> -->
|
||||||
|
<ng-container *ngFor="let skill of mob.skills">
|
||||||
|
<div *ngIf="skill.uiDisplay" class=" g-brd-bottom--dashed g-brd-gray-light-v2">
|
||||||
|
<label for='' class='MD2text g-font-size-22 label mb-2'>
|
||||||
|
{{MobSkillType[skill.type]}} {{skill.skillRoll}} <md2-icon icon="enemySkill" size="md"></md2-icon>
|
||||||
</label>
|
</label>
|
||||||
<div class='g-font-size-20 skillDesc MD2text' [innerHtml]="mob.combatSkill.description"></div>
|
<div class='g-font-size-20 skillDesc MD2text' [innerHtml]="skill.description"></div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
+5
-2
@@ -23,6 +23,9 @@
|
|||||||
font-size: 45px;
|
font-size: 45px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.skillDesc .MD2Icon {
|
|
||||||
font-size: 45px;
|
//override the style of the skillDesc class and sub elements
|
||||||
|
|
||||||
|
:host ::ng-deep .skillDesc .MD2Icon {
|
||||||
|
font-size: 30px;
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-9
@@ -8,7 +8,7 @@ import { MobSkillType } from '../../../massive-darkness2.model.boss';
|
|||||||
styleUrls: ['./mob-combat-info.component.scss']
|
styleUrls: ['./mob-combat-info.component.scss']
|
||||||
})
|
})
|
||||||
export class MobCombatInfoComponent implements OnInit {
|
export class MobCombatInfoComponent implements OnInit {
|
||||||
|
MobSkillType = MobSkillType;
|
||||||
MD2Icon = MD2Icon;
|
MD2Icon = MD2Icon;
|
||||||
private _mob: MobInfo;
|
private _mob: MobInfo;
|
||||||
public get mob(): MobInfo {
|
public get mob(): MobInfo {
|
||||||
@@ -22,29 +22,31 @@ export class MobCombatInfoComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Input() mode: MobDlgType = MobDlgType.PreView;
|
@Input() mode: MobDlgType = MobDlgType.PreView;
|
||||||
showSkill: boolean = false;
|
showAllSkill: boolean = false;
|
||||||
showBlackDice: boolean
|
showBlackDice: boolean
|
||||||
skillTriggerHtml: string = '';
|
skillTriggerHtml: string = '';
|
||||||
constructor() { }
|
constructor() { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
if (this.mob.combatSkill) {
|
this.showAllSkill = [MobDlgType.PreView, MobDlgType.Dashboard].includes(this.mode);
|
||||||
|
if (this.mob.skills && this.mob.skills.length > 0) {
|
||||||
|
this.mob.skills.forEach(element => {
|
||||||
switch (this.mode) {
|
switch (this.mode) {
|
||||||
case MobDlgType.Activating:
|
case MobDlgType.Activating:
|
||||||
this.showSkill = [MobSkillType.Combat, MobSkillType.Attack].includes(this.mob.combatSkill.type);
|
element.uiDisplay = [MobSkillType.Combat, MobSkillType.Attack].includes(element.type);
|
||||||
break;
|
break;
|
||||||
case MobDlgType.BeenAttacked:
|
case MobDlgType.BeenAttacked:
|
||||||
this.showSkill = [MobSkillType.Combat, MobSkillType.Defense].includes(this.mob.combatSkill.type);
|
element.uiDisplay = [MobSkillType.Combat, MobSkillType.Defense].includes(element.type);
|
||||||
break;
|
break;
|
||||||
case MobDlgType.PreView:
|
case MobDlgType.PreView:
|
||||||
this.showSkill = true;
|
case MobDlgType.Dashboard:
|
||||||
|
element.uiDisplay = [MobSkillType.Combat, MobSkillType.Attack, MobSkillType.Defense].includes(element.type);
|
||||||
break;
|
break;
|
||||||
case MobDlgType.Spawn:
|
case MobDlgType.Spawn:
|
||||||
default:
|
element.uiDisplay = false;
|
||||||
this.showSkill = false;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this.skillTriggerHtml = `${MobSkillType[this.mob.combatSkill.type]} ${this.mob.combatSkill.skillRoll} `
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.showBlackDice = this.mob.type == MobType.Mob && (this.mode == MobDlgType.Activating || this.mode == MobDlgType.BeenAttacked) && this.mob.minionAmount > 0;;
|
this.showBlackDice = this.mob.type == MobType.Mob && (this.mode == MobDlgType.Activating || this.mode == MobDlgType.BeenAttacked) && this.mob.minionAmount > 0;;
|
||||||
|
|||||||
+2
-2
@@ -5,12 +5,12 @@
|
|||||||
<md2-icon icon="defense" size="lg"></md2-icon>
|
<md2-icon icon="defense" size="lg"></md2-icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<div *ngIf="mob.defenseInfo.blue" class="g-height-45">
|
<div *ngIf="mob.defenseInfo?.blue" class="g-height-45">
|
||||||
<span class="MD2Icon Blue dice g-font-size-50">
|
<span class="MD2Icon Blue dice g-font-size-50">
|
||||||
<span class="MD2text diceAmount">x{{mob.defenseInfo.blue}}</span>
|
<span class="MD2text diceAmount">x{{mob.defenseInfo.blue}}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="mob.defenseInfo.black" class="g-height-45 mt-1">
|
<div *ngIf="mob.defenseInfo?.black" class="g-height-45 mt-1">
|
||||||
<span class="MD2Icon Black dice g-font-size-50">
|
<span class="MD2Icon Black dice g-font-size-50">
|
||||||
<span class="MD2text diceAmount">x{{mob.defenseInfo.black}}</span>
|
<span class="MD2text diceAmount">x{{mob.defenseInfo.black}}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
+3
@@ -38,6 +38,9 @@ export class MobDefInfoComponent implements OnInit {
|
|||||||
this.display = false;
|
this.display = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (!this.mob.defenseInfo || this.mob.defenseInfo.blue == 0 && this.mob.defenseInfo.black == 0) {
|
||||||
|
this.display = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-3
@@ -8,8 +8,8 @@
|
|||||||
<div class="pl-2 col-md-6" *ngIf="mob.mobAmount">
|
<div class="pl-2 col-md-6" *ngIf="mob.mobAmount">
|
||||||
|
|
||||||
<ng-container>
|
<ng-container>
|
||||||
<label class='label g-text-nowrap'>Alive Units <b
|
<label class='label g-text-nowrap'>Minions <b
|
||||||
class="MD2text g-font-size-18">{{mob.mobAmount}}</b></label><br>
|
class="MD2text g-font-size-18">{{mob.mobAmount-1}}</b></label><br>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -34,11 +34,21 @@
|
|||||||
|
|
||||||
|
|
||||||
<ng-container *ngIf="showAdjustment">
|
<ng-container *ngIf="showAdjustment">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
|
||||||
<adj-number-input name="mob{{mob.name}}" [ngModel]="mob.unitRemainHp" [maximum]="mob.hp" minimum="1"
|
<adj-number-input name="mob{{mob.name}}" [ngModel]="mob.unitRemainHp" [maximum]="mob.hp" minimum="1"
|
||||||
title="Target Unit HP" (hitChange)="adjustMobHp($event)" (hitMinimum)="adjustMobHp(-1)"
|
title="Target Unit HP" (hitChange)="adjustMobHp($event)" (hitMinimum)="adjustMobHp(-1)"
|
||||||
(hitMaximum)="adjustMobHp(1)">
|
(hitMaximum)="adjustMobHp(1)">
|
||||||
</adj-number-input>
|
</adj-number-input>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6" *ngIf="mode==MobDlgType.Activating">
|
||||||
|
|
||||||
|
<adj-number-input name="mob{{mob.name}}Actions" [(ngModel)]="mob.actions" title="Remaining Actions"
|
||||||
|
hideIncreaseBtn>
|
||||||
|
</adj-number-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
|
||||||
@@ -50,4 +60,8 @@
|
|||||||
<md2-mob-attack-info [mob]="mob" [mode]="mode">
|
<md2-mob-attack-info [mob]="mob" [mode]="mode">
|
||||||
</md2-mob-attack-info>
|
</md2-mob-attack-info>
|
||||||
<md2-mob-def-info [mob]="mob" [mode]="mode"></md2-mob-def-info>
|
<md2-mob-def-info [mob]="mob" [mode]="mode"></md2-mob-def-info>
|
||||||
<md2-mob-combat-info [mob]="mob" [mode]="mode"></md2-mob-combat-info>
|
<md2-mob-combat-info *ngIf="!hideCombatInfo" [mob]="mob" [mode]="mode"></md2-mob-combat-info>
|
||||||
|
<div *ngIf="!hideCombatInfo && mob.extraRule">
|
||||||
|
<div class="alert alert-warning" role="alert" [innerHtml]="mob.extraRule">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -12,7 +12,7 @@ import { MD2ComponentBase } from '../../MD2Base';
|
|||||||
styleUrls: ['./mob-detail-info.component.scss']
|
styleUrls: ['./mob-detail-info.component.scss']
|
||||||
})
|
})
|
||||||
export class MobDetailInfoComponent extends MD2ComponentBase implements OnInit {
|
export class MobDetailInfoComponent extends MD2ComponentBase implements OnInit {
|
||||||
|
MobDlgType = MobDlgType;
|
||||||
MD2Icon = MD2Icon;
|
MD2Icon = MD2Icon;
|
||||||
private _mob: MobInfo;
|
private _mob: MobInfo;
|
||||||
public get mob(): MobInfo {
|
public get mob(): MobInfo {
|
||||||
@@ -33,6 +33,10 @@ export class MobDetailInfoComponent extends MD2ComponentBase implements OnInit {
|
|||||||
return this.mode == MobDlgType.Spawn;
|
return this.mode == MobDlgType.Spawn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get hideCombatInfo(): boolean {
|
||||||
|
return this.mode == MobDlgType.Dashboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
showAttackingInfo: boolean = false;
|
showAttackingInfo: boolean = false;
|
||||||
@Input("showAttackingInfo")
|
@Input("showAttackingInfo")
|
||||||
|
|||||||
@@ -1,7 +1,22 @@
|
|||||||
<img class="g-width-95x img-thumbnail mobBg" src="{{imgUrl('/Mobs/BG.png')}}" />
|
<img class="g-width-95x img-thumbnail mobBg" src="{{imgUrl('/Mobs/BG.png')}}" />
|
||||||
<img class="mobImg roamingMonster" src="{{getMobImageUrl(mob)}}" (click)="showMobImage(mob)" *ngIf="!isMob" />
|
<img class="mobImg roamingMonster" src="{{getMobImageUrl(mob)}}" (click)="showMobImage(mob)" *ngIf="!isMob" />
|
||||||
<div *ngIf="isMob">
|
<div *ngIf="isMob">
|
||||||
<img class="mobImg mobLeader" src="{{mob.leaderImgUrl}}" (click)="showMobImage(mob)" />
|
<img class="mobImg mobLeader" src="{{mob.leaderImgUrl}}" (click)="showMobImage(mob)"
|
||||||
<img class="mobImg mobMinion" src="{{mob.minionImgUrl}}" (click)="showMobImage(mob)" />
|
[ngClass]="{'noMinions': mob.mobAmount==1}" />
|
||||||
|
<img class="mobImg mobMinion" src="{{mob.minionImgUrl}}" (click)="showMobImage(mob)" *ngIf="mob.mobAmount>1" />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- HP and Mana Bars -->
|
||||||
|
<div class="hero-stats-overlay">
|
||||||
|
<div class="stat-bar-overlay hp-bar-overlay">
|
||||||
|
<div class="stat-bar-label-overlay">
|
||||||
|
<md2-icon [icon]="MD2Icon.HP_Color" size="sm"></md2-icon>
|
||||||
|
<span class="stat-value-overlay">{{mob.unitRemainHp}}/{{mob.hp}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-progress-bar-overlay">
|
||||||
|
<div class="stat-progress-fill-overlay hp-fill-overlay" [style.width.%]="(mob.unitRemainHp / mob.hp) * 100">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -1,23 +1,153 @@
|
|||||||
.mobImg {
|
.mobImg {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
object-fit: contain;
|
||||||
&.roamingMonster {
|
&.roamingMonster {
|
||||||
width: 95%;
|
width: 95%;
|
||||||
max-height: 80%;
|
max-height: 80%;
|
||||||
|
left: 0;
|
||||||
}
|
}
|
||||||
&.mobLeader {
|
&.mobLeader {
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
width: 70%;
|
width: 70%;
|
||||||
max-height: 80%;
|
max-height: 80%;
|
||||||
top: 40px;
|
top: 40px;
|
||||||
|
left: 0;
|
||||||
|
&.noMinions {
|
||||||
|
width: 95%;
|
||||||
|
max-height: 90%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&.mobMinion {
|
&.mobMinion {
|
||||||
width: 60%;
|
width: 60%;
|
||||||
max-height: 80%;
|
max-height: 80%;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
top: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.mobBg {
|
.mobBg {
|
||||||
position: absolute;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HP and Mana Bars Overlay
|
||||||
|
.hero-stats-overlay {
|
||||||
|
position: relative;
|
||||||
|
bottom: 60px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
background: linear-gradient(to top, rgba(0, 0, 0, 0.7) 0%, rgba(0, 0, 0, 0.5) 70%, transparent 100%);
|
||||||
|
border-radius: 0 0 8px 8px;
|
||||||
|
z-index: 1;
|
||||||
|
width: 95%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-bar-overlay {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
margin-bottom: 0.35rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-bar-label-overlay {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
|
md2-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value-overlay {
|
||||||
|
color: white;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
margin-bottom: 0.15rem;
|
||||||
|
gap: 0.3rem;
|
||||||
|
|
||||||
|
md2-icon {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value-overlay {
|
||||||
|
font-size: 0.65rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-progress-bar-overlay {
|
||||||
|
width: 100%;
|
||||||
|
height: 20px;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-progress-fill-overlay {
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 10px;
|
||||||
|
transition: width 0.5s ease-out;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
@media (max-height: 450px) and (orientation: landscape) {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
|
||||||
|
animation: shimmer 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.full-stat {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hp-fill-overlay {
|
||||||
|
background: linear-gradient(90deg, #ff6b6b, #ee5a6f);
|
||||||
|
box-shadow: 0 0 8px rgba(238, 90, 111, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mp-fill-overlay {
|
||||||
|
background: linear-gradient(90deg, #4ecdc4, #44a08d);
|
||||||
|
box-shadow: 0 0 8px rgba(68, 160, 141, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shimmer {
|
||||||
|
0% {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
<img src="{{imgUrl('Mobs/MobToken.png')}}" width="40px"> {{(isRoamingMonster?'Roaming Monsters':'Mobs')}}
|
<img src="{{imgUrl('Mobs/MobToken.png')}}" width="40px"> {{(isRoamingMonster?'Roaming Monsters':'Mobs')}}
|
||||||
<!-- <button nbButton hero status="warning" size="small" (click)="initMobDecks()" class="float-right">Reset
|
<!-- <button nbButton hero status="warning" size="small" (click)="initMobDecks()" class="float-right">Reset
|
||||||
Mobs</button> -->
|
Mobs</button> -->
|
||||||
<button nbButton hero status="danger" size="small" (click)="spawnMob()" class="float-right"
|
<button nbButton hero status="danger" size="small" (click)="spawnSpecificMob()" class="float-right"
|
||||||
*ngIf="isRoamingMonster">Spawn Roaming
|
*ngIf="isRoamingMonster">Spawn Roaming
|
||||||
Monster</button>
|
Monster</button>
|
||||||
<button nbButton hero status="warning" size="small" (click)="spawnMob()" class="float-right mr-2"
|
<button nbButton hero status="warning" size="small" (click)="spawnSpecificMob()" class="float-right mr-2"
|
||||||
*ngIf="!isRoamingMonster">Spawn
|
*ngIf="!isRoamingMonster">Spawn
|
||||||
Mob</button>
|
Mob</button>
|
||||||
<!-- <button nbButton hero status="warning" size="tiny" (click)="resetTreasureBag()"
|
<!-- <button nbButton hero status="warning" size="tiny" (click)="resetTreasureBag()"
|
||||||
@@ -29,6 +29,12 @@
|
|||||||
<div class="row no-gutters">
|
<div class="row no-gutters">
|
||||||
<div class="col-12 col-md-8">
|
<div class="col-12 col-md-8">
|
||||||
<md2-mob-stand-info [mob]="mob"></md2-mob-stand-info>
|
<md2-mob-stand-info [mob]="mob"></md2-mob-stand-info>
|
||||||
|
|
||||||
|
<md2-mob-combat-info [mob]="mob" [mode]="MobDlgType.Dashboard"></md2-mob-combat-info>
|
||||||
|
<div *ngIf="mob.extraRule">
|
||||||
|
<div class="alert alert-warning" role="alert" [innerHtml]="mob.extraRule">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class=" col-12 col-md-4">
|
<div class=" col-12 col-md-4">
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { NbDialogService } from '@nebular/theme';
|
import { NbDialogService } from '@nebular/theme';
|
||||||
import { stringify } from 'querystring';
|
|
||||||
import { first } from 'rxjs/operators';
|
import { first } from 'rxjs/operators';
|
||||||
|
import { DropDownOption } from '../../../entity/dropDownOption';
|
||||||
import { FileService } from '../../../services/file.service';
|
import { FileService } from '../../../services/file.service';
|
||||||
import { MD2MobService } from '../../../services/MD2/md2-mob.service';
|
import { MD2MobService } from '../../../services/MD2/md2-mob.service';
|
||||||
import { MD2Service } from '../../../services/MD2/md2.service';
|
import { MD2Service } from '../../../services/MD2/md2.service';
|
||||||
import { MsgBoxService } from '../../../services/msg-box.service';
|
import { MsgBoxService } from '../../../services/msg-box.service';
|
||||||
import { StateService } from '../../../services/state.service';
|
import { StateService } from '../../../services/state.service';
|
||||||
import { ADIcon } from '../../../ui/alert-dlg/alert-dlg.model';
|
import { ADButtons, ADIcon } from '../../../ui/alert-dlg/alert-dlg.model';
|
||||||
import { NumberUtils } from '../../../utilities/number-utils';
|
import { NumberUtils } from '../../../utilities/number-utils';
|
||||||
import { StringUtils } from '../../../utilities/string-utils';
|
import { StringUtils } from '../../../utilities/string-utils';
|
||||||
import { DrawingBag, DrawingItem, MD2Icon, MobDlgType, MobInfo, TreasureType } from '../massive-darkness2.model';
|
import { DrawingBag, DrawingItem, MD2Icon, MobDlgType, MobInfo, TreasureType } from '../massive-darkness2.model';
|
||||||
import { MD2Base, MD2ComponentBase } from '../MD2Base';
|
import { MD2Base, MD2ComponentBase } from '../MD2Base';
|
||||||
import { SpawnMobDlgComponent } from './spawn-mob-dlg/spawn-mob-dlg.component';
|
import { SpawnMobDlgComponent } from './spawn-mob-dlg/spawn-mob-dlg.component';
|
||||||
|
import { GameBundle } from '../massive-darkness2.db.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'md2-mobs',
|
selector: 'md2-mobs',
|
||||||
@@ -86,9 +87,44 @@ export class MobsComponent extends MD2ComponentBase implements OnInit {
|
|||||||
});
|
});
|
||||||
this.cdRef.detectChanges();
|
this.cdRef.detectChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
spawnSpecificMob() {
|
||||||
|
let mobOptions = this.isRoamingMonster ?
|
||||||
|
this.md2Service.allRoamingMonsterInfos.map(f => new DropDownOption(f.id, `${StringUtils.camelToTitle(GameBundle[f.from])} - ${f.name}`)) :
|
||||||
|
this.md2Service.allMobInfos.map(f => new DropDownOption(f.id, `${StringUtils.camelToTitle(GameBundle[f.from])} - ${f.name}`));
|
||||||
|
mobOptions = mobOptions.sort((a, b) => a.value1.localeCompare(b.value1));
|
||||||
|
|
||||||
|
this.msgBoxService.showInputbox('Spawn', '',
|
||||||
|
{
|
||||||
|
inputType: 'dropdown', dropDownOptions: mobOptions,
|
||||||
|
buttons: ADButtons.YesNoCancel,
|
||||||
|
confirmButtonText: 'Spawn',
|
||||||
|
cancelButtonText: 'Random'
|
||||||
|
}).pipe(first()).subscribe(mobId => {
|
||||||
|
|
||||||
|
|
||||||
|
if (mobId || mobId === false) {
|
||||||
|
|
||||||
|
if (!mobId) { mobId = null; }
|
||||||
|
let result = this.md2Service.spawnMob(this.isRoamingMonster, mobId);
|
||||||
|
let titleText = result.exitingMob == null ? `${result.mob.description} Shows Up` : `${result.mob.description} Activate One Action Now!`;
|
||||||
|
let actType = result.exitingMob == null ? MobDlgType.Spawn : MobDlgType.Activating;
|
||||||
|
let mob = result.exitingMob == null ? result.mob : result.exitingMob;
|
||||||
|
|
||||||
|
this.dlgService.open(SpawnMobDlgComponent, { context: { title: titleText, mode: actType, mob: mob } })
|
||||||
|
.onClose.pipe(first()).subscribe(result => {
|
||||||
|
this.afterSpawn();
|
||||||
|
});
|
||||||
|
this.cdRef.detectChanges();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
afterSpawn() {
|
afterSpawn() {
|
||||||
this.cdRef.detectChanges();
|
this.cdRef.detectChanges();
|
||||||
this.md2Service.broadcastService.broadcastMobsInfo();
|
this.md2Service.broadcastMobsInfo();
|
||||||
if (this.showRoundMessage) {
|
if (this.showRoundMessage) {
|
||||||
this.msgBoxService.show(`${NumberUtils.Ordinal(this.md2Service.info.round)} Hero Phase`, { icon: ADIcon.INFO });
|
this.msgBoxService.show(`${NumberUtils.Ordinal(this.md2Service.info.round)} Hero Phase`, { icon: ADIcon.INFO });
|
||||||
}
|
}
|
||||||
@@ -119,7 +155,7 @@ export class MobsComponent extends MD2ComponentBase implements OnInit {
|
|||||||
if (attacker) {
|
if (attacker) {
|
||||||
attacker.exp += 1;
|
attacker.exp += 1;
|
||||||
this.msgBoxService.show(`${attacker.heroFullName} Gain 1 Exp`, { icon: ADIcon.INFO }).pipe(first()).subscribe(result => {
|
this.msgBoxService.show(`${attacker.heroFullName} Gain 1 Exp`, { icon: ADIcon.INFO }).pipe(first()).subscribe(result => {
|
||||||
this.md2Service.broadcastService.broadcastHeroInfoToOwner(attacker);
|
this.md2Service.broadcastHeroInfoToOwner(attacker);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,20 +5,33 @@
|
|||||||
</nb-card-header>
|
</nb-card-header>
|
||||||
<nb-card-body>
|
<nb-card-body>
|
||||||
<div class="row no-gutters">
|
<div class="row no-gutters">
|
||||||
<div class="col-md-7 g-height-90vh">
|
<div class="col-md-7">
|
||||||
<!-- <img src="{{mob.imageUrl}}" class="g-width-90x"> -->
|
<!-- <img src="{{mob.imageUrl}}" class="g-width-90x"> -->
|
||||||
<md2-mob-stand-info [mob]="mob" [mode]="mode"></md2-mob-stand-info>
|
<md2-mob-stand-info [mob]="mob" [mode]="mode"></md2-mob-stand-info>
|
||||||
|
|
||||||
|
<div *ngIf="activeSkill">
|
||||||
|
<!-- <div class="alert alert-warning" role="alert">
|
||||||
|
<h4>{{activeSkill.name}}</h4>
|
||||||
|
<div [innerHtml]="activeSkill.description"></div>
|
||||||
|
</div> -->
|
||||||
|
<details class="skill-card" open>
|
||||||
|
<summary>
|
||||||
|
<h3 class="skill-name">{{ activeSkill.name }}</h3>
|
||||||
|
<!-- <span class="rarity" [ngClass]="rarity.toLowerCase()">{{ rarity }}</span> -->
|
||||||
|
</summary>
|
||||||
|
<div class="skill-body" [innerHtml]="activeSkill.description">
|
||||||
|
<!-- <p>{{ description }}</p>
|
||||||
|
<ul>
|
||||||
|
<li *ngFor="let s of stats"><strong>{{ s.label }}:</strong> {{ s.value }}</li>
|
||||||
|
</ul> -->
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-5">
|
<div class="col-md-5">
|
||||||
|
|
||||||
<md2-mob-detail-info [mob]="mob" [mode]="mode">
|
<md2-mob-detail-info [mob]="mob" [mode]="mode">
|
||||||
</md2-mob-detail-info>
|
</md2-mob-detail-info>
|
||||||
<div *ngIf="actionInfoHtml">
|
|
||||||
|
|
||||||
<div class="alert alert-warning" role="alert" [innerHtml]="actionInfoHtml">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ng-container *ngIf="mode==MobDlgType.Spawn&&mob.type==MobType.Mob">
|
<ng-container *ngIf="mode==MobDlgType.Spawn&&mob.type==MobType.Mob">
|
||||||
<div class="row form-group mt-2">
|
<div class="row form-group mt-2">
|
||||||
|
|||||||
@@ -2,3 +2,122 @@
|
|||||||
width: 885px;
|
width: 885px;
|
||||||
height: 95vh !important;
|
height: 95vh !important;
|
||||||
}
|
}
|
||||||
|
.skill-card {
|
||||||
|
border-radius: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid #252a34;
|
||||||
|
background: radial-gradient(120% 180% at 50% -40%, rgba(217, 143, 43, 0.08), transparent 60%),
|
||||||
|
linear-gradient(180deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.01)), #14161b;
|
||||||
|
transition:
|
||||||
|
transform 0.12s ease,
|
||||||
|
box-shadow 0.25s ease,
|
||||||
|
filter 0.2s ease;
|
||||||
|
margin-top: -130px;
|
||||||
|
z-index: 1;
|
||||||
|
width: 96%;
|
||||||
|
}
|
||||||
|
.skill-card::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 1px;
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgba(246, 230, 185, 0.6),
|
||||||
|
rgba(176, 136, 46, 0.35) 55%,
|
||||||
|
rgba(246, 230, 185, 0.6)
|
||||||
|
);
|
||||||
|
-webkit-mask:
|
||||||
|
linear-gradient(#000 0 0) content-box,
|
||||||
|
linear-gradient(#000 0 0);
|
||||||
|
-webkit-mask-composite: xor;
|
||||||
|
mask-composite: exclude;
|
||||||
|
}
|
||||||
|
.skill-card[open] {
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 2px rgba(217, 143, 43, 0.25),
|
||||||
|
0 12px 28px rgba(0, 0, 0, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
summary {
|
||||||
|
list-style: none;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding: 6px 1rem;
|
||||||
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0));
|
||||||
|
}
|
||||||
|
summary::-webkit-details-marker {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
summary:hover {
|
||||||
|
filter: saturate(1.08);
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 2px rgba(155, 28, 28, 0.25),
|
||||||
|
0 0 18px rgba(217, 143, 43, 0.18) inset;
|
||||||
|
}
|
||||||
|
summary:focus-visible {
|
||||||
|
outline: 2px solid #d98f2b;
|
||||||
|
outline-offset: 2px;
|
||||||
|
box-shadow: 0 0 18px rgba(217, 143, 43, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-name {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 2rem;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
background: linear-gradient(180deg, #f6e6b9, #b0882e 50%, #f6e6b9);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
color: transparent;
|
||||||
|
font-family: "DwarvenAxeBBW00-Regular", sans-serif !important;
|
||||||
|
}
|
||||||
|
.rarity {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid rgba(240, 217, 154, 0.45);
|
||||||
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.02));
|
||||||
|
color: #e8e5de;
|
||||||
|
}
|
||||||
|
.rarity.legendary {
|
||||||
|
border-color: rgba(217, 143, 43, 0.6);
|
||||||
|
box-shadow: inset 0 0 10px rgba(217, 143, 43, 0.25);
|
||||||
|
}
|
||||||
|
.rarity.epic {
|
||||||
|
border-color: rgba(155, 28, 28, 0.6);
|
||||||
|
box-shadow: inset 0 0 10px rgba(155, 28, 28, 0.3);
|
||||||
|
}
|
||||||
|
.rarity.rare {
|
||||||
|
border-color: rgba(148, 164, 58, 0.6);
|
||||||
|
box-shadow: inset 0 0 10px rgba(148, 164, 58, 0.28);
|
||||||
|
}
|
||||||
|
::ng-deep {
|
||||||
|
.skill-body {
|
||||||
|
padding: 0 1rem 1rem;
|
||||||
|
color: #cfcab7;
|
||||||
|
}
|
||||||
|
.skill-body p {
|
||||||
|
margin: 0.6rem 0 0.4rem;
|
||||||
|
color: #ddd6bf;
|
||||||
|
}
|
||||||
|
.skill-body ul {
|
||||||
|
margin: 0.25rem 0 0;
|
||||||
|
padding-left: 1.1rem;
|
||||||
|
}
|
||||||
|
.skill-body li {
|
||||||
|
margin: 0.25rem 0;
|
||||||
|
color: #a7a196;
|
||||||
|
}
|
||||||
|
.skill-body strong {
|
||||||
|
color: #efe7cf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { NbDialogRef } from '@nebular/theme';
|
import { NbDialogRef } from '@nebular/theme';
|
||||||
import { first } from 'rxjs/operators';
|
import { concatMap, find, defaultIfEmpty, first, map, switchMap } from 'rxjs/operators';
|
||||||
import { FileService } from '../../../../services/file.service';
|
import { FileService } from '../../../../services/file.service';
|
||||||
import { MD2Service } from '../../../../services/MD2/md2.service';
|
import { MD2Service } from '../../../../services/MD2/md2.service';
|
||||||
import { MsgBoxService } from '../../../../services/msg-box.service';
|
import { MsgBoxService } from '../../../../services/msg-box.service';
|
||||||
@@ -9,6 +9,13 @@ import { StateService } from '../../../../services/state.service';
|
|||||||
import { StringUtils } from '../../../../utilities/string-utils';
|
import { StringUtils } from '../../../../utilities/string-utils';
|
||||||
import { AttackInfo, AttackTarget, AttackType, MD2HeroInfo, MD2Icon, MobDlgType, MobInfo, MobType } from '../../massive-darkness2.model';
|
import { AttackInfo, AttackTarget, AttackType, MD2HeroInfo, MD2Icon, MobDlgType, MobInfo, MobType } from '../../massive-darkness2.model';
|
||||||
import { MD2ComponentBase } from '../../MD2Base';
|
import { MD2ComponentBase } from '../../MD2Base';
|
||||||
|
import { ADButtons, ADIcon } from '../../../../ui/alert-dlg/alert-dlg.model';
|
||||||
|
import { MobSkillType } from '../../massive-darkness2.model.boss';
|
||||||
|
import { Observable, from, of } from 'rxjs';
|
||||||
|
import { MD2MobSkill, MobSkillTarget } from '../../massive-darkness2.db.model';
|
||||||
|
import { MD2Logic } from '../../massive-darkness2.logic';
|
||||||
|
|
||||||
|
type SkillResolutionResult = { result: boolean; skill: MD2MobSkill | null };
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ngx-spawn-mob-dlg',
|
selector: 'ngx-spawn-mob-dlg',
|
||||||
@@ -25,7 +32,7 @@ export class SpawnMobDlgComponent extends MD2ComponentBase implements OnInit {
|
|||||||
MD2Icon = MD2Icon;
|
MD2Icon = MD2Icon;
|
||||||
mob: MobInfo;
|
mob: MobInfo;
|
||||||
actionInfoHtml: string;
|
actionInfoHtml: string;
|
||||||
beenAttackedHero = [] as MD2HeroInfo[];
|
beenAttackedHero = null as MD2HeroInfo;
|
||||||
attackTarget: string;
|
attackTarget: string;
|
||||||
otherAttackTarget: string;
|
otherAttackTarget: string;
|
||||||
constructor(
|
constructor(
|
||||||
@@ -38,16 +45,34 @@ export class SpawnMobDlgComponent extends MD2ComponentBase implements OnInit {
|
|||||||
) {
|
) {
|
||||||
super(md2Service, stateService, route, cdRef);
|
super(md2Service, stateService, route, cdRef);
|
||||||
}
|
}
|
||||||
|
activeSkill: MD2MobSkill = null;;
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
//this.mob = new MobInfo(this.mob);
|
//this.mob = new MobInfo(this.mob);
|
||||||
if (this.mode == MobDlgType.Spawn && this.mob.type == MobType.Mob) {
|
if (this.mode == MobDlgType.Spawn && this.mob.type == MobType.Mob) {
|
||||||
this.mob.attackInfos = [new AttackInfo(MD2Icon.Melee), new AttackInfo(MD2Icon.Range), new AttackInfo(MD2Icon.Magic)];
|
this.mob.attackInfos = [new AttackInfo(MD2Icon.Melee), new AttackInfo(MD2Icon.Range), new AttackInfo(MD2Icon.Magic)];
|
||||||
} else if (this.mode == MobDlgType.Activating) {
|
} else if (this.mode == MobDlgType.Activating) {
|
||||||
if (this.mob.actionSubject) {
|
if (this.mob.skills) {
|
||||||
this.mob.actionSubject.pipe(first()).subscribe(result => {
|
|
||||||
this.actionInfoHtml = result;
|
//this.mobActivate(this.mob, this.md2Service.info.heros);
|
||||||
|
//this.mob.activateFunction(this.mob, this.md2Service.msgBoxService, this.md2Service.info.heros);
|
||||||
|
this.mobActivate(this.mob, this.md2Service.info.heros).pipe(first()).subscribe(result => {
|
||||||
|
if (result && result.skill) {
|
||||||
|
this.activeSkill = result.skill;
|
||||||
|
//this.actionInfoHtml = `<h4>${result.skill.name}</h4><div>${result.skill.description}</div>`;
|
||||||
|
}
|
||||||
|
if (this.mob.type == MobType.Mob) {
|
||||||
|
this.mob.actions = 2;
|
||||||
|
} else {
|
||||||
|
if (result && result.skill) {
|
||||||
|
this.mob.actions = 1;
|
||||||
|
} else {
|
||||||
|
this.mob.actions = 2;
|
||||||
|
this.activeSkill = { name: 'Normal Action', description: `${this.mob.name} Gains 2 Actions.` } as MD2MobSkill;
|
||||||
|
//this.actionInfoHtml = `${this.mob.name} Gains 2 Actions.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
this.mob.activateFunction(this.mob, this.md2Service.msgBoxService, this.md2Service.info.heros);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.mob.uiWounds = 0;
|
this.mob.uiWounds = 0;
|
||||||
@@ -55,6 +80,52 @@ export class SpawnMobDlgComponent extends MD2ComponentBase implements OnInit {
|
|||||||
this.mob.uiFrozenTokens = 0;
|
this.mob.uiFrozenTokens = 0;
|
||||||
this.initTitleHtml();
|
this.initTitleHtml();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mobActivate(mob: MobInfo, heros: MD2HeroInfo[]): Observable<SkillResolutionResult> {
|
||||||
|
switch (mob.type) {
|
||||||
|
case MobType.Mob:
|
||||||
|
return of({ result: false, skill: null } as SkillResolutionResult);
|
||||||
|
case MobType.RoamingMonster:
|
||||||
|
if (!mob.skills || mob.skills.length === 0) {
|
||||||
|
return of({ result: false, skill: null } as SkillResolutionResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
const orderedSkills = mob.skills
|
||||||
|
.filter(s => [MobSkillType.ConditionalSkill, MobSkillType.OtherWiseSkill].includes(s.type))
|
||||||
|
.sort((a, b) => (a.seq ?? Number.MAX_SAFE_INTEGER) - (b.seq ?? Number.MAX_SAFE_INTEGER));
|
||||||
|
|
||||||
|
if (orderedSkills.length === 0) {
|
||||||
|
return of({ result: false, skill: null } as SkillResolutionResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
return from(orderedSkills).pipe(
|
||||||
|
concatMap(skill => this.resolveSkillPrompt(skill)),
|
||||||
|
find(resolution => resolution.result === true),
|
||||||
|
defaultIfEmpty({ result: false, skill: null } as SkillResolutionResult)
|
||||||
|
);
|
||||||
|
case MobType.Boss:
|
||||||
|
default:
|
||||||
|
return of({ result: false, skill: null } as SkillResolutionResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private resolveSkillPrompt(skill: MD2MobSkill): Observable<SkillResolutionResult> {
|
||||||
|
if (skill.type === MobSkillType.OtherWiseSkill) {
|
||||||
|
return of({ result: true, skill } as SkillResolutionResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
const title = 'Resolve Skill?';
|
||||||
|
const prompt = skill.skillCondition;
|
||||||
|
|
||||||
|
return this.msgBoxService.show(title, {
|
||||||
|
text: prompt,
|
||||||
|
icon: ADIcon.QUESTION,
|
||||||
|
buttons: ADButtons.YesNo
|
||||||
|
}).pipe(
|
||||||
|
map(answer => ({ result: !!answer, skill } as SkillResolutionResult))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
submit() {
|
submit() {
|
||||||
if (this.mode == MobDlgType.Spawn) {
|
if (this.mode == MobDlgType.Spawn) {
|
||||||
|
|
||||||
@@ -87,50 +158,45 @@ export class SpawnMobDlgComponent extends MD2ComponentBase implements OnInit {
|
|||||||
if (this.mode == MobDlgType.Spawn) {
|
if (this.mode == MobDlgType.Spawn) {
|
||||||
htmlText = `${this.mob.description} Shows Up`;
|
htmlText = `${this.mob.description} Shows Up`;
|
||||||
} else if (this.mode == MobDlgType.Activating) {
|
} else if (this.mode == MobDlgType.Activating) {
|
||||||
let targetType = null as AttackTarget;
|
let targetType = null as MobSkillTarget;
|
||||||
let randomAttack = Math.random() * AttackTarget.LowestLevel;
|
let randomAttack = Math.random() * MobSkillTarget.HighestLevel;
|
||||||
const values = Object.values(AttackTarget);
|
const values = Object.values(MobSkillTarget);
|
||||||
|
|
||||||
const stringKeys = Object
|
const stringKeys = Object
|
||||||
.keys(AttackTarget)
|
.keys(MobSkillTarget)
|
||||||
.filter((v) => isNaN(Number(v)))
|
.filter((v) => isNaN(Number(v)))
|
||||||
for (let i = 0; i < stringKeys.length; i++) {
|
for (let i = 0; i < stringKeys.length; i++) {
|
||||||
const element = AttackTarget[stringKeys[i]];
|
const element = MobSkillTarget[stringKeys[i]];
|
||||||
|
|
||||||
if (element >= randomAttack) {
|
if (element >= randomAttack) {
|
||||||
targetType = element;
|
targetType = element;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.beenAttackedHero = MD2Logic.getTargetHeroByFilter(this.md2Service.heros, targetType);
|
||||||
switch (targetType) {
|
switch (targetType) {
|
||||||
case AttackTarget.LeastHp:
|
case MobSkillTarget.LeastHp:
|
||||||
let lowestHp = Math.min(...this.md2Service.heros.map(h => h.hp));
|
this.otherAttackTarget = 'attacking the other <b>Lowest HP</b> hero.';
|
||||||
this.beenAttackedHero = this.md2Service.heros.filter(h => h.hp == lowestHp);
|
|
||||||
//this.otherAttackTarget = 'attacking the other <b>Lowest HP</b> hero.';
|
|
||||||
break;
|
break;
|
||||||
case AttackTarget.HighestHp:
|
case MobSkillTarget.HighestHp:
|
||||||
let highestHp = Math.max(...this.md2Service.heros.map(h => h.hp));
|
this.otherAttackTarget = 'attacking the other <b>Highest HP</b> hero.';
|
||||||
this.beenAttackedHero = this.md2Service.heros.filter(h => h.hp == highestHp);
|
|
||||||
//this.otherAttackTarget = 'attacking the other <b>Highest HP</b> hero.';
|
|
||||||
break;
|
break;
|
||||||
case AttackTarget.HighestMp:
|
case MobSkillTarget.HighestMp:
|
||||||
let highestMp = Math.max(...this.md2Service.heros.map(h => h.mp));
|
this.otherAttackTarget = 'attacking the other <b>Highest Mana</b> hero.';
|
||||||
this.beenAttackedHero = this.md2Service.heros.filter(h => h.mp == highestMp);
|
|
||||||
//this.otherAttackTarget = 'attacking the other <b>Highest Mp</b> hero.';
|
|
||||||
break;
|
break;
|
||||||
case AttackTarget.LowestLevel:
|
case MobSkillTarget.LowestLevel:
|
||||||
let lowestLevel = Math.max(...this.md2Service.heros.map(h => h.level));
|
this.otherAttackTarget = 'attacking the other <b>Lowest Level</b> hero.';
|
||||||
this.beenAttackedHero = this.md2Service.heros.filter(h => h.level == lowestLevel);
|
|
||||||
//this.otherAttackTarget = 'attacking the other <b>Lowest Level</b> hero.';
|
|
||||||
break;
|
break;
|
||||||
case AttackTarget.Random:
|
case MobSkillTarget.HighestLevel:
|
||||||
|
this.otherAttackTarget = 'attacking the other <b>Highest Level</b> hero.';
|
||||||
|
break;
|
||||||
|
case MobSkillTarget.Random:
|
||||||
default:
|
default:
|
||||||
this.beenAttackedHero = [this.md2Service.heros[Math.round(Math.random() * (this.md2Service.heros.length - 1))]];
|
this.otherAttackTarget = 'Just act like normal.';
|
||||||
//this.otherAttackTarget = 'Just act like normal.';
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let attackedHeros = StringUtils.makeCommaSeparatedString(this.beenAttackedHero.map(h => this.md2Service.heroFullName(h)), true, true);
|
//let attackedHeros = StringUtils.makeCommaSeparatedString(this.beenAttackedHero.map(h => this.md2Service.heroFullName(h)), true, true);
|
||||||
this.attackTarget = `Attacking <b>${attackedHeros}</b> first if possible.`;
|
this.attackTarget = `Attacking <b>${this.beenAttackedHero.heroFullName}</b> first if possible, otherwise ${this.otherAttackTarget}`;
|
||||||
htmlText = `${this.title}`;
|
htmlText = `${this.title}`;
|
||||||
} else {
|
} else {
|
||||||
htmlText = `${this.mob.description}`;
|
htmlText = `${this.mob.description}`;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user