mirror of
https://github.com/demostf/demo.js
synced 2026-06-03 16:44:12 +02:00
code style and add tslint
This commit is contained in:
parent
da007aca8c
commit
52a36ed8c8
61 changed files with 1708 additions and 872 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -2,3 +2,5 @@ node_modules
|
|||
build
|
||||
*.dem
|
||||
*.txt
|
||||
*.lock
|
||||
log
|
||||
|
|
|
|||
|
|
@ -9,3 +9,4 @@ install:
|
|||
- make node_modules
|
||||
script:
|
||||
- make test
|
||||
- make lint
|
||||
|
|
|
|||
4
Makefile
4
Makefile
|
|
@ -18,3 +18,7 @@ build: node_modules
|
|||
.PHONY: test
|
||||
test: node_modules
|
||||
node node_modules/.bin/mocha --opts mocha.opts
|
||||
|
||||
.PHONY: lint
|
||||
lint: node_modules
|
||||
node_modules/.bin/tslint -p tsconfig.json
|
||||
|
|
|
|||
801
package-lock.json
generated
Normal file
801
package-lock.json
generated
Normal file
|
|
@ -0,0 +1,801 @@
|
|||
{
|
||||
"name": "tf2-demo",
|
||||
"version": "1.1.3",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "https://registry.npmjs.org/@types/node/-/node-6.0.68.tgz",
|
||||
"integrity": "sha1-DEO2uLlEX+uGoPvTRX4/S8WR5m0=",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz",
|
||||
"integrity": "sha1-xQYbbg74qBd15Q9dZhUb9r83EQc=",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
|
||||
"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
|
||||
"dev": true
|
||||
},
|
||||
"arrify": {
|
||||
"version": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
|
||||
"integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=",
|
||||
"dev": true
|
||||
},
|
||||
"babel-code-frame": {
|
||||
"version": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.20.0.tgz",
|
||||
"integrity": "sha1-uWj4OQkPmovG1Bk4+5bLhPc4eyY=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
||||
"esutils": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
|
||||
"js-tokens": "https://registry.npmjs.org/js-tokens/-/js-tokens-2.0.0.tgz"
|
||||
}
|
||||
},
|
||||
"babel-helper-call-delegate": {
|
||||
"version": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.18.0.tgz",
|
||||
"integrity": "sha1-BbFKr6QwiEsDQJfvKenwZ+pBM70=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"babel-helper-hoist-variables": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.18.0.tgz",
|
||||
"babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.20.0.tgz",
|
||||
"babel-traverse": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.21.0.tgz",
|
||||
"babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.21.0.tgz"
|
||||
}
|
||||
},
|
||||
"babel-helper-function-name": {
|
||||
"version": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.18.0.tgz",
|
||||
"integrity": "sha1-aOxxrrofPiiypvBzAZC3VKm/MOY=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"babel-helper-get-function-arity": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.18.0.tgz",
|
||||
"babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.20.0.tgz",
|
||||
"babel-template": "https://registry.npmjs.org/babel-template/-/babel-template-6.16.0.tgz",
|
||||
"babel-traverse": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.21.0.tgz",
|
||||
"babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.21.0.tgz"
|
||||
}
|
||||
},
|
||||
"babel-helper-get-function-arity": {
|
||||
"version": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.18.0.tgz",
|
||||
"integrity": "sha1-pbGWlf0/nN/DKDmLR9r81wlPnyQ=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.20.0.tgz",
|
||||
"babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.21.0.tgz"
|
||||
}
|
||||
},
|
||||
"babel-helper-hoist-variables": {
|
||||
"version": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.18.0.tgz",
|
||||
"integrity": "sha1-qDW1q4tG1t6bq++uTZjqQehmuCo=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.20.0.tgz",
|
||||
"babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.21.0.tgz"
|
||||
}
|
||||
},
|
||||
"babel-messages": {
|
||||
"version": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.8.0.tgz",
|
||||
"integrity": "sha1-v1BHNsqWfm1l7wrbWipflHyODrk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.20.0.tgz"
|
||||
}
|
||||
},
|
||||
"babel-plugin-syntax-do-expressions": {
|
||||
"version": "https://registry.npmjs.org/babel-plugin-syntax-do-expressions/-/babel-plugin-syntax-do-expressions-6.13.0.tgz",
|
||||
"integrity": "sha1-V0d1YTmqJtOQ0JQQsDdEugfkeW0=",
|
||||
"dev": true
|
||||
},
|
||||
"babel-plugin-transform-do-expressions": {
|
||||
"version": "https://registry.npmjs.org/babel-plugin-transform-do-expressions/-/babel-plugin-transform-do-expressions-6.22.0.tgz",
|
||||
"integrity": "sha1-KMyvkoEtlJws0SgfaQyP3EaK6bs=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"babel-plugin-syntax-do-expressions": "https://registry.npmjs.org/babel-plugin-syntax-do-expressions/-/babel-plugin-syntax-do-expressions-6.13.0.tgz",
|
||||
"babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz"
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-runtime": {
|
||||
"version": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz",
|
||||
"integrity": "sha1-CpSJ8UTecO+zzkMArM2zKeL8VDs=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"core-js": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz",
|
||||
"regenerator-runtime": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.1.tgz"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"babel-plugin-transform-es2015-destructuring": {
|
||||
"version": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.19.0.tgz",
|
||||
"integrity": "sha1-/x2RHEs/TKtiG9ZnAqhprNGQBTM=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.20.0.tgz"
|
||||
}
|
||||
},
|
||||
"babel-plugin-transform-es2015-function-name": {
|
||||
"version": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.9.0.tgz",
|
||||
"integrity": "sha1-jBNbF9vQZOW7pW7FEbqu4vyoJxk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"babel-helper-function-name": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.18.0.tgz",
|
||||
"babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.20.0.tgz",
|
||||
"babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.21.0.tgz"
|
||||
}
|
||||
},
|
||||
"babel-plugin-transform-es2015-modules-commonjs": {
|
||||
"version": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.18.0.tgz",
|
||||
"integrity": "sha1-wVrluxGzKgq9zJilg3uqTujWe8w=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"babel-plugin-transform-strict-mode": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.18.0.tgz",
|
||||
"babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.20.0.tgz",
|
||||
"babel-template": "https://registry.npmjs.org/babel-template/-/babel-template-6.16.0.tgz",
|
||||
"babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.21.0.tgz"
|
||||
}
|
||||
},
|
||||
"babel-plugin-transform-es2015-parameters": {
|
||||
"version": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.21.0.tgz",
|
||||
"integrity": "sha1-RqZV5oZO+YQJFEjN8CTYe2Cyp9g=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"babel-helper-call-delegate": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.18.0.tgz",
|
||||
"babel-helper-get-function-arity": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.18.0.tgz",
|
||||
"babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.20.0.tgz",
|
||||
"babel-template": "https://registry.npmjs.org/babel-template/-/babel-template-6.16.0.tgz",
|
||||
"babel-traverse": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.21.0.tgz",
|
||||
"babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.21.0.tgz"
|
||||
}
|
||||
},
|
||||
"babel-plugin-transform-strict-mode": {
|
||||
"version": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.18.0.tgz",
|
||||
"integrity": "sha1-33zymR/gRvRBY9zRENXKQ7xlK50=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.20.0.tgz",
|
||||
"babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.21.0.tgz"
|
||||
}
|
||||
},
|
||||
"babel-preset-es2015-node6": {
|
||||
"version": "https://registry.npmjs.org/babel-preset-es2015-node6/-/babel-preset-es2015-node6-0.4.0.tgz",
|
||||
"integrity": "sha1-+Ik/gbZTN0eSTGVzSIZ71jtPncI=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"babel-plugin-transform-es2015-destructuring": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.19.0.tgz",
|
||||
"babel-plugin-transform-es2015-function-name": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.9.0.tgz",
|
||||
"babel-plugin-transform-es2015-modules-commonjs": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.18.0.tgz",
|
||||
"babel-plugin-transform-es2015-parameters": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.21.0.tgz"
|
||||
}
|
||||
},
|
||||
"babel-runtime": {
|
||||
"version": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.20.0.tgz",
|
||||
"integrity": "sha1-hzAL3PTNdw8JvwBIxkIE4XgG0W8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"core-js": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz",
|
||||
"regenerator-runtime": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.1.tgz"
|
||||
}
|
||||
},
|
||||
"babel-template": {
|
||||
"version": "https://registry.npmjs.org/babel-template/-/babel-template-6.16.0.tgz",
|
||||
"integrity": "sha1-4UndGp8Do1+BfdvE0EgZiOfryMo=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.20.0.tgz",
|
||||
"babel-traverse": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.21.0.tgz",
|
||||
"babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.21.0.tgz",
|
||||
"babylon": "https://registry.npmjs.org/babylon/-/babylon-6.14.1.tgz",
|
||||
"lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.2.tgz"
|
||||
}
|
||||
},
|
||||
"babel-traverse": {
|
||||
"version": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.21.0.tgz",
|
||||
"integrity": "sha1-acY2WATxpPaesSE/hbAKgYuMIa0=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"babel-code-frame": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.20.0.tgz",
|
||||
"babel-messages": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.8.0.tgz",
|
||||
"babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.20.0.tgz",
|
||||
"babel-types": "https://registry.npmjs.org/babel-types/-/babel-types-6.21.0.tgz",
|
||||
"babylon": "https://registry.npmjs.org/babylon/-/babylon-6.14.1.tgz",
|
||||
"debug": "https://registry.npmjs.org/debug/-/debug-2.4.5.tgz",
|
||||
"globals": "https://registry.npmjs.org/globals/-/globals-9.14.0.tgz",
|
||||
"invariant": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz",
|
||||
"lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.2.tgz"
|
||||
}
|
||||
},
|
||||
"babel-types": {
|
||||
"version": "https://registry.npmjs.org/babel-types/-/babel-types-6.21.0.tgz",
|
||||
"integrity": "sha1-MUuSFoiR7204Brf3qRf9+HwRpLI=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.20.0.tgz",
|
||||
"esutils": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
|
||||
"lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.2.tgz",
|
||||
"to-fast-properties": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.2.tgz"
|
||||
}
|
||||
},
|
||||
"babylon": {
|
||||
"version": "https://registry.npmjs.org/babylon/-/babylon-6.14.1.tgz",
|
||||
"integrity": "sha1-lWJ1+rcnU62bNDXXr+WPi/CimBU=",
|
||||
"dev": true
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz",
|
||||
"integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=",
|
||||
"dev": true
|
||||
},
|
||||
"bit-buffer": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bit-buffer/-/bit-buffer-0.1.0.tgz",
|
||||
"integrity": "sha1-gWTBXb0hjup04IQ9pw76VVpEAsQ="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz",
|
||||
"integrity": "sha1-cZfX6qm4fmSDkOph/GbIRCdCDfk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz",
|
||||
"concat-map": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
|
||||
}
|
||||
},
|
||||
"browser-stdout": {
|
||||
"version": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz",
|
||||
"integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=",
|
||||
"dev": true
|
||||
},
|
||||
"chalk": {
|
||||
"version": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
||||
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
|
||||
"escape-string-regexp": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"has-ansi": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
|
||||
"strip-ansi": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
"supports-color": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz"
|
||||
}
|
||||
},
|
||||
"clone": {
|
||||
"version": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz",
|
||||
"integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs="
|
||||
},
|
||||
"colors": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz",
|
||||
"integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=",
|
||||
"dev": true
|
||||
},
|
||||
"commander": {
|
||||
"version": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
|
||||
"integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-readlink": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz"
|
||||
}
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
|
||||
"dev": true
|
||||
},
|
||||
"core-js": {
|
||||
"version": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz",
|
||||
"integrity": "sha1-TekR5mew6ukSTjQlS1OupvxhjT4=",
|
||||
"dev": true
|
||||
},
|
||||
"debug": {
|
||||
"version": "https://registry.npmjs.org/debug/-/debug-2.4.5.tgz",
|
||||
"integrity": "sha1-NMexKhypZnRCj0H+ksSbTOfNBgc=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz"
|
||||
}
|
||||
},
|
||||
"diff": {
|
||||
"version": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz",
|
||||
"integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=",
|
||||
"dev": true
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
|
||||
"dev": true
|
||||
},
|
||||
"esutils": {
|
||||
"version": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
|
||||
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
|
||||
"dev": true
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
|
||||
"dev": true
|
||||
},
|
||||
"glob": {
|
||||
"version": "https://registry.npmjs.org/glob/-/glob-7.0.5.tgz",
|
||||
"integrity": "sha1-tCAqaQmbu00pKnwblbZoK2fr3JU=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fs.realpath": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"inflight": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz",
|
||||
"once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"path-is-absolute": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz"
|
||||
}
|
||||
},
|
||||
"globals": {
|
||||
"version": "https://registry.npmjs.org/globals/-/globals-9.14.0.tgz",
|
||||
"integrity": "sha1-iFmTavADh0EmMFOznQ52yiQeQDQ=",
|
||||
"dev": true
|
||||
},
|
||||
"graceful-readlink": {
|
||||
"version": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
|
||||
"integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=",
|
||||
"dev": true
|
||||
},
|
||||
"growl": {
|
||||
"version": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz",
|
||||
"integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=",
|
||||
"dev": true
|
||||
},
|
||||
"has-ansi": {
|
||||
"version": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
|
||||
"integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz"
|
||||
}
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz",
|
||||
"integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=",
|
||||
"dev": true
|
||||
},
|
||||
"inflight": {
|
||||
"version": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"wrappy": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
|
||||
"dev": true
|
||||
},
|
||||
"invariant": {
|
||||
"version": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz",
|
||||
"integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"loose-envify": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.0.tgz"
|
||||
}
|
||||
},
|
||||
"js-tokens": {
|
||||
"version": "https://registry.npmjs.org/js-tokens/-/js-tokens-2.0.0.tgz",
|
||||
"integrity": "sha1-eZA/VWPud4zBFi5tzxoAJ8l/nLU=",
|
||||
"dev": true
|
||||
},
|
||||
"json3": {
|
||||
"version": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz",
|
||||
"integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash": {
|
||||
"version": "https://registry.npmjs.org/lodash/-/lodash-4.17.2.tgz",
|
||||
"integrity": "sha1-NKMFW6vgTOQkZ7YH1wAHLH/2v0I=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash._baseassign": {
|
||||
"version": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz",
|
||||
"integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash._basecopy": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz",
|
||||
"lodash.keys": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz"
|
||||
}
|
||||
},
|
||||
"lodash._basecopy": {
|
||||
"version": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz",
|
||||
"integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash._basecreate": {
|
||||
"version": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz",
|
||||
"integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash._getnative": {
|
||||
"version": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz",
|
||||
"integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash._isiterateecall": {
|
||||
"version": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz",
|
||||
"integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.create": {
|
||||
"version": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz",
|
||||
"integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash._baseassign": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz",
|
||||
"lodash._basecreate": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz",
|
||||
"lodash._isiterateecall": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz"
|
||||
}
|
||||
},
|
||||
"lodash.isarguments": {
|
||||
"version": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
|
||||
"integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.isarray": {
|
||||
"version": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz",
|
||||
"integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.keys": {
|
||||
"version": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz",
|
||||
"integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash._getnative": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz",
|
||||
"lodash.isarguments": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
|
||||
"lodash.isarray": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz"
|
||||
}
|
||||
},
|
||||
"loose-envify": {
|
||||
"version": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.0.tgz",
|
||||
"integrity": "sha1-ayYkjEL21PpLDYVC947fzeNWQqg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"js-tokens": "https://registry.npmjs.org/js-tokens/-/js-tokens-2.0.0.tgz"
|
||||
}
|
||||
},
|
||||
"make-error": {
|
||||
"version": "https://registry.npmjs.org/make-error/-/make-error-1.2.3.tgz",
|
||||
"integrity": "sha1-bEQC33MuCXesb691SlB0s9Kx0Z0=",
|
||||
"dev": true
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz",
|
||||
"integrity": "sha1-Kk5AkLlrLbBqnX3wEFWmKnfJt3Q=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "https://registry.npmjs.org/minimist/-/minimist-1.1.3.tgz",
|
||||
"integrity": "sha1-O+39kaktOQFvz6ocaB6Pqhoe/ag="
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
|
||||
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimist": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz"
|
||||
},
|
||||
"dependencies": {
|
||||
"minimist": {
|
||||
"version": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
|
||||
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"mocha": {
|
||||
"version": "https://registry.npmjs.org/mocha/-/mocha-3.2.0.tgz",
|
||||
"integrity": "sha1-fcT0XlCIB1FxpoiWgU5q6et6heM=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"browser-stdout": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz",
|
||||
"commander": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
|
||||
"debug": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
|
||||
"diff": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz",
|
||||
"escape-string-regexp": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"glob": "https://registry.npmjs.org/glob/-/glob-7.0.5.tgz",
|
||||
"growl": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz",
|
||||
"json3": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz",
|
||||
"lodash.create": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz",
|
||||
"mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
|
||||
"supports-color": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
|
||||
"integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
|
||||
"integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=",
|
||||
"dev": true
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz",
|
||||
"integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz",
|
||||
"integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=",
|
||||
"dev": true
|
||||
},
|
||||
"once": {
|
||||
"version": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"wrappy": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz"
|
||||
}
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
|
||||
"dev": true
|
||||
},
|
||||
"path-parse": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz",
|
||||
"integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=",
|
||||
"dev": true
|
||||
},
|
||||
"pinkie": {
|
||||
"version": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
|
||||
"integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
|
||||
"dev": true
|
||||
},
|
||||
"regenerator-runtime": {
|
||||
"version": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.1.tgz",
|
||||
"integrity": "sha1-JX9BlhzkRVixj3gUr0jBdVn5+us=",
|
||||
"dev": true
|
||||
},
|
||||
"resolve": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.4.0.tgz",
|
||||
"integrity": "sha512-aW7sVKPufyHqOmyyLzg/J+8606v5nevBgaliIlV7nUpVMsDnoBGV/cbSLNjZAg9q0Cfd/+easKVKQ8vOu8fn1Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"path-parse": "1.0.5"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
|
||||
"integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==",
|
||||
"dev": true
|
||||
},
|
||||
"snappyjs": {
|
||||
"version": "https://registry.npmjs.org/snappyjs/-/snappyjs-0.5.0.tgz",
|
||||
"integrity": "sha1-JgDnWlDweZx5sFXD3xt/cAgEWDg="
|
||||
},
|
||||
"source-map": {
|
||||
"version": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz",
|
||||
"integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=",
|
||||
"dev": true
|
||||
},
|
||||
"source-map-support": {
|
||||
"version": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.14.tgz",
|
||||
"integrity": "sha1-nURjdyWYuGJxtPUj9sH04Cp9au8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"source-map": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz"
|
||||
}
|
||||
},
|
||||
"strip-bom": {
|
||||
"version": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
|
||||
"integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
|
||||
"dev": true
|
||||
},
|
||||
"strip-json-comments": {
|
||||
"version": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
||||
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
|
||||
"dev": true
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
|
||||
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
|
||||
"dev": true
|
||||
},
|
||||
"to-fast-properties": {
|
||||
"version": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.2.tgz",
|
||||
"integrity": "sha1-8/XAw7pymafvmUJ+RGMyV63kMyA=",
|
||||
"dev": true
|
||||
},
|
||||
"ts-node": {
|
||||
"version": "https://registry.npmjs.org/ts-node/-/ts-node-2.1.2.tgz",
|
||||
"integrity": "sha1-RQh7ReezcbPa8E7MRw7CmoNmVeo=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"arrify": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
|
||||
"chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
||||
"diff": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz",
|
||||
"make-error": "https://registry.npmjs.org/make-error/-/make-error-1.2.3.tgz",
|
||||
"minimist": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
||||
"mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
|
||||
"pinkie": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
|
||||
"source-map-support": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.14.tgz",
|
||||
"tsconfig": "https://registry.npmjs.org/tsconfig/-/tsconfig-6.0.0.tgz",
|
||||
"v8flags": "https://registry.npmjs.org/v8flags/-/v8flags-2.0.12.tgz",
|
||||
"xtend": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
|
||||
"yn": "https://registry.npmjs.org/yn/-/yn-1.2.0.tgz"
|
||||
},
|
||||
"dependencies": {
|
||||
"diff": {
|
||||
"version": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz",
|
||||
"integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=",
|
||||
"dev": true
|
||||
},
|
||||
"minimist": {
|
||||
"version": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
||||
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"tsconfig": {
|
||||
"version": "https://registry.npmjs.org/tsconfig/-/tsconfig-6.0.0.tgz",
|
||||
"integrity": "sha1-aw6DdgA9evGGT434+J3QBZ/80DI=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"strip-bom": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
|
||||
"strip-json-comments": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz"
|
||||
}
|
||||
},
|
||||
"tslib": {
|
||||
"version": "1.7.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.7.1.tgz",
|
||||
"integrity": "sha1-vIAEFkaRkjp5/oN4u+s9ogF1OOw=",
|
||||
"dev": true
|
||||
},
|
||||
"tslint": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/tslint/-/tslint-5.5.0.tgz",
|
||||
"integrity": "sha1-EOjas+MGH6YelELozuOYKs8gpqo=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"babel-code-frame": "6.22.0",
|
||||
"colors": "1.1.2",
|
||||
"commander": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
|
||||
"diff": "3.3.0",
|
||||
"glob": "7.1.2",
|
||||
"minimatch": "3.0.4",
|
||||
"resolve": "1.4.0",
|
||||
"semver": "5.4.1",
|
||||
"tslib": "1.7.1",
|
||||
"tsutils": "2.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-code-frame": {
|
||||
"version": "6.22.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz",
|
||||
"integrity": "sha1-AnYgvuVnqIwyVhV05/0IAdMxGOQ=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
||||
"esutils": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
|
||||
"js-tokens": "3.0.2"
|
||||
}
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
|
||||
"dev": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
|
||||
"integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "1.0.0",
|
||||
"concat-map": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
|
||||
}
|
||||
},
|
||||
"diff": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-3.3.0.tgz",
|
||||
"integrity": "sha512-w0XZubFWn0Adlsapj9EAWX0FqWdO4tz8kc3RiYdWLh4k/V8PTb6i0SMgXt0vRM3zyKnT8tKO7mUlieRQHIjMNg==",
|
||||
"dev": true
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
|
||||
"integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fs.realpath": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"inflight": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"minimatch": "3.0.4",
|
||||
"once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"path-is-absolute": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz"
|
||||
}
|
||||
},
|
||||
"js-tokens": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
|
||||
"integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=",
|
||||
"dev": true
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "1.1.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tsutils": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.8.0.tgz",
|
||||
"integrity": "sha1-AWAXNymzvxOGKN0UoVN+AIUdgUo=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"tslib": "1.7.1"
|
||||
}
|
||||
},
|
||||
"typescript": {
|
||||
"version": "https://registry.npmjs.org/typescript/-/typescript-2.2.2.tgz",
|
||||
"integrity": "sha1-YGAiUIR5tV/6NotY/uljoD39eww=",
|
||||
"dev": true
|
||||
},
|
||||
"user-home": {
|
||||
"version": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz",
|
||||
"integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=",
|
||||
"dev": true
|
||||
},
|
||||
"v8flags": {
|
||||
"version": "https://registry.npmjs.org/v8flags/-/v8flags-2.0.12.tgz",
|
||||
"integrity": "sha1-cyNdn3F2+OiDP7KGeVRF95ONhOU=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"user-home": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz"
|
||||
}
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
|
||||
"dev": true
|
||||
},
|
||||
"xtend": {
|
||||
"version": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
|
||||
"integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=",
|
||||
"dev": true
|
||||
},
|
||||
"yn": {
|
||||
"version": "https://registry.npmjs.org/yn/-/yn-1.2.0.tgz",
|
||||
"integrity": "sha1-0jekxTPyebK4nTrKwttLjHleSmM=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
"jsnext:main": "build/es6/index.js",
|
||||
"module": "build/es6/index.js",
|
||||
"dependencies": {
|
||||
"bit-buffer": "0.1.0",
|
||||
"bit-buffer": "^0.1.0",
|
||||
"clone": "^2.1.0",
|
||||
"minimist": "1.1.x",
|
||||
"snappyjs": "^0.5.0"
|
||||
|
|
@ -20,6 +20,7 @@
|
|||
"babel-preset-es2015-node6": "^0.4.0",
|
||||
"mocha": "^3.2.0",
|
||||
"ts-node": "^2.1.0",
|
||||
"tslint": "^5.5.0",
|
||||
"typescript": "^2.1.4"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import {Vector} from "./Vector";
|
||||
import {Vector} from './Vector';
|
||||
|
||||
export interface BaseBuilding {
|
||||
builder: number;
|
||||
|
|
|
|||
|
|
@ -15,13 +15,13 @@ export interface GameEventEntry {
|
|||
}
|
||||
|
||||
export enum GameEventType {
|
||||
STRING = 1,
|
||||
FLOAT = 2,
|
||||
LONG = 3,
|
||||
SHORT = 4,
|
||||
BYTE = 5,
|
||||
STRING = 1,
|
||||
FLOAT = 2,
|
||||
LONG = 3,
|
||||
SHORT = 4,
|
||||
BYTE = 5,
|
||||
BOOLEAN = 6,
|
||||
LOCAL = 7
|
||||
LOCAL = 7,
|
||||
}
|
||||
|
||||
export interface DeathEventValues {
|
||||
|
|
@ -40,7 +40,7 @@ export interface RoundWinEventValues {
|
|||
export interface PlayerSpawnEventValues {
|
||||
userid: number;
|
||||
team: number;
|
||||
'class': number
|
||||
'class': number;
|
||||
}
|
||||
|
||||
export interface ObjectDestroyedValues {
|
||||
|
|
@ -52,9 +52,9 @@ export interface ObjectDestroyedValues {
|
|||
index: number;
|
||||
}
|
||||
|
||||
export type GameEventValue = string|number|boolean;
|
||||
export type GameEventValue = string | number | boolean;
|
||||
|
||||
export type GameEventValueMap = {
|
||||
export interface GameEventValueMap {
|
||||
[name: string]: GameEventValue;
|
||||
}
|
||||
|
||||
|
|
@ -64,6 +64,6 @@ export type GameEventValues = GameEventValueMap |
|
|||
PlayerSpawnEventValues |
|
||||
ObjectDestroyedValues;
|
||||
|
||||
export type GameEventDefinitionMap = {
|
||||
export interface GameEventDefinitionMap {
|
||||
[id: number]: GameEventDefinition;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,81 +1,81 @@
|
|||
import {PacketEntity} from "./PacketEntity";
|
||||
import {ServerClass} from "./ServerClass";
|
||||
import {SendTable} from "./SendTable";
|
||||
import {StringTable} from "./StringTable";
|
||||
import {GameEventDefinitionMap} from "./GameEvent";
|
||||
import {BitStream} from "bit-buffer";
|
||||
import {UserInfo} from "./UserInfo";
|
||||
import {World} from "./World";
|
||||
import {Player} from "./Player";
|
||||
import {Death} from "./Death";
|
||||
import {handleStringTable} from "../PacketHandler/StringTable";
|
||||
import {handleSayText2} from "../PacketHandler/SayText2";
|
||||
import {handleGameEvent} from "../PacketHandler/GameEvent";
|
||||
import {handlePacketEntities} from "../PacketHandler/PacketEntities";
|
||||
import {handleGameEventList} from "../PacketHandler/GameEventList";
|
||||
import {handleDataTable} from "../PacketHandler/DataTable";
|
||||
import {Weapon} from "./Weapon";
|
||||
import {Team} from "./Team";
|
||||
import {Building} from "./Building";
|
||||
import {PlayerResource} from "./PlayerResource";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {handleDataTable} from '../PacketHandler/DataTable';
|
||||
import {handleGameEvent} from '../PacketHandler/GameEvent';
|
||||
import {handleGameEventList} from '../PacketHandler/GameEventList';
|
||||
import {handlePacketEntities} from '../PacketHandler/PacketEntities';
|
||||
import {handleSayText2} from '../PacketHandler/SayText2';
|
||||
import {handleStringTable} from '../PacketHandler/StringTable';
|
||||
import {Building} from './Building';
|
||||
import {Death} from './Death';
|
||||
import {GameEventDefinitionMap} from './GameEvent';
|
||||
import {PacketEntity} from './PacketEntity';
|
||||
import {Player} from './Player';
|
||||
import {PlayerResource} from './PlayerResource';
|
||||
import {SendTable} from './SendTable';
|
||||
import {ServerClass} from './ServerClass';
|
||||
import {StringTable} from './StringTable';
|
||||
import {Team} from './Team';
|
||||
import {UserInfo} from './UserInfo';
|
||||
import {Weapon} from './Weapon';
|
||||
import {World} from './World';
|
||||
|
||||
export class Match {
|
||||
tick: number;
|
||||
chat: any[];
|
||||
users: {[id: string]: UserInfo};
|
||||
deaths: Death[];
|
||||
rounds: any[];
|
||||
startTick: number;
|
||||
intervalPerTick: number;
|
||||
stringTables: StringTable[];
|
||||
serverClasses: ServerClass[];
|
||||
sendTables: SendTable[];
|
||||
staticBaseLines: BitStream[];
|
||||
eventDefinitions: GameEventDefinitionMap;
|
||||
world: World;
|
||||
players: Player[];
|
||||
playerMap: {[entityId: number]: Player};
|
||||
entityClasses: {[entityId: string]: ServerClass};
|
||||
sendTableMap: {[name: string]: SendTable};
|
||||
baseLineCache: {[serverClass: string]: PacketEntity};
|
||||
weaponMap: {[entityId: string]: Weapon};
|
||||
outerMap: {[outer: number]: number};
|
||||
teams: Team[];
|
||||
teamMap: {[entityId: string]: Team};
|
||||
version: number;
|
||||
buildings: {[entityId: string]: Building} = {};
|
||||
playerResources: PlayerResource[] = [];
|
||||
public tick: number;
|
||||
public chat: any[];
|
||||
public users: { [id: string]: UserInfo };
|
||||
public deaths: Death[];
|
||||
public rounds: any[];
|
||||
public startTick: number;
|
||||
public intervalPerTick: number;
|
||||
public staticBaseLines: BitStream[];
|
||||
public eventDefinitions: GameEventDefinitionMap;
|
||||
public world: World;
|
||||
public players: Player[];
|
||||
public playerMap: { [entityId: number]: Player };
|
||||
public entityClasses: { [entityId: string]: ServerClass };
|
||||
public sendTableMap: { [name: string]: SendTable };
|
||||
public baseLineCache: { [serverClass: string]: PacketEntity };
|
||||
public weaponMap: { [entityId: string]: Weapon };
|
||||
public outerMap: { [outer: number]: number };
|
||||
public teams: Team[];
|
||||
public teamMap: { [entityId: string]: Team };
|
||||
public version: number;
|
||||
public buildings: { [entityId: string]: Building } = {};
|
||||
public playerResources: PlayerResource[] = [];
|
||||
public stringTables: StringTable[];
|
||||
public sendTables: SendTable[];
|
||||
public serverClasses: ServerClass[];
|
||||
|
||||
constructor() {
|
||||
this.tick = 0;
|
||||
this.chat = [];
|
||||
this.users = {};
|
||||
this.deaths = [];
|
||||
this.rounds = [];
|
||||
this.startTick = 0;
|
||||
this.intervalPerTick = 0;
|
||||
this.stringTables = [];
|
||||
this.sendTables = [];
|
||||
this.serverClasses = [];
|
||||
this.staticBaseLines = [];
|
||||
this.tick = 0;
|
||||
this.chat = [];
|
||||
this.users = {};
|
||||
this.deaths = [];
|
||||
this.rounds = [];
|
||||
this.startTick = 0;
|
||||
this.intervalPerTick = 0;
|
||||
this.stringTables = [];
|
||||
this.sendTables = [];
|
||||
this.serverClasses = [];
|
||||
this.staticBaseLines = [];
|
||||
this.eventDefinitions = {};
|
||||
this.players = [];
|
||||
this.playerMap = {};
|
||||
this.world = {
|
||||
this.players = [];
|
||||
this.playerMap = {};
|
||||
this.world = {
|
||||
boundaryMin: {x: 0, y: 0, z: 0},
|
||||
boundaryMax: {x: 0, y: 0, z: 0}
|
||||
boundaryMax: {x: 0, y: 0, z: 0},
|
||||
};
|
||||
this.entityClasses = {};
|
||||
this.sendTableMap = {};
|
||||
this.baseLineCache = {};
|
||||
this.weaponMap = {};
|
||||
this.outerMap = {};
|
||||
this.teams = [];
|
||||
this.teamMap = {};
|
||||
this.version = 0;
|
||||
this.entityClasses = {};
|
||||
this.sendTableMap = {};
|
||||
this.baseLineCache = {};
|
||||
this.weaponMap = {};
|
||||
this.outerMap = {};
|
||||
this.teams = [];
|
||||
this.teamMap = {};
|
||||
this.version = 0;
|
||||
}
|
||||
|
||||
getSendTable(name) {
|
||||
public getSendTable(name) {
|
||||
if (this.sendTableMap[name]) {
|
||||
return this.sendTableMap[name];
|
||||
}
|
||||
|
|
@ -85,10 +85,10 @@ export class Match {
|
|||
return table;
|
||||
}
|
||||
}
|
||||
throw new Error("unknown SendTable " + name);
|
||||
throw new Error('unknown SendTable ' + name);
|
||||
}
|
||||
|
||||
getStringTable(name) {
|
||||
public getStringTable(name) {
|
||||
for (const table of this.stringTables) {
|
||||
if (table.name === name) {
|
||||
return table;
|
||||
|
|
@ -97,16 +97,16 @@ export class Match {
|
|||
return null;
|
||||
}
|
||||
|
||||
getState() {
|
||||
public getState() {
|
||||
const users = {};
|
||||
for (const key in this.users) {
|
||||
const user = this.users[key];
|
||||
if (this.users.hasOwnProperty(key)) {
|
||||
const user = this.users[key];
|
||||
users[key] = {
|
||||
classes: user.classes,
|
||||
name: user.name,
|
||||
name: user.name,
|
||||
steamId: user.steamId,
|
||||
userId: user.userId,
|
||||
userId: user.userId,
|
||||
};
|
||||
if (user.team) {
|
||||
users[key].team = user.team;
|
||||
|
|
@ -115,16 +115,16 @@ export class Match {
|
|||
}
|
||||
|
||||
return {
|
||||
'chat': this.chat,
|
||||
'users': users,
|
||||
'deaths': this.deaths,
|
||||
'rounds': this.rounds,
|
||||
'startTick': this.startTick,
|
||||
'intervalPerTick': this.intervalPerTick
|
||||
chat: this.chat,
|
||||
users,
|
||||
deaths: this.deaths,
|
||||
rounds: this.rounds,
|
||||
startTick: this.startTick,
|
||||
intervalPerTick: this.intervalPerTick,
|
||||
};
|
||||
}
|
||||
|
||||
handlePacket(packet) {
|
||||
public handlePacket(packet) {
|
||||
switch (packet.packetType) {
|
||||
case 'packetEntities':
|
||||
handlePacketEntities(packet, this);
|
||||
|
|
@ -137,7 +137,7 @@ export class Match {
|
|||
break;
|
||||
case 'serverInfo':
|
||||
this.intervalPerTick = packet.intervalPerTick;
|
||||
this.version = packet.version;
|
||||
this.version = packet.version;
|
||||
break;
|
||||
case 'sayText2':
|
||||
handleSayText2(packet, this);
|
||||
|
|
@ -157,7 +157,7 @@ export class Match {
|
|||
}
|
||||
}
|
||||
|
||||
getUserInfo(userId: number): UserInfo {
|
||||
public getUserInfo(userId: number): UserInfo {
|
||||
// no clue why it does this
|
||||
// only seems to be the case with per user ready
|
||||
while (userId > 256) {
|
||||
|
|
@ -165,18 +165,18 @@ export class Match {
|
|||
}
|
||||
if (!this.users[userId]) {
|
||||
this.users[userId] = {
|
||||
name: '',
|
||||
userId: userId,
|
||||
steamId: '',
|
||||
classes: {},
|
||||
name: '',
|
||||
userId,
|
||||
steamId: '',
|
||||
classes: {},
|
||||
entityId: 0,
|
||||
team: ''
|
||||
}
|
||||
team: '',
|
||||
};
|
||||
}
|
||||
return this.users[userId];
|
||||
}
|
||||
|
||||
getUserInfoForEntity(entity: PacketEntity): UserInfo {
|
||||
public getUserInfoForEntity(entity: PacketEntity): UserInfo {
|
||||
for (const id of Object.keys(this.users)) {
|
||||
const user = this.users[id];
|
||||
if (user && user.entityId === entity.entityIndex) {
|
||||
|
|
@ -186,7 +186,7 @@ export class Match {
|
|||
throw new Error('User not found for entity ' + entity.entityIndex);
|
||||
}
|
||||
|
||||
getPlayerByUserId(userId: number): Player {
|
||||
public getPlayerByUserId(userId: number): Player {
|
||||
for (const player of this.players) {
|
||||
if (player.user.userId === userId) {
|
||||
return player;
|
||||
|
|
@ -196,6 +196,6 @@ export class Match {
|
|||
}
|
||||
|
||||
get classBits() {
|
||||
return Math.ceil(Math.log(this.serverClasses.length) * Math.LOG2E)
|
||||
return Math.ceil(Math.log(this.serverClasses.length) * Math.LOG2E);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import {StringTable} from "./StringTable";
|
||||
import {Vector} from "./Vector";
|
||||
import {GameEvent, GameEventDefinitionMap} from "./GameEvent";
|
||||
import {PacketEntity} from "./PacketEntity";
|
||||
import {SendTable} from "./SendTable";
|
||||
import {ServerClass} from "./ServerClass";
|
||||
import {BitStream} from "bit-buffer";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {GameEvent, GameEventDefinitionMap} from './GameEvent';
|
||||
import {PacketEntity} from './PacketEntity';
|
||||
import {SendTable} from './SendTable';
|
||||
import {ServerClass} from './ServerClass';
|
||||
import {StringTable} from './StringTable';
|
||||
import {Vector} from './Vector';
|
||||
|
||||
export interface StringTablePacket {
|
||||
packetType: 'stringTable';
|
||||
|
|
@ -35,11 +35,11 @@ export interface ClassInfoPacket {
|
|||
packetType: 'classInfo';
|
||||
number: number;
|
||||
create: boolean;
|
||||
entries: {
|
||||
entries: Array<{
|
||||
classId: number;
|
||||
className: string;
|
||||
dataTableName: string;
|
||||
}[]
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface EntityMessagePacket {
|
||||
|
|
@ -66,7 +66,7 @@ export interface PacketEntitiesPacket {
|
|||
maxEntries: number;
|
||||
isDelta: boolean;
|
||||
delta: number;
|
||||
baseLine : number;
|
||||
baseLine: number;
|
||||
updatedEntries: number;
|
||||
length: number;
|
||||
updatedBaseLine: boolean;
|
||||
|
|
@ -113,7 +113,7 @@ export interface VoiceInitPacket {
|
|||
packetType: 'voiceInit';
|
||||
codec: string;
|
||||
quality: number;
|
||||
extraData: number
|
||||
extraData: number;
|
||||
}
|
||||
|
||||
export interface VoiceDataPacket {
|
||||
|
|
|
|||
|
|
@ -1,41 +1,40 @@
|
|||
import {ServerClass} from "./ServerClass";
|
||||
import {SendTable} from "./SendTable";
|
||||
import {SendProp} from "./SendProp";
|
||||
import {SendPropDefinition} from "./SendPropDefinition";
|
||||
import {SendProp} from './SendProp';
|
||||
import {SendPropDefinition} from './SendPropDefinition';
|
||||
import {ServerClass} from './ServerClass';
|
||||
|
||||
export enum PVS {
|
||||
PRESERVE = 0,
|
||||
ENTER = 1,
|
||||
LEAVE = 2,
|
||||
DELETE = 4
|
||||
ENTER = 1,
|
||||
LEAVE = 2,
|
||||
DELETE = 4,
|
||||
}
|
||||
|
||||
export class PacketEntity {
|
||||
pvs: PVS;
|
||||
serverClass: ServerClass;
|
||||
entityIndex: number;
|
||||
props: SendProp[];
|
||||
inPVS: boolean;
|
||||
serialNumber?: number;
|
||||
public serverClass: ServerClass;
|
||||
public entityIndex: number;
|
||||
public props: SendProp[];
|
||||
public inPVS: boolean;
|
||||
public pvs: PVS;
|
||||
public serialNumber?: number;
|
||||
|
||||
constructor(serverClass: ServerClass, entityIndex: number, pvs: PVS) {
|
||||
this.serverClass = serverClass;
|
||||
this.entityIndex = entityIndex;
|
||||
this.props = [];
|
||||
this.inPVS = false;
|
||||
this.pvs = pvs;
|
||||
this.props = [];
|
||||
this.inPVS = false;
|
||||
this.pvs = pvs;
|
||||
}
|
||||
|
||||
getPropByDefinition(definition: SendPropDefinition) {
|
||||
for (let i = 0; i < this.props.length; i++) {
|
||||
if (this.props[i].definition === definition) {
|
||||
return this.props[i];
|
||||
public getPropByDefinition(definition: SendPropDefinition) {
|
||||
for (const prop of this.props) {
|
||||
if (prop.definition === definition) {
|
||||
return prop;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getProperty(originTable: string, name: string) {
|
||||
public getProperty(originTable: string, name: string) {
|
||||
for (const prop of this.props) {
|
||||
if (prop.definition.ownerTableName === originTable && prop.definition.name === name) {
|
||||
return prop;
|
||||
|
|
@ -44,7 +43,7 @@ export class PacketEntity {
|
|||
throw new Error(`Property not found in entity (${originTable}.${name})`);
|
||||
}
|
||||
|
||||
clone(): PacketEntity {
|
||||
public clone(): PacketEntity {
|
||||
const result = new PacketEntity(this.serverClass, this.entityIndex, this.pvs);
|
||||
for (const prop of this.props) {
|
||||
result.props.push(prop.clone());
|
||||
|
|
@ -52,4 +51,3 @@ export class PacketEntity {
|
|||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,36 +1,36 @@
|
|||
import {UserInfo} from "./UserInfo";
|
||||
import {Vector} from "./Vector";
|
||||
import {PlayerCondition} from "./PlayerCondition";
|
||||
import {Weapon} from "./Weapon";
|
||||
import {Match} from "./Match";
|
||||
import {Match} from './Match';
|
||||
import {PlayerCondition} from './PlayerCondition';
|
||||
import {UserInfo} from './UserInfo';
|
||||
import {Vector} from './Vector';
|
||||
import {Weapon} from './Weapon';
|
||||
|
||||
export enum LifeState {
|
||||
ALIVE = 0,
|
||||
DYING = 1,
|
||||
DEATH = 2,
|
||||
RESPAWNABLE = 3
|
||||
ALIVE = 0,
|
||||
DYING = 1,
|
||||
DEATH = 2,
|
||||
RESPAWNABLE = 3,
|
||||
}
|
||||
|
||||
export class Player {
|
||||
match: Match;
|
||||
user: UserInfo;
|
||||
position: Vector = new Vector(0, 0, 0);
|
||||
health: number = 0;
|
||||
maxHealth: number = 0;
|
||||
classId: number = 0;
|
||||
team: number = 0;
|
||||
viewAngle: number = 0;
|
||||
weaponIds: number[] = [];
|
||||
ammo: number[] = [];
|
||||
lifeState: LifeState = LifeState.DEATH;
|
||||
activeWeapon: number = 0;
|
||||
public match: Match;
|
||||
public user: UserInfo;
|
||||
public position: Vector = new Vector(0, 0, 0);
|
||||
public health: number = 0;
|
||||
public maxHealth: number = 0;
|
||||
public classId: number = 0;
|
||||
public team: number = 0;
|
||||
public viewAngle: number = 0;
|
||||
public weaponIds: number[] = [];
|
||||
public ammo: number[] = [];
|
||||
public lifeState: LifeState = LifeState.DEATH;
|
||||
public activeWeapon: number = 0;
|
||||
|
||||
constructor(match: Match, userInfo: UserInfo) {
|
||||
this.match = match;
|
||||
this.user = userInfo;
|
||||
this.user = userInfo;
|
||||
}
|
||||
|
||||
get weapons(): Weapon[] {
|
||||
return this.weaponIds.map(id => this.match.weaponMap[this.match.outerMap[id]]);
|
||||
return this.weaponIds.map((id) => this.match.weaponMap[this.match.outerMap[id]]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
import {SendPropDefinition} from "./SendPropDefinition";
|
||||
import {Vector} from "./Vector";
|
||||
import {SendPropDefinition} from './SendPropDefinition';
|
||||
import {Vector} from './Vector';
|
||||
|
||||
export class SendProp {
|
||||
definition: SendPropDefinition;
|
||||
value: SendPropValue|null;
|
||||
public definition: SendPropDefinition;
|
||||
public value: SendPropValue|null;
|
||||
|
||||
constructor(definition: SendPropDefinition) {
|
||||
this.definition = definition;
|
||||
this.value = null;
|
||||
}
|
||||
|
||||
clone():SendProp {
|
||||
public clone(): SendProp {
|
||||
const prop = new SendProp(this.definition);
|
||||
prop.value = this.value;
|
||||
return prop;
|
||||
|
|
|
|||
|
|
@ -1,49 +1,50 @@
|
|||
import {SendTable} from "./SendTable";
|
||||
import {SendTable} from './SendTable';
|
||||
|
||||
export class SendPropDefinition {
|
||||
type: SendPropType;
|
||||
name: string;
|
||||
flags: number;
|
||||
excludeDTName: string|null;
|
||||
lowValue: number;
|
||||
highValue: number;
|
||||
bitCount: number;
|
||||
table: SendTable|null;
|
||||
numElements: number;
|
||||
arrayProperty: SendPropDefinition|null;
|
||||
ownerTableName: string;
|
||||
public type: SendPropType;
|
||||
public name: string;
|
||||
public flags: number;
|
||||
public excludeDTName: string | null;
|
||||
public lowValue: number;
|
||||
public highValue: number;
|
||||
public bitCount: number;
|
||||
public table: SendTable | null;
|
||||
public numElements: number;
|
||||
public arrayProperty: SendPropDefinition | null;
|
||||
public ownerTableName: string;
|
||||
|
||||
constructor(type, name, flags, ownerTableName) {
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.flags = flags;
|
||||
this.excludeDTName = null;
|
||||
this.lowValue = 0;
|
||||
this.highValue = 0;
|
||||
this.bitCount = 0;
|
||||
this.table = null;
|
||||
this.numElements = 0;
|
||||
this.arrayProperty = null;
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.flags = flags;
|
||||
this.excludeDTName = null;
|
||||
this.lowValue = 0;
|
||||
this.highValue = 0;
|
||||
this.bitCount = 0;
|
||||
this.table = null;
|
||||
this.numElements = 0;
|
||||
this.arrayProperty = null;
|
||||
this.ownerTableName = ownerTableName;
|
||||
}
|
||||
|
||||
hasFlag(flag: SendPropFlag) {
|
||||
return (this.flags & flag) != 0;
|
||||
public hasFlag(flag: SendPropFlag) {
|
||||
return (this.flags & flag) !== 0;
|
||||
}
|
||||
|
||||
isExcludeProp() {
|
||||
public isExcludeProp() {
|
||||
return this.hasFlag(SendPropFlag.SPROP_EXCLUDE);
|
||||
}
|
||||
|
||||
inspect() {
|
||||
let data: any = {
|
||||
public inspect() {
|
||||
const data: any = {
|
||||
fromTable: this.ownerTableName,
|
||||
name: this.name,
|
||||
type: SendPropType[this.type],
|
||||
flags: SendPropDefinition.formatFlags(this.flags),
|
||||
bitCount: this.bitCount
|
||||
name: this.name,
|
||||
type: SendPropType[this.type],
|
||||
flags: this.flags,
|
||||
bitCount: this.bitCount,
|
||||
};
|
||||
if (this.type === SendPropType.DPT_Float) {
|
||||
data.lowValue = this.lowValue;
|
||||
data.lowValue = this.lowValue;
|
||||
data.highValue = this.highValue;
|
||||
}
|
||||
if (this.type === SendPropType.DPT_DataTable && this.table) {
|
||||
|
|
@ -52,19 +53,6 @@ export class SendPropDefinition {
|
|||
|
||||
return data;
|
||||
}
|
||||
|
||||
static formatFlags(flags: number) {
|
||||
let names: string[] = [];
|
||||
for (const name in SendPropFlag) {
|
||||
const flagValue = <SendPropFlag|string>SendPropFlag[name];
|
||||
if (typeof flagValue === 'number') {
|
||||
if (flags & flagValue) {
|
||||
names.push(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
}
|
||||
|
||||
export enum SendPropType {
|
||||
|
|
@ -75,32 +63,32 @@ export enum SendPropType {
|
|||
DPT_String,
|
||||
DPT_Array,
|
||||
DPT_DataTable,
|
||||
DPT_NUMSendPropTypes
|
||||
DPT_NUMSendPropTypes,
|
||||
}
|
||||
|
||||
|
||||
export enum SendPropFlag {
|
||||
SPROP_UNSIGNED = (1 << 0),// Unsigned integer data.
|
||||
SPROP_COORD = (1 << 1),// If this is set, the float/vector is treated like a world coordinate.
|
||||
// Note that the bit count is ignored in this case.
|
||||
SPROP_NOSCALE = (1 << 2),// For floating point, don't scale into range, just take value as is.
|
||||
SPROP_ROUNDDOWN = (1 << 3),// For floating point, limit high value to range minus one bit unit
|
||||
SPROP_ROUNDUP = (1 << 4),// For floating point, limit low value to range minus one bit unit
|
||||
SPROP_NORMAL = (1 << 5),// If this is set, the vector is treated like a normal (only valid for vectors)
|
||||
SPROP_EXCLUDE = (1 << 6),// This is an exclude prop (not excludED, but it points at another prop to be excluded).
|
||||
SPROP_XYZE = (1 << 7),// Use XYZ/Exponent encoding for vectors.
|
||||
SPROP_INSIDEARRAY = (1 << 8),// This tells us that the property is inside an array, so it shouldn't be put into the
|
||||
// flattened property list. Its array will point at it when it needs to.
|
||||
SPROP_PROXY_ALWAYS_YES = (1 << 9),// Set for datatable props using one of the default datatable proxies like
|
||||
// SendProxy_DataTableToDataTable that always send the data to all clients.
|
||||
SPROP_CHANGES_OFTEN = (1 << 10),// this is an often changed field, moved to head of sendtable so it gets a small index
|
||||
SPROP_IS_A_VECTOR_ELEM = (1 << 11),// Set automatically if SPROP_VECTORELEM is used.
|
||||
SPROP_COLLAPSIBLE = (1 << 12),// Set automatically if it's a datatable with an offset of 0 that doesn't change the pointer
|
||||
// (ie: for all automatically-chained base classes).
|
||||
// In this case, it can get rid of this SendPropDataTable altogether and spare the
|
||||
// trouble of walking the hierarchy more than necessary.
|
||||
SPROP_COORD_MP = (1 << 13),// Like SPROP_COORD, but special handling for multiplayer games
|
||||
SPROP_COORD_MP_LOWPRECISION = (1 << 14),// Like SPROP_COORD, but special handling for multiplayer games where the fractional component only gets a 3 bits instead of 5
|
||||
SPROP_COORD_MP_INTEGRAL = (1 << 15),// SPROP_COORD_MP, but coordinates are rounded to integral boundaries
|
||||
SPROP_VARINT = (1 << 5)
|
||||
SPROP_UNSIGNED = (1 << 0), // Unsigned integer data.
|
||||
SPROP_COORD = (1 << 1), // If this is set, the float/vector is treated like a world coordinate.
|
||||
// Note that the bit count is ignored in this case.
|
||||
SPROP_NOSCALE = (1 << 2), // For floating point, don't scale into range, just take value as is.
|
||||
SPROP_ROUNDDOWN = (1 << 3), // For floating point, limit high value to range minus one bit unit
|
||||
SPROP_ROUNDUP = (1 << 4), // For floating point, limit low value to range minus one bit unit
|
||||
SPROP_NORMAL = (1 << 5), // If this is set, the vector is treated like a normal (only valid for vectors)
|
||||
SPROP_EXCLUDE = (1 << 6), // This is an exclude prop (not excludED, but it points at another prop to be excluded).
|
||||
SPROP_XYZE = (1 << 7), // Use XYZ/Exponent encoding for vectors.
|
||||
SPROP_INSIDEARRAY = (1 << 8), // This tells us that the property is inside an array, so it shouldn't be put into the
|
||||
// flattened property list. Its array will point at it when it needs to.
|
||||
SPROP_PROXY_ALWAYS_YES = (1 << 9), // Set for datatable props using one of the default datatable proxies like
|
||||
// SendProxy_DataTableToDataTable that always send the data to all clients.
|
||||
SPROP_CHANGES_OFTEN = (1 << 10), // this is an often changed field, moved to head of sendtable so it gets a small index
|
||||
SPROP_IS_A_VECTOR_ELEM = (1 << 11), // Set automatically if SPROP_VECTORELEM is used.
|
||||
SPROP_COLLAPSIBLE = (1 << 12), // Set automatically if it's a datatable with an offset of 0 that doesn't change the pointer
|
||||
// (ie: for all automatically-chained base classes).
|
||||
// In this case, it can get rid of this SendPropDataTable altogether and spare the
|
||||
// trouble of walking the hierarchy more than necessary.
|
||||
SPROP_COORD_MP = (1 << 13), // Like SPROP_COORD, but special handling for multiplayer games
|
||||
SPROP_COORD_MP_LOWPRECISION = (1 << 14), // Like SPROP_COORD, but special handling for multiplayer games
|
||||
// where the fractional component only gets a 3 bits instead of 5
|
||||
SPROP_COORD_MP_INTEGRAL = (1 << 15), // SPROP_COORD_MP, but coordinates are rounded to integral boundaries
|
||||
SPROP_VARINT = (1 << 5),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,55 +1,35 @@
|
|||
import {SendPropDefinition, SendPropType, SendPropFlag} from './SendPropDefinition';
|
||||
import {SendPropDefinition, SendPropFlag, SendPropType} from './SendPropDefinition';
|
||||
|
||||
export class SendTable {
|
||||
name: string;
|
||||
props: SendPropDefinition[];
|
||||
private _flattenedProps: SendPropDefinition[];
|
||||
public name: string;
|
||||
public props: SendPropDefinition[];
|
||||
private cachedFlattenedProps: SendPropDefinition[];
|
||||
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
this.props = [];
|
||||
this._flattenedProps = [];
|
||||
this.cachedFlattenedProps = [];
|
||||
}
|
||||
|
||||
addProp(prop) {
|
||||
public addProp(prop) {
|
||||
this.props.push(prop);
|
||||
}
|
||||
|
||||
private flatten() {
|
||||
let excludes: SendPropDefinition[] = this.excludes;
|
||||
let props: SendPropDefinition[] = [];
|
||||
this.getAllProps(excludes, props);
|
||||
|
||||
// sort often changed props before the others
|
||||
let start = 0;
|
||||
for (let i = 0; i < props.length; i++) {
|
||||
if (props[i].hasFlag(SendPropFlag.SPROP_CHANGES_OFTEN)) {
|
||||
if (i != start) {
|
||||
const temp = props[i];
|
||||
props[i] = props[start];
|
||||
props[start] = temp;
|
||||
}
|
||||
start++;
|
||||
}
|
||||
}
|
||||
this._flattenedProps = props;
|
||||
}
|
||||
|
||||
getAllProps(excludes: SendPropDefinition[], props: SendPropDefinition[]) {
|
||||
let localProps = [];
|
||||
public getAllProps(excludes: SendPropDefinition[], props: SendPropDefinition[]) {
|
||||
const localProps = [];
|
||||
this.getAllPropsIteratorProps(excludes, localProps, props);
|
||||
for (const localProp of localProps) {
|
||||
props.push(localProp);
|
||||
}
|
||||
}
|
||||
|
||||
getAllPropsIteratorProps(excludes: SendPropDefinition[], props: SendPropDefinition[], childProps: SendPropDefinition[]) {
|
||||
public getAllPropsIteratorProps(excludes: SendPropDefinition[], props: SendPropDefinition[], childProps: SendPropDefinition[]) {
|
||||
for (const prop of this.props) {
|
||||
if (prop.hasFlag(SendPropFlag.SPROP_EXCLUDE) || excludes.indexOf(prop) !== -1) {
|
||||
continue;
|
||||
}
|
||||
if (excludes.filter((exclude) => {
|
||||
return exclude.name == prop.name && exclude.excludeDTName == prop.ownerTableName;
|
||||
return exclude.name === prop.name && exclude.excludeDTName === prop.ownerTableName;
|
||||
}).length > 0) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -67,10 +47,10 @@ export class SendTable {
|
|||
}
|
||||
|
||||
get flattenedProps() {
|
||||
if (this._flattenedProps.length === 0) {
|
||||
if (this.cachedFlattenedProps.length === 0) {
|
||||
this.flatten();
|
||||
}
|
||||
return this._flattenedProps;
|
||||
return this.cachedFlattenedProps;
|
||||
}
|
||||
|
||||
get excludes() {
|
||||
|
|
@ -84,7 +64,24 @@ export class SendTable {
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private flatten() {
|
||||
const excludes: SendPropDefinition[] = this.excludes;
|
||||
const props: SendPropDefinition[] = [];
|
||||
this.getAllProps(excludes, props);
|
||||
|
||||
// sort often changed props before the others
|
||||
let start = 0;
|
||||
for (let i = 0; i < props.length; i++) {
|
||||
if (props[i].hasFlag(SendPropFlag.SPROP_CHANGES_OFTEN)) {
|
||||
if (i !== start) {
|
||||
const temp = props[i];
|
||||
props[i] = props[start];
|
||||
props[start] = temp;
|
||||
}
|
||||
start++;
|
||||
}
|
||||
}
|
||||
this.cachedFlattenedProps = props;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
export class ServerClass {
|
||||
id: number;
|
||||
name: string;
|
||||
dataTable: string;
|
||||
public id: number;
|
||||
public name: string;
|
||||
public dataTable: string;
|
||||
|
||||
constructor(id:number, name:string, dataTable:string) {
|
||||
constructor(id: number, name: string, dataTable: string) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.dataTable = dataTable;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import {BitStream} from "bit-buffer";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
export interface StringTable {
|
||||
name: string;
|
||||
entries: StringTableEntry[],
|
||||
entries: StringTableEntry[];
|
||||
maxEntries: number;
|
||||
fixedUserDataSize?: number;
|
||||
fixedUserDataSizeBits?: number;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
export interface UserInfo {
|
||||
name: string
|
||||
name: string;
|
||||
userId: number;
|
||||
steamId: string;
|
||||
entityId: number;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
export class Vector {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
public x: number;
|
||||
public y: number;
|
||||
public z: number;
|
||||
|
||||
constructor(x, y, z) {
|
||||
this.x = x;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import {Vector} from "./Vector";
|
||||
import {Vector} from './Vector';
|
||||
export interface World {
|
||||
boundaryMin: Vector;
|
||||
boundaryMax: Vector;
|
||||
|
|
|
|||
53
src/Demo.ts
53
src/Demo.ts
|
|
@ -1,51 +1,40 @@
|
|||
import {Stream} from "stream";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {Parser} from './Parser';
|
||||
import {StreamParser} from './StreamParser';
|
||||
import {PacketType} from "./Parser/Message/Packet";
|
||||
import {PacketType} from './Parser/Message/Packet';
|
||||
import {StreamDemo} from './StreamDemo';
|
||||
|
||||
export {StreamDemo} from './StreamDemo';
|
||||
|
||||
export class Demo {
|
||||
stream: BitStream;
|
||||
parser: Parser|null;
|
||||
|
||||
constructor(arrayBuffer: ArrayBuffer) {
|
||||
this.stream = new BitStream(arrayBuffer);
|
||||
}
|
||||
|
||||
getParser(fastMode: boolean = false) {
|
||||
if (!this.parser) {
|
||||
const skippedPackets = fastMode ? [
|
||||
PacketType.packetEntities,
|
||||
PacketType.tempEntities,
|
||||
PacketType.entityMessage,
|
||||
] : [];
|
||||
this.parser = new Parser(this.stream, skippedPackets);
|
||||
}
|
||||
return this.parser;
|
||||
}
|
||||
|
||||
static fromNodeBuffer(nodeBuffer) {
|
||||
public static fromNodeBuffer(nodeBuffer) {
|
||||
const arrayBuffer = new ArrayBuffer(nodeBuffer.length);
|
||||
const view = new Uint8Array(arrayBuffer);
|
||||
const view = new Uint8Array(arrayBuffer);
|
||||
for (let i = 0; i < nodeBuffer.length; ++i) {
|
||||
view[i] = nodeBuffer[i];
|
||||
}
|
||||
return new Demo(arrayBuffer);
|
||||
}
|
||||
|
||||
static fromNodeStream(nodeStream) {
|
||||
public static fromNodeStream(nodeStream) {
|
||||
return new StreamDemo(nodeStream);
|
||||
}
|
||||
}
|
||||
|
||||
export class StreamDemo {
|
||||
stream: Stream;
|
||||
public stream: BitStream;
|
||||
public parser: Parser | null;
|
||||
|
||||
constructor(nodeStream: Stream) {
|
||||
this.stream = nodeStream;
|
||||
constructor(arrayBuffer: ArrayBuffer) {
|
||||
this.stream = new BitStream(arrayBuffer);
|
||||
}
|
||||
|
||||
getParser() {
|
||||
return new StreamParser(this.stream);
|
||||
public getParser(fastMode: boolean = false) {
|
||||
if (!this.parser) {
|
||||
const skippedPackets = fastMode ? [
|
||||
PacketType.packetEntities,
|
||||
PacketType.tempEntities,
|
||||
PacketType.entityMessage,
|
||||
] : [];
|
||||
this.parser = new Parser(this.stream, skippedPackets);
|
||||
}
|
||||
return this.parser;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
export function logBase2(num: number): number {
|
||||
let result = 0;
|
||||
while ((num >>= 1) != 0) {
|
||||
num >>= 1;
|
||||
while (num !== 0) {
|
||||
result++;
|
||||
num >>= 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import {DataTablePacket} from "../Data/Packet";
|
||||
import {Match} from "../Data/Match";
|
||||
import {Match} from '../Data/Match';
|
||||
import {DataTablePacket} from '../Data/Packet';
|
||||
|
||||
export function handleDataTable(packet: DataTablePacket, match: Match) {
|
||||
match.sendTables = packet.tables;
|
||||
|
|
|
|||
|
|
@ -1,66 +1,82 @@
|
|||
import {GameEventPacket} from "../Data/Packet";
|
||||
import {Match} from "../Data/Match";
|
||||
import {DeathEventValues, RoundWinEventValues, PlayerSpawnEventValues, ObjectDestroyedValues} from "../Data/GameEvent";
|
||||
import {DeathEventValues, ObjectDestroyedValues, PlayerSpawnEventValues, RoundWinEventValues} from '../Data/GameEvent';
|
||||
import {Match} from '../Data/Match';
|
||||
import {GameEventPacket} from '../Data/Packet';
|
||||
|
||||
export function handleGameEvent(packet: GameEventPacket, match: Match) {
|
||||
switch (packet.event.name) {
|
||||
case 'player_death': {
|
||||
const values = <DeathEventValues>packet.event.values;
|
||||
while (values.assister > 256 && values.assister < (1024 * 16)) {
|
||||
values.assister -= 256;
|
||||
}
|
||||
const assister = values.assister < 256 ? values.assister : null;
|
||||
// todo get player names, not same id as the name string table (entity id?)
|
||||
while (values.attacker > 256) {
|
||||
values.attacker -= 256;
|
||||
}
|
||||
while (values.userid > 256) {
|
||||
values.userid -= 256;
|
||||
}
|
||||
match.deaths.push({
|
||||
killer: values.attacker,
|
||||
assister: assister,
|
||||
victim: values.userid,
|
||||
weapon: values.weapon,
|
||||
tick: match.tick
|
||||
});
|
||||
}
|
||||
case 'player_death':
|
||||
handlePlayerDeath(packet, match);
|
||||
break;
|
||||
case 'teamplay_round_win': {
|
||||
const values = <RoundWinEventValues>packet.event.values;
|
||||
if (values.winreason !== 6) {// 6 = timelimit
|
||||
match.rounds.push({
|
||||
winner: values.team === 2 ? 'red' : 'blue',
|
||||
length: values.round_time,
|
||||
end_tick: match.tick
|
||||
});
|
||||
}
|
||||
}
|
||||
case 'teamplay_round_win':
|
||||
handleRoundWin(packet, match);
|
||||
break;
|
||||
case 'player_spawn': {
|
||||
const values = <PlayerSpawnEventValues>packet.event.values;
|
||||
const userId = values.userid;
|
||||
const userState = match.getUserInfo(userId);
|
||||
const player = match.playerMap[userState.entityId];
|
||||
userState.team = values.team === 2 ? 'red' : 'blue';
|
||||
const classId = values.class;
|
||||
if (player) {
|
||||
player.classId = classId;
|
||||
player.team = values.team;
|
||||
}
|
||||
if (!userState.classes[classId]) {
|
||||
userState.classes[classId] = 0;
|
||||
}
|
||||
userState.classes[classId]++;
|
||||
}
|
||||
case 'player_spawn':
|
||||
handlePlayerSpawn(packet, match);
|
||||
break;
|
||||
case 'object_destroyed': {
|
||||
const values = <ObjectDestroyedValues>packet.event.values;
|
||||
delete match.buildings[values.index];
|
||||
}
|
||||
case 'object_destroyed':
|
||||
handleObjectDestroyed(packet, match);
|
||||
break;
|
||||
case 'teamplay_round_start':
|
||||
match.buildings = {};
|
||||
handleRoundStart(packet, match);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function handlePlayerDeath(packet: GameEventPacket, match: Match) {
|
||||
const values = packet.event.values as DeathEventValues;
|
||||
while (values.assister > 256 && values.assister < (1024 * 16)) {
|
||||
values.assister -= 256;
|
||||
}
|
||||
const assister = values.assister < 256 ? values.assister : null;
|
||||
// todo get player names, not same id as the name string table (entity id?)
|
||||
while (values.attacker > 256) {
|
||||
values.attacker -= 256;
|
||||
}
|
||||
while (values.userid > 256) {
|
||||
values.userid -= 256;
|
||||
}
|
||||
match.deaths.push({
|
||||
killer: values.attacker,
|
||||
assister,
|
||||
victim: values.userid,
|
||||
weapon: values.weapon,
|
||||
tick: match.tick,
|
||||
});
|
||||
}
|
||||
|
||||
function handleRoundWin(packet: GameEventPacket, match: Match) {
|
||||
const values = packet.event.values as RoundWinEventValues;
|
||||
if (values.winreason !== 6) {// 6 = timelimit
|
||||
match.rounds.push({
|
||||
winner: values.team === 2 ? 'red' : 'blue',
|
||||
length: values.round_time,
|
||||
end_tick: match.tick,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handlePlayerSpawn(packet: GameEventPacket, match: Match) {
|
||||
const values = packet.event.values as PlayerSpawnEventValues;
|
||||
const userId = values.userid;
|
||||
const userState = match.getUserInfo(userId);
|
||||
const player = match.playerMap[userState.entityId];
|
||||
userState.team = values.team === 2 ? 'red' : 'blue';
|
||||
const classId = values.class;
|
||||
if (player) {
|
||||
player.classId = classId;
|
||||
player.team = values.team;
|
||||
}
|
||||
if (!userState.classes[classId]) {
|
||||
userState.classes[classId] = 0;
|
||||
}
|
||||
userState.classes[classId]++;
|
||||
}
|
||||
|
||||
function handleObjectDestroyed(packet: GameEventPacket, match: Match) {
|
||||
const values = packet.event.values as ObjectDestroyedValues;
|
||||
delete match.buildings[values.index];
|
||||
}
|
||||
|
||||
function handleRoundStart(packet: GameEventPacket, match: Match) {
|
||||
match.buildings = {};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import {GameEventListPacket} from "../Data/Packet";
|
||||
import {Match} from "../Data/Match";
|
||||
import {Match} from '../Data/Match';
|
||||
import {GameEventListPacket} from '../Data/Packet';
|
||||
|
||||
export function handleGameEventList(packet: GameEventListPacket, match: Match) {
|
||||
match.eventDefinitions = packet.eventList;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import {PacketEntitiesPacket} from "../Data/Packet";
|
||||
import {Match} from "../Data/Match";
|
||||
import {PacketEntity, PVS} from "../Data/PacketEntity";
|
||||
import {Vector} from "../Data/Vector";
|
||||
import {Player, LifeState} from "../Data/Player";
|
||||
import {CWeaponMedigun, Weapon} from "../Data/Weapon";
|
||||
import {Building, Sentry, Dispenser, Teleporter} from "../Data/Building";
|
||||
import {SendProp} from "../Data/SendProp";
|
||||
import {Building, Dispenser, Sentry, Teleporter} from '../Data/Building';
|
||||
import {Match} from '../Data/Match';
|
||||
import {PacketEntitiesPacket} from '../Data/Packet';
|
||||
import {PacketEntity, PVS} from '../Data/PacketEntity';
|
||||
import {LifeState, Player} from '../Data/Player';
|
||||
import {SendProp} from '../Data/SendProp';
|
||||
import {Vector} from '../Data/Vector';
|
||||
import {CWeaponMedigun, Weapon} from '../Data/Weapon';
|
||||
|
||||
export function handlePacketEntities(packet: PacketEntitiesPacket, match: Match) {
|
||||
for (const removedEntityId of packet.removedEntities) {
|
||||
|
|
@ -29,8 +29,8 @@ function saveEntity(packetEntity: PacketEntity, match: Match) {
|
|||
function handleEntity(entity: PacketEntity, match: Match) {
|
||||
for (const prop of entity.props) {
|
||||
if (prop.definition.ownerTableName === 'DT_AttributeContainer' && prop.definition.name === 'm_hOuter') {
|
||||
if (!match.outerMap[<number>prop.value]) {
|
||||
match.outerMap[<number>prop.value] = entity.entityIndex;
|
||||
if (!match.outerMap[prop.value as number]) {
|
||||
match.outerMap[prop.value as number] = entity.entityIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -40,43 +40,43 @@ function handleEntity(entity: PacketEntity, match: Match) {
|
|||
if (!match.weaponMap[entity.entityIndex]) {
|
||||
match.weaponMap[entity.entityIndex] = {
|
||||
className: entity.serverClass.name,
|
||||
owner: <number>prop.value
|
||||
}
|
||||
owner: prop.value as number,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (entity.serverClass.name) {
|
||||
case 'CWorld':
|
||||
match.world.boundaryMin = <Vector>entity.getProperty('DT_WORLD', 'm_WorldMins').value;
|
||||
match.world.boundaryMax = <Vector>entity.getProperty('DT_WORLD', 'm_WorldMaxs').value;
|
||||
match.world.boundaryMin = entity.getProperty('DT_WORLD', 'm_WorldMins').value as Vector;
|
||||
match.world.boundaryMax = entity.getProperty('DT_WORLD', 'm_WorldMaxs').value as Vector;
|
||||
break;
|
||||
case 'CTFPlayer':
|
||||
/**
|
||||
"DT_TFPlayerScoringDataExclusive.m_iCaptures": 0,
|
||||
"DT_TFPlayerScoringDataExclusive.m_iDefenses": 0,
|
||||
"DT_TFPlayerScoringDataExclusive.m_iKills": 5,
|
||||
"DT_TFPlayerScoringDataExclusive.m_iDeaths": 17,
|
||||
"DT_TFPlayerScoringDataExclusive.m_iSuicides": 7,
|
||||
"DT_TFPlayerScoringDataExclusive.m_iDominations": 0,
|
||||
"DT_TFPlayerScoringDataExclusive.m_iRevenge": 0,
|
||||
"DT_TFPlayerScoringDataExclusive.m_iBuildingsBuilt": 0,
|
||||
"DT_TFPlayerScoringDataExclusive.m_iBuildingsDestroyed": 0,
|
||||
"DT_TFPlayerScoringDataExclusive.m_iHeadshots": 0,
|
||||
"DT_TFPlayerScoringDataExclusive.m_iBackstabs": 0,
|
||||
"DT_TFPlayerScoringDataExclusive.m_iHealPoints": 0,
|
||||
"DT_TFPlayerScoringDataExclusive.m_iInvulns": 0,
|
||||
"DT_TFPlayerScoringDataExclusive.m_iTeleports": 0,
|
||||
"DT_TFPlayerScoringDataExclusive.m_iDamageDone": 847,
|
||||
"DT_TFPlayerScoringDataExclusive.m_iCrits": 0,
|
||||
"DT_TFPlayerScoringDataExclusive.m_iResupplyPoints": 0,
|
||||
"DT_TFPlayerScoringDataExclusive.m_iKillAssists": 0,
|
||||
"DT_TFPlayerScoringDataExclusive.m_iBonusPoints": 0,
|
||||
"DT_TFPlayerScoringDataExclusive.m_iPoints": 6,
|
||||
"DT_TFPlayerSharedLocal.m_nDesiredDisguiseTeam": 0,
|
||||
"DT_TFPlayerSharedLocal.m_nDesiredDisguiseClass": 0,
|
||||
"DT_TFPlayerShared.m_iKillStreak": 0,
|
||||
"DT_TFPlayerShared.m_flCloakMeter": 100,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iCaptures": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iDefenses": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iKills": 5,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iDeaths": 17,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iSuicides": 7,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iDominations": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iRevenge": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iBuildingsBuilt": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iBuildingsDestroyed": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iHeadshots": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iBackstabs": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iHealPoints": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iInvulns": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iTeleports": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iDamageDone": 847,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iCrits": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iResupplyPoints": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iKillAssists": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iBonusPoints": 0,
|
||||
* "DT_TFPlayerScoringDataExclusive.m_iPoints": 6,
|
||||
* "DT_TFPlayerSharedLocal.m_nDesiredDisguiseTeam": 0,
|
||||
* "DT_TFPlayerSharedLocal.m_nDesiredDisguiseClass": 0,
|
||||
* "DT_TFPlayerShared.m_iKillStreak": 0,
|
||||
* "DT_TFPlayerShared.m_flCloakMeter": 100,
|
||||
*/
|
||||
|
||||
const player: Player = (match.playerMap[entity.entityIndex]) ?
|
||||
|
|
@ -90,44 +90,44 @@ function handleEntity(entity: PacketEntity, match: Match) {
|
|||
for (const prop of entity.props) {
|
||||
if (prop.definition.ownerTableName === 'm_hMyWeapons') {
|
||||
if (prop.value !== 2097151) {
|
||||
player.weaponIds[parseInt(prop.definition.name, 10)] = <number>prop.value;
|
||||
player.weaponIds[parseInt(prop.definition.name, 10)] = prop.value as number;
|
||||
}
|
||||
}
|
||||
if (prop.definition.ownerTableName === 'm_iAmmo') {
|
||||
if (prop.value !== null && prop.value > 0) {
|
||||
player.ammo[parseInt(prop.definition.name, 10)] = <number>prop.value;
|
||||
player.ammo[parseInt(prop.definition.name, 10)] = prop.value as number;
|
||||
}
|
||||
}
|
||||
const propName = prop.definition.ownerTableName + '.' + prop.definition.name;
|
||||
switch (propName) {
|
||||
case 'DT_BasePlayer.m_iHealth':
|
||||
player.health = <number>prop.value;
|
||||
player.health = prop.value as number;
|
||||
break;
|
||||
case 'DT_BasePlayer.m_iMaxHealth':
|
||||
player.maxHealth = <number>prop.value;
|
||||
player.maxHealth = prop.value as number;
|
||||
break;
|
||||
case 'DT_TFLocalPlayerExclusive.m_vecOrigin':
|
||||
player.position.x = (<Vector>prop.value).x;
|
||||
player.position.y = (<Vector>prop.value).y;
|
||||
player.position.x = (prop.value as Vector).x;
|
||||
player.position.y = (prop.value as Vector).y;
|
||||
break;
|
||||
case 'DT_TFNonLocalPlayerExclusive.m_vecOrigin':
|
||||
player.position.x = (<Vector>prop.value).x;
|
||||
player.position.y = (<Vector>prop.value).y;
|
||||
player.position.x = (prop.value as Vector).x;
|
||||
player.position.y = (prop.value as Vector).y;
|
||||
break;
|
||||
case 'DT_TFLocalPlayerExclusive.m_vecOrigin[2]':
|
||||
player.position.z = <number>prop.value;
|
||||
player.position.z = prop.value as number;
|
||||
break;
|
||||
case 'DT_TFNonLocalPlayerExclusive.m_vecOrigin[2]':
|
||||
player.position.z = <number>prop.value;
|
||||
player.position.z = prop.value as number;
|
||||
break;
|
||||
case 'DT_TFNonLocalPlayerExclusive.m_angEyeAngles[1]':
|
||||
player.viewAngle = <number>prop.value;
|
||||
player.viewAngle = prop.value as number;
|
||||
break;
|
||||
case 'DT_TFLocalPlayerExclusive.m_angEyeAngles[1]':
|
||||
player.viewAngle = <number>prop.value;
|
||||
player.viewAngle = prop.value as number;
|
||||
break;
|
||||
case 'DT_BasePlayer.m_lifeState':
|
||||
player.lifeState = <number>prop.value;
|
||||
player.lifeState = prop.value as number;
|
||||
break;
|
||||
case 'DT_BaseCombatCharacter.m_hActiveWeapon':
|
||||
for (let i = 0; i < player.weapons.length; i++) {
|
||||
|
|
@ -139,7 +139,7 @@ function handleEntity(entity: PacketEntity, match: Match) {
|
|||
}
|
||||
break;
|
||||
case 'CWeaponMedigun':
|
||||
const weapon = <CWeaponMedigun>match.weaponMap[entity.entityIndex];
|
||||
const weapon = match.weaponMap[entity.entityIndex] as CWeaponMedigun;
|
||||
if (!weapon) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -147,27 +147,27 @@ function handleEntity(entity: PacketEntity, match: Match) {
|
|||
const propName = prop.definition.ownerTableName + '.' + prop.definition.name;
|
||||
switch (propName) {
|
||||
case 'DT_WeaponMedigun.m_hHealingTarget':
|
||||
weapon.healTarget = <number>prop.value;
|
||||
weapon.healTarget = prop.value as number;
|
||||
break;
|
||||
case 'DT_TFWeaponMedigunDataNonLocal.m_flChargeLevel':
|
||||
weapon.chargeLevel = <number>prop.value;
|
||||
weapon.chargeLevel = prop.value as number;
|
||||
break;
|
||||
case 'DT_LocalTFWeaponMedigunData.m_flChargeLevel':
|
||||
weapon.chargeLevel = <number>prop.value;
|
||||
weapon.chargeLevel = prop.value as number;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case'CTFTeam':
|
||||
try {
|
||||
const teamId = <number>entity.getProperty('DT_Team', 'm_iTeamNum').value;
|
||||
const teamId = entity.getProperty('DT_Team', 'm_iTeamNum').value as number;
|
||||
if (!match.teams[teamId]) {
|
||||
match.teams[teamId] = {
|
||||
name: <string>entity.getProperty('DT_Team', 'm_szTeamname').value,
|
||||
score: <number>entity.getProperty('DT_Team', 'm_iScore').value,
|
||||
roundsWon: <number>entity.getProperty('DT_Team', 'm_iRoundsWon').value,
|
||||
players: <number[]>entity.getProperty('DT_Team', '"player_array"').value,
|
||||
teamNumber: <number>teamId
|
||||
name: entity.getProperty('DT_Team', 'm_szTeamname').value as string,
|
||||
score: entity.getProperty('DT_Team', 'm_iScore').value as number,
|
||||
roundsWon: entity.getProperty('DT_Team', 'm_iRoundsWon').value as number,
|
||||
players: entity.getProperty('DT_Team', '"player_array"').value as number[],
|
||||
teamNumber: teamId as number,
|
||||
};
|
||||
match.teamMap[entity.entityIndex] = match.teams[teamId];
|
||||
}
|
||||
|
|
@ -177,16 +177,16 @@ function handleEntity(entity: PacketEntity, match: Match) {
|
|||
const propName = prop.definition.ownerTableName + '.' + prop.definition.name;
|
||||
switch (propName) {
|
||||
case 'DT_Team.m_iScore':
|
||||
team.score = <number>prop.value;
|
||||
team.score = prop.value as number;
|
||||
break;
|
||||
case 'DT_Team.m_szTeamname':
|
||||
team.name = <string>prop.value;
|
||||
team.name = prop.value as string;
|
||||
break;
|
||||
case 'DT_Team.m_iRoundsWon':
|
||||
team.roundsWon = <number>prop.value;
|
||||
team.roundsWon = prop.value as number;
|
||||
break;
|
||||
case 'DT_Team."player_array"':
|
||||
team.players = <number[]>prop.value;
|
||||
team.players = prop.value as number[];
|
||||
break;
|
||||
|
||||
}
|
||||
|
|
@ -212,34 +212,34 @@ function handleEntity(entity: PacketEntity, match: Match) {
|
|||
shieldLevel: 0,
|
||||
isMini: false,
|
||||
team: 0,
|
||||
angle: 0
|
||||
angle: 0,
|
||||
};
|
||||
}
|
||||
const sentry = <Sentry>match.buildings[entity.entityIndex];
|
||||
const sentry = match.buildings[entity.entityIndex] as Sentry;
|
||||
for (const prop of entity.props) {
|
||||
const propName = prop.definition.ownerTableName + '.' + prop.definition.name;
|
||||
applyBuildingProp(sentry, prop, propName);
|
||||
switch (propName) {
|
||||
case 'DT_ObjectSentrygun.m_bPlayerControlled':
|
||||
sentry.playerControlled = <number>prop.value > 0;
|
||||
sentry.playerControlled = prop.value as number > 0;
|
||||
break;
|
||||
case 'DT_ObjectSentrygun.m_hAutoAimTarget':
|
||||
sentry.autoAimTarget = <number>prop.value;
|
||||
sentry.autoAimTarget = prop.value as number;
|
||||
break;
|
||||
case 'DT_ObjectSentrygun.m_nShieldLevel':
|
||||
sentry.shieldLevel = <number>prop.value;
|
||||
sentry.shieldLevel = prop.value as number;
|
||||
break;
|
||||
case 'DT_ObjectSentrygun.m_iAmmoShells':
|
||||
sentry.ammoShells = <number>prop.value;
|
||||
sentry.ammoShells = prop.value as number;
|
||||
break;
|
||||
case 'DT_ObjectSentrygun.m_iAmmoRockets':
|
||||
sentry.ammoRockets = <number>prop.value;
|
||||
sentry.ammoRockets = prop.value as number;
|
||||
break;
|
||||
case 'DT_BaseObject.m_bMiniBuilding':
|
||||
sentry.isMini = <number>prop.value > 1;
|
||||
sentry.isMini = prop.value as number > 1;
|
||||
break;
|
||||
case 'DT_TFNonLocalPlayerExclusive.m_angEyeAngles[1]':
|
||||
sentry.angle = <number>prop.value;
|
||||
sentry.angle = prop.value as number;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -261,19 +261,19 @@ function handleEntity(entity: PacketEntity, match: Match) {
|
|||
team: 0,
|
||||
healing: [],
|
||||
metal: 0,
|
||||
angle: 0
|
||||
angle: 0,
|
||||
};
|
||||
}
|
||||
const dispenser = <Dispenser>match.buildings[entity.entityIndex];
|
||||
const dispenser = match.buildings[entity.entityIndex] as Dispenser;
|
||||
for (const prop of entity.props) {
|
||||
const propName = prop.definition.ownerTableName + '.' + prop.definition.name;
|
||||
applyBuildingProp(dispenser, prop, propName);
|
||||
switch (propName) {
|
||||
case 'DT_ObjectDispenser.m_iAmmoMetal':
|
||||
dispenser.metal = <number>prop.value;
|
||||
dispenser.metal = prop.value as number;
|
||||
break;
|
||||
case 'DT_ObjectDispenser."healing_array"':
|
||||
dispenser.healing = <number[]>prop.value;
|
||||
dispenser.healing = prop.value as number[];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -299,31 +299,31 @@ function handleEntity(entity: PacketEntity, match: Match) {
|
|||
rechargeDuration: 0,
|
||||
timesUsed: 0,
|
||||
angle: 0,
|
||||
yawToExit: 0
|
||||
yawToExit: 0,
|
||||
};
|
||||
}
|
||||
const teleporter = <Teleporter>match.buildings[entity.entityIndex];
|
||||
const teleporter = match.buildings[entity.entityIndex] as Teleporter;
|
||||
for (const prop of entity.props) {
|
||||
const propName = prop.definition.ownerTableName + '.' + prop.definition.name;
|
||||
applyBuildingProp(teleporter, prop, propName);
|
||||
switch (propName) {
|
||||
case 'DT_ObjectTeleporter.m_flRechargeTime':
|
||||
teleporter.rechargeTime = <number>prop.value;
|
||||
teleporter.rechargeTime = prop.value as number;
|
||||
break;
|
||||
case 'DT_ObjectTeleporter.m_flCurrentRechargeDuration':
|
||||
teleporter.rechargeDuration = <number>prop.value;
|
||||
teleporter.rechargeDuration = prop.value as number;
|
||||
break;
|
||||
case 'DT_ObjectTeleporter.m_iTimesUsed':
|
||||
teleporter.timesUsed = <number>prop.value;
|
||||
teleporter.timesUsed = prop.value as number;
|
||||
break;
|
||||
case 'DT_ObjectTeleporter.m_bMatchBuilding':
|
||||
teleporter.otherEnd = <number>prop.value;
|
||||
teleporter.otherEnd = prop.value as number;
|
||||
break;
|
||||
case 'DT_ObjectTeleporter.m_flYawToExit':
|
||||
teleporter.yawToExit = <number>prop.value;
|
||||
teleporter.yawToExit = prop.value as number;
|
||||
break;
|
||||
case 'DT_BaseObject.m_iObjectMode':
|
||||
teleporter.isEntrance = <number>prop.value === 0;
|
||||
teleporter.isEntrance = prop.value as number === 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -334,7 +334,7 @@ function handleEntity(entity: PacketEntity, match: Match) {
|
|||
case 'CTFPlayerResource':
|
||||
for (const prop of entity.props) {
|
||||
const playerId = parseInt(prop.definition.name, 10);
|
||||
const value = <number>prop.value;
|
||||
const value = prop.value as number;
|
||||
if (!match.playerResources[playerId]) {
|
||||
match.playerResources[playerId] = {
|
||||
alive: false,
|
||||
|
|
@ -359,7 +359,7 @@ function handleEntity(entity: PacketEntity, match: Match) {
|
|||
score: 0,
|
||||
team: 0,
|
||||
totalScore: 0,
|
||||
damage: 0
|
||||
damage: 0,
|
||||
};
|
||||
}
|
||||
const playerResource = match.playerResources[playerId];
|
||||
|
|
@ -458,31 +458,31 @@ function handleEntity(entity: PacketEntity, match: Match) {
|
|||
function applyBuildingProp(building: Building, prop: SendProp, propName: string) {
|
||||
switch (propName) {
|
||||
case 'DT_BaseObject.m_iUpgradeLevel':
|
||||
building.level = <number>prop.value;
|
||||
building.level = prop.value as number;
|
||||
break;
|
||||
case 'DT_BaseObject.m_hBuilder':
|
||||
building.builder = <number>prop.value;
|
||||
building.builder = prop.value as number;
|
||||
break;
|
||||
case 'DT_BaseObject.m_iMaxHealth':
|
||||
building.maxHealth = <number>prop.value;
|
||||
building.maxHealth = prop.value as number;
|
||||
break;
|
||||
case 'DT_BaseObject.m_iHealth':
|
||||
building.health = <number>prop.value;
|
||||
building.health = prop.value as number;
|
||||
break;
|
||||
case 'DT_BaseObject.m_bBuilding':
|
||||
building.isBuilding = <number>prop.value > 0;
|
||||
building.isBuilding = prop.value as number > 0;
|
||||
break;
|
||||
case 'DT_BaseObject.m_bHasSapper':
|
||||
building.isSapped = <number>prop.value > 0;
|
||||
building.isSapped = prop.value as number > 0;
|
||||
break;
|
||||
case 'DT_BaseEntity.m_vecOrigin':
|
||||
building.position = <Vector>prop.value;
|
||||
building.position = prop.value as Vector;
|
||||
break;
|
||||
case 'DT_BaseEntity.m_iTeamNum':
|
||||
building.team = <number>prop.value;
|
||||
building.team = prop.value as number;
|
||||
break;
|
||||
case 'DT_BaseEntity.m_angRotation':
|
||||
building.angle = (<Vector>prop.value).y;
|
||||
building.angle = (prop.value as Vector).y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import {SayText2Packet} from "../Data/Packet";
|
||||
import {Match} from "../Data/Match";
|
||||
import {Match} from '../Data/Match';
|
||||
import {SayText2Packet} from '../Data/Packet';
|
||||
|
||||
export function handleSayText2(packet: SayText2Packet, match: Match) {
|
||||
match.chat.push({
|
||||
kind: packet.kind,
|
||||
from: packet.from,
|
||||
text: packet.text,
|
||||
tick: match.tick
|
||||
tick: match.tick,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import {StringTablePacket} from "../Data/Packet";
|
||||
import {Match} from "../Data/Match";
|
||||
import {StringTableEntry} from "../Data/StringTable";
|
||||
import {Match} from '../Data/Match';
|
||||
import {StringTablePacket} from '../Data/Packet';
|
||||
import {StringTableEntry} from '../Data/StringTable';
|
||||
|
||||
export function handleStringTable(packet: StringTablePacket, match: Match) {
|
||||
for (const table of packet.tables) {
|
||||
|
|
|
|||
|
|
@ -1,48 +1,32 @@
|
|||
import {Packet, PacketType} from './Parser/Message/Packet';
|
||||
import {ConsoleCmd} from './Parser/Message/ConsoleCmd';
|
||||
import {StringTable} from './Parser/Message/StringTable';
|
||||
import {DataTable} from './Parser/Message/DataTable';
|
||||
import {UserCmd} from './Parser/Message/UserCmd';
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {EventEmitter} from 'events';
|
||||
import {Header} from './Data/Header';
|
||||
import {Match} from './Data/Match';
|
||||
import {ConsoleCmd} from './Parser/Message/ConsoleCmd';
|
||||
import {DataTable} from './Parser/Message/DataTable';
|
||||
import {Packet, PacketType} from './Parser/Message/Packet';
|
||||
import {Parser as MessageParser} from './Parser/Message/Parser';
|
||||
import {Header} from "./Data/Header";
|
||||
import {StringTable} from './Parser/Message/StringTable';
|
||||
import {UserCmd} from './Parser/Message/UserCmd';
|
||||
|
||||
export class Parser extends EventEmitter {
|
||||
stream: BitStream;
|
||||
match: Match;
|
||||
skipPackets: PacketType[];
|
||||
public stream: BitStream;
|
||||
public match: Match;
|
||||
protected skipPackets: PacketType[];
|
||||
|
||||
constructor(stream: BitStream, skipPackets: PacketType[] = []) {
|
||||
super();
|
||||
this.stream = stream;
|
||||
this.match = new Match();
|
||||
this.match = new Match();
|
||||
this.on('packet', this.match.handlePacket.bind(this.match));
|
||||
this.skipPackets = skipPackets;
|
||||
}
|
||||
|
||||
readHeader() {
|
||||
public readHeader() {
|
||||
return this.parseHeader(this.stream);
|
||||
}
|
||||
|
||||
parseHeader(stream): Header {
|
||||
return {
|
||||
'type': stream.readASCIIString(8),
|
||||
'version': stream.readInt32(),
|
||||
'protocol': stream.readInt32(),
|
||||
'server': stream.readASCIIString(260),
|
||||
'nick': stream.readASCIIString(260),
|
||||
'map': stream.readASCIIString(260),
|
||||
'game': stream.readASCIIString(260),
|
||||
'duration': stream.readFloat32(),
|
||||
'ticks': stream.readInt32(),
|
||||
'frames': stream.readInt32(),
|
||||
'sigon': stream.readInt32()
|
||||
}
|
||||
}
|
||||
|
||||
parseBody() {
|
||||
public parseBody() {
|
||||
let hasNext = true;
|
||||
while (hasNext) {
|
||||
hasNext = this.tick();
|
||||
|
|
@ -51,7 +35,7 @@ export class Parser extends EventEmitter {
|
|||
return this.match;
|
||||
}
|
||||
|
||||
tick() {
|
||||
public tick() {
|
||||
const message = this.readMessage(this.stream, this.match);
|
||||
if (message instanceof MessageParser) {
|
||||
this.handleMessage(message);
|
||||
|
|
@ -59,7 +43,7 @@ export class Parser extends EventEmitter {
|
|||
return !!message;
|
||||
}
|
||||
|
||||
parseMessage(data: BitStream, type: MessageType, tick: number, length: number, match: Match): MessageParser {
|
||||
protected parseMessage(data: BitStream, type: MessageType, tick: number, length: number, match: Match): MessageParser {
|
||||
|
||||
switch (type) {
|
||||
case MessageType.Sigon:
|
||||
|
|
@ -74,15 +58,30 @@ export class Parser extends EventEmitter {
|
|||
case MessageType.StringTables:
|
||||
return new StringTable(type, tick, data, length, match, this.skipPackets);
|
||||
default:
|
||||
throw new Error("unknown message type");
|
||||
throw new Error('unknown message type');
|
||||
}
|
||||
}
|
||||
|
||||
handleMessage(message: MessageParser) {
|
||||
protected parseHeader(stream): Header {
|
||||
return {
|
||||
type: stream.readASCIIString(8),
|
||||
version: stream.readInt32(),
|
||||
protocol: stream.readInt32(),
|
||||
server: stream.readASCIIString(260),
|
||||
nick: stream.readASCIIString(260),
|
||||
map: stream.readASCIIString(260),
|
||||
game: stream.readASCIIString(260),
|
||||
duration: stream.readFloat32(),
|
||||
ticks: stream.readInt32(),
|
||||
frames: stream.readInt32(),
|
||||
sigon: stream.readInt32(),
|
||||
};
|
||||
}
|
||||
|
||||
protected handleMessage(message: MessageParser) {
|
||||
if (message.parse) {
|
||||
const packets = message.parse();
|
||||
for (let i = 0; i < packets.length; i++) {
|
||||
const packet = packets[i];
|
||||
for (const packet of packets) {
|
||||
if (packet) {
|
||||
this.emit('packet', packet);
|
||||
}
|
||||
|
|
@ -90,7 +89,7 @@ export class Parser extends EventEmitter {
|
|||
}
|
||||
}
|
||||
|
||||
readMessage(stream: BitStream, match: Match): MessageParser|boolean {
|
||||
protected readMessage(stream: BitStream, match: Match): MessageParser | boolean {
|
||||
if (stream.bitsLeft < 8) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -100,8 +99,8 @@ export class Parser extends EventEmitter {
|
|||
}
|
||||
const tick = stream.readInt32();
|
||||
|
||||
let viewOrigin: number[][] = [];
|
||||
let viewAngles: number[][] = [];
|
||||
const viewOrigin: number[][] = [];
|
||||
const viewAngles: number[][] = [];
|
||||
|
||||
switch (type) {
|
||||
case MessageType.Sigon:
|
||||
|
|
@ -137,12 +136,12 @@ export class Parser extends EventEmitter {
|
|||
}
|
||||
|
||||
export enum MessageType {
|
||||
Sigon = 1,
|
||||
Packet = 2,
|
||||
SyncTick = 3,
|
||||
ConsoleCmd = 4,
|
||||
UserCmd = 5,
|
||||
DataTables = 6,
|
||||
Stop = 7,
|
||||
StringTables = 8
|
||||
Sigon = 1,
|
||||
Packet = 2,
|
||||
SyncTick = 3,
|
||||
ConsoleCmd = 4,
|
||||
UserCmd = 5,
|
||||
DataTables = 6,
|
||||
Stop = 7,
|
||||
StringTables = 8,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,22 @@
|
|||
import {PacketEntity} from "../Data/PacketEntity";
|
||||
import {BitStream} from "bit-buffer";
|
||||
import {SendProp} from "../Data/SendProp";
|
||||
import {SendPropParser} from "./SendPropParser";
|
||||
import {readUBitVar} from "./readBitVar";
|
||||
import {SendTable} from "../Data/SendTable";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {PacketEntity} from '../Data/PacketEntity';
|
||||
import {SendProp} from '../Data/SendProp';
|
||||
import {SendTable} from '../Data/SendTable';
|
||||
import {readUBitVar} from './readBitVar';
|
||||
import {SendPropParser} from './SendPropParser';
|
||||
|
||||
export function applyEntityUpdate(entity: PacketEntity, sendTable: SendTable, stream: BitStream): PacketEntity {
|
||||
let index = -1;
|
||||
let index = -1;
|
||||
const allProps = sendTable.flattenedProps;
|
||||
while ((index = readFieldIndex(stream, index)) != -1) {
|
||||
index = readFieldIndex(stream, index);
|
||||
while (index !== -1) {
|
||||
if (index >= 4096 || index > allProps.length) {
|
||||
throw new Error('prop index out of bounds while applying update for ' + sendTable.name + ' got ' + index
|
||||
+ ' property only has ' + allProps.length + ' properties');
|
||||
}
|
||||
|
||||
const propDefinition = allProps[index];
|
||||
const existingProp = entity.getPropByDefinition(propDefinition);
|
||||
const existingProp = entity.getPropByDefinition(propDefinition);
|
||||
|
||||
const prop = existingProp ? existingProp : new SendProp(propDefinition);
|
||||
prop.value = SendPropParser.decode(propDefinition, stream);
|
||||
|
|
@ -23,14 +24,16 @@ export function applyEntityUpdate(entity: PacketEntity, sendTable: SendTable, st
|
|||
if (!existingProp) {
|
||||
entity.props.push(prop);
|
||||
}
|
||||
|
||||
index = readFieldIndex(stream, index);
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
const readFieldIndex = function (stream: BitStream, lastIndex: number): number {
|
||||
function readFieldIndex(stream: BitStream, lastIndex: number): number {
|
||||
if (!stream.readBoolean()) {
|
||||
return -1;
|
||||
}
|
||||
const diff = readUBitVar(stream);
|
||||
return lastIndex + diff + 1;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import {ConsoleCmdPacket} from "../../Data/Packet";
|
||||
import {ConsoleCmdPacket} from '../../Data/Packet';
|
||||
import {Parser} from './Parser';
|
||||
|
||||
export class ConsoleCmd extends Parser {
|
||||
parse(): ConsoleCmdPacket[] {
|
||||
public parse(): ConsoleCmdPacket[] {
|
||||
return [{
|
||||
packetType: 'consoleCmd',
|
||||
command: this.stream.readUTF8String()
|
||||
command: this.stream.readUTF8String(),
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
import {SendTable} from '../../Data/SendTable';
|
||||
import {DataTablePacket} from '../../Data/Packet';
|
||||
import {SendPropDefinition, SendPropFlag, SendPropType} from '../../Data/SendPropDefinition';
|
||||
import {SendTable} from '../../Data/SendTable';
|
||||
import {ServerClass} from '../../Data/ServerClass';
|
||||
import {Parser} from './Parser';
|
||||
import {DataTablePacket} from "../../Data/Packet";
|
||||
|
||||
export class DataTable extends Parser {
|
||||
parse(): DataTablePacket[] {
|
||||
public parse(): DataTablePacket[] {
|
||||
// https://github.com/LestaD/SourceEngine2007/blob/43a5c90a5ada1e69ca044595383be67f40b33c61/src_main/engine/dt_common_eng.cpp#L356
|
||||
// https://github.com/LestaD/SourceEngine2007/blob/43a5c90a5ada1e69ca044595383be67f40b33c61/src_main/engine/dt_recv_eng.cpp#L310
|
||||
// https://github.com/PazerOP/DemoLib/blob/master/DemoLib/Commands/DemoDataTablesCommand.cs
|
||||
let tables: SendTable[] = [];
|
||||
let i, j;
|
||||
const tables: SendTable[] = [];
|
||||
const tableMap: {[key: string]: SendTable} = {};
|
||||
while (this.stream.readBoolean()) {
|
||||
const needsDecoder = this.stream.readBoolean();
|
||||
|
|
@ -20,7 +19,7 @@ export class DataTable extends Parser {
|
|||
|
||||
// get props metadata
|
||||
let arrayElementProp;
|
||||
for (i = 0; i < numProps; i++) {
|
||||
for (let i = 0; i < numProps; i++) {
|
||||
const propType = this.stream.readBits(5);
|
||||
const propName = this.stream.readASCIIString();
|
||||
const nFlagsBits = 16; // might be 11 (old?), 13 (new?), 16(networked) or 17(??)
|
||||
|
|
@ -52,7 +51,7 @@ export class DataTable extends Parser {
|
|||
|
||||
if (arrayElementProp) {
|
||||
if (prop.type !== SendPropType.DPT_Array) {
|
||||
throw "expected prop of type array";
|
||||
throw new Error('expected prop of type array');
|
||||
}
|
||||
prop.arrayProperty = arrayElementProp;
|
||||
arrayElementProp = null;
|
||||
|
|
@ -60,10 +59,10 @@ export class DataTable extends Parser {
|
|||
|
||||
if (prop.hasFlag(SendPropFlag.SPROP_INSIDEARRAY)) {
|
||||
if (arrayElementProp) {
|
||||
throw new Error("array element already set");
|
||||
throw new Error('array element already set');
|
||||
}
|
||||
if (prop.hasFlag(SendPropFlag.SPROP_CHANGES_OFTEN)) {
|
||||
throw new Error("unexpected CHANGES_OFTEN prop in array");
|
||||
throw new Error('unexpected CHANGES_OFTEN prop in array');
|
||||
}
|
||||
arrayElementProp = prop;
|
||||
} else {
|
||||
|
|
@ -89,13 +88,13 @@ export class DataTable extends Parser {
|
|||
const numServerClasses = this.stream.readUint16(); // short
|
||||
const serverClasses: ServerClass[] = [];
|
||||
if (numServerClasses <= 0) {
|
||||
throw "expected one or more serverclasses";
|
||||
throw new Error('expected one or more serverclasses');
|
||||
}
|
||||
|
||||
for (i = 0; i < numServerClasses; i++) {
|
||||
for (let i = 0; i < numServerClasses; i++) {
|
||||
const classId = this.stream.readUint16();
|
||||
if (classId > numServerClasses) {
|
||||
throw "invalid class id";
|
||||
throw new Error('invalid class id');
|
||||
}
|
||||
const className = this.stream.readASCIIString();
|
||||
const dataTable = this.stream.readASCIIString();
|
||||
|
|
@ -104,14 +103,13 @@ export class DataTable extends Parser {
|
|||
|
||||
const bitsLeft = (this.length * 8) - this.stream.index;
|
||||
if (bitsLeft > 7 || bitsLeft < 0) {
|
||||
throw "unexpected remaining data in datatable (" + bitsLeft + " bits)";
|
||||
throw new Error('unexpected remaining data in datatable (' + bitsLeft + ' bits)');
|
||||
}
|
||||
|
||||
|
||||
return [{
|
||||
packetType: 'dataTable',
|
||||
tables: tables,
|
||||
serverClasses: serverClasses
|
||||
tables,
|
||||
serverClasses,
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,25 @@
|
|||
import * as ParserGenerator from '../Packet/ParserGenerator';
|
||||
|
||||
import {Parser} from './Parser';
|
||||
import {BSPDecal} from '../Packet/BSPDecal';
|
||||
import {ClassInfo} from '../Packet/ClassInfo';
|
||||
import {CmdKeyValues} from '../Packet/CmdKeyValues';
|
||||
import {CreateStringTable} from '../Packet/CreateStringTable';
|
||||
import {EntityMessage} from '../Packet/EntityMessage';
|
||||
import {GameEvent} from '../Packet/GameEvent';
|
||||
import {GameEventList} from '../Packet/GameEventList';
|
||||
import {Menu} from '../Packet/Menu';
|
||||
import {PacketEntities} from '../Packet/PacketEntities';
|
||||
import {PacketParserMap} from '../Packet/Parser';
|
||||
import {ParseSounds} from '../Packet/ParseSounds';
|
||||
import {SetConVar} from '../Packet/SetConVar';
|
||||
import {TempEntities} from '../Packet/TempEntities';
|
||||
import {UpdateStringTable} from '../Packet/UpdateStringTable';
|
||||
import {UserMessage} from '../Packet/UserMessage';
|
||||
import {PacketParserMap} from '../Packet/Parser';
|
||||
import {TempEntities} from '../Packet/TempEntities';
|
||||
import {VoiceInit} from '../Packet/VoiceInit';
|
||||
import {VoiceData} from '../Packet/VoiceData';
|
||||
import {Menu} from '../Packet/Menu';
|
||||
import {CmdKeyValues} from '../Packet/CmdKeyValues';
|
||||
|
||||
import {GameEventDefinitionMap} from "../../Data/GameEvent";
|
||||
import {VoiceInit} from '../Packet/VoiceInit';
|
||||
import {Parser} from './Parser';
|
||||
|
||||
import {GameEventDefinitionMap} from '../../Data/GameEvent';
|
||||
|
||||
import {Packet as IPacket} from '../../Data/Packet';
|
||||
|
||||
|
|
@ -30,30 +29,7 @@ import {Packet as IPacket} from '../../Data/Packet';
|
|||
// https://github.com/LestaD/SourceEngine2007/blob/master/src_main/common/netmessages.cpp
|
||||
|
||||
export class Packet extends Parser {
|
||||
parse() {
|
||||
let packets: IPacket[] = [];
|
||||
let lastPacketType = 0;
|
||||
while (this.bitsLeft > 6) { // last 6 bits for NOOP
|
||||
const type = this.stream.readBits(6);
|
||||
if (type !== 0) {
|
||||
if (Packet.parsers[type]) {
|
||||
const skip = this.skippedPackets.indexOf(type) !== -1;
|
||||
const packet = Packet.parsers[type].call(this, this.stream, this.match, skip);
|
||||
packets.push(packet);
|
||||
} else {
|
||||
throw new Error('Unknown packet type ' + type + " just parsed a " + PacketType[lastPacketType]);
|
||||
}
|
||||
lastPacketType = type;
|
||||
}
|
||||
}
|
||||
return packets;
|
||||
}
|
||||
|
||||
get bitsLeft() {
|
||||
return (this.length * 8) - this.stream.index;
|
||||
}
|
||||
|
||||
static parsers: PacketParserMap = {
|
||||
private static parsers: PacketParserMap = {
|
||||
2: ParserGenerator.make('file', 'transferId{32}fileName{s}requested{b}'),
|
||||
3: ParserGenerator.make('netTick', 'tick{32}frameTime{16}stdDev{16}'),
|
||||
4: ParserGenerator.make('stringCmd', 'command{s}'),
|
||||
|
|
@ -83,8 +59,31 @@ export class Packet extends Parser {
|
|||
29: Menu,
|
||||
30: GameEventList,
|
||||
31: ParserGenerator.make('getCvarValue', 'cookie{32}value{s}'),
|
||||
32: CmdKeyValues
|
||||
32: CmdKeyValues,
|
||||
};
|
||||
|
||||
public parse() {
|
||||
const packets: IPacket[] = [];
|
||||
let lastPacketType = 0;
|
||||
while (this.bitsLeft > 6) { // last 6 bits for NOOP
|
||||
const type = this.stream.readBits(6);
|
||||
if (type !== 0) {
|
||||
if (Packet.parsers[type]) {
|
||||
const skip = this.skippedPackets.indexOf(type) !== -1;
|
||||
const packet = Packet.parsers[type].call(this, this.stream, this.match, skip);
|
||||
packets.push(packet);
|
||||
} else {
|
||||
throw new Error('Unknown packet type ' + type + ' just parsed a ' + PacketType[lastPacketType]);
|
||||
}
|
||||
lastPacketType = type;
|
||||
}
|
||||
}
|
||||
return packets;
|
||||
}
|
||||
|
||||
get bitsLeft() {
|
||||
return (this.length * 8) - this.stream.index;
|
||||
}
|
||||
}
|
||||
|
||||
export enum PacketType {
|
||||
|
|
@ -114,5 +113,5 @@ export enum PacketType {
|
|||
menu = 29,
|
||||
gameEventList = 30,
|
||||
getCvarValue = 30,
|
||||
cmdKeyValues = 32
|
||||
cmdKeyValues = 32,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,25 @@
|
|||
import {BitStream} from 'bit-buffer';
|
||||
import {Match} from '../../Data/Match';
|
||||
import {Packet} from "../../Data/Packet";
|
||||
import {MessageType} from "../../Parser";
|
||||
import {PacketType} from "./Packet";
|
||||
import {Packet} from '../../Data/Packet';
|
||||
import {MessageType} from '../../Parser';
|
||||
import {PacketType} from './Packet';
|
||||
|
||||
export abstract class Parser {
|
||||
type: any;
|
||||
tick: number;
|
||||
stream: BitStream;
|
||||
length: number;
|
||||
match: Match;
|
||||
skippedPackets: PacketType[];
|
||||
protected type: any;
|
||||
protected tick: number;
|
||||
protected stream: BitStream;
|
||||
protected length: number;
|
||||
protected match: Match;
|
||||
protected skippedPackets: PacketType[];
|
||||
|
||||
constructor(type: MessageType, tick: number, stream: BitStream, length: number, match: Match, skippedPacket: PacketType[] = []) {
|
||||
this.type = type;
|
||||
this.tick = tick;
|
||||
this.stream = stream;
|
||||
this.length = length;//length in bytes
|
||||
this.length = length; // length in bytes
|
||||
this.match = match;
|
||||
this.skippedPackets = skippedPacket;
|
||||
}
|
||||
|
||||
abstract parse(): Packet[];
|
||||
public abstract parse(): Packet[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import {StringTablePacket} from '../../Data/Packet';
|
||||
import {StringTable as StringTableObject, StringTableEntry} from '../../Data/StringTable';
|
||||
import {Parser} from './Parser';
|
||||
import {StringTableEntry, StringTable as StringTableObject} from "../../Data/StringTable";
|
||||
import {StringTablePacket} from "../../Data/Packet";
|
||||
|
||||
export class StringTable extends Parser {
|
||||
parse(): StringTablePacket[] {
|
||||
public parse(): StringTablePacket[] {
|
||||
// we get the tables from the packets
|
||||
// return [{
|
||||
// packetType: 'stringTable',
|
||||
|
|
@ -11,22 +11,22 @@ export class StringTable extends Parser {
|
|||
// }];
|
||||
// https://github.com/StatsHelix/demoinfo/blob/3d28ea917c3d44d987b98bb8f976f1a3fcc19821/DemoInfo/ST/StringTableParser.cs
|
||||
const tableCount = this.stream.readUint8();
|
||||
let tables: StringTableObject[] = [];
|
||||
const tables: StringTableObject[] = [];
|
||||
let extraDataLength;
|
||||
for (let i = 0; i < tableCount; i++) {
|
||||
let entries: StringTableEntry[] = [];
|
||||
const entries: StringTableEntry[] = [];
|
||||
const tableName = this.stream.readASCIIString();
|
||||
const entryCount = this.stream.readUint16();
|
||||
for (let j = 0; j < entryCount; j++) {
|
||||
let entry: StringTableEntry;
|
||||
try {
|
||||
entry = {
|
||||
text: this.stream.readUTF8String()
|
||||
text: this.stream.readUTF8String(),
|
||||
};
|
||||
} catch (e) {
|
||||
return [{
|
||||
packetType: 'stringTable',
|
||||
tables: tables
|
||||
tables,
|
||||
}];
|
||||
}
|
||||
if (this.stream.readBoolean()) {
|
||||
|
|
@ -36,7 +36,7 @@ export class StringTable extends Parser {
|
|||
// seems to happen in POV demos after the MyM update
|
||||
return [{
|
||||
packetType: 'stringTable',
|
||||
tables: tables
|
||||
tables,
|
||||
}];
|
||||
}
|
||||
entry.extraData = this.stream.readBitStream(extraDataLength * 8);
|
||||
|
|
@ -46,13 +46,13 @@ export class StringTable extends Parser {
|
|||
const table: StringTableObject = {
|
||||
entries,
|
||||
name: tableName,
|
||||
maxEntries: entryCount
|
||||
maxEntries: entryCount,
|
||||
};
|
||||
tables.push(table);
|
||||
if (this.stream.readBits(1)) {
|
||||
this.stream.readASCIIString();
|
||||
if (this.stream.readBits(1)) {
|
||||
//throw 'more extra data not implemented';
|
||||
// throw 'more extra data not implemented';
|
||||
extraDataLength = this.stream.readBits(16);
|
||||
this.stream.readBits(extraDataLength);
|
||||
}
|
||||
|
|
@ -60,26 +60,7 @@ export class StringTable extends Parser {
|
|||
}
|
||||
return [{
|
||||
packetType: 'stringTable',
|
||||
tables: tables
|
||||
tables,
|
||||
}];
|
||||
}
|
||||
|
||||
readExtraData(length): string[] {
|
||||
const end = this.stream.index + (length * 8);
|
||||
let data: string[] = [];
|
||||
//console.log(this.stream.readUTF8String());
|
||||
data.push(this.stream.readUTF8String());
|
||||
while (this.stream.index < end && this.stream.index < (this.stream.length - 7)) { // -7 because we need a full byte
|
||||
try {
|
||||
let string = this.stream.readUTF8String();
|
||||
if (string) {
|
||||
data.push(string);
|
||||
}
|
||||
} catch (e) {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
this.stream.index = end;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import {Parser} from './Parser';
|
||||
|
||||
export class UserCmd extends Parser {
|
||||
parse() {
|
||||
public parse() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import {BSPDecalPacket} from "../../Data/Packet";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {Vector} from "../../Data/Vector";
|
||||
import {BSPDecalPacket} from '../../Data/Packet';
|
||||
import {Vector} from '../../Data/Vector';
|
||||
|
||||
const getCoord = function (stream) {
|
||||
const hasInt = !!stream.readBits(1);
|
||||
function getCoord(stream) {
|
||||
const hasInt = !!stream.readBits(1);
|
||||
const hasFract = !!stream.readBits(1);
|
||||
let value = 0;
|
||||
let value = 0;
|
||||
if (hasInt || hasFract) {
|
||||
const sign = !!stream.readBits(1);
|
||||
if (hasInt) {
|
||||
|
|
@ -19,34 +19,35 @@ const getCoord = function (stream) {
|
|||
}
|
||||
}
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
const getVecCoord = function (stream): Vector {
|
||||
function getVecCoord(stream): Vector {
|
||||
const hasX = !!stream.readBits(1);
|
||||
const hasY = !!stream.readBits(1);
|
||||
const hasZ = !!stream.readBits(1);
|
||||
return {
|
||||
x: hasX ? getCoord(stream) : 0,
|
||||
y: hasY ? getCoord(stream) : 0,
|
||||
z: hasZ ? getCoord(stream) : 0
|
||||
}
|
||||
};
|
||||
z: hasZ ? getCoord(stream) : 0,
|
||||
};
|
||||
}
|
||||
|
||||
export function BSPDecal(stream: BitStream): BSPDecalPacket { // 21: BSPDecal
|
||||
let modelIndex, entIndex;
|
||||
const position = getVecCoord(stream);
|
||||
let modelIndex = 0;
|
||||
let entIndex = 0;
|
||||
const position = getVecCoord(stream);
|
||||
const textureIndex = stream.readBits(9);
|
||||
if (stream.readBits(1)) {
|
||||
entIndex = stream.readBits(11);
|
||||
entIndex = stream.readBits(11);
|
||||
modelIndex = stream.readBits(12);
|
||||
}
|
||||
const lowPriority = stream.readBoolean();
|
||||
return {
|
||||
packetType: 'bspDecal',
|
||||
position: position,
|
||||
textureIndex: textureIndex,
|
||||
entIndex: entIndex,
|
||||
modelIndex: modelIndex,
|
||||
lowPriority: lowPriority
|
||||
}
|
||||
packetType: 'bspDecal',
|
||||
position,
|
||||
textureIndex,
|
||||
entIndex,
|
||||
modelIndex,
|
||||
lowPriority,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,26 @@
|
|||
import {ClassInfoPacket} from "../../Data/Packet";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {ClassInfoPacket} from '../../Data/Packet';
|
||||
import {logBase2} from '../../Math';
|
||||
|
||||
export function ClassInfo(stream: BitStream): ClassInfoPacket { // 10: classInfo
|
||||
const number = stream.readBits(16);
|
||||
const count = stream.readBits(16);
|
||||
const create = stream.readBoolean();
|
||||
let entries: any[] = [];
|
||||
const entries: any[] = [];
|
||||
if (!create) {
|
||||
const bits = logBase2(number) + 1;
|
||||
for (let i = 0; i < number; i++) {
|
||||
const bits = logBase2(count) + 1;
|
||||
for (let i = 0; i < count; i++) {
|
||||
const entry = {
|
||||
'classId': stream.readBits(bits),
|
||||
'className': stream.readASCIIString(),
|
||||
'dataTableName': stream.readASCIIString()
|
||||
classId: stream.readBits(bits),
|
||||
className: stream.readASCIIString(),
|
||||
dataTableName: stream.readASCIIString(),
|
||||
};
|
||||
entries.push(entry);
|
||||
}
|
||||
}
|
||||
return {
|
||||
'packetType': 'classInfo',
|
||||
number: number,
|
||||
create: create,
|
||||
entries: entries
|
||||
}
|
||||
packetType: 'classInfo',
|
||||
number: count,
|
||||
create,
|
||||
entries,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import {CmdKeyValuesPacket} from "../../Data/Packet";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {CmdKeyValuesPacket} from '../../Data/Packet';
|
||||
|
||||
export function CmdKeyValues(stream: BitStream): CmdKeyValuesPacket {
|
||||
//'length{32}data{$length}'
|
||||
// 'length{32}data{$length}'
|
||||
const length = stream.readUint32();
|
||||
const data = stream.readBitStream(length);
|
||||
return {
|
||||
packetType: 'cmdKeyValues',
|
||||
length: length,
|
||||
data: data
|
||||
length,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import {StringTablePacket} from "../../Data/Packet";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {logBase2} from "../../Math";
|
||||
import {readVarInt} from "../readBitVar";
|
||||
import {StringTablePacket} from '../../Data/Packet';
|
||||
import {logBase2} from '../../Math';
|
||||
import {readVarInt} from '../readBitVar';
|
||||
|
||||
import {uncompress} from "snappyjs";
|
||||
import {StringTable} from "../../Data/StringTable";
|
||||
import {parseStringTable} from "../StringTableParser";
|
||||
import {Match} from "../../Data/Match";
|
||||
import {uncompress} from 'snappyjs';
|
||||
import {Match} from '../../Data/Match';
|
||||
import {StringTable} from '../../Data/StringTable';
|
||||
import {parseStringTable} from '../StringTableParser';
|
||||
|
||||
export function CreateStringTable(stream: BitStream, match: Match): StringTablePacket { // 12: createStringTable
|
||||
const tableName = stream.readASCIIString();
|
||||
|
|
@ -38,12 +38,12 @@ export function CreateStringTable(stream: BitStream, match: Match): StringTableP
|
|||
const compressedData = data.readArrayBuffer(compressedByteSize - 4); // 4 magic bytes
|
||||
|
||||
if (magic !== 'SNAP') {
|
||||
throw new Error("Unknown compressed stringtable format");
|
||||
throw new Error('Unknown compressed stringtable format');
|
||||
}
|
||||
|
||||
const decompressedData = uncompress(compressedData);
|
||||
if (decompressedData.byteLength !== decompressedByteSize) {
|
||||
throw new Error("Incorrect length of decompressed stringtable");
|
||||
throw new Error('Incorrect length of decompressed stringtable');
|
||||
}
|
||||
|
||||
data = new BitStream(decompressedData.buffer);
|
||||
|
|
@ -52,15 +52,15 @@ export function CreateStringTable(stream: BitStream, match: Match): StringTableP
|
|||
const table: StringTable = {
|
||||
name: tableName,
|
||||
entries: [],
|
||||
maxEntries: maxEntries,
|
||||
maxEntries,
|
||||
fixedUserDataSize: userDataSize,
|
||||
fixedUserDataSizeBits: userDataSizeBits
|
||||
fixedUserDataSizeBits: userDataSizeBits,
|
||||
};
|
||||
|
||||
parseStringTable(data, table, entityCount, match);
|
||||
|
||||
return {
|
||||
packetType: 'stringTable',
|
||||
tables: [table]
|
||||
tables: [table],
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import {make} from './ParserGenerator';
|
||||
|
||||
import {EntityMessagePacket} from "../../Data/Packet";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {Match} from "../../Data/Match";
|
||||
import {Match} from '../../Data/Match';
|
||||
import {EntityMessagePacket} from '../../Data/Packet';
|
||||
|
||||
const baseParser = make('entityMessage', 'index{11}classId{9}length{11}data{$length}');
|
||||
|
||||
export function EntityMessage(stream: BitStream, match: Match): EntityMessagePacket { // 24: entityMessage
|
||||
const result = <EntityMessagePacket>baseParser(stream); //todo parse data further?
|
||||
return result;
|
||||
};
|
||||
return baseParser(stream) as EntityMessagePacket; // todo parse data further?
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,30 @@
|
|||
import {GameEventPacket} from "../../Data/Packet";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {
|
||||
GameEventType, GameEventValue, GameEventEntry, GameEventDefinition, GameEvent as IGameEvent,
|
||||
GameEventValueMap, GameEventDefinitionMap
|
||||
} from "../../Data/GameEvent";
|
||||
import {Match} from "../../Data/Match";
|
||||
GameEvent as IGameEvent, GameEventDefinition, GameEventDefinitionMap, GameEventEntry, GameEventType,
|
||||
GameEventValue, GameEventValueMap,
|
||||
} from '../../Data/GameEvent';
|
||||
import {Match} from '../../Data/Match';
|
||||
import {GameEventPacket} from '../../Data/Packet';
|
||||
|
||||
const parseGameEvent = function (eventId: number, stream: BitStream, events: GameEventDefinitionMap): IGameEvent {
|
||||
function parseGameEvent(eventId: number, stream: BitStream, events: GameEventDefinitionMap): IGameEvent {
|
||||
if (!events[eventId]) {
|
||||
throw new Error('unknown event type')
|
||||
throw new Error('unknown event type');
|
||||
}
|
||||
const eventDescription: GameEventDefinition = events[eventId];
|
||||
const values: GameEventValueMap = {};
|
||||
for (let i = 0; i < eventDescription.entries.length; i++) {
|
||||
const entry: GameEventEntry = eventDescription.entries[i];
|
||||
const value = getGameEventValue(stream, entry);
|
||||
const values: GameEventValueMap = {};
|
||||
for (const entry of eventDescription.entries) {
|
||||
const value = getGameEventValue(stream, entry);
|
||||
if (value) {
|
||||
values[entry.name] = value;
|
||||
}
|
||||
}
|
||||
return {
|
||||
name: eventDescription.name,
|
||||
values: values
|
||||
name: eventDescription.name,
|
||||
values,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const getGameEventValue = function (stream: BitStream, entry: GameEventEntry): GameEventValue|null {
|
||||
function getGameEventValue(stream: BitStream, entry: GameEventEntry): GameEventValue | null {
|
||||
switch (entry.type) {
|
||||
case GameEventType.STRING:
|
||||
return stream.readUTF8String();
|
||||
|
|
@ -44,17 +43,16 @@ const getGameEventValue = function (stream: BitStream, entry: GameEventEntry): G
|
|||
default:
|
||||
throw new Error('invalid game event type');
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
export function GameEvent(stream: BitStream, match: Match): GameEventPacket { // 25: game event
|
||||
const length = stream.readBits(11);
|
||||
const end = stream.index + length;
|
||||
const length = stream.readBits(11);
|
||||
const end = stream.index + length;
|
||||
const eventId = stream.readBits(9);
|
||||
const event = parseGameEvent(eventId, stream, match.eventDefinitions);
|
||||
const event = parseGameEvent(eventId, stream, match.eventDefinitions);
|
||||
stream.index = end;
|
||||
return {
|
||||
packetType: 'gameEvent',
|
||||
event: event
|
||||
}
|
||||
event,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import {GameEventListPacket} from "../../Data/Packet";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {GameEventEntry, GameEventDefinitionMap} from "../../Data/GameEvent";
|
||||
import {Match} from "../../Data/Match";
|
||||
import {GameEventDefinitionMap, GameEventEntry} from '../../Data/GameEvent';
|
||||
import {Match} from '../../Data/Match';
|
||||
import {GameEventListPacket} from '../../Data/Packet';
|
||||
|
||||
export function GameEventList(stream: BitStream, match: Match): GameEventListPacket { // 30: gameEventList
|
||||
// list of game events and parameters
|
||||
|
|
@ -15,19 +15,19 @@ export function GameEventList(stream: BitStream, match: Match): GameEventListPac
|
|||
const entries: GameEventEntry[] = [];
|
||||
while (type !== 0) {
|
||||
entries.push({
|
||||
type: type,
|
||||
name: stream.readASCIIString()
|
||||
type,
|
||||
name: stream.readASCIIString(),
|
||||
});
|
||||
type = stream.readBits(3);
|
||||
}
|
||||
eventList[id] = {
|
||||
id: id,
|
||||
name: name,
|
||||
entries: entries
|
||||
id,
|
||||
name,
|
||||
entries,
|
||||
};
|
||||
}
|
||||
return {
|
||||
packetType: 'gameEventList',
|
||||
eventList: eventList
|
||||
}
|
||||
eventList,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
import {MenuPacket} from "../../Data/Packet";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {MenuPacket} from '../../Data/Packet';
|
||||
|
||||
export function Menu(stream: BitStream): MenuPacket {
|
||||
//'type{16}length{16}_{$length}_{$length}_{$length}_{$length}_{$length}_{$length}_{$length}'
|
||||
const type = stream.readUint16();
|
||||
const length = stream.readUint16();
|
||||
const data = stream.readBitStream(length * 8); //length is in bytes
|
||||
const data = stream.readBitStream(length * 8); // length is in bytes
|
||||
return {
|
||||
packetType: 'menu',
|
||||
type: type,
|
||||
length: length,
|
||||
data: data
|
||||
type,
|
||||
length,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
import {BitStream} from 'bit-buffer';
|
||||
import {Match} from '../../Data/Match';
|
||||
import {PacketEntitiesPacket} from '../../Data/Packet';
|
||||
import {PacketEntity, PVS} from '../../Data/PacketEntity';
|
||||
import {SendProp} from '../../Data/SendProp';
|
||||
import {PacketEntitiesPacket} from "../../Data/Packet";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {Match} from "../../Data/Match";
|
||||
import {readUBitVar} from "../readBitVar";
|
||||
import {applyEntityUpdate} from "../EntityDecoder";
|
||||
import {applyEntityUpdate} from '../EntityDecoder';
|
||||
import {readUBitVar} from '../readBitVar';
|
||||
|
||||
const pvsMap = {
|
||||
0: PVS.PRESERVE,
|
||||
2: PVS.ENTER,
|
||||
1: PVS.LEAVE,
|
||||
3: PVS.LEAVE + PVS.DELETE
|
||||
3: PVS.LEAVE + PVS.DELETE,
|
||||
};
|
||||
|
||||
function readPVSType(stream: BitStream): PVS {
|
||||
|
|
@ -58,7 +58,7 @@ function getPacketEntityForExisting(entityId: number, match: Match, pvs: PVS) {
|
|||
return new PacketEntity(serverClass, entityId, pvs);
|
||||
}
|
||||
|
||||
export function PacketEntities(stream: BitStream, match: Match, skip: boolean = false): PacketEntitiesPacket { //26: packetEntities
|
||||
export function PacketEntities(stream: BitStream, match: Match, skip: boolean = false): PacketEntitiesPacket { // 26: packetEntities
|
||||
// https://github.com/skadistats/smoke/blob/master/smoke/replay/handler/svc_packetentities.pyx
|
||||
// https://github.com/StatsHelix/demoinfo/blob/3d28ea917c3d44d987b98bb8f976f1a3fcc19821/DemoInfo/DP/Handler/PacketEntitesHandler.cs
|
||||
// https://github.com/StatsHelix/demoinfo/blob/3d28ea917c3d44d987b98bb8f976f1a3fcc19821/DemoInfo/DP/Entity.cs
|
||||
|
|
@ -106,8 +106,7 @@ export function PacketEntities(stream: BitStream, match: Match, skip: boolean =
|
|||
|
||||
if (isDelta) {
|
||||
while (stream.readBoolean()) {
|
||||
const entityId = stream.readBits(11);
|
||||
removedEntityIds.push(entityId);
|
||||
removedEntityIds.push(stream.readBits(11));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -117,12 +116,12 @@ export function PacketEntities(stream: BitStream, match: Match, skip: boolean =
|
|||
packetType: 'packetEntities',
|
||||
entities: receivedEntities,
|
||||
removedEntities: removedEntityIds,
|
||||
maxEntries: maxEntries,
|
||||
isDelta: isDelta,
|
||||
delta: delta,
|
||||
baseLine: baseLine,
|
||||
updatedEntries: updatedEntries,
|
||||
length: length,
|
||||
updatedBaseLine: updatedBaseLine
|
||||
maxEntries,
|
||||
isDelta,
|
||||
delta,
|
||||
baseLine,
|
||||
updatedEntries,
|
||||
length,
|
||||
updatedBaseLine,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import {ParseSoundsPacket} from "../../Data/Packet";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {ParseSoundsPacket} from '../../Data/Packet';
|
||||
|
||||
export function ParseSounds(stream: BitStream): ParseSoundsPacket { // 17: parseSounds
|
||||
const reliable = stream.readBoolean();
|
||||
|
|
@ -8,8 +8,8 @@ export function ParseSounds(stream: BitStream): ParseSoundsPacket { // 17: parse
|
|||
stream.index += length;
|
||||
return {
|
||||
packetType: 'parseSounds',
|
||||
reliable: reliable,
|
||||
num: num,
|
||||
length: length
|
||||
}
|
||||
reliable,
|
||||
num,
|
||||
length,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import {Packet} from "../../Data/Packet";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {Match} from "../../Data/Match";
|
||||
import {Match} from '../../Data/Match';
|
||||
import {Packet} from '../../Data/Packet';
|
||||
|
||||
export type Parser = (stream: BitStream, match?: Match, skip?: boolean) => Packet;
|
||||
export type PacketParserMap = {[id: number]: Parser};
|
||||
|
||||
export interface PacketParserMap {
|
||||
[id: number]: Parser;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +1,30 @@
|
|||
import {Packet} from '../../Data/Packet';
|
||||
import {Parser} from './Parser';
|
||||
import {Packet} from "../../Data/Packet";
|
||||
|
||||
export function make(name: string, definition: string): Parser {
|
||||
const parts = definition.substr(0, definition.length - 1).split('}');//remove leading } to prevent empty part
|
||||
const items = parts.map(function (part) {
|
||||
const parts = definition.substr(0, definition.length - 1).split('}'); // remove leading } to prevent empty part
|
||||
const items = parts.map((part) => {
|
||||
return part.split('{');
|
||||
});
|
||||
return function (stream):Packet {
|
||||
let result = {
|
||||
'packetType': name
|
||||
return (stream) => {
|
||||
const result = {
|
||||
packetType: name,
|
||||
};
|
||||
try {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const value = readItem(stream, items[i][1], result);
|
||||
if (items[i][0] !== '_') {
|
||||
result[items[i][0]] = value;
|
||||
for (const group of items) {
|
||||
const value = readItem(stream, group[1], result);
|
||||
if (group[0] !== '_') {
|
||||
result[group[0]] = value;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error('Failed reading pattern ' + definition + '. ' + e);
|
||||
}
|
||||
return <Packet>result;
|
||||
}
|
||||
return result as Packet;
|
||||
};
|
||||
}
|
||||
|
||||
const readItem = function (stream, description, data) {
|
||||
function readItem(stream, description, data) {
|
||||
let length;
|
||||
if (description[0] === 'b') {
|
||||
return stream.readBoolean();
|
||||
|
|
@ -46,4 +46,4 @@ const readItem = function (stream, description, data) {
|
|||
} else {
|
||||
return stream.readBits(parseInt(description, 10), true);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import {SetConVarPacket} from "../../Data/Packet";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {SetConVarPacket} from '../../Data/Packet';
|
||||
|
||||
export function SetConVar(stream: BitStream): SetConVarPacket { // 5: setconvar
|
||||
const count = stream.readBits(8);
|
||||
let vars: {[key: string]: string} = {};
|
||||
const vars: {[key: string]: string} = {};
|
||||
for (let i = 0; i < count; i++) {
|
||||
vars[stream.readUTF8String()] = stream.readUTF8String();
|
||||
}
|
||||
return {
|
||||
packetType: 'setConVar',
|
||||
vars: vars
|
||||
}
|
||||
vars,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import {TempEntitiesPacket} from "../../Data/Packet";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {Match} from "../../Data/Match";
|
||||
import {PacketEntity, PVS} from "../../Data/PacketEntity";
|
||||
import {applyEntityUpdate} from "../EntityDecoder";
|
||||
import {Match} from '../../Data/Match';
|
||||
import {TempEntitiesPacket} from '../../Data/Packet';
|
||||
import {PacketEntity, PVS} from '../../Data/PacketEntity';
|
||||
import {applyEntityUpdate} from '../EntityDecoder';
|
||||
|
||||
export function TempEntities(stream: BitStream, match: Match, skip: boolean = false): TempEntitiesPacket { // 10: classInfo
|
||||
const entityCount = stream.readBits(8);
|
||||
|
|
@ -10,10 +10,10 @@ export function TempEntities(stream: BitStream, match: Match, skip: boolean = fa
|
|||
const end = stream.index + length;
|
||||
|
||||
let entity: PacketEntity|null = null;
|
||||
let entities: PacketEntity[] = [];
|
||||
const entities: PacketEntity[] = [];
|
||||
if (!skip) {
|
||||
for (let i = 0; i < entityCount; i++) {
|
||||
const delay = (stream.readBoolean()) ? stream.readUint8() / 100 : 0; //unused it seems
|
||||
const delay = (stream.readBoolean()) ? stream.readUint8() / 100 : 0; // unused it seems
|
||||
if (stream.readBoolean()) {
|
||||
const classId = stream.readBits(match.classBits);
|
||||
const serverClass = match.serverClasses[classId - 1];
|
||||
|
|
@ -28,20 +28,20 @@ export function TempEntities(stream: BitStream, match: Match, skip: boolean = fa
|
|||
if (entity) {
|
||||
applyEntityUpdate(entity, match.getSendTable(entity.serverClass.dataTable), stream);
|
||||
} else {
|
||||
throw new Error("no entity set to update");
|
||||
throw new Error('no entity set to update');
|
||||
}
|
||||
}
|
||||
}
|
||||
if (end - stream.index > 8) {
|
||||
throw new Error("unexpected content after TempEntities");
|
||||
throw new Error('unexpected content after TempEntities');
|
||||
}
|
||||
}
|
||||
|
||||
stream.index = end;
|
||||
return {
|
||||
packetType: 'tempEntities',
|
||||
entities: entities
|
||||
}
|
||||
entities,
|
||||
};
|
||||
}
|
||||
|
||||
function readVarInt(stream: BitStream) {
|
||||
|
|
@ -50,7 +50,7 @@ function readVarInt(stream: BitStream) {
|
|||
const byte = stream.readUint8();
|
||||
result |= ((byte & 0x7F) << run);
|
||||
|
||||
if ((byte >> 7) == 0) {
|
||||
if ((byte >> 7) === 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import {StringTablePacket} from "../../Data/Packet";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {Match} from "../../Data/Match";
|
||||
import {parseStringTable} from "../StringTableParser";
|
||||
import {Match} from '../../Data/Match';
|
||||
import {StringTablePacket} from '../../Data/Packet';
|
||||
import {parseStringTable} from '../StringTableParser';
|
||||
|
||||
export function UpdateStringTable(stream: BitStream, match: Match): StringTablePacket { // 12: updateStringTable
|
||||
const tableId = stream.readBits(5);
|
||||
|
|
@ -21,6 +21,6 @@ export function UpdateStringTable(stream: BitStream, match: Match): StringTableP
|
|||
|
||||
return {
|
||||
packetType: 'stringTable',
|
||||
tables: [table]
|
||||
tables: [table],
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import {UserMessagePacket} from "../../Data/Packet";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {make} from './ParserGenerator';
|
||||
import {UserMessagePacket} from '../../Data/Packet';
|
||||
import {SayText2} from '../UserMessage/SayText2';
|
||||
import {make} from './ParserGenerator';
|
||||
|
||||
enum UserMessageType{
|
||||
enum UserMessageType {
|
||||
Geiger = 0,
|
||||
Train = 1,
|
||||
HudText = 2,
|
||||
|
|
@ -61,12 +61,12 @@ enum UserMessageType{
|
|||
HapPunch = 54,
|
||||
HapSetDrag = 55,
|
||||
HapSet = 56,
|
||||
HapMeleeContact = 57
|
||||
HapMeleeContact = 57,
|
||||
}
|
||||
|
||||
const userMessageParsers = {
|
||||
4: SayText2,
|
||||
5: make('textMsg', 'destType{8}text{s}')
|
||||
5: make('textMsg', 'destType{8}text{s}'),
|
||||
};
|
||||
|
||||
export function UserMessage(stream: BitStream): UserMessagePacket { // 23: user message
|
||||
|
|
@ -79,7 +79,7 @@ export function UserMessage(stream: BitStream): UserMessagePacket { // 23: user
|
|||
} else {
|
||||
result = {
|
||||
packetType: 'unknownUserMessage',
|
||||
type: type
|
||||
type,
|
||||
};
|
||||
}
|
||||
stream.index = pos + length;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import {VoiceDataPacket} from "../../Data/Packet";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {VoiceDataPacket} from '../../Data/Packet';
|
||||
|
||||
export function VoiceData(stream: BitStream): VoiceDataPacket {
|
||||
// 'client{8}proximity{8}length{16}_{$length}'
|
||||
|
|
@ -9,9 +9,9 @@ export function VoiceData(stream: BitStream): VoiceDataPacket {
|
|||
const data = stream.readBitStream(length);
|
||||
return {
|
||||
packetType: 'voiceData',
|
||||
client: client,
|
||||
proximity: proximity,
|
||||
length: length,
|
||||
data: data
|
||||
client,
|
||||
proximity,
|
||||
length,
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import {VoiceInitPacket} from "../../Data/Packet";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {VoiceInitPacket} from '../../Data/Packet';
|
||||
|
||||
export function VoiceInit(stream: BitStream): VoiceInitPacket {
|
||||
const codec = stream.readASCIIString();
|
||||
|
|
@ -8,8 +8,8 @@ export function VoiceInit(stream: BitStream): VoiceInitPacket {
|
|||
const extraData = (codec === 'vaudio_celt' && quality === 255) ? stream.readUint16() : 0;
|
||||
return {
|
||||
packetType: 'voiceInit',
|
||||
codec: codec,
|
||||
quality: quality,
|
||||
extraData: extraData
|
||||
codec,
|
||||
quality,
|
||||
extraData,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import {SendPropDefinition, SendPropType, SendPropFlag} from '../Data/SendPropDefinition';
|
||||
import {Vector} from "../Data/Vector";
|
||||
import {BitStream} from "bit-buffer";
|
||||
import {SendPropValue, SendPropArrayValue} from "../Data/SendProp";
|
||||
import {readVarInt} from "./readBitVar";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {SendPropArrayValue, SendPropValue} from '../Data/SendProp';
|
||||
import {SendPropDefinition, SendPropFlag, SendPropType} from '../Data/SendPropDefinition';
|
||||
import {Vector} from '../Data/Vector';
|
||||
import {logBase2} from '../Math';
|
||||
import {readVarInt} from './readBitVar';
|
||||
|
||||
export class SendPropParser {
|
||||
static decode(propDefinition: SendPropDefinition, stream: BitStream): SendPropValue {
|
||||
public static decode(propDefinition: SendPropDefinition, stream: BitStream): SendPropValue {
|
||||
switch (propDefinition.type) {
|
||||
case SendPropType.DPT_Int:
|
||||
return SendPropParser.readInt(propDefinition, stream);
|
||||
|
|
@ -23,7 +24,7 @@ export class SendPropParser {
|
|||
throw new Error('Unknown property type');
|
||||
}
|
||||
|
||||
static readInt(propDefinition: SendPropDefinition, stream: BitStream) {
|
||||
public static readInt(propDefinition: SendPropDefinition, stream: BitStream) {
|
||||
if (propDefinition.hasFlag(SendPropFlag.SPROP_VARINT)) {
|
||||
return readVarInt(stream, !propDefinition.hasFlag(SendPropFlag.SPROP_UNSIGNED));
|
||||
} else {
|
||||
|
|
@ -31,13 +32,10 @@ export class SendPropParser {
|
|||
}
|
||||
}
|
||||
|
||||
static readArray(propDefinition: SendPropDefinition, stream: BitStream): SendPropArrayValue[] {
|
||||
let maxElements = propDefinition.numElements;
|
||||
let numBits = 1;
|
||||
while ((maxElements >>= 1) != 0)
|
||||
numBits++;
|
||||
public static readArray(propDefinition: SendPropDefinition, stream: BitStream): SendPropArrayValue[] {
|
||||
const numBits = logBase2(propDefinition.numElements) + 1;
|
||||
|
||||
const count = stream.readBits(numBits);
|
||||
const count = stream.readBits(numBits);
|
||||
const values: SendPropArrayValue[] = [];
|
||||
if (!propDefinition.arrayProperty) {
|
||||
throw new Error('Array of undefined type');
|
||||
|
|
@ -52,25 +50,25 @@ export class SendPropParser {
|
|||
return values;
|
||||
}
|
||||
|
||||
static readString(stream: BitStream): string {
|
||||
public static readString(stream: BitStream): string {
|
||||
const length = stream.readBits(9);
|
||||
return stream.readASCIIString(length);
|
||||
}
|
||||
|
||||
static readVector(propDefinition: SendPropDefinition, stream: BitStream): Vector {
|
||||
public static readVector(propDefinition: SendPropDefinition, stream: BitStream): Vector {
|
||||
const x = SendPropParser.readFloat(propDefinition, stream);
|
||||
const y = SendPropParser.readFloat(propDefinition, stream);
|
||||
const z = SendPropParser.readFloat(propDefinition, stream);
|
||||
return new Vector(x, y, z);
|
||||
}
|
||||
|
||||
static readVectorXY(propDefinition: SendPropDefinition, stream: BitStream): Vector {
|
||||
public static readVectorXY(propDefinition: SendPropDefinition, stream: BitStream): Vector {
|
||||
const x = SendPropParser.readFloat(propDefinition, stream);
|
||||
const y = SendPropParser.readFloat(propDefinition, stream);
|
||||
return new Vector(x, y, 0);
|
||||
}
|
||||
|
||||
static readFloat(propDefinition: SendPropDefinition, stream: BitStream): number {
|
||||
public static readFloat(propDefinition: SendPropDefinition, stream: BitStream): number {
|
||||
if (propDefinition.hasFlag(SendPropFlag.SPROP_COORD)) {
|
||||
return SendPropParser.readBitCoord(stream);
|
||||
} else if (propDefinition.hasFlag(SendPropFlag.SPROP_COORD_MP)) {
|
||||
|
|
@ -84,36 +82,36 @@ export class SendPropParser {
|
|||
} else if (propDefinition.hasFlag(SendPropFlag.SPROP_NORMAL)) {
|
||||
return SendPropParser.readBitNormal(stream);
|
||||
} else {
|
||||
const raw = stream.readBits(propDefinition.bitCount);
|
||||
const raw = stream.readBits(propDefinition.bitCount);
|
||||
const percentage = raw / ((1 << propDefinition.bitCount) - 1);
|
||||
return propDefinition.lowValue + (propDefinition.highValue - propDefinition.lowValue) * percentage;
|
||||
}
|
||||
}
|
||||
|
||||
static readBitNormal(stream: BitStream) {
|
||||
public static readBitNormal(stream: BitStream) {
|
||||
const isNegative = stream.readBoolean();
|
||||
const fractVal = stream.readBits(11);
|
||||
const value = fractVal * (1 / ((1 << 11) - 1));
|
||||
const fractVal = stream.readBits(11);
|
||||
const value = fractVal * (1 / ((1 << 11) - 1));
|
||||
return (isNegative) ? -value : value;
|
||||
}
|
||||
|
||||
static readBitCoord(stream: BitStream) {
|
||||
const hasIntVal = stream.readBoolean();
|
||||
public static readBitCoord(stream: BitStream) {
|
||||
const hasIntVal = stream.readBoolean();
|
||||
const hasFractVal = stream.readBoolean();
|
||||
|
||||
if (hasIntVal || hasFractVal) {
|
||||
const isNegative = stream.readBoolean();
|
||||
const intVal = (hasIntVal) ? stream.readBits(14) + 1 : 0;
|
||||
const fractVal = (hasFractVal) ? stream.readBits(5) : 0;
|
||||
const value = intVal + fractVal * (1 / (1 << 5));
|
||||
const intVal = (hasIntVal) ? stream.readBits(14) + 1 : 0;
|
||||
const fractVal = (hasFractVal) ? stream.readBits(5) : 0;
|
||||
const value = intVal + fractVal * (1 / (1 << 5));
|
||||
return (isNegative) ? -value : value;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static readBitCoordMP(propDefinition: SendPropDefinition, stream: BitStream, isIntegral: boolean, isLowPrecision: boolean): number {
|
||||
let value = 0;
|
||||
public static readBitCoordMP(propDefinition: SendPropDefinition, stream: BitStream, isIntegral: boolean, isLowPrecision: boolean): number {
|
||||
let value = 0;
|
||||
let isNegative = false;
|
||||
const inBounds = stream.readBoolean();
|
||||
|
||||
|
|
@ -127,7 +125,7 @@ export class SendPropParser {
|
|||
} else {
|
||||
value = stream.readBits(14) + 1;
|
||||
if (value < (1 << 11)) {
|
||||
throw new Error("Something's fishy...");
|
||||
throw new Error('Something\'s fishy...');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import {BitStream} from 'bit-buffer';
|
||||
import {StringTable, StringTableEntry} from "../Data/StringTable";
|
||||
import {logBase2} from "../Math";
|
||||
import {Match} from "../Data/Match";
|
||||
import {Match} from '../Data/Match';
|
||||
import {StringTable, StringTableEntry} from '../Data/StringTable';
|
||||
import {logBase2} from '../Math';
|
||||
|
||||
export function parseStringTable(stream: BitStream, table: StringTable, entries: number, match: Match) {
|
||||
const entryBits = logBase2(table.maxEntries);
|
||||
|
|
@ -19,7 +19,7 @@ export function parseStringTable(stream: BitStream, table: StringTable, entries:
|
|||
lastEntry = entryIndex;
|
||||
|
||||
if (entryIndex < 0 || entryIndex > table.maxEntries) {
|
||||
throw new Error("Invalid string index for stringtable");
|
||||
throw new Error('Invalid string index for stringtable');
|
||||
}
|
||||
|
||||
let value;
|
||||
|
|
@ -67,7 +67,7 @@ export function parseStringTable(stream: BitStream, table: StringTable, entries:
|
|||
} else {
|
||||
table.entries[entryIndex] = {
|
||||
text: value,
|
||||
extraData: userData
|
||||
extraData: userData,
|
||||
};
|
||||
history.push(table.entries[entryIndex]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,47 +1,51 @@
|
|||
import {SayText2Packet} from "../../Data/Packet";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {SayText2Packet} from '../../Data/Packet';
|
||||
|
||||
export function SayText2(stream: BitStream): SayText2Packet { // 4: SayText2
|
||||
var client = stream.readBits(8);
|
||||
var raw = stream.readBits(8);
|
||||
var pos = stream.index;
|
||||
var from, text, kind, arg1, arg2;
|
||||
const client = stream.readBits(8);
|
||||
const raw = stream.readBits(8);
|
||||
const pos = stream.index;
|
||||
let from;
|
||||
let text;
|
||||
let kind;
|
||||
if (stream.readBits(8) === 1) {
|
||||
var first = stream.readBits(8);
|
||||
const first = stream.readBits(8);
|
||||
if (first === 7) {
|
||||
var color = stream.readUTF8String(6);
|
||||
const color = stream.readUTF8String(6);
|
||||
} else {
|
||||
stream.index = pos + 8;
|
||||
}
|
||||
text = stream.readUTF8String();
|
||||
if (text.substr(0, 6) === '*DEAD*') {
|
||||
// grave talk is in the format '*DEAD* \u0003$from\u0001: $text'
|
||||
var start = text.indexOf('\u0003');
|
||||
var end = text.indexOf('\u0001');
|
||||
from = text.substr(start + 1, end - start - 1);
|
||||
text = text.substr(end + 5);
|
||||
kind = 'TF_Chat_AllDead';
|
||||
const start = text.indexOf('\u0003');
|
||||
const end = text.indexOf('\u0001');
|
||||
from = text.substr(start + 1, end - start - 1);
|
||||
text = text.substr(end + 5);
|
||||
kind = 'TF_Chat_AllDead';
|
||||
}
|
||||
} else {
|
||||
stream.index = pos;
|
||||
kind = stream.readUTF8String();
|
||||
from = stream.readUTF8String();
|
||||
text = stream.readUTF8String();
|
||||
kind = stream.readUTF8String();
|
||||
from = stream.readUTF8String();
|
||||
text = stream.readUTF8String();
|
||||
stream.readASCIIString();
|
||||
stream.readASCIIString();
|
||||
}
|
||||
// cleanup color codes
|
||||
text = text.replace(/\u0001/g, '');
|
||||
text = text.replace(/\u0003/g, '');
|
||||
while ((pos = text.indexOf('\u0007')) !== -1) {
|
||||
text = text.slice(0, pos) + text.slice(pos + 7);
|
||||
let stringPos = text.indexOf('\u0007');
|
||||
while (stringPos !== -1) {
|
||||
text = text.slice(0, stringPos) + text.slice(stringPos + 7);
|
||||
stringPos = text.indexOf('\u0007');
|
||||
}
|
||||
return {
|
||||
packetType: 'sayText2',
|
||||
client: client,
|
||||
raw: raw,
|
||||
kind: kind,
|
||||
from: from,
|
||||
text: text
|
||||
}
|
||||
};
|
||||
client,
|
||||
raw,
|
||||
kind,
|
||||
from,
|
||||
text,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import {BitStream} from "bit-buffer";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
export function readBitVar(stream: BitStream, signed?: boolean): number {
|
||||
const type = stream.readBits(2);
|
||||
switch (type) {
|
||||
|
|
@ -16,8 +16,7 @@ export function readBitVar(stream: BitStream, signed?: boolean): number {
|
|||
|
||||
export const readUBitVar = readBitVar;
|
||||
|
||||
|
||||
export function readVarInt(stream: BitStream, signed:boolean = false) {
|
||||
export function readVarInt(stream: BitStream, signed: boolean = false) {
|
||||
let result = 0;
|
||||
for (let i = 0; i < 35; i += 7) {
|
||||
const byte = stream.readBits(8);
|
||||
|
|
|
|||
14
src/StreamDemo.ts
Normal file
14
src/StreamDemo.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import {Stream} from 'stream';
|
||||
import {StreamParser} from './StreamParser';
|
||||
|
||||
export class StreamDemo {
|
||||
public stream: Stream;
|
||||
|
||||
constructor(nodeStream: Stream) {
|
||||
this.stream = nodeStream;
|
||||
}
|
||||
|
||||
public getParser() {
|
||||
return new StreamParser(this.stream);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
import {BitStream} from 'bit-buffer';
|
||||
import {Parser, MessageType} from './Parser';
|
||||
import {Stream} from "stream";
|
||||
import {Buffer} from 'buffer';
|
||||
import {Stream} from 'stream';
|
||||
import {MessageType, Parser} from './Parser';
|
||||
|
||||
export class StreamParser extends Parser {
|
||||
buffer: Buffer;
|
||||
header: any;
|
||||
sourceStream: Stream;
|
||||
public header: any;
|
||||
private buffer: Buffer;
|
||||
private sourceStream: Stream;
|
||||
|
||||
constructor(stream: Stream) {
|
||||
super(new BitStream(new ArrayBuffer(0)));
|
||||
|
|
@ -16,18 +16,18 @@ export class StreamParser extends Parser {
|
|||
this.buffer = new Buffer(0);
|
||||
}
|
||||
|
||||
eatBuffer(length) {
|
||||
this.buffer = shrinkBuffer(this.buffer, length);
|
||||
}
|
||||
|
||||
start() {
|
||||
public start() {
|
||||
this.sourceStream.on('data', this.handleData.bind(this));
|
||||
this.sourceStream.on('end', function () {
|
||||
this.sourceStream.on('end', function() {
|
||||
this.emit('done', this.match);
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
handleData(data) {
|
||||
private eatBuffer(length) {
|
||||
this.buffer = shrinkBuffer(this.buffer, length);
|
||||
}
|
||||
|
||||
private handleData(data) {
|
||||
this.buffer = Buffer.concat([this.buffer, data]);
|
||||
if (this.header === null) {
|
||||
if (this.buffer.length > 1072) {
|
||||
|
|
@ -39,14 +39,13 @@ export class StreamParser extends Parser {
|
|||
}
|
||||
}
|
||||
|
||||
readStreamMessage() {
|
||||
private readStreamMessage() {
|
||||
if (this.buffer.length < 9) { // 9 byte minimum message header (type, tick, length)
|
||||
return;
|
||||
}
|
||||
const stream = new BitStream(this.buffer);
|
||||
const type = stream.readBits(8);
|
||||
if (type === MessageType.Stop) {
|
||||
console.log('stop');
|
||||
return;
|
||||
}
|
||||
const tick = stream.readInt32();
|
||||
|
|
@ -72,11 +71,9 @@ export class StreamParser extends Parser {
|
|||
headerSize += extraHeader + 4;
|
||||
|
||||
if (this.buffer.length < (headerSize + length)) {
|
||||
console.log('wants ' + length);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('got message ' + tick);
|
||||
const messageStream = stream.readBitStream(length * 8);
|
||||
const message = this.parseMessage(messageStream, type, tick, length, this.match);
|
||||
this.handleMessage(message);
|
||||
|
|
@ -85,7 +82,7 @@ export class StreamParser extends Parser {
|
|||
|
||||
function shrinkBuffer(buffer, length) {
|
||||
if (length < 0) {
|
||||
throw 'cant shrink by negative length ' + length;
|
||||
throw new Error('cant shrink by negative length ' + length);
|
||||
}
|
||||
return buffer.slice(length, buffer.length);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import {readFileSync} from 'fs';
|
||||
import {Demo} from "../../Demo";
|
||||
import * as assert from 'assert';
|
||||
import {readFileSync} from 'fs';
|
||||
import {Demo} from '../../Demo';
|
||||
|
||||
function testDemo(name: string, fastMode: boolean = false) {
|
||||
const target = JSON.parse(readFileSync(`${__dirname}/../data/${name}.json`, 'utf8'));
|
||||
|
|
|
|||
46
tslint.json
Normal file
46
tslint.json
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"extends": "tslint:recommended",
|
||||
"rules": {
|
||||
"max-line-length": {
|
||||
"options": [
|
||||
140
|
||||
]
|
||||
},
|
||||
"object-literal-sort-keys": false,
|
||||
"interface-name": [
|
||||
true,
|
||||
"never-prefix"
|
||||
],
|
||||
"indent": [
|
||||
true,
|
||||
"tabs"
|
||||
],
|
||||
"quotemark": [
|
||||
true,
|
||||
"single",
|
||||
"jsx-double"
|
||||
],
|
||||
"new-parens": true,
|
||||
"no-arg": true,
|
||||
"no-bitwise": false,
|
||||
"no-conditional-assignment": true,
|
||||
"no-consecutive-blank-lines": true,
|
||||
"no-console": {
|
||||
"options": [
|
||||
"debug",
|
||||
"info",
|
||||
"log",
|
||||
"time",
|
||||
"timeEnd",
|
||||
"trace"
|
||||
]
|
||||
}
|
||||
},
|
||||
"jsRules": {
|
||||
"max-line-length": {
|
||||
"options": [
|
||||
140
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue