Commit 2aa3893b authored by timel's avatar timel

Merge branch 'dev/valtio' into 'main'

Valtio

See merge request mycard/Neos!169
parents 39c16d26 0d7db7ab
...@@ -14,7 +14,6 @@ ...@@ -14,7 +14,6 @@
"@react-spring/shared": "^9.7.2", "@react-spring/shared": "^9.7.2",
"@react-spring/types": "^9.7.2", "@react-spring/types": "^9.7.2",
"@react-spring/web": "^9.7.2", "@react-spring/web": "^9.7.2",
"@reduxjs/toolkit": "^1.9.3",
"@testing-library/jest-dom": "^5.16.5", "@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
...@@ -22,16 +21,15 @@ ...@@ -22,16 +21,15 @@
"@types/node": "^16.18.23", "@types/node": "^16.18.23",
"@types/react": "^18.0.33", "@types/react": "^18.0.33",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"@types/react-redux": "^7.1.25",
"@types/sql.js": "^1.4.4", "@types/sql.js": "^1.4.4",
"antd": "^5.4.0", "antd": "^5.4.0",
"axios": "^0.27.2", "axios": "^0.27.2",
"google-protobuf": "^3.21.2", "google-protobuf": "^3.21.2",
"lodash-es": "^4.17.21",
"react": "^18.2.0", "react": "^18.2.0",
"react-babylonjs": "^3.1.15", "react-babylonjs": "^3.1.15",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-draggable": "^4.4.5", "react-draggable": "^4.4.5",
"react-redux": "^8.0.5",
"react-router-dom": "^6.10.0", "react-router-dom": "^6.10.0",
"react-scripts": "^2.1.3", "react-scripts": "^2.1.3",
"socket.io-client": "^4.6.1", "socket.io-client": "^4.6.1",
...@@ -45,6 +43,7 @@ ...@@ -45,6 +43,7 @@
"@babylonjs/core": "^5.54.0", "@babylonjs/core": "^5.54.0",
"@babylonjs/gui": "^5.54.0", "@babylonjs/gui": "^5.54.0",
"@types/google-protobuf": "^3.15.6", "@types/google-protobuf": "^3.15.6",
"@types/lodash-es": "^4.17.7",
"@typescript-eslint/eslint-plugin": "^5.57.1", "@typescript-eslint/eslint-plugin": "^5.57.1",
"@typescript-eslint/parser": "^5.57.1", "@typescript-eslint/parser": "^5.57.1",
"@vitejs/plugin-react": "^3.1.0", "@vitejs/plugin-react": "^3.1.0",
...@@ -3178,29 +3177,6 @@ ...@@ -3178,29 +3177,6 @@
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
} }
}, },
"node_modules/@reduxjs/toolkit": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.3.tgz",
"integrity": "sha512-GU2TNBQVofL09VGmuSioNPQIu6Ml0YLf4EJhgj0AvBadRlCGzUWet8372LjvO4fqKZF2vH1xU0htAa7BrK9pZg==",
"dependencies": {
"immer": "^9.0.16",
"redux": "^4.2.0",
"redux-thunk": "^2.4.2",
"reselect": "^4.1.7"
},
"peerDependencies": {
"react": "^16.9.0 || ^17.0.0 || ^18",
"react-redux": "^7.2.1 || ^8.0.2"
},
"peerDependenciesMeta": {
"react": {
"optional": true
},
"react-redux": {
"optional": true
}
}
},
"node_modules/@remix-run/router": { "node_modules/@remix-run/router": {
"version": "1.5.0", "version": "1.5.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.5.0.tgz", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.5.0.tgz",
...@@ -3626,15 +3602,6 @@ ...@@ -3626,15 +3602,6 @@
"integrity": "sha512-pYVNNJ+winC4aek+lZp93sIKxnXt5qMkuKmaqS3WGuTq0Bw1ZDYNBgzG5kkdtwcv+GmYJGo3yEg6z2cKKAiEdw==", "integrity": "sha512-pYVNNJ+winC4aek+lZp93sIKxnXt5qMkuKmaqS3WGuTq0Bw1ZDYNBgzG5kkdtwcv+GmYJGo3yEg6z2cKKAiEdw==",
"dev": true "dev": true
}, },
"node_modules/@types/hoist-non-react-statics": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
"dependencies": {
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0"
}
},
"node_modules/@types/jest": { "node_modules/@types/jest": {
"version": "27.5.2", "version": "27.5.2",
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.5.2.tgz", "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.5.2.tgz",
...@@ -3656,6 +3623,21 @@ ...@@ -3656,6 +3623,21 @@
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
"dev": true "dev": true
}, },
"node_modules/@types/lodash": {
"version": "4.14.194",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.194.tgz",
"integrity": "sha512-r22s9tAS7imvBt2lyHC9B8AGwWnXaYb1tY09oyLkXDs4vArpYJzw09nj8MLx5VfciBPGIb+ZwG0ssYnEPJxn/g==",
"dev": true
},
"node_modules/@types/lodash-es": {
"version": "4.17.7",
"resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.7.tgz",
"integrity": "sha512-z0ptr6UI10VlU6l5MYhGwS4mC8DZyYer2mCoyysZtSF7p26zOX8UpbrV0YpNYLGS8K4PUFIyEr62IMFFjveSiQ==",
"dev": true,
"dependencies": {
"@types/lodash": "*"
}
},
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "16.18.23", "version": "16.18.23",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.23.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.23.tgz",
...@@ -3694,17 +3676,6 @@ ...@@ -3694,17 +3676,6 @@
"@types/react": "*" "@types/react": "*"
} }
}, },
"node_modules/@types/react-redux": {
"version": "7.1.25",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.25.tgz",
"integrity": "sha512-bAGh4e+w5D8dajd6InASVIyCo4pZLJ66oLb80F9OBLO1gKESbZcRCJpTT6uLXX+HAB57zw1WTdwJdAsewuTweg==",
"dependencies": {
"@types/hoist-non-react-statics": "^3.3.0",
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0",
"redux": "^4.0.0"
}
},
"node_modules/@types/scheduler": { "node_modules/@types/scheduler": {
"version": "0.16.2", "version": "0.16.2",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
...@@ -3738,11 +3709,6 @@ ...@@ -3738,11 +3709,6 @@
"@types/jest": "*" "@types/jest": "*"
} }
}, },
"node_modules/@types/use-sync-external-store": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
"integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
},
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "5.57.1", "version": "5.57.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.57.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.57.1.tgz",
...@@ -10988,14 +10954,6 @@ ...@@ -10988,14 +10954,6 @@
"node": ">=4.0.0" "node": ">=4.0.0"
} }
}, },
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"dependencies": {
"react-is": "^16.7.0"
}
},
"node_modules/home-or-tmp": { "node_modules/home-or-tmp": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz",
...@@ -11551,15 +11509,6 @@ ...@@ -11551,15 +11509,6 @@
"node": ">= 4" "node": ">= 4"
} }
}, },
"node_modules/immer": {
"version": "9.0.21",
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
"integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/immer"
}
},
"node_modules/immutable": { "node_modules/immutable": {
"version": "4.2.4", "version": "4.2.4",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.4.tgz", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.4.tgz",
...@@ -15348,6 +15297,11 @@ ...@@ -15348,6 +15297,11 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
}, },
"node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
},
"node_modules/lodash._reinterpolate": { "node_modules/lodash._reinterpolate": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz",
...@@ -21390,49 +21344,6 @@ ...@@ -21390,49 +21344,6 @@
"loose-envify": "^1.1.0" "loose-envify": "^1.1.0"
} }
}, },
"node_modules/react-redux": {
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz",
"integrity": "sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==",
"dependencies": {
"@babel/runtime": "^7.12.1",
"@types/hoist-non-react-statics": "^3.3.1",
"@types/use-sync-external-store": "^0.0.3",
"hoist-non-react-statics": "^3.3.2",
"react-is": "^18.0.0",
"use-sync-external-store": "^1.0.0"
},
"peerDependencies": {
"@types/react": "^16.8 || ^17.0 || ^18.0",
"@types/react-dom": "^16.8 || ^17.0 || ^18.0",
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0",
"react-native": ">=0.59",
"redux": "^4"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
},
"react-dom": {
"optional": true
},
"react-native": {
"optional": true
},
"redux": {
"optional": true
}
}
},
"node_modules/react-redux/node_modules/react-is": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
},
"node_modules/react-refresh": { "node_modules/react-refresh": {
"version": "0.14.0", "version": "0.14.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz",
...@@ -23046,22 +22957,6 @@ ...@@ -23046,22 +22957,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/redux": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz",
"integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==",
"dependencies": {
"@babel/runtime": "^7.9.2"
}
},
"node_modules/redux-thunk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz",
"integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==",
"peerDependencies": {
"redux": "^4"
}
},
"node_modules/regenerate": { "node_modules/regenerate": {
"version": "1.4.2", "version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
...@@ -23431,11 +23326,6 @@ ...@@ -23431,11 +23326,6 @@
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
}, },
"node_modules/reselect": {
"version": "4.1.7",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz",
"integrity": "sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A=="
},
"node_modules/resize-observer-polyfill": { "node_modules/resize-observer-polyfill": {
"version": "1.5.1", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
...@@ -30933,17 +30823,6 @@ ...@@ -30933,17 +30823,6 @@
"@react-spring/types": "~9.7.2" "@react-spring/types": "~9.7.2"
} }
}, },
"@reduxjs/toolkit": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.3.tgz",
"integrity": "sha512-GU2TNBQVofL09VGmuSioNPQIu6Ml0YLf4EJhgj0AvBadRlCGzUWet8372LjvO4fqKZF2vH1xU0htAa7BrK9pZg==",
"requires": {
"immer": "^9.0.16",
"redux": "^4.2.0",
"redux-thunk": "^2.4.2",
"reselect": "^4.1.7"
}
},
"@remix-run/router": { "@remix-run/router": {
"version": "1.5.0", "version": "1.5.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.5.0.tgz", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.5.0.tgz",
...@@ -31212,15 +31091,6 @@ ...@@ -31212,15 +31091,6 @@
"integrity": "sha512-pYVNNJ+winC4aek+lZp93sIKxnXt5qMkuKmaqS3WGuTq0Bw1ZDYNBgzG5kkdtwcv+GmYJGo3yEg6z2cKKAiEdw==", "integrity": "sha512-pYVNNJ+winC4aek+lZp93sIKxnXt5qMkuKmaqS3WGuTq0Bw1ZDYNBgzG5kkdtwcv+GmYJGo3yEg6z2cKKAiEdw==",
"dev": true "dev": true
}, },
"@types/hoist-non-react-statics": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
"requires": {
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0"
}
},
"@types/jest": { "@types/jest": {
"version": "27.5.2", "version": "27.5.2",
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.5.2.tgz", "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.5.2.tgz",
...@@ -31242,6 +31112,21 @@ ...@@ -31242,6 +31112,21 @@
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
"dev": true "dev": true
}, },
"@types/lodash": {
"version": "4.14.194",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.194.tgz",
"integrity": "sha512-r22s9tAS7imvBt2lyHC9B8AGwWnXaYb1tY09oyLkXDs4vArpYJzw09nj8MLx5VfciBPGIb+ZwG0ssYnEPJxn/g==",
"dev": true
},
"@types/lodash-es": {
"version": "4.17.7",
"resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.7.tgz",
"integrity": "sha512-z0ptr6UI10VlU6l5MYhGwS4mC8DZyYer2mCoyysZtSF7p26zOX8UpbrV0YpNYLGS8K4PUFIyEr62IMFFjveSiQ==",
"dev": true,
"requires": {
"@types/lodash": "*"
}
},
"@types/node": { "@types/node": {
"version": "16.18.23", "version": "16.18.23",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.23.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.23.tgz",
...@@ -31280,17 +31165,6 @@ ...@@ -31280,17 +31165,6 @@
"@types/react": "*" "@types/react": "*"
} }
}, },
"@types/react-redux": {
"version": "7.1.25",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.25.tgz",
"integrity": "sha512-bAGh4e+w5D8dajd6InASVIyCo4pZLJ66oLb80F9OBLO1gKESbZcRCJpTT6uLXX+HAB57zw1WTdwJdAsewuTweg==",
"requires": {
"@types/hoist-non-react-statics": "^3.3.0",
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0",
"redux": "^4.0.0"
}
},
"@types/scheduler": { "@types/scheduler": {
"version": "0.16.2", "version": "0.16.2",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
...@@ -31324,11 +31198,6 @@ ...@@ -31324,11 +31198,6 @@
"@types/jest": "*" "@types/jest": "*"
} }
}, },
"@types/use-sync-external-store": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
"integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
},
"@typescript-eslint/eslint-plugin": { "@typescript-eslint/eslint-plugin": {
"version": "5.57.1", "version": "5.57.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.57.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.57.1.tgz",
...@@ -37009,14 +36878,6 @@ ...@@ -37009,14 +36878,6 @@
"resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz",
"integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==" "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA=="
}, },
"hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"requires": {
"react-is": "^16.7.0"
}
},
"home-or-tmp": { "home-or-tmp": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz",
...@@ -37450,11 +37311,6 @@ ...@@ -37450,11 +37311,6 @@
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
"integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==" "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ=="
}, },
"immer": {
"version": "9.0.21",
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
"integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA=="
},
"immutable": { "immutable": {
"version": "4.2.4", "version": "4.2.4",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.4.tgz", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.4.tgz",
...@@ -40451,6 +40307,11 @@ ...@@ -40451,6 +40307,11 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
}, },
"lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
},
"lodash._reinterpolate": { "lodash._reinterpolate": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz",
...@@ -45061,26 +44922,6 @@ ...@@ -45061,26 +44922,6 @@
} }
} }
}, },
"react-redux": {
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz",
"integrity": "sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==",
"requires": {
"@babel/runtime": "^7.12.1",
"@types/hoist-non-react-statics": "^3.3.1",
"@types/use-sync-external-store": "^0.0.3",
"hoist-non-react-statics": "^3.3.2",
"react-is": "^18.0.0",
"use-sync-external-store": "^1.0.0"
},
"dependencies": {
"react-is": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
}
}
},
"react-refresh": { "react-refresh": {
"version": "0.14.0", "version": "0.14.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz",
...@@ -46320,20 +46161,6 @@ ...@@ -46320,20 +46161,6 @@
"strip-indent": "^3.0.0" "strip-indent": "^3.0.0"
} }
}, },
"redux": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz",
"integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==",
"requires": {
"@babel/runtime": "^7.9.2"
}
},
"redux-thunk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz",
"integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==",
"requires": {}
},
"regenerate": { "regenerate": {
"version": "1.4.2", "version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
...@@ -46620,11 +46447,6 @@ ...@@ -46620,11 +46447,6 @@
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
}, },
"reselect": {
"version": "4.1.7",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz",
"integrity": "sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A=="
},
"resize-observer-polyfill": { "resize-observer-polyfill": {
"version": "1.5.1", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
...@@ -9,7 +9,6 @@ ...@@ -9,7 +9,6 @@
"@react-spring/shared": "^9.7.2", "@react-spring/shared": "^9.7.2",
"@react-spring/types": "^9.7.2", "@react-spring/types": "^9.7.2",
"@react-spring/web": "^9.7.2", "@react-spring/web": "^9.7.2",
"@reduxjs/toolkit": "^1.9.3",
"@testing-library/jest-dom": "^5.16.5", "@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
...@@ -17,16 +16,15 @@ ...@@ -17,16 +16,15 @@
"@types/node": "^16.18.23", "@types/node": "^16.18.23",
"@types/react": "^18.0.33", "@types/react": "^18.0.33",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"@types/react-redux": "^7.1.25",
"@types/sql.js": "^1.4.4", "@types/sql.js": "^1.4.4",
"antd": "^5.4.0", "antd": "^5.4.0",
"axios": "^0.27.2", "axios": "^0.27.2",
"google-protobuf": "^3.21.2", "google-protobuf": "^3.21.2",
"lodash-es": "^4.17.21",
"react": "^18.2.0", "react": "^18.2.0",
"react-babylonjs": "^3.1.15", "react-babylonjs": "^3.1.15",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-draggable": "^4.4.5", "react-draggable": "^4.4.5",
"react-redux": "^8.0.5",
"react-router-dom": "^6.10.0", "react-router-dom": "^6.10.0",
"react-scripts": "^2.1.3", "react-scripts": "^2.1.3",
"socket.io-client": "^4.6.1", "socket.io-client": "^4.6.1",
...@@ -69,6 +67,7 @@ ...@@ -69,6 +67,7 @@
"@babylonjs/core": "^5.54.0", "@babylonjs/core": "^5.54.0",
"@babylonjs/gui": "^5.54.0", "@babylonjs/gui": "^5.54.0",
"@types/google-protobuf": "^3.15.6", "@types/google-protobuf": "^3.15.6",
"@types/lodash-es": "^4.17.7",
"@typescript-eslint/eslint-plugin": "^5.57.1", "@typescript-eslint/eslint-plugin": "^5.57.1",
"@typescript-eslint/parser": "^5.57.1", "@typescript-eslint/parser": "^5.57.1",
"@vitejs/plugin-react": "^3.1.0", "@vitejs/plugin-react": "^3.1.0",
......
...@@ -51,15 +51,14 @@ export interface CardText { ...@@ -51,15 +51,14 @@ export interface CardText {
* */ * */
export async function fetchCard( export async function fetchCard(
id: number, id: number,
local?: boolean local: boolean = true
): Promise<CardMeta> { ): Promise<CardMeta> {
if (local) { if (local) {
return await sqliteMiddleWare({ const res = await sqliteMiddleWare({
cmd: sqliteCmd.SELECT, cmd: sqliteCmd.SELECT,
payload: { id }, payload: { id },
}).then((res) => });
res.selectResult ? res.selectResult : { id, data: {}, text: {} } return res.selectResult ? res.selectResult : { id, data: {}, text: {} };
);
} }
const res = await axios.get<CardMeta>("http://localhost:3030/cards/" + id); const res = await axios.get<CardMeta>("http://localhost:3030/cards/" + id);
......
export * from "./cards";
export * from "./deck";
export * from "./ocgcore/idl/ocgcore";
export * from "./ocgcore/ocgHelper";
export * from "./strings";
...@@ -10,3 +10,10 @@ interface ImportMetaEnv { ...@@ -10,3 +10,10 @@ interface ImportMetaEnv {
interface ImportMeta { interface ImportMeta {
readonly env: ImportMetaEnv; readonly env: ImportMetaEnv;
} }
// // 重新声明useSnapshot,暂时先这么写。原版的会把所有的改成readonly,引发一些棘手的类型报错。
// import "valtio/react";
// declare module "valtio/react" {
// export declare function useSnapshot<T extends object>(proxyObject: T): T;
// export {};
// }
export * from "./useApp";
export * from "./useEnv"; export * from "./useEnv";
export * from "./useMeshClick"; export * from "./useMeshClick";
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import type { AppDispatch, RootState } from "@/store";
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
...@@ -23,12 +23,8 @@ import { ConfigProvider, theme } from "antd"; ...@@ -23,12 +23,8 @@ import { ConfigProvider, theme } from "antd";
import zhCN from "antd/locale/zh_CN"; import zhCN from "antd/locale/zh_CN";
import React from "react"; import React from "react";
import ReactDOM from "react-dom/client"; import ReactDOM from "react-dom/client";
import { Provider } from "react-redux";
import { BrowserRouter } from "react-router-dom"; import { BrowserRouter } from "react-router-dom";
import { ValtioProvider } from "@/valtioStores";
import { store } from "./store";
import Neos from "./ui/Neos"; import Neos from "./ui/Neos";
const root = ReactDOM.createRoot( const root = ReactDOM.createRoot(
...@@ -37,16 +33,9 @@ const root = ReactDOM.createRoot( ...@@ -37,16 +33,9 @@ const root = ReactDOM.createRoot(
root.render( root.render(
<React.StrictMode> <React.StrictMode>
<BrowserRouter> <BrowserRouter>
<Provider store={store}> <ConfigProvider theme={{ algorithm: theme.darkAlgorithm }} locale={zhCN}>
<ValtioProvider> <Neos />
<ConfigProvider </ConfigProvider>
theme={{ algorithm: theme.darkAlgorithm }}
locale={zhCN}
>
<Neos />
</ConfigProvider>
</ValtioProvider>
</Provider>
</BrowserRouter> </BrowserRouter>
</React.StrictMode> </React.StrictMode>
); );
...@@ -75,7 +75,10 @@ export default async function (action: sqliteAction): Promise<sqliteResult> { ...@@ -75,7 +75,10 @@ export default async function (action: sqliteAction): Promise<sqliteResult> {
selectResult: constructCardMeta(code, dataResult, textResult), selectResult: constructCardMeta(code, dataResult, textResult),
}; };
} else { } else {
console.warn("ygo db not init or id not provied!"); if (action.payload?.id !== 0) {
// 0是无效的卡片ID,不需要报错,返回空即可
console.warn("ygo db not init or id not provied!");
}
} }
return {}; return {};
......
/*
* Chat状态更新逻辑
*
* */
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { RootState } from "@/store";
export interface chatState {
message: string;
}
const initialState: chatState = {
message: "",
};
const chatSlice = createSlice({
name: "chat",
initialState,
reducers: {
postChat: (state, action: PayloadAction<string>) => {
state.message = action.payload;
},
},
});
export const { postChat } = chatSlice.actions;
export const selectChat = (state: RootState) => state.chat.message;
export default chatSlice.reducer;
import {
ActionReducerMapBuilder,
CaseReducer,
PayloadAction,
} from "@reduxjs/toolkit";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { RootState } from "@/store";
import {
createAsyncMetaThunk,
DuelFieldState,
DuelReducer,
extendIdleInteractivities,
extendMeta,
extendState,
Interactivity,
removeCard,
} from "./generic";
import { DuelState } from "./mod";
import { judgeSelf } from "./util";
export interface BanishedZoneState extends DuelFieldState {}
// 初始化除外区状态
export const initBanishedZoneImpl: CaseReducer<
DuelState,
PayloadAction<number>
> = (state, action) => {
const player = action.payload;
if (judgeSelf(player, state)) {
state.meBanishedZone = { inner: [] };
} else {
state.opBanishedZone = { inner: [] };
}
};
// 增加除外区
export const fetchBanishedZoneMeta = createAsyncMetaThunk(
"duel/fetchBanishedZoneMeta"
);
export const banishedZoneCase = (
builder: ActionReducerMapBuilder<DuelState>
) => {
builder.addCase(fetchBanishedZoneMeta.pending, (state, action) => {
// Meta结果没返回之前先更新`ID`
const controler = action.meta.arg.controler;
const sequence = action.meta.arg.sequence;
const code = action.meta.arg.code;
const newExclusion = {
occupant: { id: code, data: {}, text: {} },
location: {
controler,
location: ygopro.CardZone.REMOVED,
},
idleInteractivities: [],
counters: {},
};
if (judgeSelf(controler, state)) {
extendState(state.meBanishedZone, newExclusion, sequence);
} else {
extendState(state.opBanishedZone, newExclusion, sequence);
}
});
builder.addCase(fetchBanishedZoneMeta.fulfilled, (state, action) => {
const controler = action.payload.controler;
const sequence = action.payload.sequence;
const meta = action.payload.meta;
if (judgeSelf(controler, state)) {
extendMeta(state.meBanishedZone, meta, sequence);
} else {
extendMeta(state.opBanishedZone, meta, sequence);
}
});
};
// 删除除外区
export const removeBanishedZoneImpl: CaseReducer<
DuelState,
PayloadAction<{ controler: number; sequence: number }>
> = (state, action) => {
const banishedZone = judgeSelf(action.payload.controler, state)
? state.meBanishedZone
: state.opBanishedZone;
removeCard(banishedZone, action.payload.sequence);
};
export const addBanishedZoneIdleInteractivitiesImpl: DuelReducer<{
player: number;
sequence: number;
interactivity: Interactivity<number>;
}> = (state, action) => {
const banishedZone = judgeSelf(action.payload.player, state)
? state.meBanishedZone
: state.opBanishedZone;
extendIdleInteractivities(
banishedZone,
action.payload.sequence,
action.payload.interactivity
);
};
export const selectMeBanishedZone = (state: RootState) =>
state.duel.meBanishedZone || { inner: [] };
export const selectOpBanishedZone = (state: RootState) =>
state.duel.opBanishedZone || { inner: [] };
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import {
clearIdleInteractivities,
clearPlaceInteractivities,
DuelReducer,
reloadFieldMeta,
updateCardData,
} from "./generic";
import { judgeSelf } from "./util";
import MsgReloadField = ygopro.StocGameMessage.MsgReloadField;
type MsgUpdateData = ReturnType<
typeof ygopro.StocGameMessage.MsgUpdateData.prototype.toObject
>;
export const clearAllIdleInteractivitiesImpl: DuelReducer<number> = (
state,
action
) => {
const player = action.payload;
const states = judgeSelf(player, state)
? [
state.meHands,
state.meMonsters,
state.meMagics,
state.meGraveyard,
state.meBanishedZone,
state.meExtraDeck,
]
: [
state.opHands,
state.opMonsters,
state.opMagics,
state.opGraveyard,
state.opBanishedZone,
state.opExtraDeck,
];
states.forEach((item) => clearIdleInteractivities(item));
};
export const clearAllPlaceInteractivitiesImpl: DuelReducer<number> = (
state,
action
) => {
const player = action.payload;
const states = judgeSelf(player, state)
? [
state.meHands,
state.meMonsters,
state.meMagics,
state.meGraveyard,
state.meBanishedZone,
]
: [
state.opHands,
state.opMonsters,
state.opMagics,
state.opGraveyard,
state.opBanishedZone,
];
states.forEach((item) => clearPlaceInteractivities(item));
};
export const updateFieldDataImpl: DuelReducer<MsgUpdateData> = (
state,
action
) => {
const player = action.payload.player;
const zone = action.payload.zone;
const actions = action.payload.actions;
if (player !== undefined && zone !== undefined && actions !== undefined) {
switch (zone) {
case ygopro.CardZone.HAND: {
const hand = judgeSelf(player, state) ? state.meHands : state.opHands;
updateCardData(hand, actions);
break;
}
case ygopro.CardZone.EXTRA: {
const extra = judgeSelf(player, state)
? state.meExtraDeck
: state.opExtraDeck;
updateCardData(extra, actions);
break;
}
case ygopro.CardZone.MZONE: {
const monster = judgeSelf(player, state)
? state.meMonsters
: state.opMonsters;
updateCardData(monster, actions);
break;
}
case ygopro.CardZone.SZONE: {
const magics = judgeSelf(player, state)
? state.meMagics
: state.opMagics;
updateCardData(magics, actions);
break;
}
case ygopro.CardZone.GRAVE: {
const graveyard = judgeSelf(player, state)
? state.meGraveyard
: state.opGraveyard;
updateCardData(graveyard, actions);
break;
}
case ygopro.CardZone.REMOVED: {
const BanishedZone = judgeSelf(player, state)
? state.meBanishedZone
: state.opBanishedZone;
updateCardData(BanishedZone, actions);
break;
}
default: {
break;
}
}
}
};
export const reloadFieldImpl: DuelReducer<MsgReloadField> = (state, action) => {
const _duel_rule = action.payload.duel_rule;
// 初始化`DuelState`
state.meDeck = { inner: [] };
state.opDeck = { inner: [] };
state.meExtraDeck = { inner: [] };
state.opExtraDeck = { inner: [] };
state.meMonsters = { inner: [] };
state.opMonsters = { inner: [] };
state.meMagics = { inner: [] };
state.opMagics = { inner: [] };
state.meGraveyard = { inner: [] };
state.opGraveyard = { inner: [] };
state.meBanishedZone = { inner: [] };
state.opBanishedZone = { inner: [] };
state.meHands = { inner: [] };
state.opHands = { inner: [] };
for (const reload of action.payload.actions) {
const player = reload.player;
// DECK
const deck = judgeSelf(player, state) ? state.meDeck : state.opDeck;
reloadFieldMeta(
deck,
reload.zone_actions.filter((item) => item.zone == ygopro.CardZone.DECK),
player
);
// EXTRA_DECK
const extraDeck = judgeSelf(player, state)
? state.meExtraDeck
: state.opExtraDeck;
reloadFieldMeta(
extraDeck,
reload.zone_actions.filter((item) => item.zone == ygopro.CardZone.EXTRA),
player
);
// MZONE
const monster = judgeSelf(player, state)
? state.meMonsters
: state.opMonsters;
reloadFieldMeta(
monster,
reload.zone_actions.filter((item) => item.zone == ygopro.CardZone.MZONE),
player
);
// SZONE
const magics = judgeSelf(player, state) ? state.meMagics : state.opMagics;
reloadFieldMeta(
magics,
reload.zone_actions.filter((item) => item.zone == ygopro.CardZone.SZONE),
player
);
// GRAVE
const graveyard = judgeSelf(player, state)
? state.meGraveyard
: state.opGraveyard;
reloadFieldMeta(
graveyard,
reload.zone_actions.filter((item) => item.zone == ygopro.CardZone.GRAVE),
player
);
// REMOVED
const banishedZone = judgeSelf(player, state)
? state.meBanishedZone
: state.opBanishedZone;
reloadFieldMeta(
banishedZone,
reload.zone_actions.filter(
(item) => item.zone == ygopro.CardZone.REMOVED
),
player
);
// HANDS
const hands = judgeSelf(player, state) ? state.meHands : state.opHands;
reloadFieldMeta(
hands,
reload.zone_actions.filter((item) => item.zone == ygopro.CardZone.HAND),
player
);
}
};
import { CaseReducer, PayloadAction } from "@reduxjs/toolkit";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { RootState } from "@/store";
import { CardState, DuelFieldState } from "./generic";
import { DuelState } from "./mod";
import { judgeSelf } from "./util";
export interface DeckState extends DuelFieldState {}
// 初始化卡组状态
export const initDeckImpl: CaseReducer<
DuelState,
PayloadAction<{ player: number; deskSize: number }>
> = (state, action) => {
const player = action.payload.player;
const deckSize = action.payload.deskSize;
let deck: CardState[] = new Array(deckSize);
for (let i = 0; i < deckSize; i++) {
deck.push({
occupant: { id: 0, data: {}, text: {} },
location: {
controler: player,
location: ygopro.CardZone.DECK,
},
idleInteractivities: [],
counters: {},
});
}
if (judgeSelf(player, state)) {
state.meDeck = { inner: deck };
} else {
state.opDeck = { inner: deck };
}
};
export const selectMeDeck = (state: RootState) =>
state.duel.meDeck || { inner: [] };
export const selectOpDeck = (state: RootState) =>
state.duel.opDeck || { inner: [] };
import { ActionReducerMapBuilder } from "@reduxjs/toolkit";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { RootState } from "@/store";
import {
createAsyncMetaThunk,
createAsyncRepeatedMetaThunk,
DuelFieldState,
DuelReducer,
extendIdleInteractivities,
extendMeta,
extendState,
Interactivity,
removeCard,
updateCardMeta,
} from "./generic";
import { DuelState } from "./mod";
import { judgeSelf } from "./util";
export interface ExtraDeckState extends DuelFieldState {}
// 初始化额外卡组
export const initMeExtraDeckMeta = createAsyncRepeatedMetaThunk(
"duel/initExtraDeckMeta"
);
// 增加额外卡组
export const fetchExtraDeckMeta = createAsyncMetaThunk(
"duel/fetchExtraDeckMeta"
);
export const extraDeckCase = (builder: ActionReducerMapBuilder<DuelState>) => {
builder.addCase(initMeExtraDeckMeta.pending, (state, action) => {
const _ = action.meta.arg.controler;
const ids = action.meta.arg.codes;
const cards = ids.map((id) => {
return {
occupant: { id, data: {}, text: {} },
location: {
location: ygopro.CardZone.EXTRA,
},
idleInteractivities: [],
counters: {},
};
});
state.meExtraDeck = { inner: cards };
});
builder.addCase(initMeExtraDeckMeta.fulfilled, (state, action) => {
const _ = action.payload.controler;
const metas = action.payload.metas;
updateCardMeta(state.meExtraDeck, metas);
});
builder.addCase(fetchExtraDeckMeta.pending, (state, action) => {
const controler = action.meta.arg.controler;
const sequence = action.meta.arg.sequence;
const code = action.meta.arg.code;
const newExtraDeck = {
occupant: { id: code, data: {}, text: {} },
location: {
controler,
location: ygopro.CardZone.EXTRA,
},
idleInteractivities: [],
counters: {},
};
const extraDeck = judgeSelf(controler, state)
? state.meExtraDeck
: state.opExtraDeck;
extendState(extraDeck, newExtraDeck, sequence);
});
builder.addCase(fetchExtraDeckMeta.fulfilled, (state, action) => {
const controler = action.payload.controler;
const sequence = action.payload.sequence;
const meta = action.payload.meta;
const extraDeck = judgeSelf(controler, state)
? state.meExtraDeck
: state.opExtraDeck;
extendMeta(extraDeck, meta, sequence);
});
};
// 删除额外卡组
export const removeExtraDeckImpl: DuelReducer<{
controler: number;
sequence: number;
}> = (state, action) => {
const extraDeck = judgeSelf(action.payload.controler, state)
? state.meExtraDeck
: state.opExtraDeck;
removeCard(extraDeck, action.payload.sequence);
};
export const addExtraDeckIdleInteractivitiesImpl: DuelReducer<{
player: number;
sequence: number;
interactivity: Interactivity<number>;
}> = (state, action) => {
const extraDeck = judgeSelf(action.payload.player, state)
? state.meExtraDeck
: state.opExtraDeck;
extendIdleInteractivities(
extraDeck,
action.payload.sequence,
action.payload.interactivity
);
};
export const selectMeExtraDeck = (state: RootState) =>
state.duel.meExtraDeck || { inner: [] };
export const selectOpExtraDeck = (state: RootState) =>
state.duel.opExtraDeck || { inner: [] };
import {
AsyncThunk,
CaseReducer,
createAsyncThunk,
PayloadAction,
} from "@reduxjs/toolkit";
import { CardMeta, fetchCard } from "@/api/cards";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { DuelState } from "./mod";
import ReloadFieldAction = ygopro.StocGameMessage.MsgReloadField.ZoneAction;
type UpdateDataAction = ReturnType<
typeof ygopro.StocGameMessage.MsgUpdateData.Action.prototype.toObject
>;
export type DuelReducer<T> = CaseReducer<DuelState, PayloadAction<T>>;
export interface DuelFieldState {
inner: CardState[];
}
export interface CardState {
occupant?: CardMeta; // 占据此位置的卡牌元信息
location: {
controler?: number;
location?: number;
position?: ygopro.CardPosition;
overlay_sequence?: number;
}; // 位置信息
idleInteractivities: Interactivity<number>[]; // IDLE状态下的互动信息
placeInteractivities?: Interactivity<{
controler: number;
zone: ygopro.CardZone;
sequence: number;
}>; // 选择位置状态下的互动信息
overlay_materials?: CardMeta[]; // 超量素材
counters: { [type: number]: number }; // 指示器
reload?: boolean; // 这个字段会在收到MSG_RELOAD_FIELD的时候设置成true,在收到MSG_UPDATE_DATE的时候设置成false
}
export enum InteractType {
// 可普通召唤
SUMMON = 1,
// 可特殊召唤
SP_SUMMON = 2,
// 可改变表示形式
POS_CHANGE = 3,
// 可前场放置
MSET = 4,
// 可后场放置
SSET = 5,
// 可发动效果
ACTIVATE = 6,
// 可作为位置选择
PLACE_SELECTABLE = 7,
// 可攻击
ATTACK = 8,
}
export interface Interactivity<T> {
interactType: InteractType;
// 如果`interactType`是`ACTIVATE`,这个字段是对应的效果编号
activateIndex?: number;
// 如果`interactType`是`ATTACK`,这个字段表示是否可以直接攻击
directAttackAble?: boolean;
// 用户点击后,需要回传给服务端的`response`
response: T;
}
export function createAsyncMetaThunk(name: string): AsyncThunk<
{ controler: number; sequence: number; meta: CardMeta },
{
controler: number;
sequence: number;
position?: ygopro.CardPosition;
code: number;
},
{}
> {
return createAsyncThunk(
name,
async (param: {
controler: number;
sequence: number;
position?: ygopro.CardPosition;
code: number;
}) => {
const code = param.code;
const meta = await fetchCard(code, true);
const response = {
controler: param.controler,
sequence: param.sequence,
meta,
};
return response;
}
);
}
export function createAsyncRepeatedMetaThunk(
name: string
): AsyncThunk<
{ controler: number; metas: CardMeta[] },
{ controler: number; codes: number[] },
{}
> {
return createAsyncThunk(
name,
async (param: { controler: number; codes: number[] }) => {
const controler = param.controler;
const Ids = param.codes;
const metas = await Promise.all(
Ids.map(async (id) => {
if (id == 0) {
return { id, data: {}, text: {} };
} else {
return await fetchCard(id, true);
}
})
);
const response = { controler, metas };
return response;
}
);
}
/*
* 扩充决斗区域卡片内容
*
* @param state - 需要扩充的区域,比如`MonsterState`
* @param newState - 新增加的`CardState`
* @sequence - 新增加的卡片的序列号,可选,如果为空则补充到列表末尾
*
* */
export function extendState<T extends DuelFieldState>(
state: T | undefined,
newState: CardState,
sequence?: number
) {
if (state) {
let index = sequence !== undefined ? sequence : state.inner.length;
state.inner.splice(index, 0, newState);
}
}
export function extendOccupant<T extends DuelFieldState>(
state: T | undefined,
newMeta: CardMeta,
sequence: number,
position?: ygopro.CardPosition
) {
if (state) {
const target = state.inner.find((_, idx) => idx == sequence);
if (target) {
target.occupant = newMeta;
if (typeof position !== "undefined") {
target.location.position = position;
}
}
}
}
export function extendMeta<T extends DuelFieldState>(
state: T | undefined,
newMeta: CardMeta,
sequence: number
) {
if (state) {
const target = state.inner.find((_, idx) => idx == sequence);
if (target) {
target.occupant = newMeta;
}
}
}
export function extendPlaceInteractivity<T extends DuelFieldState>(
state: T | undefined,
controler: number,
sequence: number,
zone: ygopro.CardZone
) {
if (state) {
const target = state.inner.find((_, idx) => idx == sequence);
if (target) {
target.placeInteractivities = {
interactType: InteractType.PLACE_SELECTABLE,
response: {
controler,
zone,
sequence,
},
};
}
}
}
export function clearPlaceInteractivities<T extends DuelFieldState>(
state: T | undefined
) {
if (state) {
for (let item of state.inner) {
item.placeInteractivities = undefined;
}
}
}
export function removeCard<T extends DuelFieldState>(
state: T | undefined,
sequence: number
) {
if (state) {
state.inner = state.inner.filter((_, idx) => idx != sequence);
}
}
export function removeOccupant<T extends DuelFieldState>(
state: T | undefined,
sequence: number
) {
if (state) {
const target = state.inner.find((_, idx) => idx == sequence);
if (target) {
target.occupant = undefined;
}
}
}
export function removeOverlay<T extends DuelFieldState>(
state: T | undefined,
sequence: number
) {
if (state) {
const target = state.inner.find((_, idx) => idx == sequence);
if (target) {
target.overlay_materials = [];
}
}
}
export function insertCard<T extends DuelFieldState>(
state: T | undefined,
sequence: number,
card: CardState
) {
if (state) {
state.inner.splice(sequence, 0, card);
}
}
export function updateCardMeta<T extends DuelFieldState>(
state: T | undefined,
metas: CardMeta[]
) {
if (state) {
state.inner.forEach((item) => {
metas.forEach((meta) => {
if (item.occupant?.id === meta.id) {
item.occupant = meta;
}
});
});
}
}
export function extendIdleInteractivities<T extends DuelFieldState>(
state: T | undefined,
sequence: number,
interactivity: Interactivity<number>
) {
if (state) {
const target = state.inner.find((_, idx) => idx == sequence);
if (target) {
target.idleInteractivities.push(interactivity);
}
}
}
export function clearIdleInteractivities<T extends DuelFieldState>(
state: T | undefined
) {
if (state) {
state.inner.forEach((item) => {
item.idleInteractivities = [];
});
}
}
export function setPosition<T extends DuelFieldState>(
state: T | undefined,
sequence: number,
position: ygopro.CardPosition
) {
const target = state?.inner.find((_, idx) => idx == sequence);
if (target && target.occupant) {
target.location.position = position;
}
}
export function updateCardData<T extends DuelFieldState>(
state: T | undefined,
actions: UpdateDataAction[]
) {
for (const payload of actions) {
const sequence = payload.location?.sequence;
if (typeof sequence !== "undefined") {
const target = state?.inner.find((_, idx) => idx == sequence);
if (target && (target.occupant || target.reload)) {
if (target.occupant === undefined) {
target.occupant = { id: payload.code!, data: {}, text: {} };
}
const occupant = target.occupant;
// 目前只更新以下字段
if (payload.code !== undefined && payload.code >= 0) {
occupant.id = payload.code;
occupant.text.id = payload.code;
}
if (payload.location !== undefined) {
target.location.position = payload.location.position;
}
if (payload.type_ !== undefined && payload.type_ >= 0) {
occupant.data.type = payload.type_;
}
if (payload.level !== undefined && payload.level >= 0) {
occupant.data.level = payload.level;
}
if (payload.attribute !== undefined && payload.attribute >= 0) {
occupant.data.attribute = payload.attribute;
}
if (payload.race !== undefined && payload.race >= 0) {
occupant.data.race = payload.race;
}
if (payload.attack !== undefined && payload.attack >= 0) {
occupant.data.atk = payload.attack;
}
if (payload.defense !== undefined && payload.defense >= 0) {
occupant.data.def = payload.defense;
}
// TODO: counters
}
if (target?.reload) {
target.reload = false;
}
}
}
}
export function reloadFieldMeta<T extends DuelFieldState>(
state: T,
actions: ReloadFieldAction[],
controler: number
) {
actions.sort((a, b) => a.sequence - b.sequence);
const cards = actions.map((action) => {
// FIXME: OVERLAY
return {
location: {
controler,
location: action.zone,
position: action.position,
},
idleInteractivities: [],
counters: {},
reload: true,
};
});
state.inner = cards;
}
import {
ActionReducerMapBuilder,
CaseReducer,
PayloadAction,
} from "@reduxjs/toolkit";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { RootState } from "@/store";
import {
createAsyncMetaThunk,
DuelFieldState,
DuelReducer,
extendIdleInteractivities,
extendMeta,
extendState,
Interactivity,
removeCard,
} from "./generic";
import { DuelState } from "./mod";
import { judgeSelf } from "./util";
export interface GraveyardState extends DuelFieldState {}
// 初始化墓地状态
export const initGraveyardImpl: CaseReducer<
DuelState,
PayloadAction<number>
> = (state, action) => {
const player = action.payload;
if (judgeSelf(player, state)) {
state.meGraveyard = { inner: [] };
} else {
state.opGraveyard = { inner: [] };
}
};
// 增加墓地
export const fetchGraveyardMeta = createAsyncMetaThunk(
"duel/fetchGraveyardMeta"
);
export const graveyardCase = (builder: ActionReducerMapBuilder<DuelState>) => {
builder.addCase(fetchGraveyardMeta.pending, (state, action) => {
// Meta结果没返回之前先更新`ID`
const controler = action.meta.arg.controler;
const sequence = action.meta.arg.sequence;
const code = action.meta.arg.code;
const newGraveyard = {
occupant: { id: code, data: {}, text: {} },
location: {
controler,
location: ygopro.CardZone.GRAVE,
},
idleInteractivities: [],
counters: {},
};
if (judgeSelf(controler, state)) {
extendState(state.meGraveyard, newGraveyard, sequence);
} else {
extendState(state.opGraveyard, newGraveyard, sequence);
}
});
builder.addCase(fetchGraveyardMeta.fulfilled, (state, action) => {
const controler = action.payload.controler;
const sequence = action.payload.sequence;
const meta = action.payload.meta;
if (judgeSelf(controler, state)) {
extendMeta(state.meGraveyard, meta, sequence);
} else {
extendMeta(state.opGraveyard, meta, sequence);
}
});
};
// 删除墓地
export const removeGraveyardImpl: CaseReducer<
DuelState,
PayloadAction<{ controler: number; sequence: number }>
> = (state, action) => {
const graveyard = judgeSelf(action.payload.controler, state)
? state.meGraveyard
: state.opGraveyard;
removeCard(graveyard, action.payload.sequence);
};
export const addGraveyardIdleInteractivitiesImpl: DuelReducer<{
player: number;
sequence: number;
interactivity: Interactivity<number>;
}> = (state, action) => {
const graveyard = judgeSelf(action.payload.player, state)
? state.meGraveyard
: state.opGraveyard;
extendIdleInteractivities(
graveyard,
action.payload.sequence,
action.payload.interactivity
);
};
export const selectMeGraveyard = (state: RootState) =>
state.duel.meGraveyard || { inner: [] };
export const selectOpGraveyard = (state: RootState) =>
state.duel.opGraveyard || { inner: [] };
import {
ActionReducerMapBuilder,
CaseReducer,
PayloadAction,
} from "@reduxjs/toolkit";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { RootState } from "@/store";
import {
createAsyncMetaThunk,
createAsyncRepeatedMetaThunk,
DuelFieldState,
extendMeta,
insertCard,
Interactivity,
removeCard,
updateCardMeta,
} from "./generic";
import { DuelState } from "./mod";
import { judgeSelf } from "./util";
export interface HandState extends DuelFieldState {}
// 增加手牌
export const fetchHandsMeta = createAsyncRepeatedMetaThunk(
"duel/fetchHandsMeta"
);
// 清空手牌互动性
export const clearHandsIdleInteractivityImpl: CaseReducer<
DuelState,
PayloadAction<number>
> = (state, action) => {
const player = action.payload;
const hands = judgeSelf(player, state) ? state.meHands : state.opHands;
if (hands) {
for (let hand of hands.inner) {
hand.idleInteractivities = [];
}
}
};
// 添加手牌互动性
export const addHandsIdleInteractivityImpl: CaseReducer<
DuelState,
PayloadAction<{
player: number;
sequence: number;
interactivity: Interactivity<number>;
}>
> = (state, action) => {
const player = action.payload.player;
const hands = judgeSelf(player, state) ? state.meHands : state.opHands;
if (hands) {
const sequence = action.payload.sequence;
const interactivity = action.payload.interactivity;
hands.inner[sequence].idleInteractivities.push(interactivity);
}
};
// 删除手牌
export const removeHandImpl: CaseReducer<
DuelState,
PayloadAction<[number, number]>
> = (state, action) => {
const controler = action.payload[0];
const sequence = action.payload[1];
const hands = judgeSelf(controler, state) ? state.meHands : state.opHands;
removeCard(hands, sequence);
};
// 在特定位置增加手牌
export const insertHandMeta = createAsyncMetaThunk("duel/insertHandMeta");
export const updateHandsMeta = createAsyncRepeatedMetaThunk(
"duel/updateHandsMeta"
);
export const handsCase = (builder: ActionReducerMapBuilder<DuelState>) => {
builder.addCase(fetchHandsMeta.pending, (state, action) => {
// Meta结果没返回之前先更新手牌`ID`
const player = action.meta.arg.controler;
const ids = action.meta.arg.codes;
const cards = ids.map((id) => {
return {
occupant: { id, data: {}, text: {} },
location: {
controler: player,
location: ygopro.CardZone.HAND,
},
counters: {},
idleInteractivities: [],
};
});
if (judgeSelf(player, state)) {
if (state.meHands) {
state.meHands.inner = state.meHands.inner.concat(cards);
} else {
state.meHands = { inner: cards };
}
} else {
if (state.opHands) {
state.opHands.inner = state.opHands.inner.concat(cards);
} else {
state.opHands = { inner: cards };
}
}
});
builder.addCase(fetchHandsMeta.fulfilled, (state, action) => {
// `Meta`结果回来后更新手牌的`Meta`结果
const player = action.payload.controler;
const metas = action.payload.metas;
const hands = judgeSelf(player, state) ? state.meHands : state.opHands;
updateCardMeta(hands, metas);
});
builder.addCase(insertHandMeta.pending, (state, action) => {
const controler = action.meta.arg.controler;
const sequence = action.meta.arg.sequence;
const code = action.meta.arg.code;
const hands = judgeSelf(controler, state) ? state.meHands : state.opHands;
insertCard(hands, sequence, {
occupant: { id: code, data: {}, text: {} },
location: { controler },
idleInteractivities: [],
counters: {},
});
});
builder.addCase(insertHandMeta.fulfilled, (state, action) => {
const controler = action.payload.controler;
const sequence = action.payload.sequence;
const meta = action.payload.meta;
const hands = judgeSelf(controler, state) ? state.meHands : state.opHands;
extendMeta(hands, meta, sequence);
});
builder.addCase(updateHandsMeta.pending, (state, action) => {
const controler = action.meta.arg.controler;
const codes = action.meta.arg.codes;
const metas = codes.map((code) => {
return {
occupant: { id: code, data: {}, text: {} },
location: {
controler,
location: ygopro.CardZone.HAND,
},
idleInteractivities: [],
counters: {},
};
});
const hands = judgeSelf(controler, state) ? state.meHands : state.opHands;
if (hands) {
hands.inner = metas;
}
});
builder.addCase(updateHandsMeta.fulfilled, (state, action) => {
const controler = action.payload.controler;
const metas = action.payload.metas;
const hands = judgeSelf(controler, state) ? state.meHands : state.opHands;
updateCardMeta(hands, metas);
});
};
export const selectMeHands = (state: RootState) =>
state.duel.meHands || { inner: [] };
export const selectOpHands = (state: RootState) =>
state.duel.opHands || { inner: [] };
import { ActionReducerMapBuilder, createAsyncThunk } from "@reduxjs/toolkit";
import { fetchCard } from "@/api/cards";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { DESCRIPTION_LIMIT, fetchStrings, getStrings } from "@/api/strings";
import { RootState } from "@/store";
import { DuelReducer } from "./generic";
import { DuelState } from "./mod";
import { findCardByLocation } from "./util";
export interface HintState {
code: number;
msg?: string;
esHint?: string;
esSelectHint?: string;
}
export const initHintImpl: DuelReducer<void> = (state) => {
state.hint = { code: 0 };
};
export const fetchCommonHintMeta = createAsyncThunk(
"duel/fetchCommonHintMeta",
async (hintData: number) => {
return fetchStrings("!system", hintData);
}
);
export const fetchSelectHintMeta = createAsyncThunk(
"duel/fetchSelectHintMeta",
async (param: { selectHintData: number; esHint?: string }) => {
const selectHintData = param.selectHintData;
let selectHintMeta = "";
if (selectHintData > DESCRIPTION_LIMIT) {
// 针对`MSG_SELECT_PLACE`的特化逻辑
const cardMeta = await fetchCard(selectHintData, true);
selectHintMeta = fetchStrings("!system", 569).replace(
"[%ls]",
cardMeta.text.name || "[?]"
);
} else {
selectHintMeta = await getStrings(selectHintData);
}
return {
selectHintMeta,
esHint: param.esHint,
};
}
);
export const fetchEsHintMeta = createAsyncThunk(
"duel/fetchEsHintMeta",
async (param: {
originMsg: string | number;
location?: ygopro.CardLocation;
cardID?: number;
}) => {
const originMsg =
typeof param.originMsg === "string"
? param.originMsg
: fetchStrings("!system", param.originMsg);
const location = param.location;
if (param.cardID) {
const cardMeta = await fetchCard(param.cardID, true);
return { originMsg, cardMeta, location };
} else {
return { originMsg, location };
}
}
);
export const hintCase = (builder: ActionReducerMapBuilder<DuelState>) => {
builder.addCase(fetchCommonHintMeta.pending, (state, action) => {
const code = action.meta.arg;
if (state.hint) {
state.hint.code = code;
}
});
builder.addCase(fetchCommonHintMeta.fulfilled, (state, action) => {
const hintMeta = action.payload;
if (state.hint) {
state.hint.msg = hintMeta;
}
});
builder.addCase(fetchSelectHintMeta.pending, (state, action) => {
const code = action.meta.arg.selectHintData;
if (state.hint) {
state.hint.code = code;
}
});
builder.addCase(fetchSelectHintMeta.fulfilled, (state, action) => {
const selectHintMsg = action.payload.selectHintMeta;
const esHint = action.payload.esHint;
const hint = state.hint;
if (hint) {
if (hint.code > DESCRIPTION_LIMIT) {
// 针对`MSG_SELECT_PLACE`的特化逻辑
hint.msg = selectHintMsg;
} else {
hint.esSelectHint = selectHintMsg;
if (esHint) hint.esHint = esHint;
}
}
});
builder.addCase(fetchEsHintMeta.fulfilled, (state, action) => {
const originMsg = action.payload.originMsg;
const cardMeta = action.payload.cardMeta;
const location = action.payload.location;
const hint = state.hint;
if (hint) {
let esHint = originMsg;
if (cardMeta?.text.name) {
esHint = originMsg.replace("[?]", cardMeta.text.name);
}
if (location) {
const fieldMeta = findCardByLocation(state, location);
if (fieldMeta?.occupant?.text.name) {
esHint = originMsg.replace("[?]", fieldMeta.occupant.text.name);
}
}
hint.esHint = esHint;
}
});
};
export const selectHint = (state: RootState) => state.duel.hint;
import { CaseReducer, PayloadAction } from "@reduxjs/toolkit";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { RootState } from "@/store";
import { DuelState } from "./mod";
import { judgeSelf } from "./util";
import MsgUpdateHp = ygopro.StocGameMessage.MsgUpdateHp;
export interface InitInfo {
masterRule?: string;
life: number;
deckSize: number;
extraSize: number;
}
// 更新自己的初始生命值,卡组信息
export const infoInitImpl: CaseReducer<
DuelState,
PayloadAction<[number, InitInfo]>
> = (state, action) => {
const player = action.payload[0];
const initInfo = action.payload[1];
if (judgeSelf(player, state)) {
state.meInitInfo = initInfo;
} else {
state.opInitInfo = initInfo;
}
};
export const updateHpImpl: CaseReducer<
DuelState,
PayloadAction<ygopro.StocGameMessage.MsgUpdateHp>
> = (state, action) => {
const player = action.payload.player;
const actionType = action.payload.type_;
const value = action.payload.value;
const info = judgeSelf(player, state) ? state.meInitInfo : state.opInitInfo;
if (info) {
switch (actionType) {
case MsgUpdateHp.ActionType.DAMAGE: {
info.life = info.life - value;
break;
}
case MsgUpdateHp.ActionType.RECOVER: {
info.life = info.life + value;
break;
}
default: {
break;
}
}
}
};
export const selectMeInitInfo = (state: RootState) => state.duel.meInitInfo;
export const selectOpInitInfo = (state: RootState) => state.duel.opInitInfo;
import {
ActionReducerMapBuilder,
CaseReducer,
PayloadAction,
} from "@reduxjs/toolkit";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { RootState } from "@/store";
import {
clearIdleInteractivities,
clearPlaceInteractivities,
createAsyncMetaThunk,
DuelFieldState,
extendIdleInteractivities,
extendOccupant,
extendPlaceInteractivity,
Interactivity,
removeOccupant,
setPosition,
} from "./generic";
import { DuelState } from "./mod";
import { judgeSelf } from "./util";
export interface MagicState extends DuelFieldState {}
// 初始化自己的魔法陷阱区状态
export const initMagicsImpl: CaseReducer<DuelState, PayloadAction<number>> = (
state,
action
) => {
const player = action.payload;
const magics = {
inner: [
{
location: {
controler: player,
location: ygopro.CardZone.SZONE,
},
idleInteractivities: [],
counters: {},
},
{
location: {
controler: player,
location: ygopro.CardZone.SZONE,
},
idleInteractivities: [],
counters: {},
},
{
location: {
controler: player,
location: ygopro.CardZone.SZONE,
},
idleInteractivities: [],
counters: {},
},
{
location: {
controler: player,
location: ygopro.CardZone.SZONE,
},
idleInteractivities: [],
counters: {},
},
{
location: {
controler: player,
location: ygopro.CardZone.SZONE,
},
idleInteractivities: [],
counters: {},
},
{
// 场地区
location: {
controler: player,
location: ygopro.CardZone.SZONE,
},
idleInteractivities: [],
counters: {},
},
],
};
if (judgeSelf(player, state)) {
state.meMagics = magics;
} else {
state.opMagics = magics;
}
};
export const addMagicPlaceInteractivitiesImpl: CaseReducer<
DuelState,
PayloadAction<[number, number]>
> = (state, action) => {
const controler = action.payload[0];
const sequence = action.payload[1];
const magics = judgeSelf(controler, state) ? state.meMagics : state.opMagics;
extendPlaceInteractivity(magics, controler, sequence, ygopro.CardZone.SZONE);
};
export const clearMagicPlaceInteractivitiesImpl: CaseReducer<
DuelState,
PayloadAction<number>
> = (state, action) => {
const player = action.payload;
const magics = judgeSelf(player, state) ? state.meMagics : state.opMagics;
clearPlaceInteractivities(magics);
};
export const addMagicIdleInteractivitiesImpl: CaseReducer<
DuelState,
PayloadAction<{
player: number;
sequence: number;
interactivity: Interactivity<number>;
}>
> = (state, action) => {
const magics = judgeSelf(action.payload.player, state)
? state.meMagics
: state.opMagics;
extendIdleInteractivities(
magics,
action.payload.sequence,
action.payload.interactivity
);
};
export const clearMagicIdleInteractivitiesImpl: CaseReducer<
DuelState,
PayloadAction<number>
> = (state, action) => {
const magics = judgeSelf(action.payload, state)
? state.meMagics
: state.opMagics;
clearIdleInteractivities(magics);
};
// 增加魔法陷阱
export const fetchMagicMeta = createAsyncMetaThunk("duel/fetchMagicMeta");
export const magicCase = (builder: ActionReducerMapBuilder<DuelState>) => {
builder.addCase(fetchMagicMeta.pending, (state, action) => {
// Meta结果没返回之前先更新`ID`
const controler = action.meta.arg.controler;
const sequence = action.meta.arg.sequence;
const position = action.meta.arg.position;
const code = action.meta.arg.code;
const meta = { id: code, data: {}, text: {} };
if (judgeSelf(controler, state)) {
extendOccupant(state.meMagics, meta, sequence, position);
} else {
extendOccupant(state.opMagics, meta, sequence, position);
}
});
builder.addCase(fetchMagicMeta.fulfilled, (state, action) => {
const controler = action.payload.controler;
const sequence = action.payload.sequence;
const meta = action.payload.meta;
if (judgeSelf(controler, state)) {
extendOccupant(state.meMagics, meta, sequence);
} else {
extendOccupant(state.opMagics, meta, sequence);
}
});
};
// 删除魔法陷阱
export const removeMagicImpl: CaseReducer<
DuelState,
PayloadAction<{ controler: number; sequence: number }>
> = (state, action) => {
const controler = action.payload.controler;
const magics = judgeSelf(controler, state) ? state.meMagics : state.opMagics;
removeOccupant(magics, action.payload.sequence);
};
// 改变魔法表示形式
export const setMagicPositionImpl: CaseReducer<
DuelState,
PayloadAction<{
controler: number;
sequence: number;
position: ygopro.CardPosition;
}>
> = (state, action) => {
const controler = action.payload.controler;
const sequence = action.payload.sequence;
const position = action.payload.position;
const magics = judgeSelf(controler, state) ? state.meMagics : state.opMagics;
setPosition(magics, sequence, position);
};
export const selectMeMagics = (state: RootState) =>
state.duel.meMagics || { inner: [] };
export const selectOpMagics = (state: RootState) =>
state.duel.opMagics || { inner: [] };
/*
* 对局内的状态更新逻辑
*
* */
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { RootState } from "@/store";
import {
addBanishedZoneIdleInteractivitiesImpl,
banishedZoneCase,
BanishedZoneState,
initBanishedZoneImpl,
removeBanishedZoneImpl,
} from "./banishedZoneSlice";
import {
clearAllIdleInteractivitiesImpl,
clearAllPlaceInteractivitiesImpl,
reloadFieldImpl,
updateFieldDataImpl,
} from "./commonSlice";
import { DeckState, initDeckImpl } from "./deckSlice";
import {
addExtraDeckIdleInteractivitiesImpl,
extraDeckCase,
ExtraDeckState,
removeExtraDeckImpl,
} from "./extraDeckSlice";
import {
addGraveyardIdleInteractivitiesImpl,
graveyardCase,
GraveyardState,
initGraveyardImpl,
removeGraveyardImpl,
} from "./graveyardSlice";
import {
addHandsIdleInteractivityImpl,
clearHandsIdleInteractivityImpl,
handsCase,
HandState,
removeHandImpl,
} from "./handsSlice";
import { hintCase, HintState, initHintImpl } from "./hintSlice";
import { infoInitImpl, InitInfo, updateHpImpl } from "./initInfoSlice";
import {
addMagicIdleInteractivitiesImpl,
addMagicPlaceInteractivitiesImpl,
clearMagicIdleInteractivitiesImpl,
clearMagicPlaceInteractivitiesImpl,
initMagicsImpl,
magicCase,
MagicState,
removeMagicImpl,
setMagicPositionImpl,
} from "./magicSlice";
import {
checkCardModalCase,
checkCardModalV2Case,
checkCardModalV3Case,
clearCheckCounterImpl,
ModalState,
optionModalCase,
resetCheckCardModalImpl,
resetCheckCardModalV2Impl,
resetCheckCardModalV3Impl,
resetOptionModalImpl,
resetPositionModalImpl,
resetSortCardModalImpl,
setCardListModalInfoImpl,
setCardListModalIsOpenImpl,
setCardModalCountersImpl,
setCardModalInteractiviesImpl,
setCardModalIsOpenImpl,
setCardModalMetaImpl,
setCheckCardMOdalCancelAbleImpl,
setCheckCardModalCancelResponseImpl,
setCheckCardModalIsOpenImpl,
setCheckCardModalMinMaxImpl,
setCheckCardModalOnSubmitImpl,
setCheckCardModalV2CancelAbleImpl,
setCheckCardModalV2FinishAbleImpl,
setCheckCardModalV2IsOpenImpl,
setCheckCardModalV2MinMaxImpl,
setCheckCardModalV2ResponseAbleImpl,
setCheckCardModalV3AllLevelImpl,
setCheckCardModalV3IsOpenImpl,
setCheckCardModalV3MinMaxImpl,
setCheckCardModalV3OverFlowImpl,
setCheckCardModalV3ResponseAbleImpl,
setCheckCounterImpl,
setOptionModalIsOpenImpl,
setPositionModalIsOpenImpl,
setPositionModalPositionsImpl,
setSortCardModalIsOpenImpl,
setYesNoModalIsOpenImpl,
sortCardModalCase,
YesNoModalCase,
} from "./modal/mod";
import {
addMonsterIdleInteractivitiesImpl,
addMonsterPlaceInteractivitiesImpl,
clearMonsterIdleInteractivitiesImpl,
clearMonsterPlaceInteractivitiesImpl,
initMonstersImpl,
monsterCase,
MonsterState,
removeMonsterImpl,
removeOverlayImpl,
setMonsterPositionImpl,
updateMonsterCountersImpl,
} from "./monstersSlice";
import {
newPhaseImpl,
PhaseState,
setEnableBpImpl,
setEnableEpImpl,
setEnableM2Impl,
} from "./phaseSlice";
import { TimeLimit, updateTimeLimitImpl } from "./timeLimit";
import { newTurnImpl } from "./turnSlice";
import MsgWin = ygopro.StocGameMessage.MsgWin;
export interface DuelState {
selfType?: number;
meInitInfo?: InitInfo; // 自己的初始状态
opInitInfo?: InitInfo; // 对手的初始状态
meHands?: HandState; // 自己的手牌
opHands?: HandState; // 对手的手牌
meMonsters?: MonsterState; // 自己的怪兽区状态
opMonsters?: MonsterState; // 对手的怪兽区状态
meMagics?: MagicState; // 自己的魔法陷阱区状态
opMagics?: MagicState; // 对手的魔法陷阱区状态
meGraveyard?: GraveyardState; // 自己的墓地状态
opGraveyard?: GraveyardState; // 对手的墓地状态
meBanishedZone?: BanishedZoneState; // 自己的除外区状态
opBanishedZone?: BanishedZoneState; // 对手的除外区状态
meDeck?: DeckState; // 自己的卡组状态
opDeck?: DeckState; // 对手的卡组状态
meExtraDeck?: ExtraDeckState; // 自己的额外卡组状态
opExtraDeck?: ExtraDeckState; // 对手的额外卡组状态
meTimeLimit?: TimeLimit; // 自己的计时
opTimeLimit?: TimeLimit; // 对手的计时
hint?: HintState;
currentPlayer?: number; // 当前的操作方
phase?: PhaseState;
result?: MsgWin.ActionType;
waiting?: boolean;
unimplemented?: number; // 未处理的`Message`
// UI相关
modalState: ModalState;
}
const initialState: DuelState = {
modalState: {
cardModal: { isOpen: false, interactivies: [], counters: {} },
cardListModal: { isOpen: false, list: [] },
checkCardModal: { isOpen: false, cancelAble: false, tags: [] },
yesNoModal: { isOpen: false },
positionModal: { isOpen: false, positions: [] },
optionModal: { isOpen: false, options: [] },
checkCardModalV2: {
isOpen: false,
cancelAble: false,
finishAble: false,
responseable: false,
selectableOptions: [],
selectedOptions: [],
},
checkCardModalV3: {
isOpen: false,
overflow: false,
allLevel: 0,
mustSelectList: [],
selectAbleList: [],
},
checkCounterModal: {
isOpen: false,
options: [],
},
sortCardModal: {
isOpen: false,
options: [],
},
},
};
const duelSlice = createSlice({
name: "duel",
initialState,
reducers: {
setSelfType: (state, action: PayloadAction<number>) => {
state.selfType = action.payload;
},
infoInit: infoInitImpl,
updateHp: updateHpImpl,
updateTurn: newTurnImpl,
updateTimeLimit: updateTimeLimitImpl,
// 手牌相关`Reducer`
clearHandsIdleInteractivity: clearHandsIdleInteractivityImpl,
addHandsIdleInteractivity: addHandsIdleInteractivityImpl,
removeHand: removeHandImpl,
// 怪兽区相关`Reducer`
initMonsters: initMonstersImpl,
addMonsterPlaceInteractivities: addMonsterPlaceInteractivitiesImpl,
clearMonsterPlaceInteractivities: clearMonsterPlaceInteractivitiesImpl,
addMonsterIdleInteractivities: addMonsterIdleInteractivitiesImpl,
clearMonsterIdleInteractivities: clearMonsterIdleInteractivitiesImpl,
setMonsterPosition: setMonsterPositionImpl,
removeMonster: removeMonsterImpl,
removeOverlay: removeOverlayImpl,
updateMonsterCounters: updateMonsterCountersImpl,
// 魔法陷阱区相关`Reducer`
initMagics: initMagicsImpl,
addMagicPlaceInteractivities: addMagicPlaceInteractivitiesImpl,
clearMagicPlaceInteractivities: clearMagicPlaceInteractivitiesImpl,
addMagicIdleInteractivities: addMagicIdleInteractivitiesImpl,
clearMagicIdleInteractivities: clearMagicIdleInteractivitiesImpl,
setMagicPosition: setMagicPositionImpl,
removeMagic: removeMagicImpl,
// 墓地相关`Reducer`
initGraveyard: initGraveyardImpl,
removeGraveyard: removeGraveyardImpl,
addGraveyardIdleInteractivities: addGraveyardIdleInteractivitiesImpl,
// 除外区相关`Reducer`
initBanishedZone: initBanishedZoneImpl,
removeBanishedZone: removeBanishedZoneImpl,
addBanishedZoneIdleInteractivities: addBanishedZoneIdleInteractivitiesImpl,
// 卡组相关`Reducer`
initDeck: initDeckImpl,
// 额外卡组相关`Reducer`
removeExtraDeck: removeExtraDeckImpl,
addExtraDeckIdleInteractivities: addExtraDeckIdleInteractivitiesImpl,
// 阶段相关
updatePhase: newPhaseImpl,
setEnableBp: setEnableBpImpl,
setEnableM2: setEnableM2Impl,
setEnableEp: setEnableEpImpl,
// UI相关`Reducer`
setCardModalIsOpen: setCardModalIsOpenImpl,
setCardModalMeta: setCardModalMetaImpl,
setCardModalInteractivies: setCardModalInteractiviesImpl,
setCardListModalIsOpen: setCardListModalIsOpenImpl,
setCardListModalInfo: setCardListModalInfoImpl,
setCheckCardModalIsOpen: setCheckCardModalIsOpenImpl,
setCheckCardModalMinMax: setCheckCardModalMinMaxImpl,
setCheckCardModalOnSubmit: setCheckCardModalOnSubmitImpl,
setCheckCardMOdalCancelAble: setCheckCardMOdalCancelAbleImpl,
setCheckCardModalCancelResponse: setCheckCardModalCancelResponseImpl,
resetCheckCardModal: resetCheckCardModalImpl,
setYesNoModalIsOpen: setYesNoModalIsOpenImpl,
setPositionModalIsOpen: setPositionModalIsOpenImpl,
setPositionModalPositions: setPositionModalPositionsImpl,
resetPositionModal: resetPositionModalImpl,
setOptionModalIsOpen: setOptionModalIsOpenImpl,
resetOptionModal: resetOptionModalImpl,
setCheckCardModalV2FinishAble: setCheckCardModalV2FinishAbleImpl,
setCheckCardModalV2MinMax: setCheckCardModalV2MinMaxImpl,
setCheckCardModalV2CancelAble: setCheckCardModalV2CancelAbleImpl,
setCheckCardModalV2IsOpen: setCheckCardModalV2IsOpenImpl,
resetCheckCardModalV2: resetCheckCardModalV2Impl,
setCheckCardModalV2ResponseAble: setCheckCardModalV2ResponseAbleImpl,
setCheckCardModalV3IsOpen: setCheckCardModalV3IsOpenImpl,
setCheckCardModalV3MinMax: setCheckCardModalV3MinMaxImpl,
setCheckCardModalV3AllLevel: setCheckCardModalV3AllLevelImpl,
setCheckCardModalV3OverFlow: setCheckCardModalV3OverFlowImpl,
setCheckCardModalV3ResponseAble: setCheckCardModalV3ResponseAbleImpl,
resetCheckCardModalV3: resetCheckCardModalV3Impl,
setCardModalCounters: setCardModalCountersImpl,
setCheckCounter: setCheckCounterImpl,
clearCheckCounter: clearCheckCounterImpl,
setSortCardModalIsOpen: setSortCardModalIsOpenImpl,
resetSortCardModal: resetSortCardModalImpl,
// 提示相关`Reducer`
initHint: initHintImpl,
// 通用的`Reducer`
clearAllIdleInteractivities: clearAllIdleInteractivitiesImpl,
clearAllPlaceInteractivities: clearAllPlaceInteractivitiesImpl,
updateFieldData: updateFieldDataImpl,
reloadField: reloadFieldImpl,
// 对局结果`Reducer`
setResult: (state, action: PayloadAction<MsgWin.ActionType>) => {
state.result = action.payload;
},
// 等待状态`Reducer`
setWaiting: (state, action: PayloadAction<boolean>) => {
state.waiting = action.payload;
},
// 未处理状态`Reducer`
setUnimplemented: (state, action: PayloadAction<number>) => {
state.unimplemented = action.payload;
},
},
extraReducers(builder) {
handsCase(builder);
hintCase(builder);
monsterCase(builder);
magicCase(builder);
graveyardCase(builder);
banishedZoneCase(builder);
extraDeckCase(builder);
checkCardModalCase(builder);
YesNoModalCase(builder);
optionModalCase(builder);
checkCardModalV2Case(builder);
checkCardModalV3Case(builder);
sortCardModalCase(builder);
},
});
export const {
setSelfType,
infoInit,
updateHp,
updateTurn,
updatePhase,
setEnableBp,
setEnableM2,
setEnableEp,
clearHandsIdleInteractivity,
addHandsIdleInteractivity,
updateTimeLimit,
setCardModalIsOpen,
setCardModalMeta,
setCardModalInteractivies,
initMonsters,
addMonsterPlaceInteractivities,
clearMonsterPlaceInteractivities,
addMonsterIdleInteractivities,
clearMonsterIdleInteractivities,
setMonsterPosition,
removeMonster,
updateMonsterCounters,
removeOverlay,
initMagics,
addMagicPlaceInteractivities,
clearMagicPlaceInteractivities,
addMagicIdleInteractivities,
clearMagicIdleInteractivities,
setMagicPosition,
removeMagic,
removeHand,
initGraveyard,
removeGraveyard,
addGraveyardIdleInteractivities,
setCardListModalIsOpen,
setCardListModalInfo,
setCheckCardModalIsOpen,
setCheckCardModalMinMax,
setCheckCardModalOnSubmit,
setCheckCardMOdalCancelAble,
setCheckCardModalCancelResponse,
resetCheckCardModal,
setYesNoModalIsOpen,
setPositionModalIsOpen,
setPositionModalPositions,
resetPositionModal,
setOptionModalIsOpen,
resetOptionModal,
initDeck,
removeExtraDeck,
addExtraDeckIdleInteractivities,
initBanishedZone,
removeBanishedZone,
addBanishedZoneIdleInteractivities,
setCheckCardModalV2IsOpen,
setCheckCardModalV2MinMax,
setCheckCardModalV2CancelAble,
setCheckCardModalV2FinishAble,
resetCheckCardModalV2,
setCheckCardModalV2ResponseAble,
clearAllIdleInteractivities,
clearAllPlaceInteractivities,
setResult,
setWaiting,
setUnimplemented,
updateFieldData,
reloadField,
setCheckCardModalV3IsOpen,
setCheckCardModalV3MinMax,
setCheckCardModalV3AllLevel,
setCheckCardModalV3OverFlow,
setCheckCardModalV3ResponseAble,
resetCheckCardModalV3,
setCardModalCounters,
setCheckCounter,
clearCheckCounter,
setSortCardModalIsOpen,
resetSortCardModal,
initHint,
} = duelSlice.actions;
export const selectDuelHsStart = (state: RootState) => {
return state.duel.meInitInfo != null;
};
export const selectDuelResult = (state: RootState) => {
return state.duel.result;
};
export const selectWaiting = (state: RootState) => {
return state.duel.waiting;
};
export const selectUnimplemented = (state: RootState) => {
return state.duel.unimplemented;
};
export default duelSlice.reducer;
import { CaseReducer, PayloadAction } from "@reduxjs/toolkit";
import { CardMeta } from "@/api/cards";
import { RootState } from "@/store";
import { DuelState } from "../mod";
// 更新卡牌列表弹窗打开状态
export const setCardListModalIsOpenImpl: CaseReducer<
DuelState,
PayloadAction<boolean>
> = (state, action) => {
state.modalState.cardListModal.isOpen = action.payload;
};
// 更新卡牌列表数据
export const setCardListModalInfoImpl: CaseReducer<
DuelState,
PayloadAction<
{
meta?: CardMeta;
interactivies: { desc: string; response: number }[];
}[]
>
> = (state, action) => {
const list = action.payload;
state.modalState.cardListModal.list = list;
};
export const selectCardListModalIsOpen = (state: RootState) =>
state.duel.modalState.cardListModal.isOpen;
export const selectCardListModalInfo = (state: RootState) =>
state.duel.modalState.cardListModal.list;
import { CaseReducer, PayloadAction } from "@reduxjs/toolkit";
import { CardMeta } from "@/api/cards";
import { RootState } from "@/store";
import { DuelState } from "../mod";
// 更新卡牌弹窗打开状态
export const setCardModalIsOpenImpl: CaseReducer<
DuelState,
PayloadAction<boolean>
> = (state, action) => {
state.modalState.cardModal.isOpen = action.payload;
};
// 更新卡牌弹窗文本
export const setCardModalMetaImpl: CaseReducer<
DuelState,
PayloadAction<CardMeta>
> = (state, action) => {
state.modalState.cardModal.meta = action.payload;
};
// 更新卡牌弹窗互动选项
export const setCardModalInteractiviesImpl: CaseReducer<
DuelState,
PayloadAction<{ desc: string; response: number }[]>
> = (state, action) => {
state.modalState.cardModal.interactivies = action.payload;
};
// 更新卡牌弹窗指示器
export const setCardModalCountersImpl: CaseReducer<
DuelState,
PayloadAction<{ [type: number]: number }>
> = (state, action) => {
state.modalState.cardModal.counters = action.payload;
};
export const selectCardModalIsOpen = (state: RootState) =>
state.duel.modalState.cardModal.isOpen;
export const selectCardModalMeta = (state: RootState) =>
state.duel.modalState.cardModal.meta;
export const selectCardModalInteractivies = (state: RootState) =>
state.duel.modalState.cardModal.interactivies;
export const selectCardModalCounters = (state: RootState) =>
state.duel.modalState.cardModal.counters;
import {
ActionReducerMapBuilder,
CaseReducer,
createAsyncThunk,
PayloadAction,
} from "@reduxjs/toolkit";
import { fetchCard, getCardStr } from "@/api/cards";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { RootState } from "@/store";
import { DuelState } from "../mod";
import { cmpCardLocation, findCardByLocation, judgeSelf } from "../util";
// 更新卡牌选择弹窗打开状态
export const setCheckCardModalIsOpenImpl: CaseReducer<
DuelState,
PayloadAction<boolean>
> = (state, action) => {
state.modalState.checkCardModal.isOpen = action.payload;
};
// 更新卡牌选择弹窗选择数目状态
export const setCheckCardModalMinMaxImpl: CaseReducer<
DuelState,
PayloadAction<{ min: number; max: number }>
> = (state, action) => {
state.modalState.checkCardModal.selectMin = action.payload.min;
state.modalState.checkCardModal.selectMax = action.payload.max;
};
// 更新卡牌选择弹窗的提交回调
export const setCheckCardModalOnSubmitImpl: CaseReducer<
DuelState,
PayloadAction<string>
> = (state, action) => {
state.modalState.checkCardModal.onSubmit = action.payload;
};
// 更新卡牌选择弹窗是否可以取消
export const setCheckCardMOdalCancelAbleImpl: CaseReducer<
DuelState,
PayloadAction<boolean>
> = (state, action) => {
state.modalState.checkCardModal.cancelAble = action.payload;
};
// 更新卡牌选择弹窗取消时返回给服务端的`Response`
export const setCheckCardModalCancelResponseImpl: CaseReducer<
DuelState,
PayloadAction<number>
> = (state, action) => {
state.modalState.checkCardModal.cancelResponse = action.payload;
};
// 增加卡牌选择选项
export const fetchCheckCardMeta = createAsyncThunk(
"duel/fetchCheckCardMeta",
async (param: {
tagName: string;
option: {
code: number;
location: ygopro.CardLocation;
response: number;
effectDescCode?: number;
};
}) => {
// FIXME: 这里如果传的`controler`如果是对手,对应的`code`会为零,这时候就无法更新对应的`Meta`信息了,后续需要修复。`fetchCheckCardMetaV2`和`fetchCheckCardMetaV3`同理
const meta = await fetchCard(param.option.code, true);
const response = {
tagName: param.tagName,
option: {
meta,
location: param.option.location.toObject(),
},
};
return response;
}
);
export const checkCardModalCase = (
builder: ActionReducerMapBuilder<DuelState>
) => {
builder.addCase(fetchCheckCardMeta.pending, (state, action) => {
const tagName = action.meta.arg.tagName;
const code = action.meta.arg.option.code;
const location = action.meta.arg.option.location;
const controler = location.controler;
const effectDescCode = action.meta.arg.option.effectDescCode;
const response = action.meta.arg.option.response;
const combinedTagName = judgeSelf(controler, state)
? `我方的${tagName}`
: `对方的${tagName}`;
const newID =
code != 0 ? code : findCardByLocation(state, location)?.occupant?.id || 0;
const newOption = {
meta: { id: newID, data: {}, text: {} },
location: location.toObject(),
effectDescCode,
response,
};
for (const tag of state.modalState.checkCardModal.tags) {
if (tag.tagName === combinedTagName) {
tag.options.push(newOption);
return;
}
}
state.modalState.checkCardModal.tags.push({
tagName: combinedTagName,
options: [newOption],
});
});
builder.addCase(fetchCheckCardMeta.fulfilled, (state, action) => {
const tagName = action.payload.tagName;
const option = action.payload.option;
const controler = option.location.controler!;
const combinedTagName = judgeSelf(controler, state)
? `我方的${tagName}`
: `对方的${tagName}`;
for (const tag of state.modalState.checkCardModal.tags) {
if (tag.tagName === combinedTagName) {
for (const old of tag.options) {
if (
option.meta.id == old.meta.id &&
cmpCardLocation(option.location, old.location)
) {
const cardID = old.meta.id;
old.meta = option.meta;
old.meta.id = cardID;
const effectDescCode = old.effectDescCode;
const effectDesc = effectDescCode
? getCardStr(old.meta, effectDescCode & 0xf)
: undefined;
old.effectDesc = effectDesc;
}
}
}
}
});
};
export const resetCheckCardModalImpl: CaseReducer<DuelState> = (state) => {
state.modalState.checkCardModal.isOpen = false;
state.modalState.checkCardModal.selectMin = undefined;
state.modalState.checkCardModal.selectMax = undefined;
state.modalState.checkCardModal.cancelAble = false;
state.modalState.checkCardModal.cancelResponse = undefined;
state.modalState.checkCardModal.tags = [];
};
export const selectCheckCardModalIsOpen = (state: RootState) =>
state.duel.modalState.checkCardModal.isOpen;
export const selectCheckCardModalMinMax = (state: RootState) => {
return {
min: state.duel.modalState.checkCardModal.selectMin || 0,
max: state.duel.modalState.checkCardModal.selectMax || 0,
};
};
export const selectCheckCardModalTags = (state: RootState) =>
state.duel.modalState.checkCardModal.tags;
export const selectCheckCardModalOnSubmit = (state: RootState) =>
state.duel.modalState.checkCardModal.onSubmit;
export const selectCheckCardModalCancelAble = (state: RootState) =>
state.duel.modalState.checkCardModal.cancelAble;
export const selectCheckCardModalCacnelResponse = (state: RootState) =>
state.duel.modalState.checkCardModal.cancelResponse;
import {
ActionReducerMapBuilder,
CaseReducer,
createAsyncThunk,
} from "@reduxjs/toolkit";
import { fetchCard } from "@/api/cards";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { RootState } from "@/store";
import { DuelReducer } from "../generic";
import { DuelState } from "../mod";
import { findCardByLocation } from "../util";
// 更新打开状态
export const setCheckCardModalV2IsOpenImpl: DuelReducer<boolean> = (
state,
action
) => {
state.modalState.checkCardModalV2.isOpen = action.payload;
};
// 更新选择数目
export const setCheckCardModalV2MinMaxImpl: DuelReducer<{
min: number;
max: number;
}> = (state, action) => {
state.modalState.checkCardModalV2.selectMin = action.payload.min;
state.modalState.checkCardModalV2.selectMax = action.payload.max;
};
// 更新是否可以取消
export const setCheckCardModalV2CancelAbleImpl: DuelReducer<boolean> = (
state,
action
) => {
state.modalState.checkCardModalV2.cancelAble = action.payload;
};
// 更新是否可以结束
export const setCheckCardModalV2FinishAbleImpl: DuelReducer<boolean> = (
state,
action
) => {
state.modalState.checkCardModalV2.finishAble = action.payload;
};
// 更新是否可以回应
export const setCheckCardModalV2ResponseAbleImpl: DuelReducer<boolean> = (
state,
action
) => {
state.modalState.checkCardModalV2.responseable = action.payload;
};
// 增加卡牌选项
export const fetchCheckCardMetasV2 = createAsyncThunk(
"duel/fetchCheckCardMetaV2",
async (param: {
selected: boolean;
options: {
code: number;
location: ygopro.CardLocation;
response: number;
}[];
}) => {
const metas = await Promise.all(
param.options.map(async (option) => {
return await fetchCard(option.code, true);
})
);
const response = {
selected: param.selected,
metas,
};
return response;
}
);
export const checkCardModalV2Case = (
builder: ActionReducerMapBuilder<DuelState>
) => {
builder.addCase(fetchCheckCardMetasV2.pending, (state, action) => {
const selected = action.meta.arg.selected;
const options = action.meta.arg.options;
for (const option of options) {
if (option.code == 0) {
const newCode =
findCardByLocation(state, option.location)?.occupant?.id || 0;
option.code = newCode;
}
}
if (selected) {
state.modalState.checkCardModalV2.selectedOptions = options;
} else {
state.modalState.checkCardModalV2.selectableOptions = options;
}
});
builder.addCase(fetchCheckCardMetasV2.fulfilled, (state, action) => {
const selected = action.payload.selected;
const metas = action.payload.metas;
const options = selected
? state.modalState.checkCardModalV2.selectedOptions
: state.modalState.checkCardModalV2.selectableOptions;
options.forEach((option) => {
metas.forEach((meta) => {
if (option.code == meta.id) {
option.name = meta.text.name;
option.desc = meta.text.desc;
}
});
});
});
};
export const resetCheckCardModalV2Impl: CaseReducer<DuelState> = (state) => {
const modalState = state.modalState.checkCardModalV2;
modalState.isOpen = false;
modalState.finishAble = false;
modalState.cancelAble = false;
modalState.responseable = false;
modalState.selectableOptions = [];
modalState.selectedOptions = [];
};
export const selectCheckCardModalV2IsOpen = (state: RootState) =>
state.duel.modalState.checkCardModalV2.isOpen;
export const selectCheckCardModalV2MinMax = (state: RootState) => {
return {
min: state.duel.modalState.checkCardModalV2.selectMin || 0,
max: state.duel.modalState.checkCardModalV2.selectMax || 0,
};
};
export const selectCheckCardModalV2CancelAble = (state: RootState) =>
state.duel.modalState.checkCardModalV2.cancelAble;
export const selectCheckCardModalV2FinishAble = (state: RootState) =>
state.duel.modalState.checkCardModalV2.finishAble;
export const selectCheckCardModalV2ResponseAble = (state: RootState) =>
state.duel.modalState.checkCardModalV2.responseable;
export const selectCheckCardModalV2SelectAbleOptions = (state: RootState) =>
state.duel.modalState.checkCardModalV2.selectableOptions;
export const selectCheckCardModalV2SelectedOptions = (state: RootState) =>
state.duel.modalState.checkCardModalV2.selectedOptions;
import {
ActionReducerMapBuilder,
CaseReducer,
createAsyncThunk,
} from "@reduxjs/toolkit";
import { fetchCard } from "@/api/cards";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { RootState } from "@/store";
import { DuelReducer } from "../generic";
import { DuelState } from "../mod";
import { findCardByLocation } from "../util";
// 更新打开状态
export const setCheckCardModalV3IsOpenImpl: DuelReducer<boolean> = (
state,
action
) => {
state.modalState.checkCardModalV3.isOpen = action.payload;
};
// 更新选择数目
export const setCheckCardModalV3MinMaxImpl: DuelReducer<{
min: number;
max: number;
}> = (state, action) => {
state.modalState.checkCardModalV3.selectMin = action.payload.min;
state.modalState.checkCardModalV3.selectMax = action.payload.max;
};
export const setCheckCardModalV3AllLevelImpl: DuelReducer<number> = (
state,
action
) => {
state.modalState.checkCardModalV3.allLevel = action.payload;
};
export const setCheckCardModalV3OverFlowImpl: DuelReducer<boolean> = (
state,
action
) => {
state.modalState.checkCardModalV3.overflow = action.payload;
};
export const setCheckCardModalV3ResponseAbleImpl: DuelReducer<boolean> = (
state,
action
) => {
state.modalState.checkCardModalV3.responseable = action.payload;
};
// 增加卡牌选项
export const fetchCheckCardMetasV3 = createAsyncThunk(
"duel/fetchCheckCardMetaV3",
async (param: {
mustSelect: boolean;
options: {
code: number;
location: ygopro.CardLocation;
level1: number;
level2: number;
response: number;
}[];
}) => {
const metas = await Promise.all(
param.options.map(async (option) => {
return await fetchCard(option.code, true);
})
);
const response = {
mustSelect: param.mustSelect,
metas,
};
return response;
}
);
export const checkCardModalV3Case = (
builder: ActionReducerMapBuilder<DuelState>
) => {
builder.addCase(fetchCheckCardMetasV3.pending, (state, action) => {
const mustSelect = action.meta.arg.mustSelect;
const options = action.meta.arg.options.map((option) => {
if (option.code == 0) {
const newCode =
findCardByLocation(state, option.location)?.occupant?.id || 0;
option.code = newCode;
}
return {
meta: { id: option.code, data: {}, text: {} },
level1: option.level1,
level2: option.level2,
response: option.response,
};
});
if (mustSelect) {
state.modalState.checkCardModalV3.mustSelectList = options;
} else {
state.modalState.checkCardModalV3.selectAbleList = options;
}
});
builder.addCase(fetchCheckCardMetasV3.fulfilled, (state, action) => {
const mustSelect = action.payload.mustSelect;
const metas = action.payload.metas;
const options = mustSelect
? state.modalState.checkCardModalV3.mustSelectList
: state.modalState.checkCardModalV3.selectAbleList;
options.forEach((option) => {
metas.forEach((meta) => {
if (option.meta.id == meta.id) {
option.meta = meta;
}
});
});
});
};
export const resetCheckCardModalV3Impl: CaseReducer<DuelState> = (state) => {
const modalState = state.modalState.checkCardModalV3;
modalState.isOpen = false;
modalState.overflow = false;
modalState.allLevel = 0;
modalState.responseable = undefined;
modalState.mustSelectList = [];
modalState.selectAbleList = [];
};
export const selectCheckCardModalV3 = (state: RootState) =>
state.duel.modalState.checkCardModalV3;
// 后续对于`MSG_SELECT_XXX`的处理UI都尽量用`Babylon.js`实现而不会通过`Antd`的`Modal`实现,因此这里不追求工程质量,暂时简单实现下。
import { CaseReducer, PayloadAction } from "@reduxjs/toolkit";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { RootState } from "@/store";
import { DuelState } from "../mod";
import { findCardByLocation } from "../util";
type SelectCounter = ReturnType<
typeof ygopro.StocGameMessage.MsgSelectCounter.prototype.toObject
>;
export const setCheckCounterImpl: CaseReducer<
DuelState,
PayloadAction<SelectCounter>
> = (state, action) => {
const modal = state.modalState.checkCounterModal;
const payload = action.payload;
modal.counterType = payload.counter_type;
modal.min = payload.min;
modal.options = payload.options!.map((option) => {
const code = option.code
? option.code
: findCardByLocation(state, option.location!)?.occupant?.id || 0;
return {
code,
max: option.counter_count!,
};
});
modal.isOpen = true;
};
export const clearCheckCounterImpl: CaseReducer<DuelState> = (state) => {
state.modalState.checkCounterModal.isOpen = false;
state.modalState.checkCounterModal.min = undefined;
state.modalState.checkCounterModal.counterType = undefined;
state.modalState.checkCounterModal.options = [];
};
export const selectCheckCounterModal = (state: RootState) =>
state.duel.modalState.checkCounterModal;
import {
ActionReducerMapBuilder,
CaseReducer,
createAsyncThunk,
PayloadAction,
} from "@reduxjs/toolkit";
import { fetchCard, getCardStr } from "@/api/cards";
import { RootState } from "@/store";
import { DuelState } from "../mod";
export const setOptionModalIsOpenImpl: CaseReducer<
DuelState,
PayloadAction<boolean>
> = (state, action) => {
state.modalState.optionModal.isOpen = action.payload;
};
export const resetOptionModalImpl: CaseReducer<DuelState> = (state) => {
state.modalState.optionModal.options = [];
};
// 增加选项
export const fetchOptionMeta = createAsyncThunk(
"duel/fetchOptionMeta",
async (param: { code: number; response: number }) => {
const meta = await fetchCard(param.code >> 4, true);
const msg = getCardStr(meta, param.code & 0xf) || "[?]";
const response = { msg, response: param.response };
return response;
}
);
export const optionModalCase = (
builder: ActionReducerMapBuilder<DuelState>
) => {
builder.addCase(fetchOptionMeta.fulfilled, (state, action) => {
state.modalState.optionModal.options.push(action.payload);
});
};
export const selectOptionModalIsOpen = (state: RootState) =>
state.duel.modalState.optionModal.isOpen;
export const selectOptionModalOptions = (state: RootState) =>
state.duel.modalState.optionModal.options;
import { CaseReducer, PayloadAction } from "@reduxjs/toolkit";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { RootState } from "@/store";
import { DuelState } from "../mod";
export const setPositionModalIsOpenImpl: CaseReducer<
DuelState,
PayloadAction<boolean>
> = (state, action) => {
state.modalState.positionModal.isOpen = action.payload;
};
export const setPositionModalPositionsImpl: CaseReducer<
DuelState,
PayloadAction<ygopro.CardPosition[]>
> = (state, action) => {
state.modalState.positionModal.positions = action.payload;
};
export const resetPositionModalImpl: CaseReducer<DuelState> = (state) => {
state.modalState.positionModal.isOpen = false;
state.modalState.positionModal.positions = [];
};
export const selectPositionModalIsOpen = (state: RootState) =>
state.duel.modalState.positionModal.isOpen;
export const selectPositionModalPositions = (state: RootState) =>
state.duel.modalState.positionModal.positions;
import {
ActionReducerMapBuilder,
CaseReducer,
createAsyncThunk,
} from "@reduxjs/toolkit";
import { fetchCard } from "@/api/cards";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { RootState } from "@/store";
import { DuelReducer } from "../generic";
import { DuelState } from "../mod";
type SortCard = ReturnType<
typeof ygopro.StocGameMessage.MsgSortCard.Info.prototype.toObject
>;
export const setSortCardModalIsOpenImpl: DuelReducer<boolean> = (
state,
action
) => {
state.modalState.sortCardModal.isOpen = action.payload;
};
export const resetSortCardModalImpl: CaseReducer<DuelState> = (state) => {
state.modalState.sortCardModal.isOpen = false;
state.modalState.sortCardModal.options = [];
};
export const fetchSortCardMeta = createAsyncThunk(
"duel/fetchSortCardMeta",
async (param: SortCard) => {
const meta = await fetchCard(param.code!, true);
return {
meta,
response: param.response!,
};
}
);
export const sortCardModalCase = (
builder: ActionReducerMapBuilder<DuelState>
) => {
// 这里更合理的做法是`pending`的时候先更新`options`,等`meta`数据返回后再异步更新`meta`
builder.addCase(fetchSortCardMeta.fulfilled, (state, action) => {
state.modalState.sortCardModal.options.push(action.payload);
});
};
export const selectSortCardModal = (state: RootState) =>
state.duel.modalState.sortCardModal;
import {
ActionReducerMapBuilder,
CaseReducer,
createAsyncThunk,
PayloadAction,
} from "@reduxjs/toolkit";
import { CardMeta, fetchCard } from "@/api/cards";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { fetchStrings, getStrings } from "@/api/strings";
import { RootState } from "@/store";
import { DuelState } from "../mod";
// 更新YesNo弹窗是否打开状态
export const setYesNoModalIsOpenImpl: CaseReducer<
DuelState,
PayloadAction<boolean>
> = (state, action) => {
state.modalState.yesNoModal.isOpen = action.payload;
};
// 设置YesNo弹窗展示内容
export const fetchYesNoMeta = createAsyncThunk(
"duel/fetchYesNoMeta",
async (param: {
code: number;
location: ygopro.CardLocation;
descCode: number;
textGenerator: (
desc: string,
cardMeta: CardMeta,
cardLocation: ygopro.CardLocation
) => string;
}) => {
const desc = fetchStrings("!system", param.descCode);
const meta = await fetchCard(param.code, true);
// TODO: 国际化文案
return param.textGenerator(desc, meta, param.location);
}
);
export const fetchYesNoMetaWithEffecDesc = createAsyncThunk(
"duel/fetchYesNoMetaWithEffecDesc",
async (effectDesc: number) => {
return getStrings(effectDesc);
}
);
export const YesNoModalCase = (builder: ActionReducerMapBuilder<DuelState>) => {
builder.addCase(fetchYesNoMeta.fulfilled, (state, action) => {
state.modalState.yesNoModal.msg = action.payload;
});
builder.addCase(fetchYesNoMetaWithEffecDesc.fulfilled, (state, action) => {
state.modalState.yesNoModal.msg = action.payload;
});
};
export const selectYesNoModalIsOpen = (state: RootState) =>
state.duel.modalState.yesNoModal.isOpen;
export const selectYesNOModalMsg = (state: RootState) =>
state.duel.modalState.yesNoModal.msg;
import {
ActionReducerMapBuilder,
CaseReducer,
createAsyncThunk,
PayloadAction,
} from "@reduxjs/toolkit";
import { fetchCard } from "@/api/cards";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { RootState } from "@/store";
import {
clearIdleInteractivities,
clearPlaceInteractivities,
createAsyncMetaThunk,
DuelFieldState,
extendIdleInteractivities,
extendOccupant,
extendPlaceInteractivity,
Interactivity,
removeOccupant,
removeOverlay,
setPosition,
} from "./generic";
import { DuelState } from "./mod";
import { judgeSelf } from "./util";
type MsgUpdateCounter = ReturnType<
typeof ygopro.StocGameMessage.MsgUpdateCounter.prototype.toObject
>;
export interface MonsterState extends DuelFieldState {}
// 初始化怪兽区状态
export const initMonstersImpl: CaseReducer<DuelState, PayloadAction<number>> = (
state,
action
) => {
const player = action.payload;
const monsters = {
inner: [
{
location: {
controler: player,
location: ygopro.CardZone.MZONE,
},
idleInteractivities: [],
counters: {},
},
{
location: {
controler: player,
location: ygopro.CardZone.MZONE,
},
idleInteractivities: [],
counters: {},
},
{
location: {
controler: player,
location: ygopro.CardZone.MZONE,
},
idleInteractivities: [],
counters: {},
},
{
location: {
controler: player,
location: ygopro.CardZone.MZONE,
},
idleInteractivities: [],
counters: {},
},
{
location: {
controler: player,
location: ygopro.CardZone.MZONE,
},
idleInteractivities: [],
counters: {},
},
{
location: {
controler: player,
location: ygopro.CardZone.MZONE,
},
idleInteractivities: [],
counters: {},
},
{
location: {
controler: player,
location: ygopro.CardZone.MZONE,
},
idleInteractivities: [],
counters: {},
},
],
};
if (judgeSelf(player, state)) {
state.meMonsters = monsters;
} else {
state.opMonsters = monsters;
}
};
export const addMonsterPlaceInteractivitiesImpl: CaseReducer<
DuelState,
PayloadAction<[number, number]>
> = (state, action) => {
const controler = action.payload[0];
const sequence = action.payload[1];
const monsters = judgeSelf(controler, state)
? state.meMonsters
: state.opMonsters;
extendPlaceInteractivity(
monsters,
controler,
sequence,
ygopro.CardZone.MZONE
);
};
export const clearMonsterPlaceInteractivitiesImpl: CaseReducer<
DuelState,
PayloadAction<number>
> = (state, action) => {
const player = action.payload;
const monsters = judgeSelf(player, state)
? state.meMonsters
: state.opMonsters;
clearPlaceInteractivities(monsters);
};
export const addMonsterIdleInteractivitiesImpl: CaseReducer<
DuelState,
PayloadAction<{
player: number;
sequence: number;
interactivity: Interactivity<number>;
}>
> = (state, action) => {
const monsters = judgeSelf(action.payload.player, state)
? state.meMonsters
: state.opMonsters;
extendIdleInteractivities(
monsters,
action.payload.sequence,
action.payload.interactivity
);
};
export const updateMonsterCountersImpl: CaseReducer<
DuelState,
PayloadAction<MsgUpdateCounter>
> = (state, action) => {
const monsters = judgeSelf(action.payload.location?.controler!, state)
? state.meMonsters
: state.opMonsters;
if (monsters) {
const target = monsters.inner.find(
(_, idx) => idx == action.payload.location?.sequence!
);
if (target) {
const count = action.payload.count!;
const counterType = action.payload.action_type!;
switch (action.payload.action_type!) {
case ygopro.StocGameMessage.MsgUpdateCounter.ActionType.ADD: {
if (counterType in target.counters) {
target.counters[counterType] += count;
} else {
target.counters[counterType] = count;
}
break;
}
case ygopro.StocGameMessage.MsgUpdateCounter.ActionType.REMOVE: {
if (counterType in target.counters) {
target.counters[counterType] -= count;
}
break;
}
default: {
break;
}
}
}
}
};
export const clearMonsterIdleInteractivitiesImpl: CaseReducer<
DuelState,
PayloadAction<number>
> = (state, action) => {
const monsters = judgeSelf(action.payload, state)
? state.meMonsters
: state.opMonsters;
clearIdleInteractivities(monsters);
};
// 增加怪兽
export const fetchMonsterMeta = createAsyncMetaThunk("duel/fetchMonsterMeta");
// 增加怪兽的超量素材
export const fetchOverlayMeta = createAsyncThunk(
"duel/fetchOverlayMeta",
async (param: {
controler: number;
sequence: number;
overlayCodes: number[];
append?: boolean;
}) => {
const controler = param.controler;
const sequence = param.sequence;
const overlayCodes = param.overlayCodes;
const metas = await Promise.all(
overlayCodes.map(async (id) => {
if (id == 0) {
return { id, data: {}, text: {} };
} else {
return await fetchCard(id, true);
}
})
);
const response = { controler, sequence, metas };
return response;
}
);
export const monsterCase = (builder: ActionReducerMapBuilder<DuelState>) => {
builder.addCase(fetchMonsterMeta.pending, (state, action) => {
// Meta结果没返回之前先更新`ID`
const controler = action.meta.arg.controler;
const sequence = action.meta.arg.sequence;
const position = action.meta.arg.position;
const code = action.meta.arg.code;
const meta = { id: code, data: {}, text: {} };
if (judgeSelf(controler, state)) {
extendOccupant(state.meMonsters, meta, sequence, position);
} else {
extendOccupant(state.opMonsters, meta, sequence, position);
}
});
builder.addCase(fetchMonsterMeta.fulfilled, (state, action) => {
const controler = action.payload.controler;
const sequence = action.payload.sequence;
const meta = action.payload.meta;
if (judgeSelf(controler, state)) {
extendOccupant(state.meMonsters, meta, sequence);
} else {
extendOccupant(state.opMonsters, meta, sequence);
}
});
builder.addCase(fetchOverlayMeta.pending, (state, action) => {
// Meta结果没返回之前先更新`ID`
const controler = action.meta.arg.controler;
const sequence = action.meta.arg.sequence;
const overlayCodes = action.meta.arg.overlayCodes;
const append = action.meta.arg.append;
const metas = overlayCodes.map((id) => {
return { id, data: {}, text: {} };
});
const monsters = judgeSelf(controler, state)
? state.meMonsters
: state.opMonsters;
if (monsters) {
const target = monsters.inner.find((_, idx) => idx == sequence);
if (target && target.occupant) {
if (append) {
target.overlay_materials = (target.overlay_materials || []).concat(
metas
);
} else {
target.overlay_materials = metas;
}
}
}
});
builder.addCase(fetchOverlayMeta.fulfilled, (state, action) => {
const controler = action.payload.controler;
const sequence = action.payload.sequence;
const overlayMetas = action.payload.metas;
const monsters = judgeSelf(controler, state)
? state.meMonsters
: state.opMonsters;
if (monsters) {
const target = monsters.inner.find((_, idx) => idx == sequence);
for (const meta of overlayMetas) {
for (const overlay of target?.overlay_materials || []) {
if (meta.id == overlay.id) {
overlay.data = meta.data;
overlay.text = meta.text;
}
}
}
}
});
};
// 删除怪兽
export const removeMonsterImpl: CaseReducer<
DuelState,
PayloadAction<{ controler: number; sequence: number }>
> = (state, action) => {
const controler = action.payload.controler;
const monsters = judgeSelf(controler, state)
? state.meMonsters
: state.opMonsters;
removeOccupant(monsters, action.payload.sequence);
removeOverlay(monsters, action.payload.sequence);
};
// 删除超量素材
export const removeOverlayImpl: CaseReducer<
DuelState,
PayloadAction<{
controler: number;
sequence: number;
overlaySequence: number;
}>
> = (state, action) => {
const controler = action.payload.controler;
const sequence = action.payload.sequence;
const overlaySequence = action.payload.overlaySequence;
const monsters = judgeSelf(controler, state)
? state.meMonsters
: state.opMonsters;
if (monsters) {
const target = monsters.inner.find((_, idx) => idx == sequence);
if (target && target.overlay_materials) {
target.overlay_materials = target.overlay_materials.filter(
(_, idx) => idx != overlaySequence
);
}
}
};
// 改变怪兽表示形式
export const setMonsterPositionImpl: CaseReducer<
DuelState,
PayloadAction<{
controler: number;
sequence: number;
position: ygopro.CardPosition;
}>
> = (state, action) => {
const controler = action.payload.controler;
const sequence = action.payload.sequence;
const position = action.payload.position;
const monsters = judgeSelf(controler, state)
? state.meMonsters
: state.opMonsters;
setPosition(monsters, sequence, position);
};
export const selectMeMonsters = (state: RootState) =>
state.duel.meMonsters || { inner: [] };
export const selectOpMonsters = (state: RootState) =>
state.duel.opMonsters || { inner: [] };
import { CaseReducer, PayloadAction } from "@reduxjs/toolkit";
import { RootState } from "@/store";
import { DuelState } from "./mod";
export interface PhaseState {
currentPhase: string; // 当前的阶段
enableBp: boolean; // 允许进入战斗阶段
enableM2: boolean; // 允许进入M2阶段
enableEp: boolean; // 允许回合结束
}
export const newPhaseImpl: CaseReducer<DuelState, PayloadAction<string>> = (
state,
action
) => {
if (state.phase) {
state.phase.currentPhase = action.payload;
} else {
state.phase = {
currentPhase: action.payload,
enableBp: false,
enableM2: false,
enableEp: false,
};
}
};
export const setEnableBpImpl: CaseReducer<DuelState, PayloadAction<boolean>> = (
state,
action
) => {
if (state.phase) {
state.phase.enableBp = action.payload;
}
};
export const setEnableM2Impl: CaseReducer<DuelState, PayloadAction<boolean>> = (
state,
action
) => {
if (state.phase) {
state.phase.enableM2 = action.payload;
}
};
export const setEnableEpImpl: CaseReducer<DuelState, PayloadAction<boolean>> = (
state,
action
) => {
if (state.phase) {
state.phase.enableEp = action.payload;
}
};
export const selectCurrentPhase = (state: RootState) =>
state.duel.phase?.currentPhase;
export const selectEnableBp = (state: RootState) =>
state.duel.phase?.enableBp || false;
export const selectEnableM2 = (state: RootState) =>
state.duel.phase?.enableBp || false;
export const selectEnableEp = (state: RootState) =>
state.duel.phase?.enableEp || false;
import { CaseReducer, PayloadAction } from "@reduxjs/toolkit";
import { DuelState } from "./mod";
import { judgeSelf } from "./util";
export interface TimeLimit {
leftTime: number;
}
// 更新计时
export const updateTimeLimitImpl: CaseReducer<
DuelState,
PayloadAction<[number, number]>
> = (state, action) => {
const player = action.payload[0];
const leftTime = action.payload[1];
if (judgeSelf(player, state)) {
state.meTimeLimit = { leftTime };
} else {
state.opTimeLimit = { leftTime };
}
};
import { CaseReducer, PayloadAction } from "@reduxjs/toolkit";
import { RootState } from "@/store";
import { DuelState } from "./mod";
import { judgeSelf } from "./util";
export const newTurnImpl: CaseReducer<DuelState, PayloadAction<number>> = (
state,
action
) => {
state.currentPlayer = action.payload;
};
export const selectCurrentPlayerIsMe = (state: RootState) =>
judgeSelf(state.duel.currentPlayer!, state.duel);
/*
* 对局内状态更新逻辑的一些共用函数和数据结构
*
* */
import { Draft } from "@reduxjs/toolkit";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { CardState } from "./generic";
import { DuelState } from "./mod";
type Location =
| ygopro.CardLocation
| ReturnType<typeof ygopro.CardLocation.prototype.toObject>;
/*
* 通过`player`和`selfType`判断是应该处理自己还是对手
* */
export function judgeSelf(player: number, state: Draft<DuelState>): boolean {
const selfType = state.selfType;
if (selfType === 1) {
// 自己是先攻
return player == 0;
} else if (selfType === 2) {
// 自己是后攻
return player == 1;
} else {
// currently never reach
return false;
}
}
/*
* 通过`controler`,`zone`和`sequence`获取卡牌状态*/
export function findCardByLocation(
state: Draft<DuelState>,
location: Location
): CardState | undefined {
const controler = location.controler!;
const zone = location.location;
const sequence = location.sequence;
const finder = (_: any, idx: number) => idx == sequence;
switch (zone) {
case ygopro.CardZone.HAND: {
const hands = judgeSelf(controler, state) ? state.meHands : state.opHands;
return hands?.inner.find(finder);
}
case ygopro.CardZone.MZONE: {
const monsters = judgeSelf(controler, state)
? state.meMonsters
: state.opMonsters;
return monsters?.inner.find(finder);
}
case ygopro.CardZone.SZONE: {
const magics = judgeSelf(controler, state)
? state.meMagics
: state.opMagics;
return magics?.inner.find(finder);
}
case ygopro.CardZone.REMOVED: {
const banishedZones = judgeSelf(controler, state)
? state.meBanishedZone
: state.opBanishedZone;
return banishedZones?.inner.find(finder);
}
case ygopro.CardZone.GRAVE: {
const cemerety = judgeSelf(controler, state)
? state.meGraveyard
: state.opGraveyard;
return cemerety?.inner.find(finder);
}
default: {
return undefined;
}
}
}
export function cmpCardLocation(
left: Location,
right?: Location,
strict?: boolean
): boolean {
if (strict) {
return JSON.stringify(left) === JSON.stringify(right);
} else {
return (
left.controler === right?.controler &&
left.location === right?.location &&
left.sequence === right?.sequence
);
}
}
/*
* 加入房间状态更新逻辑
*
* */
import { createSlice } from "@reduxjs/toolkit";
import { RootState } from "@/store";
export interface JoinState {
value: boolean;
}
const initialState: JoinState = {
value: false,
};
const joinedSlice = createSlice({
name: "join",
initialState,
reducers: {
setJoined: (state) => {
state.value = true;
},
setUnJoined: (state) => {
state.value = false;
},
},
});
export const { setJoined, setUnJoined } = joinedSlice.actions;
export const selectJoined = (state: RootState) => state.join.value;
export default joinedSlice.reducer;
/*
* 猜拳页面的状态更新逻辑
*
* */
import { createSlice } from "@reduxjs/toolkit";
import { RootState } from "@/store";
export interface moraState {
duelStart: boolean;
selectHandAble: boolean;
selectTpAble: boolean;
}
const initialState: moraState = {
duelStart: false,
selectHandAble: false,
selectTpAble: false,
};
const moraSlice = createSlice({
name: "mora",
initialState,
reducers: {
duelStart: (state) => {
state.duelStart = true;
},
selectHandAble: (state) => {
state.selectHandAble = true;
},
unSelectHandAble: (state) => {
state.selectHandAble = false;
},
selectTpAble: (state) => {
state.selectTpAble = true;
},
unSelectTpAble: (state) => {
state.selectTpAble = false;
},
},
});
export const {
duelStart,
selectHandAble,
unSelectHandAble,
selectTpAble,
unSelectTpAble,
} = moraSlice.actions;
export const selectDuelStart = (state: RootState) => state.mora.duelStart;
export const selectHandSelectAble = (state: RootState) =>
state.mora.selectHandAble;
export const selectTpSelectAble = (state: RootState) => state.mora.selectTpAble;
export default moraSlice.reducer;
/*
* 进入房间的玩家状态更新逻辑
*
* */
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { RootState } from "@/store";
export interface Player {
name?: string;
state?: string;
isHost?: boolean;
deckInfo?: deckInfo;
}
export interface deckInfo {
mainCnt: number;
extraCnt: number;
sideCnt: number;
}
export interface playerState {
player0: Player;
player1: Player;
observerCount: number;
isHost: boolean;
}
const initialState: playerState = {
player0: {},
player1: {},
observerCount: 0,
isHost: false,
};
const playerSlice = createSlice({
name: "player",
initialState,
reducers: {
player0Enter: (state, action: PayloadAction<string>) => {
state.player0.name = action.payload;
},
player1Enter: (state, action: PayloadAction<string>) => {
state.player1.name = action.payload;
},
player0Update: (state, action: PayloadAction<string>) => {
state.player0.state = action.payload;
},
player1Update: (state, action: PayloadAction<string>) => {
state.player1.state = action.payload;
},
player0Leave: (state) => {
state.player0 = {};
},
player1Leave: (state) => {
state.player1 = {};
},
player0DeckInfo: (state, action: PayloadAction<deckInfo>) => {
state.player0.deckInfo = action.payload;
},
player1DeckInfo: (state, action: PayloadAction<deckInfo>) => {
state.player1.deckInfo = action.payload;
},
hostChange: (state, action: PayloadAction<number>) => {
const i = action.payload;
if (i === 0) {
state.player0.isHost = true;
state.player1.isHost = false;
} else {
state.player1.isHost = true;
state.player0.isHost = false;
}
},
observerIncrement: (state) => {
state.observerCount += 1;
},
observerChange: (state, action: PayloadAction<number>) => {
state.observerCount = action.payload;
},
updateIsHost: (state, action: PayloadAction<boolean>) => {
state.isHost = action.payload;
},
},
});
export const {
player0Enter,
player1Enter,
player0Update,
player1Update,
player0Leave,
player1Leave,
player0DeckInfo,
player1DeckInfo,
hostChange,
observerIncrement,
observerChange,
updateIsHost,
} = playerSlice.actions;
export const selectPlayer0 = (state: RootState) => state.player.player0;
export const selectPlayer1 = (state: RootState) => state.player.player1;
export const selectIsHost = (state: RootState) => state.player.isHost;
export const selectObserverCount = (state: RootState) =>
state.player.observerCount;
export default playerSlice.reducer;
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { fetchEsHintMeta } from "@/reducers/duel/hintSlice"; import { fetchEsHintMeta } from "@/stores";
import { AppDispatch } from "@/store";
export default ( export default (attack: ygopro.StocGameMessage.MsgAttack) => {
attack: ygopro.StocGameMessage.MsgAttack, fetchEsHintMeta({
dispatch: AppDispatch originMsg: "「[?]」攻击时",
) => { location: attack.location,
dispatch( });
fetchEsHintMeta({ originMsg: "「[?]」攻击时", location: attack.location })
);
}; };
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { fetchEsHintMeta } from "@/reducers/duel/hintSlice"; import { fetchEsHintMeta } from "@/stores";
import { AppDispatch } from "@/store";
export default ( export default (_: ygopro.StocGameMessage.MsgAttackDisabled) => {
_: ygopro.StocGameMessage.MsgAttackDisabled, fetchEsHintMeta({ originMsg: "攻击被无效时" });
dispatch: AppDispatch
) => {
dispatch(fetchEsHintMeta({ originMsg: "攻击被无效时" }));
}; };
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { fetchEsHintMeta } from "@/reducers/duel/hintSlice"; import { fetchEsHintMeta } from "@/stores";
import { AppDispatch } from "@/store"; export default (chaining: ygopro.StocGameMessage.MsgChaining) => {
fetchEsHintMeta({
export default ( originMsg: "「[?]」被发动时",
chaining: ygopro.StocGameMessage.MsgChaining, cardID: chaining.code,
dispatch: AppDispatch });
) => {
dispatch(
fetchEsHintMeta({ originMsg: "「[?]」被发动时", cardID: chaining.code })
);
}; };
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { fetchHandsMeta } from "@/reducers/duel/handsSlice"; import { fetchEsHintMeta, matStore } from "@/stores";
import { fetchEsHintMeta } from "@/reducers/duel/hintSlice";
import { AppDispatch } from "@/store";
export default ( export default (draw: ygopro.StocGameMessage.MsgDraw) => {
draw: ygopro.StocGameMessage.MsgDraw, fetchEsHintMeta({ originMsg: "玩家抽卡时" });
dispatch: AppDispatch matStore.hands.of(draw.player).add(draw.cards);
) => {
dispatch(fetchEsHintMeta({ originMsg: "玩家抽卡时" }));
dispatch(fetchHandsMeta({ controler: draw.player, codes: draw.cards }));
}; };
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { fetchEsHintMeta } from "@/reducers/duel/hintSlice"; import { fetchEsHintMeta } from "@/stores";
import { AppDispatch } from "@/store";
export default ( export default (_: ygopro.StocGameMessage.MsgFlipSummoned) => {
_: ygopro.StocGameMessage.MsgFlipSummoned, fetchEsHintMeta({ originMsg: 1608 });
dispatch: AppDispatch
) => {
dispatch(fetchEsHintMeta({ originMsg: 1608 }));
}; };
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { fetchEsHintMeta } from "@/reducers/duel/hintSlice"; import { fetchEsHintMeta } from "@/stores";
import { AppDispatch } from "@/store";
export default ( export default (flipSummoning: ygopro.StocGameMessage.MsgFlipSummoning) => {
flipSummoning: ygopro.StocGameMessage.MsgFlipSummoning, fetchEsHintMeta({
dispatch: AppDispatch originMsg: "「[?]」反转召唤宣言时",
) => { cardID: flipSummoning.code,
dispatch( });
fetchEsHintMeta({
originMsg: "「[?]」反转召唤宣言时",
cardID: flipSummoning.code,
})
);
}; };
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { setWaiting } from "@/reducers/duel/mod"; import { matStore } from "@/stores";
import { store } from "@/store";
import onMsgAttack from "./attack"; import onMsgAttack from "./attack";
import onMsgAttackDisable from "./attackDisable"; import onMsgAttackDisable from "./attackDisable";
...@@ -57,211 +56,210 @@ const ActiveList = [ ...@@ -57,211 +56,210 @@ const ActiveList = [
]; ];
export default function handleGameMsg(pb: ygopro.YgoStocMsg) { export default function handleGameMsg(pb: ygopro.YgoStocMsg) {
const dispatch = store.dispatch;
const msg = pb.stoc_game_msg; const msg = pb.stoc_game_msg;
if (ActiveList.includes(msg.gameMsg)) { if (ActiveList.includes(msg.gameMsg)) {
dispatch(setWaiting(false)); matStore.waiting = false;
} }
switch (msg.gameMsg) { switch (msg.gameMsg) {
case "start": { case "start": {
onMsgStart(msg.start, dispatch); onMsgStart(msg.start);
break; break;
} }
case "draw": { case "draw": {
onMsgDraw(msg.draw, dispatch); onMsgDraw(msg.draw);
break; break;
} }
case "new_turn": { case "new_turn": {
onMsgNewTurn(msg.new_turn, dispatch); onMsgNewTurn(msg.new_turn);
break; break;
} }
case "new_phase": { case "new_phase": {
onMsgNewPhase(msg.new_phase, dispatch); onMsgNewPhase(msg.new_phase);
break; break;
} }
case "hint": { case "hint": {
onMsgHint(msg.hint, dispatch); onMsgHint(msg.hint);
break; break;
} }
case "select_idle_cmd": { case "select_idle_cmd": {
onMsgSelectIdleCmd(msg.select_idle_cmd, dispatch); onMsgSelectIdleCmd(msg.select_idle_cmd);
break; break;
} }
case "select_place": { case "select_place": {
onMsgSelectPlace(msg.select_place, dispatch); onMsgSelectPlace(msg.select_place);
break; break;
} }
case "move": { case "move": {
onMsgMove(msg.move, dispatch); onMsgMove(msg.move);
break; break;
} }
case "select_card": { case "select_card": {
onMsgSelectCard(msg.select_card, dispatch); onMsgSelectCard(msg.select_card);
break; break;
} }
case "select_chain": { case "select_chain": {
onMsgSelectChain(msg.select_chain, dispatch); onMsgSelectChain(msg.select_chain);
break; break;
} }
case "select_effect_yn": { case "select_effect_yn": {
onMsgSelectEffectYn(msg.select_effect_yn, dispatch); onMsgSelectEffectYn(msg.select_effect_yn);
break; break;
} }
case "select_position": { case "select_position": {
onMsgSelectPosition(msg.select_position, dispatch); onMsgSelectPosition(msg.select_position);
break; break;
} }
case "select_option": { case "select_option": {
onMsgSelectOption(msg.select_option, dispatch); onMsgSelectOption(msg.select_option);
break; break;
} }
case "shuffle_hand": { case "shuffle_hand": {
onMsgShuffleHand(msg.shuffle_hand, dispatch); onMsgShuffleHand(msg.shuffle_hand);
break; break;
} }
case "select_battle_cmd": { case "select_battle_cmd": {
onMsgSelectBattleCmd(msg.select_battle_cmd, dispatch); onMsgSelectBattleCmd(msg.select_battle_cmd);
break; break;
} }
case "pos_change": { case "pos_change": {
onMsgPosChange(msg.pos_change, dispatch); onMsgPosChange(msg.pos_change);
break; break;
} }
case "select_unselect_card": { case "select_unselect_card": {
onMsgSelectUnselectCard(msg.select_unselect_card, dispatch); onMsgSelectUnselectCard(msg.select_unselect_card);
break; break;
} }
case "select_yes_no": { case "select_yes_no": {
onMsgSelectYesNo(msg.select_yes_no, dispatch); onMsgSelectYesNo(msg.select_yes_no);
break; break;
} }
case "update_hp": { case "update_hp": {
onMsgUpdateHp(msg.update_hp, dispatch); onMsgUpdateHp(msg.update_hp);
break; break;
} }
case "win": { case "win": {
onMsgWin(msg.win, dispatch); onMsgWin(msg.win);
break; break;
} }
case "wait": { case "wait": {
onMsgWait(msg.wait, dispatch); onMsgWait(msg.wait);
break; break;
} }
case "update_data": { case "update_data": {
onMsgUpdateData(msg.update_data, dispatch); onMsgUpdateData(msg.update_data);
break; break;
} }
case "reload_field": { case "reload_field": {
onMsgReloadField(msg.reload_field, dispatch); onMsgReloadField(msg.reload_field);
break; break;
} }
case "select_sum": { case "select_sum": {
onMsgSelectSum(msg.select_sum, dispatch); onMsgSelectSum(msg.select_sum);
break; break;
} }
case "select_tribute": { case "select_tribute": {
onMsgSelectTribute(msg.select_tribute, dispatch); onMsgSelectTribute(msg.select_tribute);
break; break;
} }
case "update_counter": { case "update_counter": {
onMsgUpdateCounter(msg.update_counter, dispatch); onMsgUpdateCounter(msg.update_counter);
break; break;
} }
case "select_counter": { case "select_counter": {
onMsgSelectCounter(msg.select_counter, dispatch); onMsgSelectCounter(msg.select_counter);
break; break;
} }
case "sort_card": { case "sort_card": {
onMsgSortCard(msg.sort_card, dispatch); onMsgSortCard(msg.sort_card);
break; break;
} }
case "set": { case "set": {
onMsgSet(msg.set, dispatch); onMsgSet(msg.set);
break; break;
} }
case "swap": { case "swap": {
onMsgSwap(msg.swap, dispatch); onMsgSwap(msg.swap);
break; break;
} }
case "attack": { case "attack": {
onMsgAttack(msg.attack, dispatch); onMsgAttack(msg.attack);
break; break;
} }
case "attack_disable": { case "attack_disable": {
onMsgAttackDisable(msg.attack_disable, dispatch); onMsgAttackDisable(msg.attack_disable);
break; break;
} }
case "chaining": { case "chaining": {
onMsgChaining(msg.chaining, dispatch); onMsgChaining(msg.chaining);
break; break;
} }
case "summoning": { case "summoning": {
onMsgSummoning(msg.summoning, dispatch); onMsgSummoning(msg.summoning);
break; break;
} }
case "summoned": { case "summoned": {
onMsgSummoned(msg.summoned, dispatch); onMsgSummoned(msg.summoned);
break; break;
} }
case "flip_summoning": { case "flip_summoning": {
onMsgFlipSummoning(msg.flip_summoning, dispatch); onMsgFlipSummoning(msg.flip_summoning);
break; break;
} }
case "flip_summoned": { case "flip_summoned": {
onMsgFilpSummoned(msg.flip_summoned, dispatch); onMsgFilpSummoned(msg.flip_summoned);
break; break;
} }
case "sp_summoning": { case "sp_summoning": {
onMsgSpSummoning(msg.sp_summoning, dispatch); onMsgSpSummoning(msg.sp_summoning);
break; break;
} }
case "sp_summoned": { case "sp_summoned": {
onMsgSpSummoned(msg.sp_summoned, dispatch); onMsgSpSummoned(msg.sp_summoned);
break; break;
} }
case "unimplemented": { case "unimplemented": {
onUnimplemented(msg.unimplemented, dispatch); onUnimplemented(msg.unimplemented);
break; break;
} }
......
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { import {
fetchCommonHintMeta, fetchCommonHintMeta,
fetchEsHintMeta, fetchEsHintMeta,
fetchSelectHintMeta, fetchSelectHintMeta,
} from "@/reducers/duel/hintSlice"; } from "@/stores";
import { AppDispatch } from "@/store";
import MsgHint = ygopro.StocGameMessage.MsgHint; import MsgHint = ygopro.StocGameMessage.MsgHint;
export default (hint: MsgHint, dispatch: AppDispatch) => { export default (hint: MsgHint) => {
switch (hint.hint_type) { switch (hint.hint_type) {
case MsgHint.HintType.HINT_EVENT: { case MsgHint.HintType.HINT_EVENT: {
dispatch(fetchEsHintMeta({ originMsg: hint.hint_data })); fetchEsHintMeta({ originMsg: hint.hint_data });
break; break;
} }
case MsgHint.HintType.HINT_MESSAGE: { case MsgHint.HintType.HINT_MESSAGE: {
dispatch(fetchCommonHintMeta(hint.hint_data)); fetchCommonHintMeta(hint.hint_data);
break; break;
} }
case MsgHint.HintType.HINT_SELECTMSG: { case MsgHint.HintType.HINT_SELECTMSG: {
dispatch( fetchSelectHintMeta({
fetchSelectHintMeta({ selectHintData: hint.hint_data, esHint: "" }) selectHintData: hint.hint_data,
); esHint: "",
});
break; break;
} }
default: { default: {
......
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import MsgMove = ygopro.StocGameMessage.MsgMove; import { fetchOverlayMeta, store } from "@/stores";
import { fetchBanishedZoneMeta } from "@/reducers/duel/banishedZoneSlice"; type MsgMove = ygopro.StocGameMessage.MsgMove;
import { fetchExtraDeckMeta } from "@/reducers/duel/extraDeckSlice";
import { fetchGraveyardMeta } from "@/reducers/duel/graveyardSlice";
import { insertHandMeta } from "@/reducers/duel/handsSlice";
import { fetchMagicMeta } from "@/reducers/duel/magicSlice";
import {
removeBanishedZone,
removeExtraDeck,
removeGraveyard,
removeHand,
removeMagic,
removeMonster,
removeOverlay,
} from "@/reducers/duel/mod";
import {
fetchMonsterMeta,
fetchOverlayMeta,
} from "@/reducers/duel/monstersSlice";
import { AppDispatch } from "@/store";
import { REASON_MATERIAL } from "../../common"; import { REASON_MATERIAL } from "../../common";
const { matStore } = store;
const OVERLAY_STACK: { code: number; sequence: number }[] = []; const OVERLAY_STACK: { code: number; sequence: number }[] = [];
export default (move: MsgMove, dispatch: AppDispatch) => { export default (move: MsgMove) => {
const code = move.code; const code = move.code;
const from = move.from; const from = move.from;
const to = move.to; const to = move.to;
const reason = move.reason; const reason = move.reason;
switch (from.location) { // TODO: 如果后面做动画的话,要考虑DECK的情况。
case ygopro.CardZone.HAND: { // 现在不会对DECK做判断
dispatch(removeHand([from.controler, from.sequence]));
break;
}
case ygopro.CardZone.MZONE: {
dispatch(
removeMonster({ controler: from.controler, sequence: from.sequence })
);
break; switch (from.location) {
} case ygopro.CardZone.MZONE:
case ygopro.CardZone.SZONE: { case ygopro.CardZone.SZONE: {
dispatch( // 魔陷和怪兽需要清掉占用、清掉超量素材
removeMagic({ controler: from.controler, sequence: from.sequence }) const target = matStore.in(from.location).of(from.controler)[
); from.sequence
];
break; target.occupant = undefined;
} target.overlay_materials = [];
case ygopro.CardZone.GRAVE: {
dispatch(
removeGraveyard({ controler: from.controler, sequence: from.sequence })
);
break;
}
case ygopro.CardZone.REMOVED: {
dispatch(
removeBanishedZone({
controler: from.controler,
sequence: from.sequence,
})
);
break; break;
} }
case ygopro.CardZone.REMOVED:
case ygopro.CardZone.GRAVE:
case ygopro.CardZone.HAND:
case ygopro.CardZone.EXTRA: { case ygopro.CardZone.EXTRA: {
dispatch( // 其余区域就是在list删掉这张卡
removeExtraDeck({ controler: from.controler, sequence: from.sequence }) matStore.in(from.location).of(from.controler).remove(from.sequence);
);
break; break;
} }
// 仅仅去除超量素材
case ygopro.CardZone.OVERLAY: { case ygopro.CardZone.OVERLAY: {
dispatch( const target = matStore.monsters.of(from.controler)[from.sequence];
removeOverlay({ if (target && target.overlay_materials) {
controler: from.controler, target.overlay_materials.splice(from.overlay_sequence, 1);
sequence: from.sequence, }
overlaySequence: from.overlay_sequence,
})
);
break; break;
} }
default: { default: {
...@@ -92,81 +51,28 @@ export default (move: MsgMove, dispatch: AppDispatch) => { ...@@ -92,81 +51,28 @@ export default (move: MsgMove, dispatch: AppDispatch) => {
} }
switch (to.location) { switch (to.location) {
// @ts-ignore
case ygopro.CardZone.MZONE: { case ygopro.CardZone.MZONE: {
dispatch( // 设置超量素材
fetchMonsterMeta({
controler: to.controler,
sequence: to.sequence,
position: to.position,
code,
})
);
// 处理超量素材
const overlayMetarials = OVERLAY_STACK.splice(0, OVERLAY_STACK.length); const overlayMetarials = OVERLAY_STACK.splice(0, OVERLAY_STACK.length);
let sorted = overlayMetarials const sorted = overlayMetarials
.sort((a, b) => a.sequence - b.sequence) .sort((a, b) => a.sequence - b.sequence)
.map((overlay) => overlay.code); .map((overlay) => overlay.code);
dispatch( fetchOverlayMeta(to.controler, to.sequence, sorted);
fetchOverlayMeta({ // 设置Occupant,和魔陷区/其他区共用一个逻辑,特地不写break
controler: to.controler,
sequence: to.sequence,
overlayCodes: sorted,
})
);
break;
} }
case ygopro.CardZone.SZONE: { case ygopro.CardZone.SZONE: {
dispatch( matStore
fetchMagicMeta({ .in(to.location)
controler: to.controler, .of(to.controler)
sequence: to.sequence, .setOccupant(to.sequence, code, to.position);
position: to.position,
code,
})
);
break;
}
case ygopro.CardZone.GRAVE: {
dispatch(
fetchGraveyardMeta({
controler: to.controler,
sequence: to.sequence,
code,
})
);
break; break;
} }
case ygopro.CardZone.REMOVED:
case ygopro.CardZone.GRAVE:
case ygopro.CardZone.EXTRA:
case ygopro.CardZone.HAND: { case ygopro.CardZone.HAND: {
dispatch( matStore.in(to.location).of(to.controler).insert(to.sequence, code);
insertHandMeta({ controler: to.controler, sequence: to.sequence, code })
);
break;
}
case ygopro.CardZone.REMOVED: {
dispatch(
fetchBanishedZoneMeta({
controler: to.controler,
sequence: to.sequence,
code,
})
);
break;
}
case ygopro.CardZone.EXTRA: {
dispatch(
fetchExtraDeckMeta({
controler: to.controler,
sequence: to.sequence,
code,
})
);
break; break;
} }
case ygopro.CardZone.OVERLAY: { case ygopro.CardZone.OVERLAY: {
...@@ -176,21 +82,12 @@ export default (move: MsgMove, dispatch: AppDispatch) => { ...@@ -176,21 +82,12 @@ export default (move: MsgMove, dispatch: AppDispatch) => {
OVERLAY_STACK.push({ code, sequence: to.overlay_sequence }); OVERLAY_STACK.push({ code, sequence: to.overlay_sequence });
} else { } else {
// 其他情况下,比如“宵星的机神 丁吉尔苏”的“补充超量素材”效果,直接更新状态中 // 其他情况下,比如“宵星的机神 丁吉尔苏”的“补充超量素材”效果,直接更新状态中
dispatch( fetchOverlayMeta(to.controler, to.sequence, [code], true);
fetchOverlayMeta({
controler: to.controler,
sequence: to.sequence,
overlayCodes: [code],
append: true,
})
);
} }
break; break;
} }
default: { default: {
console.log(`Unhandled zone type ${to.location}`); console.log(`Unhandled zone type ${to.location}`);
break; break;
} }
} }
......
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { updatePhase } from "@/reducers/duel/mod"; import { matStore, type PhaseName } from "@/stores";
import { AppDispatch } from "@/store";
export default ( export default (newPhase: ygopro.StocGameMessage.MsgNewPhase) => {
newPhase: ygopro.StocGameMessage.MsgNewPhase, // ts本身还没有这么智能,所以需要手动指定类型
dispatch: AppDispatch const currentPhase = ygopro.StocGameMessage.MsgNewPhase.PhaseType[
) => { newPhase.phase_type
dispatch( ] as PhaseName;
updatePhase( matStore.phase.currentPhase = currentPhase;
ygopro.StocGameMessage.MsgNewPhase.PhaseType[newPhase.phase_type]
)
);
}; };
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { updateTurn } from "@/reducers/duel/mod"; import { matStore } from "@/stores";
import { AppDispatch } from "@/store";
export default ( export default (newTurn: ygopro.StocGameMessage.MsgNewTurn) => {
newTurn: ygopro.StocGameMessage.MsgNewTurn,
dispatch: AppDispatch
) => {
const player = newTurn.player; const player = newTurn.player;
dispatch(updateTurn(player)); matStore.currentPlayer = player;
}; };
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { fetchEsHintMeta } from "@/reducers/duel/hintSlice";
import { setMagicPosition, setMonsterPosition } from "@/reducers/duel/mod";
import { AppDispatch } from "@/store";
import MsgPosChange = ygopro.StocGameMessage.MsgPosChange; import MsgPosChange = ygopro.StocGameMessage.MsgPosChange;
import { fetchEsHintMeta, matStore } from "@/stores";
export default (posChange: MsgPosChange) => {
const { location, controler, sequence } = posChange.card_info;
export default (posChange: MsgPosChange, dispatch: AppDispatch) => { switch (location) {
const cardInfo = posChange.card_info;
switch (cardInfo.location) {
case ygopro.CardZone.MZONE: { case ygopro.CardZone.MZONE: {
dispatch( matStore.monsters.of(controler)[sequence].location.position =
setMonsterPosition({ posChange.cur_position;
controler: cardInfo.controler,
sequence: cardInfo.sequence,
position: posChange.cur_position,
})
);
break; break;
} }
case ygopro.CardZone.SZONE: { case ygopro.CardZone.SZONE: {
dispatch( matStore.magics.of(controler)[sequence].location.position =
setMagicPosition({ posChange.cur_position;
controler: cardInfo.controler,
sequence: cardInfo.sequence,
position: posChange.cur_position,
})
);
break; break;
} }
default: { default: {
console.log(`Unhandled zone ${cardInfo.location}`); console.log(`Unhandled zone ${location}`);
} }
} }
fetchEsHintMeta({
dispatch(fetchEsHintMeta({ originMsg: 1600 })); originMsg: 1600,
});
}; };
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { reloadField } from "@/reducers/duel/mod"; import { matStore } from "@/stores";
import { AppDispatch } from "@/store";
import MsgReloadField = ygopro.StocGameMessage.MsgReloadField;
export default (field: MsgReloadField, dispatch: AppDispatch) => { type MsgReloadField = ygopro.StocGameMessage.MsgReloadField;
dispatch(reloadField(field)); type ZoneActions = ygopro.StocGameMessage.MsgReloadField.ZoneAction[];
export default (field: MsgReloadField) => {
const _duel_rule = field.duel_rule; // TODO: duel_rule
const gamers = ["me", "op"] as const;
gamers.forEach((gamer) => {
matStore.banishedZones[gamer].length = 0;
matStore.extraDecks[gamer].length = 0;
matStore.graveyards[gamer].length = 0;
matStore.hands[gamer].length = 0;
matStore.monsters[gamer].length = 0;
matStore.magics[gamer].length = 0;
});
const { MZONE, SZONE, HAND, DECK, GRAVE, REMOVED, EXTRA } = ygopro.CardZone;
const zones = [MZONE, SZONE, HAND, DECK, GRAVE, REMOVED, EXTRA] as const;
field.actions.forEach(({ player, zone_actions }) => {
zones.forEach((zone) => {
reloadDuelField(
zone,
zone_actions.filter((item) => item.zone === zone),
player
);
});
});
}; };
/** 可以理解成reload DuelFieldState */
function reloadDuelField(
cardZone: ygopro.CardZone,
zoneActions: ZoneActions,
controller: number
) {
zoneActions.sort((a, b) => a.sequence - b.sequence);
const cards = zoneActions.map((action) => {
// FIXME: OVERLAY
return {
location: {
controler: controller,
location: action.zone,
position: action.position,
},
idleInteractivities: [],
counters: {},
reload: true,
};
});
matStore.in(cardZone).of(controller).length = 0;
matStore
.in(cardZone)
.of(controller)
.push(...cards);
}
import { ActionCreatorWithPayload } from "@reduxjs/toolkit"; import { ygopro } from "@/api";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import { Interactivity, InteractType } from "@/reducers/duel/generic";
import { import {
addHandsIdleInteractivity, clearAllIdleInteractivities as clearAllIdleInteractivities,
addMagicIdleInteractivities, type Interactivity,
addMonsterIdleInteractivities, InteractType,
clearAllIdleInteractivities, matStore,
setEnableEp, } from "@/stores";
setEnableM2,
} from "@/reducers/duel/mod";
import { AppDispatch } from "@/store";
import MsgSelectBattleCmd = ygopro.StocGameMessage.MsgSelectBattleCmd; import MsgSelectBattleCmd = ygopro.StocGameMessage.MsgSelectBattleCmd;
export default (selectBattleCmd: MsgSelectBattleCmd, dispatch: AppDispatch) => { export default (selectBattleCmd: MsgSelectBattleCmd) => {
const player = selectBattleCmd.player; const player = selectBattleCmd.player;
const cmds = selectBattleCmd.battle_cmds; const cmds = selectBattleCmd.battle_cmds;
// 先清掉之前的互动性 // 先清掉之前的互动性
dispatch(clearAllIdleInteractivities(player)); clearAllIdleInteractivities(player);
const dispatcher = (
battleData: MsgSelectBattleCmd.BattleCmd.BattleData,
interactType: InteractType | undefined,
actionCreator: ActionCreatorWithPayload<
{
player: number;
sequence: number;
interactivity: Interactivity<number>;
},
string
>
) => {
const cardInfo = battleData.card_info;
if (interactType === InteractType.ACTIVATE) {
dispatch(
actionCreator({
player,
sequence: cardInfo.sequence,
interactivity: {
interactType,
activateIndex: battleData.effect_description,
response: battleData.response,
},
})
);
} else if (interactType === InteractType.ATTACK) {
dispatch(
actionCreator({
player,
sequence: cardInfo.sequence,
interactivity: {
interactType,
directAttackAble: battleData.direct_attackable,
response: battleData.response,
},
})
);
} else {
console.log(`Unhandled InteractType`);
}
};
cmds.forEach((cmd) => { cmds.forEach((cmd) => {
const interactType = battleTypeToInteracType(cmd.battle_type); const interactType = battleTypeToInteracType(cmd.battle_type);
cmd.battle_datas.forEach((data) => { cmd.battle_datas.forEach((data) => {
const cardInfo = data.card_info; const { location, sequence } = data.card_info;
switch (cardInfo.location) {
case ygopro.CardZone.HAND: {
dispatcher(data, interactType, addHandsIdleInteractivity);
break;
}
case ygopro.CardZone.MZONE: {
dispatcher(data, interactType, addMonsterIdleInteractivities);
break;
}
case ygopro.CardZone.SZONE: {
dispatcher(data, interactType, addMagicIdleInteractivities);
break; // valtio
} if (interactType) {
default: { const map: Partial<
} Record<InteractType, undefined | Partial<Interactivity<number>>>
> = {
[InteractType.ACTIVATE]: { activateIndex: data.effect_description },
[InteractType.ATTACK]: { directAttackAble: data.direct_attackable },
};
const tmp = map[interactType]; // 添加额外信息
matStore
.in(location)
.of(player)
.addIdleInteractivity(sequence, {
...tmp,
interactType,
response: data.response,
});
} else {
console.warn(`Undefined InteractType`);
} }
}); });
}); });
matStore.phase.enableM2 = selectBattleCmd.enable_m2;
dispatch(setEnableM2(selectBattleCmd.enable_m2)); matStore.phase.enableEp = selectBattleCmd.enable_ep;
dispatch(setEnableEp(selectBattleCmd.enable_ep));
}; };
function battleTypeToInteracType( function battleTypeToInteracType(
......
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import {
setCheckCardModalIsOpen,
setCheckCardModalMinMax,
setCheckCardModalOnSubmit,
} from "@/reducers/duel/mod";
import { fetchCheckCardMeta } from "@/reducers/duel/modal/mod";
import { AppDispatch } from "@/store";
import MsgSelectCard = ygopro.StocGameMessage.MsgSelectCard; import MsgSelectCard = ygopro.StocGameMessage.MsgSelectCard;
import { CardZoneToChinese } from "./util"; import { CardZoneToChinese, fetchCheckCardMeta, messageStore } from "@/stores";
export default (selectCard: MsgSelectCard, dispatch: AppDispatch) => { export default (selectCard: MsgSelectCard) => {
const _player = selectCard.player; const _player = selectCard.player;
const _cancelable = selectCard.cancelable; // TODO: 处理可取消逻辑 const _cancelable = selectCard.cancelable; // TODO: 处理可取消逻辑
const min = selectCard.min; const min = selectCard.min;
...@@ -17,23 +10,17 @@ export default (selectCard: MsgSelectCard, dispatch: AppDispatch) => { ...@@ -17,23 +10,17 @@ export default (selectCard: MsgSelectCard, dispatch: AppDispatch) => {
const cards = selectCard.cards; const cards = selectCard.cards;
// TODO: handle release_param // TODO: handle release_param
messageStore.checkCardModal.selectMin = min;
dispatch(setCheckCardModalMinMax({ min, max })); messageStore.checkCardModal.selectMax = max;
dispatch(setCheckCardModalOnSubmit("sendSelectCardResponse")); messageStore.checkCardModal.onSubmit = "sendSelectCardResponse";
for (const card of cards) { for (const card of cards) {
const tagName = CardZoneToChinese(card.location.location); const tagName = CardZoneToChinese(card.location.location);
dispatch( fetchCheckCardMeta(card.location.location, {
fetchCheckCardMeta({ code: card.code,
tagName, location: card.location,
option: { response: card.response,
code: card.code, });
location: card.location,
response: card.response,
},
})
);
} }
messageStore.checkCardModal.isOpen = true;
dispatch(setCheckCardModalIsOpen(true));
}; };
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { sendSelectChainResponse, ygopro } from "@/api";
import { sendSelectChainResponse } from "@/api/ocgcore/ocgHelper";
import { fetchSelectHintMeta } from "@/reducers/duel/hintSlice";
import { import {
setCheckCardMOdalCancelAble, fetchCheckCardMeta,
setCheckCardModalCancelResponse, fetchSelectHintMeta,
setCheckCardModalIsOpen, messageStore,
setCheckCardModalMinMax, } from "@/stores";
setCheckCardModalOnSubmit,
} from "@/reducers/duel/mod";
import { fetchCheckCardMeta } from "@/reducers/duel/modal/mod";
import { AppDispatch } from "@/store";
import { CardZoneToChinese } from "./util"; type MsgSelectChain = ygopro.StocGameMessage.MsgSelectChain;
import MsgSelectChain = ygopro.StocGameMessage.MsgSelectChain; export default (selectChain: MsgSelectChain) => {
export default (selectChain: MsgSelectChain, dispatch: AppDispatch) => {
const player = selectChain.player; const player = selectChain.player;
const spCount = selectChain.special_count; const spCount = selectChain.special_count;
const forced = selectChain.forced; const forced = selectChain.forced;
...@@ -66,32 +58,24 @@ export default (selectChain: MsgSelectChain, dispatch: AppDispatch) => { ...@@ -66,32 +58,24 @@ export default (selectChain: MsgSelectChain, dispatch: AppDispatch) => {
case 3: { case 3: {
// 处理强制发动的卡 // 处理强制发动的卡
dispatch(setCheckCardModalMinMax({ min: 1, max: 1 })); messageStore.checkCardModal.selectMin = 1;
dispatch(setCheckCardModalOnSubmit("sendSelectChainResponse")); messageStore.checkCardModal.selectMax = 1;
dispatch(setCheckCardMOdalCancelAble(!forced)); messageStore.checkCardModal.onSubmit = "sendSelectChainResponse";
dispatch(setCheckCardModalCancelResponse(-1)); messageStore.checkCardModal.cancelAble = !forced;
messageStore.checkCardModal.cancelResponse = -1;
for (const chain of chains) { for (const chain of chains) {
const tagName = CardZoneToChinese(chain.location.location); fetchCheckCardMeta(chain.location.location, {
dispatch( code: chain.code,
fetchCheckCardMeta({ location: chain.location,
tagName, response: chain.response,
option: { effectDescCode: chain.effect_description,
code: chain.code, });
location: chain.location,
response: chain.response,
effectDescCode: chain.effect_description,
},
})
);
} }
dispatch( fetchSelectHintMeta({
fetchSelectHintMeta({ selectHintData: 203,
selectHintData: 203, });
}) messageStore.checkCardModal.isOpen = true;
);
dispatch(setCheckCardModalIsOpen(true));
break; break;
} }
......
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { setCheckCounter } from "@/reducers/duel/mod"; import { getCardByLocation, messageStore } from "@/stores";
import { AppDispatch } from "@/store"; type MsgSelectCounter = ygopro.StocGameMessage.MsgSelectCounter;
import MsgSelectCounter = ygopro.StocGameMessage.MsgSelectCounter;
export default (selectCounter: MsgSelectCounter, dispatch: AppDispatch) => { export default (selectCounter: MsgSelectCounter) => {
dispatch(setCheckCounter(selectCounter.toObject())); messageStore.checkCounterModal.counterType = selectCounter.counter_type;
messageStore.checkCounterModal.min = selectCounter.min;
messageStore.checkCounterModal.options = selectCounter.options!.map(
({ location, code, counter_count }) => {
const id = getCardByLocation(location)?.occupant?.id;
const newCode = code ? code : id || 0;
return {
code: newCode,
max: counter_count!,
};
}
);
messageStore.checkCounterModal.isOpen = true;
}; };
import { CardMeta } from "@/api/cards"; import { fetchStrings, ygopro } from "@/api";
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { CardMeta, fetchCard } from "@/api/cards";
import { setYesNoModalIsOpen } from "@/reducers/duel/mod"; import { CardZoneToChinese, messageStore } from "@/stores";
import { fetchYesNoMeta } from "@/reducers/duel/modal/mod";
import { AppDispatch } from "@/store";
import { CardZoneToChinese } from "./util"; type MsgSelectEffectYn = ygopro.StocGameMessage.MsgSelectEffectYn;
import MsgSelectEffectYn = ygopro.StocGameMessage.MsgSelectEffectYn;
export default (selectEffectYn: MsgSelectEffectYn, dispatch: AppDispatch) => { // 这里改成了 async 不知道有没有影响
export default async (selectEffectYn: MsgSelectEffectYn) => {
const player = selectEffectYn.player; const player = selectEffectYn.player;
const code = selectEffectYn.code; const code = selectEffectYn.code;
const location = selectEffectYn.location; const location = selectEffectYn.location;
...@@ -31,13 +29,18 @@ export default (selectEffectYn: MsgSelectEffectYn, dispatch: AppDispatch) => { ...@@ -31,13 +29,18 @@ export default (selectEffectYn: MsgSelectEffectYn, dispatch: AppDispatch) => {
const desc1 = desc.replace(`[%ls]`, cardMeta.text.name || "[?]"); const desc1 = desc.replace(`[%ls]`, cardMeta.text.name || "[?]");
return desc1; return desc1;
}; };
dispatch( // dispatch(
fetchYesNoMeta({ // fetchYesNoMeta({
code, // code,
location, // location,
descCode: effect_description, // descCode: effect_description,
textGenerator, // textGenerator,
}) // })
); // );
dispatch(setYesNoModalIsOpen(true)); // TODO: 国际化文案
const desc = fetchStrings("!system", effect_description);
const meta = await fetchCard(code);
messageStore.yesNoModal.msg = textGenerator(desc, meta, location);
messageStore.yesNoModal.isOpen = true;
}; };
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { Interactivity, InteractType } from "@/reducers/duel/generic";
import { import {
addBanishedZoneIdleInteractivities, clearAllIdleInteractivities as clearAllIdleInteractivities,
addExtraDeckIdleInteractivities, type Interactivity,
addGraveyardIdleInteractivities, InteractType,
addHandsIdleInteractivity, matStore,
addMagicIdleInteractivities, } from "@/stores";
addMonsterIdleInteractivities,
clearAllIdleInteractivities,
setEnableBp,
setEnableEp,
} from "@/reducers/duel/mod";
import { AppDispatch } from "@/store";
import MsgSelectIdleCmd = ygopro.StocGameMessage.MsgSelectIdleCmd; import MsgSelectIdleCmd = ygopro.StocGameMessage.MsgSelectIdleCmd;
import { ActionCreatorWithPayload } from "@reduxjs/toolkit";
export default (selectIdleCmd: MsgSelectIdleCmd, dispatch: AppDispatch) => { export default (selectIdleCmd: MsgSelectIdleCmd) => {
const player = selectIdleCmd.player; const player = selectIdleCmd.player;
const cmds = selectIdleCmd.idle_cmds; const cmds = selectIdleCmd.idle_cmds;
// 先清掉之前的互动性 // 先清掉之前的互动性
dispatch(clearAllIdleInteractivities(player)); clearAllIdleInteractivities(player);
const dispatcher = (
idleData: MsgSelectIdleCmd.IdleCmd.IdleData,
interactType: InteractType | undefined,
actionCreator: ActionCreatorWithPayload<
{
player: number;
sequence: number;
interactivity: Interactivity<number>;
},
string
>
) => {
const cardInfo = idleData.card_info;
if (interactType === InteractType.ACTIVATE) {
// 发动效果会多一个字段
dispatch(
actionCreator({
player,
sequence: cardInfo.sequence,
interactivity: {
interactType,
activateIndex: idleData.effect_description,
response: idleData.response,
},
})
);
} else if (interactType) {
dispatch(
actionCreator({
player,
sequence: cardInfo.sequence,
interactivity: { interactType, response: idleData.response },
})
);
} else {
console.log(`InteractType undefined`);
}
};
cmds.forEach((cmd) => { cmds.forEach((cmd) => {
const interactType = idleTypeToInteractType(cmd.idle_type); const interactType = idleTypeToInteractType(cmd.idle_type);
cmd.idle_datas.forEach((data) => { cmd.idle_datas.forEach((data) => {
const cardInfo = data.card_info; const { location, sequence } = data.card_info;
switch (cardInfo.location) {
case ygopro.CardZone.HAND: {
dispatcher(data, interactType, addHandsIdleInteractivity);
break;
}
case ygopro.CardZone.MZONE: {
dispatcher(data, interactType, addMonsterIdleInteractivities);
break; // valtio: 代码从 ./selectBattleCmd.ts 复制过来的
} if (interactType) {
case ygopro.CardZone.SZONE: { const map: Partial<
dispatcher(data, interactType, addMagicIdleInteractivities); Record<InteractType, undefined | Partial<Interactivity<number>>>
> = {
break; [InteractType.ACTIVATE]: { activateIndex: data.effect_description },
} };
case ygopro.CardZone.GRAVE: { const tmp = map[interactType];
dispatcher(data, interactType, addGraveyardIdleInteractivities); matStore
.in(location)
break; .of(player)
} .addIdleInteractivity(sequence, {
case ygopro.CardZone.REMOVED: { ...tmp,
dispatcher(data, interactType, addBanishedZoneIdleInteractivities); interactType,
response: data.response,
break; });
} } else {
case ygopro.CardZone.EXTRA: { console.warn(`Undefined InteractType`);
dispatcher(data, interactType, addExtraDeckIdleInteractivities);
break;
}
default: {
console.log(`Unhandled zone type: ${cardInfo.location}`);
}
} }
}); });
}); });
dispatch(setEnableBp(selectIdleCmd.enable_bp)); matStore.phase.enableBp = selectIdleCmd.enable_bp;
dispatch(setEnableEp(selectIdleCmd.enable_ep)); matStore.phase.enableEp = selectIdleCmd.enable_ep;
}; };
function idleTypeToInteractType( function idleTypeToInteractType(
......
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { fetchCard, getCardStr, ygopro } from "@/api";
import { setOptionModalIsOpen } from "@/reducers/duel/mod";
import { fetchOptionMeta } from "@/reducers/duel/modal/mod";
import { AppDispatch } from "@/store";
import MsgSelectOption = ygopro.StocGameMessage.MsgSelectOption; import MsgSelectOption = ygopro.StocGameMessage.MsgSelectOption;
import { messageStore } from "@/stores";
export default (selectOption: MsgSelectOption, dispatch: AppDispatch) => { export default async (selectOption: MsgSelectOption) => {
const player = selectOption.player; const player = selectOption.player;
const options = selectOption.options; const options = selectOption.options;
for (let option of options) { await Promise.all(
dispatch(fetchOptionMeta(option)); options.map(async ({ code, response }) => {
} const meta = await fetchCard(code >> 4);
const msg = getCardStr(meta, code & 0xf) || "[?]";
const newResponse = { msg, response };
messageStore.optionModal.options.push(newResponse);
})
);
dispatch(setOptionModalIsOpen(true)); messageStore.optionModal.isOpen = true;
}; };
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { AppDispatch } from "@/store"; import { InteractType, matStore } from "@/stores";
import MsgSelectPlace = ygopro.StocGameMessage.MsgSelectPlace;
import {
addMagicPlaceInteractivities,
addMonsterPlaceInteractivities,
} from "@/reducers/duel/mod";
export default (selectPlace: MsgSelectPlace, dispatch: AppDispatch) => { type MsgSelectPlace = ygopro.StocGameMessage.MsgSelectPlace;
export default (selectPlace: MsgSelectPlace) => {
if (selectPlace.count != 1) { if (selectPlace.count != 1) {
console.warn(`Unhandled case: ${selectPlace}`); console.warn(`Unhandled case: ${selectPlace}`);
return; return;
} }
for (const place of selectPlace.places) { for (const place of selectPlace.places) {
switch (place.zone) { switch (place.zone) {
case ygopro.CardZone.MZONE: { case ygopro.CardZone.MZONE: {
dispatch( matStore.monsters
addMonsterPlaceInteractivities([place.controler, place.sequence]) .of(place.controler)
); .setPlaceInteractivityType(
place.sequence,
InteractType.PLACE_SELECTABLE
);
break; break;
} }
case ygopro.CardZone.SZONE: { case ygopro.CardZone.SZONE: {
dispatch( matStore.magics
addMagicPlaceInteractivities([place.controler, place.sequence]) .of(place.controler)
); .setPlaceInteractivityType(
place.sequence,
InteractType.PLACE_SELECTABLE
);
break; break;
} }
default: { default: {
......
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { import { messageStore } from "@/stores";
setPositionModalIsOpen,
setPositionModalPositions,
} from "@/reducers/duel/mod";
import { AppDispatch } from "@/store";
import MsgSelectPosition = ygopro.StocGameMessage.MsgSelectPosition;
export default (selectPosition: MsgSelectPosition, dispatch: AppDispatch) => { type MsgSelectPosition = ygopro.StocGameMessage.MsgSelectPosition;
export default (selectPosition: MsgSelectPosition) => {
const player = selectPosition.player; const player = selectPosition.player;
const positions = selectPosition.positions; const positions = selectPosition.positions;
dispatch( messageStore.positionModal.positions = positions.map(
setPositionModalPositions(positions.map((position) => position.position)) (position) => position.position
); );
dispatch(setPositionModalIsOpen(true)); messageStore.positionModal.isOpen = true;
}; };
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { import { fetchCheckCardMetasV3, messageStore } from "@/stores";
setCheckCardModalV3AllLevel, type MsgSelectSum = ygopro.StocGameMessage.MsgSelectSum;
setCheckCardModalV3IsOpen,
setCheckCardModalV3MinMax,
setCheckCardModalV3OverFlow,
} from "@/reducers/duel/mod";
import { fetchCheckCardMetasV3 } from "@/reducers/duel/modal/checkCardModalV3Slice";
import { AppDispatch } from "@/store";
import MsgSelectSum = ygopro.StocGameMessage.MsgSelectSum;
export default (selectSum: MsgSelectSum, dispatch: AppDispatch) => { export default (selectSum: MsgSelectSum) => {
dispatch(setCheckCardModalV3OverFlow(selectSum.overflow != 0)); messageStore.checkCardModalV3.overflow = selectSum.overflow != 0;
dispatch(setCheckCardModalV3AllLevel(selectSum.level_sum)); messageStore.checkCardModalV3.allLevel = selectSum.level_sum;
dispatch( messageStore.checkCardModalV3.selectMin = selectSum.min;
setCheckCardModalV3MinMax({ min: selectSum.min, max: selectSum.max }) messageStore.checkCardModalV3.selectMax = selectSum.max;
);
dispatch( fetchCheckCardMetasV3({
fetchCheckCardMetasV3({ mustSelect: true,
mustSelect: true, options: selectSum.must_select_cards,
options: selectSum.must_select_cards, });
})
); fetchCheckCardMetasV3({
dispatch( mustSelect: false,
fetchCheckCardMetasV3({ options: selectSum.selectable_cards,
mustSelect: false, });
options: selectSum.selectable_cards,
}) messageStore.checkCardModalV3.isOpen = true;
);
dispatch(setCheckCardModalV3IsOpen(true));
}; };
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { import { fetchCheckCardMetasV3, messageStore } from "@/stores";
setCheckCardModalV3AllLevel,
setCheckCardModalV3IsOpen,
setCheckCardModalV3MinMax,
setCheckCardModalV3OverFlow,
} from "@/reducers/duel/mod";
import { fetchCheckCardMetasV3 } from "@/reducers/duel/modal/checkCardModalV3Slice";
import { AppDispatch } from "@/store";
import MsgSelectTribute = ygopro.StocGameMessage.MsgSelectTribute;
export default (selectTribute: MsgSelectTribute, dispatch: AppDispatch) => { type MsgSelectTribute = ygopro.StocGameMessage.MsgSelectTribute;
export default (selectTribute: MsgSelectTribute) => {
// TODO: 当玩家选择卡数大于`max`时,是否也合法? // TODO: 当玩家选择卡数大于`max`时,是否也合法?
dispatch(setCheckCardModalV3OverFlow(true)); messageStore.checkCardModalV3.overflow = true;
dispatch(setCheckCardModalV3AllLevel(0)); messageStore.checkCardModalV3.allLevel = 0;
dispatch( messageStore.checkCardModalV3.selectMin = selectTribute.min;
setCheckCardModalV3MinMax({ messageStore.checkCardModalV3.selectMax = selectTribute.max;
min: selectTribute.min,
max: selectTribute.max, fetchCheckCardMetasV3({
}) mustSelect: false,
); options: selectTribute.selectable_cards.map((card) => {
dispatch( return {
fetchCheckCardMetasV3({ code: card.code,
mustSelect: false, location: card.location,
options: selectTribute.selectable_cards.map((card) => { level1: card.level,
return { level2: card.level,
code: card.code, response: card.response,
location: card.location, };
level1: card.level, }),
level2: card.level, });
response: card.response,
}; messageStore.checkCardModalV3.isOpen = true;
}),
})
);
dispatch(setCheckCardModalV3IsOpen(true));
}; };
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { import { fetchCheckCardMetasV2, messageStore } from "@/stores";
setCheckCardModalV2CancelAble,
setCheckCardModalV2FinishAble,
setCheckCardModalV2IsOpen,
setCheckCardModalV2MinMax,
setCheckCardModalV2ResponseAble,
} from "@/reducers/duel/mod";
import { fetchCheckCardMetasV2 } from "@/reducers/duel/modal/checkCardModalV2Slice";
import { AppDispatch } from "@/store";
import MsgSelectUnselectCard = ygopro.StocGameMessage.MsgSelectUnselectCard;
export default ( type MsgSelectUnselectCard = ygopro.StocGameMessage.MsgSelectUnselectCard;
selectUnselectCard: MsgSelectUnselectCard,
dispatch: AppDispatch
) => {
const finishable = selectUnselectCard.finishable;
const cancelable = selectUnselectCard.cancelable;
const min = selectUnselectCard.min;
const max = selectUnselectCard.max;
const selectableCards = selectUnselectCard.selectable_cards;
const selectedCards = selectUnselectCard.selected_cards;
dispatch(setCheckCardModalV2IsOpen(true)); export default ({
dispatch(setCheckCardModalV2FinishAble(finishable)); finishable,
dispatch(setCheckCardModalV2CancelAble(cancelable)); cancelable,
dispatch(setCheckCardModalV2MinMax({ min, max })); min,
max,
selectable_cards: selectableCards,
selected_cards: selectedCards,
}: MsgSelectUnselectCard) => {
messageStore.checkCardModalV2.isOpen = true;
messageStore.checkCardModalV2.finishAble = finishable;
messageStore.checkCardModalV2.cancelAble = cancelable;
messageStore.checkCardModalV2.selectMin = min;
messageStore.checkCardModalV2.selectMax = max;
dispatch( fetchCheckCardMetasV2({
fetchCheckCardMetasV2({ selected: false,
selected: false, options: selectableCards.map((card) => {
options: selectableCards.map((card) => { return {
return { code: card.code,
code: card.code, location: card.location,
location: card.location, response: card.response,
response: card.response, };
}; }),
}), });
})
);
dispatch( fetchCheckCardMetasV2({
fetchCheckCardMetasV2({ selected: true,
selected: true, options: selectedCards.map((card) => {
options: selectedCards.map((card) => { return {
return { code: card.code,
code: card.code, location: card.location,
location: card.location, response: card.response,
response: card.response, };
}; }),
}), });
})
);
dispatch(setCheckCardModalV2ResponseAble(true)); messageStore.checkCardModalV2.responseable = true;
}; };
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { getStrings, ygopro } from "@/api";
import { setYesNoModalIsOpen } from "@/reducers/duel/mod"; import { messageStore } from "@/stores";
import { fetchYesNoMetaWithEffecDesc } from "@/reducers/duel/modal/yesNoModalSlice";
import { AppDispatch } from "@/store";
import MsgSelectYesNo = ygopro.StocGameMessage.MsgSelectYesNo;
export default (selectYesNo: MsgSelectYesNo, dispatch: AppDispatch) => { type MsgSelectYesNo = ygopro.StocGameMessage.MsgSelectYesNo;
export default async (selectYesNo: MsgSelectYesNo) => {
const player = selectYesNo.player; const player = selectYesNo.player;
const effect_description = selectYesNo.effect_description; const effect_description = selectYesNo.effect_description;
dispatch(fetchYesNoMetaWithEffecDesc(effect_description)); messageStore.yesNoModal.msg = await getStrings(effect_description);
dispatch(setYesNoModalIsOpen(true)); messageStore.yesNoModal.isOpen = true;
}; };
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { fetchEsHintMeta } from "@/reducers/duel/hintSlice"; import { fetchEsHintMeta } from "@/stores";
import { AppDispatch } from "@/store";
export default (_set: ygopro.StocGameMessage.MsgSet, dispatch: AppDispatch) => { export default (_set: ygopro.StocGameMessage.MsgSet) => {
dispatch(fetchEsHintMeta({ originMsg: 1601 })); fetchEsHintMeta({ originMsg: 1601 });
}; };
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { updateHandsMeta } from "@/reducers/duel/handsSlice"; import { matStore } from "@/stores";
import { AppDispatch } from "@/store";
import MsgShuffleHand = ygopro.StocGameMessage.MsgShuffleHand; type MsgShuffleHand = ygopro.StocGameMessage.MsgShuffleHand;
export default (shuffleHand: MsgShuffleHand, dispatch: AppDispatch) => { export default (shuffleHand: MsgShuffleHand) => {
dispatch( const { hands: codes, player: controller } = shuffleHand;
updateHandsMeta({ controler: shuffleHand.player, codes: shuffleHand.hands })
); const metas = codes.map((code) => {
return {
occupant: { id: code, data: {}, text: {} },
location: {
controler: controller,
location: ygopro.CardZone.HAND,
},
idleInteractivities: [],
counters: {},
};
});
matStore.hands.of(controller).length = 0;
matStore.hands.of(controller).push(...metas);
}; };
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { fetchCard, ygopro } from "@/api";
import { setSortCardModalIsOpen } from "@/reducers/duel/mod"; import { messageStore } from "@/stores";
import { fetchSortCardMeta } from "@/reducers/duel/modal/sortCardModalSlice";
import { AppDispatch } from "@/store";
import MsgSortCard = ygopro.StocGameMessage.MsgSortCard;
export default (sortCard: MsgSortCard, dispatch: AppDispatch) => { type MsgSortCard = ygopro.StocGameMessage.MsgSortCard;
for (const option of sortCard.options) {
dispatch(fetchSortCardMeta(option.toObject())); export default async (sortCard: MsgSortCard) => {
} await Promise.all(
dispatch(setSortCardModalIsOpen(true)); sortCard.options.map(async ({ code, response }) => {
const meta = await fetchCard(code!, true);
messageStore.sortCardModal.options.push({
meta,
response: response!,
});
})
);
messageStore.sortCardModal.isOpen = true;
}; };
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { fetchEsHintMeta } from "@/reducers/duel/hintSlice"; import { fetchEsHintMeta } from "@/stores";
import { AppDispatch } from "@/store";
export default ( export default (_: ygopro.StocGameMessage.MsgSpSummoned) => {
_: ygopro.StocGameMessage.MsgSpSummoned, fetchEsHintMeta({ originMsg: 1606 });
dispatch: AppDispatch
) => {
dispatch(fetchEsHintMeta({ originMsg: 1606 }));
}; };
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { fetchEsHintMeta } from "@/reducers/duel/hintSlice"; import { fetchEsHintMeta } from "@/stores";
import { AppDispatch } from "@/store"; export default (spSummoning: ygopro.StocGameMessage.MsgSpSummoning) => {
fetchEsHintMeta({
export default ( originMsg: "「[?]」特殊召唤宣言时",
spSummoning: ygopro.StocGameMessage.MsgSpSummoning, cardID: spSummoning.code,
dispatch: AppDispatch });
) => {
dispatch(
fetchEsHintMeta({
originMsg: "「[?]」特殊召唤宣言时",
cardID: spSummoning.code,
})
);
}; };
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { import { store } from "@/stores";
infoInit,
initBanishedZone,
initDeck,
initGraveyard,
initHint,
initMagics,
initMonsters,
setSelfType,
} from "@/reducers/duel/mod";
import { AppDispatch } from "@/store";
export default ( const { matStore } = store;
start: ygopro.StocGameMessage.MsgStart,
dispatch: AppDispatch export default (start: ygopro.StocGameMessage.MsgStart) => {
) => { matStore.selfType = start.playerType;
dispatch(setSelfType(start.playerType));
dispatch( matStore.initInfo.set(0, {
infoInit([ life: start.life1,
0, deckSize: start.deckSize1,
{ extraSize: start.extraSize1,
life: start.life1, });
deckSize: start.deckSize1, matStore.initInfo.set(1, {
extraSize: start.extraSize1, life: start.life2,
}, deckSize: start.deckSize2,
]) extraSize: start.extraSize2,
); });
dispatch(
infoInit([ matStore.monsters.of(0).forEach((x) => (x.location.controler = 0));
1, matStore.monsters.of(1).forEach((x) => (x.location.controler = 1));
{ matStore.magics.of(0).forEach((x) => (x.location.controler = 0));
life: start.life2, matStore.magics.of(1).forEach((x) => (x.location.controler = 1));
deckSize: start.deckSize2,
extraSize: start.extraSize2, matStore.decks.of(0).add(Array(start.deckSize1).fill(0));
}, matStore.decks.of(1).add(Array(start.deckSize2).fill(0));
])
);
dispatch(initMonsters(0));
dispatch(initMonsters(1));
dispatch(initMagics(0));
dispatch(initMagics(1));
dispatch(initGraveyard(0));
dispatch(initGraveyard(1));
dispatch(initDeck({ player: 0, deskSize: start.deckSize1 }));
dispatch(initDeck({ player: 1, deskSize: start.deckSize2 }));
dispatch(initBanishedZone(0));
dispatch(initBanishedZone(1));
dispatch(initHint());
}; };
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { fetchEsHintMeta } from "@/reducers/duel/hintSlice"; import { fetchEsHintMeta } from "@/stores";
import { AppDispatch } from "@/store";
export default ( export default (_: ygopro.StocGameMessage.MsgSummoned) => {
_: ygopro.StocGameMessage.MsgSummoned, fetchEsHintMeta({ originMsg: 1604 });
dispatch: AppDispatch
) => {
dispatch(fetchEsHintMeta({ originMsg: 1604 }));
}; };
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { fetchEsHintMeta } from "@/reducers/duel/hintSlice"; import { fetchEsHintMeta } from "@/stores";
import { AppDispatch } from "@/store";
export default ( export default (summoning: ygopro.StocGameMessage.MsgSummoning) => {
summoning: ygopro.StocGameMessage.MsgSummoning, fetchEsHintMeta({
dispatch: AppDispatch originMsg: "「[?]」通常召唤宣言时",
) => { cardID: summoning.code,
dispatch( });
fetchEsHintMeta({
originMsg: "「[?]」通常召唤宣言时",
cardID: summoning.code,
})
);
}; };
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { fetchEsHintMeta } from "@/reducers/duel/hintSlice"; import { fetchEsHintMeta } from "@/stores";
import { AppDispatch } from "@/store";
export default ( export default (_swap: ygopro.StocGameMessage.MsgSwap) => {
_swap: ygopro.StocGameMessage.MsgSwap, fetchEsHintMeta({ originMsg: 1602 });
dispatch: AppDispatch
) => {
dispatch(fetchEsHintMeta({ originMsg: 1602 }));
}; };
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { sendTimeConfirm, ygopro } from "@/api";
import { sendTimeConfirm } from "@/api/ocgcore/ocgHelper"; import { matStore } from "@/stores";
import { updateTimeLimit } from "@/reducers/duel/mod";
import { store } from "@/store";
export default function handleTimeLimit(timeLimit: ygopro.StocTimeLimit) { export default function handleTimeLimit(timeLimit: ygopro.StocTimeLimit) {
const dispatch = store.dispatch; matStore.timeLimits.set(timeLimit.player, timeLimit.left_time);
dispatch(updateTimeLimit([timeLimit.player, timeLimit.left_time]));
sendTimeConfirm(); sendTimeConfirm();
} }
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import { setUnimplemented } from "@/reducers/duel/mod"; import { matStore } from "@/stores";
import { AppDispatch } from "@/store";
const NeosConfig = useConfig(); const NeosConfig = useConfig();
export default ( export default (unimplemented: ygopro.StocGameMessage.MsgUnimplemented) => {
unimplemented: ygopro.StocGameMessage.MsgUnimplemented,
dispatch: AppDispatch
) => {
if (!NeosConfig.unimplementedWhiteList.includes(unimplemented.command)) { if (!NeosConfig.unimplementedWhiteList.includes(unimplemented.command)) {
dispatch(setUnimplemented(unimplemented.command)); matStore.unimplemented = unimplemented.command;
} }
}; };
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { updateMonsterCounters } from "@/reducers/duel/mod"; import { getCardByLocation } from "@/stores";
import { AppDispatch } from "@/store";
import MsgUpdateCounter = ygopro.StocGameMessage.MsgUpdateCounter;
export default (updateCounter: MsgUpdateCounter, dispatch: AppDispatch) => { type MsgUpdateCounter = ygopro.StocGameMessage.MsgUpdateCounter;
dispatch(updateMonsterCounters(updateCounter.toObject()));
export default (updateCounter: MsgUpdateCounter) => {
const { location, count, action_type: counterType } = updateCounter;
const target = getCardByLocation(location); // 不太确定这个后面能不能相应,我不好说
if (target) {
switch (counterType) {
case ygopro.StocGameMessage.MsgUpdateCounter.ActionType.ADD: {
if (counterType in target.counters) {
target.counters[counterType] += count;
} else {
target.counters[counterType] = count;
}
break;
}
case ygopro.StocGameMessage.MsgUpdateCounter.ActionType.REMOVE: {
if (counterType in target.counters) {
target.counters[counterType] -= count;
}
break;
}
default: {
break;
}
}
}
}; };
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { updateFieldData } from "@/reducers/duel/mod";
import { AppDispatch } from "@/store";
import MsgUpdateData = ygopro.StocGameMessage.MsgUpdateData; import MsgUpdateData = ygopro.StocGameMessage.MsgUpdateData;
export default (updateData: MsgUpdateData, dispatch: AppDispatch) => { import { matStore } from "@/stores";
dispatch(updateFieldData(updateData.toObject()));
export default (updateData: MsgUpdateData) => {
const { player: controller, zone, actions } = updateData;
if (controller !== undefined && zone !== undefined && actions !== undefined) {
const field = matStore.in(zone).of(controller);
actions.forEach((action) => {
const sequence = action.location?.sequence;
if (typeof sequence !== "undefined") {
const target = field[sequence];
if (target && (target.occupant || target.reload)) {
if (target.occupant === undefined) {
target.occupant = { id: action.code!, data: {}, text: {} };
}
const occupant = target.occupant;
// 目前只更新以下字段
if (action.code !== undefined && action.code >= 0) {
occupant.id = action.code;
occupant.text.id = action.code;
}
if (action.location !== undefined) {
target.location.position = action.location.position;
}
if (action.type_ !== undefined && action.type_ >= 0) {
occupant.data.type = action.type_;
}
if (action.level !== undefined && action.level >= 0) {
occupant.data.level = action.level;
}
if (action.attribute !== undefined && action.attribute >= 0) {
occupant.data.attribute = action.attribute;
}
if (action.race !== undefined && action.race >= 0) {
occupant.data.race = action.race;
}
if (action.attack !== undefined && action.attack >= 0) {
occupant.data.atk = action.attack;
}
if (action.defense !== undefined && action.defense >= 0) {
occupant.data.def = action.defense;
}
// TODO: counters
}
if (target?.reload) {
target.reload = false;
}
}
});
}
}; };
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { fetchEsHintMeta } from "@/reducers/duel/hintSlice"; import { fetchEsHintMeta, matStore } from "@/stores";
import { updateHp } from "@/reducers/duel/mod";
import { AppDispatch } from "@/store";
import MsgUpdateHp = ygopro.StocGameMessage.MsgUpdateHp; import MsgUpdateHp = ygopro.StocGameMessage.MsgUpdateHp;
export default (msgUpdateHp: MsgUpdateHp, dispatch: AppDispatch) => { export default (msgUpdateHp: MsgUpdateHp) => {
if (msgUpdateHp.type_ == MsgUpdateHp.ActionType.DAMAGE) { if (msgUpdateHp.type_ == MsgUpdateHp.ActionType.DAMAGE) {
dispatch(fetchEsHintMeta({ originMsg: "玩家收到伤害时" })); // TODO: i18n fetchEsHintMeta({ originMsg: "玩家收到伤害时" }); // TODO: i18n
matStore.initInfo.of(msgUpdateHp.player).life -= msgUpdateHp.value;
} else if (msgUpdateHp.type_ == MsgUpdateHp.ActionType.RECOVER) { } else if (msgUpdateHp.type_ == MsgUpdateHp.ActionType.RECOVER) {
dispatch(fetchEsHintMeta({ originMsg: "玩家生命值回复时" })); // TODO: i18n fetchEsHintMeta({ originMsg: "玩家生命值回复时" }); // TODO: i18n
matStore.initInfo.of(msgUpdateHp.player).life += msgUpdateHp.value;
} }
dispatch(updateHp(msgUpdateHp));
}; };
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { clearAllIdleInteractivities, setWaiting } from "@/reducers/duel/mod"; import {
import { AppDispatch } from "@/store"; clearAllIdleInteractivities as clearAllIdleInteractivities,
matStore,
} from "@/stores";
export default ( export default (_wait: ygopro.StocGameMessage.MsgWait) => {
_wait: ygopro.StocGameMessage.MsgWait, clearAllIdleInteractivities(0);
dispatch: AppDispatch clearAllIdleInteractivities(1);
) => { matStore.waiting = true;
dispatch(clearAllIdleInteractivities(0));
dispatch(clearAllIdleInteractivities(1));
dispatch(setWaiting(true));
}; };
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { setResult } from "@/reducers/duel/mod"; import { matStore } from "@/stores";
import { AppDispatch } from "@/store";
export default (win: ygopro.StocGameMessage.MsgWin, dispatch: AppDispatch) => { export default (win: ygopro.StocGameMessage.MsgWin) => {
dispatch(setResult(win.type_)); matStore.result = win.type_;
}; };
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { player0DeckInfo, player1DeckInfo } from "@/reducers/playerSlice"; import { playerStore } from "@/stores";
import { store } from "@/store";
import { playerStore } from "@/valtioStores";
// FIXME: player0 不一定是当前玩家 // FIXME: player0 不一定是当前玩家
export default function handleDeckCount(pb: ygopro.YgoStocMsg) { export default function handleDeckCount(pb: ygopro.YgoStocMsg) {
const dispath = store.dispatch;
const deckCount = pb.stoc_deck_count; const deckCount = pb.stoc_deck_count;
dispath(
player0DeckInfo({
mainCnt: deckCount.meMain,
extraCnt: deckCount.meExtra,
sideCnt: deckCount.meSide,
})
);
playerStore.player0.deckInfo = { playerStore.player0.deckInfo = {
mainCnt: deckCount.meMain, mainCnt: deckCount.meMain,
extraCnt: deckCount.meExtra, extraCnt: deckCount.meExtra,
sideCnt: deckCount.meSide, sideCnt: deckCount.meSide,
}; };
dispath(
player1DeckInfo({
mainCnt: deckCount.opMain,
extraCnt: deckCount.opExtra,
sideCnt: deckCount.opSide,
})
);
playerStore.player1.deckInfo = { playerStore.player1.deckInfo = {
mainCnt: deckCount.opMain, mainCnt: deckCount.opMain,
extraCnt: deckCount.opExtra, extraCnt: deckCount.opExtra,
......
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { selectHandAble } from "@/reducers/moraSlice"; import { moraStore } from "@/stores";
import { store } from "@/store";
import { moraStore } from "@/valtioStores";
export default function handleSelectHand(_: ygopro.YgoStocMsg) { export default function handleSelectHand(_: ygopro.YgoStocMsg) {
const dispatch = store.dispatch;
dispatch(selectHandAble());
moraStore.selectHandAble = true; moraStore.selectHandAble = true;
} }
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { selectTpAble } from "@/reducers/moraSlice"; import { moraStore } from "@/stores";
import { store } from "@/store";
import { moraStore } from "@/valtioStores";
export default function handleSelectTp(_: ygopro.YgoStocMsg) { export default function handleSelectTp(_: ygopro.YgoStocMsg) {
const dispatch = store.dispatch;
dispatch(selectTpAble());
moraStore.selectTpAble = true; moraStore.selectTpAble = true;
} }
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* 长连接建立事件订阅处理逻辑 * 长连接建立事件订阅处理逻辑
* *
* */ * */
import { sendJoinGame, sendPlayerInfo } from "@/api/ocgcore/ocgHelper"; import { sendJoinGame, sendPlayerInfo } from "@/api";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
const NeosConfig = useConfig(); const NeosConfig = useConfig();
......
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { postChat } from "@/reducers/chatSlice"; import { chatStore } from "@/stores";
import { store } from "@/store";
import { chatStore } from "@/valtioStores";
export default function handleChat(pb: ygopro.YgoStocMsg) { export default function handleChat(pb: ygopro.YgoStocMsg) {
const dispatch = store.dispatch;
const chat = pb.stoc_chat; const chat = pb.stoc_chat;
dispatch(postChat(chat.msg));
chatStore.message = chat.msg; chatStore.message = chat.msg;
} }
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { duelStart } from "@/reducers/moraSlice"; import { moraStore } from "@/stores";
import { store } from "@/store";
import { moraStore } from "@/valtioStores";
export default function handleDuelStart(_pb: ygopro.YgoStocMsg) { export default function handleDuelStart(_pb: ygopro.YgoStocMsg) {
const dispatch = store.dispatch;
dispatch(duelStart());
moraStore.duelStart = true; moraStore.duelStart = true;
} }
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { import { playerStore } from "@/stores";
observerIncrement,
player0Leave,
player0Update,
player1Leave,
player1Update,
} from "@/reducers/playerSlice";
import { store } from "@/store";
import { playerStore } from "@/valtioStores";
const READY_STATE = "ready"; const READY_STATE = "ready";
const NO_READY_STATE = "not ready"; const NO_READY_STATE = "not ready";
export default function handleHsPlayerChange(pb: ygopro.YgoStocMsg) { export default function handleHsPlayerChange(pb: ygopro.YgoStocMsg) {
const dispatch = store.dispatch;
const change = pb.stoc_hs_player_change; const change = pb.stoc_hs_player_change;
if (change.pos > 1) { if (change.pos > 1) {
...@@ -35,42 +26,25 @@ export default function handleHsPlayerChange(pb: ygopro.YgoStocMsg) { ...@@ -35,42 +26,25 @@ export default function handleHsPlayerChange(pb: ygopro.YgoStocMsg) {
// todo // todo
// if (src === 0 && dst === 1) {
// setPlayer1(player0);
// setPlayer0({});
// } else if (src === 1 && dst === 0) {
// setPlayer0(player1);
// setPlayer1({});
// }
break; break;
} }
case ygopro.StocHsPlayerChange.State.READY: { case ygopro.StocHsPlayerChange.State.READY: {
change.pos == 0
? dispatch(player0Update(READY_STATE))
: dispatch(player1Update(READY_STATE));
playerStore[change.pos == 0 ? "player0" : "player1"].state = playerStore[change.pos == 0 ? "player0" : "player1"].state =
READY_STATE; READY_STATE;
break; break;
} }
case ygopro.StocHsPlayerChange.State.NO_READY: { case ygopro.StocHsPlayerChange.State.NO_READY: {
change.pos == 0
? dispatch(player0Update(NO_READY_STATE))
: dispatch(player1Update(NO_READY_STATE));
playerStore[change.pos == 0 ? "player0" : "player1"].state = playerStore[change.pos == 0 ? "player0" : "player1"].state =
NO_READY_STATE; NO_READY_STATE;
break; break;
} }
case ygopro.StocHsPlayerChange.State.LEAVE: { case ygopro.StocHsPlayerChange.State.LEAVE: {
change.pos == 0 ? dispatch(player0Leave) : dispatch(player1Leave);
playerStore[change.pos == 0 ? "player0" : "player1"] = {}; playerStore[change.pos == 0 ? "player0" : "player1"] = {};
break; break;
} }
case ygopro.StocHsPlayerChange.State.TO_OBSERVER: { case ygopro.StocHsPlayerChange.State.TO_OBSERVER: {
change.pos == 0 ? dispatch(player0Leave) : dispatch(player1Leave);
dispatch(observerIncrement());
playerStore[change.pos == 0 ? "player0" : "player1"] = {}; // todo: 有没有必要? playerStore[change.pos == 0 ? "player0" : "player1"] = {}; // todo: 有没有必要?
playerStore.observerCount += 1; playerStore.observerCount += 1;
break; break;
......
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { player0Enter, player1Enter } from "@/reducers/playerSlice"; import { playerStore } from "@/stores";
import { store } from "@/store";
import { playerStore } from "@/valtioStores";
export default function handleHsPlayerEnter(pb: ygopro.YgoStocMsg) { export default function handleHsPlayerEnter(pb: ygopro.YgoStocMsg) {
const dispatch = store.dispatch;
const name = pb.stoc_hs_player_enter.name; const name = pb.stoc_hs_player_enter.name;
const pos = pb.stoc_hs_player_enter.pos; const pos = pb.stoc_hs_player_enter.pos;
if (pos > 1) { if (pos > 1) {
console.log("Currently only supported 2v2 mode."); console.log("Currently only supported 2v2 mode.");
} else { } else {
pos == 0 ? dispatch(player0Enter(name)) : dispatch(player1Enter(name));
playerStore[pos == 0 ? "player0" : "player1"].name = name; playerStore[pos == 0 ? "player0" : "player1"].name = name;
} }
} }
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { observerChange } from "@/reducers/playerSlice"; import { playerStore } from "@/stores";
import { store } from "@/store";
import { playerStore } from "@/valtioStores";
export default function handleHsWatchChange(pb: ygopro.YgoStocMsg) { export default function handleHsWatchChange(pb: ygopro.YgoStocMsg) {
const dispatch = store.dispatch;
const count = pb.stoc_hs_watch_change.count; const count = pb.stoc_hs_watch_change.count;
dispatch(observerChange(count));
playerStore.observerCount = count; playerStore.observerCount = count;
} }
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { setJoined } from "@/reducers/joinSlice"; import { joinStore } from "@/stores";
import { store } from "@/store";
import { joinStore } from "@/valtioStores";
export default function handleJoinGame(pb: ygopro.YgoStocMsg) { export default function handleJoinGame(pb: ygopro.YgoStocMsg) {
const dispatch = store.dispatch;
const msg = pb.stoc_join_game; const msg = pb.stoc_join_game;
// todo // todo
dispatch(setJoined());
joinStore.value = true; joinStore.value = true;
} }
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { import { playerStore } from "@/stores";
hostChange,
player0Update,
player1Update,
updateIsHost,
} from "@/reducers/playerSlice";
import { store } from "@/store";
import { playerStore } from "@/valtioStores";
const NO_READY_STATE = "not ready"; const NO_READY_STATE = "not ready";
export default function handleTypeChange(pb: ygopro.YgoStocMsg) { export default function handleTypeChange(pb: ygopro.YgoStocMsg) {
const dispatch = store.dispatch;
const selfType = pb.stoc_type_change.self_type; const selfType = pb.stoc_type_change.self_type;
const assertHost = pb.stoc_type_change.is_host; const assertHost = pb.stoc_type_change.is_host;
dispatch(updateIsHost(assertHost)); playerStore.isHost = assertHost;
if (assertHost) { if (assertHost) {
switch (selfType) { switch (selfType) {
case ygopro.StocTypeChange.SelfType.PLAYER1: { case ygopro.StocTypeChange.SelfType.PLAYER1: {
dispatch(hostChange(0));
dispatch(player0Update(NO_READY_STATE));
playerStore.player0.isHost = true; playerStore.player0.isHost = true;
playerStore.player1.isHost = false; playerStore.player1.isHost = false;
playerStore.player0.state = NO_READY_STATE; playerStore.player0.state = NO_READY_STATE;
break; break;
} }
case ygopro.StocTypeChange.SelfType.PLAYER2: { case ygopro.StocTypeChange.SelfType.PLAYER2: {
dispatch(hostChange(0));
dispatch(player1Update(NO_READY_STATE));
playerStore.player0.isHost = false; playerStore.player0.isHost = false;
playerStore.player1.isHost = true; playerStore.player1.isHost = true;
playerStore.player1.state = NO_READY_STATE; playerStore.player1.state = NO_READY_STATE;
break; break;
} }
default: { default: {
......
/*
* 全局状态存储模块
* */
import { configureStore, Unsubscribe } from "@reduxjs/toolkit";
import chatReducer from "./reducers/chatSlice";
import duelReducer from "./reducers/duel/mod";
import joinedReducer from "./reducers/joinSlice";
import moraReducer from "./reducers/moraSlice";
import playerReducer from "./reducers/playerSlice";
export const store = configureStore({
reducer: {
join: joinedReducer,
chat: chatReducer,
player: playerReducer,
mora: moraReducer,
duel: duelReducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: ["duel/updateHp", "duel/reloadField"],
},
}),
});
// Ref: https://github.com/reduxjs/redux/issues/303
export function observeStore<T>(
select: (state: RootState) => T,
onChange: (prev: T | null, cur: T) => void
): Unsubscribe {
let currentState: T | null = null;
const changeHook = () => {
const nextState = select(store.getState());
if (nextState !== currentState) {
onChange(currentState, nextState);
currentState = nextState;
}
};
const unsubscribe = store.subscribe(changeHook);
changeHook();
return unsubscribe;
}
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export * from "./chatStore"; export * from "./chatStore";
export * from "./joinStore"; export * from "./joinStore";
export * from "./matStore";
export * from "./messageStore";
export * from "./moraStore"; export * from "./moraStore";
export * from "./playerStore"; export * from "./playerStore";
import { createContext, type ReactNode, useRef } from "react";
import { proxy } from "valtio"; import { proxy } from "valtio";
import { devtools } from "valtio/utils"; import { devtools } from "valtio/utils";
import { chatStore } from "./chatStore"; import { chatStore } from "./chatStore";
import { joinStore } from "./joinStore"; import { joinStore } from "./joinStore";
import { matStore } from "./matStore";
import { messageStore } from "./messageStore";
import { moraStore } from "./moraStore"; import { moraStore } from "./moraStore";
import { playerStore } from "./playerStore"; import { playerStore } from "./playerStore";
export const valtioStore = proxy({ export const store = proxy({
playerStore, playerStore,
chatStore, chatStore,
joinStore, joinStore,
moraStore, moraStore,
matStore, // 决斗盘
messageStore, // 决斗的信息,包括模态框
}); });
devtools(valtioStore, { name: "valtio store", enabled: true }); devtools(store, { name: "valtio store", enabled: true });
/**
* 在组件之中使用valtio store
*/
export const valtioContext = createContext<typeof valtioStore>({} as any);
/**
* 包裹根节点,使得所有子组件都可以使用valtio store
*/
export const ValtioProvider: React.FC<{ children: ReactNode }> = ({
children,
}) => {
const state = useRef(valtioStore).current;
return (
<valtioContext.Provider value={state}>{children}</valtioContext.Provider>
);
};
export * from "./methods";
export * from "./store";
export * from "./types";
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
export function CardZoneToChinese(zone: ygopro.CardZone): string { export function CardZoneToChinese(zone: ygopro.CardZone): string {
switch (zone) { switch (zone) {
......
import { ygopro } from "@/api";
import { fetchCard, getCardStr } from "@/api/cards";
import { matStore, messageStore } from "@/stores";
import { CardZoneToChinese } from "./cardZoneToChinese";
type Location =
| ygopro.CardLocation
| ReturnType<typeof ygopro.CardLocation.prototype.toObject>;
function cmpCardLocation(
left: Location,
right?: Location,
strict?: boolean
): boolean {
if (strict) {
return JSON.stringify(left) === JSON.stringify(right);
} else {
return (
left.controler === right?.controler &&
left.location === right?.location &&
left.sequence === right?.sequence
);
}
}
/**
* 这段代码定义了一个异步函数 fetchCheckCardMeta,它的作用是获取一张卡片的元数据并将其添加到某个名为 messageStore.checkCardModal 的对象上。
该函数的第一个参数是一个枚举值 ygopro.CardZone,表示卡片所在的区域。其余参数是一个包含卡片编号、位置、响应码和效果描述代码等信息的对象。
首先,这个函数会根据区域类型调用 CardZoneToChinese() 函数生成一个中文名称。然后,它会调用 fetchCard() 异步函数来获取指定卡片的元数据 meta。
接下来,函数会根据传递进来的 location 对象获取卡片所属的控制者,并根据控制者判断这张卡片是我方的还是对方的。然后,它会根据卡片的位置信息获取卡片的实际 ID,并构造一个新的选项 newOption。
接着,函数会遍历已有的 messageStore.checkCardModal.tags,查找是否存在名为 combinedTagName 的标签。如果找到了,则将新选项 newOption 加入该标签的选项列表中并立即返回。如果找不到,则创建一个新标签,并将新选项 newOption 添加到其中。
最后,函数会再次遍历所有标签,查找是否存在名为 combinedTagName 的标签。如果找到了,则遍历该标签中的所有选项,并查找是否存在与 location 对象中指定的卡片位置信息完全相同的选项。如果找到了,则更新该选项的元数据和效果描述等信息。
*/
export const fetchCheckCardMeta = async (
zone: ygopro.CardZone,
{
code,
location,
response,
effectDescCode,
}: {
code: number;
location: ygopro.CardLocation;
response: number;
effectDescCode?: number;
}
) => {
const tagName = CardZoneToChinese(zone);
const meta = await fetchCard(code);
const controller = location.controler;
const combinedTagName = matStore.isMe(controller)
? `我方的${tagName}`
: `对方的${tagName}`;
const newID =
code != 0
? code
: matStore.in(location.location).of(controller)[location.sequence]
?.occupant?.id || 0;
const newOption = {
meta: { id: newID, data: {}, text: {} },
location: location.toObject(),
effectDescCode,
response,
};
for (const tag of messageStore.checkCardModal.tags) {
if (tag.tagName === combinedTagName) {
tag.options.push(newOption);
return;
}
}
messageStore.checkCardModal.tags.push({
tagName: combinedTagName,
options: [newOption],
});
for (const tag of messageStore.checkCardModal.tags) {
if (tag.tagName === combinedTagName) {
for (const old of tag.options) {
if (meta.id == old.meta.id && cmpCardLocation(location, old.location)) {
const cardID = old.meta.id;
old.meta = meta;
old.meta.id = cardID;
const effectDescCode = old.effectDescCode;
const effectDesc = effectDescCode
? getCardStr(old.meta, effectDescCode & 0xf)
: undefined;
old.effectDesc = effectDesc;
}
}
}
}
};
import type { ygopro } from "@/api";
import { DESCRIPTION_LIMIT, fetchStrings, getStrings } from "@/api";
import { fetchCard } from "@/api/cards";
import { matStore } from "../store";
const { hint } = matStore;
export const fetchCommonHintMeta = (code: number) => {
hint.code = code;
hint.msg = fetchStrings("!system", code);
};
export const fetchSelectHintMeta = async ({
selectHintData,
esHint,
}: {
selectHintData: number;
esHint?: string;
}) => {
let selectHintMeta = "";
if (selectHintData > DESCRIPTION_LIMIT) {
// 针对`MSG_SELECT_PLACE`的特化逻辑
const cardMeta = await fetchCard(selectHintData, true);
selectHintMeta = fetchStrings("!system", 569).replace(
"[%ls]",
cardMeta.text.name || "[?]"
);
} else {
selectHintMeta = await getStrings(selectHintData);
}
hint.code = selectHintData;
if (hint.code > DESCRIPTION_LIMIT) {
// 针对`MSG_SELECT_PLACE`的特化逻辑
hint.msg = selectHintMeta;
} else {
hint.esSelectHint = selectHintMeta;
hint.esHint = esHint;
}
};
export const fetchEsHintMeta = async ({
originMsg,
location,
cardID,
}: {
originMsg: string | number;
location?: ygopro.CardLocation;
cardID?: number;
}) => {
const newOriginMsg =
typeof originMsg === "string"
? originMsg
: fetchStrings("!system", originMsg);
const cardMeta = cardID ? await fetchCard(cardID) : undefined;
let esHint = newOriginMsg;
if (cardMeta?.text.name) {
esHint = esHint.replace("[?]", cardMeta.text.name);
}
if (location) {
const fieldMeta = matStore
.in(location.location)
.of(location.controler)
.at(location.sequence);
if (fieldMeta?.occupant?.text.name) {
esHint = esHint.replace("[?]", fieldMeta.occupant.text.name);
}
}
hint.esHint = esHint;
};
import { fetchCard } from "@/api";
import { matStore } from "@/stores";
export const fetchOverlayMeta = async (
controller: number,
sequence: number,
overlayCodes: number[],
append?: boolean
) => {
const metas = await Promise.all(
overlayCodes.map(async (id) => await fetchCard(id))
);
const target = matStore.monsters.of(controller)[sequence];
if (target && target.occupant) {
if (append) {
target.overlay_materials = (target.overlay_materials || []).concat(metas);
} else {
target.overlay_materials = metas;
}
}
};
import type { ygopro } from "@/api";
import { matStore } from "@/stores";
export const getCardByLocation = (location: ygopro.CardLocation) => {
return matStore.in(location.location).of(location.controler)[
location.sequence
];
};
export * from "./cardZoneToChinese";
export * from "./fetchCheckCardMeta";
export * from "./fetchHint";
export * from "./fetchOverlayMeta";
export * from "./getCardByLocation";
import { cloneDeep } from "lodash-es";
import { proxy } from "valtio";
import { ygopro } from "@/api";
import { fetchCard } from "@/api/cards";
import type {
CardState,
DuelFieldState as ArrayCardState,
InitInfo,
MatState,
} from "./types";
import { InteractType } from "./types";
/**
* 根据controller判断是自己还是对方。
* 这个无需export,尽量逻辑收拢在store内部。
*/
const getWhom = (controller: number): "me" | "op" =>
isMe(controller) ? "me" : "op";
/** 卡的列表,提供了一些方便的方法 */
class CardArray extends Array<CardState> implements ArrayCardState {
public __proto__ = CardArray.prototype;
public zone: ygopro.CardZone = ygopro.CardZone.MZONE;
public getController: () => number = () => 1;
private genCard = async (controller: number, id: number) => ({
occupant: await fetchCard(id, true),
location: {
controler: controller,
location: this.zone,
},
counters: {},
idleInteractivities: [],
});
// methods
remove(sequence: number) {
this.splice(sequence, 1);
}
async insert(sequence: number, id: number) {
const card = await this.genCard(this.getController(), id);
this.splice(sequence, 0, card);
}
async add(ids: number[]) {
const cards = await Promise.all(
ids.map(async (id) => this.genCard(this.getController(), id))
);
this.splice(this.length, 0, ...cards);
}
async setOccupant(
sequence: number,
id: number,
position?: ygopro.CardPosition
) {
const meta = await fetchCard(id);
const target = this[sequence];
target.occupant = meta;
if (position) {
target.location.position = position;
}
}
addIdleInteractivity(
sequence: number,
interactivity: CardState["idleInteractivities"][number]
) {
this[sequence].idleInteractivities.push(interactivity);
}
clearIdleInteractivities() {
this.forEach((card) => (card.idleInteractivities = []));
}
setPlaceInteractivityType(sequence: number, interactType: InteractType) {
this[sequence].placeInteractivity = {
interactType: interactType,
response: {
controler: this.getController(),
zone: this.zone,
sequence,
},
};
}
clearPlaceInteractivity() {
this.forEach((card) => (card.placeInteractivity = undefined));
}
}
const genDuelCardArray = (cardStates: CardState[], zone: ygopro.CardZone) => {
// 为什么不放在构造函数里面,是因为不想改造继承自Array的构造函数
const me = cloneDeep(new CardArray(...cardStates));
me.zone = zone;
me.getController = () => (matStore.selfType === 1 ? 0 : 1);
const op = cloneDeep(new CardArray(...cardStates));
op.zone = zone;
op.getController = () => (matStore.selfType === 1 ? 1 : 0);
const res = proxy({
me,
op,
of: (controller: number) => res[getWhom(controller)],
});
return res;
};
/**
* 根据自己的先后手判断是否是自己
* 原本名字叫judgeSelf
*/
const isMe = (controller: number): boolean => {
switch (matStore.selfType) {
case 1:
// 自己是先攻
return controller === 0;
case 2:
// 自己是后攻
return controller === 1;
default:
// 目前不可能出现这种情况
console.error("judgeSelf error", controller, matStore.selfType);
return false;
}
};
/**
* 生成一个指定长度的卡片数组
*/
const genBlock = (location: ygopro.CardZone, n: number) =>
Array(n)
.fill(null)
.map((_) => ({
location: {
location,
},
idleInteractivities: [],
counters: {},
}));
const initInfo: MatState["initInfo"] = (() => {
const defaultInitInfo = {
masterRule: "UNKNOWN",
life: -1, // 特地设置一个不可能的值
deckSize: 0,
extraSize: 0,
};
return proxy({
me: { ...defaultInitInfo },
op: { ...defaultInitInfo },
of: (controller: number) => initInfo[getWhom(controller)],
set: (controller: number, obj: Partial<InitInfo>) => {
initInfo[getWhom(controller)] = {
...initInfo[getWhom(controller)],
...obj,
};
},
});
})();
const hint: MatState["hint"] = proxy({
code: -1,
});
/**
* zone -> matStore
*/
const getZone = (zone: ygopro.CardZone) => {
switch (zone) {
case ygopro.CardZone.MZONE:
return matStore.monsters;
case ygopro.CardZone.SZONE:
return matStore.magics;
case ygopro.CardZone.HAND:
return matStore.hands;
case ygopro.CardZone.DECK:
return matStore.decks;
case ygopro.CardZone.GRAVE:
return matStore.graveyards;
case ygopro.CardZone.REMOVED:
return matStore.banishedZones;
case ygopro.CardZone.EXTRA:
return matStore.extraDecks;
default:
console.error("in error", zone);
return matStore.extraDecks;
}
};
const { SZONE, MZONE, GRAVE, REMOVED, HAND, DECK, EXTRA } = ygopro.CardZone;
/**
* 💡 决斗盘状态仓库,本文件核心,
* 具体介绍可以点进`MatState`去看
*/
export const matStore: MatState = proxy<MatState>({
magics: genDuelCardArray(genBlock(SZONE, 6), SZONE),
monsters: genDuelCardArray(genBlock(MZONE, 7), MZONE),
graveyards: genDuelCardArray([], GRAVE),
banishedZones: genDuelCardArray([], REMOVED),
hands: genDuelCardArray([], HAND),
decks: genDuelCardArray([], DECK),
extraDecks: genDuelCardArray([], EXTRA),
timeLimits: {
// 时间限制
me: -1,
op: -1,
of: (controller: number) => matStore.timeLimits[getWhom(controller)],
set: (controller: number, time: number) => {
matStore.timeLimits[getWhom(controller)] = time;
},
},
initInfo,
selfType: ygopro.StocTypeChange.SelfType.UNKNOWN,
hint,
currentPlayer: -1,
phase: {
currentPhase: "UNKNOWN", // TODO 当前的阶段 应该改成enum
enableBp: false, // 允许进入战斗阶段
enableM2: false, // 允许进入M2阶段
enableEp: false, // 允许回合结束
},
result: ygopro.StocGameMessage.MsgWin.ActionType.UNKNOWN,
waiting: false,
unimplemented: 0,
// methods
in: getZone,
isMe,
});
// @ts-ignore 挂到全局,便于调试
window.matStore = matStore;
// 修改原型链,因为valtio的proxy会把原型链改掉。这应该是valtio的一个bug...有空提issue去改
(["me", "op"] as const).forEach((who) => {
(
[
"hands",
"decks",
"extraDecks",
"graveyards",
"banishedZones",
"monsters",
"magics",
] as const
).forEach((zone) => {
matStore[zone][who].__proto__ = CardArray.prototype;
});
});
import type { ygopro } from "@/api";
import type { CardMeta } from "@/api/cards"; import type { CardMeta } from "@/api/cards";
import type { ygopro } from "@/api/ocgcore/idl/ocgcore";
export interface DuelState { // >>> play mat state >>>
selfType?: number;
meInitInfo?: InitInfo; // 自己的初始状态
opInitInfo?: InitInfo; // 对手的初始状态
meHands?: HandState; // 自己的手牌 export interface BothSide<T> {
opHands?: HandState; // 对手的手牌 me: T;
op: T;
/** 根据controller返回对应的数组,op或者me */
of: (controller: number) => T;
}
/**
* CardState的顺序index,被称为sequence
*/
export interface DuelFieldState extends Array<CardState> {
/** 移除特定位置的卡片 */
remove: (sequence: number) => void;
/** 在末尾添加卡片 */
insert: (sequence: number, id: number) => Promise<void>;
/** 在指定位置插入卡片 */
add: (ids: number[]) => Promise<void>;
/** 设置占据这个位置的卡片信息 */
setOccupant: (
sequence: number,
id: number,
position?: ygopro.CardPosition
) => Promise<void>;
/** 添加 idle 的交互性 */
addIdleInteractivity: (
sequence: number,
interactivity: CardState["idleInteractivities"][number]
) => void;
/** 移除 idle 的交互性 */
clearIdleInteractivities: () => void;
/** 设置 place 的交互种类 */
setPlaceInteractivityType: (
sequence: number,
interactType: InteractType
) => void;
/** 移除 place 的交互性 */
clearPlaceInteractivity: () => void;
// 让原型链不报错
__proto__?: DuelFieldState;
}
type test = DuelFieldState extends (infer S)[] ? S : never;
export interface MatState {
selfType: number;
initInfo: BothSide<InitInfo> & {
set: (controller: number, obj: Partial<InitInfo>) => void;
}; // 双方的初始化信息
hands: BothSide<HandState>; // 双方的手牌
meMonsters?: MonsterState; // 自己的怪兽区状态 monsters: BothSide<MonsterState>; // 双方的怪兽区状态
opMonsters?: MonsterState; // 对手的怪兽区状态
meMagics?: MagicState; // 自己的魔法陷阱区状态 magics: BothSide<MagicState>; // 双方的魔法区状态
opMagics?: MagicState; // 对手的魔法陷阱区状态
meGraveyard?: GraveyardState; // 自己的墓地状态 graveyards: BothSide<GraveyardState>; // 双方的墓地状态
opGraveyard?: GraveyardState; // 对手的墓地状态
meBanishedZone?: BanishedZoneState; // 自己的除外区状态 banishedZones: BothSide<BanishedZoneState>; // 双方的除外区状态
opBanishedZone?: BanishedZoneState; // 对手的除外区状态
meDeck?: DeckState; // 自己的卡组状态 decks: BothSide<DeckState>; // 双方的卡组状态
opDeck?: DeckState; // 对手的卡组状态
meExtraDeck?: ExtraDeckState; // 自己的额外卡组状态 extraDecks: BothSide<ExtraDeckState>; // 双方的额外卡组状态
opExtraDeck?: ExtraDeckState; // 对手的额外卡组状态
meTimeLimit?: TimeLimit; // 自己的计时 timeLimits: BothSide<number> & {
opTimeLimit?: TimeLimit; // 对手的计时 set: (controller: number, time: number) => void;
}; // 双方的时间限制
hint?: HintState; hint: HintState;
currentPlayer?: number; // 当前的操作方 currentPlayer: number; // 当前的操作方
phase?: PhaseState; phase: PhaseState;
result?: ygopro.StocGameMessage.MsgWin.ActionType; result: ygopro.StocGameMessage.MsgWin.ActionType;
waiting?: boolean; waiting: boolean;
unimplemented?: number; // 未处理的`Message` unimplemented: number; // 未处理的`Message`
// >>> methods >>>
/** 根据zone获取hands/masters/magics... */
in: (zone: ygopro.CardZone) => BothSide<DuelFieldState>;
/** 根据自己的先后手判断是否是自己 */
isMe: (player: number) => boolean;
} }
export interface InitInfo { export interface InitInfo {
...@@ -50,16 +97,19 @@ export interface InitInfo { ...@@ -50,16 +97,19 @@ export interface InitInfo {
extraSize: number; extraSize: number;
} }
/**
* 场上某位置的状态,
* 以后会更名为 BlockState
*/
export interface CardState { export interface CardState {
occupant?: CardMeta; // 占据此位置的卡牌元信息 occupant?: CardMeta; // 占据此位置的卡牌元信息
location: { location: {
controler?: number; controler?: number; // 控制这个位置的玩家,0或1
location?: number; location: ygopro.CardZone; // 怪兽区/魔法陷阱区/手牌/卡组/墓地/除外区
position?: ygopro.CardPosition; position?: ygopro.CardPosition; // 卡片的姿势:攻击还是守备
overlay_sequence?: number; }; // 位置信息,叫location的原因是为了和ygo对齐
}; // 位置信息
idleInteractivities: Interactivity<number>[]; // IDLE状态下的互动信息 idleInteractivities: Interactivity<number>[]; // IDLE状态下的互动信息
placeInteractivities?: Interactivity<{ placeInteractivity?: Interactivity<{
controler: number; controler: number;
zone: ygopro.CardZone; zone: ygopro.CardZone;
sequence: number; sequence: number;
...@@ -69,10 +119,6 @@ export interface CardState { ...@@ -69,10 +119,6 @@ export interface CardState {
reload?: boolean; // 这个字段会在收到MSG_RELOAD_FIELD的时候设置成true,在收到MSG_UPDATE_DATE的时候设置成false reload?: boolean; // 这个字段会在收到MSG_RELOAD_FIELD的时候设置成true,在收到MSG_UPDATE_DATE的时候设置成false
} }
export interface DuelFieldState {
inner: CardState[];
}
export interface Interactivity<T> { export interface Interactivity<T> {
interactType: InteractType; interactType: InteractType;
// 如果`interactType`是`ACTIVATE`,这个字段是对应的效果编号 // 如果`interactType`是`ACTIVATE`,这个字段是对应的效果编号
...@@ -112,15 +158,21 @@ export interface ExtraDeckState extends DuelFieldState {} ...@@ -112,15 +158,21 @@ export interface ExtraDeckState extends DuelFieldState {}
export interface TimeLimit { export interface TimeLimit {
leftTime: number; leftTime: number;
} }
export interface HintState { export interface HintState {
code: number; code: number;
msg?: string; msg?: string;
esHint?: string; esHint?: string;
esSelectHint?: string; esSelectHint?: string;
} }
export type PhaseName =
keyof typeof ygopro.StocGameMessage.MsgNewPhase.PhaseType;
export interface PhaseState { export interface PhaseState {
currentPhase: string; // TODO 当前的阶段 应该改成enum currentPhase: PhaseName; // TODO 当前的阶段 应该改成enum
enableBp: boolean; // 允许进入战斗阶段 enableBp: boolean; // 允许进入战斗阶段
enableM2: boolean; // 允许进入M2阶段 enableM2: boolean; // 允许进入M2阶段
enableEp: boolean; // 允许回合结束 enableEp: boolean; // 允许回合结束
} }
// <<< play mat state <<<
export * from "./methods";
export * from "./store";
import { matStore } from "@/stores";
export const clearAllIdleInteractivities = (controller: number) => {
matStore.banishedZones.of(controller).clearIdleInteractivities();
matStore.decks.of(controller).clearIdleInteractivities();
matStore.extraDecks.of(controller).clearIdleInteractivities();
matStore.graveyards.of(controller).clearIdleInteractivities();
matStore.hands.of(controller).clearIdleInteractivities();
matStore.magics.of(controller).clearIdleInteractivities();
matStore.monsters.of(controller).clearIdleInteractivities();
};
import { ygopro } from "@/api";
import { matStore } from "@/stores";
/** 清空所有place互动性,也可以删除某一个zone的互动性。zone为空则为清除所有。 */
export const clearAllPlaceInteradtivities = (
controller: number,
zone?: ygopro.CardZone
) => {
if (zone) {
matStore.in(zone).of(controller).clearPlaceInteractivity();
} else {
matStore.banishedZones.of(controller).clearPlaceInteractivity();
matStore.decks.of(controller).clearPlaceInteractivity();
matStore.extraDecks.of(controller).clearPlaceInteractivity();
matStore.graveyards.of(controller).clearPlaceInteractivity();
matStore.hands.of(controller).clearPlaceInteractivity();
matStore.magics.of(controller).clearPlaceInteractivity();
matStore.monsters.of(controller).clearPlaceInteractivity();
}
};
import { fetchCard, type ygopro } from "@/api";
import { getCardByLocation, messageStore } from "@/stores";
export const fetchCheckCardMetasV2 = async ({
selected,
options,
}: {
selected: boolean;
options: {
code: number;
location: ygopro.CardLocation;
response: number;
name?: string;
desc?: string;
}[];
}) => {
const metas = await Promise.all(
options.map(async (option) => {
return await fetchCard(option.code, true);
})
);
for (const option of options) {
if (option.code == 0) {
const newCode = getCardByLocation(option.location)?.occupant?.id || 0;
option.code = newCode;
}
}
options.forEach((option) => {
metas.forEach((meta) => {
if (option.code == meta.id) {
option.name = meta.text.name;
option.desc = meta.text.desc;
}
});
});
if (selected) {
messageStore.checkCardModalV2.selectedOptions = options;
} else {
messageStore.checkCardModalV2.selectableOptions = options;
}
};
import { fetchCard, type ygopro } from "@/api";
import { getCardByLocation, messageStore } from "@/stores";
export const fetchCheckCardMetasV3 = async ({
mustSelect,
options,
}: {
mustSelect: boolean;
options: {
code: number;
location: ygopro.CardLocation;
level1: number;
level2: number;
response: number;
}[];
}) => {
const metas = await Promise.all(
options.map(async (option) => {
return await fetchCard(option.code, true);
})
);
const newOptions = options.map((option) => {
if (option.code == 0) {
const newCode = getCardByLocation(option.location)?.occupant?.id || 0;
option.code = newCode;
}
return {
meta: { id: option.code, data: {}, text: {} },
level1: option.level1,
level2: option.level2,
response: option.response,
};
});
newOptions.forEach((option) => {
metas.forEach((meta) => {
if (option.meta.id == meta.id) {
option.meta = meta;
}
});
});
if (mustSelect) {
messageStore.checkCardModalV3.mustSelectList = newOptions;
} else {
messageStore.checkCardModalV3.selectAbleList = newOptions;
}
};
export * from "./clearAllIdleInteractivities";
export * from "./clearAllPlaceInteradtivities";
export * from "./fetchCheckCardMetasV2";
export * from "./fetchCheckCardMetasV3";
import { proxy } from "valtio";
import type { ModalState } from "./types";
export const messageStore = proxy<ModalState>({
cardModal: { isOpen: false, interactivies: [], counters: {} },
cardListModal: { isOpen: false, list: [] },
checkCardModal: { isOpen: false, cancelAble: false, tags: [] },
yesNoModal: { isOpen: false },
positionModal: { isOpen: false, positions: [] },
optionModal: { isOpen: false, options: [] },
checkCardModalV2: {
isOpen: false,
cancelAble: false,
finishAble: false,
responseable: false,
selectableOptions: [],
selectedOptions: [],
},
checkCardModalV3: {
isOpen: false,
overflow: false,
allLevel: 0,
mustSelectList: [],
selectAbleList: [],
},
checkCounterModal: {
isOpen: false,
options: [],
},
sortCardModal: {
isOpen: false,
options: [],
},
});
// >>> modal types >>>
import { CardMeta } from "@/api/cards"; import type { CardMeta, ygopro } from "@/api";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
type CardLocation = ReturnType<typeof ygopro.CardLocation.prototype.toObject>; type CardLocation = ReturnType<typeof ygopro.CardLocation.prototype.toObject>;
export interface ModalState { export interface ModalState {
...@@ -113,14 +112,3 @@ export interface ModalState { ...@@ -113,14 +112,3 @@ export interface ModalState {
}[]; }[];
}; };
} }
export * from "./cardListModalSlice";
export * from "./cardModalSlice";
export * from "./checkCardModalSlice";
export * from "./checkCardModalV2Slice";
export * from "./checkCardModalV3Slice";
export * from "./checkCounterModalSlice";
export * from "./optionModalSlice";
export * from "./positionModalSlice";
export * from "./sortCardModalSlice";
export * from "./yesNoModalSlice";
...@@ -8,6 +8,6 @@ export interface MoraState { ...@@ -8,6 +8,6 @@ export interface MoraState {
export const moraStore = proxy<MoraState>({ export const moraStore = proxy<MoraState>({
duelStart: false, duelStart: false,
selectHandAble: false, selectHandAble: true,
selectTpAble: false, selectTpAble: true,
}); });
...@@ -2,7 +2,6 @@ import * as BABYLON from "@babylonjs/core"; ...@@ -2,7 +2,6 @@ import * as BABYLON from "@babylonjs/core";
import { Row } from "antd"; import { Row } from "antd";
import React from "react"; import React from "react";
import { Engine, Scene } from "react-babylonjs"; import { Engine, Scene } from "react-babylonjs";
import { Provider, ReactReduxContext } from "react-redux";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
...@@ -75,28 +74,22 @@ const NeosSider = () => ( ...@@ -75,28 +74,22 @@ const NeosSider = () => (
); );
const NeosCanvas = () => ( const NeosCanvas = () => (
<ReactReduxContext.Consumer> <Engine antialias adaptToDeviceRatio canvasId="babylonJS">
{({ store }) => ( <Scene>
<Engine antialias adaptToDeviceRatio canvasId="babylonJS"> <Camera />
<Scene> <Light />
<Provider store={store}> <Hands />
<Camera /> <Monsters />
<Light /> <Magics />
<Hands /> <Field />
<Monsters /> <CommonDeck />
<Magics /> <ExtraDeck />
<Field /> <Graveyard />
<CommonDeck /> <BanishedZone />
<ExtraDeck /> <Field />
<Graveyard /> <Ground />
<BanishedZone /> </Scene>
<Field /> </Engine>
<Ground />
</Provider>
</Scene>
</Engine>
)}
</ReactReduxContext.Consumer>
); );
const Camera = () => ( const Camera = () => (
......
import { Alert as AntdAlert } from "antd"; import { Alert as AntdAlert } from "antd";
import React from "react"; import React from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { useSnapshot } from "valtio";
import { sendSurrender } from "@/api/ocgcore/ocgHelper"; import { sendSurrender } from "@/api";
import { useAppSelector } from "@/hook"; import { matStore } from "@/stores";
import { selectUnimplemented } from "@/reducers/duel/mod";
export const Alert = () => { export const Alert = () => {
const unimplemented = useAppSelector(selectUnimplemented); const matSnap = useSnapshot(matStore);
const unimplemented = matSnap.unimplemented;
const navigate = useNavigate(); const navigate = useNavigate();
return ( return (
......
import { Button, Drawer, List } from "antd"; import { Button, Drawer, List } from "antd";
import React from "react"; import React from "react";
import { useSnapshot } from "valtio";
import { sendSelectIdleCmdResponse } from "@/api/ocgcore/ocgHelper"; import { sendSelectIdleCmdResponse } from "@/api";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import { useAppSelector } from "@/hook";
import { import {
clearAllIdleInteractivities, clearAllIdleInteractivities as clearAllIdleInteractivities,
setCardListModalIsOpen, messageStore,
} from "@/reducers/duel/mod"; } from "@/stores";
import {
selectCardListModalInfo,
selectCardListModalIsOpen,
} from "@/reducers/duel/modal/mod";
import { store } from "@/store";
const NeosConfig = useConfig(); const NeosConfig = useConfig();
const CARD_WIDTH = 100; const CARD_WIDTH = 100;
const { cardListModal } = messageStore;
export const CardListModal = () => { export const CardListModal = () => {
const dispatch = store.dispatch; const snapCardListModal = useSnapshot(cardListModal);
const isOpen = useAppSelector(selectCardListModalIsOpen); const isOpen = snapCardListModal.isOpen;
const list = useAppSelector(selectCardListModalInfo); const list = snapCardListModal.list as typeof cardListModal.list;
const handleOkOrCancel = () => { const handleOkOrCancel = () => {
dispatch(setCardListModalIsOpen(false)); cardListModal.isOpen = false;
}; };
return ( return (
...@@ -39,9 +36,9 @@ export const CardListModal = () => { ...@@ -39,9 +36,9 @@ export const CardListModal = () => {
key={idx} key={idx}
onClick={() => { onClick={() => {
sendSelectIdleCmdResponse(interactivy.response); sendSelectIdleCmdResponse(interactivy.response);
dispatch(setCardListModalIsOpen(false)); cardListModal.isOpen = false;
dispatch(clearAllIdleInteractivities(0)); clearAllIdleInteractivities(0);
dispatch(clearAllIdleInteractivities(1)); clearAllIdleInteractivities(1);
}} }}
> >
{interactivy.desc} {interactivy.desc}
......
...@@ -3,22 +3,14 @@ import { Button, Card, Col, Modal, Row } from "antd"; ...@@ -3,22 +3,14 @@ import { Button, Card, Col, Modal, Row } from "antd";
import { ReactComponent as BattleSvg } from "neos-assets/battle-axe.svg"; import { ReactComponent as BattleSvg } from "neos-assets/battle-axe.svg";
import { ReactComponent as DefenceSvg } from "neos-assets/checked-shield.svg"; import { ReactComponent as DefenceSvg } from "neos-assets/checked-shield.svg";
import React from "react"; import React from "react";
import { useSnapshot } from "valtio";
import { sendSelectIdleCmdResponse } from "@/api/ocgcore/ocgHelper"; import { fetchStrings, sendSelectIdleCmdResponse } from "@/api";
import { fetchStrings } from "@/api/strings";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import { useAppSelector } from "@/hook";
import { import {
clearAllIdleInteractivities, clearAllIdleInteractivities as clearAllIdleInteractivities,
setCardModalIsOpen, messageStore,
} from "@/reducers/duel/mod"; } from "@/stores";
import {
selectCardModalCounters,
selectCardModalInteractivies,
selectCardModalIsOpen,
selectCardModalMeta,
} from "@/reducers/duel/modal/mod";
import { store } from "@/store";
import { import {
Attribute2StringCodeMap, Attribute2StringCodeMap,
...@@ -31,10 +23,18 @@ const NeosConfig = useConfig(); ...@@ -31,10 +23,18 @@ const NeosConfig = useConfig();
const { Meta } = Card; const { Meta } = Card;
const CARD_WIDTH = 240; const CARD_WIDTH = 240;
const { cardModal } = messageStore;
export const CardModal = () => { export const CardModal = () => {
const dispatch = store.dispatch; const snapCardModal = useSnapshot(cardModal);
const isOpen = useAppSelector(selectCardModalIsOpen);
const meta = useAppSelector(selectCardModalMeta); // const dispatch = store.dispatch;
// const isOpen = useAppSelector(selectCardModalIsOpen);
// const meta = useAppSelector(selectCardModalMeta);
const isOpen = snapCardModal.isOpen;
const meta = snapCardModal.meta;
const name = meta?.text.name; const name = meta?.text.name;
const types = meta?.data.type; const types = meta?.data.type;
const race = meta?.data.race; const race = meta?.data.race;
...@@ -43,14 +43,17 @@ export const CardModal = () => { ...@@ -43,14 +43,17 @@ export const CardModal = () => {
const desc = meta?.text.desc; const desc = meta?.text.desc;
const atk = meta?.data.atk; const atk = meta?.data.atk;
const def = meta?.data.def; const def = meta?.data.def;
const counters = useAppSelector(selectCardModalCounters);
// const counters = useAppSelector(selectCardModalCounters);
const counters = snapCardModal.counters;
const imgUrl = meta?.id const imgUrl = meta?.id
? `${NeosConfig.cardImgUrl}/${meta.id}.jpg` ? `${NeosConfig.cardImgUrl}/${meta.id}.jpg`
: undefined; : undefined;
const interactivies = useAppSelector(selectCardModalInteractivies); const interactivies = snapCardModal.interactivies;
const handleOkOrCancel = () => { const handleOkOrCancel = () => {
dispatch(setCardModalIsOpen(false)); cardModal.isOpen = false;
}; };
return ( return (
...@@ -76,9 +79,9 @@ export const CardModal = () => { ...@@ -76,9 +79,9 @@ export const CardModal = () => {
key={idx} key={idx}
onClick={() => { onClick={() => {
sendSelectIdleCmdResponse(interactive.response); sendSelectIdleCmdResponse(interactive.response);
dispatch(setCardModalIsOpen(false)); cardModal.isOpen = false;
dispatch(clearAllIdleInteractivities(0)); clearAllIdleInteractivities(0);
dispatch(clearAllIdleInteractivities(1)); clearAllIdleInteractivities(1);
}} }}
> >
{interactive.desc} {interactive.desc}
......
...@@ -2,42 +2,31 @@ import { ThunderboltOutlined } from "@ant-design/icons"; ...@@ -2,42 +2,31 @@ import { ThunderboltOutlined } from "@ant-design/icons";
import { CheckCard, CheckCardProps } from "@ant-design/pro-components"; import { CheckCard, CheckCardProps } from "@ant-design/pro-components";
import { Button, Col, Popover, Row } from "antd"; import { Button, Col, Popover, Row } from "antd";
import React, { useState } from "react"; import React, { useState } from "react";
import { useSnapshot } from "valtio";
import { import { sendSelectCardResponse, sendSelectChainResponse } from "@/api";
sendSelectCardResponse,
sendSelectChainResponse,
} from "@/api/ocgcore/ocgHelper";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import { useAppSelector } from "@/hook"; import { matStore, messageStore } from "@/stores";
import { selectHint } from "@/reducers/duel/hintSlice";
import {
resetCheckCardModal,
setCheckCardModalIsOpen,
} from "@/reducers/duel/mod";
import {
selectCheckCardModalCacnelResponse,
selectCheckCardModalCancelAble,
selectCheckCardModalIsOpen,
selectCheckCardModalMinMax,
selectCheckCardModalOnSubmit,
selectCheckCardModalTags,
} from "@/reducers/duel/modal/mod";
import { store } from "@/store";
import { DragModal } from "./DragModal"; import { DragModal } from "./DragModal";
const NeosConfig = useConfig(); const NeosConfig = useConfig();
const { checkCardModal } = messageStore;
export const CheckCardModal = () => { export const CheckCardModal = () => {
const dispatch = store.dispatch; const snapCheckCardModal = useSnapshot(checkCardModal);
const isOpen = useAppSelector(selectCheckCardModalIsOpen); const isOpen = snapCheckCardModal.isOpen;
const { min, max } = useAppSelector(selectCheckCardModalMinMax); const min = snapCheckCardModal.selectMin ?? 0;
const tabs = useAppSelector(selectCheckCardModalTags); const max = snapCheckCardModal.selectMax ?? 10;
const onSubmit = useAppSelector(selectCheckCardModalOnSubmit); const tabs = snapCheckCardModal.tags;
const cancelAble = useAppSelector(selectCheckCardModalCancelAble); const onSubmit = snapCheckCardModal.onSubmit;
const cancelResponse = useAppSelector(selectCheckCardModalCacnelResponse); const cancelAble = snapCheckCardModal.cancelAble;
const cancelResponse = snapCheckCardModal.cancelResponse;
const [response, setResponse] = useState<number[]>([]); const [response, setResponse] = useState<number[]>([]);
const defaultValue: number[] = []; const defaultValue: number[] = [];
const hint = useAppSelector(selectHint); const hint = useSnapshot(matStore.hint);
const preHintMsg = hint?.esHint || ""; const preHintMsg = hint?.esHint || "";
const selectHintMsg = hint?.esSelectHint || "请选择卡片"; const selectHintMsg = hint?.esSelectHint || "请选择卡片";
...@@ -60,6 +49,15 @@ export const CheckCardModal = () => { ...@@ -60,6 +49,15 @@ export const CheckCardModal = () => {
} }
}; };
const resetCheckCardModal = () => {
checkCardModal.isOpen = false;
checkCardModal.selectMin = undefined;
checkCardModal.selectMax = undefined;
checkCardModal.cancelAble = false;
checkCardModal.cancelResponse = undefined;
checkCardModal.tags = [];
};
return ( return (
<DragModal <DragModal
title={`${preHintMsg} ${selectHintMsg} ${min}-${max}`} title={`${preHintMsg} ${selectHintMsg} ${min}-${max}`}
...@@ -71,8 +69,8 @@ export const CheckCardModal = () => { ...@@ -71,8 +69,8 @@ export const CheckCardModal = () => {
disabled={response.length < min || response.length > max} disabled={response.length < min || response.length > max}
onClick={() => { onClick={() => {
sendResponseHandler(onSubmit, response); sendResponseHandler(onSubmit, response);
dispatch(setCheckCardModalIsOpen(false)); checkCardModal.isOpen = false;
dispatch(resetCheckCardModal()); resetCheckCardModal();
}} }}
onFocus={() => {}} onFocus={() => {}}
onBlur={() => {}} onBlur={() => {}}
...@@ -85,8 +83,8 @@ export const CheckCardModal = () => { ...@@ -85,8 +83,8 @@ export const CheckCardModal = () => {
if (cancelResponse) { if (cancelResponse) {
sendResponseHandler(onSubmit, [cancelResponse]); sendResponseHandler(onSubmit, [cancelResponse]);
} }
dispatch(setCheckCardModalIsOpen(false)); checkCardModal.isOpen = false;
dispatch(resetCheckCardModal()); resetCheckCardModal();
}} }}
onFocus={() => {}} onFocus={() => {}}
onBlur={() => {}} onBlur={() => {}}
......
import { CheckCard } from "@ant-design/pro-components"; import { CheckCard } from "@ant-design/pro-components";
import { Button, Card, Col, Row } from "antd"; import { Button, Card, Col, Row } from "antd";
import React from "react"; import React from "react";
import { useSnapshot } from "valtio";
import { sendSelectUnselectCardResponse } from "@/api/ocgcore/ocgHelper"; import { sendSelectUnselectCardResponse } from "@/api";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import { useAppSelector } from "@/hook"; import { matStore, messageStore } from "@/stores";
import { selectHint } from "@/reducers/duel/hintSlice";
import {
resetCheckCardModalV2,
setCheckCardModalV2IsOpen,
setCheckCardModalV2ResponseAble,
} from "@/reducers/duel/mod";
import {
selectCheckCardModalV2CancelAble,
selectCheckCardModalV2FinishAble,
selectCheckCardModalV2IsOpen,
selectCheckCardModalV2MinMax,
selectCheckCardModalV2ResponseAble,
selectCheckCardModalV2SelectAbleOptions,
selectCheckCardModalV2SelectedOptions,
} from "@/reducers/duel/modal/checkCardModalV2Slice";
import { store } from "@/store";
import { DragModal } from "./DragModal"; import { DragModal } from "./DragModal";
const { checkCardModalV2 } = messageStore;
const NeosConfig = useConfig(); const NeosConfig = useConfig();
export const CheckCardModalV2 = () => { export const CheckCardModalV2 = () => {
const dispatch = store.dispatch; const snapCheckCardModalV2 = useSnapshot(checkCardModalV2);
const isOpen = useAppSelector(selectCheckCardModalV2IsOpen);
const { min, max } = useAppSelector(selectCheckCardModalV2MinMax); const isOpen = snapCheckCardModalV2.isOpen;
const cancelable = useAppSelector(selectCheckCardModalV2CancelAble); const min = snapCheckCardModalV2.selectMin ?? 0;
const finishable = useAppSelector(selectCheckCardModalV2FinishAble); const max = snapCheckCardModalV2.selectMax ?? 10;
const selectableOptions = useAppSelector( const cancelable = snapCheckCardModalV2.cancelAble;
selectCheckCardModalV2SelectAbleOptions const finishable = snapCheckCardModalV2.finishAble;
); const selectableOptions = snapCheckCardModalV2.selectableOptions;
const selectedOptions = useAppSelector(selectCheckCardModalV2SelectedOptions); const selectedOptions = snapCheckCardModalV2.selectedOptions;
const responseable = useAppSelector(selectCheckCardModalV2ResponseAble); const responseable = snapCheckCardModalV2.responseable;
const hint = useAppSelector(selectHint); const hint = useSnapshot(matStore.hint);
const preHintMsg = hint?.esHint || ""; const preHintMsg = hint?.esHint || "";
const selectHintMsg = hint?.esSelectHint || "请选择卡片"; const selectHintMsg = hint?.esSelectHint || "请选择卡片";
const resetCheckCardModalV2 = () => {
checkCardModalV2.isOpen = false;
checkCardModalV2.finishAble = false;
checkCardModalV2.cancelAble = false;
checkCardModalV2.responseable = false;
checkCardModalV2.selectableOptions = [];
checkCardModalV2.selectedOptions = [];
};
const onFinishOrCancel = () => { const onFinishOrCancel = () => {
sendSelectUnselectCardResponse({ cancel_or_finish: true }); sendSelectUnselectCardResponse({ cancel_or_finish: true });
dispatch(setCheckCardModalV2IsOpen(false));
dispatch(resetCheckCardModalV2()); checkCardModalV2.isOpen = false;
dispatch(setCheckCardModalV2ResponseAble(false)); checkCardModalV2.responseable = false;
resetCheckCardModalV2();
}; };
return ( return (
...@@ -75,10 +72,10 @@ export const CheckCardModalV2 = () => { ...@@ -75,10 +72,10 @@ export const CheckCardModalV2 = () => {
size="small" size="small"
onChange={(value) => { onChange={(value) => {
if (responseable) { if (responseable) {
dispatch(setCheckCardModalV2IsOpen(false));
// @ts-ignore // @ts-ignore
sendSelectUnselectCardResponse({ selected_ptr: value }); sendSelectUnselectCardResponse({ selected_ptr: value });
dispatch(setCheckCardModalV2ResponseAble(false)); checkCardModalV2.isOpen = false;
checkCardModalV2.responseable = false;
} }
}} }}
> >
......
import { CheckCard } from "@ant-design/pro-components"; import { CheckCard } from "@ant-design/pro-components";
import { Button, Card, Col, Row } from "antd"; import { Button, Card, Col, Row } from "antd";
import React, { useState } from "react"; import React, { useState } from "react";
import { useSnapshot } from "valtio";
import { sendSelectCardResponse } from "@/api/ocgcore/ocgHelper"; import { sendSelectCardResponse } from "@/api";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import { useAppSelector } from "@/hook"; import { matStore, messageStore } from "@/stores";
import { selectHint } from "@/reducers/duel/hintSlice";
import {
resetCheckCardModalV3,
setCheckCardModalV3IsOpen,
setCheckCardModalV3ResponseAble,
} from "@/reducers/duel/mod";
import { selectCheckCardModalV3 } from "@/reducers/duel/modal/checkCardModalV3Slice";
import { store } from "@/store";
import { DragModal } from "./DragModal"; import { DragModal } from "./DragModal";
const NeosConfig = useConfig(); const NeosConfig = useConfig();
const { checkCardModalV3 } = messageStore;
export const CheckCardModalV3 = () => { export const CheckCardModalV3 = () => {
const dispatch = store.dispatch; const snapCheckCardModalV3 = useSnapshot(checkCardModalV3);
const state = useAppSelector(selectCheckCardModalV3);
const isOpen = state.isOpen; const isOpen = snapCheckCardModalV3.isOpen;
const min = state.selectMin || 0; const min = snapCheckCardModalV3.selectMin || 0;
const max = state.selectMax || 0; const max = snapCheckCardModalV3.selectMax || 0;
const mustSelectOptions = state.mustSelectList; const mustSelectOptions = snapCheckCardModalV3.mustSelectList;
const selectAbleOptions = state.selectAbleList; const selectAbleOptions = snapCheckCardModalV3.selectAbleList;
const overflow = snapCheckCardModalV3.overflow;
const LevelSum = snapCheckCardModalV3.allLevel;
const [selectedOptions, setSelectedOptions] = useState([]); const [selectedOptions, setSelectedOptions] = useState([]);
const overflow = state.overflow;
const LevelSum = state.allLevel;
const Level1Sum = mustSelectOptions const Level1Sum = mustSelectOptions
.concat(selectedOptions) .concat(selectedOptions)
.map((option) => option.level1) .map((option) => option.level1)
...@@ -37,7 +33,7 @@ export const CheckCardModalV3 = () => { ...@@ -37,7 +33,7 @@ export const CheckCardModalV3 = () => {
.concat(selectedOptions) .concat(selectedOptions)
.map((option) => option.level2) .map((option) => option.level2)
.reduce((sum, current) => sum + current, 0); .reduce((sum, current) => sum + current, 0);
const hint = useAppSelector(selectHint); const hint = useSnapshot(matStore.hint);
const preHintMsg = hint?.esHint || ""; const preHintMsg = hint?.esHint || "";
const selectHintMsg = hint?.esSelectHint || "请选择卡片"; const selectHintMsg = hint?.esSelectHint || "请选择卡片";
...@@ -51,9 +47,12 @@ export const CheckCardModalV3 = () => { ...@@ -51,9 +47,12 @@ export const CheckCardModalV3 = () => {
sendSelectCardResponse( sendSelectCardResponse(
mustSelectOptions.concat(selectedOptions).map((option) => option.response) mustSelectOptions.concat(selectedOptions).map((option) => option.response)
); );
dispatch(setCheckCardModalV3IsOpen(false)); checkCardModalV3.isOpen = false;
dispatch(resetCheckCardModalV3()); checkCardModalV3.responseable = false;
dispatch(setCheckCardModalV3ResponseAble(false)); checkCardModalV3.overflow = false;
checkCardModalV3.allLevel = 0;
checkCardModalV3.mustSelectList = [];
checkCardModalV3.selectAbleList = [];
}; };
return ( return (
......
import { Button, Card, Col, InputNumber, Row } from "antd"; import { Button, Card, Col, InputNumber, Row } from "antd";
import React, { useState } from "react"; import React, { useState } from "react";
import { useSnapshot } from "valtio";
import { sendSelectCounterResponse } from "@/api/ocgcore/ocgHelper"; import { fetchStrings, sendSelectCounterResponse } from "@/api";
import { fetchStrings } from "@/api/strings";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import { useAppSelector } from "@/hook"; import { messageStore } from "@/stores";
import { clearCheckCounter } from "@/reducers/duel/mod";
import { selectCheckCounterModal } from "@/reducers/duel/modal/checkCounterModalSlice";
import { store } from "@/store";
import { DragModal } from "./DragModal"; import { DragModal } from "./DragModal";
const { checkCounterModal } = messageStore;
const NeosConfig = useConfig(); const NeosConfig = useConfig();
export const CheckCounterModal = () => { export const CheckCounterModal = () => {
const dispatch = store.dispatch; const snapCheckCounterModal = useSnapshot(checkCounterModal);
const state = useAppSelector(selectCheckCounterModal);
const isOpen = state.isOpen; const isOpen = snapCheckCounterModal.isOpen;
const counterName = fetchStrings("!counter", `0x${state.counterType!}`); const min = snapCheckCounterModal.min || 0;
const min = state.min || 0; const options = snapCheckCounterModal.options;
const options = state.options; const counterName = fetchStrings(
"!counter",
`0x${snapCheckCounterModal.counterType!}`
);
const [selected, setSelected] = useState(new Array(options.length)); const [selected, setSelected] = useState(new Array(options.length));
const sum = selected.reduce((sum, current) => sum + current, 0); const sum = selected.reduce((sum, current) => sum + current, 0);
const finishable = sum == min; const finishable = sum == min;
const onFinish = () => { const onFinish = () => {
sendSelectCounterResponse(selected); sendSelectCounterResponse(selected);
dispatch(clearCheckCounter()); messageStore.checkCounterModal.isOpen = false;
messageStore.checkCounterModal.min = undefined;
messageStore.checkCounterModal.counterType = undefined;
messageStore.checkCounterModal.options = [];
}; };
return ( return (
......
import { notification } from "antd"; import { notification } from "antd";
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { useSnapshot } from "valtio";
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { ygopro } from "@/api";
import { useAppSelector } from "@/hook";
import { selectHint } from "@/reducers/duel/hintSlice";
import { selectDuelResult, selectWaiting } from "@/reducers/duel/mod";
import { selectCurrentPhase } from "@/reducers/duel/phaseSlice";
import MsgWin = ygopro.StocGameMessage.MsgWin;
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import { matStore } from "@/stores";
const MsgWin = ygopro.StocGameMessage.MsgWin;
const NeosConfig = useConfig(); const NeosConfig = useConfig();
export const HintNotification = () => { export const HintNotification = () => {
const hint = useAppSelector(selectHint); const hintState = matStore.hint;
const currentPhase = useAppSelector(selectCurrentPhase); const hintSnap = useSnapshot(matStore.hint);
const waiting = useAppSelector(selectWaiting);
const result = useAppSelector(selectDuelResult); const currentPhase = matStore.phase.currentPhase;
const waiting = matStore.waiting;
const result = matStore.result;
const navigate = useNavigate(); const navigate = useNavigate();
const [api, contextHolder] = notification.useNotification({ const [api, contextHolder] = notification.useNotification({
maxCount: NeosConfig.ui.hint.maxCount, maxCount: NeosConfig.ui.hint.maxCount,
}); });
useEffect(() => { useEffect(() => {
if (hint && hint.msg) { if (hintState && hintState.msg) {
api.info({ api.info({
message: `${hint.msg}`, message: `${hintState.msg}`,
placement: "bottom", placement: "bottom",
}); });
} }
}, [hint?.msg]); }, [hintSnap?.msg]);
useEffect(() => { useEffect(() => {
if (currentPhase) { if (currentPhase) {
......
import { CheckCard } from "@ant-design/pro-components"; import { CheckCard } from "@ant-design/pro-components";
import { Button } from "antd"; import { Button } from "antd";
import React, { useState } from "react"; import React, { useState } from "react";
import { useSnapshot } from "valtio";
import { sendSelectOptionResponse } from "@/api/ocgcore/ocgHelper"; import { sendSelectOptionResponse } from "@/api";
import { useAppSelector } from "@/hook"; import { messageStore } from "@/stores";
import { resetOptionModal, setOptionModalIsOpen } from "@/reducers/duel/mod";
import {
selectOptionModalIsOpen,
selectOptionModalOptions,
} from "@/reducers/duel/modal/mod";
import { store } from "@/store";
import { DragModal } from "./DragModal"; import { DragModal } from "./DragModal";
const { optionModal } = messageStore;
export const OptionModal = () => { export const OptionModal = () => {
const dispatch = store.dispatch; const snapOptionModal = useSnapshot(optionModal);
const isOpen = useAppSelector(selectOptionModalIsOpen);
const options = useAppSelector(selectOptionModalOptions); const isOpen = snapOptionModal.isOpen;
const options = snapOptionModal.options;
const [selected, setSelected] = useState<number | undefined>(undefined); const [selected, setSelected] = useState<number | undefined>(undefined);
return ( return (
...@@ -30,8 +29,8 @@ export const OptionModal = () => { ...@@ -30,8 +29,8 @@ export const OptionModal = () => {
onClick={() => { onClick={() => {
if (selected !== undefined) { if (selected !== undefined) {
sendSelectOptionResponse(selected); sendSelectOptionResponse(selected);
dispatch(setOptionModalIsOpen(false)); optionModal.isOpen = false;
dispatch(resetOptionModal()); optionModal.options = [];
} }
}} }}
> >
......
...@@ -5,26 +5,17 @@ import { ReactComponent as EpSvg } from "neos-assets/power-button.svg"; ...@@ -5,26 +5,17 @@ import { ReactComponent as EpSvg } from "neos-assets/power-button.svg";
import { ReactComponent as Main2Svg } from "neos-assets/sword-in-stone.svg"; import { ReactComponent as Main2Svg } from "neos-assets/sword-in-stone.svg";
import { ReactComponent as SurrenderSvg } from "neos-assets/truce.svg"; import { ReactComponent as SurrenderSvg } from "neos-assets/truce.svg";
import React, { useState } from "react"; import React, { useState } from "react";
import { useSnapshot } from "valtio";
import { import {
sendSelectBattleCmdResponse, sendSelectBattleCmdResponse,
sendSelectIdleCmdResponse, sendSelectIdleCmdResponse,
sendSurrender, sendSurrender,
} from "@/api/ocgcore/ocgHelper"; } from "@/api";
import { useAppSelector } from "@/hook";
import { import {
clearAllIdleInteractivities, clearAllIdleInteractivities as clearAllIdleInteractivities,
setEnableBp, matStore,
setEnableEp, } from "@/stores";
setEnableM2,
} from "@/reducers/duel/mod";
import {
selectCurrentPhase,
selectEnableBp,
selectEnableEp,
selectEnableM2,
} from "@/reducers/duel/phaseSlice";
import { store } from "@/store";
const IconSize = "150%"; const IconSize = "150%";
const SpaceSize = 16; const SpaceSize = 16;
...@@ -47,12 +38,15 @@ const PhaseButton = (props: { ...@@ -47,12 +38,15 @@ const PhaseButton = (props: {
); );
}; };
const { phase } = matStore;
export const Phase = () => { export const Phase = () => {
const dispatch = store.dispatch; const snapPhase = useSnapshot(phase);
const enableBp = useAppSelector(selectEnableBp); const enableBp = snapPhase.enableBp;
const enableM2 = useAppSelector(selectEnableM2); const enableM2 = snapPhase.enableM2;
const enableEp = useAppSelector(selectEnableEp); const enableEp = snapPhase.enableEp;
const currentPhase = useAppSelector(selectCurrentPhase); const currentPhase = snapPhase.currentPhase;
const [modalOpen, setModalOpen] = useState(false); const [modalOpen, setModalOpen] = useState(false);
const response = const response =
...@@ -65,25 +59,22 @@ export const Phase = () => { ...@@ -65,25 +59,22 @@ export const Phase = () => {
: 7; : 7;
const onBp = () => { const onBp = () => {
dispatch(clearAllIdleInteractivities(0));
dispatch(clearAllIdleInteractivities(0));
sendSelectIdleCmdResponse(6); sendSelectIdleCmdResponse(6);
dispatch(setEnableBp(false)); clearAllIdleInteractivities(0); // 为什么要clear两次?
clearAllIdleInteractivities(0);
phase.enableBp = false;
}; };
const onM2 = () => { const onM2 = () => {
dispatch(clearAllIdleInteractivities(0));
dispatch(clearAllIdleInteractivities(0));
sendSelectBattleCmdResponse(2); sendSelectBattleCmdResponse(2);
dispatch(setEnableM2(false)); clearAllIdleInteractivities(0);
clearAllIdleInteractivities(0);
phase.enableM2 = false;
}; };
const onEp = () => { const onEp = () => {
dispatch(clearAllIdleInteractivities(0));
dispatch(clearAllIdleInteractivities(0));
sendSelectIdleCmdResponse(response); sendSelectIdleCmdResponse(response);
dispatch(setEnableEp(false)); clearAllIdleInteractivities(0);
clearAllIdleInteractivities(0);
phase.enableEp = false;
}; };
const onSurrender = () => { const onSurrender = () => {
setModalOpen(true); setModalOpen(true);
......
import { CheckCard } from "@ant-design/pro-components"; import { CheckCard } from "@ant-design/pro-components";
import { Button } from "antd"; import { Button } from "antd";
import React, { useState } from "react"; import React, { useState } from "react";
import { useSnapshot } from "valtio";
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { sendSelectPositionResponse, ygopro } from "@/api";
import { sendSelectPositionResponse } from "@/api/ocgcore/ocgHelper"; import { messageStore } from "@/stores";
import { useAppSelector } from "@/hook";
import {
resetPositionModal,
setPositionModalIsOpen,
} from "@/reducers/duel/mod";
import {
selectPositionModalIsOpen,
selectPositionModalPositions,
} from "@/reducers/duel/modal/mod";
import { store } from "@/store";
import { DragModal } from "./DragModal"; import { DragModal } from "./DragModal";
const { positionModal } = messageStore;
export const PositionModal = () => { export const PositionModal = () => {
const dispatch = store.dispatch; const snapPositionModal = useSnapshot(positionModal);
const isOpen = useAppSelector(selectPositionModalIsOpen); const isOpen = snapPositionModal.isOpen;
const positions = useAppSelector(selectPositionModalPositions); const positions = snapPositionModal.positions;
const [selected, setSelected] = useState<ygopro.CardPosition | undefined>( const [selected, setSelected] = useState<ygopro.CardPosition | undefined>(
undefined undefined
); );
...@@ -36,8 +30,8 @@ export const PositionModal = () => { ...@@ -36,8 +30,8 @@ export const PositionModal = () => {
onClick={() => { onClick={() => {
if (selected !== undefined) { if (selected !== undefined) {
sendSelectPositionResponse(selected); sendSelectPositionResponse(selected);
dispatch(setPositionModalIsOpen(false)); positionModal.isOpen = false;
dispatch(resetPositionModal()); positionModal.positions = [];
} }
}} }}
> >
......
...@@ -2,7 +2,7 @@ import { SendOutlined } from "@ant-design/icons"; ...@@ -2,7 +2,7 @@ import { SendOutlined } from "@ant-design/icons";
import { Button, Col, Input, Row } from "antd"; import { Button, Col, Input, Row } from "antd";
import React, { useState } from "react"; import React, { useState } from "react";
import { sendChat } from "@/api/ocgcore/ocgHelper"; import { sendChat } from "@/api";
export const SendBox = () => { export const SendBox = () => {
const [content, setContent] = useState(""); const [content, setContent] = useState("");
......
...@@ -17,22 +17,21 @@ import { ...@@ -17,22 +17,21 @@ import {
import { CSS } from "@dnd-kit/utilities"; import { CSS } from "@dnd-kit/utilities";
import { Button, Card, Modal } from "antd"; import { Button, Card, Modal } from "antd";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useSnapshot } from "valtio";
import { sendSortCardResponse } from "@/api";
import { CardMeta } from "@/api/cards"; import { CardMeta } from "@/api/cards";
import { sendSortCardResponse } from "@/api/ocgcore/ocgHelper";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import { useAppSelector } from "@/hook"; import { messageStore } from "@/stores";
import { resetSortCardModal } from "@/reducers/duel/mod";
import { selectSortCardModal } from "@/reducers/duel/modal/sortCardModalSlice";
import { store } from "@/store";
const NeosConfig = useConfig(); const NeosConfig = useConfig();
const { sortCardModal } = messageStore;
export const SortCardModal = () => { export const SortCardModal = () => {
const dispatch = store.dispatch; const snapSortCardModal = useSnapshot(sortCardModal);
const state = useAppSelector(selectSortCardModal); const isOpen = snapSortCardModal.isOpen;
const isOpen = state.isOpen; const options = snapSortCardModal.options;
const options = state.options;
const [items, setItems] = useState(options); const [items, setItems] = useState(options);
const sensors = useSensors( const sensors = useSensors(
useSensor(PointerSensor), useSensor(PointerSensor),
...@@ -43,7 +42,8 @@ export const SortCardModal = () => { ...@@ -43,7 +42,8 @@ export const SortCardModal = () => {
const onFinish = () => { const onFinish = () => {
sendSortCardResponse(items.map((item) => item.response)); sendSortCardResponse(items.map((item) => item.response));
dispatch(resetSortCardModal()); sortCardModal.isOpen = false;
sortCardModal.options = [];
}; };
const onDragEnd = (event: DragEndEvent) => { const onDragEnd = (event: DragEndEvent) => {
const { active, over } = event; const { active, over } = event;
...@@ -52,7 +52,7 @@ export const SortCardModal = () => { ...@@ -52,7 +52,7 @@ export const SortCardModal = () => {
setItems((items) => { setItems((items) => {
const oldIndex = items.findIndex((item) => item.response == active.id); const oldIndex = items.findIndex((item) => item.response == active.id);
const newIndex = items.findIndex((item) => item.response === over?.id); const newIndex = items.findIndex((item) => item.response === over?.id);
// @ts-ignore
return arrayMove(items, oldIndex, newIndex); return arrayMove(items, oldIndex, newIndex);
}); });
} }
......
...@@ -4,12 +4,6 @@ import { Avatar } from "antd"; ...@@ -4,12 +4,6 @@ import { Avatar } from "antd";
import React from "react"; import React from "react";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import { useAppSelector } from "@/hook";
import {
selectMeInitInfo,
selectOpInitInfo,
} from "@/reducers/duel/initInfoSlice";
import { selectWaiting } from "@/reducers/duel/mod";
const NeosConfig = useConfig(); const NeosConfig = useConfig();
...@@ -18,10 +12,14 @@ const avatarSize = 40; ...@@ -18,10 +12,14 @@ const avatarSize = 40;
const ME_VALUE = "myself"; const ME_VALUE = "myself";
const OP_VALUE = "opponent"; const OP_VALUE = "opponent";
import { useSnapshot } from "valtio";
import { matStore } from "@/stores";
export const PlayerStatus = () => { export const PlayerStatus = () => {
const meInfo = useAppSelector(selectMeInitInfo); const meInfo = useSnapshot(matStore.initInfo.me);
const opInfo = useAppSelector(selectOpInitInfo); const opInfo = useSnapshot(matStore.initInfo.op);
const waiting = useAppSelector(selectWaiting) || false; const waiting = useSnapshot(matStore).waiting;
return ( return (
<CheckCard.Group <CheckCard.Group
......
import { MessageOutlined } from "@ant-design/icons"; import { MessageOutlined } from "@ant-design/icons";
import { Timeline, TimelineItemProps } from "antd"; import { Timeline, TimelineItemProps } from "antd";
import React, { useContext, useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useSnapshot } from "valtio"; import { useSnapshot } from "valtio";
import { useAppSelector } from "@/hook"; import { chatStore } from "@/stores";
import { selectChat } from "@/reducers/chatSlice";
import { valtioContext } from "@/valtioStores";
export const DuelTimeLine = () => { export const DuelTimeLine = () => {
const [items, setItems] = useState<TimelineItemProps[]>([]); const [items, setItems] = useState<TimelineItemProps[]>([]);
const chat = useAppSelector(selectChat);
const stateChat = useContext(valtioContext).chatStore; const stateChat = chatStore;
const snapChat = useSnapshot(stateChat); const snapChat = useSnapshot(stateChat);
// const chat = snapChat.message; const chat = snapChat.message;
useEffect(() => { useEffect(() => {
setItems((prev) => setItems((prev) =>
......
import { Button } from "antd"; import { Button } from "antd";
import React from "react"; import React from "react";
import { useSnapshot } from "valtio";
import { sendSelectEffectYnResponse } from "@/api/ocgcore/ocgHelper"; import { sendSelectEffectYnResponse } from "@/api";
import { useAppSelector } from "@/hook"; import { matStore, messageStore } from "@/stores";
import { selectHint } from "@/reducers/duel/hintSlice";
import { setYesNoModalIsOpen } from "@/reducers/duel/mod";
import {
selectYesNoModalIsOpen,
selectYesNOModalMsg,
} from "@/reducers/duel/modal/mod";
import { store } from "@/store";
import { DragModal } from "./DragModal"; import { DragModal } from "./DragModal";
const { yesNoModal } = messageStore;
export const YesNoModal = () => { export const YesNoModal = () => {
const dispatch = store.dispatch; const snapYesNoModal = useSnapshot(yesNoModal);
const isOpen = useAppSelector(selectYesNoModalIsOpen); const isOpen = snapYesNoModal.isOpen;
const msg = useAppSelector(selectYesNOModalMsg); const msg = snapYesNoModal.msg;
const hint = useAppSelector(selectHint); const hint = useSnapshot(matStore.hint);
const preHintMsg = hint?.esHint || ""; const preHintMsg = hint?.esHint || "";
return ( return (
...@@ -30,7 +27,8 @@ export const YesNoModal = () => { ...@@ -30,7 +27,8 @@ export const YesNoModal = () => {
<Button <Button
onClick={() => { onClick={() => {
sendSelectEffectYnResponse(true); sendSelectEffectYnResponse(true);
dispatch(setYesNoModalIsOpen(false)); // dispatch(setYesNoModalIsOpen(false));
yesNoModal.isOpen = false;
}} }}
> >
Yes Yes
...@@ -38,7 +36,8 @@ export const YesNoModal = () => { ...@@ -38,7 +36,8 @@ export const YesNoModal = () => {
<Button <Button
onClick={() => { onClick={() => {
sendSelectEffectYnResponse(false); sendSelectEffectYnResponse(false);
dispatch(setYesNoModalIsOpen(false)); // dispatch(setYesNoModalIsOpen(false));
yesNoModal.isOpen = false;
}} }}
> >
No No
......
import * as BABYLON from "@babylonjs/core"; import * as BABYLON from "@babylonjs/core";
import { useSnapshot } from "valtio";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import { useAppSelector } from "@/hook"; import { matStore } from "@/stores";
import {
selectMeBanishedZone,
selectOpBanishedZone,
} from "@/reducers/duel/banishedZoneSlice";
import { cardSlotRotation } from "../utils"; import { cardSlotRotation } from "../utils";
import { Depth, SingleSlot } from "./SingleSlot"; import { Depth, SingleSlot } from "./SingleSlot";
const NeosConfig = useConfig(); const NeosConfig = useConfig();
export const BanishedZone = () => { export const BanishedZone = () => {
const meBanishedZone = useAppSelector(selectMeBanishedZone).inner; const meBanishedZone = useSnapshot(matStore.banishedZones.me);
const opBanishedZone = useAppSelector(selectOpBanishedZone).inner; const opBanishedZone = useSnapshot(matStore.banishedZones.op);
return ( return (
<> <>
<SingleSlot <SingleSlot
state={meBanishedZone} // 因为singleSlot里面会有snap,所以这儿可以直接传入store
state={matStore.banishedZones.me}
position={banishedZonePosition(0, meBanishedZone.length)} position={banishedZonePosition(0, meBanishedZone.length)}
rotation={cardSlotRotation(false)} rotation={cardSlotRotation(false)}
/> />
<SingleSlot <SingleSlot
state={opBanishedZone} state={matStore.banishedZones.op}
position={banishedZonePosition(1, opBanishedZone.length)} position={banishedZonePosition(1, opBanishedZone.length)}
rotation={cardSlotRotation(true)} rotation={cardSlotRotation(true)}
/> />
......
import * as BABYLON from "@babylonjs/core"; import * as BABYLON from "@babylonjs/core";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import { useAppSelector } from "@/hook";
import { selectMeDeck, selectOpDeck } from "@/reducers/duel/deckSlice";
import { cardSlotRotation } from "../utils"; import { cardSlotRotation } from "../utils";
import { Depth, SingleSlot } from "./SingleSlot"; import { Depth, SingleSlot } from "./SingleSlot";
const NeosConfig = useConfig(); const NeosConfig = useConfig();
import { useSnapshot } from "valtio";
import { matStore } from "@/stores";
export const CommonDeck = () => { export const CommonDeck = () => {
const meDeck = useAppSelector(selectMeDeck).inner; const meDeck = useSnapshot(matStore.decks.me);
const opDeck = useAppSelector(selectOpDeck).inner; const opDeck = useSnapshot(matStore.decks.op);
return ( return (
<> <>
<SingleSlot <SingleSlot
state={meDeck} state={matStore.decks.me}
position={deckPosition(0, meDeck.length)} position={deckPosition(0, meDeck.length)}
rotation={cardSlotRotation(false)} rotation={cardSlotRotation(false)}
/> />
<SingleSlot <SingleSlot
state={opDeck} state={matStore.decks.op}
position={deckPosition(1, opDeck.length)} position={deckPosition(1, opDeck.length)}
rotation={cardSlotRotation(true)} rotation={cardSlotRotation(true)}
/> />
......
import * as BABYLON from "@babylonjs/core"; import * as BABYLON from "@babylonjs/core";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import { useAppSelector } from "@/hook";
import {
selectMeExtraDeck,
selectOpExtraDeck,
} from "@/reducers/duel/extraDeckSlice";
import { cardSlotRotation } from "../utils"; import { cardSlotRotation } from "../utils";
import { Depth, SingleSlot } from "./SingleSlot"; import { Depth, SingleSlot } from "./SingleSlot";
const NeosConfig = useConfig(); const NeosConfig = useConfig();
import { useSnapshot } from "valtio";
import { matStore } from "@/stores";
export const ExtraDeck = () => { export const ExtraDeck = () => {
const meExtraDeck = useAppSelector(selectMeExtraDeck).inner; const meExtraDeck = useSnapshot(matStore.extraDecks.me);
const opExtraDeck = useAppSelector(selectOpExtraDeck).inner; const opExtraDeck = useSnapshot(matStore.extraDecks.op);
return ( return (
<> <>
<SingleSlot <SingleSlot
state={meExtraDeck} state={matStore.extraDecks.me}
position={extraDeckPosition(0, meExtraDeck.length)} position={extraDeckPosition(0, meExtraDeck.length)}
rotation={cardSlotRotation(false)} rotation={cardSlotRotation(false)}
/> />
<SingleSlot <SingleSlot
state={opExtraDeck} state={matStore.extraDecks.op}
position={extraDeckPosition(1, opExtraDeck.length)} position={extraDeckPosition(1, opExtraDeck.length)}
rotation={cardSlotRotation(true)} rotation={cardSlotRotation(true)}
/> />
......
import * as BABYLON from "@babylonjs/core"; import * as BABYLON from "@babylonjs/core";
import { useSnapshot } from "valtio";
import { ygopro } from "@/api";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import { useAppSelector } from "@/hook"; import { clearAllPlaceInteradtivities, matStore } from "@/stores";
import { selectMeMagics, selectOpMagics } from "@/reducers/duel/magicSlice";
import { clearMagicPlaceInteractivities } from "@/reducers/duel/mod";
import { cardSlotRotation } from "../utils"; import { cardSlotRotation } from "../utils";
import { FixedSlot } from "./FixedSlot"; import { FixedSlot } from "./FixedSlot";
...@@ -11,33 +11,35 @@ import { Depth } from "./SingleSlot"; ...@@ -11,33 +11,35 @@ import { Depth } from "./SingleSlot";
const NeosConfig = useConfig(); const NeosConfig = useConfig();
export const Field = () => { export const Field = () => {
const meField = useAppSelector(selectMeMagics).inner.find( // 这儿的find可能是出于某种考虑,以后再深思
(_, sequence) => sequence == 5 const meFieldState = matStore.magics.me[5];
); const meField = useSnapshot(meFieldState);
const opField = useAppSelector(selectOpMagics).inner.find( const opFieldState = matStore.magics.op[5];
(_, sequence) => sequence == 5 const opField = useSnapshot(opFieldState);
);
const clearPlaceInteractivitiesAction = (controller: number) =>
clearAllPlaceInteradtivities(controller, ygopro.CardZone.MZONE); // 应该是对的
return ( return (
<> <>
{meField ? ( {meField ? (
<FixedSlot <FixedSlot
state={meField} state={meFieldState}
sequence={0} sequence={0}
position={fieldPosition(0)} position={fieldPosition(0)}
rotation={cardSlotRotation(false)} rotation={cardSlotRotation(false)}
clearPlaceInteractivitiesAction={clearMagicPlaceInteractivities} clearPlaceInteractivitiesAction={clearPlaceInteractivitiesAction}
/> />
) : ( ) : (
<></> <></>
)} )}
{opField ? ( {opField ? (
<FixedSlot <FixedSlot
state={opField} state={opFieldState}
sequence={0} sequence={0}
position={fieldPosition(1)} position={fieldPosition(1)}
rotation={cardSlotRotation(true)} rotation={cardSlotRotation(true)}
clearPlaceInteractivitiesAction={clearMagicPlaceInteractivities} clearPlaceInteractivitiesAction={clearPlaceInteractivitiesAction}
/> />
) : ( ) : (
<></> <></>
......
import * as BABYLON from "@babylonjs/core"; import * as BABYLON from "@babylonjs/core";
import { ActionCreatorWithPayload } from "@reduxjs/toolkit";
import { useRef } from "react"; import { useRef } from "react";
import { useSnapshot } from "valtio";
import { ygopro } from "@/api/ocgcore/idl/ocgcore"; import { sendSelectPlaceResponse, ygopro } from "@/api";
import { sendSelectPlaceResponse } from "@/api/ocgcore/ocgHelper";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import { useClick } from "@/hook"; import { useClick } from "@/hook";
import { CardState } from "@/reducers/duel/generic";
import { import {
setCardListModalInfo, type CardState,
setCardListModalIsOpen, clearAllPlaceInteradtivities,
setCardModalCounters, messageStore,
setCardModalInteractivies, } from "@/stores";
setCardModalIsOpen,
setCardModalMeta,
} from "@/reducers/duel/mod";
import { store } from "@/store";
import { interactTypeToString } from "../utils"; import { interactTypeToString } from "../utils";
...@@ -35,68 +29,61 @@ export const FixedSlot = (props: { ...@@ -35,68 +29,61 @@ export const FixedSlot = (props: {
position: BABYLON.Vector3; position: BABYLON.Vector3;
rotation: BABYLON.Vector3; rotation: BABYLON.Vector3;
deffenseRotation?: BABYLON.Vector3; deffenseRotation?: BABYLON.Vector3;
clearPlaceInteractivitiesAction: ActionCreatorWithPayload<number, string>; clearPlaceInteractivitiesAction: (controller: number) => void;
}) => { }) => {
const planeRef = useRef(null); const planeRef = useRef(null);
const snapState = useSnapshot(props.state);
const rotation = const rotation =
props.state.location.position === ygopro.CardPosition.DEFENSE || snapState.location.position === ygopro.CardPosition.DEFENSE ||
props.state.location.position === ygopro.CardPosition.FACEUP_DEFENSE || snapState.location.position === ygopro.CardPosition.FACEUP_DEFENSE ||
props.state.location.position === ygopro.CardPosition.FACEDOWN_DEFENSE snapState.location.position === ygopro.CardPosition.FACEDOWN_DEFENSE
? props.deffenseRotation || cardDefenceRotation ? props.deffenseRotation || cardDefenceRotation
: props.rotation; : props.rotation;
const edgesWidth = 2.0; const edgesWidth = 2.0;
const edgesColor = BABYLON.Color4.FromColor3(BABYLON.Color3.Yellow()); const edgesColor = BABYLON.Color4.FromColor3(BABYLON.Color3.Yellow());
const dispatch = store.dispatch;
const faceDown = const faceDown =
props.state.location.position === ygopro.CardPosition.FACEDOWN_DEFENSE || snapState.location.position === ygopro.CardPosition.FACEDOWN_DEFENSE ||
props.state.location.position === ygopro.CardPosition.FACEDOWN_ATTACK || snapState.location.position === ygopro.CardPosition.FACEDOWN_ATTACK ||
props.state.location.position === ygopro.CardPosition.FACEDOWN; snapState.location.position === ygopro.CardPosition.FACEDOWN;
useClick( useClick(
(_event) => { (_event) => {
if (props.state.placeInteractivities) { if (snapState.placeInteractivity) {
sendSelectPlaceResponse(props.state.placeInteractivities.response); sendSelectPlaceResponse(snapState.placeInteractivity.response);
dispatch(props.clearPlaceInteractivitiesAction(0)); // 其实不应该从外面传进来的...
dispatch(props.clearPlaceInteractivitiesAction(1)); // props.clearPlaceInteractivitiesAction(0);
} else if (props.state.occupant) { // props.clearPlaceInteractivitiesAction(1);
clearAllPlaceInteradtivities(0);
clearAllPlaceInteradtivities(1);
} else if (snapState.occupant) {
// 中央弹窗展示选中卡牌信息 // 中央弹窗展示选中卡牌信息
dispatch(setCardModalMeta(props.state.occupant)); messageStore.cardModal.meta = snapState.occupant;
dispatch( messageStore.cardModal.interactivies =
setCardModalInteractivies( snapState.idleInteractivities.map((interactivity) => ({
props.state.idleInteractivities.map((interactivity) => { desc: interactTypeToString(interactivity.interactType),
return { response: interactivity.response,
desc: interactTypeToString(interactivity.interactType), }));
response: interactivity.response, messageStore.cardModal.counters = snapState.counters;
}; messageStore.cardModal.isOpen = true;
})
)
);
dispatch(setCardModalCounters(props.state.counters));
dispatch(setCardModalIsOpen(true));
// 侧边栏展示超量素材信息 // 侧边栏展示超量素材信息
if ( if (
props.state.overlay_materials && snapState.overlay_materials &&
props.state.overlay_materials.length > 0 snapState.overlay_materials.length > 0
) { ) {
dispatch( messageStore.cardListModal.list =
setCardListModalInfo( snapState.overlay_materials?.map((overlay) => ({
props.state.overlay_materials?.map((overlay) => { meta: overlay,
return { interactivies: [],
meta: overlay, })) || [];
interactivies: [], messageStore.cardListModal.isOpen = true;
};
}) || []
)
);
dispatch(setCardListModalIsOpen(true));
} }
} }
}, },
planeRef, planeRef,
[props.state] [snapState]
); );
return ( return (
...@@ -109,8 +96,7 @@ export const FixedSlot = (props: { ...@@ -109,8 +96,7 @@ export const FixedSlot = (props: {
rotation={rotation} rotation={rotation}
enableEdgesRendering enableEdgesRendering
edgesWidth={ edgesWidth={
props.state.placeInteractivities || snapState.placeInteractivity || snapState.idleInteractivities.length > 0
props.state.idleInteractivities.length > 0
? edgesWidth ? edgesWidth
: 0 : 0
} }
...@@ -119,15 +105,15 @@ export const FixedSlot = (props: { ...@@ -119,15 +105,15 @@ export const FixedSlot = (props: {
<standardMaterial <standardMaterial
name={`fixedslot-mat-${props.sequence}`} name={`fixedslot-mat-${props.sequence}`}
diffuseTexture={ diffuseTexture={
props.state.occupant snapState.occupant
? faceDown ? faceDown
? new BABYLON.Texture(`${NeosConfig.assetsPath}/card_back.jpg`) ? new BABYLON.Texture(`${NeosConfig.assetsPath}/card_back.jpg`)
: new BABYLON.Texture( : new BABYLON.Texture(
`${NeosConfig.cardImgUrl}/${props.state.occupant.id}.jpg` `${NeosConfig.cardImgUrl}/${snapState.occupant.id}.jpg`
) )
: new BABYLON.Texture(`${NeosConfig.assetsPath}/card_slot.png`) : new BABYLON.Texture(`${NeosConfig.assetsPath}/card_slot.png`)
} }
alpha={props.state.occupant ? 1 : 0} alpha={snapState.occupant ? 1 : 0}
></standardMaterial> ></standardMaterial>
</plane> </plane>
); );
......
import * as BABYLON from "@babylonjs/core"; import * as BABYLON from "@babylonjs/core";
import { useSnapshot } from "valtio";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import { useAppSelector } from "@/hook"; import { matStore } from "@/stores";
import {
selectMeGraveyard,
selectOpGraveyard,
} from "@/reducers/duel/graveyardSlice";
import { cardSlotRotation } from "../utils"; import { cardSlotRotation } from "../utils";
import { Depth, SingleSlot } from "./SingleSlot"; import { Depth, SingleSlot } from "./SingleSlot";
const NeosConfig = useConfig(); const NeosConfig = useConfig();
export const Graveyard = () => { export const Graveyard = () => {
const meGraveyard = useAppSelector(selectMeGraveyard).inner; const meGraveyard = useSnapshot(matStore.graveyards.me);
const opGraveyard = useAppSelector(selectOpGraveyard).inner; const opGraveyard = useSnapshot(matStore.graveyards.op);
return ( return (
<> <>
<SingleSlot <SingleSlot
state={meGraveyard} state={matStore.graveyards.me}
position={graveyardPosition(0, meGraveyard.length)} position={graveyardPosition(0, meGraveyard.length)}
rotation={cardSlotRotation(false)} rotation={cardSlotRotation(false)}
/> />
<SingleSlot <SingleSlot
state={opGraveyard} state={matStore.graveyards.op}
position={graveyardPosition(1, opGraveyard.length)} position={graveyardPosition(1, opGraveyard.length)}
rotation={cardSlotRotation(true)} rotation={cardSlotRotation(true)}
/> />
......
import * as BABYLON from "@babylonjs/core"; import * as BABYLON from "@babylonjs/core";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { useHover } from "react-babylonjs"; import { useHover } from "react-babylonjs";
import { INTERNAL_Snapshot, useSnapshot } from "valtio";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import { useAppSelector, useClick } from "@/hook"; import { useClick } from "@/hook";
import { CardState } from "@/reducers/duel/generic"; import { type CardState, matStore, messageStore } from "@/stores";
import { selectMeHands, selectOpHands } from "@/reducers/duel/handsSlice";
import {
setCardModalInteractivies,
setCardModalIsOpen,
setCardModalMeta,
} from "@/reducers/duel/mod";
import { store } from "@/store";
import { animated, useSpring } from "../spring"; import { animated, useSpring } from "../spring";
import { interactTypeToString, zip } from "../utils"; import { interactTypeToString, zip } from "../utils";
...@@ -26,14 +20,16 @@ const handRotation = new BABYLON.Vector3(rotation.x, rotation.y, rotation.z); ...@@ -26,14 +20,16 @@ const handRotation = new BABYLON.Vector3(rotation.x, rotation.y, rotation.z);
const hoverScaling = NeosConfig.ui.card.handHoverScaling; const hoverScaling = NeosConfig.ui.card.handHoverScaling;
export const Hands = () => { export const Hands = () => {
const meHands = useAppSelector(selectMeHands).inner; const meHandsState = matStore.hands.me;
const meHandPositions = handPositons(0, meHands); const opHandsState = matStore.hands.op;
const opHands = useAppSelector(selectOpHands).inner; const meHandsSnap = useSnapshot(meHandsState);
const opHandPositions = handPositons(1, opHands); const opHandsSnap = useSnapshot(opHandsState);
const meHandPositions = handPositons(0, meHandsSnap);
const opHandPositions = handPositons(1, opHandsSnap);
return ( return (
<> <>
{zip(meHands, meHandPositions).map(([hand, position], idx) => { {zip(meHandsState, meHandPositions).map(([hand, position], idx) => {
return ( return (
<CHand <CHand
key={idx} key={idx}
...@@ -41,11 +37,10 @@ export const Hands = () => { ...@@ -41,11 +37,10 @@ export const Hands = () => {
sequence={idx} sequence={idx}
position={position} position={position}
rotation={handRotation} rotation={handRotation}
cover={(id) => `${NeosConfig.cardImgUrl}/${id}.jpg`}
/> />
); );
})} })}
{zip(opHands, opHandPositions).map(([hand, position], idx) => { {zip(opHandsState, opHandPositions).map(([hand, position], idx) => {
return ( return (
<CHand <CHand
key={idx} key={idx}
...@@ -53,7 +48,7 @@ export const Hands = () => { ...@@ -53,7 +48,7 @@ export const Hands = () => {
sequence={idx} sequence={idx}
position={position} position={position}
rotation={handRotation} rotation={handRotation}
cover={(_) => `${NeosConfig.assetsPath}/card_back.jpg`} back={true}
/> />
); );
})} })}
...@@ -66,7 +61,7 @@ const CHand = (props: { ...@@ -66,7 +61,7 @@ const CHand = (props: {
sequence: number; sequence: number;
position: BABYLON.Vector3; position: BABYLON.Vector3;
rotation: BABYLON.Vector3; rotation: BABYLON.Vector3;
cover: (id: number) => string; back?: boolean;
}) => { }) => {
const hoverScale = new BABYLON.Vector3( const hoverScale = new BABYLON.Vector3(
hoverScaling.x, hoverScaling.x,
...@@ -80,7 +75,6 @@ const CHand = (props: { ...@@ -80,7 +75,6 @@ const CHand = (props: {
const state = props.state; const state = props.state;
const [hovered, setHovered] = useState(false); const [hovered, setHovered] = useState(false);
const position = props.position; const position = props.position;
const dispatch = store.dispatch;
const [spring, api] = useSpring( const [spring, api] = useSpring(
() => ({ () => ({
...@@ -115,19 +109,15 @@ const CHand = (props: { ...@@ -115,19 +109,15 @@ const CHand = (props: {
useClick( useClick(
() => { () => {
if (state.occupant) { if (state.occupant) {
dispatch(setCardModalMeta(state.occupant)); messageStore.cardModal.meta = state.occupant;
} }
dispatch( messageStore.cardModal.interactivies = state.idleInteractivities.map(
setCardModalInteractivies( (interactive) => ({
state.idleInteractivities.map((interactive) => { desc: interactTypeToString(interactive.interactType),
return { response: interactive.response,
desc: interactTypeToString(interactive.interactType), })
response: interactive.response,
};
})
)
); );
dispatch(setCardModalIsOpen(true)); messageStore.cardModal.isOpen = true;
}, },
planeRef, planeRef,
[state] [state]
...@@ -145,7 +135,7 @@ const CHand = (props: { ...@@ -145,7 +135,7 @@ const CHand = (props: {
rotation={props.rotation} rotation={props.rotation}
enableEdgesRendering enableEdgesRendering
edgesWidth={ edgesWidth={
state.idleInteractivities.length > 0 || state.placeInteractivities state.idleInteractivities.length > 0 || state.placeInteractivity
? edgesWidth ? edgesWidth
: 0 : 0
} }
...@@ -154,7 +144,11 @@ const CHand = (props: { ...@@ -154,7 +144,11 @@ const CHand = (props: {
<animated.standardMaterial <animated.standardMaterial
name={`hand-mat-${props.sequence}`} name={`hand-mat-${props.sequence}`}
diffuseTexture={ diffuseTexture={
new BABYLON.Texture(props.cover(state.occupant?.id || 0)) new BABYLON.Texture(
props.back
? `${NeosConfig.assetsPath}/card_back.jpg`
: `${NeosConfig.cardImgUrl}/${state.occupant?.id || 0}.jpg`
)
} }
/> />
</animated.plane> </animated.plane>
...@@ -162,7 +156,10 @@ const CHand = (props: { ...@@ -162,7 +156,10 @@ const CHand = (props: {
); );
}; };
const handPositons = (player: number, hands: CardState[]) => { const handPositons = (
player: number,
hands: INTERNAL_Snapshot<CardState[]>
) => {
const gap = groundShape.width / (hands.length - 1); const gap = groundShape.width / (hands.length - 1);
const x = (idx: number) => const x = (idx: number) =>
player == 0 ? left + gap * idx : -left - gap * idx; player == 0 ? left + gap * idx : -left - gap * idx;
......
import * as BABYLON from "@babylonjs/core"; import * as BABYLON from "@babylonjs/core";
import { type INTERNAL_Snapshot, useSnapshot } from "valtio";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import { useAppSelector } from "@/hook"; import { type CardState, matStore } from "@/stores";
import { CardState } from "@/reducers/duel/generic";
import { selectMeMagics, selectOpMagics } from "@/reducers/duel/magicSlice";
import { clearMagicPlaceInteractivities } from "@/reducers/duel/mod";
import { cardSlotRotation, zip } from "../utils"; import { cardSlotRotation, zip } from "../utils";
import { FixedSlot } from "./FixedSlot"; import { FixedSlot } from "./FixedSlot";
...@@ -16,14 +14,21 @@ const gap = 1.05; ...@@ -16,14 +14,21 @@ const gap = 1.05;
const transform = NeosConfig.ui.card.transform; const transform = NeosConfig.ui.card.transform;
export const Magics = () => { export const Magics = () => {
const meMagics = useAppSelector(selectMeMagics).inner; const meMagicState = matStore.magics.me;
const meMagicPositions = magicPositions(0, meMagics); const opMagicState = matStore.magics.op;
const opMagics = useAppSelector(selectOpMagics).inner; const meMagicsSnap = useSnapshot(meMagicState);
const opMagicPositions = magicPositions(1, opMagics); const opMagicsSnap = useSnapshot(opMagicState);
const meMagicPositions = magicPositions(0, meMagicsSnap);
const opMagicPositions = magicPositions(1, opMagicsSnap);
const clearPlaceInteractivitiesAction = (controller: number) => {
console.warn("magic clearPlaceInteractivitiesAction");
matStore.magics.of(controller).clearPlaceInteractivity();
};
return ( return (
<> <>
{zip(meMagics, meMagicPositions) {zip(meMagicState, meMagicPositions)
.slice(0, 5) .slice(0, 5)
.map(([magic, position], sequence) => { .map(([magic, position], sequence) => {
return ( return (
...@@ -33,11 +38,11 @@ export const Magics = () => { ...@@ -33,11 +38,11 @@ export const Magics = () => {
sequence={sequence} sequence={sequence}
position={position} position={position}
rotation={cardSlotRotation(false)} rotation={cardSlotRotation(false)}
clearPlaceInteractivitiesAction={clearMagicPlaceInteractivities} clearPlaceInteractivitiesAction={clearPlaceInteractivitiesAction}
/> />
); );
})} })}
{zip(opMagics, opMagicPositions) {zip(opMagicState, opMagicPositions)
.slice(0, 5) .slice(0, 5)
.map(([magic, position], sequence) => { .map(([magic, position], sequence) => {
return ( return (
...@@ -47,7 +52,7 @@ export const Magics = () => { ...@@ -47,7 +52,7 @@ export const Magics = () => {
sequence={sequence} sequence={sequence}
position={position} position={position}
rotation={cardSlotRotation(true)} rotation={cardSlotRotation(true)}
clearPlaceInteractivitiesAction={clearMagicPlaceInteractivities} clearPlaceInteractivitiesAction={clearPlaceInteractivitiesAction}
/> />
); );
})} })}
...@@ -55,7 +60,10 @@ export const Magics = () => { ...@@ -55,7 +60,10 @@ export const Magics = () => {
); );
}; };
const magicPositions = (player: number, magics: CardState[]) => { const magicPositions = (
player: number,
magics: INTERNAL_Snapshot<CardState[]>
) => {
const x = (sequence: number) => const x = (sequence: number) =>
player == 0 ? left + gap * sequence : -left - gap * sequence; player == 0 ? left + gap * sequence : -left - gap * sequence;
const y = transform.z / 2 + NeosConfig.ui.card.floating; const y = transform.z / 2 + NeosConfig.ui.card.floating;
......
import "react-babylonjs"; import "react-babylonjs";
import * as BABYLON from "@babylonjs/core"; import * as BABYLON from "@babylonjs/core";
import { type INTERNAL_Snapshot, useSnapshot } from "valtio";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import { useAppSelector } from "@/hook"; import { type CardState, matStore } from "@/stores";
import { CardState } from "@/reducers/duel/generic";
import { clearMonsterPlaceInteractivities } from "@/reducers/duel/mod";
import {
selectMeMonsters,
selectOpMonsters,
} from "@/reducers/duel/monstersSlice";
import { cardSlotDefenceRotation, cardSlotRotation, zip } from "../utils"; import { cardSlotDefenceRotation, cardSlotRotation, zip } from "../utils";
import { FixedSlot } from "./FixedSlot"; import { FixedSlot } from "./FixedSlot";
...@@ -20,15 +15,22 @@ const floating = NeosConfig.ui.card.floating; ...@@ -20,15 +15,22 @@ const floating = NeosConfig.ui.card.floating;
const left = -2.15; // TODO: config const left = -2.15; // TODO: config
const gap = 1.05; const gap = 1.05;
const clearPlaceInteractivitiesAction = (controller: number) => {
console.warn("monster clearPlaceInteractivitiesAction");
matStore.monsters.of(controller).clearPlaceInteractivity();
};
export const Monsters = () => { export const Monsters = () => {
const meMonsters = useAppSelector(selectMeMonsters).inner; const meMonstersStore = matStore.monsters.me;
const meMonsterPositions = monsterPositions(0, meMonsters); const opMonstersStore = matStore.monsters.op;
const opMonsters = useAppSelector(selectOpMonsters).inner; const meMonstersSnap = useSnapshot(meMonstersStore);
const opMonsterPositions = monsterPositions(1, opMonsters); const opMonstersSnap = useSnapshot(opMonstersStore);
const meMonsterPositions = monsterPositions(0, meMonstersSnap);
const opMonsterPositions = monsterPositions(1, opMonstersSnap);
return ( return (
<> <>
{zip(meMonsters, meMonsterPositions) {zip(meMonstersStore, meMonsterPositions)
.slice(0, 5) .slice(0, 5)
.map(([monster, position], sequence) => ( .map(([monster, position], sequence) => (
<FixedSlot <FixedSlot
...@@ -38,10 +40,10 @@ export const Monsters = () => { ...@@ -38,10 +40,10 @@ export const Monsters = () => {
position={position} position={position}
rotation={cardSlotRotation(false)} rotation={cardSlotRotation(false)}
deffenseRotation={cardSlotDefenceRotation()} deffenseRotation={cardSlotDefenceRotation()}
clearPlaceInteractivitiesAction={clearMonsterPlaceInteractivities} clearPlaceInteractivitiesAction={clearPlaceInteractivitiesAction}
/> />
))} ))}
{zip(opMonsters, opMonsterPositions) {zip(opMonstersStore, opMonsterPositions)
.slice(0, 5) .slice(0, 5)
.map(([monster, position], sequence) => ( .map(([monster, position], sequence) => (
<FixedSlot <FixedSlot
...@@ -51,10 +53,13 @@ export const Monsters = () => { ...@@ -51,10 +53,13 @@ export const Monsters = () => {
position={position} position={position}
rotation={cardSlotRotation(true)} rotation={cardSlotRotation(true)}
deffenseRotation={cardSlotDefenceRotation()} deffenseRotation={cardSlotDefenceRotation()}
clearPlaceInteractivitiesAction={clearMonsterPlaceInteractivities} clearPlaceInteractivitiesAction={clearPlaceInteractivitiesAction}
/> />
))} ))}
<ExtraMonsters meMonsters={meMonsters} opMonsters={opMonsters} /> <ExtraMonsters
meMonsters={meMonstersStore}
opMonsters={opMonstersStore}
/>
</> </>
); );
}; };
...@@ -64,10 +69,10 @@ const ExtraMonsters = (props: { ...@@ -64,10 +69,10 @@ const ExtraMonsters = (props: {
meMonsters: CardState[]; meMonsters: CardState[];
opMonsters: CardState[]; opMonsters: CardState[];
}) => { }) => {
const meLeft = props.meMonsters.find((_, sequence) => sequence == 5); const meLeft = props.meMonsters[5];
const meRight = props.meMonsters.find((_, sequence) => sequence == 6); const meRight = props.meMonsters[6];
const opLeft = props.opMonsters.find((_, sequence) => sequence == 5); const opLeft = props.opMonsters[5];
const opRight = props.opMonsters.find((_, sequence) => sequence == 6); const opRight = props.opMonsters[6];
const leftPosition = new BABYLON.Vector3(-1.1, transform.z / 2 + floating, 0); const leftPosition = new BABYLON.Vector3(-1.1, transform.z / 2 + floating, 0);
const rightPosition = new BABYLON.Vector3(1.1, transform.z / 2 + floating, 0); const rightPosition = new BABYLON.Vector3(1.1, transform.z / 2 + floating, 0);
...@@ -77,59 +82,46 @@ const ExtraMonsters = (props: { ...@@ -77,59 +82,46 @@ const ExtraMonsters = (props: {
return ( return (
<> <>
{meLeft ? ( <FixedSlot
<FixedSlot state={meLeft}
state={meLeft} sequence={5}
sequence={5} position={leftPosition}
position={leftPosition} rotation={meRotation}
rotation={meRotation} deffenseRotation={cardSlotDefenceRotation()}
deffenseRotation={cardSlotDefenceRotation()} clearPlaceInteractivitiesAction={clearPlaceInteractivitiesAction}
clearPlaceInteractivitiesAction={clearMonsterPlaceInteractivities} />
/> <FixedSlot
) : ( state={meRight}
<></> sequence={6}
)} position={rightPosition}
{meRight ? ( rotation={meRotation}
<FixedSlot deffenseRotation={cardSlotDefenceRotation()}
state={meRight} clearPlaceInteractivitiesAction={clearPlaceInteractivitiesAction}
sequence={6} />
position={rightPosition} <FixedSlot
rotation={meRotation} state={opLeft}
deffenseRotation={cardSlotDefenceRotation()} sequence={5}
clearPlaceInteractivitiesAction={clearMonsterPlaceInteractivities} position={rightPosition}
/> rotation={opRotation}
) : ( deffenseRotation={cardSlotDefenceRotation()}
<></> clearPlaceInteractivitiesAction={clearPlaceInteractivitiesAction}
)} />
{opLeft ? ( <FixedSlot
<FixedSlot state={opRight}
state={opLeft} sequence={6}
sequence={5} position={leftPosition}
position={rightPosition} rotation={opRotation}
rotation={opRotation} deffenseRotation={cardSlotDefenceRotation()}
deffenseRotation={cardSlotDefenceRotation()} clearPlaceInteractivitiesAction={clearPlaceInteractivitiesAction}
clearPlaceInteractivitiesAction={clearMonsterPlaceInteractivities} />
/>
) : (
<></>
)}
{opRight ? (
<FixedSlot
state={opRight}
sequence={6}
position={leftPosition}
rotation={opRotation}
deffenseRotation={cardSlotDefenceRotation()}
clearPlaceInteractivitiesAction={clearMonsterPlaceInteractivities}
/>
) : (
<></>
)}
</> </>
); );
}; };
const monsterPositions = (player: number, monsters: CardState[]) => { const monsterPositions = (
player: number,
monsters: INTERNAL_Snapshot<CardState[]>
) => {
const x = (sequence: number) => const x = (sequence: number) =>
player == 0 ? left + gap * sequence : -left - gap * sequence; player == 0 ? left + gap * sequence : -left - gap * sequence;
const y = transform.z / 2 + floating; const y = transform.z / 2 + floating;
......
import * as BABYLON from "@babylonjs/core"; import * as BABYLON from "@babylonjs/core";
import { useRef } from "react"; import { useRef } from "react";
import { useSnapshot } from "valtio";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import { useClick } from "@/hook"; import { useClick } from "@/hook";
import { CardState } from "@/reducers/duel/generic"; import { type CardState, messageStore } from "@/stores";
import {
setCardListModalInfo,
setCardListModalIsOpen,
} from "@/reducers/duel/mod";
import { store } from "@/store";
import { interactTypeToString } from "../utils"; import { interactTypeToString } from "../utils";
...@@ -21,10 +17,10 @@ export const SingleSlot = (props: { ...@@ -21,10 +17,10 @@ export const SingleSlot = (props: {
position: BABYLON.Vector3; position: BABYLON.Vector3;
rotation: BABYLON.Vector3; rotation: BABYLON.Vector3;
}) => { }) => {
const snapState = useSnapshot(props.state);
const boxRef = useRef(null); const boxRef = useRef(null);
const dispatch = store.dispatch;
const edgeRender = const edgeRender =
props.state.find((item) => snapState.find((item) =>
item === undefined ? false : item.idleInteractivities.length > 0 item === undefined ? false : item.idleInteractivities.length > 0
) !== undefined; ) !== undefined;
const edgesWidth = 2.0; const edgesWidth = 2.0;
...@@ -32,31 +28,24 @@ export const SingleSlot = (props: { ...@@ -32,31 +28,24 @@ export const SingleSlot = (props: {
useClick( useClick(
(_event) => { (_event) => {
if (props.state.length != 0) { if (snapState.length != 0) {
dispatch( messageStore.cardListModal.list = snapState
setCardListModalInfo( .filter(
props.state (item) => item.occupant !== undefined && item.occupant.id !== 0
.filter(
(item) => item.occupant !== undefined && item.occupant.id !== 0
)
.map((item) => {
return {
meta: item.occupant,
interactivies: item.idleInteractivities.map((interactivy) => {
return {
desc: interactTypeToString(interactivy.interactType),
response: interactivy.response,
};
}),
};
})
) )
); .map((item) => ({
dispatch(setCardListModalIsOpen(true)); meta: item.occupant,
interactivies: item.idleInteractivities.map((interactivy) => ({
desc: interactTypeToString(interactivy.interactType),
response: interactivy.response,
})),
}));
// dispatch(setCardListModalIsOpen(true));
messageStore.cardListModal.isOpen = true;
} }
}, },
boxRef, boxRef,
[props.state] [snapState]
); );
return ( return (
...@@ -64,11 +53,7 @@ export const SingleSlot = (props: { ...@@ -64,11 +53,7 @@ export const SingleSlot = (props: {
name="single-slot" name="single-slot"
ref={boxRef} ref={boxRef}
scaling={ scaling={
new BABYLON.Vector3( new BABYLON.Vector3(transform.x, transform.y, Depth * snapState.length)
transform.x,
transform.y,
Depth * props.state.length
)
} }
position={props.position} position={props.position}
rotation={props.rotation} rotation={props.rotation}
...@@ -81,7 +66,7 @@ export const SingleSlot = (props: { ...@@ -81,7 +66,7 @@ export const SingleSlot = (props: {
diffuseTexture={ diffuseTexture={
new BABYLON.Texture(`${NeosConfig.assetsPath}/card_back.jpg`) new BABYLON.Texture(`${NeosConfig.assetsPath}/card_back.jpg`)
} }
alpha={props.state.length == 0 ? 0 : 1} alpha={snapState.length == 0 ? 0 : 1}
/> />
</box> </box>
); );
......
import { InteractType } from "@/reducers/duel/generic"; import { InteractType } from "@/stores";
export function interactTypeToString(t: InteractType): string { export function interactTypeToString(t: InteractType): string {
switch (t) { switch (t) {
......
...@@ -4,22 +4,13 @@ import { ...@@ -4,22 +4,13 @@ import {
TableOutlined, TableOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { Button, Modal } from "antd"; import { Button, Modal } from "antd";
import React, { useContext, useEffect } from "react"; import React, { useEffect } from "react";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import { useSnapshot } from "valtio"; import { useSnapshot } from "valtio";
import { sendHandResult, sendTpResult } from "@/api/ocgcore/ocgHelper"; import { sendHandResult, sendTpResult } from "@/api";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import { useAppSelector } from "@/hook"; import { matStore, moraStore } from "@/stores";
import { selectDuelHsStart } from "@/reducers/duel/mod";
import {
selectHandSelectAble,
selectTpSelectAble,
unSelectHandAble,
unSelectTpAble,
} from "@/reducers/moraSlice";
import { store } from "@/store";
import { valtioContext } from "@/valtioStores";
const { const {
automation: { isAiMode, isAiFirst }, automation: { isAiMode, isAiFirst },
...@@ -27,17 +18,11 @@ const { ...@@ -27,17 +18,11 @@ const {
} = useConfig(); } = useConfig();
const Mora = () => { const Mora = () => {
const stateMora = useContext(valtioContext).moraStore; const snapMora = useSnapshot(moraStore);
const snapMora = useSnapshot(stateMora); const snapMatInitInfo = useSnapshot(matStore.initInfo);
const dispatch = store.dispatch;
const selectHandAble = useAppSelector(selectHandSelectAble);
const selectTpAble = useAppSelector(selectTpSelectAble);
const duelHsStart = useAppSelector(selectDuelHsStart);
// const selectHandAble = snapMora.selectHandAble; const selectHandAble = snapMora.selectHandAble;
// const selectTpAble = snapMora.selectTpAble; const selectTpAble = snapMora.selectTpAble;
// const duelHsStart = snapMora.duelStart;
const navigate = useNavigate(); const navigate = useNavigate();
const { player, passWd, ip } = useParams<{ const { player, passWd, ip } = useParams<{
...@@ -48,21 +33,19 @@ const Mora = () => { ...@@ -48,21 +33,19 @@ const Mora = () => {
const handleSelectMora = (selected: string) => { const handleSelectMora = (selected: string) => {
sendHandResult(selected); sendHandResult(selected);
dispatch(unSelectHandAble()); moraStore.selectHandAble = false;
stateMora.selectHandAble = false;
}; };
const handleSelectTp = (isFirst: boolean) => { const handleSelectTp = (isFirst: boolean) => {
sendTpResult(isFirst); sendTpResult(isFirst);
dispatch(unSelectTpAble()); moraStore.selectTpAble = false;
stateMora.selectTpAble = false;
}; };
useEffect(() => { useEffect(() => {
// 若对局已经开始,自动跳转 // 若对局已经开始,自动跳转
if (duelHsStart) { if (snapMatInitInfo.me.life > 0) {
navigate(`/duel/${player}/${passWd}/${ip}`); navigate(`/duel/${player}/${passWd}/${ip}`);
} }
}, [duelHsStart]); }, [snapMatInitInfo.me]);
useEffect(() => { useEffect(() => {
if (isAiMode) { if (isAiMode) {
......
...@@ -19,34 +19,18 @@ import { ...@@ -19,34 +19,18 @@ import {
Space, Space,
Upload, Upload,
} from "antd"; } from "antd";
import React, { useContext, useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import rustInit from "rust-src"; import rustInit from "rust-src";
import { useSnapshot } from "valtio"; import { useSnapshot } from "valtio";
import YGOProDeck from "ygopro-deck-encode"; import YGOProDeck from "ygopro-deck-encode";
import { initStrings, sendHsReady, sendHsStart, sendUpdateDeck } from "@/api";
import { DeckManager, fetchDeck, type IDeck } from "@/api/deck"; import { DeckManager, fetchDeck, type IDeck } from "@/api/deck";
import {
sendHsReady,
sendHsStart,
sendUpdateDeck,
} from "@/api/ocgcore/ocgHelper";
import { initStrings } from "@/api/strings";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import { useAppSelector } from "@/hook";
import socketMiddleWare, { socketCmd } from "@/middleware/socket"; import socketMiddleWare, { socketCmd } from "@/middleware/socket";
import sqliteMiddleWare, { sqliteCmd } from "@/middleware/sqlite"; import sqliteMiddleWare, { sqliteCmd } from "@/middleware/sqlite";
import { selectChat } from "@/reducers/chatSlice"; import { store } from "@/stores";
import { initMeExtraDeckMeta } from "@/reducers/duel/extraDeckSlice";
import { selectJoined } from "@/reducers/joinSlice";
import { selectDuelStart } from "@/reducers/moraSlice";
import {
selectIsHost,
selectPlayer0,
selectPlayer1,
} from "@/reducers/playerSlice";
import { store } from "@/store";
import { valtioContext } from "@/valtioStores";
const NeosConfig = useConfig(); const NeosConfig = useConfig();
...@@ -58,7 +42,7 @@ const { ...@@ -58,7 +42,7 @@ const {
} = useConfig(); } = useConfig();
const WaitRoom = () => { const WaitRoom = () => {
const state = useContext(valtioContext); const state = store;
const snap = useSnapshot(state); const snap = useSnapshot(state);
const params = useParams<{ const params = useParams<{
player?: string; player?: string;
...@@ -103,21 +87,14 @@ const WaitRoom = () => { ...@@ -103,21 +87,14 @@ const WaitRoom = () => {
} }
}, []); }, []);
const dispatch = store.dispatch;
const joined = useAppSelector(selectJoined);
const chat = useAppSelector(selectChat);
const isHost = useAppSelector(selectIsHost);
const player0 = useAppSelector(selectPlayer0);
const player1 = useAppSelector(selectPlayer1);
const duelStart = useAppSelector(selectDuelStart);
const [api, contextHolder] = notification.useNotification(); const [api, contextHolder] = notification.useNotification();
// const joined = snap.joinStore.value; const joined = snap.joinStore.value;
// const chat = snap.chatStore.message; const chat = snap.chatStore.message;
// const isHost = snap.playerStore.isHost; const isHost = snap.playerStore.isHost;
// const player0 = snap.playerStore.player0; const player0 = snap.playerStore.player0;
// const player1 = snap.playerStore.player1; const player1 = snap.playerStore.player1;
// const duelStart = snap.moraStore.duelStart; const duelStart = snap.moraStore.duelStart;
// FIXME: 这些数据应该从`store`中获取 // FIXME: 这些数据应该从`store`中获取
// TODO: 云卡组 // TODO: 云卡组
...@@ -160,9 +137,7 @@ const WaitRoom = () => { ...@@ -160,9 +137,7 @@ const WaitRoom = () => {
const onDeckReady = async (deck: IDeck) => { const onDeckReady = async (deck: IDeck) => {
sendUpdateDeck(deck); sendUpdateDeck(deck);
await dispatch( store.matStore.extraDecks.me.add(deck.extra?.reverse() || []);
initMeExtraDeckMeta({ controler: 0, codes: deck.extra?.reverse() || [] })
);
setChoseDeck(true); setChoseDeck(true);
}; };
......
import { proxy } from "valtio";
import { ygopro } from "@/api/ocgcore/idl/ocgcore";
import type { DuelState } from "./types";
export const playMat = proxy<DuelState>({
opMagics: {
inner: Array(5)
.fill(null)
.map((_, i) => ({
location: {
location: ygopro.CardZone.SZONE,
},
idleInteractivities: [],
counters: {},
})),
},
});
import { proxy } from "valtio";
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment